Spaces:
Paused
Paused
| """Quadruped pose to gait features: §24 from the keypoints onward. | |
| The pose model is not here. What is here is everything a pose model feeds: | |
| a keypoint vocabulary that more than one model can be mapped into | |
| (`vocabulary`), a body frame that removes the animal's translation and its | |
| changing apparent size (`tracks`), and the §24 features plus a screen that | |
| cannot express a diagnosis (`gait`). | |
| That boundary is drawn where it is because of the licences, which are the part of | |
| §24 most likely to be discovered late. | |
| ## Licence positions, recorded because they decide what can be sold | |
| **DeepLabCut SuperAnimal-Quadruped — Modified MIT, academic and non-commercial | |
| use only.** The model card on Hugging Face states it plainly: use is restricted | |
| to academic and non-commercial purposes, the model "may not be used to harm any | |
| animal deliberately", and commercial licensing is available on application to | |
| Prof. Mackenzie W. Mathis or EPFL's technology transfer office. §24 names this | |
| model first, and nothing about that is a reason not to evaluate it — but Animap | |
| is a commercial product, so shipping it would need that licence. Read on | |
| 2026-08-22 from | |
| `https://huggingface.co/mwmathis/DeepLabCutModelZoo-SuperAnimal-Quadruped`. | |
| **ViTPose — Apache-2.0 for the code and the Hugging Face ports.** The AP-10K | |
| vocabulary it can be run against covers 54 species including cattle. It is the | |
| alternative §36 requires to be tried, and it is the one with no commercial | |
| obstacle. | |
| `experiments/cattle_gait/README.md` records which of these actually ran and what | |
| it produced. This docstring records only what their terms say. | |
| """ | |
| from __future__ import annotations | |
| from app.adapters.base import ( | |
| Adapter, | |
| AdapterSpec, | |
| Availability, | |
| Modality, | |
| Placement, | |
| Task, | |
| ) | |
| from app.adapters.pose.gait import ( | |
| GaitFeatures, | |
| GaitScreen, | |
| LimbCycle, | |
| PairAsymmetry, | |
| STATEMENTS, | |
| Verdict, | |
| features, | |
| screen, | |
| ) | |
| from app.adapters.pose.tracks import ( | |
| BodyFrame, | |
| PoseSequence, | |
| TrackUnusable, | |
| body_frame, | |
| in_body_frame, | |
| ) | |
| from app.adapters.pose.vocabulary import ( | |
| AP10K, | |
| AP10K_ORDER, | |
| CONTRALATERAL_PAIRS, | |
| SUPERANIMAL_QUADRUPED, | |
| VOCABULARIES, | |
| Landmark, | |
| missing, | |
| ) | |
| GAIT_FEATURES_SPEC = AdapterSpec( | |
| adapter_id="gait-features", | |
| runtime="opencv-numpy", | |
| tasks=(Task.MEASURE,), | |
| modalities=(Modality.VIDEO,), | |
| directive_role=( | |
| "§24 cattle gait — stride timing, left/right symmetry, hoof " | |
| "trajectories, back-line movement, head movement and stance duration, " | |
| "derived from quadruped keypoint tracks. The pose model itself is a " | |
| "separate adapter; this is the deterministic half §4 prefers." | |
| ), | |
| requires_artefact=False, | |
| placement=Placement.CPU_SERVICE, | |
| placement_reason=( | |
| "Gradients and one FFT over a few hundred frames of keypoints — " | |
| "microseconds. It sits wherever the pose model does, and the pose model " | |
| "is the thing that needs a GPU." | |
| ), | |
| notes=( | |
| "**No clinical threshold exists and none is offered.** `screen()` " | |
| "requires the caller to supply the asymmetry index it judges against, " | |
| "because deriving one needs cattle a vet locomotion-scored and this " | |
| "code has never seen any. What `experiments/cattle_gait` measures " | |
| "instead is the method's own resolution — the smallest asymmetry it can " | |
| "separate from its noise — which is a different claim and is labelled " | |
| "as one." | |
| ), | |
| ) | |
| class GaitFeatureAdapter(Adapter): | |
| """§24 from the keypoints onward. Always available; never diagnoses.""" | |
| spec = GAIT_FEATURES_SPEC | |
| def availability(self) -> Availability: | |
| return Availability(True) | |
| def load(self) -> "GaitFeatureAdapter": | |
| return self | |
| def measure(self, sequence: PoseSequence) -> GaitFeatures: | |
| return features(sequence) | |
| def screen(self, sequence: PoseSequence, *, threshold: float, | |
| threshold_basis: str = "unstated") -> GaitScreen: | |
| return screen(sequence, threshold=threshold, threshold_basis=threshold_basis) | |
| __all__ = [ | |
| "AP10K", | |
| "AP10K_ORDER", | |
| "CONTRALATERAL_PAIRS", | |
| "GAIT_FEATURES_SPEC", | |
| "STATEMENTS", | |
| "SUPERANIMAL_QUADRUPED", | |
| "VOCABULARIES", | |
| "BodyFrame", | |
| "GaitFeatureAdapter", | |
| "GaitFeatures", | |
| "GaitScreen", | |
| "Landmark", | |
| "LimbCycle", | |
| "PairAsymmetry", | |
| "PoseSequence", | |
| "TrackUnusable", | |
| "Verdict", | |
| "body_frame", | |
| "features", | |
| "in_body_frame", | |
| "missing", | |
| "screen", | |
| ] | |