Initial MVP application skeleton
Add PySide6 camera UI, YOLO/Tesseract detection pipeline, capture metadata, configuration, and project gitignore.
This commit is contained in:
96
app/config.py
Normal file
96
app/config.py
Normal file
@@ -0,0 +1,96 @@
|
||||
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",
|
||||
"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
|
||||
Reference in New Issue
Block a user