move_detect/tests/test_geometry.py
유창욱 e81ea8961c refactor(edge): DRY displacement helper into geometry.track_displacement (FIX 6)
_displacement (mean per-keypoint delta between the last two track frames,
normalized by bbox height) was duplicated verbatim in
edge/detectors/fall.py and edge/detectors/immobility.py.

Move it to edge/geometry.py as track_displacement(track) and have both
detectors call the shared helper instead of their own private copy.
Pure refactor — behavior is identical (byte-identical formula), no
config or public API changes.

Tests: tests/test_geometry.py adds direct coverage for the new public
helper (fewer-than-two-frames default, and the normalized-by-height
calculation). Existing fall/immobility tests are unchanged and stay green,
confirming behavior parity.
2026-07-11 22:21:27 +09:00

56 lines
2.3 KiB
Python

import math
from collections import deque
from edge.models import Keypoint, BBox
from edge.tracking import Track, TrackFrame
from edge.geometry import (
com, torso_angle_deg, head_below_hips, visible_count,
core_visible, normalize, track_displacement,
)
def _kps(overrides):
kps = [Keypoint(0, 0, 0.0) for _ in range(17)]
for i, (x, y, c) in overrides.items():
kps[i] = Keypoint(x, y, c)
return kps
def test_com_is_mean_of_shoulders_and_hips():
kps = _kps({5: (0, 0, 1), 6: (10, 0, 1), 11: (0, 10, 1), 12: (10, 10, 1)})
assert com(kps) == (5.0, 5.0)
def test_torso_angle_upright_near_zero():
# shoulders above hips (smaller y), vertical torso
kps = _kps({5: (0, 0, 1), 6: (10, 0, 1), 11: (0, 100, 1), 12: (10, 100, 1)})
assert torso_angle_deg(kps) < 10
def test_torso_angle_lying_near_ninety():
# shoulders and hips at same height, horizontal torso
kps = _kps({5: (0, 50, 1), 6: (0, 55, 1), 11: (100, 50, 1), 12: (100, 55, 1)})
assert torso_angle_deg(kps) > 80
def test_head_below_hips_true_when_nose_lower_on_screen():
kps = _kps({0: (5, 120, 1), 11: (0, 100, 1), 12: (10, 100, 1)})
assert head_below_hips(kps) is True
def test_visible_count_and_core_visible():
kps = _kps({5: (0, 0, 0.9), 6: (1, 0, 0.9), 11: (0, 1, 0.9), 12: (1, 1, 0.9)})
assert visible_count(kps, thr=0.2) == 4
assert core_visible(kps, thr=0.5) is True
def test_normalize_divides_by_bbox_height():
assert normalize(30, 60) == 0.5
assert normalize(30, 0) == 0.0
def test_track_displacement_defaults_to_one_with_fewer_than_two_frames():
tr = Track(1, deque(maxlen=90))
tr.frames.append(TrackFrame(0.0, BBox(0, 0, 20, 60),
[Keypoint(0, 0, 0.9) for _ in range(17)]))
assert track_displacement(tr) == 1.0
def test_track_displacement_normalizes_mean_delta_by_bbox_height():
tr = Track(1, deque(maxlen=90))
tr.frames.append(TrackFrame(0.0, BBox(0, 0, 20, 60),
[Keypoint(0, 0, 0.9) for _ in range(17)]))
tr.frames.append(TrackFrame(0.1, BBox(0, 0, 20, 60),
[Keypoint(6, 8, 0.9) for _ in range(17)]))
# per-point delta = sqrt(6^2+8^2) = 10; bbox height = 60 -> 10/60
assert math.isclose(track_displacement(tr), 10 / 60)