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:
2026-09-03 01:02:10 +08:00
parent a6041cbe03
commit bdbde1a10d
47 changed files with 1120 additions and 257 deletions

View File

@@ -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",
)