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

@@ -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)