| | """ |
| | Dataset / bundle layout helpers. |
| | |
| | The spec (Appendix C) defines a canonical on-disk layout. This module provides |
| | non-opinionated helpers for creating and validating directories without baking |
| | policy into unrelated code. |
| | """ |
| |
|
| | from __future__ import annotations |
| |
|
| | from dataclasses import dataclass |
| | from pathlib import Path |
| | from typing import Iterable, List, Optional |
| |
|
| |
|
| | @dataclass(frozen=True) |
| | class CaptureBundleLayout: |
| | root: Path |
| |
|
| | @property |
| | def manifest_path(self) -> Path: |
| | return self.root / "manifest.json" |
| |
|
| | @property |
| | def devices_dir(self) -> Path: |
| | return self.root / "devices" |
| |
|
| | @property |
| | def calibration_dir(self) -> Path: |
| | return self.root / "calibration" |
| |
|
| | @property |
| | def annotations_dir(self) -> Path: |
| | return self.root / "annotations" |
| |
|
| | @property |
| | def teacher_outputs_dir(self) -> Path: |
| | return self.root / "teacher_outputs" |
| |
|
| | def device_dir(self, device_subdir: str) -> Path: |
| | return self.devices_dir / device_subdir |
| |
|
| |
|
| | def discover_capture_bundles(root: Path) -> List[Path]: |
| | """ |
| | Discover capture bundles under a directory. |
| | |
| | We define a capture bundle as any directory containing a `manifest.json`. |
| | """ |
| | root = Path(root) |
| | if not root.exists(): |
| | return [] |
| |
|
| | bundles: List[Path] = [] |
| | for child in root.iterdir(): |
| | if child.is_dir() and (child / "manifest.json").exists(): |
| | bundles.append(child) |
| | return sorted(bundles) |
| |
|
| |
|
| | def ensure_dir(path: Path) -> Path: |
| | path = Path(path) |
| | path.mkdir(parents=True, exist_ok=True) |
| | return path |
| |
|
| |
|
| | def resolve_relative(root: Path, rel: Optional[str]) -> Optional[Path]: |
| | if rel is None: |
| | return None |
| | return (Path(root) / rel).resolve() |
| |
|
| |
|
| | def validate_paths_exist(root: Path, rel_paths: Iterable[Optional[str]]) -> List[str]: |
| | """ |
| | Validate that each non-null relative path exists (relative to root). |
| | |
| | Returns a list of human-readable errors; empty means OK. |
| | """ |
| | errors: List[str] = [] |
| | for rel in rel_paths: |
| | if not rel: |
| | continue |
| | p = Path(root) / rel |
| | if not p.exists(): |
| | errors.append(f"Missing path: {rel} (resolved to {p})") |
| | return errors |
| |
|