Files
hair/tests/test_vertical_lines.py
T
xslandCursor b64313dc73 feat(测量): 接口1/6 返回与标注图一致的竖线坐标
前端需要按标注图重绘竖线,坐标与画线共用同一套几何。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-05 22:18:12 +08:00

84 lines
2.8 KiB
Python

"""竖线坐标:与标注图同一套几何,接口1/6 的返回字段由此而来。"""
import numpy as np
from face_analysis.measure import MeasureResult
from face_analysis.annotation import vertical_line_geometry
def _result():
"""已知像素坐标的测量结果。顶庭 2cm,不会触发发际线修正。"""
vertical = {
"hair_top": (500.0, 100.0),
"hairline": (500.0, 200.2),
"brow_center": (500.0, 350.0),
"nose_bottom": (500.0, 500.0),
"chin_tip": (500.0, 700.4),
"top_court_px": 100.0,
"upper_court_px": 150.0,
"middle_court_px": 150.0,
"lower_court_px": 200.0,
}
eyes = {
"eye_width_px": 80.0,
"face_width_px": 400.0,
"inter_eye_distance_px": 90.0,
"points": {
"left_cheek": (300.4, 400.0),
"left_outer": (360.6, 400.0),
"left_inner": (440.2, 400.0),
"right_inner": (530.8, 400.0),
"right_outer": (610.4, 400.0),
"right_cheek": (700.7, 400.0),
},
}
return MeasureResult(vertical, eyes, 50.0, "estimated", None)
_INNER = [
"left_cheek", "left_eye_outer", "left_eye_inner",
"right_eye_inner", "right_eye_outer", "right_cheek",
]
def test_v1_inner_lines_and_extended_span():
geom = vertical_line_geometry(_result(), 1000, 800, variant="v1")
# 短边 800,伸出 max(6, round(800*0.03)) = 24
assert geom["y_top"] == 76
assert geom["y_bottom"] == 724
assert [line["name"] for line in geom["lines"]] == _INNER
assert [line["x"] for line in geom["lines"]] == [300, 361, 440, 531, 610, 701]
def test_v6_span_is_hairline_to_chin_without_head_edges():
ear = np.zeros((800, 1000), dtype=np.uint8)
ear[120:680, 250] = 255
ear[120:680, 760] = 255
geom = vertical_line_geometry(
_result(), 1000, 800, ear_mask=ear, hair_mask=None, variant="v6")
assert geom["y_top"] == 200
assert geom["y_bottom"] == 700
assert [line["name"] for line in geom["lines"]] == _INNER
def test_v1_includes_ear_edges_left_to_right():
ear = np.zeros((800, 1000), dtype=np.uint8)
ear[120:680, 250] = 255
ear[120:680, 760] = 255
geom = vertical_line_geometry(
_result(), 1000, 800, ear_mask=ear, hair_mask=None, variant="v1")
names = [line["name"] for line in geom["lines"]]
xs = [line["x"] for line in geom["lines"]]
assert names == ["left_head_edge", *_INNER, "right_head_edge"]
assert xs[0] == 250 and xs[-1] == 760
assert xs == sorted(xs)
def test_missing_ear_omits_that_edge():
ear = np.zeros((800, 1000), dtype=np.uint8)
ear[120:680, 250] = 255
geom = vertical_line_geometry(
_result(), 1000, 800, ear_mask=ear, hair_mask=None, variant="v1")
names = [line["name"] for line in geom["lines"]]
assert names[0] == "left_head_edge"
assert "right_head_edge" not in names