dkrak737's picture
Upload folder using huggingface_hub
c8716b4 verified
Raw
History Blame Contribute Delete
5.95 kB
# -*- coding: utf-8 -*-
"""
render.py
ํŒ์ • ๊ฒฐ๊ณผ โ†’ ๊ฒ€์‚ฌ๊ด€์šฉ ์ถœ๋ ฅ (๋Œ€ํ‘œ ์Šฌ๋ผ์ด์Šค ์˜ค๋ฒ„๋ ˆ์ด + ๋ณด๊ณ ์„œ + RAG ์กฐ์น˜ ์ง€์นจ).
ํ‘œ์‹œ ๊ทœ์น™(๋ฐ‘์— ๊น”๋ฆฐ ๋ชจ๋ธ์ด ๋‹ฌ๋ผ์„œ):
- ๋‹ค๊ณต์„ฑ(porosity) : seg ๋งˆ์Šคํฌ โ†’ ๋นจ๊ฐ• ๋ฐ˜ํˆฌ๋ช… (๋Œ€ํ‘œ ์Šฌ๋ผ์ด์Šค์—๋งŒ lazy ๊ณ„์‚ฐ)
- ๋ ˆ์ง„(resin) : ๊ฒ€์ถœ bbox โ†’ ๋นจ๊ฐ• ์‚ฌ๊ฐํ˜•
- ํŒฝ์ฐฝ(swelling) : ์œ„์น˜ ์—†์Œ(๋ถ„๋ฅ˜๊ธฐ) โ†’ ํ‘œ์‹œ ์•ˆ ํ•จ
์ขŒํ‘œ๊ณ„: ๊ฒ€์ถœ ๋ฐ•์Šค/ํ‘œ์‹œ ์ด๋ฏธ์ง€ ๋ชจ๋‘ 'recipe ํฌ๋กญ' ์ขŒํ‘œ๊ณ„๋ผ ๊ทธ๋Œ€๋กœ ๊ฒน์ณ์ง„๋‹ค.
"""
import os
import sys
import cv2
import numpy as np
import recipe
from decide import DEFECT_CANON
from data import open_slice
from seg_tiles import seg_porosity_mask
# generate_guidance.py(RAG) ์œ„์น˜๋ฅผ ๋ ˆ์ด์•„์›ƒ์— ๋ฌด๊ด€ํ•˜๊ฒŒ ํƒ์ƒ‰.
# ๋กœ์ปฌ: ../teammate/rag, HF Space: ./rag, env GLUE_RAG_DIR ์šฐ์„ .
def _find_rag_dir():
here = os.path.dirname(os.path.abspath(__file__))
cands = [os.environ.get("GLUE_RAG_DIR"),
os.path.join(here, "rag"),
os.path.join(here, "teammate", "rag"),
os.path.join(os.path.dirname(here), "teammate", "rag")]
for c in cands:
if c and os.path.exists(os.path.join(c, "generate_guidance.py")):
return c
return cands[-1]
_RAG_DIR = _find_rag_dir()
if _RAG_DIR not in sys.path:
sys.path.insert(0, _RAG_DIR)
# ----------------------------------------------------------------------
# ๋Œ€ํ‘œ ์Šฌ๋ผ์ด์Šค ์„ ํƒ + ํฌ๋กญ ์ด๋ฏธ์ง€
# ----------------------------------------------------------------------
def _crop_rgb(slices, idx):
"""idx ์Šฌ๋ผ์ด์Šค์˜ ํฌ๋กญ ์ด๋ฏธ์ง€(๊ฒ€์ถœ/ํ‘œ์‹œ ๊ณตํ†ต ์ขŒํ‘œ๊ณ„) โ†’ RGB ndarray."""
sl = next((s for s in slices if s["idx"] == idx), None)
if sl is None:
return None
return np.array(recipe.to_rgb(open_slice(sl))) # ์ด๋ฏธ ํฌ๋กญ๋จ
def pick_slice(result, slices):
"""๋Œ€ํ‘œ ์Šฌ๋ผ์ด์Šค = ๊ฒ€์ถœ ์‹ ๋ขฐ๋„๊ฐ€ ๊ฐ€์žฅ ๋†’์€ ์Šฌ๋ผ์ด์Šค.
๊ฒ€์ถœ ๋ฐ•์Šค๊ฐ€ ์—†์œผ๋ฉด(์˜ˆ: swelling ๋‹จ๋…) (None, None) โ†’ ์œ„์น˜ ํ‘œ์‹œ ์ƒ๋žต.
๋ฐ˜ํ™˜: (slice_idx, crop_rgb ndarray)."""
best_idx, best_conf = None, -1.0
for box in result["porosity"]["boxes"] + result["resin"]["boxes"]:
cf, si = box[4], int(box[5])
if cf > best_conf:
best_conf, best_idx = cf, si
if best_idx is None:
return None, None
return best_idx, _crop_rgb(slices, best_idx)
def _blend_mask(img, mask, color=(255, 0, 0), alpha=0.45):
"""๋งˆ์Šคํฌ ์˜์—ญ ๋ฐ˜ํˆฌ๋ช… ์ƒ‰(RGB)."""
overlay = img.copy()
overlay[mask.astype(bool)] = color
return cv2.addWeighted(overlay, alpha, img, 1 - alpha, 0)
def make_overlay(crop_rgb, result, cur, seg_model=None):
"""๋Œ€ํ‘œ ์Šฌ๋ผ์ด์Šค(cur) ์œ„์— ๊ทธ ์Šฌ๋ผ์ด์Šค์˜ ๊ฒฐํ•จ๋งŒ ๊ทธ๋ฆฐ๋‹ค."""
if crop_rgb is None:
return None
img = crop_rgb.copy()
# ๋‹ค๊ณต์„ฑ โ†’ seg ๋งˆ์Šคํฌ(์ด ํฌ๋กญ์—๋งŒ lazy). ์‹คํŒจ ์‹œ ๊ฒ€์ถœ ๋ฐ•์Šค๋กœ ํด๋ฐฑ.
drew_mask = False
if seg_model is not None and result["porosity"]["flag"]:
try:
mask, _ = seg_porosity_mask(seg_model, crop_rgb, result["cell_type"])
if mask.any():
img = _blend_mask(img, mask)
drew_mask = True
except Exception as e:
print(f"[seg ์ƒ๋žต] {e}", file=sys.stderr)
if not drew_mask:
for x1, y1, x2, y2, cf, si in result["porosity"]["boxes"]:
if si == cur:
cv2.rectangle(img, (int(x1), int(y1)), (int(x2), int(y2)), (255, 0, 0), 2)
# ๋ ˆ์ง„ โ†’ ๋ฐ•์Šค
for x1, y1, x2, y2, cf, si in result["resin"]["boxes"]:
if si == cur:
cv2.rectangle(img, (int(x1), int(y1)), (int(x2), int(y2)), (255, 128, 0), 2)
# ํŒฝ์ฐฝ โ†’ ํ‘œ์‹œ ์—†์Œ
return img
# ----------------------------------------------------------------------
# ๋ณด๊ณ ์„œ + RAG ์กฐ์น˜ ์ง€์นจ
# ----------------------------------------------------------------------
def make_report(result, decision):
sw = result["swelling"]
return {
"๋ฐฐํ„ฐ๋ฆฌ": result["battery_id"],
"์…€ ํ˜•ํƒœ": result["cell_type"],
"ํŒ์ •": decision["zone"],
"๊ฒฐํ•จ ์ข…๋ฅ˜": [DEFECT_CANON[d] for d in decision["defects"]] or ["์—†์Œ"],
"์ตœ๊ณ  ์‹ ๋ขฐ๋„": round(decision["worst_conf"], 3),
"swelling ๋น„์œจ": f"{sw['ratio']*100:.0f}% ({sw['n_swell']}/{result['n_slices']})",
"์‚ฌ์œ ": decision["reasons"],
}
def recommend(result, decision):
"""RAG(generate_guidance) โ†’ IATA ๊ทœ์ • ๊ธฐ๋ฐ˜ ์กฐ์น˜ ์ง€์นจ(ํ•œ/์˜).
๊ฒฐํ•จ ์—†์œผ๋ฉด '์ด์ƒ์—†์Œ' ํ‘œ๊ธฐ(API ํ˜ธ์ถœ ์—†์Œ). ํ‚ค/๋„คํŠธ์›Œํฌ ์—†์œผ๋ฉด ๊ฒฐ์ •์  ๋งคํ•‘์œผ๋กœ ํด๋ฐฑ."""
import generate_guidance as gg
defects = [DEFECT_CANON[d] for d in decision["defects"]]
if not defects:
return f"{gg.NORMAL_KO}\n\n{gg.NORMAL_EN}"
battery = {"battery_id": result["battery_id"],
"form": result["cell_type"], "defects": defects}
try:
kb, by_id = gg.load_kb()
chunks = gg.retrieve(defects, kb, by_id)
return gg.generate(battery, chunks) # Claude API (ANTHROPIC_API_KEY ํ•„์š”)
except Exception as e:
# ํด๋ฐฑ: ๊ฒฐ์ •์  ๋งคํ•‘๋งŒ์œผ๋กœ ๊ทผ๊ฑฐ ์กฐํ•ญ ๋‚˜์—ด (API ๋ถˆ๊ฐ€ ์‹œ ๋ฐ๋ชจ ์ง€์†)
try:
kb, by_id = gg.load_kb()
mapping = kb["meta"]["defect_to_chunk"]
refs = []
for d in defects:
for cid in mapping.get(d, []):
c = by_id.get(cid)
if c:
refs.append(f"- [{c['source']} ยท {c['ref']}] {c['text_ko']}")
body = "\n".join(dict.fromkeys(refs)) or "(๋งคํ•‘ ์กฐํ•ญ ์—†์Œ)"
return (f"โš ๏ธ AI ์ง€์นจ ์ƒ์„ฑ ๋ถˆ๊ฐ€({e}) โ†’ ๊ทœ์ • ๋งคํ•‘๋งŒ ํ‘œ์‹œ.\n"
f"ํƒ์ง€ ๊ฒฐํ•จ: {', '.join(defects)}\n{body}")
except Exception:
return f"โš ๏ธ ์ง€์นจ ์ƒ์„ฑ ๋ถˆ๊ฐ€: {e}"