# Metrics The benchmark evaluates generated motion along three axes: - **Motion quality** -- foot-skate and contact-consistency metrics, - **Constraint following** -- position error for root, end-effector, and full-body constraints, - **Text alignment** -- TMR retrieval and distributional metrics. Metrics are implemented in `kimodo/metrics/` and orchestrated by `benchmark/evaluate_folder.py`. The protocol is aligned with the [tech report](https://research.nvidia.com/labs/sil/projects/kimodo/assets/kimodo_tech_report.pdf) (Sec. 6.1, "Evaluation Metrics"). ## Evaluation Protocol The evaluation pipeline runs two passes over each group of test cases: 1. **Generated pass** -- evaluates `motion.npz` with all metrics (foot skate, contact consistency, constraint following) and, when TMR embeddings are available, computes retrieval and FID scores. 2. **Ground-truth pass** -- evaluates `gt_motion.npz` with the same motion-quality and constraint metrics. TMR retrieval metrics are not recomputed in this pass. Running both passes enables side-by-side comparison: the GT row serves as an empirical upper bound for motion quality, and deviations between GT and generated metrics highlight where the model can improve. See [Evaluation pipeline](pipeline.md) for the full workflow. ## Metrics Reference The table below lists every key written to `metrics.json`. Detailed descriptions follow in subsequent sections. | Key | Category | Unit | Direction | | --- | --- | --- | --- | | `foot_skate_from_height` | Motion quality | m/s | Lower is better | | `foot_skate_from_pred_contacts` | Motion quality | m/s | Lower is better | | `foot_skate_max_vel` | Motion quality | m/s | Lower is better | | `foot_skate_ratio` | Motion quality | ratio (0--1) | Lower is better | | `foot_contact_consistency` | Motion quality | ratio (0--1) | Higher is better | | `constraint_root2d_err` | Constraint follow | m | Lower is better | | `constraint_root2d_err_p95` | Constraint follow | m | Lower is better | | `constraint_root2d_acc` | Constraint follow | ratio (0--1) | Higher is better | | `constraint_fullbody_keyframe` | Constraint follow | m | Lower is better | | `constraint_end_effector` | Constraint follow | m | Lower is better | | `TMR/t2m_sim` | Text alignment | score (0--1) | Higher is better | | `TMR/t2m_R/R01` ... `R10` | Text alignment | % | Higher is better | | `TMR/t2m_R/MedR` | Text alignment | rank | Lower is better | | `TMR/FID/gen_text` | Text alignment | distance | Lower is better | | `TMR/FID/gen_gt` | Text alignment | distance | Lower is better | | `TMR/FID/gt_text` | Text alignment | distance | Lower is better | | `TMR/m2m_sim` | Text alignment | score (0--1) | Higher is better | | `TMR/t2m_gt_sim` | Text alignment | score (0--1) | Higher is better | | `TMR/m2m_R/R01` ... `R10` | Text alignment | % | Higher is better | | `TMR/t2m_gt_R/R01` ... `R10` | Text alignment | % | Higher is better | :::{note} Raw metric values are stored in SI units (meters for positions, m/s for velocities). The summary tables printed by `benchmark/parse_folder.py` convert constraint position errors to **cm** and foot-skate velocities to **cm/s** for readability. ::: ### Foot Skating Metrics Foot skating measures how much a foot slides along the ground when it should be in static contact with the ground. Four complementary metrics capture different aspects of this artifact. - **`foot_skate_from_height`** (m/s, lower is better): Mean velocity of the **toe joints** (left toe, right toe) on frames where the toe height is below a floor threshold (`height_thresh = 0.05 m`). This metric does not rely on predicted contact labels -- it uses a geometric criterion (Y-coordinate < threshold) to identify ground-contact frames. - **`foot_skate_from_pred_contacts`** (m/s, lower is better): Mean velocity of all **four foot joints** (left/right heel and toe) on frames where the model predicts contact via the `foot_contacts` output. Unlike `foot_skate_from_height`, this metric trusts the model's own contact predictions and measures all four foot joints rather than toes only. - **`foot_skate_max_vel`** (m/s, lower is better): Maximum velocity across all four foot joints and all time steps where predicted contact is active. This captures worst-case slip spikes that mean-based metrics can hide. - **`foot_skate_ratio`** (ratio 0--1, lower is better): Fraction of ground-contact frames where toe velocity exceeds a threshold (`vel_thresh = 0.2 m/s`). A frame counts as ground contact when the toe is below `height_thresh = 0.05 m` on both the current and the next frame. Inspired by the [GMD](https://github.com/korrawe/guided-motion-diffusion) skating metric. ### Contact Consistency Metric - **`foot_contact_consistency`** (ratio 0--1, higher is better): Agreement between the model's predicted foot contacts and a heuristic contact detector based on joint height and velocity (`vel_thresh = 0.15 m/s`, `height_thresh = 0.10 m`). Computed as accuracy (`1 - incorrect_ratio`) over all time steps and four contact channels. A score of 1.0 means perfect agreement between predicted and heuristic contacts. As noted in the [tech report](https://research.nvidia.com/labs/sil/projects/kimodo/assets/kimodo_tech_report.pdf), this metric provides important context for interpreting the contact-based foot-skate metrics above: if contact consistency is low, `foot_skate_from_pred_contacts` may be unreliable. ### Constraint-Following Metrics Constraint metrics are computed only when the test case includes a `constraints.json` file. The `ContraintFollow` metric class dispatches by [constraint type](../key_concepts/constraints.md): - **`constraint_end_effector`** (m, lower is better): Mean Euclidean distance between target end-effector positions and generated joint positions at the constrained frames. Only position-constrained joints are evaluated (rotation targets are not measured by this metric). - **`constraint_fullbody_keyframe`** (m, lower is better): Mean per-joint Euclidean distance between target and generated full-body joint positions at keyframes. The error is averaged over all joints and all keyframe frames. - **`constraint_root2d_err`** (m, lower is better): Mean 2D Euclidean distance (in the XZ ground plane) between target and generated root positions at constrained frames. - **`constraint_root2d_err_p95`** (m, lower is better): 95th percentile of the per-frame root 2D error across all samples in a group. Computed during aggregation by `evaluate_folder.py` to capture tail-end failures that the mean can mask. - **`constraint_root2d_acc`** (ratio 0--1, higher is better): Fraction of constrained root frames where the 2D position error is within a distance threshold (`root_threshold = 0.10 m`). ### TMR-Based Metrics Text alignment is evaluated using [TMR](https://mathis.petrovich.fr/tmr/) (Text-to-Motion Retrieval), a separate encoder model that maps both text and motion into a shared embedding space. TMR is not used for generation -- it is loaded only for evaluation (see `kimodo/model/tmr.py`). We release a version of TMR retrained on the full Rigplay dataset as [`TMR-SOMA-RP-v1`](https://huggingface.co/nvidia/TMR-SOMA-RP-v1). The original TMR was trained on HumanML3D; our retrained variant uses the same architecture but is trained on the Rigplay motion dataset, SOMA skeleton, and with [LLM2Vec](https://github.com/McGill-NLP/llm2vec) text embeddings. #### Similarity Scores TMR encodes each text prompt and each motion clip into a unit-length embedding vector. Cosine similarity between text and motion embeddings is rescaled to a [0, 1] range: ``` score = cosine_similarity / 2 + 0.5 ``` Three per-test-case similarity scores are recorded: - **`TMR/t2m_sim`** (0--1, higher is better): similarity between the text prompt and the generated motion. - **`TMR/m2m_sim`** (0--1, higher is better): similarity between the generated and ground-truth motions (only when GT is available). - **`TMR/t2m_gt_sim`** (0--1, higher is better): similarity between the text prompt and the GT motion (only when GT is available). #### R-precision (Retrieval Accuracy) R-precision measures whether the correct motion can be retrieved from a pool given its corresponding text query. For each text query in the evaluation group, all motions are ranked by TMR similarity. R@k is the percentage of queries where the correct motion appears in the top k results. Reported keys: `TMR/t2m_R/R01`, `R02`, `R03`, `R05`, `R10` (%), and `TMR/t2m_R/MedR` (median rank, lower is better) correspond to retrieval accuracy when using generated motions. When ground-truth motions are available, analogous retrieval metrics are computed for motion-to-GT-motion (`TMR/m2m_R/...`) and text-to-GT-motion (`TMR/t2m_gt_R/...`). :::{note} Near-duplicate text prompts can artificially penalize retrieval ranking. The evaluation handles this by grouping prompts whose text-text similarity exceeds a threshold of 0.99 and treating any motion in that group as a valid match. ::: #### FID (Frechet Inception Distance) FID measures distributional distance between two sets of TMR embeddings by fitting a multivariate Gaussian to each set and computing the Frechet distance. Three FID variants are reported: - **`TMR/FID/gen_gt`**: distance between generated-motion and GT-motion embeddings (only when GT is available). This is the FID metric that is typically reported in the motion generation literature. - **`TMR/FID/gen_text`**: distance between generated-motion embeddings and text embeddings. - **`TMR/FID/gt_text`**: distance between GT-motion and text embeddings (only when GT is available). Lower values indicate that the two distributions are more similar. FID requires at least 2 samples; groups with fewer samples report `NaN`. #### Per-Test-Case Retrieval In addition to the aggregate metrics above, each test case's `metrics.json` includes a `tmr` block with single motion retrieval results: - `t2m_rank`: the rank of the correct motion when retrieving with this test case's text query. - `top5_retrieved`: the top-5 retrieved motions (sample IDs and text prompts) for inspection. ## JSON Output Format Below is a representative `metrics.json` written by `evaluate_folder.py` for a single test case with mixed constraints (root + end-effector + full-body) and TMR embeddings: ```json { "num_motions": 1, "folder": "...", "per_motion_mean_gen": { "foot_skate_from_height": 0.3144, "foot_skate_from_pred_contacts": 0.0672, "foot_skate_max_vel": 0.2109, "foot_contact_consistency": 0.9522, "foot_skate_ratio": 0.2182, "constraint_end_effector": 0.0286, "constraint_root2d_err": 0.0534, "constraint_root2d_acc": 1.0, "constraint_fullbody_keyframe": 0.0324, "TMR/t2m_sim": 0.8209 }, "per_motion_mean_gt": { "foot_skate_from_height": 0.2361, "foot_skate_from_pred_contacts": 0.0269, "foot_skate_max_vel": 0.1459, "foot_contact_consistency": 1.0, "foot_skate_ratio": 0.1402, "constraint_end_effector": 9.82e-07, "constraint_root2d_err": 0.0407, "constraint_root2d_acc": 1.0, "constraint_fullbody_keyframe": 8.73e-07 }, "tmr": { "t2m_rank": 2, "text": "A person is swiftly performing a dance move by moving their hands and legs.", "top5_retrieved": [ { "id": "0231", "text": "A person is performing dance steps while stepping back and forward..." }, { "id": "0029", "text": "A person is swiftly performing a dance move by moving their hands and legs." } ] } } ``` Group-level aggregate JSONs (`.json`) have the same structure but with `num_motions > 1`, averaged per-motion metrics, additional keys like `constraint_root2d_err_p95`, and a `tmr` block containing the aggregate retrieval and FID scores: ```json { "num_motions": 256, "folder": "...", "per_motion_mean_gen": { "foot_skate_from_height": 0.1742, "foot_skate_from_pred_contacts": 0.0611, "foot_skate_max_vel": 0.3747, "foot_contact_consistency": 0.9483, "foot_skate_ratio": 0.1499, "constraint_end_effector": 0.0367, "constraint_root2d_err": 0.0495, "constraint_root2d_acc": 0.9212, "constraint_fullbody_keyframe": 0.0324, "constraint_root2d_err_p95": 0.1115 }, "per_motion_mean_gt": { "foot_skate_from_height": 0.1617, "foot_skate_from_pred_contacts": 0.0235, "foot_skate_max_vel": 0.1185, "foot_contact_consistency": 1.0, "foot_skate_ratio": 0.1214, "constraint_end_effector": 1.48e-06, "constraint_root2d_err": 0.0376, "constraint_root2d_acc": 1.0, "constraint_fullbody_keyframe": 1.16e-06, "constraint_root2d_err_p95": 0.0602 }, "tmr": { "TMR/t2m_sim": 0.8742, "TMR/t2m_R/R01": 75.39, "TMR/t2m_R/R02": 85.55, "TMR/t2m_R/R03": 88.28, "TMR/t2m_R/R05": 90.23, "TMR/t2m_R/R10": 93.36, "TMR/t2m_R/MedR": 1.0, "TMR/t2m_R/len": 256.0, "TMR/FID/gen_text": 0.1442, "TMR/m2m_R/R01": 94.53, "TMR/m2m_R/R02": 97.66, "TMR/m2m_R/R03": 98.05, "TMR/m2m_R/R05": 98.83, "TMR/m2m_R/R10": 99.22, "TMR/m2m_R/MedR": 1.0, "TMR/m2m_R/len": 256.0, "TMR/t2m_gt_R/R01": 80.47, "TMR/t2m_gt_R/R02": 88.28, "TMR/t2m_gt_R/R03": 91.02, "TMR/t2m_gt_R/R05": 92.58, "TMR/t2m_gt_R/R10": 94.53, "TMR/t2m_gt_R/MedR": 1.0, "TMR/t2m_gt_R/len": 256.0, "TMR/FID/gen_gt": 0.0387, "TMR/FID/gt_text": 0.1349 } } ```