Add PySide6 camera UI, YOLO/Tesseract detection pipeline, capture metadata, configuration, and project gitignore.
97 lines
2.7 KiB
Python
97 lines
2.7 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",
|
|
"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
|