What now works: - M0 scaffold: pyproject (all spec deps), uv/py3.12 env, `python -m festival4d` CLI registering synthetic|ingest|sync|reconstruct|events|serve. Vite hello page. - Synthetic fixture (synthetic.py): 3 shifted-audio videos (offsets 0/+1370/-842 ms), camera-arc poses, stage point cloud -> points.ply, seeded events + anchors, ground_truth.json. `python -m festival4d synthetic` populates data/ + DB. - DB schema exactly per spec §2 (db.py) + CRUD helpers all lanes use. - M3 API (api.py) full against synthetic data: manifest/poses/pointcloud/anchors/ events/detect/annotations; Range-capable video serving (206 verified); CORS for any localhost origin. - Frozen geometry contract: geometry.colmap_to_threejs (M5 math) + unit test (3 known vectors, random round-trip, scipy oracle); mirrored frontend/src/lib/pose.js with identical POSE_TEST_VECTORS. Lane-B stubs: slerp_pose, ray_from_pixel, triangulate_rays, nearest_point_on_ray. - Classifier contract (events_ai.py): MomentClassification model + MomentClassifier protocol + Gemini/Claude/Local provider stubs. - Lane-owned modules stubbed with final signatures (ingest, audio_sync, frames, sfm, events_ai); cli/api catch NotImplementedError and degrade gracefully. - plan/CHANGE_REQUESTS.md created; plan/status/foundation.md updated. Acceptance: pytest 24 passed; serve endpoints verified via curl + browser (video seek, manifest fetch cross-origin, pose.js self-test, 0 console errors). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
29 lines
1.1 KiB
Python
29 lines
1.1 KiB
Python
"""Sharpness-aware frame sampling for SfM (spec M2, lane B).
|
|
|
|
STUB — lane B fills the bodies; the public signatures below are frozen (``sfm.py`` calls
|
|
these). Sample candidate frames ~2/s per video; within each 0.5 s window keep the sharpest
|
|
frame (variance of Laplacian via OpenCV). Write JPEGs named ``{video_id}_{frame_idx}.jpg``
|
|
into ``config.FRAMES_DIR`` (one subfolder per video for COLMAP's single-camera-per-folder).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from pathlib import Path
|
|
|
|
log = logging.getLogger("festival4d.frames")
|
|
|
|
|
|
def sharpness(image) -> float:
|
|
"""Variance of the Laplacian of a grayscale image (OpenCV) — higher is sharper."""
|
|
raise NotImplementedError("lane B (M2): implement sharpness")
|
|
|
|
|
|
def sample_frames(video_path: Path, video_id: int, out_dir: Path,
|
|
target_fps: float = 2.0, window_s: float = 0.5) -> list[Path]:
|
|
"""Sample the sharpest frame per ``window_s`` window from ``video_path`` (spec M2).
|
|
|
|
Returns the written JPEG paths (named ``{video_id}_{frame_idx}.jpg``).
|
|
"""
|
|
raise NotImplementedError("lane B (M2): implement sample_frames")
|