Files
nanocode/urinecheck/backend/ml_config.py
T
Ubuntu 32b43f85bd a
2026-07-03 16:58:28 +08:00

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]