Spaces:
Sleeping
Sleeping
| # -*- 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}" | |