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:
190
tests/anyimage/common/test_depth.py
Normal file
190
tests/anyimage/common/test_depth.py
Normal 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)
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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]
|
||||
|
||||
@@ -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)),),)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user