Skip to content

[Bug]: trajectory_metrics silently omits final_pose_speed on low-frequency streams and skews final_pose_unsettled_ratio with unweighted np.mean #700

Description

@shobhitagnihotri69

Version or commit

Current main (3dbf43b)

Problem & Invariant Violation

The documented contract in src/hflow/checks.py:1344-1349 specifies:

"{topic}/final_pose_unsettled_ratio is emitted only when {topic}/mean_velocity is above zero. For a fully motionless episode the ratio is undefined (zero denominator), so only {topic}/final_pose_speed appears. Curation queries joining on final_pose_unsettled_ratio will silently exclude motionless episodes; use final_pose_speed when you need all episodes regardless of mean motion."

This promises that final_pose_speed is always emitted for all episodes. Furthermore, HFlow's motion metrics strictly weight steps by duration (mean_velocity, idle_fraction, _duration_weighted_median) so irregular sampling does not skew results.

However, in checks.py:1424-1433:

  1. final_window_mask evaluates (profile.stamps_ns[-1] - profile.stamps_ns[:-1]) / 1e9 <= final_pose_window_s. Because profile.stamps_ns[:-1] is the START of each step, any stream whose sample period exceeds final_pose_window_s (e.g. 1 Hz or 2 Hz streams with the default 0.5s window) has zero steps satisfying the mask. Both final_pose_speed and final_pose_unsettled_ratio are silently omitted despite the robot moving actively. Curation queries joining on final_pose_speed drop these episodes completely.
  2. When steps do match, final_speed is calculated using unweighted np.mean(profile.speeds[final_window_mask]). Irregular sampling in the final window distorts final_speed by orders of magnitude and produces severe false positives in final_pose_unsettled_ratio.

Minimal Reproduction

import asyncio
from pathlib import Path
import tempfile
import hflow
from hflow.testing import synthesize_episode, SyntheticEpisodeSpec

with tempfile.TemporaryDirectory() as tmp_dir:
    mcap_path = Path(tmp_dir) / "test.mcap"
    synthesize_episode(
        mcap_path,
        SyntheticEpisodeSpec(
            duration_s=5.0,
            cameras=(),
            joint_hz=1.0,
            joint_jump_at_s=None,
        ),
    )
    with hflow.Episode(mcap_path) as ep:
        result = asyncio.run(hflow.checks.trajectory_metrics(ep))
        assert "/joint_states/final_pose_speed" in result.measurements, (
            "CRITICAL BUG: final_pose_speed was silently omitted!"
        )
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions