from __future__ import annotations import json import logging import math import platform import time from pathlib import Path from typing import Any, Iterable import numpy as np LOGGER_NAME = "egg_damage" def get_logger(name: str | None = None) -> logging.Logger: logger = logging.getLogger(name or LOGGER_NAME) if not logger.handlers: handler = logging.StreamHandler() handler.setFormatter( logging.Formatter("%(asctime)s | %(levelname)s | %(name)s | %(message)s") ) logger.addHandler(handler) logger.setLevel(logging.INFO) return logger def save_json(data: Any, path: str | Path) -> None: path = Path(path) path.parent.mkdir(parents=True, exist_ok=True) with path.open("w", encoding="utf-8") as f: json.dump(make_json_safe(data), f, indent=2) def load_json(path: str | Path) -> Any: with Path(path).open("r", encoding="utf-8") as f: return json.load(f) def make_json_safe(value: Any) -> Any: if isinstance(value, dict): return {str(k): make_json_safe(v) for k, v in value.items()} if isinstance(value, (list, tuple)): return [make_json_safe(v) for v in value] if isinstance(value, Path): return str(value) if isinstance(value, (np.integer,)): return int(value) if isinstance(value, (np.floating,)): value = float(value) return value if math.isfinite(value) else None if isinstance(value, float): return value if math.isfinite(value) else None if isinstance(value, np.ndarray): return value.tolist() return value def now_stamp() -> str: return time.strftime("%Y%m%d-%H%M%S") def model_file_size_mb(path: str | Path | None) -> float | None: if not path: return None p = Path(path) if not p.exists(): return None return p.stat().st_size / (1024 * 1024) def timer() -> float: return time.perf_counter() def elapsed_ms(start: float, count: int = 1) -> float: if count <= 0: return 0.0 return (time.perf_counter() - start) * 1000.0 / count def format_distribution(rows: Iterable[dict[str, Any]]) -> str: lines = ["split | label | count", "--- | --- | ---"] for row in rows: lines.append(f"{row['split']} | {row['label']} | {row['count']}") return "\n".join(lines) def environment_summary() -> dict[str, Any]: summary = {"python": platform.python_version(), "platform": platform.platform()} try: import torch summary["torch"] = torch.__version__ summary["cuda_available"] = bool(torch.cuda.is_available()) summary["cuda_device"] = torch.cuda.get_device_name(0) if torch.cuda.is_available() else None except Exception: summary["torch"] = "not importable" summary["cuda_available"] = False summary["cuda_device"] = None return summary