Add geometry nodes asset and validation script
- Implemented a new geometry nodes asset in `build_geometry_nodes_asset.py` to handle mesh refinement and topology fixing. - Created a validation script in `validate_geometry_nodes_asset.py` to ensure the integrity and functionality of the geometry node group. - Added input handling for various parameters including resolution, iterations, and smoothing options. - Included checks for node group structure and attributes to validate the geometry processing pipeline.
This commit is contained in:
@@ -982,7 +982,14 @@ class WorkerTest(unittest.TestCase):
|
||||
"export_textured_mesh_glb",
|
||||
side_effect=fake_mesh_export,
|
||||
):
|
||||
for export_format in self.worker.EXPORT_FORMATS:
|
||||
debug_formats = (
|
||||
"glb",
|
||||
"gs_ply",
|
||||
"depth_vis",
|
||||
"mini_npz",
|
||||
"mesh",
|
||||
)
|
||||
for export_format in debug_formats:
|
||||
args = SimpleNamespace(
|
||||
input=str(input_path),
|
||||
video_frames=16,
|
||||
@@ -1172,6 +1179,124 @@ class WorkerTest(unittest.TestCase):
|
||||
|
||||
self.assertEqual(threshold, 2.5)
|
||||
|
||||
def test_zero_mesh_confidence_disables_confidence_filter(self):
|
||||
depth = np.ones((2, 2), dtype=np.float32)
|
||||
confidence = np.full((2, 2), np.nan, dtype=np.float32)
|
||||
x, y = np.meshgrid([0.0, 1.0], [0.0, 1.0])
|
||||
points = np.stack((x, y, depth), axis=-1)
|
||||
|
||||
threshold = self.worker.confidence_percentile_threshold(
|
||||
depth,
|
||||
confidence,
|
||||
0.0,
|
||||
)
|
||||
faces = self.worker.mesh_triangle_faces(
|
||||
points,
|
||||
depth,
|
||||
confidence,
|
||||
threshold,
|
||||
filter_edges=False,
|
||||
max_edge_length=0.0,
|
||||
)
|
||||
|
||||
self.assertIsNone(threshold)
|
||||
self.assertEqual(len(faces), 2)
|
||||
|
||||
def test_mesh_filters_triangles_independently(self):
|
||||
depth = np.ones((2, 2), dtype=np.float32)
|
||||
confidence = np.ones((2, 2), dtype=np.float32)
|
||||
confidence[1, 0] = 0.0
|
||||
x, y = np.meshgrid(
|
||||
np.arange(2, dtype=np.float32),
|
||||
np.arange(2, dtype=np.float32),
|
||||
)
|
||||
points = np.stack((x, y, depth), axis=-1)
|
||||
|
||||
faces = self.worker.mesh_triangle_faces(
|
||||
points,
|
||||
depth,
|
||||
confidence,
|
||||
confidence_threshold=0.5,
|
||||
filter_edges=False,
|
||||
max_edge_length=0.0,
|
||||
)
|
||||
|
||||
np.testing.assert_array_equal(faces, [[0, 3, 1]])
|
||||
|
||||
def test_mesh_background_mask_combines_with_confidence(self):
|
||||
depth = np.ones((2, 3), dtype=np.float32)
|
||||
confidence = np.ones((2, 3), dtype=np.float32)
|
||||
x, y = np.meshgrid(
|
||||
np.arange(3, dtype=np.float32),
|
||||
np.arange(2, dtype=np.float32),
|
||||
)
|
||||
points = np.stack((x, y, depth), axis=-1)
|
||||
foreground = np.ones((2, 3), dtype=bool)
|
||||
foreground[:, 0] = False
|
||||
|
||||
faces = self.worker.mesh_triangle_faces(
|
||||
points,
|
||||
depth,
|
||||
confidence,
|
||||
confidence_threshold=None,
|
||||
filter_edges=False,
|
||||
max_edge_length=0.0,
|
||||
vertex_mask=foreground,
|
||||
)
|
||||
|
||||
np.testing.assert_array_equal(faces, [[1, 4, 5], [1, 5, 2]])
|
||||
|
||||
def test_depth_edge_filter_ignores_smooth_slope(self):
|
||||
smooth = np.tile(
|
||||
np.linspace(2.0, 2.04, 5, dtype=np.float32),
|
||||
(3, 1),
|
||||
)
|
||||
discontinuous = smooth.copy()
|
||||
discontinuous[:, 3:] += 1.0
|
||||
|
||||
self.assertFalse(self.worker.depth_edge_mask(smooth).any())
|
||||
self.assertTrue(self.worker.depth_edge_mask(discontinuous)[:, 2:4].all())
|
||||
|
||||
def test_higher_edge_culling_allows_less_stretch(self):
|
||||
limits = [
|
||||
self.worker.edge_culling_stretch_limit(value)
|
||||
for value in (0.0, 50.0, 100.0)
|
||||
]
|
||||
|
||||
self.assertEqual(limits, [10000.0, 2.0, 1.0])
|
||||
|
||||
def test_stretched_edge_culling_uses_camera_space_edge_ratio(self):
|
||||
flat_points = np.asarray(
|
||||
[
|
||||
[[0.0, 0.0, 1.0], [1.0, 0.0, 1.0]],
|
||||
[[0.0, 1.0, 1.0], [1.0, 1.0, 1.0]],
|
||||
],
|
||||
dtype=np.float32,
|
||||
)
|
||||
stretched_points = flat_points.copy()
|
||||
stretched_points[0, 1] = (2.0, 0.0, 2.0)
|
||||
triangles = np.asarray([[0, 2, 3], [0, 3, 1]], dtype=np.int32)
|
||||
|
||||
flat_mask = self.worker.triangle_edge_stretch_mask(
|
||||
flat_points,
|
||||
triangles,
|
||||
1.0,
|
||||
)
|
||||
loose_mask = self.worker.triangle_edge_stretch_mask(
|
||||
stretched_points,
|
||||
triangles,
|
||||
2.0,
|
||||
)
|
||||
strict_mask = self.worker.triangle_edge_stretch_mask(
|
||||
stretched_points,
|
||||
triangles,
|
||||
1.0,
|
||||
)
|
||||
|
||||
self.assertTrue(flat_mask.all())
|
||||
self.assertTrue(loose_mask.all())
|
||||
self.assertFalse(strict_mask.all())
|
||||
|
||||
def test_mesh_faces_apply_confidence_threshold_and_face_direction(self):
|
||||
points = np.array(
|
||||
[
|
||||
@@ -1228,6 +1353,113 @@ class WorkerTest(unittest.TestCase):
|
||||
|
||||
self.assertEqual(face_counts, [8, 2, 0])
|
||||
|
||||
def test_mesh_compaction_removes_unreferenced_vertices(self):
|
||||
vertices = np.arange(18, dtype=np.float32).reshape(6, 3)
|
||||
faces = np.asarray([[1, 3, 5]], dtype=np.int32)
|
||||
|
||||
compact_vertices, compact_faces = self.worker.compact_mesh(
|
||||
vertices,
|
||||
faces,
|
||||
)
|
||||
|
||||
np.testing.assert_array_equal(
|
||||
compact_vertices,
|
||||
vertices[[1, 3, 5]],
|
||||
)
|
||||
np.testing.assert_array_equal(compact_faces, [[0, 1, 2]])
|
||||
|
||||
def test_image_mesh_data_keeps_camera_geometry_and_uv(self):
|
||||
class FakeImage:
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, *_args):
|
||||
return None
|
||||
|
||||
def convert(self, mode):
|
||||
self.mode = mode
|
||||
return self
|
||||
|
||||
def save(self, path, **_options):
|
||||
Path(path).write_bytes(b"texture")
|
||||
|
||||
fake_pil = ModuleType("PIL")
|
||||
fake_pil.Image = SimpleNamespace(open=lambda _path: FakeImage())
|
||||
depth = np.full((1, 3, 3), 2.0, dtype=np.float32)
|
||||
confidence = np.ones((1, 3, 3), dtype=np.float32)
|
||||
intrinsics = np.asarray(
|
||||
[[[2.0, 0.0, 1.0], [0.0, 2.0, 1.0], [0.0, 0.0, 1.0]]],
|
||||
dtype=np.float32,
|
||||
)
|
||||
prediction = SimpleNamespace(
|
||||
depth=depth,
|
||||
conf=confidence,
|
||||
intrinsics=intrinsics,
|
||||
)
|
||||
progress = []
|
||||
|
||||
with tempfile.TemporaryDirectory() as directory, patch.dict(
|
||||
sys.modules,
|
||||
{"PIL": fake_pil},
|
||||
), patch.object(
|
||||
self.worker,
|
||||
"compact_mesh",
|
||||
side_effect=lambda vertices, faces: (
|
||||
vertices,
|
||||
faces,
|
||||
),
|
||||
):
|
||||
result_path, texture_path = self.worker.export_image_mesh_data(
|
||||
prediction,
|
||||
Path(directory) / "input.png",
|
||||
Path(directory) / "result",
|
||||
progress_callback=lambda step, total, label: progress.append(
|
||||
(step, total, label)
|
||||
),
|
||||
)
|
||||
with np.load(result_path) as result:
|
||||
self.assertEqual(result["vertices"].shape, (9, 3))
|
||||
self.assertEqual(result["faces"].shape, (8, 3))
|
||||
self.assertEqual(result["uv"].shape, (9, 2))
|
||||
self.assertEqual(float(result["reference_depth"]), 2.0)
|
||||
self.assertNotIn("postprocess_method", result.files)
|
||||
self.assertEqual(result["point_depth"].shape, (9,))
|
||||
self.assertEqual(
|
||||
result["point_confidence_percentile"].shape,
|
||||
(9,),
|
||||
)
|
||||
self.assertEqual(
|
||||
result["face_edge_stretch"].shape,
|
||||
(8,),
|
||||
)
|
||||
self.assertEqual(result["face_background"].shape, (8,))
|
||||
np.testing.assert_allclose(result["uv"][0], [0.0, 1.0])
|
||||
np.testing.assert_allclose(result["uv"][-1], [1.0, 0.0])
|
||||
self.assertTrue(texture_path.is_file())
|
||||
self.assertEqual([item[0] for item in progress], list(range(1, 7)))
|
||||
self.assertTrue(all(item[1] == 6 for item in progress))
|
||||
|
||||
def test_prediction_attributes_keep_debug_structure(self):
|
||||
prediction = SimpleNamespace(
|
||||
depth=np.ones((1, 2, 3), dtype=np.float32),
|
||||
conf=np.arange(6, dtype=np.float32).reshape(1, 2, 3),
|
||||
intrinsics=np.eye(3, dtype=np.float32)[None],
|
||||
extrinsics=np.eye(4, dtype=np.float32)[None],
|
||||
)
|
||||
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
output_path = self.worker.export_prediction_attributes(
|
||||
prediction,
|
||||
directory,
|
||||
)
|
||||
with np.load(output_path) as attributes:
|
||||
self.assertEqual(attributes["depth"].shape, (1, 2, 3))
|
||||
self.assertEqual(attributes["conf"].shape, (1, 2, 3))
|
||||
self.assertEqual(
|
||||
attributes["confidence_percentile"].shape,
|
||||
(1, 2, 3),
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user