import numpy as np import pytest from tests.node_tests._shared import make_field def run_tip_shape(field, feature_type="edge", feature_radius=100e-9, n_points=100): from backend.nodes.tip_shape_estimate import TipShapeEstimate node = TipShapeEstimate() tip_shape, parameters = node.process( field=field, feature_type=feature_type, feature_radius=feature_radius, n_points=n_points, ) return tip_shape, parameters # ── Output shape and type ──────────────────────────────────────────────────── def test_output_shape(): """tip_shape must be a 2D DataField.""" from backend.data_types import DataField field = make_field(shape=(64, 64), xreal=64e-9, yreal=64e-9) tip_shape, _ = run_tip_shape(field, feature_type="edge", n_points=33) assert isinstance(tip_shape, DataField) assert tip_shape.data.ndim == 2 assert tip_shape.data.shape[0] == tip_shape.data.shape[1] # ── Parameters table ───────────────────────────────────────────────────────── def test_parameters_table(): """parameters must be a RecordTable containing a tip_radius entry.""" from backend.data_types import RecordTable field = make_field(shape=(64, 64), xreal=64e-9, yreal=64e-9) _, parameters = run_tip_shape(field, feature_type="edge", n_points=33) assert isinstance(parameters, RecordTable) quantities = {row["quantity"] for row in parameters} assert "tip_radius" in quantities # ── Tip apex is maximum ────────────────────────────────────────────────────── def test_tip_apex_is_maximum(): """The centre of the tip shape should be the highest point.""" field = make_field(shape=(64, 64), xreal=64e-9, yreal=64e-9) tip_shape, _ = run_tip_shape(field, feature_type="edge", n_points=33) n = tip_shape.data.shape[0] ci = n // 2 assert tip_shape.data[ci, ci] == pytest.approx(tip_shape.data.max(), abs=1e-20) # ── Sphere feature produces valid estimate ─────────────────────────────────── def test_sphere_feature(): """Using a synthetic sphere as input produces a valid tip estimate.""" from backend.data_types import DataField # Create a synthetic sphere on a 64x64 grid. R = 100e-9 # sphere radius n = 64 pixel_size = 10e-9 xreal = n * pixel_size Y, X = np.mgrid[:n, :n] r = np.sqrt((X - 32) ** 2 + (Y - 32) ** 2) * pixel_size data = np.sqrt(np.maximum(R ** 2 - r ** 2, 0.0)) field = make_field(data=data, xreal=xreal, yreal=xreal) tip_shape, parameters = run_tip_shape( field, feature_type="sphere", feature_radius=R, n_points=33, ) # The output must be a valid 2D DataField. assert isinstance(tip_shape, DataField) assert tip_shape.data.ndim == 2 # Apex should be the maximum. ci = tip_shape.data.shape[0] // 2 assert tip_shape.data[ci, ci] == pytest.approx(tip_shape.data.max(), abs=1e-20) # Parameters should contain tip_radius. quantities = {row["quantity"]: row["value"] for row in parameters} assert "tip_radius" in quantities # The estimated radius must be a positive finite number. assert quantities["tip_radius"] > 0