Refactor depth reference handling and cleanup

- Unified reference depth calculation to use a single implementation in common/depth.py.
- Removed server-side reference depth computation and adjusted depth.json to only include image_size and intrinsics.
- Updated depth texture handling to ignore transparent, invalid, and non-positive pixels when calculating reference depth.
- Changed reference depth calculation to use the 95th percentile of valid depth values.
- Adjusted depth direction fitting to return a flat direction when no samples are available.
- Removed cleanup threshold parameters from Cutout and related nodes, simplifying the interface.
- Updated tests to verify new reference depth logic and ensure compatibility with existing functionality.
This commit is contained in:
2026-09-15 22:50:16 +08:00
parent 94130ff38f
commit 2dfa9b2add
37 changed files with 665 additions and 639 deletions

View File

@@ -0,0 +1,190 @@
"""Verify shared Depth calibration and metadata from Depth artifacts."""
import json
from types import SimpleNamespace
import numpy as np
import pytest
from anyimage.common.coordinate import canonical_direction_to_symmetry_legacy
from anyimage.common.depth import (
FLAT_DEPTH_DIRECTION,
REFERENCE_DEPTH_LIMIT,
depth_uniform_scale,
fit_depth_direction,
fit_symmetry_depth_direction,
load_depth_metadata,
reference_depth,
)
from tests.support.image_objects import _depth_image
def _camera_depth_image(depth, alpha=None):
depth = np.asarray(depth, dtype=np.float32)
points = np.zeros((*depth.shape, 3), dtype=np.float32)
points[..., 2] = depth
if alpha is None:
alpha = np.ones(depth.shape, dtype=np.float32)
return _depth_image(points, alpha)
def test_depth_metadata_loader_reads_only_size_and_intrinsics(tmp_path):
path = tmp_path / "depth.json"
path.write_text(
json.dumps(
{
"image_size": [3, 2],
"intrinsics": np.eye(3).tolist(),
}
),
encoding="utf-8",
)
metadata = load_depth_metadata(path)
assert metadata == {
"image_size": (3, 2),
"intrinsics": ((1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 1.0)),
}
def test_base_plane_fit_is_stable_for_a_slender_diagonal_domain():
along = np.linspace(-1.0, 1.0, 101)
across = np.resize(np.asarray((-0.01, 0.0, 0.01)), len(along))
coordinates = np.column_stack((along - across, along + across))
values = 0.4 * along + np.sign(across) * 0.05
direction = fit_depth_direction(
coordinates,
values + 2.0,
np.ones(len(values), dtype=bool),
1.0,
)
slopes = np.asarray(direction[:2]) / direction[2]
selected_plane = coordinates @ slopes
corners = np.asarray(((-1.0, -1.0), (-1.0, 1.0), (1.0, -1.0), (1.0, 1.0)))
corner_plane = corners @ slopes
assert np.ptp(corner_plane) <= np.ptp(selected_plane) * 1.25
assert np.corrcoef(selected_plane, along)[0, 1] > 0.99
def test_reference_depth_uses_the_upper_depth_percentile():
depth = np.linspace(1.0, 2.0, 400, dtype=np.float32).reshape(20, 20)
reference = reference_depth(_camera_depth_image(depth))
assert reference == pytest.approx(float(np.percentile(depth, 95.0)))
assert reference > float(np.median(depth))
def test_reference_depth_ignores_transparent_invalid_and_non_positive_pixels():
depth = np.asarray(
((1.0, 2.0, 3.0, 4.0), (5.0, 0.0, np.nan, 6.0)),
dtype=np.float32,
)
alpha = np.asarray(
((True, True, True, False), (True, True, True, True)),
)
reference = reference_depth(_camera_depth_image(depth, alpha))
assert reference == pytest.approx(
float(np.percentile(np.asarray((1.0, 2.0, 3.0, 5.0, 6.0)), 95.0))
)
def test_reference_depth_narrows_its_domain_to_the_mask():
depth = np.asarray(((4.0, 5.0, 1.0, 2.0),), dtype=np.float32)
image = _camera_depth_image(depth)
mask = np.asarray(((False, False, True, True),))
assert reference_depth(image) == pytest.approx(
float(np.percentile(depth, 95.0))
)
assert reference_depth(image, mask) == pytest.approx(
float(np.percentile(np.asarray((1.0, 2.0)), 95.0))
)
def test_reference_depth_ignores_soft_depth_alpha():
depth = np.asarray(((1.0, 2.0, 3.0, 4.0),), dtype=np.float32)
image = _camera_depth_image(depth, ((1.0, 1.0, 0.9, 0.6),))
assert reference_depth(image) == pytest.approx(
float(np.percentile(np.asarray((1.0, 2.0)), 95.0))
)
def test_reference_depth_ignores_samples_beyond_the_metric_limit():
depth = np.asarray(((2.0, 4.0, 6.0, 1000.0),), dtype=np.float32)
reference = reference_depth(_camera_depth_image(depth))
assert reference == pytest.approx(
float(np.percentile(np.asarray((2.0, 4.0, 6.0)), 95.0))
)
def test_reference_depth_falls_back_to_the_limit_without_a_selection():
transparent = _camera_depth_image(
np.zeros((2, 2), dtype=np.float32),
np.zeros((2, 2), dtype=np.float32),
)
far = _camera_depth_image(
np.asarray(((1000.0, 2000.0), (4000.0, 8000.0)), dtype=np.float32),
)
assert reference_depth(transparent) == pytest.approx(REFERENCE_DEPTH_LIMIT)
assert reference_depth(far) == pytest.approx(REFERENCE_DEPTH_LIMIT)
def test_symmetry_depth_direction_fits_a_sloped_base_plane():
vertices = []
uv = []
camera_depth = np.empty((3, 3), dtype=np.float32)
for row in range(3):
for column in range(3):
local_x, local_y = column - 1.0, 1.0 - row
vertices.append((local_x, 0.0, local_y))
uv.append((column * 0.5, 1.0 - row * 0.5))
camera_depth[row, column] = 2.0 + 0.2 * local_x + 0.1 * local_y
direction = fit_symmetry_depth_direction(
_camera_depth_image(camera_depth),
vertices,
uv,
0.5,
)
expected = np.asarray((0.1, 1.0, 0.05), dtype=np.float64)
expected /= np.linalg.norm(expected)
assert np.allclose(direction, expected, atol=1e-6)
def test_symmetry_depth_direction_returns_the_flat_direction_without_samples():
vertices = ((0.0, 0.0, 0.0), (1.0, 0.0, 0.0), (0.0, 0.0, 1.0))
uv = ((0.0, 0.0), (1.0, 0.0), (0.0, 1.0))
depth = np.ones((2, 2), dtype=np.float32)
image = _camera_depth_image(depth, np.zeros((2, 2), dtype=bool))
direction = fit_symmetry_depth_direction(image, vertices, uv, 1.0)
assert direction == FLAT_DEPTH_DIRECTION
assert canonical_direction_to_symmetry_legacy(direction) == (0.0, 0.0, 1.0)
def test_depth_uniform_scale_converts_model_units_at_the_reference_depth():
metadata = {
"intrinsics": ((100.0, 0.0, 50.0), (0.0, 100.0, 25.0), (0.0, 0.0, 1.0)),
"image_size": (101, 51),
}
uniform_scale = depth_uniform_scale(
SimpleNamespace(),
metadata,
2.0,
bounds=(-2.0, 2.0, -1.0, 1.0),
)
assert uniform_scale == pytest.approx(2.0)

View File

@@ -1,25 +0,0 @@
import json
import numpy as np
from anyimage.common.depth import load_depth_metadata
def test_depth_metadata_loader_uses_one_schema_for_plane_and_cutout(tmp_path):
path = tmp_path / "depth.json"
path.write_text(
json.dumps(
{
"image_size": [3, 2],
"intrinsics": np.eye(3).tolist(),
"reference_depth": 1.0,
}
),
encoding="utf-8",
)
metadata = load_depth_metadata(path)
assert metadata["image_size"] == (3, 2)
assert metadata["intrinsics"] == ((1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 1.0))
assert metadata["reference_depth"] == 1.0

View File

@@ -48,12 +48,13 @@ def run_conversion(source, entry, monkeypatch):
if entry == "PLANE":
operators.ConvertToPlane.execute(SimpleNamespace(mesh_detail=0), bpy.context)
else:
metadata = {"image_size": (2, 1), "reference_depth": 1,
metadata = {"image_size": (2, 1),
"intrinsics": ((2, 0, 1), (0, 2, 0.5), (0, 0, 1))}
monkeypatch.setattr(conversion, "load_depth_metadata", lambda _: metadata)
def load_depth(*_):
image = bpy.data.images.new("Depth", width=2, height=1, float_buffer=True)
image.colorspace_settings.name = "Non-Color"
image.pixels.foreach_set(np.ones(8, dtype=np.float32))
return image
monkeypatch.setattr(conversion, "load_depth_result_image", load_depth)
monkeypatch.setattr(conversion, "load_normal_result_image", load_depth)

View File

@@ -128,8 +128,8 @@ def test_client_processing_updates_and_packs_color_and_depth_together():
depth = Image(depth_values)
metadata = {
"intrinsics": ((8.0, 0.0, 8.0), (0.0, 8.0, 8.0), (0.0, 0.0, 1.0)),
"reference_depth": 12.0,
}
original_metadata = dict(metadata)
pad_cutout_images(color, depth, metadata, content, 0.9, 2)
@@ -139,5 +139,5 @@ def test_client_processing_updates_and_packs_color_and_depth_together():
assert padded_color[2, 8, 3] == 1
assert padded_depth[2, 8, 2] > depth_values[3, 8, 2]
assert padded_depth[2, 8, 0] == 0
assert metadata["reference_depth"] == 12
assert metadata == original_metadata
assert (color.updated, color.packed, depth.updated, depth.packed) == (1, 1, 1, 1)

View File

@@ -1,159 +0,0 @@
from types import SimpleNamespace
import numpy as np
from anyimage.common.selection import SelectionMask
from anyimage.common.depth import depth_uniform_scale, fit_depth_direction
from anyimage.operators.cutout_tool import geometry as cutout_geometry
from anyimage.operators.cutout_tool import depth_calibration
def test_base_plane_fit_is_stable_for_a_slender_diagonal_domain():
along = np.linspace(-1.0, 1.0, 101)
across = np.resize(np.asarray((-0.01, 0.0, 0.01)), len(along))
coordinates = np.column_stack(
(
along - across,
along + across,
)
)
values = 0.4 * along + np.sign(across) * 0.05
direction = fit_depth_direction(
coordinates,
values + 2.0,
np.ones(len(values), dtype=bool),
1.0,
)
slopes = np.asarray(direction[:2]) / direction[2]
selected_plane = coordinates @ slopes
corners = np.asarray(((-1.0, -1.0), (-1.0, 1.0), (1.0, -1.0), (1.0, 1.0)))
corner_plane = corners @ slopes
assert np.ptp(corner_plane) <= np.ptp(selected_plane) * 1.25
assert np.corrcoef(selected_plane, along)[0, 1] > 0.99
def test_depth_symmetry_plane_calibration_matches_cutout_axes():
vertices = []
uv = []
camera_depth = np.empty((3, 3), dtype=np.float32)
for row in range(3):
local_y = 1.0 - row
for column in range(3):
local_x = column - 1.0
vertices.append((local_x, 0.0, local_y))
uv.append((column * 0.5, 1.0 - row * 0.5))
camera_depth[row, column] = 2.0 + 0.2 * local_x + 0.1 * local_y
reference, direction = depth_calibration.depth_symmetry_projection_parameters(
vertices,
uv,
camera_depth,
np.ones((3, 3), dtype=bool),
0.5,
)
expected_direction = np.asarray((-0.1, -0.05, 1.0), dtype=np.float64)
expected_direction /= np.linalg.norm(expected_direction)
assert np.isclose(
reference,
np.percentile(camera_depth, 95.0) * 0.5,
atol=1e-6,
)
assert np.allclose(direction, expected_direction, atol=1e-6)
def test_depth_symmetry_reference_uses_the_shared_upper_depth_percentile():
size = 10
row, column = np.indices((size, size))
camera_depth = np.full((size, size), 2.0, dtype=np.float32)
camera_depth[(row + column) % 2 == 0] = 2.2
vertices = []
uv = []
for y in range(size):
for x in range(size):
vertices.append((float(x), 0.0, float(size - 1 - y)))
uv.append((x / (size - 1), 1.0 - y / (size - 1)))
reference, direction = depth_calibration.depth_symmetry_projection_parameters(
vertices,
uv,
camera_depth,
np.ones((size, size), dtype=bool),
1.0,
)
assert reference > float(np.median(camera_depth))
assert np.isclose(reference, 2.2, atol=1e-6)
assert np.allclose(direction, (0.0, 0.0, 1.0), atol=1e-6)
def test_depth_surface_projection_uses_one_scale_for_all_camera_axes():
points = np.zeros((51, 101, 3), dtype=np.float32)
points[..., 2] = np.linspace(1.0, 3.0, 51 * 101).reshape(51, 101)
metadata = {
"intrinsics": (
(100.0, 0.0, 50.0),
(0.0, 100.0, 25.0),
(0.0, 0.0, 1.0),
),
"image_size": (101, 51),
"reference_depth": 2.0,
}
uniform_scale = depth_uniform_scale(
SimpleNamespace(),
metadata,
bounds=(-2.0, 2.0, -1.0, 1.0),
)
assert uniform_scale == 2.0
reference_depth = depth_calibration.metric_depth_reference(
points[..., 2],
np.ones((51, 101), dtype=bool),
uniform_scale,
)
assert np.isclose(reference_depth, 5.8, atol=1e-6)
def test_cutout_threshold_controls_contour_and_calibration():
alpha = np.zeros((12, 16), dtype=np.float32)
alpha[2:10, 2:14] = 0.2
alpha[4:8, 5:11] = 1.0
for threshold, lower, upper in [
(0.0, (2, 2), (14, 10)),
(0.1, (2, 2), (14, 10)),
(0.5, (5, 4), (11, 8)),
(1.0, (5, 4), (11, 8)),
]:
contours = cutout_geometry.extract_cutout_contours(
alpha, 64, threshold=threshold
)
contour = np.asarray(contours.components[0][0])
np.testing.assert_allclose(contour.min(axis=0), lower)
np.testing.assert_allclose(contour.max(axis=0), upper)
valid, _ = cutout_geometry.selection_calibration(
alpha, alpha.shape, threshold=threshold
)
np.testing.assert_array_equal(valid, (alpha > 0) & (alpha >= threshold))
def test_cutout_depth_calibration_uses_five_percent_transparency_threshold():
assert not depth_calibration.use_adaptive_depth_calibration(0.0499)
assert depth_calibration.use_adaptive_depth_calibration(0.05)
def test_selection_calibration_uses_the_final_selection_mask():
selection_mask = SelectionMask(
np.asarray(((1.0, 1.0), (1.0, 0.0)), dtype=np.float32),
(0, 0, 2, 2),
)
valid, transparent_fraction = cutout_geometry.selection_calibration(
selection_mask.values,
(2, 2),
)
assert np.array_equal(valid, ((True, True), (True, False)))
assert transparent_fraction == 0.25

View File

@@ -118,25 +118,30 @@ def test_operator_length_property_accepts_world_units():
bpy.utils.unregister_class(CutoutLengthTestOperator)
def test_effective_length_report_and_geometry_share_value():
def test_effective_length_report_and_geometry_build_share_value():
report = Mock()
context = SimpleNamespace(scene=SimpleNamespace(unit_settings=SimpleNamespace(system="METRIC", scale_length=0.01)))
source = object()
content = np.ones((10, 10))
with patch.object(operators, "effective_cutout_edge_length", return_value=20), \
patch.object(operators, "build_base_shape") as build, \
patch.object(operators, "create_shape_object") as create, \
patch.object(operators.bpy.ops.ed, "undo_push", return_value={"FINISHED"}):
operators.create_cutout_shape(
context,
object(),
source,
"FLAT",
10,
np.ones((10, 10)),
content,
(0, 0, 10, 10),
boundary_padding=0.0,
color_image=object(),
report=report,
)
assert build.call_args.args[2] == 20
assert create.call_args.args[3] == 20
assert create.call_args.args[0] is context
assert create.call_args.args[1] is source
assert create.call_args.args[2] == "FLAT"
assert create.call_args.args[3] is content
assert report.call_args.args[0] == {"INFO"}
assert "20 cm" in report.call_args.args[1]

View File

@@ -115,6 +115,16 @@ def test_depth_surface_cutout_uses_default_alpha_boundary():
np.testing.assert_allclose(contour.max(axis=0), (11.0, 8.0))
def test_cutout_reference_mask_resamples_content_to_depth_resolution():
content = np.zeros((4, 4), dtype=np.float32)
content[:, 2:] = 1.0
mask = cutout_geometry.build_reference_mask(content, (2, 2))
assert mask.dtype == np.bool_
np.testing.assert_array_equal(mask, ((False, True), (False, True)))
def test_blender_cutout_alpha_pipeline_does_not_import_pillow():
original_import = builtins.__import__
@@ -154,23 +164,6 @@ def test_blender_cutout_contour_resolution_tracks_sampling_resolution():
assert trace.call_args_list[1].kwargs["max_resolution"] == 1024
def test_cutout_cleanup_value_scales_the_ui_without_changing_node_area():
assert np.isclose(
cutout_geometry.cutout_cleanup_value(
0.25,
((1.0, 0.0), (0.0, 1.0)),
),
6.25,
)
assert np.isclose(
cutout_geometry.cutout_cleanup_value(
0.25,
((4.0, 0.0), (0.0, 9.0)),
),
6.25 / 6.0,
)
def test_cutout_domain_adapts_boundary_spacing_for_a_small_valid_selection():
components = ((((2046, 2046), (2050, 2046), (2050, 2050), (2046, 2050)),),)

View File

@@ -26,7 +26,7 @@ from anyimage.server.models.geometry import GeometryFrame
from tests.support.image_objects import _add_cutout_modifier, _create_cutout_shape, _depth_image, _depth_metadata, _evaluated_positions, _group_node
def test_depth_result_image_loads_server_vector_exr_and_keeps_data_name(tmp_path):
def test_depth_result_image_loads_camera_exr_and_keeps_data_name(tmp_path):
bpy.ops.wm.read_factory_settings(use_empty=True)
source_image = bpy.data.images.new("Source.png", width=2, height=2)
source = bpy.data.objects.new("Source", None)
@@ -42,7 +42,7 @@ def test_depth_result_image_loads_server_vector_exr_and_keeps_data_name(tmp_path
path = tmp_path / "depth.exr"
alpha = np.asarray(((0.8, 1.0), (0.75, 0.0)), dtype=np.float32)
write_depth_texture(frame, path, alpha=alpha)
metadata = _depth_metadata(np.eye(3), (2, 2), 1.0)
metadata = _depth_metadata(np.eye(3), (2, 2))
image = depth_data.load_depth_result_image(path, source, metadata)
@@ -53,7 +53,10 @@ def test_depth_result_image_loads_server_vector_exr_and_keeps_data_name(tmp_path
assert np.array_equal(packed[..., 3], alpha * validity)
camera_depth, valid = depth_data.camera_depth_values(image)
np.testing.assert_array_equal(camera_depth, points[..., 2])
np.testing.assert_array_equal(valid, alpha * validity > 0.5)
np.testing.assert_array_equal(
valid,
alpha * validity > depth_data.DEPTH_ALPHA_THRESHOLD,
)
assert image.alpha_mode == "CHANNEL_PACKED"
assert image.name == "Source_depth.exr"
@@ -75,7 +78,7 @@ def test_depth_texture_keeps_points_outside_positive_validity(tmp_path):
image = depth_data.load_depth_result_image(
path,
source,
_depth_metadata(np.eye(3), (2, 1), 2.0),
_depth_metadata(np.eye(3), (2, 1)),
)
packed = np.empty(8, dtype=np.float32)
@@ -141,7 +144,7 @@ def test_depth_surface_object_evaluates(gesture):
points[..., 2] = depth
valid = np.ones((size, size), dtype=bool)
depth_image = _depth_image(points, valid)
depth_metadata = _depth_metadata(intrinsics, (size, size), 1.0)
depth_metadata = _depth_metadata(intrinsics, (size, size))
result = _create_cutout_shape(
source,
@@ -166,22 +169,21 @@ def test_depth_surface_object_evaluates(gesture):
object_data.modifier_input_identifier(modifier.node_group, "Thickness", subtype="DISTANCE"),
0.0,
)
expected_cleanup = cutout_geometry.cutout_cleanup_value(
0.25,
cutout_geometry.world_plane_metric(source.matrix_world)[0],
)
assert np.isclose(
modifier_value(modifier, object_data.modifier_input_identifier(modifier.node_group, "Cleanup Threshold")),
expected_cleanup,
)
assert np.isclose(modifier_value(modifier, object_data.modifier_input_identifier(modifier.node_group, "Smooth Weight")), 1.0)
for name, value in (("Mode", 1), ("Front Inflation", 0.0)):
object_data.set_modifier_input(modifier, object_data.modifier_input_identifier(modifier.node_group, name), value)
scale_id = object_data.modifier_input_identifier(
modifier.node_group,
"Uniform Scale",
)
reference_id = object_data.modifier_input_identifier(
modifier.node_group,
"Reference Depth",
)
assert modifier_value(modifier, reference_id) == 8.0
# The Depth texture holds a constant unit camera depth.
assert modifier_value(modifier, reference_id) == pytest.approx(
modifier_value(modifier, scale_id)
)
object_data.set_modifier_input(
modifier,
object_data.modifier_input_identifier(modifier.node_group, "Smooth Weight"),
@@ -241,7 +243,7 @@ def test_depth_surface_keeps_continuous_points_across_zero_validity_edge():
"DEPTH_SOLID",
0.1,
depth_image=_depth_image(points, validity),
depth_metadata=_depth_metadata(intrinsics, (size, size), 1.0),
depth_metadata=_depth_metadata(intrinsics, (size, size)),
)
modifier = result.modifiers[0]
object_data.set_modifier_input(
@@ -288,7 +290,7 @@ def test_depth_symmetry_uses_metric_plane_inputs(gesture):
)
valid = np.ones((61, 41), dtype=bool)
depth_image = _depth_image(points, valid)
depth_metadata = _depth_metadata(intrinsics, (41, 61), 2.0)
depth_metadata = _depth_metadata(intrinsics, (41, 61))
selection_values = np.ones((60, 40), dtype=np.float32)
selection_values[:, :2] = 0.0
@@ -324,7 +326,7 @@ def test_depth_symmetry_uses_metric_plane_inputs(gesture):
assert value(modifier, "Mode") == (1 if gesture == "POLYLINE" else 0)
for subtype in ("NONE", "DISTANCE"):
assert value(modifier, "Thickness", subtype) == 0
initialized = {"Mode", "Cleanup Threshold", "Depth Scale", "Uniform Scale", "Reference Depth", "Depth Image"}
initialized = {"Mode", "Depth Scale", "Uniform Scale", "Reference Depth", "Depth Image"}
for socket in modifier.node_group.interface.items_tree:
if (socket.item_type == "SOCKET" and socket.in_out == "INPUT"
and socket.name not in initialized
@@ -469,14 +471,6 @@ def test_local_cutout_builds_from_blender_image_pixels_without_a_job():
assert result["anyimage_mesh_shape"] == "FLAT"
modifier = result.modifiers[0]
assert modifier.node_group.name == cutout_shape.CUTOUT_NODE_GROUP_NAMES["FLAT"]
expected_cleanup = cutout_geometry.cutout_cleanup_value(
0.1,
cutout_geometry.world_plane_metric(source.matrix_world)[0],
)
assert np.isclose(
modifier_value(modifier, object_data.modifier_input_identifier(modifier.node_group, "Cleanup Threshold")),
expected_cleanup,
)
assert len(result.data.vertices) > 4
assert len(result.data.polygons) > 1
assert all(polygon.use_smooth for polygon in result.data.polygons)

View File

@@ -309,7 +309,7 @@ class CutoutOperatorsTest(BlenderTestCase):
[operator.model],
)
self.assertTrue(parameters["generate_depth"])
self.assertEqual(parameters["alpha_threshold"], 0.25)
self.assertNotIn("alpha_threshold", parameters)
self.assertEqual(parameters["normal_mode"], "OBJECT")

View File

@@ -7,6 +7,7 @@ from tests.support.planes import surface
def test_conversion_preserves_offset_world_placement_and_initializes_inputs(surface, monkeypatch):
from anyimage.operators.convert_to_plane import object as conversion
from anyimage.common.image import image_empty_bounds
from anyimage.common.depth import reference_depth
from mathutils import Matrix, Vector
obj, _, _, inputs, image = surface
@@ -22,7 +23,7 @@ def test_conversion_preserves_offset_world_placement_and_initializes_inputs(surf
bounds = image_empty_bounds(source)
matrix = source.matrix_world.copy()
material = bpy.data.materials.new("Conversion")
metadata = {"image_size": (16, 8), "reference_depth": 2.0,
metadata = {"image_size": (16, 8),
"intrinsics": ((4, 0, 5), (0, 4, 3), (0, 0, 1))}
result = conversion.create_depth_plane_object(
bpy.context, source, 2, material, image, metadata, "DEPTH")
@@ -40,7 +41,7 @@ def test_conversion_preserves_offset_world_placement_and_initializes_inputs(surf
assert modifier[inputs["Depth Image"].identifier] == image
assert modifier[inputs["Depth Mask"].identifier] is True
assert modifier[inputs["Reference Depth"].identifier] == pytest.approx(
2 * modifier[inputs["Uniform Scale"].identifier])
reference_depth(image) * modifier[inputs["Uniform Scale"].identifier])
assert result.data.materials[0] == material
@@ -60,7 +61,7 @@ def test_failed_modifier_initialization_removes_partial_object(surface, monkeypa
raise RuntimeError("input failed")
monkeypatch.setattr(conversion, "set_modifier_input", fail)
metadata = {"image_size": (16, 8), "reference_depth": 2,
metadata = {"image_size": (16, 8),
"intrinsics": ((4, 0, 5), (0, 4, 3), (0, 0, 1))}
with pytest.raises(RuntimeError, match="input failed"):
conversion.create_depth_plane_object(bpy.context, source, 2, material, image, metadata, "DEPTH")

View File

@@ -143,7 +143,6 @@ def test_converted_planes_center_the_origin_without_moving_world_corners():
_depth_metadata(
((0.5, 0.0, 0.5), (0.0, 0.5, 0.5), (0.0, 0.0, 1.0)),
(2, 2),
1.0,
),
"DEPTH",
)
@@ -192,7 +191,6 @@ def test_relief_plane_builds_a_fixed_base_terrain_block():
_depth_metadata(
((1.5, 0.0, 1.5), (0.0, 1.5, 1.5), (0.0, 0.0, 1.0)),
(4, 4),
1.0,
),
"RELIEF",
)
@@ -208,7 +206,7 @@ def test_relief_plane_builds_a_fixed_base_terrain_block():
] == 0.5
assert modifier[
object_data.modifier_input_identifier(group, "Depth Offset")
] == 1.0
] == 0.0
def set_input(name, value):
object_data.set_modifier_input(
@@ -299,7 +297,6 @@ def test_relief_plane_initializes_a_fitted_depth_direction():
metadata = _depth_metadata(
((2.0, 0.0, 2.0), (0.0, 2.0, 1.0), (0.0, 0.0, 1.0)),
(5, 3),
2.0,
)
result = plane_object.create_depth_plane_object(
bpy.context,

View File

@@ -67,7 +67,10 @@ class PlaneOperatorsTest(BlenderTestCase):
depth_image = SimpleNamespace(users=0)
normal_image = SimpleNamespace(users=0)
material = SimpleNamespace(users=0)
metadata = {"reference_depth": 1.0}
metadata = {
"image_size": (2, 1),
"intrinsics": ((1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 1.0)),
}
files = {
"depth_metadata": Path("depth.json"),
"depth": Path("depth.exr"),
@@ -153,7 +156,10 @@ class PlaneOperatorsTest(BlenderTestCase):
patch.object(
module,
"load_depth_metadata",
return_value={"reference_depth": 1.0},
return_value={
"image_size": (2, 1),
"intrinsics": ((1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 1.0)),
},
),
patch.object(module, "load_depth_result_image", return_value=depth_image),
patch.object(module, "load_normal_result_image", return_value=normal_image),

View File

@@ -43,7 +43,6 @@ class GeometryArtifactsTest(ServerTestCase):
((1.0, 1.0, 0.0), (1.0, 1.0, 1.0)),
dtype=np.float32,
)
calibration = np.asarray(((False, True, True), (False, False, True)))
frame = GeometryFrame(
depth=np.asarray(
((1.0, 2.0, 3.0), (4.0, 5.0, 6.0)),
@@ -55,14 +54,14 @@ class GeometryArtifactsTest(ServerTestCase):
)
depth.write_depth_texture(frame, texture_path, alpha=np.ones_like(validity))
depth.write_depth_metadata(frame, calibration, metadata_path)
depth.write_depth_metadata(frame, metadata_path)
self.assertTrue(texture_path.is_file())
self.assertEqual(texture_path.read_bytes()[:4], b"v/1\x01")
metadata = json.loads(metadata_path.read_text(encoding="utf-8"))
self.assertEqual(metadata["image_size"], [3, 2])
self.assertEqual(metadata["intrinsics"], np.eye(3).tolist())
self.assertAlmostEqual(metadata["reference_depth"], 4.0)
self.assertEqual(set(metadata), {"image_size", "intrinsics"})
def test_object_normal_texture_matches_depth_surface_axes(self):
@@ -192,7 +191,9 @@ class GeometryArtifactsTest(ServerTestCase):
np.testing.assert_array_equal(write.call_args.args[0][..., 3], [[1, 0], [1, 1]])
np.testing.assert_array_equal(frame.validity, np.ones((2, 2)))
self.assertEqual(json.loads(result["depth_metadata"].read_text())["reference_depth"], 1)
metadata = json.loads(result["depth_metadata"].read_text())
self.assertEqual(metadata["image_size"], [2, 2])
self.assertNotIn("reference_depth", metadata)
infer.assert_called_once()
self.assertEqual(
set(result),
@@ -251,5 +252,4 @@ class GeometryArtifactsTest(ServerTestCase):
input_path,
generate_depth=True,
normal_mode="OBJECT",
alpha_threshold=0.9,
)

View File

@@ -30,7 +30,7 @@ def test_moge_infer_returns_geometry_frame_with_model_validity():
)
def test_moge_z_depth_frame_does_not_require_points():
def test_moge_frame_omits_points_when_not_requested():
prediction = {
"depth": np.ones((2, 2), dtype=np.float32),
"normal": np.zeros((2, 2, 3), dtype=np.float32),