| """ | |
| Configuration management for the Thermal Pattern Analysis project. | |
| Loads YAML configs, provides attribute-style access, and handles | |
| device selection + reproducibility seeding. | |
| """ | |
| import os | |
| import yaml | |
| import torch | |
| import random | |
| import numpy as np | |
| from pathlib import Path | |
| class Config: | |
| """Hierarchical configuration with attribute-style access.""" | |
| def __init__(self, config_dict: dict): | |
| for key, value in config_dict.items(): | |
| if isinstance(value, dict): | |
| setattr(self, key, Config(value)) | |
| elif isinstance(value, list): | |
| setattr(self, key, [ | |
| Config(v) if isinstance(v, dict) else v for v in value | |
| ]) | |
| else: | |
| setattr(self, key, value) | |
| def to_dict(self) -> dict: | |
| """Convert back to a plain dictionary.""" | |
| result = {} | |
| for key, value in self.__dict__.items(): | |
| if isinstance(value, Config): | |
| result[key] = value.to_dict() | |
| elif isinstance(value, list): | |
| result[key] = [ | |
| v.to_dict() if isinstance(v, Config) else v for v in value | |
| ] | |
| else: | |
| result[key] = value | |
| return result | |
| def __repr__(self): | |
| return f"Config({self.to_dict()})" | |
| def get(self, key, default=None): | |
| """Safe attribute access with a default value.""" | |
| return getattr(self, key, default) | |
| def load_config(config_path: str = "configs/config.yaml") -> Config: | |
| """ | |
| Load configuration from a YAML file. | |
| Args: | |
| config_path: Path to the YAML configuration file. | |
| Returns: | |
| Config object with attribute-style access. | |
| """ | |
| config_path = Path(config_path) | |
| if not config_path.exists(): | |
| raise FileNotFoundError(f"Config file not found: {config_path}") | |
| with open(config_path, "r") as f: | |
| config_dict = yaml.safe_load(f) | |
| return Config(config_dict) | |
| def setup_device(config: Config) -> torch.device: | |
| """ | |
| Determine the compute device based on config and availability. | |
| Auto mode picks CUDA if available, otherwise CPU. | |
| """ | |
| device_str = config.get("device", "auto") | |
| if device_str == "auto": | |
| device = torch.device("cuda" if torch.cuda.is_available() else "cpu") | |
| else: | |
| device = torch.device(device_str) | |
| return device | |
| def set_seed(seed: int = 42): | |
| """Set random seeds for reproducibility across all libraries.""" | |
| random.seed(seed) | |
| np.random.seed(seed) | |
| torch.manual_seed(seed) | |
| if torch.cuda.is_available(): | |
| torch.cuda.manual_seed_all(seed) | |
| torch.backends.cudnn.deterministic = True | |
| torch.backends.cudnn.benchmark = False | |
| def ensure_dirs(config: Config): | |
| """Create all output directories specified in config.paths.""" | |
| paths = config.get("paths", None) | |
| if paths is None: | |
| return | |
| for attr in ["checkpoints", "logs", "results", "visualizations"]: | |
| dir_path = paths.get(attr, None) | |
| if dir_path: | |
| os.makedirs(dir_path, exist_ok=True) | |