Files
duck-stain-yolo/app/config.py

98 lines
2.8 KiB
Python

from __future__ import annotations
import json
from copy import deepcopy
from pathlib import Path
from typing import Any
APP_ROOT = Path(__file__).resolve().parent.parent
CONFIG_PATH = APP_ROOT / "app_config.json"
DEFAULT_CONFIG: dict[str, Any] = {
"camera": {
"index": 0,
"width": 1920,
"height": 1080,
"fps": 30,
"backend": "auto",
"rotation_degrees": 0,
"properties": {
"brightness": None,
"contrast": None,
"saturation": None,
"hue": None,
"gain": None,
"exposure": None,
"sharpness": None,
"auto_exposure": None,
"focus": None,
"auto_focus": None,
},
},
"detection": {
"model_path": "models/best.pt",
"confidence_threshold": 0.25,
"mode": "best",
"frame_stride": 5,
"image_size": 640,
"device": "cpu",
},
"ocr": {
"enabled": True,
"language": "eng",
"tesseract_cmd": None,
"threshold": True,
"scale": 2.0,
},
"capture": {
"photos_dir": "captures/photos",
"videos_dir": "captures/videos",
"image_extension": "jpg",
"video_extension": "mp4",
"video_codec": "mp4v",
},
"label_data": {"models": ["Regius", "Duvell"], "colors": ["T-NF-BLK-OUT-BST-G", "T-BLK-G"]},
}
def deep_merge(base: dict[str, Any], override: dict[str, Any]) -> dict[str, Any]:
result = deepcopy(base)
for key, value in override.items():
if isinstance(value, dict) and isinstance(result.get(key), dict):
result[key] = deep_merge(result[key], value)
else:
result[key] = value
return result
class AppConfig:
def __init__(self, path: Path = CONFIG_PATH) -> None:
self.path = path
self.data = self.load()
def load(self) -> dict[str, Any]:
if not self.path.exists():
self.path.parent.mkdir(parents=True, exist_ok=True)
self.save(DEFAULT_CONFIG)
return deepcopy(DEFAULT_CONFIG)
with self.path.open("r", encoding="utf-8") as config_file:
loaded = json.load(config_file)
return deep_merge(DEFAULT_CONFIG, loaded)
def save(self, data: dict[str, Any] | None = None) -> None:
if data is not None:
self.data = data
self.path.parent.mkdir(parents=True, exist_ok=True)
with self.path.open("w", encoding="utf-8") as config_file:
json.dump(self.data, config_file, indent=2, ensure_ascii=False)
config_file.write("\n")
def resolve_path(self, configured_path: str) -> Path:
path = Path(configured_path)
if path.is_absolute():
return path
return APP_ROOT / path