Add new functionality for image rectification and depth metadata handling
- Implemented a preview tool for rectifying images with interactive aspect controls in `preview.py`. - Added tests for depth metadata loading to ensure consistent schema usage in `test_depth_metadata.py`. - Developed comprehensive tests for cutout calibration processes in `test_cutout_calibration.py`, including stability checks and depth calibration. - Created tests for frame compositing operations in `test_frame_compositing.py`, ensuring correct handling of depth and transparency. - Introduced material slot inheritance functionality in `material_slots.py` to manage geometry material assignments. - Built shared shader nodes for material processing in `shader.py`, including roughness and bump mapping setups.
This commit is contained in:
@@ -7,7 +7,6 @@ import numpy as np
|
||||
import pytest
|
||||
|
||||
|
||||
from tests.support.nodes import CUTOUT_BUILDERS
|
||||
from tests.support.depth_surface import assert_closed, component_count, evaluated, surface
|
||||
|
||||
|
||||
@@ -20,7 +19,7 @@ def test_shell_walls_directly_connect_caps(mode, split):
|
||||
obj.data.attributes.new("o_balloon", "FLOAT", "POINT").data.foreach_set(
|
||||
"value", np.full(len(obj.data.vertices), 0.3)
|
||||
)
|
||||
set_value("Split Threshold", split)
|
||||
set_value("Depth Split", split)
|
||||
front, faces = evaluated(obj)
|
||||
edges = Counter(tuple(sorted((a, b))) for face in faces for a, b in zip(face, (*face[1:], face[0])))
|
||||
boundary_count = sum(count == 1 for count in edges.values())
|
||||
@@ -51,7 +50,7 @@ def test_shell_walls_directly_connect_caps(mode, split):
|
||||
@pytest.mark.parametrize("split", [0.0, 0.5])
|
||||
def test_surface_caps_and_segmented_walls_are_connected(thickness, split):
|
||||
obj, set_value = surface()
|
||||
set_value("Split Threshold", split)
|
||||
set_value("Depth Split", split)
|
||||
set_value("Thickness", thickness)
|
||||
coords, faces = evaluated(obj)
|
||||
assert np.isfinite(coords).all()
|
||||
@@ -69,7 +68,7 @@ def test_split_mapping_and_uniform_scale(size):
|
||||
# The normalized ray jump crosses the threshold only at full strength.
|
||||
counts = []
|
||||
for split in (0.0, 0.25, 0.5, 1.0):
|
||||
set_value("Split Threshold", split)
|
||||
set_value("Depth Split", split)
|
||||
coords, faces = evaluated(obj)
|
||||
counts.append(len(coords))
|
||||
assert len(faces) == 8
|
||||
@@ -82,7 +81,7 @@ def test_split_mapping_and_uniform_scale(size):
|
||||
|
||||
def test_midpoint_selects_relative_ray_jump_and_preserves_faces():
|
||||
obj, set_value = surface(step=4.0)
|
||||
set_value("Split Threshold", 0.5)
|
||||
set_value("Depth Split", 0.5)
|
||||
coords, faces = evaluated(obj)
|
||||
assert len(coords) == 18 and len(faces) == 8
|
||||
assert set(np.round(coords[:, 2], 4)) == {2.0, -2.0}
|
||||
@@ -92,7 +91,7 @@ def test_cleanup_keeps_split_parts_of_a_large_source_island():
|
||||
obj, set_value = surface(wrapper=True)
|
||||
set_value("Cleanup Threshold", 110.0)
|
||||
assert len(evaluated(obj)[1]) == 8
|
||||
set_value("Split Threshold", 0.5)
|
||||
set_value("Depth Split", 0.5)
|
||||
assert len(evaluated(obj)[1]) == 8
|
||||
set_value("Thickness", 0.2)
|
||||
assert_closed(evaluated(obj)[1])
|
||||
@@ -103,7 +102,7 @@ def test_cleanup_keeps_split_parts_of_a_large_source_island():
|
||||
@pytest.mark.parametrize("depth_scale", [0.0, 0.25, 1.0, 2.0, 4.0])
|
||||
def test_split_topology_uses_depth_before_display_scaling(depth_scale):
|
||||
obj, set_value = surface(step=4.0)
|
||||
set_value("Split Threshold", 0.5)
|
||||
set_value("Depth Split", 0.5)
|
||||
original, original_faces = evaluated(obj)
|
||||
set_value("Depth Scale", depth_scale)
|
||||
coords, faces = evaluated(obj)
|
||||
@@ -112,18 +111,9 @@ def test_split_topology_uses_depth_before_display_scaling(depth_scale):
|
||||
np.testing.assert_allclose(coords[:, 2], original[:, 2] * depth_scale, atol=1e-6)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("shape", ["SOLID", "DEPTH_SOLID", "DEPTH_BALLOON"])
|
||||
def test_each_asset_cleans_the_original_geometry_before_shaping(shape):
|
||||
bpy.ops.wm.read_factory_settings(use_empty=True)
|
||||
group = CUTOUT_BUILDERS[shape]()
|
||||
links = [link for link in group.links if link.from_node.bl_idname == "NodeGroupInput" and link.from_socket.name == "Geometry"]
|
||||
assert len(links) == 1
|
||||
assert links[0].to_node.bl_idname == "GeometryNodeDeleteGeometry"
|
||||
|
||||
|
||||
def test_full_wrapper_smooth_keeps_closed_shell():
|
||||
obj, set_value = surface(wrapper=True)
|
||||
set_value("Split Threshold", 0.5)
|
||||
set_value("Depth Split", 0.5)
|
||||
set_value("Thickness", 0.2)
|
||||
set_value("Smooth", 2)
|
||||
coords, faces = evaluated(obj)
|
||||
@@ -183,7 +173,7 @@ def test_shell_source_indices_cross_identity_grid_rows():
|
||||
def test_planar_depth_preserves_blocks_and_cleans_single_face_width(resolution):
|
||||
obj, set_value = surface(step=0.0, resolution=resolution)
|
||||
original, original_faces = evaluated(obj)
|
||||
set_value("Split Threshold", 1.0)
|
||||
set_value("Depth Split", 1.0)
|
||||
coords, faces = evaluated(obj)
|
||||
if resolution == 1:
|
||||
assert len(coords) == 0 and not faces
|
||||
@@ -196,7 +186,7 @@ def test_planar_depth_preserves_blocks_and_cleans_single_face_width(resolution):
|
||||
def test_small_positive_split_preserves_unsplit_projection():
|
||||
obj, set_value = surface(step=2.0)
|
||||
original, original_faces = evaluated(obj)
|
||||
set_value("Split Threshold", 0.01)
|
||||
set_value("Depth Split", 0.01)
|
||||
coords, faces = evaluated(obj)
|
||||
np.testing.assert_allclose(coords, original, atol=1e-6)
|
||||
assert faces == original_faces
|
||||
@@ -205,7 +195,7 @@ def test_small_positive_split_preserves_unsplit_projection():
|
||||
def test_cut_vertices_preserve_camera_rays_and_other_vertices():
|
||||
obj, set_value = surface(step=4.0)
|
||||
original, original_faces = evaluated(obj)
|
||||
set_value("Split Threshold", 0.5)
|
||||
set_value("Depth Split", 0.5)
|
||||
coords, faces = evaluated(obj)
|
||||
for source_face, result_face in zip(original_faces, faces):
|
||||
for source_index, result_index in zip(source_face, result_face):
|
||||
@@ -220,7 +210,7 @@ def test_cut_vertices_preserve_camera_rays_and_other_vertices():
|
||||
|
||||
def test_source_surface_sampling_preserves_depth_on_side_walls():
|
||||
obj, set_value = surface(step=4.0)
|
||||
set_value("Split Threshold", 0.5)
|
||||
set_value("Depth Split", 0.5)
|
||||
set_value("Thickness", 0.1)
|
||||
coords, faces = evaluated(obj)
|
||||
assert_closed(faces)
|
||||
@@ -240,7 +230,7 @@ def test_temporary_fields_are_removed_and_user_attributes_survive(thickness, spl
|
||||
obj.data.attributes.new(".o_anyimage_user_weight", "FLOAT", "POINT")
|
||||
for value in attribute.data:
|
||||
value.value = 0.75
|
||||
set_value("Split Threshold", split)
|
||||
set_value("Depth Split", split)
|
||||
set_value("Thickness", thickness)
|
||||
set_value("Smooth", 2)
|
||||
result = obj.evaluated_get(bpy.context.evaluated_depsgraph_get())
|
||||
@@ -279,7 +269,7 @@ def test_zero_center_distance_does_not_split_or_produce_nan():
|
||||
for li in face.loop_indices:
|
||||
uv.data[li].uv = (0.25 if face.index == 0 else 0.75, 0.5)
|
||||
obj.data = mesh
|
||||
set_value("Split Threshold", 1.0)
|
||||
set_value("Depth Split", 1.0)
|
||||
coords, faces = evaluated(obj)
|
||||
assert len(coords) == 4 and len(faces) == 2
|
||||
assert np.isfinite(coords).all()
|
||||
@@ -297,7 +287,7 @@ def test_triangle_corner_weights_do_not_open_sloped_side_walls():
|
||||
pixels[..., 2] += np.arange(32)[:, None] * 0.02
|
||||
image.pixels.foreach_set(pixels.ravel())
|
||||
image.update()
|
||||
set_value("Split Threshold", 0.5)
|
||||
set_value("Depth Split", 0.5)
|
||||
set_value("Thickness", 0.8)
|
||||
coords, faces = evaluated(obj)
|
||||
assert_closed(faces)
|
||||
@@ -319,34 +309,28 @@ def test_relative_distance_slope_has_density_independent_selection(resolution):
|
||||
pixels[..., :2] = 0
|
||||
image.pixels.foreach_set(pixels.ravel())
|
||||
image.update()
|
||||
set_value("Split Threshold", 0.25)
|
||||
set_value("Depth Split", 0.25)
|
||||
assert len(evaluated(obj)[0]) == len(obj.data.vertices)
|
||||
set_value("Split Threshold", 1.0)
|
||||
set_value("Depth Split", 1.0)
|
||||
assert len(evaluated(obj)[0]) > len(obj.data.vertices)
|
||||
|
||||
@pytest.mark.parametrize("kind", ["plane", "cutout"])
|
||||
@pytest.mark.parametrize("source", ["definition", "asset"])
|
||||
def test_split_uses_ray_distance_and_ignores_absolute_distance_scale(kind, source):
|
||||
def test_split_uses_ray_distance_and_ignores_absolute_distance_scale(kind):
|
||||
from tests.support.planes import create_surface
|
||||
|
||||
if kind == "plane":
|
||||
obj, set_value, _, _, image = create_surface(source, "DEPTH")
|
||||
obj, set_value, _, _, image = create_surface("DEPTH")
|
||||
set_value("Subdivide", 1)
|
||||
else:
|
||||
obj, set_value = surface(step=0)
|
||||
if source == "asset":
|
||||
from tests.support.paths import PROJECT_ROOT
|
||||
with bpy.data.libraries.load(str(PROJECT_ROOT / "src/anyimage/assets/O_AnyImage.blend")) as (_, target):
|
||||
target.node_groups = ["O Image Depth Cutout"]
|
||||
obj.modifiers[0].node_group = target.node_groups[0]
|
||||
image = bpy.data.images["Camera"]
|
||||
output_split_mesh(obj)
|
||||
set_value("Reference Depth", 3)
|
||||
set_value("Split Threshold", 0)
|
||||
set_value("Depth Split", 0)
|
||||
width, height = image.size
|
||||
pixels = np.ones((height, width, 4), np.float32)
|
||||
original_count = len(evaluated(obj)[0])
|
||||
set_value("Split Threshold", 1)
|
||||
set_value("Depth Split", 1)
|
||||
# Equal axial Z, but a tenfold ray-distance jump: Z-only splitting misses it.
|
||||
pixels[..., :3] = (0, 0, 1)
|
||||
pixels[:, width // 2:, 0] = np.sqrt(99)
|
||||
|
||||
Reference in New Issue
Block a user