a
This commit is contained in:
@@ -0,0 +1,215 @@
|
||||
"""
|
||||
ML配置加载器 — 解析 /usr/local/Models/ml.json
|
||||
直接翻译 C++ UrineCheck14::initconfig() + UrineCheck_Class::initconfig()
|
||||
|
||||
Author: 小月
|
||||
Date: 2026-05-13
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
from collections import defaultdict
|
||||
from typing import Dict, List, Optional, Callable, Tuple
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
logger = logging.getLogger("ml_config")
|
||||
|
||||
# 常量(与C++一致)
|
||||
MAX_PAPER_TYPE = 21
|
||||
MAX_PAPER_BLOCK = 15
|
||||
MAX_ALG_TAG = 8
|
||||
|
||||
# 各检测项在 ml.json 中的 name → 结果索引函数类型映射
|
||||
# algorithm=0 → Red, algorithm=1 → Green/Blue (原始)
|
||||
ITEM_FUNC_MAP = {
|
||||
"uro": "uro",
|
||||
"glu": "glu",
|
||||
"ket": "ket",
|
||||
"bil": "bil",
|
||||
"pro": "pro",
|
||||
"nit": "nit",
|
||||
"ph": "ph",
|
||||
"bld": "bld",
|
||||
"sg": "sg",
|
||||
"wbc": "wbc",
|
||||
"vc": "vc",
|
||||
"cre": "cre",
|
||||
"uca": "uca",
|
||||
"mau": "mau",
|
||||
}
|
||||
|
||||
|
||||
class MLConfigLoader:
|
||||
"""ML模型配置加载器 — 全局单例"""
|
||||
|
||||
def __init__(self, config_path: str = "/usr/local/Models/ml.json"):
|
||||
self.config_path = config_path
|
||||
|
||||
# 三维查表: [paperType][position][rawIndex] → outputIndex
|
||||
self.itemsResultIdxMapTbl: List[List[List[int]]] = [
|
||||
[[-1] * MAX_ALG_TAG for _ in range(MAX_PAPER_BLOCK)]
|
||||
for _ in range(MAX_PAPER_TYPE)
|
||||
]
|
||||
|
||||
# 二维: [paperType][position] → 输出id
|
||||
self.itemsStatIds: List[List[int]] = [
|
||||
[-1] * MAX_PAPER_BLOCK for _ in range(MAX_PAPER_TYPE)
|
||||
]
|
||||
|
||||
# [paperType][position] → 已加载的 OpenCV RTrees 模型
|
||||
self.models: List[Dict[int, cv2.ml.RTrees]] = [
|
||||
{} for _ in range(MAX_PAPER_TYPE)
|
||||
]
|
||||
|
||||
# [paperType][position] → 结果索引函数类型名
|
||||
# "uro"|"glu"|...|"red_uro"|"red_glu"|...|"pet_glu"|"pet_sg"
|
||||
self.result_funcs: List[Dict[int, str]] = [
|
||||
{} for _ in range(MAX_PAPER_TYPE)
|
||||
]
|
||||
|
||||
# PRO/MAU/SG/pH 在各 paperType 上的 position
|
||||
self.positionsPro: List[int] = [-1] * MAX_PAPER_TYPE
|
||||
self.positionsMau: List[int] = [-1] * MAX_PAPER_TYPE
|
||||
self.positionsSg: List[int] = [-1] * MAX_PAPER_TYPE
|
||||
self.positionsPh: List[int] = [-1] * MAX_PAPER_TYPE
|
||||
|
||||
# 文件名缓存(避免重复加载同一个 .data 文件)
|
||||
self._file_cache: Dict[str, cv2.ml.RTrees] = {}
|
||||
|
||||
self._loaded = False
|
||||
|
||||
def load(self) -> bool:
|
||||
"""加载 ml.json 并初始化所有配置"""
|
||||
if self._loaded:
|
||||
return True
|
||||
|
||||
try:
|
||||
with open(self.config_path, 'r') as f:
|
||||
config = json.load(f)
|
||||
except Exception as e:
|
||||
logger.error(f"无法加载 {self.config_path}: {e}")
|
||||
return False
|
||||
|
||||
ml = config.get("ml", [])
|
||||
if not ml:
|
||||
logger.error("ml.json 中未找到 'ml' 数组")
|
||||
return False
|
||||
|
||||
for item in ml:
|
||||
name = item.get("name", "")
|
||||
models_list = item.get("model", [])
|
||||
|
||||
if name not in ITEM_FUNC_MAP:
|
||||
logger.warning(f"未知检测项目: {name}")
|
||||
continue
|
||||
|
||||
for paper_type, block in enumerate(models_list):
|
||||
if paper_type >= MAX_PAPER_TYPE:
|
||||
break
|
||||
|
||||
position = block.get("positon", -1)
|
||||
if position < 0:
|
||||
continue
|
||||
|
||||
item_id = block.get("id", 0)
|
||||
filepath = block.get("filepath", "")
|
||||
algorithm = block.get("algorithm", 1)
|
||||
indexmap_str = block.get("indexmap", "0")
|
||||
|
||||
# 记录 PRO/MAU/SG/pH 的 position
|
||||
if name == "pro":
|
||||
self.positionsPro[paper_type] = position
|
||||
elif name == "mau":
|
||||
self.positionsMau[paper_type] = position
|
||||
elif name == "sg":
|
||||
self.positionsSg[paper_type] = position
|
||||
elif name == "ph":
|
||||
self.positionsPh[paper_type] = position
|
||||
|
||||
# 记录 output id
|
||||
self.itemsStatIds[paper_type][position] = item_id
|
||||
|
||||
# 解析 indexmap
|
||||
index_map = [int(x.strip()) for x in indexmap_str.split(",")]
|
||||
for k, val in enumerate(index_map):
|
||||
if k < MAX_ALG_TAG:
|
||||
self.itemsResultIdxMapTbl[paper_type][position][k] = val
|
||||
|
||||
# 加载模型文件(带缓存)
|
||||
if filepath not in self._file_cache:
|
||||
try:
|
||||
model = cv2.ml.RTrees_load(filepath)
|
||||
if model is None or model.empty():
|
||||
logger.error(f"模型加载失败: {filepath}")
|
||||
continue
|
||||
self._file_cache[filepath] = model
|
||||
except Exception as e:
|
||||
logger.error(f"模型加载异常 {filepath}: {e}")
|
||||
continue
|
||||
|
||||
self.models[paper_type][position] = self._file_cache[filepath]
|
||||
|
||||
# 确定结果索引函数
|
||||
func_type = self._resolve_func_type(name, algorithm)
|
||||
self.result_funcs[paper_type][position] = func_type
|
||||
|
||||
self._loaded = True
|
||||
count = sum(len(m) for m in self.models)
|
||||
logger.info(f"ML配置加载完成: {count} 个模型, {len(self._file_cache)} 个唯一文件")
|
||||
return True
|
||||
|
||||
def _resolve_func_type(self, name: str, algorithm: int) -> str:
|
||||
"""根据试纸类型确定结果索引函数名"""
|
||||
base = ITEM_FUNC_MAP[name]
|
||||
if algorithm == 0:
|
||||
# 红色试纸
|
||||
return f"red_{base}"
|
||||
elif algorithm == 2:
|
||||
# PET 试纸 (仅 glu/sg 有)
|
||||
return f"pet_{base}"
|
||||
else:
|
||||
# 绿色/蓝色试纸(原始)
|
||||
return base
|
||||
|
||||
# ---- 便捷查询方法 ----
|
||||
|
||||
def get_model(self, paper_type: int, position: int) -> Optional[cv2.ml.RTrees]:
|
||||
if paper_type < 0 or paper_type >= MAX_PAPER_TYPE:
|
||||
return None
|
||||
return self.models[paper_type].get(position)
|
||||
|
||||
def get_func_type(self, paper_type: int, position: int) -> Optional[str]:
|
||||
if paper_type < 0 or paper_type >= MAX_PAPER_TYPE:
|
||||
return None
|
||||
return self.result_funcs[paper_type].get(position)
|
||||
|
||||
def get_stat_id(self, paper_type: int, position: int) -> int:
|
||||
if paper_type < 0 or paper_type >= MAX_PAPER_TYPE:
|
||||
return -1
|
||||
return self.itemsStatIds[paper_type][position]
|
||||
|
||||
def get_output_index(self, paper_type: int, position: int, raw_index: int) -> int:
|
||||
"""通过 indexmap 将算法输出index映射为最终输出index"""
|
||||
if paper_type < 0 or paper_type >= MAX_PAPER_TYPE:
|
||||
return 0
|
||||
if position < 0 or position >= MAX_PAPER_BLOCK:
|
||||
return 0
|
||||
if raw_index < 0 or raw_index >= MAX_ALG_TAG:
|
||||
return 0
|
||||
mapped = self.itemsResultIdxMapTbl[paper_type][position][raw_index]
|
||||
return mapped if mapped >= 0 else 0
|
||||
|
||||
def get_max_mau(self, paper_type: int) -> int:
|
||||
"""MAU最大等级: red=3, others=2"""
|
||||
return 2 if paper_type == 0 else 3
|
||||
|
||||
def get_max_sg(self, paper_type: int) -> int:
|
||||
"""SG最大等级: red=6, others=5"""
|
||||
return 5 if paper_type == 0 else 6
|
||||
|
||||
def has_model_at(self, paper_type: int, position: int) -> bool:
|
||||
if paper_type < 0 or paper_type >= MAX_PAPER_TYPE:
|
||||
return False
|
||||
return position in self.models[paper_type]
|
||||
Reference in New Issue
Block a user