Files
AareDAQ/tests/unit/common/test_face_detection.py
T
appleb_m 6f44da0078
Build and Publish / test (push) Failing after 1m4s
Build and Publish / build (push) Has been skipped
Build and Publish / Build and Deploy Docs (push) Has been skipped
tests: added tests for aerotech, models, autofocus, automation, beamline, diffraction_geometry, enum_pv, face_detection_find_xtal, jfjoch_client, mlbox, my_motor, spreadsheet_updater and worfkflows
2026-04-28 14:35:54 +02:00

119 lines
3.8 KiB
Python

import numpy as np
import pytest
from aare.common.face_detection import (
box_height_from_tuple,
box_area_from_tuple,
prepare_samples,
cos_model,
mad_filter,
fit_metrics,
fit_cosine,
get_samples_out,
choose_best_fit,
get_flat_face,
chose_best_angle
)
def test_box_height_from_tuple():
assert box_height_from_tuple((0, 0, 10, 20)) == 20
assert box_height_from_tuple((0, 20, 10, 0)) == 20
def test_box_area_from_tuple():
assert box_area_from_tuple((0, 0, 10, 20)) == 200
assert box_area_from_tuple((0, 20, 10, 0)) == 200
def test_prepare_samples():
boxes = {0: (0, 0, 10, 10), 90: (0, 0, 10, 20)}
samples = prepare_samples(boxes, area=False)
assert (0.0, 10.0) in samples
assert (90.0, 20.0) in samples
samples_area = prepare_samples(boxes, area=True)
assert (0.0, 100.0) in samples_area
assert (90.0, 200.0) in samples_area
def test_cos_model():
# A + B * cos(C * deg2rad(theta) - phi)
# theta=0, A=10, B=5, phi=0, C=1 -> 10 + 5 * cos(0) = 15
assert pytest.approx(cos_model(0, 10, 5, 0, 1)) == 15.0
# theta=180, A=10, B=5, phi=0, C=1 -> 10 + 5 * cos(pi) = 5
assert pytest.approx(cos_model(180, 10, 5, 0, 1)) == 5.0
def test_mad_filter():
samples = [(0, 10), (10, 11), (20, 12), (30, 100), (40, 11)]
# med = 11, devs = [1, 0, 1, 89, 0], mad = median([0, 0, 1, 1, 89]) = 1
# k*mad = 3.5. 100-11 = 89 > 3.5. Outlier.
filtered = mad_filter(samples)
assert (30, 100) not in filtered
assert len(filtered) == 4
assert mad_filter([]) == []
def test_fit_metrics():
y_true = np.array([10, 20, 30])
y_pred = np.array([11, 19, 31])
rmse, mae, r2 = fit_metrics(y_true, y_pred)
assert rmse == pytest.approx(1.0)
assert mae == pytest.approx(1.0)
# ss_tot = (10-20)^2 + (20-20)^2 + (30-20)^2 = 100 + 0 + 100 = 200
# ss_res = 1^2 + (-1)^2 + 1^2 = 3
# r2 = 1 - 3/200 = 0.985
assert r2 == pytest.approx(0.985)
def test_fit_cosine_minimal_samples():
samples = [(0, 10), (90, 20)]
result = fit_cosine(samples)
assert result["A"] == 15.0
assert result["rmse"] is None
def test_fit_cosine_normal():
# Generate perfect cosine data
degs = np.linspace(0, 360, 10)
ys = 10 + 5 * np.cos(np.deg2rad(degs) - 0.5)
samples = list(zip(degs, ys))
result = fit_cosine(samples)
assert result["A"] == pytest.approx(10.0, abs=1e-2)
assert result["B"] == pytest.approx(5.0, abs=1e-2)
assert result["phi_rad"] == pytest.approx(0.5, abs=1e-2)
assert result["r2"] > 0.99
def test_get_samples_out():
boxes = {0: (0, 0, 10, 10), 90: (0, 0, 10, 20)}
out = get_samples_out(boxes)
assert len(out) == 2
assert out[0]["angle_deg"] == 0.0
assert out[0]["height"] == 10.0
assert out[1]["angle_deg"] == 90.0
assert out[1]["height"] == 20.0
def test_choose_best_fit():
fits = {
"Height": {"angle": 45, "params": {"rmse": 0.1, "mae": 0.1, "r2": 0.95}},
"Area": {"angle": 50, "params": {"rmse": 0.05, "mae": 0.05, "r2": 0.98}}
}
angle, fit, name = choose_best_fit(fits)
assert name == "Area"
assert angle == 50.0
assert choose_best_fit({}) == (None, None, None)
def test_get_flat_face():
boxes = {0: (0, 0, 10, 10), 90: (0, 0, 10, 20), 180: (0, 0, 10, 10), 270: (0, 0, 10, 5)}
angle, params = get_flat_face(boxes, 0, 360)
assert isinstance(angle, int)
assert "A" in params
def test_chose_best_angle():
boxes = {0: (0, 0, 10, 10), 90: (0, 0, 10, 20)}
fit_results = {
"Area": {"angle": 88},
"Height": {"angle": 92}
}
# Candidates are 88 and 92. Measured are 0 and 90.
# 88 is closer to 90 than 92 is to 90? No, both are 2 deg away.
# min(abs(88-0), abs(88-90)) = 2
# min(abs(92-0), abs(92-90)) = 2
# It should pick one.
chosen = chose_best_angle(boxes, fit_results)
assert chosen in [88, 92]