121 lines
4.8 KiB
Python
121 lines
4.8 KiB
Python
# 검사 판별 로직 — PatMax 결과 판독 + 모델 판별 + Pass/Fail 판정
|
|
import cv2
|
|
import numpy as np
|
|
|
|
# Cognex 카메라 셀 매핑 (GV 방식 fallback용으로 유지)
|
|
PATTERN_RESULT_CELLS = {
|
|
"A27": {"id": 1, "name": "LOW REF", "model": "LX3", "type": "RH"},
|
|
"A77": {"id": 2, "name": "LOW REF", "model": "LX3", "type": "LH"},
|
|
"A127": {"id": 3, "name": "LOW REF NAS", "model": "LX3", "type": "RH"},
|
|
"A177": {"id": 4, "name": "LOW REF NAS", "model": "LX3", "type": "LH"},
|
|
}
|
|
|
|
|
|
class Inspector:
|
|
|
|
# ── 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:
|
|
"""A27/A77/A127/A177 셀 조회 → #ERR이면 실패, 그 외 점수 파싱."""
|
|
results = {}
|
|
for cell, model_info in PATTERN_RESULT_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) -> 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"]
|
|
in_allowed = model["id"] in allowed_model_ids
|
|
|
|
return {
|
|
"matched": True,
|
|
"in_allowed": in_allowed,
|
|
"model": model,
|
|
"score": best_info["score"],
|
|
"cognex_pass": in_allowed,
|
|
"status": (
|
|
f"{model['name']} {model['model']} {model['type']} ({best_info['score']:.1f}점)"
|
|
if in_allowed
|
|
else f"허용 외 모델: {model['name']} {model['model']} {model['type']}"
|
|
),
|
|
}
|
|
|
|
def judge(self, cognex_pass: bool, basler_pass: bool) -> str:
|
|
return "PASS" if cognex_pass and basler_pass else "FAIL"
|