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:
@@ -6,8 +6,6 @@ import numpy as np
|
||||
import pytest
|
||||
|
||||
from tests.support.planes import surface, relief, evaluated
|
||||
from tests.support.nodes import validate_node_structure
|
||||
from tools.nodes.arrangement.flow import origin_socket
|
||||
|
||||
|
||||
@pytest.mark.parametrize("subdivide", [0, 1, 3])
|
||||
@@ -59,7 +57,7 @@ def test_shell_keeps_front_and_closes_rectangular_boundary(surface, thickness):
|
||||
set_value("Depth Scale", 1.0)
|
||||
front = evaluated(obj)[0]
|
||||
set_value("Thickness", thickness)
|
||||
points, faces, uv, materials, indices, attributes = evaluated(obj)
|
||||
points, faces, uv, materials, indices, _ = evaluated(obj)
|
||||
assert len(points) == 2 * len(front)
|
||||
edges = Counter(tuple(sorted((a, b))) for f in faces for a, b in zip(f, (*f[1:], f[0])))
|
||||
assert set(edges.values()) == {2}
|
||||
@@ -70,7 +68,6 @@ def test_shell_keeps_front_and_closes_rectangular_boundary(surface, thickness):
|
||||
assert cKDTree(points).query(expected)[0].max() < 1e-6
|
||||
assert np.isfinite(points).all() and np.isfinite(uv).all()
|
||||
assert materials[0] == "Image" and set(indices) == {0}
|
||||
assert not any(name.startswith("o_depth_") for name in attributes)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("valid_only", [False, True])
|
||||
@@ -84,16 +81,15 @@ def test_depth_split_preserves_faces_and_closes_each_shell(surface, valid_only):
|
||||
image.pixels.foreach_set(pixels.ravel())
|
||||
image.update()
|
||||
set_value("Depth Scale", 1.0)
|
||||
set_value("Valid Only", valid_only)
|
||||
set_value("Depth Mask", valid_only)
|
||||
base = evaluated(obj)
|
||||
set_value("Split Threshold", 1.0)
|
||||
points, faces, uv, materials, indices, attributes = evaluated(obj)
|
||||
set_value("Depth Split", 1.0)
|
||||
points, faces, uv, materials, indices, _ = evaluated(obj)
|
||||
assert len(points) > len(base[0])
|
||||
assert len(faces) == len(base[1])
|
||||
assert component_count(faces) > component_count(base[1])
|
||||
np.testing.assert_allclose(uv, base[2])
|
||||
assert (materials, indices) == base[3:5]
|
||||
assert not any(name.startswith("o_depth_") for name in attributes)
|
||||
set_value("Thickness", 0.2)
|
||||
shell_points, shell_faces, shell_uv, *_ = evaluated(obj)
|
||||
assert len(shell_points) == 2 * len(points)
|
||||
@@ -102,7 +98,7 @@ def test_depth_split_preserves_faces_and_closes_each_shell(surface, valid_only):
|
||||
assert set(edges.values()) == {2}
|
||||
assert np.isfinite(shell_points).all() and np.isfinite(shell_uv).all()
|
||||
set_value("Thickness", 0.0)
|
||||
set_value("Split Threshold", 0.0)
|
||||
set_value("Depth Split", 0.0)
|
||||
restored = evaluated(obj)
|
||||
np.testing.assert_allclose(restored[0], base[0])
|
||||
assert restored[1] == base[1]
|
||||
@@ -197,35 +193,6 @@ def test_curved_shell_smooths_normals_without_shrinking_thickness(surface):
|
||||
assert roughness(normals) < roughness(raw_normals)
|
||||
|
||||
|
||||
def test_surface_interface_and_node_structure(surface):
|
||||
obj, _, _, inputs, _ = surface
|
||||
expected = ["Geometry", "Subdivide", "Thickness", "Depth Scale", "Split Threshold", "Smooth", "Valid Only",
|
||||
"Smooth Weight", "Reference Depth", "Boundary Smooth", "Validity Threshold", "Uniform Scale", "Depth Image"]
|
||||
assert list(inputs) == expected
|
||||
assert inputs["Split Threshold"].default_value == pytest.approx(0.1)
|
||||
assert inputs["Split Threshold"].min_value == 0
|
||||
assert inputs["Split Threshold"].max_value == 1
|
||||
assert inputs["Split Threshold"].subtype == "FACTOR"
|
||||
assert inputs["Split Threshold"].parent == inputs["Smooth"].parent
|
||||
assert inputs["Split Threshold"].parent.name != "Options"
|
||||
assert inputs["Validity Threshold"].default_value == pytest.approx(0.9)
|
||||
assert inputs["Validity Threshold"].parent.name == "Options"
|
||||
assert inputs["Validity Threshold"].subtype == "FACTOR"
|
||||
assert inputs["Valid Only"].default_value is True
|
||||
assert inputs["Smooth"].default_value == 0
|
||||
assert inputs["Smooth"].min_value == 0 and inputs["Smooth"].max_value == 20
|
||||
assert inputs["Smooth Weight"].default_value == 1
|
||||
assert inputs["Smooth Weight"].subtype == "FACTOR"
|
||||
assert inputs["Smooth Weight"].parent.name == "Options"
|
||||
assert not {"Normal Smooth", "Cleanup Threshold"} & inputs.keys()
|
||||
assert inputs["Thickness"].default_value == 0
|
||||
assert inputs["Subdivide"].default_value == 6
|
||||
assert inputs["Depth Scale"].default_value == 1
|
||||
assert all(item.structure_type == "SINGLE" for item in inputs.values())
|
||||
assert inputs["Thickness"].subtype == inputs["Reference Depth"].subtype == "DISTANCE"
|
||||
assert inputs["Uniform Scale"].hide_in_modifier and inputs["Depth Image"].hide_in_modifier
|
||||
validate_node_structure(obj.modifiers[0].node_group)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("thickness", [0.0, 0.4])
|
||||
@pytest.mark.parametrize("direction", [(0.2, -0.1, 1), (-0.2, 0.1, 1), (0.4, -0.2, 2), (0, 0, 0), (1, 0, 0)])
|
||||
@@ -278,30 +245,13 @@ def test_relief_smooth_preserves_topology_uv_and_materials(relief, thickness):
|
||||
np.testing.assert_allclose(evaluated(obj)[0], base[0], atol=1e-6)
|
||||
|
||||
|
||||
def test_relief_interface_and_node_structure(relief):
|
||||
obj, _, _, inputs, _ = relief
|
||||
assert list(inputs) == ["Geometry", "Subdivide", "Thickness", "Depth Scale", "Smooth",
|
||||
"Reference Depth", "Smooth Weight", "Depth Direction", "Uniform Scale", "Depth Image"]
|
||||
assert inputs["Smooth"].default_value == 0
|
||||
assert (inputs["Smooth"].min_value, inputs["Smooth"].max_value) == (0, 20)
|
||||
assert inputs["Smooth Weight"].default_value == 1
|
||||
assert inputs["Smooth Weight"].subtype == "FACTOR"
|
||||
assert tuple(inputs["Depth Direction"].default_value) == (0, 0, 1)
|
||||
assert inputs["Depth Direction"].subtype == "DIRECTION"
|
||||
assert inputs["Thickness"].default_value == 0.5
|
||||
assert inputs["Subdivide"].default_value == 6
|
||||
assert inputs["Depth Scale"].default_value == 0.5
|
||||
assert all(item.structure_type == "SINGLE" for item in inputs.values())
|
||||
assert inputs["Thickness"].subtype == inputs["Reference Depth"].subtype == "DISTANCE"
|
||||
assert inputs["Uniform Scale"].hide_in_modifier and inputs["Depth Image"].hide_in_modifier
|
||||
validate_node_structure(obj.modifiers[0].node_group)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("threshold", [0.0, 0.4, 0.9, 1.0])
|
||||
@pytest.mark.parametrize("pattern", ["valid", "invalid", "partial", "half", "stripe"])
|
||||
@pytest.mark.parametrize("pattern,threshold", [
|
||||
("valid", 1.0), ("invalid", 0.0), ("invalid", 0.9),
|
||||
("partial", 0.4), ("partial", 0.9), ("half", 0.9), ("stripe", 0.9),
|
||||
])
|
||||
def test_valid_only_deletes_invalid_points_and_wire_edges(surface, pattern, threshold):
|
||||
obj, set_value, _, inputs, _ = surface
|
||||
set_value("Validity Threshold", threshold)
|
||||
set_value("Mask Threshold", threshold)
|
||||
depth_image = obj.modifiers[0][inputs["Depth Image"].identifier]
|
||||
pixels = np.ones((8, 16, 4), dtype=np.float32)
|
||||
alpha = np.ones(16, dtype=np.float32)
|
||||
@@ -319,18 +269,18 @@ def test_valid_only_deletes_invalid_points_and_wire_edges(surface, pattern, thre
|
||||
depth_image.update()
|
||||
for subdivisions in (0, 2, 5):
|
||||
set_value("Subdivide", subdivisions)
|
||||
set_value("Valid Only", False)
|
||||
set_value("Depth Mask", False)
|
||||
base = evaluated(obj)
|
||||
u = (base[0][:, 0] + 2) / 4
|
||||
sampled = np.interp(u * 16 - 0.5, np.arange(16), alpha)
|
||||
expected = [face for face in base[1] if np.all(sampled[list(face)] >= threshold)]
|
||||
set_value("Valid Only", True)
|
||||
set_value("Depth Mask", True)
|
||||
culled = evaluated(obj)
|
||||
assert len(culled[1]) == len(expected)
|
||||
expected_points = {tuple(base[0][index]) for face in expected for index in face}
|
||||
assert {tuple(point) for point in culled[0]} == expected_points
|
||||
assert np.isfinite(culled[2]).all()
|
||||
set_value("Valid Only", False)
|
||||
set_value("Depth Mask", False)
|
||||
np.testing.assert_allclose(evaluated(obj)[0], base[0])
|
||||
|
||||
|
||||
@@ -351,26 +301,6 @@ def test_valid_only_precedes_closed_shell_generation(surface):
|
||||
assert np.isfinite(points).all() and np.isfinite(uv).all()
|
||||
|
||||
|
||||
def test_validity_graph_matches_reference_domains_and_connections(surface):
|
||||
obj, _, _, _, _ = surface
|
||||
group = obj.modifiers[0].node_group
|
||||
delete = next(node for node in group.nodes if node.bl_idname == "GeometryNodeDeleteGeometry")
|
||||
assert (delete.domain, delete.mode) == ("POINT", "ALL")
|
||||
compare = origin_socket(delete.inputs["Selection"].links[0].from_socket).node
|
||||
assert (compare.bl_idname, compare.data_type, compare.operation) == ("FunctionNodeCompare", "FLOAT", "LESS_THAN")
|
||||
assert origin_socket(compare.inputs["B"].links[0].from_socket).name == "Validity Threshold"
|
||||
alpha = origin_socket(compare.inputs["A"].links[0].from_socket)
|
||||
assert alpha.name == "Alpha" and alpha.node.bl_idname == "FunctionNodeSeparateColor"
|
||||
color = origin_socket(alpha.node.inputs["Color"].links[0].from_socket).node
|
||||
assert origin_socket(color.inputs["Image"].links[0].from_socket).name == "Depth Image"
|
||||
consumers = [link for link in group.links if link.to_node.bl_idname != "NodeReroute"
|
||||
and origin_socket(link.from_socket) == color.outputs["Color"]]
|
||||
assert any(link.to_node == alpha.node for link in consumers)
|
||||
protected = origin_socket(delete.inputs["Geometry"].links[0].from_socket).node
|
||||
assert protected.bl_idname == "GeometryNodeGroup"
|
||||
assert protected.node_tree.name == "O Image Plane"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("thickness", [0.0, 0.2])
|
||||
def test_depth_smooth_changes_positions_and_preserves_topology(surface, thickness):
|
||||
obj, set_value, _, _, depth_image = surface
|
||||
@@ -394,21 +324,3 @@ def test_depth_smooth_changes_positions_and_preserves_topology(surface, thicknes
|
||||
set_value("Smooth Weight", 1.0)
|
||||
set_value("Smooth", 0)
|
||||
np.testing.assert_allclose(evaluated(obj)[0], base[0], atol=1e-6)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("kind", ["DEPTH", "RELIEF"])
|
||||
@pytest.mark.parametrize("source", ["definition", "asset"])
|
||||
def test_plane_interface_panels(source, kind):
|
||||
from tests.support.planes import create_surface
|
||||
obj, _, _, _, _ = create_surface(source, kind)
|
||||
node_group = obj.modifiers[0].node_group
|
||||
panels = [item for item in node_group.interface.items_tree if item.item_type == "PANEL"]
|
||||
assert [item.name for item in panels] == ["Options", "Data"]
|
||||
controls = ["Reference Depth"]
|
||||
if kind == "DEPTH":
|
||||
controls = ["Smooth Weight", "Reference Depth", "Boundary Smooth", "Validity Threshold"]
|
||||
else:
|
||||
controls += ["Smooth Weight", "Depth Direction"]
|
||||
assert [item.name for item in panels[0].interface_items] == controls
|
||||
assert [item.name for item in panels[1].interface_items] == ["Uniform Scale", "Depth Image"]
|
||||
assert all(panel.default_closed for panel in panels)
|
||||
|
||||
Reference in New Issue
Block a user