Spaces:
Sleeping
Sleeping
| """Tests for phase segmentation and phase-gated scoring.""" | |
| import numpy as np | |
| import pytest | |
| from haptal_curate.phase.segmenter import Phase, extract_phase_windows | |
| from haptal_curate.phase.phase_gated import PhaseGatedScorer | |
| from haptal_curate.metrics.smoothness import SmoothnessMetric | |
| from haptal_curate.types import Demo, DemoDataset | |
| from tests.conftest import make_demo, make_dataset | |
| def _demo_with_gripper_open_at(T: int = 100, open_at: int = 60) -> Demo: | |
| return make_demo(T=T, act_dim=7, gripper_open_at=open_at, demo_id="demo_0") | |
| def test_phase_extraction_basic(): | |
| demo = _demo_with_gripper_open_at(T=100, open_at=60) | |
| windows = extract_phase_windows(demo) | |
| assert set(windows.keys()) == set(Phase) | |
| total = sum(len(v) for v in windows.values()) | |
| assert total == demo.episode_length | |
| def test_pregrasp_before_close(): | |
| demo = _demo_with_gripper_open_at(T=100, open_at=60) | |
| # Gripper starts closed (negatively signed), so pregrasp is 0 timesteps | |
| # because actions[:, -1] starts at -1.0 (closed from the start). | |
| windows = extract_phase_windows(demo) | |
| pregrasp = windows[Phase.PREGRASP] | |
| # All pregrasp indices should be before any close event | |
| assert isinstance(pregrasp, np.ndarray) | |
| def test_phase_labels_used_when_present(): | |
| T = 50 | |
| demo = make_demo(T=T, act_dim=7) | |
| labels = np.zeros(T, dtype=np.int32) | |
| labels[20:35] = 1 # GRASP | |
| labels[35:] = 2 # POST_CONTACT | |
| demo.phase_labels = labels | |
| windows = extract_phase_windows(demo) | |
| assert len(windows[Phase.PREGRASP]) == 20 | |
| assert len(windows[Phase.GRASP]) == 15 | |
| assert len(windows[Phase.POST_CONTACT]) == 15 | |
| def test_phase_windows_cover_all_timesteps(): | |
| for open_at in [10, 50, 90]: | |
| demo = _demo_with_gripper_open_at(T=100, open_at=open_at) | |
| windows = extract_phase_windows(demo) | |
| all_idx = np.concatenate(list(windows.values())) | |
| assert len(np.unique(all_idx)) == demo.episode_length | |
| def test_phase_gated_scorer_uniform(): | |
| dataset = make_dataset(n=10, T=80) | |
| m = SmoothnessMetric() | |
| scorer = PhaseGatedScorer(m, strategy="uniform") | |
| scorer.fit(dataset) | |
| scores = scorer.score_dataset(dataset) | |
| assert scores.shape == (10,) | |
| assert np.all(np.isfinite(scores)) | |
| def test_phase_gated_scorer_best_per_phase(): | |
| dataset = make_dataset(n=10, T=80) | |
| m = SmoothnessMetric() | |
| scorer = PhaseGatedScorer(m, strategy="best_per_phase") | |
| scorer.fit(dataset) | |
| scores = scorer.score_dataset(dataset) | |
| assert scores.shape == (10,) | |
| def test_phase_gated_scorer_global(): | |
| dataset = make_dataset(n=10, T=80) | |
| m = SmoothnessMetric() | |
| scorer = PhaseGatedScorer(m, strategy="global") | |
| scorer.fit(dataset) | |
| scores = scorer.score_dataset(dataset) | |
| assert scores.shape == (10,) | |
| def test_phase_gated_single_score(): | |
| dataset = make_dataset(n=5, T=60) | |
| m = SmoothnessMetric() | |
| scorer = PhaseGatedScorer(m, strategy="uniform") | |
| scorer.fit(dataset) | |
| s = scorer.score(dataset.demos[0]) | |
| assert isinstance(s, float) | |