Spaces:
Sleeping
Sleeping
| """ | |
| visualization.py — Zengin görsel overlay üretimi. | |
| Her inspection için 3 PNG dosyası üretir: | |
| 1. annotated.jpg — ana sonuç (parça yarı saydam + hasar bbox/polygon) | |
| 2. parts.png — sadece parça segmentasyonu | |
| 3. damages.png — sadece hasar mask'ları (şiddete göre renk kodlu) | |
| Kullanim: | |
| from visualization import render_all | |
| paths = render_all(image, damages, parts, output_dir="./visuals", | |
| inspection_id="abc123") | |
| """ | |
| import hashlib | |
| import logging | |
| from pathlib import Path | |
| import cv2 | |
| import numpy as np | |
| logger = logging.getLogger(__name__) | |
| # Hasar şiddet renkleri (BGR formatında - OpenCV) | |
| SEVERITY_COLORS_BGR = { | |
| "hafif": (129, 199, 132), # yeşil | |
| "orta": (51, 153, 255), # turuncu/amber | |
| "agir": (51, 51, 239), # kırmızı | |
| None: (180, 180, 180), # gri | |
| } | |
| # Parça için deterministik renkler (hash'ten) | |
| def part_color_bgr(part_name): | |
| """Parça adından deterministik BGR renk üret.""" | |
| h = hashlib.md5(part_name.encode()).digest() | |
| # 0-255 arası 3 byte → BGR | |
| return (int(h[0]) % 200 + 55, int(h[1]) % 200 + 55, int(h[2]) % 200 + 55) | |
| def severity_color(sev_level): | |
| """Şiddet seviyesinden BGR renk.""" | |
| return SEVERITY_COLORS_BGR.get(sev_level, SEVERITY_COLORS_BGR[None]) | |
| def overlay_mask(image, mask, color, alpha=0.4): | |
| """Bir mask'ı görüntü üzerine yarı saydam overlay'le.""" | |
| if mask is None or mask.sum() == 0: | |
| return image | |
| overlay = image.copy() | |
| overlay[mask > 0] = color | |
| return cv2.addWeighted(overlay, alpha, image, 1 - alpha, 0) | |
| def draw_polygon(image, polygon, color, thickness=2, fill_alpha=0.0): | |
| """Polygon çiz. fill_alpha > 0 ise dolu da.""" | |
| if polygon is None or len(polygon) < 3: | |
| return image | |
| pts = np.array(polygon, dtype=np.int32).reshape((-1, 2)) | |
| if fill_alpha > 0: | |
| overlay = image.copy() | |
| cv2.fillPoly(overlay, [pts], color) | |
| image = cv2.addWeighted(overlay, fill_alpha, image, 1 - fill_alpha, 0) | |
| cv2.polylines(image, [pts], isClosed=True, color=color, thickness=thickness) | |
| return image | |
| def draw_bbox(image, bbox, color, thickness=2, dashed=False): | |
| """Bounding box çiz, opsiyonel dashed.""" | |
| x1, y1, x2, y2 = [int(v) for v in bbox] | |
| if not dashed: | |
| cv2.rectangle(image, (x1, y1), (x2, y2), color, thickness) | |
| else: | |
| _draw_dashed_rect(image, (x1, y1), (x2, y2), color, thickness) | |
| return image | |
| def _draw_dashed_rect(image, p1, p2, color, thickness): | |
| """Kesik çizgili dikdörtgen.""" | |
| x1, y1 = p1 | |
| x2, y2 = p2 | |
| dash_len = 8 | |
| # Üst | |
| for x in range(x1, x2, dash_len * 2): | |
| cv2.line(image, (x, y1), (min(x + dash_len, x2), y1), color, thickness) | |
| # Alt | |
| for x in range(x1, x2, dash_len * 2): | |
| cv2.line(image, (x, y2), (min(x + dash_len, x2), y2), color, thickness) | |
| # Sol | |
| for y in range(y1, y2, dash_len * 2): | |
| cv2.line(image, (x1, y), (x1, min(y + dash_len, y2)), color, thickness) | |
| # Sağ | |
| for y in range(y1, y2, dash_len * 2): | |
| cv2.line(image, (x2, y), (x2, min(y + dash_len, y2)), color, thickness) | |
| def draw_label(image, text, position, bg_color, text_color=(255, 255, 255), | |
| font_scale=0.5, thickness=1): | |
| """Etiket çiz - arka plan + yazı.""" | |
| x, y = position | |
| font = cv2.FONT_HERSHEY_SIMPLEX | |
| (tw, th), baseline = cv2.getTextSize(text, font, font_scale, thickness) | |
| pad = 4 | |
| # Arka plan | |
| cv2.rectangle( | |
| image, | |
| (x, y - th - pad * 2), | |
| (x + tw + pad * 2, y), | |
| bg_color, | |
| -1, | |
| ) | |
| # Yazı | |
| cv2.putText( | |
| image, text, (x + pad, y - pad), | |
| font, font_scale, text_color, thickness, cv2.LINE_AA, | |
| ) | |
| return image | |
| def render_annotated(image, damages, parts): | |
| """Ana sonuç görseli — parça mask'ları + hasar bbox + polygon.""" | |
| result = image.copy() | |
| # 1. Parça maskelerini soluk overlay'le | |
| for p in parts: | |
| if p.mask is not None: | |
| color = part_color_bgr(p.name) | |
| result = overlay_mask(result, p.mask, color, alpha=0.12) | |
| # 2. Hasarları üzerine ekle (şiddete göre renk) | |
| for d in damages: | |
| sev_level = d.severity.get("level") if isinstance(d.severity, dict) else None | |
| color = severity_color(sev_level) | |
| # Polygon dolu + stroke | |
| if d.polygon_normalized: | |
| h, w = image.shape[:2] | |
| poly_px = [(p[0] * w, p[1] * h) for p in d.polygon_normalized] | |
| result = draw_polygon(result, poly_px, color, thickness=2, fill_alpha=0.35) | |
| # Bbox dashed | |
| if d.bbox: | |
| result = draw_bbox(result, d.bbox, color, thickness=1, dashed=True) | |
| # Etiket | |
| x1, y1, _, _ = [int(v) for v in d.bbox] | |
| sev_tr = {"hafif": "Hafif", "orta": "Orta", "agir": "Agir"}.get(sev_level, "?") | |
| label = f"{d.type} • {sev_tr}" | |
| result = draw_label(result, label, (x1, max(y1, 20)), color) | |
| return result | |
| def render_parts_only(image, parts): | |
| """Sadece parça segmentasyonu — her parça farklı renk.""" | |
| result = image.copy() | |
| for p in parts: | |
| if p.mask is None: | |
| continue | |
| color = part_color_bgr(p.name) | |
| result = overlay_mask(result, p.mask, color, alpha=0.45) | |
| # Etiket: parçanın merkezi | |
| ys, xs = np.where(p.mask > 0) | |
| if len(xs) > 0: | |
| cx, cy = int(np.mean(xs)), int(np.mean(ys)) | |
| result = draw_label(result, p.name, (cx, cy), color) | |
| return result | |
| def render_damages_only(image, damages): | |
| """Sadece hasar overlay — şiddete göre renk kodlu.""" | |
| # Karartılmış arka plan (hasarları öne çıkar) | |
| result = cv2.addWeighted(image, 0.3, np.zeros_like(image), 0.7, 0) | |
| for d in damages: | |
| sev_level = d.severity.get("level") if isinstance(d.severity, dict) else None | |
| color = severity_color(sev_level) | |
| # Mask varsa dolu | |
| if d.mask is not None and d.mask.sum() > 0: | |
| result = overlay_mask(result, d.mask, color, alpha=0.65) | |
| # Bbox + etiket | |
| if d.bbox: | |
| result = draw_bbox(result, d.bbox, color, thickness=2) | |
| x1, y1, _, _ = [int(v) for v in d.bbox] | |
| sev_tr = {"hafif": "Hafif", "orta": "Orta", "agir": "Agir"}.get(sev_level, "?") | |
| label = f"{d.type} • {sev_tr}" | |
| result = draw_label(result, label, (x1, max(y1, 20)), color) | |
| return result | |
| def render_all(image, damages, parts, output_dir, inspection_id): | |
| """Üç ayrı görsel üret ve dosya yollarını döndür.""" | |
| out = Path(output_dir) | |
| out.mkdir(parents=True, exist_ok=True) | |
| paths = {} | |
| try: | |
| annotated = render_annotated(image, damages, parts) | |
| p1 = out / f"{inspection_id}_annotated.jpg" | |
| cv2.imwrite(str(p1), annotated, [cv2.IMWRITE_JPEG_QUALITY, 90]) | |
| paths["annotated_image"] = str(p1) | |
| except Exception as e: | |
| logger.error(f"annotated render hatası: {e}") | |
| try: | |
| parts_img = render_parts_only(image, parts) | |
| p2 = out / f"{inspection_id}_parts.png" | |
| cv2.imwrite(str(p2), parts_img) | |
| paths["parts_overlay"] = str(p2) | |
| except Exception as e: | |
| logger.error(f"parts render hatası: {e}") | |
| try: | |
| damages_img = render_damages_only(image, damages) | |
| p3 = out / f"{inspection_id}_damages.png" | |
| cv2.imwrite(str(p3), damages_img) | |
| paths["damages_overlay"] = str(p3) | |
| except Exception as e: | |
| logger.error(f"damages render hatası: {e}") | |
| return paths | |
| if __name__ == "__main__": | |
| # CLI test | |
| import argparse | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument("--image", required=True) | |
| parser.add_argument("--output_dir", default="./visuals") | |
| parser.add_argument("--damage_weights", required=True) | |
| parser.add_argument("--parts_weights", required=True) | |
| args = parser.parse_args() | |
| from pipeline import DamagePipelineV2 | |
| pipe = DamagePipelineV2( | |
| damage_weights=args.damage_weights, | |
| parts_weights=args.parts_weights, | |
| ) | |
| image = cv2.imread(args.image) | |
| damages = pipe._detect_damages(image) | |
| parts = pipe._detect_parts(image) | |
| if damages and parts: | |
| pipe._assign_parts_to_damages(damages, parts) | |
| if damages: | |
| pipe._classify_severities(damages, image) | |
| paths = render_all(image, damages, parts, args.output_dir, "test") | |
| print("Üretildi:") | |
| for k, v in paths.items(): | |
| print(f" {k}: {v}") | |