Files
code-test-drone/utils/stereo_yolo/config.py
T

87 lines
4.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""StereoYoloConfig 与 JSON 配置加载(需求说明 §4 配置项需求)。
全部配置外置、构造期一次加载并校验:缺项、未知字段、类型错误、非法取值
一律拒绝进入运行期(fail-fast)。几何参数(F/B/cx/cy/原分辨率)不在此
重复存储——输出框位于**输入图像自身的原生坐标系**,几何参数的唯一来源
是 utils/config/stereo_ranger.json(§4 配置一致性要求:同一配置源驱动,
杜绝两处漂移)。
"""
import json
from dataclasses import dataclass, fields
from typing import Any, Dict, Optional, Tuple
_CONCURRENCY_MODES = ("parallel", "sequential")
_INT_FIELDS = frozenset({"imgsz"})
_STR_FIELDS = frozenset({"model_path", "concurrency"})
@dataclass
class StereoYoloConfig:
model_path: str = "models/yolo26n-drone.pt"
imgsz: int = 640 # 左右共用(§2.2 第 2 条:同一缩放、同一填充)
conf_threshold: float = 0.25 # ⚠占位:识别端自身过滤口径(§2.4)
iou_threshold: float = 0.7 # ⚠占位:NMS IoU 门限
class_whitelist: Optional[Tuple[int, ...]] = None # None = 不过滤(§4 可选白名单)
device: Optional[str] = None # None = 自动选择(cpu / cuda:0 / …)
concurrency: str = "parallel" # parallel = 左右并发(§5 硬性要求,串行会吃掉全部预算)
def __post_init__(self) -> None:
_require(bool(self.model_path.strip()), "model_path", "不得为空")
_require(
self.imgsz >= 32 and self.imgsz % 32 == 0,
"imgsz",
"须为 32 的整数倍且 ≥ 32(YOLO 步长约束)",
)
_require(0.0 <= self.conf_threshold <= 1.0, "conf_threshold", "须落在 [0, 1]")
_require(0.0 < self.iou_threshold <= 1.0, "iou_threshold", "须落在 (0, 1]")
_require(self.concurrency in _CONCURRENCY_MODES, "concurrency", f"须为 {_CONCURRENCY_MODES} 之一")
_require(self.device is None or bool(self.device.strip()), "device", "须为非空字符串或 null")
if self.class_whitelist is not None:
ids = tuple(self.class_whitelist)
_require(
len(ids) > 0 and all(isinstance(item, int) and not isinstance(item, bool) and item >= 0 for item in ids),
"class_whitelist",
"须为非空非负整数列表或 null",
)
self.class_whitelist = ids
def load_from_json(path: str) -> StereoYoloConfig:
"""从 JSON 文件构建配置;缺项/未知字段/类型错误/非法取值一律抛 ValueError。"""
with open(path, encoding="utf-8") as handle:
data: Dict[str, Any] = json.load(handle)
if not isinstance(data, dict):
raise ValueError("配置非法: 顶层须为 JSON 对象")
known = {item.name for item in fields(StereoYoloConfig)}
unknown = sorted(set(data) - known)
if unknown:
raise ValueError(f"未知配置字段: {unknown}")
missing = sorted(known - set(data))
if missing:
raise ValueError(f"缺少配置字段: {missing}")
for name, value in data.items():
if name == "class_whitelist":
if value is not None and not (
isinstance(value, list)
and all(isinstance(item, int) and not isinstance(item, bool) for item in value)
):
raise ValueError("配置类型非法: class_whitelist 须为整数数组或 null")
elif name == "device":
if value is not None and not isinstance(value, str):
raise ValueError("配置类型非法: device 须为字符串或 null")
elif name in _STR_FIELDS:
if not isinstance(value, str):
raise ValueError(f"配置类型非法: {name} 须为字符串")
elif name in _INT_FIELDS:
if not isinstance(value, int) or isinstance(value, bool):
raise ValueError(f"配置类型非法: {name} 须为整数")
elif isinstance(value, bool) or not isinstance(value, (int, float)):
raise ValueError(f"配置类型非法: {name} 须为数值")
return StereoYoloConfig(**data)
def _require(condition: bool, field_name: str, message: str) -> None:
if not condition:
raise ValueError(f"配置非法: {field_name} {message}")