feat(quality): score every face on sharpness and alignment before it is evidence
Every embedding now carries the quality of the input it came from. Both axes fall out of the AR-005 warp for free: crop_sharpness() is the normalised Laplacian variance over the aligned 112x112, so contrast and size cannot leak into it, and the alignment residual is the part of the landmark deformation a similarity transform cannot explain, so in-plane roll reads as zero and foreshortening does not. Carried, not consumed. Nothing discounts or thresholds on either number yet -- that is AR-030 and VR-012, and the knee has to be located against recorded data before a gate is chosen. What this change buys is that the data exists to locate it with. No face is admitted unscored: the -1 sentinel is preserved rather than clamped, and a degenerate landmark fit is counted rather than silently dropped. Takes the VR-001 dump to schema_version 2. The bump is not for readers, which check for the datasets by name and replay a v1 dump unchanged; it is so a consumer can tell "never scored" from "scored zero", which is not recoverable from the arrays afterwards. TRACES: AR-028, AR-029, AR-030 | VR-001 | SR-002
This commit is contained in:
@@ -63,6 +63,14 @@ def load_frames(dump_path: str, min_conf: float = 0.0):
|
||||
bbox = f["faces/bbox"][:]
|
||||
lmk = f["faces/landmarks"][:]
|
||||
conf = f["faces/confidence"][:]
|
||||
# TRACES: AR-028 | SR-002
|
||||
# The quality vector, present from schema v2. A v1 dump predates AR-028
|
||||
# and simply has no such dataset — read as absent, never as a default,
|
||||
# so a face from an old dump stays at the C++ -1 "unscored" sentinel
|
||||
# rather than acquiring a fabricated sharpness of 0 (which is a real
|
||||
# value on this axis, meaning a featureless crop).
|
||||
qual = {k: f[f"faces/{k}"][:] for k in ("sharpness", "alignment_residual")
|
||||
if f"faces/{k}" in f}
|
||||
movie = f.attrs.get("movie", "")
|
||||
fps = float(f.attrs.get("sample_fps", 1.0))
|
||||
|
||||
@@ -81,6 +89,8 @@ def load_frames(dump_path: str, min_conf: float = 0.0):
|
||||
"landmarks": np.ascontiguousarray(lmk[keep][sel], dtype=np.float32),
|
||||
"confidence": np.ascontiguousarray(c[sel], dtype=np.float32),
|
||||
"embeddings": np.ascontiguousarray(emb[keep][sel], dtype=np.float32),
|
||||
**{k: np.ascontiguousarray(v[keep][sel], dtype=np.float32)
|
||||
for k, v in qual.items()},
|
||||
})
|
||||
else:
|
||||
frames.append({
|
||||
@@ -90,6 +100,8 @@ def load_frames(dump_path: str, min_conf: float = 0.0):
|
||||
"landmarks": np.ascontiguousarray(lmk[keep], dtype=np.float32),
|
||||
"confidence": c,
|
||||
"embeddings": np.ascontiguousarray(emb[keep], dtype=np.float32),
|
||||
**{k: np.ascontiguousarray(v[keep], dtype=np.float32)
|
||||
for k, v in qual.items()},
|
||||
})
|
||||
last_ts = float(ts[-1]) if len(ts) else 0.0
|
||||
frames.append({"timestamp_sec": last_ts, "eof": True})
|
||||
|
||||
Reference in New Issue
Block a user