File size: 13,494 Bytes
a550c4e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
# 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 (`<group_name>.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
  }
}
```