Add new tests and functionality for image processing and geometry handling
- Introduced color_image.py for preparing standalone color images for tests. - Added hdr_image.py to create HDR textures for testing. - Implemented test_image_depth_panorama.py to evaluate panorama topology, UVs, and depth discontinuities. - Created test_shadeless.py to validate the opacity and HDR color endpoints of the Shadeless group. - Developed moge3_export.py for exporting dynamic MoGe-3 backbone and ONNX graphs. - Added material.py to inherit material slots onto generated geometry. - Built image_depth_panorama.py to construct a quad panorama with depth separation and sampling.
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from ..geometry.prediction_artifacts import generate_moge2_artifacts
|
||||
from ..geometry.prediction_artifacts import generate_moge_artifacts
|
||||
|
||||
|
||||
def run(context, parameters):
|
||||
@@ -13,7 +13,7 @@ def run(context, parameters):
|
||||
generate_depth = bool(parameters.get("generate_depth", False))
|
||||
normal_mode = str(parameters.get("normal_mode", "NONE"))
|
||||
if generate_depth or normal_mode != "NONE":
|
||||
artifacts = generate_moge2_artifacts(
|
||||
artifacts = generate_moge_artifacts(
|
||||
context,
|
||||
parameters,
|
||||
input_path,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Generate model-space depth and preserve color for one complete image."""
|
||||
"""Generate model-space depth and normals for one complete image."""
|
||||
|
||||
from ..geometry.prediction_artifacts import generate_moge2_artifacts
|
||||
from ..media import copy_color_images, input_frames
|
||||
from ..geometry.prediction_artifacts import generate_moge_artifacts
|
||||
from ..media import input_frames
|
||||
|
||||
|
||||
def run(context, parameters):
|
||||
@@ -13,8 +13,7 @@ def run(context, parameters):
|
||||
with input_frames(context, parameters) as images:
|
||||
if len(images) != 1:
|
||||
raise ValueError("Depth conversion currently supports one image")
|
||||
copy_color_images(images, context.directory)
|
||||
artifacts = generate_moge2_artifacts(
|
||||
artifacts = generate_moge_artifacts(
|
||||
context,
|
||||
parameters,
|
||||
images[0],
|
||||
|
||||
82
src/anyimage/server/jobs/panorama.py
Normal file
82
src/anyimage/server/jobs/panorama.py
Normal file
@@ -0,0 +1,82 @@
|
||||
"""Generate a radial depth texture from the current RGBA panorama."""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from ..geometry.depth_texture import write_float_exr
|
||||
from ..geometry.panorama import camera_bases, merge_distances, sample_panorama, view_rays
|
||||
from ..media import VIDEO_SUFFIXES, input_frames
|
||||
from ..media.input import open_image
|
||||
from ..media.resolution import DEFAULT_MAX_AI_INPUT_SIZE, normalized_max_input_size
|
||||
from ..models.moge import infer_image as infer
|
||||
|
||||
|
||||
def run(context, parameters):
|
||||
import numpy as np
|
||||
|
||||
context.check_cancelled()
|
||||
input_path = Path(parameters["input"])
|
||||
if input_path.is_dir() or input_path.suffix.lower() in VIDEO_SUFFIXES:
|
||||
raise ValueError("Panorama conversion requires one static 2:1 image")
|
||||
maximum = normalized_max_input_size(parameters.get("max_input_size", DEFAULT_MAX_AI_INPUT_SIZE))
|
||||
context.directory.mkdir(parents=True, exist_ok=True)
|
||||
outputs = [context.directory / name for name in ("depth.exr", "depth.json")]
|
||||
try:
|
||||
with input_frames(context, parameters) as images:
|
||||
if len(images) != 1:
|
||||
raise ValueError("Panorama conversion requires one static 2:1 image")
|
||||
with open_image(images[0]) as opened:
|
||||
if getattr(opened, "n_frames", 1) != 1 or opened.width != opened.height * 2:
|
||||
raise ValueError("Panorama conversion requires one static 2:1 image")
|
||||
source = opened.convert("RGBA")
|
||||
width, height = source.size
|
||||
size = min(maximum, max(512, width // 4))
|
||||
image = np.asarray(source, dtype=np.float32) / 255.0
|
||||
manager = context.resource("model_manager")
|
||||
bases = camera_bases()
|
||||
rays = view_rays(size)
|
||||
distances, masks = [], []
|
||||
session = None
|
||||
for index, basis in enumerate(bases):
|
||||
context.check_cancelled()
|
||||
context.progress(0.05 + 0.65 * index / len(bases), f"Generating panorama view {index + 1}/12")
|
||||
directions = rays @ basis
|
||||
alpha = sample_panorama(image[..., 3], directions, order=0)
|
||||
visible = alpha > 0
|
||||
if not visible.any():
|
||||
distances.append(np.ones((size, size), dtype=np.float32))
|
||||
masks.append(visible)
|
||||
continue
|
||||
if session is None:
|
||||
session = manager.get_moge(manager.directory(parameters["model"]), parameters["device"])
|
||||
color = sample_panorama(image[..., :3], directions) * alpha[..., None]
|
||||
prediction = infer(session, color, parameters["resolution_level"],
|
||||
fov_x=90.0, source_valid=visible)
|
||||
distances.append(np.linalg.norm(prediction["points"], axis=-1))
|
||||
masks.append((prediction["mask"] > 0.5) & visible)
|
||||
del prediction
|
||||
context.progress(0.75, "Merging panoramic distance")
|
||||
distance, valid = merge_distances(distances, masks, bases, width, height,
|
||||
cancel_check=context.check_cancelled)
|
||||
alpha = np.asarray(source.getchannel("A"),
|
||||
dtype=np.float32) / 255.0
|
||||
alpha *= valid
|
||||
reference = (alpha >= 0.9) & np.isfinite(distance) & (distance > 0)
|
||||
if not reference.any():
|
||||
raise ValueError("The panorama contains no valid depth pixels")
|
||||
if not np.isfinite(distance).all() or (distance <= 0).any():
|
||||
raise ValueError("Panorama fusion produced invalid distances")
|
||||
rgba = np.empty((height, width, 4), dtype=np.float32)
|
||||
rgba[..., :3], rgba[..., 3] = distance[..., None], alpha
|
||||
write_float_exr(rgba, outputs[0])
|
||||
outputs[1].write_text(json.dumps({
|
||||
"projection": "equirectangular", "image_size": [width, height],
|
||||
"reference_distance": float(np.median(distance[reference])),
|
||||
}, separators=(",", ":")), encoding="utf-8")
|
||||
context.check_cancelled()
|
||||
context.progress(1.0, "Panorama conversion data is ready")
|
||||
return {"depth": outputs[0].name, "depth_metadata": outputs[1].name}
|
||||
except BaseException:
|
||||
for path in outputs:
|
||||
path.unlink(missing_ok=True)
|
||||
raise
|
||||
@@ -1,7 +1,8 @@
|
||||
import time
|
||||
|
||||
from ..media import input_frames
|
||||
from ..models.ben2 import infer_foreground, infer_foreground_frames
|
||||
from ..media.input import open_image
|
||||
from ..models.ben2 import infer_alpha, infer_foreground, infer_foreground_frames
|
||||
|
||||
|
||||
def run(context, parameters):
|
||||
@@ -15,10 +16,25 @@ def run(context, parameters):
|
||||
device = parameters["device"]
|
||||
model_manager = context.resource("model_manager")
|
||||
model_directory = model_manager.directory(parameters["model"])
|
||||
output_kind = parameters.get("output_kind", "foreground")
|
||||
if output_kind not in {"foreground", "alpha"}:
|
||||
raise ValueError(f"Unknown background removal output: {output_kind}")
|
||||
|
||||
with input_frames(context, parameters) as images:
|
||||
context.progress(0.12, f"Loading BEN2 for {device.upper()}")
|
||||
if len(images) == 1:
|
||||
if output_kind == "alpha":
|
||||
import numpy as np
|
||||
|
||||
if len(images) != 1:
|
||||
raise ValueError("Alpha output requires one still image")
|
||||
with open_image(images[0]) as source:
|
||||
alpha, load_ms, inference_ms = infer_alpha(
|
||||
source, model_directory, device, model_cache=model_manager,
|
||||
cancel_check=context.check_cancelled,
|
||||
)
|
||||
context.check_cancelled()
|
||||
np.save(output_directory / "alpha.npy", alpha, allow_pickle=False)
|
||||
elif len(images) == 1:
|
||||
context.progress(0.35, "Removing background")
|
||||
foreground, load_ms, inference_ms = infer_foreground(
|
||||
images[0],
|
||||
@@ -62,4 +78,4 @@ def run(context, parameters):
|
||||
)
|
||||
context.log(message)
|
||||
context.progress(1.0, message)
|
||||
return {"image": "."}
|
||||
return {"alpha": "alpha.npy"} if output_kind == "alpha" else {"image": "."}
|
||||
|
||||
Reference in New Issue
Block a user