| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import pytest |
|
|
| pytest.importorskip("transformers") |
|
|
| from lerobot.data_processing.sarm_annotations.subtask_annotation import ( |
| Subtask, |
| SubtaskAnnotation, |
| Timestamp, |
| compute_temporal_proportions, |
| ) |
|
|
|
|
| def make_annotation(subtasks: list[tuple[str, int, int]]) -> SubtaskAnnotation: |
| """Helper to create SubtaskAnnotation from list of (name, start_sec, end_sec).""" |
| return SubtaskAnnotation( |
| subtasks=[ |
| Subtask( |
| name=name, |
| timestamps=Timestamp( |
| start=f"{start // 60:02d}:{start % 60:02d}", end=f"{end // 60:02d}:{end % 60:02d}" |
| ), |
| ) |
| for name, start, end in subtasks |
| ] |
| ) |
|
|
|
|
| class TestComputeTemporalProportions: |
| """Tests for compute_temporal_proportions (SARM Paper Formula 1). |
| |
| Formula: ᾱ_k = (1/M) × Σ_i (L_{i,k} / T_i) |
| |
| Key insight: This averages the PROPORTION of each subtask within each trajectory, |
| giving equal weight to all trajectories regardless of absolute length. |
| """ |
|
|
| def test_basic_two_trajectories_equal_proportions(self): |
| """Test with two trajectories that have equal proportions.""" |
| |
| |
| |
| annotations = { |
| 0: make_annotation([("subtask1", 0, 50), ("subtask2", 50, 100)]), |
| 1: make_annotation([("subtask1", 0, 100), ("subtask2", 100, 200)]), |
| } |
|
|
| result = compute_temporal_proportions(annotations) |
|
|
| |
| assert abs(result["subtask1"] - 0.5) < 1e-6 |
| assert abs(result["subtask2"] - 0.5) < 1e-6 |
|
|
| def test_paper_example_different_from_avg_durations(self): |
| """Test that compute_temporal_proportions differs from naive average duration approach. |
| |
| This is the key test showing the difference between: |
| - Paper formula: average of (L_i,k / T_i) |
| - Naive approach: mean(L_i,k) / sum(mean(L_i,j)) |
| """ |
| |
| |
| annotations = { |
| 0: make_annotation([("subtask1", 0, 80), ("subtask2", 80, 100)]), |
| 1: make_annotation([("subtask1", 0, 40), ("subtask2", 40, 200)]), |
| } |
|
|
| result = compute_temporal_proportions(annotations) |
|
|
| |
| |
| |
| assert abs(result["subtask1"] - 0.5) < 1e-6 |
| assert abs(result["subtask2"] - 0.5) < 1e-6 |
|
|
| def test_single_trajectory(self): |
| """Test with a single trajectory.""" |
| |
| annotations = { |
| 0: make_annotation([("reach", 0, 30), ("grasp", 30, 50), ("lift", 50, 100)]), |
| } |
|
|
| result = compute_temporal_proportions(annotations) |
|
|
| assert abs(result["reach"] - 0.3) < 1e-6 |
| assert abs(result["grasp"] - 0.2) < 1e-6 |
| assert abs(result["lift"] - 0.5) < 1e-6 |
|
|
| def test_sum_to_one(self): |
| """Test that proportions always sum to 1.""" |
| |
| annotations = { |
| 0: make_annotation([("a", 0, 10), ("b", 10, 50), ("c", 50, 100)]), |
| 1: make_annotation([("a", 0, 20), ("b", 20, 70), ("c", 70, 100)]), |
| 2: make_annotation([("a", 0, 30), ("b", 30, 90), ("c", 90, 100)]), |
| } |
|
|
| result = compute_temporal_proportions(annotations) |
|
|
| total = sum(result.values()) |
| assert abs(total - 1.0) < 1e-6 |
|
|
| def test_empty_annotations_returns_empty(self): |
| """Test that empty annotations returns empty dict.""" |
| result = compute_temporal_proportions({}) |
| assert result == {} |
|
|
| def test_uniform_proportions(self): |
| """Test with uniform proportions across subtasks.""" |
| |
| annotations = { |
| 0: make_annotation([("a", 0, 25), ("b", 25, 50), ("c", 50, 75), ("d", 75, 100)]), |
| 1: make_annotation([("a", 0, 50), ("b", 50, 100), ("c", 100, 150), ("d", 150, 200)]), |
| } |
|
|
| result = compute_temporal_proportions(annotations) |
|
|
| for name in ["a", "b", "c", "d"]: |
| assert abs(result[name] - 0.25) < 1e-6 |
|
|