- Introduced a new `Boundary Padding` preference in the Cutout Tool with a default of 2 px and a configurable range of 1-4 px. - Updated the Cutout client to establish inward mapping from the final Mask, sampling in a fixed direction and processing independent material RGBA and Depth. - Enhanced the handling of RGBA and Depth during cutout operations, ensuring proper mapping and preservation of source data. - Added tests to validate the new boundary padding functionality and its integration with existing cutout processes. feat(geometry): remove overall mesh smoothing from geometry nodes - Removed the `Smooth` input from several geometry node groups to eliminate unnecessary overall mesh smoothing. - Retained `Smooth Weight` for depth nodes while removing it from Cutout and Relief Plane nodes. - Ensured that boundary, normal, and fill smoothing behaviors remain intact and consistent. - Updated tests and documentation to reflect the changes in node interfaces and behaviors. feat(normals): scale tangent normals with depth - Reused the existing `o_normal_reduction` attribute to adjust Tangent Normal strength based on the geometry's `Depth Scale`. - Ensured that Tangent Normal strength is consistent with the geometry depth, preserving full strength for materials that do not provide the attribute. - Updated tests to verify the correct behavior of normal strength adjustments in both Tangent and Object Space.
131 lines
4.3 KiB
Python
131 lines
4.3 KiB
Python
import numpy as np
|
|
|
|
from anyimage.operators.cutout_tool.boundary_padding import (
|
|
apply_boundary_mapping,
|
|
apply_depth_boundary_mapping,
|
|
build_boundary_mapping,
|
|
image_rgba_buffer,
|
|
pad_cutout_images,
|
|
)
|
|
|
|
|
|
def square_with_hole():
|
|
values = np.zeros((17, 17), dtype=np.float32)
|
|
values[3:14, 3:14] = 1
|
|
values[7:10, 7:10] = 0
|
|
return values
|
|
|
|
|
|
def test_outer_and_hole_boundaries_follow_the_local_interior():
|
|
mapping = build_boundary_mapping(square_with_hole(), 0.9, 2)
|
|
assert mapping.target[2, 8]
|
|
assert mapping.source_y[2, 8] > 3
|
|
assert abs(mapping.source_x[2, 8] - 8) < 0.25
|
|
assert mapping.target[7, 8]
|
|
assert mapping.source_y[7, 8] < 7
|
|
assert abs(mapping.source_x[7, 8] - 8) < 0.5
|
|
|
|
|
|
def test_mapping_moves_along_one_direction_instead_of_nearest_content():
|
|
values = square_with_hole()
|
|
yy, xx = np.mgrid[:17, :17]
|
|
rgba = np.stack((xx, yy, xx + yy, np.ones_like(xx)), axis=-1).astype(np.float32)
|
|
mapping = build_boundary_mapping(values, 0.9, 3)
|
|
result = apply_boundary_mapping(rgba, mapping)
|
|
assert result[1, 8, 1] > 3
|
|
assert abs(result[1, 8, 0] - 8) < 0.5
|
|
|
|
|
|
def test_thin_structure_stops_at_the_last_point_on_the_same_ray():
|
|
values = np.zeros((11, 15), dtype=np.float32)
|
|
values[5:7, 2:13] = 1
|
|
mapping = build_boundary_mapping(values, 0.9, 4)
|
|
sampled_inside = values[
|
|
np.rint(mapping.source_y[mapping.target]).astype(int),
|
|
np.rint(mapping.source_x[mapping.target]).astype(int),
|
|
]
|
|
assert sampled_inside.size
|
|
assert np.all(sampled_inside == 1)
|
|
assert np.isfinite(mapping.source_y).all()
|
|
assert np.isfinite(mapping.source_x).all()
|
|
|
|
|
|
def test_scaled_mapping_uses_proportional_padding_and_zero_is_identity():
|
|
values = square_with_hole()
|
|
original = build_boundary_mapping(values, 0.9, 2)
|
|
scaled = build_boundary_mapping(values, 0.9, 2, (34, 34))
|
|
assert scaled.target.sum() > original.target.sum() * 3
|
|
disabled = build_boundary_mapping(values, 0.9, 0)
|
|
assert not disabled.target.any()
|
|
|
|
|
|
def test_depth_moves_z_and_validity_but_reprojects_xy_on_target_rays():
|
|
values = np.zeros((9, 9), dtype=np.float32)
|
|
values[2:7, 2:7] = 1
|
|
mapping = build_boundary_mapping(values, 0.9, 2)
|
|
yy, xx = np.mgrid[:9, :9]
|
|
rgba = np.zeros((9, 9, 4), dtype=np.float32)
|
|
rgba[..., 2] = yy + 10
|
|
rgba[..., 3] = yy / 10
|
|
intrinsics = np.array(((4, 0, 4), (0, 4, 4), (0, 0, 1)), dtype=np.float32)
|
|
result = apply_depth_boundary_mapping(rgba, mapping, intrinsics)
|
|
y, x = 1, 4
|
|
assert mapping.target[y, x]
|
|
assert result[y, x, 2] > rgba[2, x, 2]
|
|
assert result[y, x, 0] == 0
|
|
assert result[y, x, 1] == (y - 4) / 4 * result[y, x, 2]
|
|
assert result[y, x, 3] > rgba[2, x, 3]
|
|
|
|
|
|
class PixelBuffer:
|
|
def __init__(self, values):
|
|
self.values = np.asarray(values, dtype=np.float32).copy()
|
|
|
|
def foreach_get(self, target):
|
|
target[:] = self.values
|
|
|
|
def foreach_set(self, values):
|
|
self.values[:] = values
|
|
|
|
|
|
class Image:
|
|
def __init__(self, top_down):
|
|
height, width = top_down.shape[:2]
|
|
self.size = (width, height)
|
|
self.pixels = PixelBuffer(np.flipud(top_down).ravel())
|
|
self.updated = 0
|
|
self.packed = 0
|
|
|
|
def update(self):
|
|
self.updated += 1
|
|
|
|
def pack(self):
|
|
self.packed += 1
|
|
|
|
|
|
def test_client_processing_updates_and_packs_color_and_depth_together():
|
|
content = square_with_hole()
|
|
yy, xx = np.mgrid[:17, :17]
|
|
color_values = np.stack((xx, yy, xx + yy, content), axis=-1).astype(np.float32)
|
|
original = color_values.copy()
|
|
color = Image(color_values)
|
|
depth_values = np.zeros((17, 17, 4), dtype=np.float32)
|
|
depth_values[..., 2] = yy + 10
|
|
depth_values[..., 3] = content
|
|
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,
|
|
}
|
|
|
|
pad_cutout_images(color, depth, metadata, content, 0.9, 2)
|
|
|
|
padded_color = image_rgba_buffer(color)
|
|
padded_depth = image_rgba_buffer(depth)
|
|
np.testing.assert_array_equal(padded_color[5, 5], original[5, 5])
|
|
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 (color.updated, color.packed, depth.updated, depth.packed) == (1, 1, 1, 1)
|