Report articulated sweep overlaps

This commit is contained in:
Meta Vens 2026-05-31 23:40:49 +01:00
commit dc3fe32bb3
2 changed files with 38 additions and 1 deletions

View file

@ -296,3 +296,4 @@
{"ts": "2026-05-31T23:14:03+01:00", "type": "implementation", "status": "ok", "note": "Changed music_keyed_move_chirp_sync.py visual pulses from square on/off flashes into sampled Move visual words. The default mimir.move_visual_contour_word.v1 uses identity color bias, attack/release envelope, symbol-dependent hue glide, tremolo articulation, emphasized Move lane, and an explicit visual_gesture_hz rate knob. Synthetic dry smoke wrote contour samples; a short non-dry Nightwing HID test at 80 Hz completed without writer failure. The camera-decodable rate still needs offline recovery scoring from captured frames."}
{"ts": "2026-05-31T23:31:00+01:00", "type": "implementation", "status": "ok", "note": "sync_sweep.py now accepts a mimir.music_keyed_move_chirp_plan.v1 as the shared gesture authority for articulated chirps and chirp-flashes. The sweep copies/renders the plan WAV, sends exact Move visual contour samples over HID, attaches RGB visual_contour decoder scores to video receipts, and runs Mimir perfect-machine live receipts for local Leap/Kiyos plus Raven/Eve configs unless disabled. This fixes the earlier DirectShow/Nightwing-only sweep omission path: Leap/Eve are explicit receipt surfaces instead of assumed present."}
{"ts": "2026-05-31T23:48:00+01:00", "type": "verification", "status": "partial", "note": "Ran an articulated all-sensor sweep from a live Scarlett-derived music plan at artifacts/runtime/all-sensor-articulated-20260531-232349. ASIO loopback channels 2/3 matched 77 chirp events with 0.083/0.292 ms median residuals; Nightwing builtin cam and nw-eye-1 each emitted 100/100 Move observations; Kiyo Pro/Kiyo captured RGB video and offline crunch extracted 47/51 marker observations. tools/crunch_sync_sweep.py wrote 298 total marker observations to calibration-observations.json/csv. Missing witnesses are explicit: nw-eye-0 timed out, Nightwing mic only matched one chirp, Focusrite DShow was unavailable while ASIO succeeded, LeapUVC KS open failed, and Raven/Eve receiver runtime saw no camera textures."}
{"ts": "2026-05-31T23:54:00+01:00", "type": "verification", "status": "ok", "note": "The articulated sweep crunch now reports pairwise shared marker events for calibration solve selection. In the 20260531-232349 run: Nightwing builtin cam and nw-eye-1 share 100 events; Kiyo shares 51 with each Nightwing view; Kiyo Pro shares 47 with each Nightwing view; Kiyo and Kiyo Pro share 28. This is enough correspondence substrate for initial multi-view frustum fitting on the sensors that actually saw the Move spheres."}

View file

@ -208,12 +208,16 @@ def main() -> int:
for name in ("kiyo-pro", "kiyo"):
sensors.append(crunch_video(sweep_dir, name, events, args))
observations = [obs for sensor in sensors for obs in sensor.get("observations", [])]
pair_overlaps = compute_pair_overlaps(observations)
event_coverage = compute_event_coverage(observations)
document = {
"kind": "mimir.articulated_sync_observation_crunch.v1",
"sweep_dir": str(sweep_dir),
"event_count": len(events),
"sensor_count": len(sensors),
"observation_count": len(observations),
"pair_overlaps": pair_overlaps,
"event_coverage": event_coverage,
"sensors": sensors,
}
output_json = Path(args.output_json) if args.output_json else sweep_dir / "calibration-observations.json"
@ -224,9 +228,41 @@ def main() -> int:
writer = csv.DictWriter(handle, fieldnames=fields, extrasaction="ignore")
writer.writeheader()
writer.writerows(observations)
print(json.dumps({"output_json": str(output_json), "output_csv": str(output_csv), "observation_count": len(observations)}, indent=2))
print(json.dumps({
"output_json": str(output_json),
"output_csv": str(output_csv),
"observation_count": len(observations),
"pair_overlaps": pair_overlaps,
}, indent=2))
return 0 if observations else 1
def compute_pair_overlaps(observations: list[dict]) -> list[dict]:
by_event: dict[int, dict[str, dict]] = {}
for obs in observations:
event_index = int(obs["event_index"])
by_event.setdefault(event_index, {})[str(obs["source_id"])] = obs
pairs: dict[tuple[str, str], int] = {}
for sensors in by_event.values():
names = sorted(sensors)
for left_index, left in enumerate(names):
for right in names[left_index + 1:]:
pairs[(left, right)] = pairs.get((left, right), 0) + 1
return [
{"left": left, "right": right, "shared_events": count}
for (left, right), count in sorted(pairs.items(), key=lambda item: (-item[1], item[0]))
]
def compute_event_coverage(observations: list[dict]) -> list[dict]:
by_event: dict[int, set[str]] = {}
for obs in observations:
by_event.setdefault(int(obs["event_index"]), set()).add(str(obs["source_id"]))
return [
{"event_index": event_index, "sensor_count": len(sources), "sources": sorted(sources)}
for event_index, sources in sorted(by_event.items())
]
if __name__ == "__main__":
raise SystemExit(main())