Refactor Cutout and Image Editing Logic
- Renamed "Base Plane Depth" to "Reference Depth" across validate_cutout.py for consistency. - Introduced a new specification for Cutout mesh sampling to ensure source pixel spacing is preserved and limits are applied correctly. - Unified naming conventions for image editing results to maintain consistency across Remove Background, Upscale, and Crop operations. - Updated the handling of Refine Selection to ensure it retains the original bounds and does not apply unnecessary cropping. - Enhanced material generation logic to connect image color directly to IOR Level, improving the rendering of dark areas. - Established clear requirements for geometry depth controls and image result materials to ensure consistent behavior across different components. - Removed outdated logic and ensured all changes are documented and tested for regression.
This commit is contained in:
@@ -700,6 +700,46 @@ class ServerRuntimeTest(unittest.TestCase):
|
||||
self.assertTrue(all(path.is_file() for path in result.values()))
|
||||
self.assertTrue(infer.call_args.kwargs["include_points"])
|
||||
|
||||
def test_refine_selection_preserves_the_full_alpha_canvas(self):
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
refine = importlib.import_module("server.selection.refine_selection")
|
||||
input_path = self.storage_root / "input.png"
|
||||
output_path = self.storage_root / "output.png"
|
||||
source = np.zeros((3, 4, 4), dtype=np.uint8)
|
||||
source[..., :3] = (32, 64, 96)
|
||||
source[..., 3] = np.asarray(
|
||||
((0, 64, 128, 255), (255, 128, 64, 0), (0, 0, 255, 255)),
|
||||
dtype=np.uint8,
|
||||
)
|
||||
Image.fromarray(source, mode="RGBA").save(input_path)
|
||||
foreground = Image.fromarray(source, mode="RGBA")
|
||||
context = SimpleNamespace(
|
||||
resource=lambda _name: SimpleNamespace(directory=lambda _model: "model"),
|
||||
check_cancelled=lambda: None,
|
||||
)
|
||||
|
||||
with patch.object(
|
||||
refine,
|
||||
"infer_foreground",
|
||||
return_value=(foreground, 0.0, 0.0),
|
||||
) as infer:
|
||||
result = refine.refine_selection(
|
||||
context,
|
||||
input_path,
|
||||
"BEN2_BASE",
|
||||
"cpu",
|
||||
output_path,
|
||||
)
|
||||
|
||||
self.assertEqual(result, output_path)
|
||||
infer.assert_called_once()
|
||||
self.assertTrue(infer.call_args.kwargs["preserve_input_alpha"])
|
||||
with Image.open(output_path) as output:
|
||||
self.assertEqual(output.size, (4, 3))
|
||||
np.testing.assert_array_equal(np.asarray(output.convert("RGBA")), source)
|
||||
|
||||
def test_generate_cutout_artifacts_only_returns_requested_outputs(self):
|
||||
job = sys.modules["server.jobs.cutout"]
|
||||
input_path = self.storage_root / "input.png"
|
||||
@@ -750,7 +790,7 @@ class ServerRuntimeTest(unittest.TestCase):
|
||||
normal_mode="OBJECT",
|
||||
)
|
||||
|
||||
def test_generate_cutout_artifacts_refines_the_bounds_image(self):
|
||||
def test_generate_cutout_artifacts_refines_the_full_bounds_image(self):
|
||||
job = sys.modules["server.jobs.cutout"]
|
||||
input_path = self.storage_root / "input.png"
|
||||
input_path.write_bytes(b"selection")
|
||||
@@ -763,7 +803,7 @@ class ServerRuntimeTest(unittest.TestCase):
|
||||
|
||||
def generate(_context, _input, _model, _device, output):
|
||||
output.write_bytes(b"refined")
|
||||
return output, (2, 3, 8, 9)
|
||||
return output
|
||||
|
||||
with patch.object(job, "refine_selection", side_effect=generate):
|
||||
result = job.run(
|
||||
@@ -778,12 +818,62 @@ class ServerRuntimeTest(unittest.TestCase):
|
||||
|
||||
self.assertEqual(
|
||||
result,
|
||||
{"selection_image": "selection.png"},
|
||||
)
|
||||
self.assertFalse((directory / "selection_bounds.json").exists())
|
||||
|
||||
def test_refined_cutout_artifacts_share_the_full_refine_image(self):
|
||||
job = sys.modules["server.jobs.cutout"]
|
||||
input_path = self.storage_root / "input.png"
|
||||
input_path.write_bytes(b"selection")
|
||||
directory = self.storage_root / "job"
|
||||
context = SimpleNamespace(
|
||||
directory=directory,
|
||||
progress=lambda *_args: None,
|
||||
check_cancelled=lambda: None,
|
||||
)
|
||||
refined_path = directory / "selection.png"
|
||||
|
||||
def refine(_context, _input, _model, _device, output):
|
||||
output.write_bytes(b"refined")
|
||||
return output
|
||||
|
||||
with (
|
||||
patch.object(job, "refine_selection", side_effect=refine),
|
||||
patch.object(
|
||||
job,
|
||||
"generate_moge2_artifacts",
|
||||
return_value={
|
||||
"z_depth": directory / "z-depth.exr",
|
||||
"depth_metadata": directory / "depth.json",
|
||||
"tangent_normal": directory / "tangent-normal.png",
|
||||
},
|
||||
) as generate,
|
||||
):
|
||||
result = job.run(
|
||||
context,
|
||||
{
|
||||
"input": str(input_path),
|
||||
"refine_selection": True,
|
||||
"refine_model": "BEN2_BASE",
|
||||
"device": "cpu",
|
||||
"depth_mode": "Z",
|
||||
"normal_mode": "TANGENT",
|
||||
},
|
||||
)
|
||||
|
||||
self.assertEqual(result["selection_image"], "selection.png")
|
||||
generate.assert_called_once_with(
|
||||
context,
|
||||
{
|
||||
"selection_image": "selection.png",
|
||||
"selection_bounds": "selection_bounds.json",
|
||||
"input": str(input_path),
|
||||
"refine_selection": True,
|
||||
"refine_model": "BEN2_BASE",
|
||||
"device": "cpu",
|
||||
"depth_mode": "Z",
|
||||
"normal_mode": "TANGENT",
|
||||
},
|
||||
)
|
||||
self.assertEqual(
|
||||
(directory / "selection_bounds.json").read_text(encoding="utf-8"),
|
||||
"[2,3,8,9]",
|
||||
refined_path,
|
||||
depth_mode="Z",
|
||||
normal_mode="TANGENT",
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user