pablovela5620's picture
Vendor the inference-path subset of the fdanyone package
1b2e96f
Raw History Blame
6.27 kB
"""Read what one finished 4DAnyone inference run left on disk.
Two readers live here. ``discover_run`` collects every artifact of a clip --
motion, generated videos, skeletons, and the source clip -- into a
``RunLayout``. The rig readers parse the ``cameras.json`` that
``fdanyone.output`` writes next to the generated videos.
"""
from __future__ import annotations
import json
import logging
from dataclasses import dataclass
from pathlib import Path
from fdanyone.errors import FourDAnyoneError
from fdanyone.io import read_json
LOGGER = logging.getLogger("fdanyone.runs")
# ---------------------------------------------------------------------------
# Run layout
# ---------------------------------------------------------------------------
@dataclass(frozen=True)
class RunLayout:
"""Files that one inference run left on disk for a single clip."""
clip: str
data_dir: Path
result_dir: Path
motion_dir: Path
cameras_json: Path | None
metadata_json: Path | None
source_video: Path | None
dense_videos: tuple[tuple[int, Path], ...]
skeleton_videos: tuple[tuple[int, Path], ...]
sparse_videos: tuple[tuple[int, Path], ...]
@property
def has_motion(self) -> bool:
return (self.motion_dir / "motion.json").is_file() and (self.motion_dir / "motion.safetensors").is_file()
def numbered_videos(directory: Path) -> tuple[tuple[int, Path], ...]:
"""Return ``(camera_id, path)`` pairs for ``NN.mp4`` files, ID-ordered."""
if not directory.is_dir():
return ()
found: list[tuple[int, Path]] = []
for path in sorted(directory.glob("*.mp4")):
try:
found.append((int(path.stem), path))
except ValueError:
LOGGER.warning("Ignoring video with a non-numeric name: %s", path)
return tuple(sorted(found))
def find_source_video(data_dir: Path, clip: str, filename: str | None) -> Path | None:
"""Locate the input clip that produced the run."""
candidates: list[Path] = []
if filename:
candidates.append(data_dir / "source" / "pexels" / filename)
for suffix in (".mp4", ".mov", ".mkv", ".webm", ".MP4", ".MOV"):
candidates.append(data_dir / "source" / "pexels" / f"{clip}{suffix}")
for candidate in candidates:
if candidate.is_file():
return candidate
# Only the input and result trees can hold a clip; the rest of the data
# root is model output that a recursive walk would scan for nothing.
roots = tuple(root for root in (data_dir / "source", data_dir / "fdanyone") if root.is_dir())
if filename:
matches = sorted(path for root in roots for path in root.rglob(filename) if path.is_file())
if matches:
return matches[0]
matches = sorted(
path for root in roots for path in root.rglob(f"{clip}.*") if path.is_file() and path.suffix != ".rrd"
)
return matches[0] if matches else None
def discover_run(data_dir: Path, clip: str) -> RunLayout:
"""Collect every artifact of ``clip`` and reject an empty run."""
result_dir = data_dir / "fdanyone" / clip
motion_dir = data_dir / "gvhmr" / "results" / clip
metadata_json = result_dir / "metadata.json"
cameras_json = result_dir / "cameras.json"
metadata = read_json(metadata_json)
filename = None
if metadata is not None:
filename = str(metadata.get("input", {}).get("filename") or "") or None
layout = RunLayout(
clip=clip,
data_dir=data_dir,
result_dir=result_dir,
motion_dir=motion_dir,
cameras_json=cameras_json if cameras_json.is_file() else None,
metadata_json=metadata_json if metadata_json.is_file() else None,
source_video=find_source_video(data_dir, clip, filename),
dense_videos=numbered_videos(result_dir / "videos" / "dense"),
skeleton_videos=numbered_videos(result_dir / "skeletons"),
sparse_videos=numbered_videos(result_dir / "videos" / "sparse"),
)
if not layout.has_motion and not layout.dense_videos and layout.source_video is None:
raise FourDAnyoneError(
f"Nothing to visualize for clip {clip!r}. Expected at least one of:\n"
f" {motion_dir / 'motion.safetensors'} (GVHMR motion)\n"
f" {result_dir / 'videos' / 'dense'} (generated views)\n"
f" a source clip named {clip}.* under {data_dir}\n"
"Run `python inference.py --video_path <clip>` first."
)
return layout
# ---------------------------------------------------------------------------
# Camera rig
# ---------------------------------------------------------------------------
def read_cameras(result: Path) -> dict:
"""Read the ``cameras.json`` a result directory must carry."""
path = result / "cameras.json"
try:
cameras = json.loads(path.read_text())
except (FileNotFoundError, json.JSONDecodeError) as exc:
raise FourDAnyoneError(f"Cannot read {path}: {exc}") from exc
if not isinstance(cameras, dict):
raise FourDAnyoneError("cameras.json must contain a JSON object.")
return cameras
def camera_records(rig: dict) -> list[dict]:
"""Validate an OPENCV rig and return its ID-ordered camera records."""
if not isinstance(rig, dict) or rig.get("camera_model") != "OPENCV":
raise FourDAnyoneError("cameras.json has no supported OPENCV camera rig.")
cameras = rig.get("cameras")
if not isinstance(cameras, list) or not cameras:
raise FourDAnyoneError("Camera rig must contain at least one camera.")
if [camera.get("camera_id") for camera in cameras if isinstance(camera, dict)] != list(range(len(cameras))):
raise FourDAnyoneError("Camera rig must be ordered by camera ID.")
return cameras
def dense_video_paths(result: Path, cameras: list[dict]) -> tuple[Path, ...]:
"""Return the generated video of every camera, in rig order."""
paths = []
for camera in cameras:
camera_id = int(camera["camera_id"])
relative = f"videos/dense/{camera_id:02d}.mp4"
if camera.get("video") != relative:
raise FourDAnyoneError(f"Camera {camera_id:02d} points to the wrong target video.")
paths.append(result / relative)
return tuple(paths)