From f340082fa3df8651a7ca241cd6e268b0843b039b Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Mon, 18 May 2026 14:24:14 +0100 Subject: [PATCH] Add camera mode probe notes --- docs/calibration.md | 12 ++++ scripts/calibration_probe.py | 131 +++++++++++++++++++++++++++++++++-- state/evidence.jsonl | 1 + state/map.yaml | 4 +- 4 files changed, 142 insertions(+), 6 deletions(-) diff --git a/docs/calibration.md b/docs/calibration.md index c9f94bb..c04c018 100644 --- a/docs/calibration.md +++ b/docs/calibration.md @@ -27,6 +27,12 @@ Capture camera snapshots: .\.venv\Scripts\python.exe .\scripts\calibration_probe.py snapshot --max-index 10 ``` +Probe camera mode behavior: + +```powershell +.\.venv\Scripts\python.exe .\scripts\calibration_probe.py mode-probe --api msmf --index 0 --profile 320x240x120 +``` + Smoke-test input devices: ```powershell @@ -68,6 +74,12 @@ Observed video access: - `dshow:2`: second Razer Kiyo-class RGB camera, likely Kiyo Pro. - PS3 Eye video interfaces are attached but not usable yet. Both `USB\VID_1415&PID_2000&MI_00` devices report Windows problem code `28`, meaning the video-side driver is missing. Their audio interfaces are working through generic USB audio. +Mode probe notes: + +- `msmf:0` LeapUVC accepts a `320x240x120` request and reports about 115 fps, with rough OpenCV measured capture around 52 fps in the first short probe. Treat that as "tracking mode is plausible," not calibrated throughput. +- Kiyo RGB devices produce snapshots, but initial isolated mode probes against Kiyo indices timed out. Do not trust FPS configuration for Kiyos until a backend-specific capture path is chosen. +- PS3 Eye tracking modes cannot be tested until the open driver is installed for `MI_00`. + Observed audio access over WASAPI at 48 kHz: - Scarlett Solo USB microphone diff --git a/scripts/calibration_probe.py b/scripts/calibration_probe.py index da71f6d..b33f2fb 100644 --- a/scripts/calibration_probe.py +++ b/scripts/calibration_probe.py @@ -2,6 +2,7 @@ import argparse import json import subprocess import sys +import multiprocessing as mp from datetime import datetime, timezone from pathlib import Path @@ -75,11 +76,7 @@ def camera_probe(max_index: int, apis: list[str], read_frames: int = 1): except Exception as exc: return [{"error": f"cv2 unavailable: {exc!r}"}] - api_map = { - "any": 0, - "dshow": cv2.CAP_DSHOW, - "msmf": cv2.CAP_MSMF, - } + api_map = cv2_api_map() results = [] for api_name in apis: api = api_map[api_name] @@ -108,6 +105,20 @@ def camera_probe(max_index: int, apis: list[str], read_frames: int = 1): return results +def cv2_api_map(): + import cv2 + + return { + "any": 0, + "dshow": cv2.CAP_DSHOW, + "msmf": cv2.CAP_MSMF, + } + + +def api_value(name: str): + return cv2_api_map()[name] + + def discover(args): out = run_dir("discover") manifest = { @@ -150,6 +161,103 @@ def snapshot(args): print(f"{item['api']}:{item['index']} -> {item['snapshot']}") +def mode_probe(args): + out = run_dir(f"mode-probe-{args.api}{args.index}") + result = { + "created_utc": datetime.now(timezone.utc).isoformat(), + "camera": {"api": args.api, "index": args.index}, + "profiles": [], + } + + profiles = [] + requested_profiles = args.profile or [ + "640x480x30", + "640x480x60", + "320x240x60", + "320x240x120", + ] + for item in requested_profiles: + parts = item.lower().split("x") + if len(parts) != 3: + raise SystemExit(f"Profile must be WIDTHxHEIGHTxFPS, got {item}") + profiles.append(tuple(int(part) for part in parts)) + + for width, height, fps in profiles: + entry = run_profile_probe(args.api, args.index, width, height, fps, args.duration, args.profile_timeout) + result["profiles"].append(entry) + + write_json(out / "manifest.json", result) + print(out) + print(json.dumps(result["profiles"], indent=2)) + + +def _profile_probe_worker(queue, api, index, width, height, fps, duration): + import cv2 + import time + + cap = cv2.VideoCapture(index, api_value(api)) + if not cap.isOpened(): + queue.put( + { + "requested": {"width": width, "height": height, "fps": fps}, + "opened": False, + } + ) + return + cap.set(cv2.CAP_PROP_FRAME_WIDTH, width) + cap.set(cv2.CAP_PROP_FRAME_HEIGHT, height) + cap.set(cv2.CAP_PROP_FPS, fps) + cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) + start = time.perf_counter() + count = 0 + shape = None + first_ok = False + while (time.perf_counter() - start) < duration: + ok, frame = cap.read() + if ok and frame is not None: + count += 1 + first_ok = True + shape = list(frame.shape) + elapsed = time.perf_counter() - start + entry = { + "requested": {"width": width, "height": height, "fps": fps}, + "opened": True, + "read_ok": first_ok, + "frames": count, + "measured_fps": count / elapsed if elapsed else 0.0, + "reported_width": cap.get(cv2.CAP_PROP_FRAME_WIDTH), + "reported_height": cap.get(cv2.CAP_PROP_FRAME_HEIGHT), + "reported_fps": cap.get(cv2.CAP_PROP_FPS), + "fourcc": int(cap.get(cv2.CAP_PROP_FOURCC)), + "shape": shape, + "backend": cap.getBackendName(), + } + cap.release() + queue.put(entry) + + +def run_profile_probe(api, index, width, height, fps, duration, timeout): + queue = mp.Queue() + proc = mp.Process(target=_profile_probe_worker, args=(queue, api, index, width, height, fps, duration)) + proc.start() + proc.join(timeout) + if proc.is_alive(): + proc.terminate() + proc.join(2) + return { + "requested": {"width": width, "height": height, "fps": fps}, + "opened": None, + "timeout": True, + } + if not queue.empty(): + return queue.get() + return { + "requested": {"width": width, "height": height, "fps": fps}, + "opened": None, + "error": f"worker exited with code {proc.exitcode}", + } + + def audio_smoke(args): import numpy as np import sounddevice as sd @@ -221,6 +329,19 @@ def main(): p.add_argument("--api", action="append", choices=["any", "dshow", "msmf"], default=["dshow", "msmf"]) p.set_defaults(func=snapshot) + p = sub.add_parser("mode-probe") + p.add_argument("--api", choices=["any", "dshow", "msmf"], default="dshow") + p.add_argument("--index", type=int, required=True) + p.add_argument("--duration", type=float, default=1.0) + p.add_argument("--profile-timeout", type=float, default=6.0) + p.add_argument( + "--profile", + action="append", + default=None, + help="WIDTHxHEIGHTxFPS, repeatable. Example: --profile 320x240x120", + ) + p.set_defaults(func=mode_probe) + p = sub.add_parser("audio-smoke") p.add_argument("--duration", type=float, default=1.0) p.add_argument("--samplerate", type=int) diff --git a/state/evidence.jsonl b/state/evidence.jsonl index 6874154..196fdf7 100644 --- a/state/evidence.jsonl +++ b/state/evidence.jsonl @@ -18,3 +18,4 @@ {"ts": "2026-05-18T12:32:40+00:00", "type": "decision", "status": "ok", "note": "GPU fusion/splatting pass: use pinned host frame rings, async CUDA uploads, GPU feature/marker/stereo extraction, GPU-shaped observation and brush packet buffers, and CUDA/Vulkan/D3D splat rendering with minimal readback; CPU owns orchestration and semantics."} {"ts": "2026-05-18T12:46:08+00:00", "type": "verification", "status": "partial", "note": "Calibration harness enumerated the local rig: LeapUVC raw IR video, two Razer Kiyo-class RGB cameras, Scarlett/Kiyo/PS3 Eye microphones, and Scarlett speakers are accessible; both PS3 Eye video interfaces are attached but report Windows problem code 28 (missing video-side driver)."} {"ts": "2026-05-18T13:14:03+00:00", "type": "decision", "status": "ok", "note": "PS3 Eye video driver path should prefer the opentrack open driver via Zadig/libusb on interface MI_00; CL Eye remains historical reference only, not a repo dependency or redistribution target."} +{"ts": "2026-05-18T13:24:02+00:00", "type": "verification", "status": "partial", "note": "Footage status: LeapUVC and two Kiyo-class cameras produce snapshots; PS3 Eye video still needs opentrack/Zadig driver. LeapUVC MSMF accepted 320x240x120 and reported about 115 fps, but rough OpenCV measured throughput was about 52 fps in a short probe; Kiyo FPS mode probes timed out."} diff --git a/state/map.yaml b/state/map.yaml index 15f17b1..8aa417a 100644 --- a/state/map.yaml +++ b/state/map.yaml @@ -116,7 +116,9 @@ current_status: has first physical-device access: LeapUVC raw IR video, two Razer Kiyo-class RGB cameras, all expected microphones, and Scarlett speaker output are accessible; both PS3 Eye video interfaces are attached but missing their - video-side driver. + video-side driver. LeapUVC MSMF mode probing accepts a 320x240x120 request + and reports about 115 fps, though measured OpenCV throughput is lower in the + first rough probe. next_action: > Fix PS3 Eye video driver access, then capture ChArUco intrinsics frames for LeapUVC and both Kiyo cameras before moving to multi-camera extrinsics.