Refactor image processing and cutout functionalities

- Updated the naming convention for upscaled images to include underscores and file extensions.
- Renamed properties related to cutout pixel size to point spacing for clarity.
- Adjusted alpha region calculations to use a half-alpha threshold for better accuracy.
- Enhanced the handling of foreground mesh masks to consider half-alpha values.
- Introduced new functions for unbounded selections and cutout images, ensuring proper handling of source and selection images.
- Improved test coverage for cutout functionalities, including bounds mapping and alpha preservation.
- Updated tests to reflect changes in naming conventions and functionality adjustments.
This commit is contained in:
2026-08-27 12:01:47 +08:00
parent 2b79a342b0
commit 6c8358f92f
27 changed files with 1058 additions and 223 deletions

View File

@@ -2,6 +2,7 @@ import builtins
import sys
import tempfile
from pathlib import Path
from types import SimpleNamespace
from unittest.mock import patch
import numpy as np
@@ -169,6 +170,34 @@ def test_alpha_regions_accepts_blender_float_alpha_with_a_hole():
assert len(regions[0]) == 2
def test_cutout_crop_bounds_map_to_the_source_plane():
source = SimpleNamespace(
data=SimpleNamespace(size=(200, 100)),
empty_display_size=2.0,
empty_image_offset=(-0.5, -0.5),
)
bounds = mesh._crop_plane_bounds(
source,
(50, 25, 150, 75),
(200, 100),
)
np.testing.assert_allclose(bounds, (-0.5, 0.5, -0.25, 0.25))
def test_depth_surface_cutout_uses_half_alpha_boundary():
alpha = np.zeros((12, 16), dtype=np.float32)
alpha[2:10, 2:14] = 0.49
alpha[4:8, 5:11] = 0.51
regions, size = mesh._cutout_regions(alpha, 64, threshold=0.5)
contour = np.asarray(regions[0][0])
assert size == (16, 12)
np.testing.assert_allclose(contour.min(axis=0), (5.0, 4.0))
np.testing.assert_allclose(contour.max(axis=0), (11.0, 8.0))
def test_blender_cutout_alpha_pipeline_does_not_import_pillow():
original_import = builtins.__import__
@@ -254,6 +283,21 @@ def test_blender_cutout_contour_resolution_tracks_mesh_detail():
assert trace.call_args_list[0].kwargs["max_resolution"] == 512
assert trace.call_args_list[1].kwargs["max_resolution"] == 1024
def test_cutout_point_spacing_uses_the_complete_source_plane():
source = SimpleNamespace(matrix_world=object())
with (
patch.object(mesh, "plane_bounds", return_value=(-2.0, 2.0, -1.0, 1.0)),
patch.object(
mesh,
"_world_plane_metric",
return_value=(((1.0, 0.0), (0.0, 1.0)), 1.0),
),
):
spacing = mesh._cutout_point_spacing(source, 200)
assert spacing == 0.02
def test_cutout_domain_adapts_boundary_spacing_for_a_small_valid_region():
regions = (
(((2046, 2046), (2050, 2046), (2050, 2050), (2046, 2050)),),
@@ -263,7 +307,7 @@ def test_cutout_domain_adapts_boundary_spacing_for_a_small_valid_region():
regions,
(4096, 4096),
(-1.0, 1.0, -1.0, 1.0),
4,
0.5,
((1.0, 0.0), (0.0, 1.0)),
)
@@ -278,7 +322,7 @@ def test_cutout_domain_builds_a_full_image_at_minimum_detail():
regions,
(4096, 4096),
(-1.0, 1.0, -1.0, 1.0),
4,
0.5,
((1.0, 0.0), (0.0, 1.0)),
)
@@ -299,7 +343,7 @@ def test_cutout_domain_keeps_overlapping_regions_disconnected():
regions,
(120, 80),
(0.0, 1.0, 0.0, 1.0),
40,
0.025,
((1.0, 0.0), (0.0, 1.0)),
)