215 lines
7.4 KiB
Python
215 lines
7.4 KiB
Python
"""
|
|
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] |