representation: Literal["coords", "angles"] on dtw_all and
dtw_per_joint. "coords" preserves the 0.1 behaviour; "angles" runs
extract_joint_angles on caller-supplied angle_triplets before DTW,
giving translation-, rotation-, and scale-invariant distances that
are directly interpretable as clinical joint-range comparisons. Under
dtw_per_joint the "unit" becomes one angle column per triplet.
nan_policy: Literal["propagate", "interpolate", "drop"] on all three
entry points. "propagate" (default) lets NaN hit fastdtw, which raises
ValueError via numpy.asarray_chkfinite — the safest default because it
surfaces degenerate-vector problems rather than silently corrupting a
distance. "interpolate" runs 1D linear interpolation per feature
column; "drop" removes NaN frames before DTW.
dtw_relation stays a standalone convenience entry point. Paper C's
typical call becomes dtw_all(representation="angles",
align="procrustes_per_sequence"); see TECHNICAL.md Phase 0.
segment_gait_cycles wraps segment_predictions with a joint_axis
extractor and gait-appropriate defaults (joint="rhee", axis="y",
min_cycle_seconds=0.4). The joint name resolves through
joint_index; axis is a "x"/"y"/"z" string literal converted to the
numeric index internally. An invert flag flips peaks and valleys
for recording conventions where a heel-strike appears as a local
minimum.
segment_gait_cycles_bilateral composes the single-side function
twice and returns a {"left_heel_strikes", "right_heel_strikes"}
dict shape-compatible with VideoPredictions.segmentations, so the
caller can merge it directly into a predictions object.
Pathological gaits (shuffling, walker-assisted) degrade to an
empty segments list rather than raising, inherited from
segment_by_peaks' peak-not-found behaviour.
Closes the gait-cycle segmentation item in TECHNICAL.md Phase 0.
Pure formatter drift picked up after running ruff format across the
tree. No behavioural changes: line-length unwrapping where strings
fit on one line, one blank-line separator added to _model.py.
Three-layer module: find_neuropose_processes() scans the process
table via psutil for running watch/serve instances; terminate_processes()
SIGINTs with a configurable grace period before optional SIGKILL
escalation; wipe_state() clears $data_dir/in/, out/, failed/,
the .neuropose.lock file, and leftover .ingest_<uuid>/ staging dirs
while preserving the container directories themselves. reset_pipeline()
composes the three and refuses to wipe while any process survives
termination.
CLI wraps it with --yes/-y, --keep-failed, --force-kill,
--grace-seconds, and --dry-run/-n. Always prints a preview before
prompting; returns EXIT_USAGE=2 when survivors block the wipe.
Unblocks the Mac benchmark iteration loop where partially-complete
runs need to be cleared between experiments.
procrustes_align in neuropose.analyzer.features — Kabsch closed-form
rigid alignment between two pose sequences, with per_frame and
per_sequence modes and an optional scale flag for cross-subject
comparisons. Returns aligned arrays plus an AlignmentDiagnostics
dataclass reporting rotation magnitude (mean and max), translation
magnitude (mean and max), and scale factor, so downstream code can
flag suspiciously large transforms.
Wired into every DTW entry point via a new keyword-only align
parameter — "none" (the default) preserves the 0.1 raw-coordinate
behaviour, while "procrustes_per_frame" and "procrustes_per_sequence"
route inputs through procrustes_align before DTW runs. Rejects
mismatched frame counts when alignment is requested (Procrustes
requires a 1:1 correspondence).
Phase 0 of TECHNICAL.md: closes one of the three methodological
gaps Paper C's pipeline is waiting on.
Captures the MeTRAbs SHA-256 and filename plus tensorflow /
tensorflow-metal / numpy / neuropose / python versions, and reserves
slots for seed, deterministic, and analysis_config. Populated
automatically by Estimator.process_video when the model was loaded via
load_model; propagates into JobResults and BenchmarkResult via the
existing output path. None on the injected-model test path where no
SHA is known.
_model.load_metrabs_model now returns a LoadedModel dataclass so the
estimator can bundle the TF handle with the pinned SHA without
re-hashing the tarball on every daemon startup. All test fakes and
the integration smoke tests updated to unwrap .model.
Bumps the optional schema_version field on VideoPredictions and
BenchmarkResult to default=CURRENT_VERSION so fresh writes stamp the
latest version; legacy payloads without it are migrated on load via
the chain registered in the previous commit.
One shared CURRENT_VERSION across the three top-level serialised
payloads (VideoPredictions, JobResults, BenchmarkResult), with
per-schema registries populated via register_*_migration(from_version)
decorators. FutureSchemaError and MigrationNotFoundError surface bad
chains clearly. CURRENT_VERSION=2 with v1→v2 migrations registered
that add an optional provenance field to the payload dicts.
Tested standalone; io.py is wired through the migrator in a follow-up
commit that introduces the Provenance schema those migrations target.