Files
tono/tests/node_tests/level_facet.py
2026-03-29 16:42:55 -07:00

69 lines
3.3 KiB
Python

import numpy as np
from backend.data_types import DataField
from tests.node_tests._shared import make_field
def test_facet_level():
from backend.node_registry import get_node_info
from backend.nodes.level_facet import FacetLevelField
from backend.nodes.level_plane import PlaneLevelField
def fit_pixel_plane(data, region):
yy, xx = np.mgrid[0:data.shape[0], 0:data.shape[1]]
A = np.column_stack([np.ones(int(np.count_nonzero(region))), xx[region].astype(np.float64), yy[region].astype(np.float64)])
coeffs, _, _, _ = np.linalg.lstsq(A, data[region].ravel().astype(np.float64), rcond=None)
return float(coeffs[0]), float(coeffs[1]), float(coeffs[2])
node = FacetLevelField()
plane_node = PlaneLevelField()
assert get_node_info("FacetLevelField")["category"] == "Level & Correct"
N = 96
yy, xx = np.mgrid[0:N, 0:N]
base = 0.055 * xx + 0.028 * yy
terraces = np.zeros((N, N), dtype=np.float64)
terraces[:, 54:] += 6.0
terraces[18:70, 68:88] += 3.5
field = make_field(data=base + terraces)
plane_leveled, = plane_node.process(field)
facet_leveled, = node.process(field, masking="ignore")
left_region = xx < 48
right_region = (xx > 60) & ~((yy >= 18) & (yy < 70) & (xx >= 68) & (xx < 88))
_, plane_left_bx, plane_left_by = fit_pixel_plane(plane_leveled.data, left_region)
_, plane_right_bx, plane_right_by = fit_pixel_plane(plane_leveled.data, right_region)
_, facet_left_bx, facet_left_by = fit_pixel_plane(facet_leveled.data, left_region)
_, facet_right_bx, facet_right_by = fit_pixel_plane(facet_leveled.data, right_region)
plane_slope = float(max(np.hypot(plane_left_bx, plane_left_by), np.hypot(plane_right_bx, plane_right_by)))
facet_slope = float(max(np.hypot(facet_left_bx, facet_left_by), np.hypot(facet_right_bx, facet_right_by)))
assert facet_slope < plane_slope * 1e-6
mask = np.zeros((N, N), dtype=np.uint8)
mask[24:72, 24:72] = 255
base_only = 0.035 * xx + 0.014 * yy
masked_facet = 5.0 - 0.065 * xx + 0.045 * yy
competing = np.where(mask > 0, masked_facet, base_only)
competing_field = make_field(data=competing)
excluded, = node.process(competing_field, masking="exclude", mask=mask)
included, = node.process(competing_field, masking="include", mask=mask)
outer_region = (mask == 0) & (xx > 4) & (xx < N - 4) & (yy > 4) & (yy < N - 4)
inner_region = (mask > 0) & (xx > 28) & (xx < 68) & (yy > 28) & (yy < 68)
_, excl_outer_bx, excl_outer_by = fit_pixel_plane(excluded.data, outer_region)
_, excl_inner_bx, excl_inner_by = fit_pixel_plane(excluded.data, inner_region)
_, incl_outer_bx, incl_outer_by = fit_pixel_plane(included.data, outer_region)
_, incl_inner_bx, incl_inner_by = fit_pixel_plane(included.data, inner_region)
assert float(np.hypot(excl_outer_bx, excl_outer_by)) < float(np.hypot(incl_outer_bx, incl_outer_by)) * 0.2
assert float(np.hypot(incl_inner_bx, incl_inner_by)) < float(np.hypot(excl_inner_bx, excl_inner_by)) * 0.2
bad_units = DataField(data=np.zeros((16, 16), dtype=np.float64), xreal=1e-6, yreal=1e-6, si_unit_xy="nm", si_unit_z="V")
try:
node.process(bad_units, masking="ignore")
except ValueError as exc:
assert "compatible XY and Z units" in str(exc)
else:
assert False, "Facet level should reject incompatible XY/Z units."