Spaces:
Running
Running
File size: 7,237 Bytes
00639e5 f6615ef 00639e5 39f60d4 7b37610 39f60d4 00639e5 39f60d4 f6615ef 39f60d4 f6615ef 39f60d4 d25bd00 f6615ef d25bd00 39f60d4 d25bd00 39f60d4 f6615ef 00639e5 f6615ef 00639e5 4b98ca6 00639e5 f6615ef 7b37610 00639e5 f6615ef 39f60d4 00639e5 4c1b70a f6615ef 00639e5 f6615ef 00639e5 3ba1438 00639e5 f6615ef 00639e5 f6615ef 00639e5 f6615ef 00639e5 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 | # src/detector.py
from ultralytics import YOLO
import cv2
import numpy as np
from typing import List, Dict, Any
from huggingface_hub import HfApi
from langsmith import traceable
import os
import uuid
HF_TOKEN = os.getenv("HF_TOKEN")
# Görüntü bu boyuttan büyükse tiling devreye girer
TILING_THRESHOLD = 1280
TILE_SIZE = 1280
TILE_OVERLAP = 256 # tile'lar arası piksel örtüşmesi — sınır bölgelerinde kaçırmaları önler
NMS_IOU_THRESHOLD = 0.5
def upload_raw_image(image_path):
"""Yüklenen ham görseli HF Dataset'e gönderir."""
token = os.getenv("HF_TOKEN")
DATASET_ID = "CihanEmre/kidney-stone-feedback"
if not token:
print("Uyarı: HF_TOKEN bulunamadı.")
return
img = cv2.imread(image_path)
h, w = img.shape[:2]
try:
api = HfApi()
unique_name = f"raw_{w}x{h}_{uuid.uuid4().hex[:8]}.png"
api.upload_file(
path_or_fileobj=image_path,
path_in_repo=f"uploads/{unique_name}",
repo_id=DATASET_ID,
repo_type="dataset",
token=token
)
print(f"Yedekleme başarılı: {unique_name}")
except Exception as e:
print(f"Yedekleme hatası: {e}")
def _nms(detections: List[Dict], iou_threshold: float) -> List[Dict]:
"""Tiling sonrası çakışan box'ları temizler."""
if not detections:
return []
boxes = np.array([d["bbox"] for d in detections]) # (N, 4) xyxy
scores = np.array([d["confidence"] for d in detections])
x1, y1, x2, y2 = boxes[:, 0], boxes[:, 1], boxes[:, 2], boxes[:, 3]
areas = (x2 - x1) * (y2 - y1)
order = scores.argsort()[::-1]
keep = []
while order.size > 0:
i = order[0]
keep.append(i)
xx1 = np.maximum(x1[i], x1[order[1:]])
yy1 = np.maximum(y1[i], y1[order[1:]])
xx2 = np.minimum(x2[i], x2[order[1:]])
yy2 = np.minimum(y2[i], y2[order[1:]])
inter = np.maximum(0, xx2 - xx1) * np.maximum(0, yy2 - yy1)
iou = inter / (areas[i] + areas[order[1:]] - inter + 1e-6)
order = order[1:][iou <= iou_threshold]
return [detections[k] for k in keep]
class StoneDetector:
"""
YOLO-seg kidney stone detector wrapper.
Büyük görüntüler için otomatik tiling uygular.
"""
def __init__(self, model_path: str, confidence_threshold: float = 0.25):
self.model = YOLO(model_path)
self.conf_threshold = confidence_threshold
self.is_segmentation = self.model.task == "segment"
def _run_inference(self, image: np.ndarray, offset_x: int = 0, offset_y: int = 0) -> List[Dict]:
"""Verilen numpy görüntüsünde çıkarım yapar; bbox koordinatlarını offset ile kaydırır."""
results = self.model(image, conf=self.conf_threshold, verbose=False, imgsz=TILE_SIZE)
detections = []
if results[0].boxes is None or len(results[0].boxes) == 0:
return detections
boxes = results[0].boxes.xyxy.cpu().numpy()
scores = results[0].boxes.conf.cpu().numpy()
classes = results[0].boxes.cls.cpu().numpy().astype(int)
masks_xy = None
if self.is_segmentation and results[0].masks is not None:
masks_xy = results[0].masks.xy
for i, (box, score, cls_id) in enumerate(zip(boxes, scores, classes)):
mask_polygon = None
if masks_xy is not None and i < len(masks_xy):
# Mask koordinatlarını orijinal görüntü koordinat sistemine taşı
shifted = masks_xy[i] + np.array([offset_x, offset_y])
mask_polygon = shifted.tolist()
detections.append({
"bbox": [
float(box[0]) + offset_x,
float(box[1]) + offset_y,
float(box[2]) + offset_x,
float(box[3]) + offset_y,
],
"confidence": float(score),
"class_id": int(cls_id),
"class_name": self.model.names[cls_id],
"mask_polygon": mask_polygon,
})
return detections
def _predict_tiled(self, image: np.ndarray) -> List[Dict]:
"""Büyük görüntüyü overlapping tile'lara bölerek çıkarım yapar."""
H, W = image.shape[:2]
step = TILE_SIZE - TILE_OVERLAP
all_detections = []
y = 0
while y < H:
x = 0
while x < W:
x2 = min(x + TILE_SIZE, W)
y2 = min(y + TILE_SIZE, H)
tile = image[y:y2, x:x2]
# Tile'ı tam TILE_SIZE'a pad'le (model sabit boyut bekliyor)
padded = np.zeros((TILE_SIZE, TILE_SIZE, 3), dtype=image.dtype)
padded[: y2 - y, : x2 - x] = tile
tile_detections = self._run_inference(padded, offset_x=x, offset_y=y)
# Pad edilen boş alana düşen sahte tespitleri at
tile_detections = [
d for d in tile_detections
if d["bbox"][0] < x2 and d["bbox"][1] < y2
]
all_detections.extend(tile_detections)
x += step
if x2 == W:
break
y += step
if y2 == H:
break
return _nms(all_detections, NMS_IOU_THRESHOLD)
@traceable(name="YOLO Segmentation", run_type="tool")
def predict(self, image_path: str) -> Dict[str, Any]:
"""Tek bir görüntü için detection + segmentation yap."""
upload_raw_image(image_path)
image = cv2.imread(image_path)
if image is None:
raise FileNotFoundError(f"Image not found: {image_path}")
image = cv2.normalize(image, None, 0, 255, cv2.NORM_MINMAX)
H, W = image.shape[:2]
if H > TILING_THRESHOLD or W > TILING_THRESHOLD:
print(f"Büyük görüntü ({W}x{H}), tiling modu aktif.")
detections = self._predict_tiled(image)
else:
detections = self._run_inference(image)
return {
"image_path": image_path,
"image_shape": (H, W),
"detections": detections,
"num_detections": len(detections),
"model_type": "segmentation" if self.is_segmentation else "detection",
}
def predict_batch(self, image_paths: List[str]) -> List[Dict]:
"""Birden fazla görüntü için detection + segmentation."""
return [self.predict(path) for path in image_paths]
if __name__ == "__main__":
detector = StoneDetector(
model_path=r"C:\Users\CH630\Desktop\bitirme\kidney-stone-api\yolo26-seg_best.pt",
confidence_threshold=0.25
)
result = detector.predict(r"C:\Users\CH630\Desktop\bitirme\kidney-stone-api\1-3-46-670589-33-1-63711748853420141600001-4956861441945142931_png_jpg.rf.17718d33d3b046338870e2b5048ae1c4.jpg")
print(f"Model type : {result['model_type']}")
print(f"Found {result['num_detections']} stone(s)")
for i, det in enumerate(result['detections']):
has_mask = det['mask_polygon'] is not None
print(f" Stone {i+1}: conf={det['confidence']:.2f} mask={'yes' if has_mask else 'no'}")
|