Add causal person fusion and MuVAP stage-3 baselines
This commit is contained in:
+112
@@ -0,0 +1,112 @@
|
||||
"""Dataset-specific metadata adapters.
|
||||
|
||||
Adapters do not normalize signals. They only convert source layouts into
|
||||
`SourceRecord`; the shared writer owns every media transformation.
|
||||
"""
|
||||
|
||||
import ast
|
||||
import csv
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Iterable
|
||||
|
||||
from preprocess.asd.schema import SAMPLE_RATE, SourceRecord
|
||||
|
||||
|
||||
def _labels(text: str) -> list[int]:
|
||||
values = ast.literal_eval(text)
|
||||
if not isinstance(values, (list, tuple)):
|
||||
raise ValueError("labels must be a list")
|
||||
result = [int(value) for value in values]
|
||||
if any(value not in (0, 1) for value in result):
|
||||
raise ValueError("labels must contain only 0 and 1")
|
||||
return result
|
||||
|
||||
|
||||
def _existing(path: Path, sample_id: str) -> Path:
|
||||
if not path.exists():
|
||||
raise FileNotFoundError(f"{sample_id}: missing {path}")
|
||||
return path
|
||||
|
||||
|
||||
def clip_directory_records(
|
||||
loader_path: Path,
|
||||
visual_root: Path,
|
||||
audio_root: Path,
|
||||
split: str,
|
||||
dataset: str = "ava",
|
||||
skip_missing: bool = False,
|
||||
) -> Iterable[SourceRecord]:
|
||||
"""Read the AVA-style loader TSV and nested JPEG/WAV layout.
|
||||
|
||||
AVA and WASD ship the identical layout - `clips_videos/<split>/<clip>/
|
||||
<entity>/<timestamp>.jpg` beside `clips_audios/<split>/<clip>/<entity>.wav`
|
||||
- and the identical five-column loader TSV, so one reader serves both.
|
||||
"""
|
||||
face_by_id = {
|
||||
path.name: path for path in (visual_root / split).glob("*/*") if path.is_dir()
|
||||
}
|
||||
audio_by_id = {path.stem: path for path in (audio_root / split).glob("*/*.wav")}
|
||||
with loader_path.open(newline="") as handle:
|
||||
for row_number, row in enumerate(csv.reader(handle, delimiter="\t"), start=1):
|
||||
if len(row) < 4:
|
||||
raise ValueError(
|
||||
f"{loader_path}:{row_number}: expected at least 4 columns"
|
||||
)
|
||||
sample_id, _, fps, label_text = row[:4]
|
||||
face_path = face_by_id.get(sample_id)
|
||||
audio_path = audio_by_id.get(sample_id)
|
||||
if face_path is None or audio_path is None:
|
||||
if skip_missing:
|
||||
continue
|
||||
raise FileNotFoundError(
|
||||
f"{sample_id}: missing face directory or clip-local WAV"
|
||||
)
|
||||
yield SourceRecord(
|
||||
dataset=dataset,
|
||||
sample_id=sample_id,
|
||||
visual_path=face_path,
|
||||
audio_path=audio_path,
|
||||
source_fps=float(fps),
|
||||
labels=_labels(label_text),
|
||||
visual_kind="jpeg_directory",
|
||||
)
|
||||
|
||||
|
||||
def manifest_records(
|
||||
manifest_path: Path,
|
||||
dataset: str,
|
||||
root: Path | None = None,
|
||||
default_npy_audio_sample_rate: int = SAMPLE_RATE,
|
||||
) -> Iterable[SourceRecord]:
|
||||
"""Read the canonical TSV adapter used for AVCC and future datasets.
|
||||
|
||||
Required header fields: sample_id, visual_path, audio_path, source_fps,
|
||||
labels. Optional: start_sec, visual_kind, audio_sample_rate.
|
||||
"""
|
||||
root = root or manifest_path.parent
|
||||
with manifest_path.open(newline="") as handle:
|
||||
reader = csv.DictReader(handle, delimiter="\t")
|
||||
required = {"sample_id", "visual_path", "audio_path", "source_fps", "labels"}
|
||||
missing = required.difference(reader.fieldnames or [])
|
||||
if missing:
|
||||
raise ValueError(f"{manifest_path}: missing columns {sorted(missing)}")
|
||||
for row_number, row in enumerate(reader, start=2):
|
||||
try:
|
||||
yield SourceRecord(
|
||||
dataset=dataset,
|
||||
sample_id=row["sample_id"],
|
||||
visual_path=_existing(root / row["visual_path"], row["sample_id"]),
|
||||
audio_path=_existing(root / row["audio_path"], row["sample_id"]),
|
||||
source_fps=float(row["source_fps"]),
|
||||
labels=json.loads(row["labels"]),
|
||||
start_sec=float(row.get("start_sec") or 0.0),
|
||||
npy_audio_sample_rate=int(
|
||||
row.get("audio_sample_rate") or default_npy_audio_sample_rate
|
||||
),
|
||||
visual_kind=row.get("visual_kind") or "npy",
|
||||
)
|
||||
except (TypeError, ValueError, json.JSONDecodeError) as error:
|
||||
raise ValueError(
|
||||
f"{manifest_path}:{row_number}: invalid row"
|
||||
) from error
|
||||
Reference in New Issue
Block a user