119 lines
3.8 KiB
Python
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]
|