"""Calibration session path resolution and camera folder discovery.""" from __future__ import annotations import os from dataclasses import dataclass from pathlib import Path from typing import Dict, List, Optional, Tuple IMAGE_EXTENSIONS = (".bmp", ".png", ".jpg", ".jpeg") # Logical camera name -> folder aliases on disk CAMERA_FOLDER_ALIASES: Dict[str, Tuple[str, ...]] = { "lc": ("lc",), "lc-ir": ("lc-ir", "lc_ir", "LC-IR"), "rc": ("rc",), "rg": ("rg", "rgb"), "ir": ("ir", "IR"), } STEREO_PARTNERS = ("rc", "rg", "ir") @dataclass(frozen=True) class CameraFolder: logical_name: str path: Path folder_name: str def resolve_session_root(input_path: str | Path) -> Path: """Return flat or nested `images/` root containing camera folders.""" input_path = Path(input_path) images_dir = input_path / "images" if images_dir.is_dir(): return images_dir return input_path def discover_camera_folder( session_root: Path, logical_name: str ) -> Optional[CameraFolder]: aliases = CAMERA_FOLDER_ALIASES.get(logical_name) if not aliases: return None for folder in aliases: path = session_root / folder if path.is_dir(): return CameraFolder(logical_name, path, folder) return None def list_image_paths(camera_dir: Path) -> List[Path]: paths = [ camera_dir / name for name in os.listdir(camera_dir) if name.lower().endswith(IMAGE_EXTENSIONS) ] return sorted(paths) def json_path_for_image(image_path: Path) -> Path: return image_path.with_suffix(".json") def list_cameras_present(session_root: Path) -> List[CameraFolder]: found = [] for logical in CAMERA_FOLDER_ALIASES: cam = discover_camera_folder(session_root, logical) if cam is not None: found.append(cam) return found