Add camera mode probe notes

This commit is contained in:
Meta Vens 2026-05-18 14:24:14 +01:00
commit f340082fa3
4 changed files with 142 additions and 6 deletions

View file

@ -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

View file

@ -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)

View file

@ -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."}

View file

@ -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.