tools(pipeline): hybrid track.py + simulator label author mode

Content-pipeline Increment 2, part 2 (the tooling). Implements design §11.5
(docs/superpowers/specs/2026-06-24-content-pipeline-design.md):

- tools/pipeline/track.py — the stage-5 geometry pass. Classical path: a
  hand-seeded normalized box propagated by OpenCV Lucas-Kanade optical flow
  (CSRT used instead when a contrib build provides it), sampled to a SPARSE
  loop-normalized keyframed track + an appear/disappear window. Optional ML
  detect+track path lazy-imports ultralytics (clear ImportError if absent;
  base install needs only cv2). Pure helpers (normalize/denormalize, loop_t,
  infer_window, sample_track, track_to_annotation) are unit-tested; the cv2
  propagation is an opt-in integration test on real abyss_wow footage.
  Semantics are never produced here — geometry only.

- Author mode — /author.html + author.js/.css reuse the preview stage: pick a
  pool clip, scrub, drag a seed box, Run tracker (or Add as static box), author
  the LEFT detail tiers (general -> scientific+fact) + salience, shift-click to
  place affect anchors with RIGHT emotion tiers, and Save to manifest. Backend:
  POST /api/author/track (runs track_seed on the clip's base) + POST
  /api/author/clip (idempotent upsert via tools.pipeline.manifest — keeps media
  + provenance, replaces only authored content, reloads in place). The tracker
  propagates box geometry; all strings/scientific names/facts are hand-typed.

- Repointed the pipeline integration test off the retired forest base to the
  cosmos pool primary. USER_GUIDE simulator section brought current (pools,
  coast, 3-knob Mood, real-time dream, progressive tiers, author mode).

267 passed / 2 skipped (+ track pure + opt-in real-footage + author endpoint
tests). Author UI by-eye review deferred to the operator (no Chrome on this box).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
BenStullsBets
2026-06-24 18:14:32 -07:00
parent 12408e505e
commit 70fd367c70
10 changed files with 1023 additions and 48 deletions
+10 -10
View File
@@ -4,7 +4,9 @@ import pytest
from tools.pipeline.run import probe_duration, process_clip, resolve_ffmpeg
_FOREST = Path("simulator/sample_media/forest/base.mp4")
# A real pool base (forest was retired with the rotating-pool rename, session 0016);
# cosmos/base.mp4 is the cosmos pool primary and the most stable PD clip on disk.
_BASE = Path("simulator/sample_media/cosmos/base.mp4")
def _ffmpeg_available() -> bool:
@@ -18,20 +20,18 @@ def _ffmpeg_available() -> bool:
_HAS_FF = _ffmpeg_available()
@pytest.mark.skipif(not _FOREST.exists(),
reason="forest POC base absent (run simulator/setup_sample_media.py)")
def test_probe_duration_reads_forest_base():
if not _FOREST.exists():
pytest.skip("forest POC base absent")
assert probe_duration(_FOREST) > 0
@pytest.mark.skipif(not _BASE.exists(),
reason="cosmos base absent (run simulator/build_pool_manifest.py)")
def test_probe_duration_reads_real_base():
assert probe_duration(_BASE) > 0
@pytest.mark.skipif(not _HAS_FF,
reason="neither system ffmpeg nor imageio-ffmpeg available")
@pytest.mark.skipif(not _FOREST.exists(),
reason="forest POC base absent (run simulator/setup_sample_media.py)")
@pytest.mark.skipif(not _BASE.exists(),
reason="cosmos base absent (run simulator/build_pool_manifest.py)")
def test_process_clip_emits_proxy_and_master(tmp_path):
out = process_clip(_FOREST, tmp_path, overlap=0.5, start=0.0, duration=4.0)
out = process_clip(_BASE, tmp_path, overlap=0.5, start=0.0, duration=4.0)
assert out["proxy"].exists() and out["master"].exists()
# Proxy is exactly 1920×1080 — verified with cv2, no ffprobe dependency.
import cv2
+120
View File
@@ -0,0 +1,120 @@
"""tools/pipeline/track.py — the hybrid motion-track pass (content-pipeline §11.5).
Pure geometry helpers are unit-tested here; the cv2 optical-flow propagation is an
opt-in integration test that runs on a real pool clip when present."""
from pathlib import Path
import pytest
from tools.pipeline.track import (
clamp01_box,
denormalize_box,
infer_window,
loop_t,
normalize_box,
sample_track,
track_to_annotation,
)
# --- pure helpers -----------------------------------------------------------
def test_clamp01_box_keeps_box_on_screen():
assert clamp01_box((-0.2, 0.1, 0.5, 0.5)) == [0.0, 0.1, 0.5, 0.5]
# size trimmed so the box never runs past the right/bottom edge
assert clamp01_box((0.8, 0.8, 0.5, 0.5)) == [0.8, 0.8, pytest.approx(0.2), pytest.approx(0.2)]
def test_normalize_denormalize_round_trip():
px = (192, 108, 384, 216)
norm = normalize_box(px, 1920, 1080)
assert norm == [pytest.approx(0.1), pytest.approx(0.1), pytest.approx(0.2), pytest.approx(0.2)]
assert denormalize_box(norm, 1920, 1080) == (192, 108, 384, 216)
def test_loop_t_is_frame_over_total():
assert loop_t(0, 100) == 0.0
assert loop_t(50, 100) == 0.5
assert loop_t(5, 0) == 0.0 # guard divide-by-zero
def test_infer_window_spans_tracked_frames():
appear, disappear = infer_window([10, 11, 30, 31], 100)
assert appear == pytest.approx(0.1)
assert disappear == pytest.approx(0.31)
# no frames -> full clip
assert infer_window([], 100) == (0.0, 1.0)
def test_sample_track_downsamples_to_sparse_keyframes():
# 21 dense frames -> at most n_keyframes, including the first and last
per_frame = {i: [i / 100, 0.2, 0.1, 0.1] for i in range(0, 21)}
track = sample_track(per_frame, total_frames=100, n_keyframes=5)
assert len(track) <= 5
ts = [k["t"] for k in track]
assert ts == sorted(ts) # sorted by t
assert ts[0] == pytest.approx(0.0) # first tracked frame
assert ts[-1] == pytest.approx(0.2) # last tracked frame (frame 20 / 100)
assert len(set(ts)) == len(ts) # de-duplicated on t
def test_sample_track_keeps_all_when_already_sparse():
per_frame = {0: [0.1, 0.1, 0.1, 0.1], 60: [0.5, 0.3, 0.1, 0.1]}
track = sample_track(per_frame, total_frames=120, n_keyframes=5)
assert [k["t"] for k in track] == [pytest.approx(0.0), pytest.approx(0.5)]
def test_sample_track_empty_is_empty():
assert sample_track({}, total_frames=100) == []
def test_track_to_annotation_assembles_geometry_only():
track = [{"t": 0.0, "box": [0.1, 0.2, 0.1, 0.1]}, {"t": 0.5, "box": [0.4, 0.2, 0.1, 0.1]}]
ann = track_to_annotation("detected.jelly", track, salience=3, appear=0.0, disappear=0.55)
assert ann["key"] == "detected.jelly"
assert ann["salience"] == 3
assert ann["track"] == track
assert ann["appear"] == 0.0 and ann["disappear"] == 0.55
# no semantics leaked in — strings/tiers are the author's, added elsewhere
assert "strings" not in ann and "_tiers" not in ann
def test_track_to_annotation_omits_window_when_absent():
ann = track_to_annotation("detected.x", [], salience=4)
assert "appear" not in ann and "disappear" not in ann
# --- opt-in: classical optical-flow propagation on a real clip --------------
_CLIP = Path("simulator/sample_media/abyss_wow/base.mp4")
def _cv2_available() -> bool:
try:
import cv2 # noqa: F401
return True
except Exception:
return False
@pytest.mark.skipif(not _CLIP.exists(),
reason="abyss_wow base absent (run simulator/build_pool_manifest.py)")
@pytest.mark.skipif(not _cv2_available(), reason="opencv-python not available")
def test_track_seed_produces_a_sparse_annotation_on_real_footage():
from tools.pipeline.track import track_box, track_seed
seed = (0.2, 0.3, 0.15, 0.18)
per_frame, total = track_box(_CLIP, seed, seed_frame=0, max_frames=40)
assert total > 0
assert per_frame[0] == pytest.approx(list(seed), abs=0.02)
assert len(per_frame) > 1 # propagated beyond the seed frame
ann = track_seed(_CLIP, "detected.jelly", seed, seed_frame=0, n_keyframes=5, max_frames=40)
assert ann["key"] == "detected.jelly"
assert 2 <= len(ann["track"]) <= 5 # sparse keyframes
for kf in ann["track"]:
assert 0.0 <= kf["t"] <= 1.0
assert all(0.0 <= v <= 1.0 for v in kf["box"])
assert 0.0 <= ann["appear"] <= ann["disappear"] <= 1.0
+75
View File
@@ -250,3 +250,78 @@ def test_delta_zero_is_an_initial_pool_pick(pool_client):
assert data["to_index"] == 1
assert data["steps"] == []
assert data["target_clip_id"] in members
# --- author mode (content-pipeline §11.5) ---
@pytest.fixture
def author_client(tmp_path):
p = tmp_path / "manifest.json"
p.write_text(json.dumps({
"clips": [{
"id": "abyss_wow", "title": "World of Water", "base_file": "abyss_wow/base.mp4",
"license": "PD", "source": "NOAA", "right_variants": {},
"annotations": [], "affect": [], "strings": {"en": {}},
}],
}))
return TestClient(create_app(manifest_path=p)), p
def test_author_clip_persists_labels_and_keeps_provenance(author_client):
client, path = author_client
body = {
"clip_id": "abyss_wow",
"annotations": [{"key": "detected.jelly", "salience": 4,
"appear": 0.0, "disappear": 0.5,
"track": [{"t": 0.0, "box": [0.1, 0.2, 0.1, 0.1]}]}],
"affect": [{"key": "feel.unease", "at": [0.5, 0.5], "min_level": 1}],
"strings": {"en": {"detected.jelly": ["jelly", "comb jelly", "Ctenophora", "Ctenophora · cilia rows"],
"feel.unease": ["uh", "unease", "disquiet", "a creeping disquiet"]}},
}
resp = client.post("/api/author/clip", json=body)
assert resp.status_code == 200 and resp.json()["ok"] is True
# persisted to disk
saved = json.loads(path.read_text())["clips"][0]
assert saved["annotations"][0]["key"] == "detected.jelly"
assert saved["affect"][0]["key"] == "feel.unease"
assert saved["strings"]["en"]["detected.jelly"][2] == "Ctenophora"
# provenance preserved (only authored content replaced)
assert saved["base_file"] == "abyss_wow/base.mp4"
assert saved["license"] == "PD"
# reflected by /api/clips without a restart
served = client.get("/api/clips").json()["clips"][0]
assert served["annotations"][0]["key"] == "detected.jelly"
def test_author_clip_unknown_clip_404(author_client):
client, _ = author_client
resp = client.post("/api/author/clip", json={"clip_id": "nope", "annotations": []})
assert resp.status_code == 404
def test_author_track_unknown_clip_404(author_client):
client, _ = author_client
resp = client.post("/api/author/track", json={
"clip_id": "nope", "key": "detected.x", "seed_box": [0.1, 0.1, 0.1, 0.1]})
assert resp.status_code == 404
_REAL_CLIP = __import__("pathlib").Path("simulator/sample_media/abyss_wow/base.mp4")
@pytest.mark.skipif(not _REAL_CLIP.exists(),
reason="abyss_wow base absent (run simulator/build_pool_manifest.py)")
def test_author_track_on_real_clip_returns_keyframes():
# Against the real default manifest + footage: the tracker yields a sparse track.
client = TestClient(create_app())
resp = client.post("/api/author/track", json={
"clip_id": "abyss_wow", "key": "detected.jelly",
"seed_box": [0.2, 0.3, 0.15, 0.18], "seed_t": 0.0,
"salience": 4, "n_keyframes": 5, "max_frames": 30,
})
assert resp.status_code == 200
ann = resp.json()
assert ann["key"] == "detected.jelly"
assert 2 <= len(ann["track"]) <= 5
assert 0.0 <= ann["appear"] <= ann["disappear"] <= 1.0