FeatureLab / backend /py /app /services /runtime_adapter.py
VitalyVorobyev's picture
dexined configurable
0a3fef3
import os
import time
from dataclasses import dataclass, field
from typing import Any, Dict, Literal, Optional
import cv2
import numpy as np
from ..inference.classical import detect_classical
from ..inference.dl import DL_MODELS, detect_dl
DEFAULT_PARAMS: Dict[str, Any] = {
"canny_low": 50,
"canny_high": 150,
"harris_k": 0.05,
"harris_block": 2,
"harris_ksize": 3,
"hough_thresh": 50,
"hough_min_len": 30,
"hough_max_gap": 5,
"ellipse_min_area": 300,
"max_ellipses": 5,
"line_detector": "hough",
"dexined_threshold_mode": "adaptive",
"dexined_threshold_sigma": 1.0,
"dexined_threshold_offset": 0.0,
"dexined_threshold_value": 0.3,
"dexined_use_marching_squares": False,
}
def _to_bool(value: Any) -> bool:
if isinstance(value, bool):
return value
if isinstance(value, str):
return value.strip().lower() in {"1", "true", "yes", "on"}
return bool(value)
PARAM_TYPES: Dict[str, Any] = {
"canny_low": int,
"canny_high": int,
"harris_k": float,
"harris_block": int,
"harris_ksize": int,
"hough_thresh": int,
"hough_min_len": int,
"hough_max_gap": int,
"ellipse_min_area": int,
"max_ellipses": int,
"line_detector": lambda x: str(x).lower(),
"dexined_threshold_mode": lambda x: str(x).lower(),
"dexined_threshold_sigma": float,
"dexined_threshold_offset": float,
"dexined_threshold_value": float,
"dexined_use_marching_squares": _to_bool,
}
CLASSICAL_MODEL_INFO = {"name": "opencv-classical", "version": cv2.__version__}
try:
import onnxruntime as ort # type: ignore
except Exception: # pragma: no cover
ort = None # type: ignore
DL_MODEL_INFO = {
"name": "onnxruntime" if ort is not None else "onnxruntime-missing",
"version": getattr(ort, "__version__", "unknown"),
}
def merge_params(params: Optional[Dict[str, Any]]) -> Dict[str, Any]:
merged = DEFAULT_PARAMS.copy()
if params:
for key, value in params.items():
if value is None or key not in DEFAULT_PARAMS:
continue
caster = PARAM_TYPES.get(key, lambda x: x)
try:
merged[key] = caster(value)
except (TypeError, ValueError):
continue
return merged
@dataclass
class DetectionResult:
overlays: Dict[str, np.ndarray] = field(default_factory=dict)
features: Dict[str, Dict[str, Any]] = field(default_factory=dict)
timings_ms: Dict[str, float] = field(default_factory=dict)
fps_estimate: Optional[float] = None
models: Dict[str, Dict[str, Any]] = field(default_factory=dict)
def run_detection(
image: np.ndarray,
detector: str,
params: Optional[Dict[str, Any]] = None,
mode: Literal["classical", "dl", "both"] = "classical",
dl_choice: Optional[str] = None,
) -> DetectionResult:
merged = merge_params(params)
overlays: Dict[str, np.ndarray] = {}
features: Dict[str, Dict[str, Any]] = {}
timings: Dict[str, float] = {}
models: Dict[str, Dict[str, Any]] = {}
execute_classical = mode in ("classical", "both")
execute_dl = mode in ("dl", "both")
total_ms = 0.0
if execute_classical:
t0 = time.perf_counter()
classical_img, classical_meta = detect_classical(
image,
detector,
merged["canny_low"],
merged["canny_high"],
merged["harris_k"],
merged["harris_block"],
merged["harris_ksize"],
merged["hough_thresh"],
merged["hough_min_len"],
merged["hough_max_gap"],
merged["ellipse_min_area"],
merged["max_ellipses"],
merged["line_detector"],
)
t_ms = (time.perf_counter() - t0) * 1000.0
overlays["classical"] = classical_img
features["classical"] = classical_meta
timings["classical"] = round(t_ms, 2)
models["classical"] = CLASSICAL_MODEL_INFO
total_ms += t_ms
if execute_dl:
t0 = time.perf_counter()
dl_img, dl_meta = detect_dl(image, detector, dl_choice, params=merged)
t_ms = (time.perf_counter() - t0) * 1000.0
overlays["dl"] = dl_img
features["dl"] = dl_meta
timings["dl"] = round(t_ms, 2)
model_name = (
os.path.basename(dl_meta["model_path"]) if "model_path" in dl_meta else DL_MODEL_INFO["name"]
)
models["dl"] = {"name": model_name, "version": DL_MODEL_INFO["version"]}
total_ms += t_ms
timings["total"] = round(total_ms, 2)
fps = round(1000.0 / total_ms, 2) if total_ms > 0 else None
return DetectionResult(
overlays=overlays,
features=features,
timings_ms=timings,
fps_estimate=fps,
models=models,
)
__all__ = [
"DetectionResult",
"DEFAULT_PARAMS",
"DL_MODELS",
"merge_params",
"run_detection",
]