from __future__ import annotations import argparse import sys from pathlib import Path import pandas as pd sys.path.append(str(Path(__file__).resolve().parents[1])) from src.io_utils import load_config, resolve_path, write_jsonl from src.label_utils import normalize_label VIDEO_COLUMNS = ["video_path", "path", "file", "filename", "video"] LABEL_COLUMNS = ["label", "gloss", "sign", "sign_label", "text"] SPLIT_COLUMNS = ["split", "subset"] ID_COLUMNS = ["id", "sign_id"] def pick_column(columns: list[str], candidates: list[str], name: str) -> str: lower = {c.lower(): c for c in columns} for cand in candidates: if cand.lower() in lower: return lower[cand.lower()] raise ValueError(f"Missing {name} column. Tried: {', '.join(candidates)}") def read_metadata(path: Path) -> pd.DataFrame: suffix = path.suffix.lower() if suffix == ".csv": return pd.read_csv(path) if suffix in {".jsonl", ".ndjson"}: return pd.read_json(path, lines=True) if suffix == ".json": return pd.read_json(path) raise ValueError(f"Unsupported metadata format: {path.suffix}. Use CSV, JSON, or JSONL.") def normalize_split(value: str) -> str: value = str(value).strip().lower() if value in {"train", "training"}: return "train" if value in {"val", "valid", "validation", "dev"}: return "val" if value in {"test", "testing"}: return "test" return value def main() -> None: parser = argparse.ArgumentParser() parser.add_argument("--config", default=None) parser.add_argument("--metadata", required=True) parser.add_argument("--video_root", required=True) parser.add_argument("--out_train", default=None) parser.add_argument("--out_val", default=None) parser.add_argument("--skip_missing_check", action="store_true") parser.add_argument("--val_fraction", type=float, default=0.1) args = parser.parse_args() cfg = load_config(args.config) if args.config else {} out_train = args.out_train or cfg.get("train_jsonl", "data/asl_citizen_train.jsonl") out_val = args.out_val or cfg.get("val_jsonl", "data/asl_citizen_val.jsonl") metadata_path = resolve_path(args.metadata, Path.cwd()) video_root = resolve_path(args.video_root, Path.cwd()) if not metadata_path.exists(): raise FileNotFoundError(f"Metadata file not found: {metadata_path}") if not video_root.exists(): raise FileNotFoundError(f"Video root not found: {video_root}") df = read_metadata(metadata_path) columns = list(df.columns) video_col = pick_column(columns, VIDEO_COLUMNS, "video path") label_col = pick_column(columns, LABEL_COLUMNS, "label") id_col = next((c for c in ID_COLUMNS if c in columns), None) split_col = next((c for c in SPLIT_COLUMNS if c in columns), None) rows = [] for idx, row in df.iterrows(): label = normalize_label(row[label_col]) if not label: raise ValueError(f"Missing label in metadata row {idx}") raw_video = Path(str(row[video_col])) video_path = raw_video if raw_video.is_absolute() else video_root / raw_video if not args.skip_missing_check and not video_path.exists(): raise FileNotFoundError(f"Missing video at metadata row {idx}: {video_path}") split = normalize_split(row[split_col]) if split_col else None rows.append( { "id": str(row[id_col]) if id_col else str(idx), "video_path": str(video_path), "label": label, "split": split, } ) if split_col: train_rows = [r for r in rows if r["split"] == "train"] val_rows = [r for r in rows if r["split"] in {"val", "validation", "dev"}] if not train_rows or not val_rows: raise ValueError("Found a split column, but could not find both train and val rows.") else: split_at = max(1, int(len(rows) * (1 - args.val_fraction))) train_rows = rows[:split_at] val_rows = rows[split_at:] for r in train_rows: r["split"] = "train" for r in val_rows: r["split"] = "val" write_jsonl(out_train, train_rows) write_jsonl(out_val, val_rows) print(f"Wrote {len(train_rows)} train rows to {out_train}") print(f"Wrote {len(val_rows)} val rows to {out_val}") if __name__ == "__main__": main()