Files
ant-vision-inspector/logic/inspector.py
2026-06-18 13:38:27 +09:00

132 lines
4.9 KiB
Python

# 검사 판별 로직 — PatMax 결과 판독 + 모델 판별 + Pass/Fail 판정
import cv2
import numpy as np
from db.sql_client import SQLClient
from logic.products import model_display_label
class Inspector:
def __init__(self):
self._pattern_cells: dict = {}
def set_pattern_cells(self, cells: dict):
"""DB 기반 PatMax 셀 매핑 (refresh_wk_results 시 갱신)."""
self._pattern_cells = cells or {}
# ── Python PatMax 매칭 (주 경로) ─────────────────────────────────── #
def match_image(self, image_bytes: bytes, matcher: "PatternMatcher") -> dict:
"""
FTP로 받은 이미지 바이트를 Python PatternMatcher로 매칭.
반환 형식은 read_patmax_results와 동일하여 identify_model에서 그대로 사용.
"""
if not image_bytes:
return {}
arr = np.frombuffer(image_bytes, dtype=np.uint8)
img = cv2.imdecode(arr, cv2.IMREAD_UNCHANGED)
if img is None:
return {}
all_scores = matcher.match_all(img)
results = {}
for pid, score in all_scores.items():
info = matcher.get_product_info(pid)
if info is None:
continue
results[f"PY_{pid}"] = {
"matched": score >= matcher.score_threshold,
"score": score,
"model": info,
"raw": f"python_match={score:.1f}",
}
return results
# ── Cognex GV 셀 방식 (fallback) ────────────────────────────────── #
def read_patmax_results(self, insight) -> dict:
"""PatMax 결과 셀 조회 → #ERR이면 실패, 그 외 점수 파싱."""
results = {}
for cell, model_info in self._pattern_cells.items():
try:
insight._send(f"GV{cell}")
code = insight._read_line()
if code != "1":
results[cell] = {
"matched": False, "score": 0.0,
"model": model_info, "raw": ""
}
continue
value = insight._read_line()
if "#ERR" in value or value.strip() == "":
results[cell] = {
"matched": False, "score": 0.0,
"model": model_info, "raw": value
}
else:
# "(736.1,742.0) -1.8 = 82.9" 형식에서 = 뒤 값 추출
try:
score = float(value.split("=")[-1].strip())
except Exception:
score = 0.0
results[cell] = {
"matched": True, "score": score,
"model": model_info, "raw": value
}
except Exception as e:
print(f"[PatMax] {cell} 읽기 오류: {e}")
results[cell] = {
"matched": False, "score": 0.0,
"model": model_info, "raw": ""
}
return results
# ── 공통: 모델 판별 + 판정 ──────────────────────────────────────── #
def identify_model(self, results: dict,
allowed_model_ids: "list | None" = None,
allowed_article_ids: "set | None" = None) -> dict:
"""매칭된 패턴 중 점수가 가장 높은 것을 선택해 허용 여부 판별."""
matched_patterns = [
(cell, info) for cell, info in results.items()
if info["matched"]
]
if not matched_patterns:
return {
"matched": False, "in_allowed": False,
"model": None, "score": 0.0,
"cognex_pass": False, "status": "인식 불가"
}
_best_cell, best_info = max(matched_patterns, key=lambda x: x[1]["score"])
model = best_info["model"]
label = model_display_label(model)
if allowed_article_ids is not None:
in_allowed = (
SQLClient._norm_id(model.get("article_id")) in allowed_article_ids
)
else:
in_allowed = model["id"] in (allowed_model_ids or [])
return {
"matched": True,
"in_allowed": in_allowed,
"model": model,
"score": best_info["score"],
"cognex_pass": in_allowed,
"status": (
f"{label} ({best_info['score']:.1f}점)"
if in_allowed
else f"작업 대상 외: {label}"
),
}
def judge(self, cognex_pass: bool, basler_pass: bool) -> str:
return "PASS" if cognex_pass and basler_pass else "FAIL"