87 lines
3.5 KiB
Python
87 lines
3.5 KiB
Python
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
|