feat: Add new Geometry Nodes groups for image relief and shadeless shader
- Implemented the "O Image Relief Plane" Geometry Nodes group to create a fixed-base image relief using depth images and various parameters. - Created the "O Shadeless" shader node group to display color without scene lighting while preserving shadows and reflections. - Introduced a preview feature for node groups, allowing for interactive visualization of node arrangements in an HTML format. - Added a template HTML file for rendering node previews with customizable views and controls.
This commit is contained in:
@@ -6,6 +6,30 @@ from unittest.mock import patch
|
||||
from server_runtime_test_support import ServerTestCase
|
||||
|
||||
class GeometryArtifactsTest(ServerTestCase):
|
||||
def test_depth_alpha_multiplies_coverage_without_changing_geometry(self):
|
||||
import numpy as np
|
||||
|
||||
depth = importlib.import_module("server.geometry.depth_texture")
|
||||
GeometryFrame = importlib.import_module("server.models.geometry").GeometryFrame
|
||||
validity = np.asarray([[0.75, 0, 1, 1]], dtype=np.float32)
|
||||
alpha = np.asarray([[0.8, 1, 0, 1]], dtype=np.float32)
|
||||
points = np.arange(12, dtype=np.float32).reshape(1, 4, 3)
|
||||
frame = GeometryFrame(
|
||||
depth=points[..., 2], points=points,
|
||||
validity=validity.copy(), intrinsics=np.eye(3, dtype=np.float32),
|
||||
)
|
||||
output = self.storage_root / "depth.exr"
|
||||
with patch.object(depth, "_write_float_exr") as write:
|
||||
depth.write_depth_texture(frame, output, alpha=alpha)
|
||||
packed = write.call_args.args[0]
|
||||
np.testing.assert_allclose(packed[..., 3], [[0.6, 0, 0, 1]])
|
||||
np.testing.assert_array_equal(packed[..., :3], points)
|
||||
np.testing.assert_array_equal(frame.validity, validity)
|
||||
self.assertEqual(packed.dtype, np.float32)
|
||||
with self.assertRaisesRegex(ValueError, "Alpha.*dimensions"):
|
||||
depth.write_depth_texture(frame, output, alpha=np.ones((4, 1)))
|
||||
self.assertEqual(write.call_count, 1)
|
||||
|
||||
def test_depth_artifacts_store_texture_and_compact_metadata(self):
|
||||
import numpy as np
|
||||
|
||||
@@ -30,7 +54,7 @@ class GeometryArtifactsTest(ServerTestCase):
|
||||
intrinsics=np.eye(3, dtype=np.float32),
|
||||
)
|
||||
|
||||
depth.write_depth_texture(frame, texture_path)
|
||||
depth.write_depth_texture(frame, texture_path, alpha=np.ones_like(validity))
|
||||
depth.write_depth_metadata(frame, calibration, metadata_path)
|
||||
|
||||
self.assertTrue(texture_path.is_file())
|
||||
@@ -93,7 +117,7 @@ class GeometryArtifactsTest(ServerTestCase):
|
||||
)
|
||||
|
||||
|
||||
def test_moge_model_input_composites_rgba_over_black(self):
|
||||
def test_moge_model_input_preserves_rgb_independently_of_alpha(self):
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
@@ -105,30 +129,24 @@ class GeometryArtifactsTest(ServerTestCase):
|
||||
dtype=np.uint8,
|
||||
)
|
||||
second = first.copy()
|
||||
second[0, 0, :3] = (1, 2, 3)
|
||||
second[..., 3] = 255
|
||||
Image.fromarray(first, mode="RGBA").save(first_path)
|
||||
Image.fromarray(second, mode="RGBA").save(second_path)
|
||||
|
||||
first_output = artifacts._limited_model_input(
|
||||
first_path,
|
||||
self.storage_root / "first-model.png",
|
||||
2048,
|
||||
)
|
||||
second_output = artifacts._limited_model_input(
|
||||
second_path,
|
||||
self.storage_root / "second-model.png",
|
||||
2048,
|
||||
)
|
||||
|
||||
with Image.open(first_output) as opened:
|
||||
first_rgb = np.asarray(opened, dtype=np.uint8)
|
||||
with Image.open(second_output) as opened:
|
||||
second_rgb = np.asarray(opened, dtype=np.uint8)
|
||||
np.testing.assert_array_equal(first_rgb, second_rgb)
|
||||
np.testing.assert_array_equal(
|
||||
first_rgb,
|
||||
np.asarray([[(0, 0, 0), (100, 50, 25), (9, 8, 7)]], dtype=np.uint8),
|
||||
)
|
||||
for max_size in (2048, 2):
|
||||
with self.subTest(max_size=max_size):
|
||||
outputs = []
|
||||
for source in (first_path, second_path):
|
||||
output = artifacts._limited_model_input(
|
||||
source, source.with_name(f"model-{source.name}"), max_size,
|
||||
)
|
||||
with Image.open(output) as opened:
|
||||
self.assertEqual(opened.mode, "RGB")
|
||||
outputs.append(np.asarray(opened))
|
||||
np.testing.assert_array_equal(*outputs)
|
||||
self.assertEqual(outputs[0].shape[1], min(3, max_size))
|
||||
if max_size == 2048:
|
||||
np.testing.assert_array_equal(outputs[0], first[..., :3])
|
||||
|
||||
|
||||
def test_geometry_writers_reject_missing_or_non_finite_fields(self):
|
||||
@@ -148,6 +166,7 @@ class GeometryArtifactsTest(ServerTestCase):
|
||||
depth.write_depth_texture(
|
||||
GeometryFrame(**base),
|
||||
self.storage_root / "missing-points.exr",
|
||||
alpha=base["validity"],
|
||||
)
|
||||
point_frame = GeometryFrame(
|
||||
**base,
|
||||
@@ -157,6 +176,7 @@ class GeometryArtifactsTest(ServerTestCase):
|
||||
depth.write_depth_texture(
|
||||
point_frame,
|
||||
self.storage_root / "nan-points.exr",
|
||||
alpha=base["validity"],
|
||||
)
|
||||
depth_frame = GeometryFrame(
|
||||
**base, points=np.asarray([[(0, 0, 1), (0, 0, np.inf)]], dtype=np.float32),
|
||||
@@ -165,6 +185,7 @@ class GeometryArtifactsTest(ServerTestCase):
|
||||
depth.write_depth_texture(
|
||||
depth_frame,
|
||||
self.storage_root / "inf-depth.exr",
|
||||
alpha=base["validity"],
|
||||
)
|
||||
with self.assertRaisesRegex(ValueError, "normal field"):
|
||||
normal.write_camera_normal_texture(
|
||||
@@ -204,7 +225,9 @@ class GeometryArtifactsTest(ServerTestCase):
|
||||
"server.models.geometry"
|
||||
).GeometryFrame
|
||||
input_path = self.storage_root / "selection.png"
|
||||
Image.new("RGBA", (2, 2), (255, 128, 64, 255)).save(input_path)
|
||||
source = Image.new("RGBA", (4, 4), (255, 128, 64, 255))
|
||||
source.paste((255, 128, 64, 0), (2, 0, 4, 2))
|
||||
source.save(input_path)
|
||||
output_directory = self.storage_root / "job"
|
||||
output_directory.mkdir()
|
||||
context = SimpleNamespace(
|
||||
@@ -221,7 +244,11 @@ class GeometryArtifactsTest(ServerTestCase):
|
||||
points=np.ones((2, 2, 3), dtype=np.float32),
|
||||
)
|
||||
|
||||
with patch.object(artifacts.moge2, "infer", return_value=[frame]) as infer:
|
||||
texture = importlib.import_module("server.geometry.depth_texture")
|
||||
with (
|
||||
patch.object(artifacts.moge2, "infer", return_value=[frame]) as infer,
|
||||
patch.object(texture, "_write_float_exr", wraps=texture._write_float_exr) as write,
|
||||
):
|
||||
result = artifacts.generate_moge2_artifacts(
|
||||
context,
|
||||
{"max_input_size": 2048},
|
||||
@@ -230,6 +257,9 @@ class GeometryArtifactsTest(ServerTestCase):
|
||||
normal_mode="OBJECT",
|
||||
)
|
||||
|
||||
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)
|
||||
infer.assert_called_once()
|
||||
self.assertEqual(
|
||||
set(result),
|
||||
@@ -288,6 +318,5 @@ class GeometryArtifactsTest(ServerTestCase):
|
||||
input_path,
|
||||
generate_depth=True,
|
||||
normal_mode="OBJECT",
|
||||
correct_edge_depth=True,
|
||||
alpha_threshold=0.9,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user