Files
tono/tests/node_tests/mask_rectangular.py
2026-04-15 23:58:34 -07:00

144 lines
4.8 KiB
Python

import numpy as np
from tests.node_tests._shared import make_field
def test_mask_rectangular_basic():
from backend.nodes.mask_rectangular import RectangularMask
node = RectangularMask()
field = make_field(data=np.zeros((32, 32)))
mask, = node.process(
field, x1=0.25, y1=0.25, x2=0.75, y2=0.75, square=False, invert=False,
)
assert mask.dtype == np.uint8
assert mask.shape == (32, 32)
# Corners defined by 0.25..0.75 on a 32-wide field → pixels 8..24
assert mask[0, 0] == 0
assert mask[16, 16] == 255
assert np.all(mask[8:24, 8:24] == 255)
assert np.all(mask[:8, :] == 0)
assert np.all(mask[24:, :] == 0)
def test_mask_rectangular_invert():
from backend.nodes.mask_rectangular import RectangularMask
node = RectangularMask()
field = make_field(data=np.zeros((32, 32)))
mask, = node.process(
field, x1=0.25, y1=0.25, x2=0.75, y2=0.75, square=False, invert=True,
)
assert mask[0, 0] == 255
assert mask[16, 16] == 0
def test_mask_rectangular_corner_inputs_override_widgets():
from backend.nodes.mask_rectangular import RectangularMask
node = RectangularMask()
field = make_field(data=np.zeros((32, 32)))
mask, = node.process(
field, x1=0.0, y1=0.0, x2=1.0, y2=1.0, square=False, invert=False,
corner_a=(0.5, 0.5), corner_b=(1.0, 1.0),
)
# Corner override → rectangle is the lower-right quadrant (pixels 16..32)
assert mask[0, 0] == 0
assert mask[24, 24] == 255
assert np.all(mask[16:32, 16:32] == 255)
assert np.all(mask[:16, :16] == 0)
def test_mask_rectangular_reversed_corners():
"""x2 < x1 or y2 < y1 should still produce the same rectangle."""
from backend.nodes.mask_rectangular import RectangularMask
node = RectangularMask()
field = make_field(data=np.zeros((32, 32)))
forward, = node.process(
field, x1=0.25, y1=0.25, x2=0.75, y2=0.75, square=False, invert=False,
)
reversed_, = node.process(
field, x1=0.75, y1=0.75, x2=0.25, y2=0.25, square=False, invert=False,
)
assert np.array_equal(forward, reversed_)
def test_mask_rectangular_clamps_out_of_bounds():
from backend.nodes.mask_rectangular import RectangularMask
node = RectangularMask()
field = make_field(data=np.zeros((16, 16)))
mask, = node.process(
field, x1=-0.5, y1=-0.5, x2=2.0, y2=2.0, square=False, invert=False,
)
assert mask.shape == (16, 16)
assert np.all(mask == 255)
def test_mask_rectangular_square_shrinks_longer_side():
"""With square=True on a square field, the longer side collapses to the shorter."""
from backend.nodes.mask_rectangular import RectangularMask
node = RectangularMask()
field = make_field(data=np.zeros((64, 64)))
# Non-square fractional region: 0.1..0.9 in x (80% wide), 0.1..0.5 in y (40% tall).
# With square=True the shorter dimension (y, 40%) wins; x shrinks to match.
mask, = node.process(
field, x1=0.1, y1=0.1, x2=0.9, y2=0.5, square=True, invert=False,
)
ys, xs = np.where(mask == 255)
assert ys.size > 0
width = xs.max() - xs.min() + 1
height = ys.max() - ys.min() + 1
assert width == height, f"expected square, got {width}x{height}"
def test_mask_rectangular_square_physical_aspect():
"""On a field with non-square physical aspect, 'square' is physical, not pixel."""
from backend.nodes.mask_rectangular import RectangularMask
node = RectangularMask()
# xreal = 2e-6, yreal = 1e-6 — so a physical square covers twice the x-fraction of the y-fraction.
field = make_field(data=np.zeros((64, 64)), xreal=2e-6, yreal=1e-6)
# Start with a region 0.1..0.9 in x (0.8 frac, 1.6e-6 phys) and 0.1..0.9 in y (0.8 frac, 0.8e-6 phys).
# Shorter physical side = 0.8e-6. In x that's 0.4 fraction → shrink x to 0.1..0.5.
mask, = node.process(
field, x1=0.1, y1=0.1, x2=0.9, y2=0.9, square=True, invert=False,
)
ys, xs = np.where(mask == 255)
assert ys.size > 0
# The selected region in pixels should be roughly 0.1..0.5 in x (pixels ~6..32)
# and 0.1..0.9 in y (pixels ~6..58)
assert xs.max() < 40
assert ys.max() > 50
def test_mask_rectangular_emits_overlay():
from backend.execution_context import active_node, execution_callbacks
from backend.nodes.mask_rectangular import RectangularMask
node = RectangularMask()
field = make_field(data=np.zeros((32, 32)))
overlays = []
with execution_callbacks(
overlay=lambda nid, d: overlays.append(d),
), active_node("test"):
node.process(
field, x1=0.1, y1=0.1, x2=0.9, y2=0.9, square=False, invert=False,
)
assert len(overlays) == 1
assert overlays[0]["kind"] == "crop_box"
assert overlays[0]["section_title"] == "Preview"
assert overlays[0]["x1"] == 0.1
assert overlays[0]["image"].startswith("data:image/png;base64,")