from dataclasses import dataclass, field from pathlib import Path import yaml @dataclass class FilenameConfig: video_in_zip: str = "left.mp4" video_tmp_suffix: str = ".mp4" zip_extension: str = ".zip" mask: str = "mask.png" metadata: str = "metadata.json" frame: str = "frame.png" overlay: str = "overlay.png" mask_vis: str = "mask_vis.png" gif_original_hires: str = "video_original_hires.gif" gif_original_lowres: str = "video_original_lowres.gif" gif_overlay_hires: str = "video_overlay_hires.gif" gif_overlay_lowres: str = "video_overlay_lowres.gif" @dataclass class AppConfig: storage: str data_dir: str out_dir: str optical_flow_config_file: str questions_config_file: str display_max: int = 480 fps_fallback: int = 25 max_frames: int = 100 clips_file: str = "config/clips.txt" filenames: FilenameConfig = field(default_factory=FilenameConfig) questions: list = field(default_factory=list, init=False) def get_questions(self): return [ ( s["section"], [ ( item["key"], item["label"], [str(o) for o in item["options"]], str(item["default"]) if item.get("default") is not None else None, ) for item in s["items"] ], ) for s in self.questions ] @dataclass class OpticalFlowConfig: enabled: bool = False norm_squared_threshold: float = 0.3 gaussian_kernel: tuple[int, int] = (5, 5) brightness_range: tuple[int, int] = (20, 235) def load_optical_flow_config(path: Path) -> OpticalFlowConfig: with open(path) as f: data = yaml.safe_load(f) data["gaussian_kernel"] = tuple(data["gaussian_kernel"]) data["brightness_range"] = tuple(data["brightness_range"]) return OpticalFlowConfig(**data) def load_questions_config(path: Path) -> list: with open(path) as f: return yaml.safe_load(f) def load_config(path: Path) -> AppConfig: with open(path) as f: data = yaml.safe_load(f) for required in ( "storage", "data_dir", "out_dir", "optical_flow_config_file", "questions_config_file", ): if not data.get(required): raise ValueError(f"{path}: missing required field '{required}'.") fn_data = data.pop("filenames", {}) cfg = AppConfig(**data) cfg.filenames = FilenameConfig(**fn_data) cfg.questions = load_questions_config(Path(cfg.questions_config_file)) return cfg