ishanprogs commited on
Commit
fe522f1
·
verified ·
1 Parent(s): 7d8bb7f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +212 -69
app.py CHANGED
@@ -33,7 +33,7 @@ CAR_PART_CLASSES = [
33
  NUM_CAR_PART_CLASSES = len(CAR_PART_CLASSES)
34
 
35
  CLIP_TEXT_FEATURES_PATH = "./clip_text_features.pt"
36
- DAMAGE_MODEL_WEIGHTS_PATH = "./best.pt"
37
  PART_MODEL_WEIGHTS_PATH = "./partdetection_yolobest.pt"
38
  DEFAULT_DAMAGE_PRED_THRESHOLD = 0.4
39
  DEFAULT_PART_PRED_THRESHOLD = 0.3
@@ -107,9 +107,73 @@ except Exception as e:
107
  part_model = None
108
 
109
  print("--- Model loading process finished. ---")
110
- print(f"CLIP STATUS: {clip_load_error_msg}" if clip_load_error_msg else "CLIP STATUS: Loaded OK.")
111
- print(f"DAMAGE MODEL STATUS: {damage_load_error_msg}" if damage_load_error_msg else "DAMAGE MODEL STATUS: Loaded OK.")
112
- print(f"PART MODEL STATUS: {part_load_error_msg}" if part_load_error_msg else "PART MODEL STATUS: Loaded OK.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
 
114
  # --- Prediction Functions ---
115
  def classify_image_clip(image_pil):
@@ -147,8 +211,17 @@ def process_car_image(image_np_bgr, damage_threshold, part_threshold):
147
  img_h, img_w = image_np_bgr.shape[:2]
148
  logger.info("Starting combined YOLO processing...")
149
  im_tensor_gpu_for_annotator = None
 
150
 
151
  try:
 
 
 
 
 
 
 
 
152
  # --- Prepare Image Tensor for Annotator ---
153
  logger.info("Preparing image tensor for annotator...")
154
  try:
@@ -169,11 +242,27 @@ def process_car_image(image_np_bgr, damage_threshold, part_threshold):
169
  damage_results = damage_model.predict(image_np_bgr, verbose=False, device=DEVICE, conf=damage_threshold)
170
  damage_result = damage_results[0]
171
  logger.info(f"Found {len(damage_result.boxes)} potential damages.")
172
- damage_masks_raw = damage_result.masks.data if damage_result.masks is not None else torch.empty((0,0,0), device=DEVICE)
 
173
  if damage_result.masks is None:
174
  logger.warning("No damage masks in result! Check if damage model is segmentation type.")
 
175
  else:
176
- logger.info(f"Damage masks available: shape={damage_masks_raw.shape if damage_masks_raw.numel() > 0 else 'Empty'}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177
  damage_classes_ids_cpu = damage_result.boxes.cls.cpu().numpy().astype(int) if damage_result.boxes is not None else np.array([])
178
  damage_boxes_xyxy_cpu = damage_result.boxes.xyxy.cpu() if damage_result.boxes is not None else torch.empty((0,4))
179
 
@@ -182,25 +271,53 @@ def process_car_image(image_np_bgr, damage_threshold, part_threshold):
182
  part_results = part_model.predict(image_np_bgr, verbose=False, device=DEVICE, conf=part_threshold)
183
  part_result = part_results[0]
184
  logger.info(f"Found {len(part_result.boxes)} potential parts.")
185
- part_masks_raw = part_result.masks.data if part_result.masks is not None else torch.empty((0,0,0), device=DEVICE)
 
186
  if part_result.masks is None:
187
  logger.warning("No part masks in result! Check if part model is segmentation type.")
 
188
  else:
189
- logger.info(f"Part masks available: shape={part_masks_raw.shape if part_masks_raw.numel() > 0 else 'Empty'}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
190
  part_classes_ids_cpu = part_result.boxes.cls.cpu().numpy().astype(int) if part_result.boxes is not None else np.array([])
191
  part_boxes_xyxy_cpu = part_result.boxes.xyxy.cpu() if part_result.boxes is not None else torch.empty((0,4))
192
 
193
  # --- 3. Resize Masks ---
194
  def resize_masks(masks_tensor, target_h, target_w):
195
- masks_np_bool = masks_tensor.cpu().numpy().astype(bool)
196
- if masks_np_bool.shape[0] == 0 or (masks_np_bool.shape[1] == target_h and masks_np_bool.shape[2] == target_w):
197
- return masks_np_bool
198
- resized_masks_list = []
199
- for i in range(masks_np_bool.shape[0]):
200
- mask = masks_np_bool[i]
201
- mask_resized = cv2.resize(mask.astype(np.uint8), (target_w, target_h), interpolation=cv2.INTER_NEAREST)
202
- resized_masks_list.append(mask_resized.astype(bool))
203
- return np.array(resized_masks_list)
 
 
 
 
 
 
 
 
 
 
 
 
204
 
205
  damage_masks_np = resize_masks(damage_masks_raw, img_h, img_w)
206
  part_masks_np = resize_masks(part_masks_raw, img_h, img_w)
@@ -246,64 +363,90 @@ def process_car_image(image_np_bgr, damage_threshold, part_threshold):
246
  final_assignments.append(f"No damages detected above threshold {damage_threshold}.")
247
  else:
248
  final_assignments.append(f"No damages or parts detected above thresholds.")
249
- logger.info(f" Assignment results: {final_assignments}")
250
-
251
- # --- 5. Visualization using YOLO Annotator ---
252
- logger.info("Visualizing results...")
253
- annotator = Annotator(annotated_image_bgr, line_width=2, example=CAR_PART_CLASSES)
254
-
255
- # Draw PART masks
256
- if part_masks_raw.numel() > 0 and im_tensor_gpu_for_annotator is not None:
257
- try:
258
- logger.info("Attempting to draw part masks...")
259
- colors_part = [(0, random.randint(100, 200), 0) for _ in part_classes_ids_cpu]
260
- mask_data_part = part_masks_raw
261
- if mask_data_part.device != im_tensor_gpu_for_annotator.device:
262
- mask_data_part = mask_data_part.to(im_tensor_gpu_for_annotator.device)
263
- annotator.masks(mask_data_part, colors=colors_part, im_gpu=im_tensor_gpu_for_annotator, alpha=0.3)
264
- logger.info("Successfully drew part masks.")
265
- for box, cls_id in zip(part_boxes_xyxy_cpu, part_classes_ids_cpu):
266
- try:
267
- label = f"{CAR_PART_CLASSES[cls_id]}"
268
- annotator.box_label(box, label=label, color=(0, 200, 0))
269
- except IndexError:
270
- logger.warning(f"Invalid part ID {cls_id} during drawing")
271
- except Exception as e_part_vis:
272
- logger.error(f"Error drawing part masks/boxes: {e_part_vis}", exc_info=True)
273
- traceback.print_exc()
274
- elif part_masks_raw.numel() > 0:
275
- logger.warning("Part masks exist but image tensor for annotator is None. Skipping part mask drawing.")
276
 
277
- # Draw DAMAGE masks
278
- if damage_masks_raw.numel() > 0 and im_tensor_gpu_for_annotator is not None:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
279
  try:
280
- logger.info("Attempting to draw damage masks...")
281
- colors_dmg = [(random.randint(100, 200), 0, 0) for _ in damage_classes_ids_cpu]
282
- mask_data_dmg = damage_masks_raw
283
- logger.info(f"Damage mask data: shape={mask_data_dmg.shape}, dtype={mask_data_dmg.dtype}, device={mask_data_dmg.device}")
284
- if mask_data_dmg.device != im_tensor_gpu_for_annotator.device:
285
- logger.warning(f"Moving damage masks to match image tensor device ({im_tensor_gpu_for_annotator.device})")
286
- mask_data_dmg = mask_data_dmg.to(im_tensor_gpu_for_annotator.device)
287
- annotator.masks(mask_data_dmg, colors=colors_dmg, im_gpu=im_tensor_gpu_for_annotator, alpha=0.4)
288
- logger.info("Successfully drew damage masks.")
289
- for box, cls_id in zip(damage_boxes_xyxy_cpu, damage_classes_ids_cpu):
290
- try:
291
- label = f"{DAMAGE_CLASSES[cls_id]}"
292
- annotator.box_label(box, label=label, color=(200, 0, 0))
293
- except IndexError:
294
- logger.warning(f"Invalid damage ID {cls_id} during drawing")
295
- except Exception as e_dmg_vis:
296
- logger.error(f"Error drawing damage masks/boxes: {e_dmg_vis}", exc_info=True)
297
- traceback.print_exc()
298
- elif damage_masks_raw.numel() > 0:
299
- logger.warning("Damage masks exist but image tensor for annotator is None. Skipping damage mask drawing.")
300
-
301
- annotated_image_bgr = annotator.result()
302
 
303
  except Exception as e:
304
  logger.error(f"Error during combined processing: {e}", exc_info=True)
305
  traceback.print_exc()
306
- final_assignments.append("Error during segmentation/processing.")
 
307
 
308
  assignment_text = "\n".join(final_assignments) if final_assignments else "No damage assignments generated."
309
  final_output_image_rgb = cv2.cvtColor(annotated_image_bgr, cv2.COLOR_BGR2RGB)
 
33
  NUM_CAR_PART_CLASSES = len(CAR_PART_CLASSES)
34
 
35
  CLIP_TEXT_FEATURES_PATH = "./clip_text_features.pt"
36
+ DAMAGE_MODEL_WEIGHTS_PATH = "./model_best.pt"
37
  PART_MODEL_WEIGHTS_PATH = "./partdetection_yolobest.pt"
38
  DEFAULT_DAMAGE_PRED_THRESHOLD = 0.4
39
  DEFAULT_PART_PRED_THRESHOLD = 0.3
 
107
  part_model = None
108
 
109
  print("--- Model loading process finished. ---")
110
+ if clip_load_error_msg:
111
+ print(f"CLIP STATUS: {clip_load_error_msg}")
112
+ else:
113
+ print("CLIP STATUS: Loaded OK.")
114
+
115
+ if damage_load_error_msg:
116
+ print(f"DAMAGE MODEL STATUS: {damage_load_error_msg}")
117
+ else:
118
+ print("DAMAGE MODEL STATUS: Loaded OK.")
119
+
120
+ if part_load_error_msg:
121
+ print(f"PART MODEL STATUS: {part_load_error_msg}")
122
+ else:
123
+ print("PART MODEL STATUS: Loaded OK.")
124
+
125
+ # --- Add DirectVisualizer class for fallback visualization ---
126
+ class DirectVisualizer:
127
+ """Fallback visualizer for when Ultralytics Annotator doesn't work"""
128
+
129
+ def __init__(self, image):
130
+ self.image = image.copy()
131
+
132
+ def draw_masks(self, masks_np, class_ids, class_names, color_type="damage"):
133
+ """Draw masks directly using OpenCV"""
134
+ if masks_np.shape[0] == 0:
135
+ return
136
+
137
+ for i, (mask, class_id) in enumerate(zip(masks_np, class_ids)):
138
+ if not np.any(mask): # Skip empty masks
139
+ continue
140
+
141
+ # Set color based on type
142
+ if color_type == "damage":
143
+ color = (0, 0, 255) # BGR Red
144
+ alpha = 0.4
145
+ else:
146
+ color = (0, 255, 0) # BGR Green
147
+ alpha = 0.3
148
+
149
+ # Create color overlay
150
+ overlay = self.image.copy()
151
+ overlay[mask] = color
152
+
153
+ # Apply with transparency
154
+ cv2.addWeighted(overlay, alpha, self.image, 1-alpha, 0, self.image)
155
+
156
+ # Draw contour
157
+ mask_8bit = mask.astype(np.uint8) * 255
158
+ contours, _ = cv2.findContours(mask_8bit, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
159
+ cv2.drawContours(self.image, contours, -1, color, 2)
160
+
161
+ # Add text label
162
+ try:
163
+ if 0 <= class_id < len(class_names):
164
+ label = class_names[class_id]
165
+ M = cv2.moments(mask_8bit)
166
+ if M["m00"] > 0:
167
+ cx = int(M["m10"] / M["m00"])
168
+ cy = int(M["m01"] / M["m00"])
169
+ cv2.putText(self.image, label, (cx, cy),
170
+ cv2.FONT_HERSHEY_SIMPLEX, 0.7, color, 2)
171
+ except Exception as e:
172
+ logger.warning(f"Error adding label: {e}")
173
+
174
+ def result(self):
175
+ """Return the final image"""
176
+ return self.image
177
 
178
  # --- Prediction Functions ---
179
  def classify_image_clip(image_pil):
 
211
  img_h, img_w = image_np_bgr.shape[:2]
212
  logger.info("Starting combined YOLO processing...")
213
  im_tensor_gpu_for_annotator = None
214
+ ultralytic_viz_success = False
215
 
216
  try:
217
+ # --- Check Ultralytics Version ---
218
+ try:
219
+ import pkg_resources
220
+ ultraytics_version = pkg_resources.get_distribution("ultralytics").version
221
+ logger.info(f"Ultralytics version: {ultraytics_version}")
222
+ except:
223
+ logger.warning("Could not determine Ultralytics version")
224
+
225
  # --- Prepare Image Tensor for Annotator ---
226
  logger.info("Preparing image tensor for annotator...")
227
  try:
 
242
  damage_results = damage_model.predict(image_np_bgr, verbose=False, device=DEVICE, conf=damage_threshold)
243
  damage_result = damage_results[0]
244
  logger.info(f"Found {len(damage_result.boxes)} potential damages.")
245
+
246
+ # Check mask availability and format
247
  if damage_result.masks is None:
248
  logger.warning("No damage masks in result! Check if damage model is segmentation type.")
249
+ damage_masks_raw = torch.empty((0,0,0), device=DEVICE)
250
  else:
251
+ logger.info(f"Damage masks type: {type(damage_result.masks)}")
252
+ try:
253
+ if hasattr(damage_result.masks, 'data'):
254
+ damage_masks_raw = damage_result.masks.data
255
+ logger.info(f"Damage masks via .data: shape={damage_masks_raw.shape}, dtype={damage_masks_raw.dtype}")
256
+ elif isinstance(damage_result.masks, torch.Tensor):
257
+ damage_masks_raw = damage_result.masks
258
+ logger.info(f"Damage masks as tensor: shape={damage_masks_raw.shape}, dtype={damage_masks_raw.dtype}")
259
+ else:
260
+ logger.warning(f"Unknown mask type: {type(damage_result.masks)}")
261
+ damage_masks_raw = torch.empty((0,0,0), device=DEVICE)
262
+ except Exception as e_mask:
263
+ logger.error(f"Error accessing damage masks: {e_mask}", exc_info=True)
264
+ damage_masks_raw = torch.empty((0,0,0), device=DEVICE)
265
+
266
  damage_classes_ids_cpu = damage_result.boxes.cls.cpu().numpy().astype(int) if damage_result.boxes is not None else np.array([])
267
  damage_boxes_xyxy_cpu = damage_result.boxes.xyxy.cpu() if damage_result.boxes is not None else torch.empty((0,4))
268
 
 
271
  part_results = part_model.predict(image_np_bgr, verbose=False, device=DEVICE, conf=part_threshold)
272
  part_result = part_results[0]
273
  logger.info(f"Found {len(part_result.boxes)} potential parts.")
274
+
275
+ # Check mask availability and format
276
  if part_result.masks is None:
277
  logger.warning("No part masks in result! Check if part model is segmentation type.")
278
+ part_masks_raw = torch.empty((0,0,0), device=DEVICE)
279
  else:
280
+ logger.info(f"Part masks type: {type(part_result.masks)}")
281
+ try:
282
+ if hasattr(part_result.masks, 'data'):
283
+ part_masks_raw = part_result.masks.data
284
+ logger.info(f"Part masks via .data: shape={part_masks_raw.shape}, dtype={part_masks_raw.dtype}")
285
+ elif isinstance(part_result.masks, torch.Tensor):
286
+ part_masks_raw = part_result.masks
287
+ logger.info(f"Part masks as tensor: shape={part_masks_raw.shape}, dtype={part_masks_raw.dtype}")
288
+ else:
289
+ logger.warning(f"Unknown mask type: {type(part_result.masks)}")
290
+ part_masks_raw = torch.empty((0,0,0), device=DEVICE)
291
+ except Exception as e_mask:
292
+ logger.error(f"Error accessing part masks: {e_mask}", exc_info=True)
293
+ part_masks_raw = torch.empty((0,0,0), device=DEVICE)
294
+
295
  part_classes_ids_cpu = part_result.boxes.cls.cpu().numpy().astype(int) if part_result.boxes is not None else np.array([])
296
  part_boxes_xyxy_cpu = part_result.boxes.xyxy.cpu() if part_result.boxes is not None else torch.empty((0,4))
297
 
298
  # --- 3. Resize Masks ---
299
  def resize_masks(masks_tensor, target_h, target_w):
300
+ if masks_tensor is None or masks_tensor.numel() == 0 or masks_tensor.shape[0] == 0:
301
+ logger.warning("Empty masks tensor passed to resize_masks")
302
+ return np.zeros((0, target_h, target_w), dtype=bool)
303
+
304
+ try:
305
+ masks_np_bool = masks_tensor.cpu().numpy().astype(bool)
306
+ logger.info(f"Resizing masks from {masks_np_bool.shape} to ({target_h}, {target_w})")
307
+
308
+ if masks_np_bool.shape[1] == target_h and masks_np_bool.shape[2] == target_w:
309
+ return masks_np_bool
310
+
311
+ resized_masks_list = []
312
+ for i in range(masks_np_bool.shape[0]):
313
+ mask = masks_np_bool[i]
314
+ mask_resized = cv2.resize(mask.astype(np.uint8), (target_w, target_h), interpolation=cv2.INTER_NEAREST)
315
+ resized_masks_list.append(mask_resized.astype(bool))
316
+
317
+ return np.array(resized_masks_list)
318
+ except Exception as e_resize:
319
+ logger.error(f"Error resizing masks: {e_resize}", exc_info=True)
320
+ return np.zeros((0, target_h, target_w), dtype=bool)
321
 
322
  damage_masks_np = resize_masks(damage_masks_raw, img_h, img_w)
323
  part_masks_np = resize_masks(part_masks_raw, img_h, img_w)
 
363
  final_assignments.append(f"No damages detected above threshold {damage_threshold}.")
364
  else:
365
  final_assignments.append(f"No damages or parts detected above thresholds.")
366
+ logger.info(f"Assignment results: {final_assignments}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
367
 
368
+ # --- 5. Try BOTH visualization approaches ---
369
+ # First attempt: Ultralytics annotator
370
+ try:
371
+ logger.info("Trying Ultralytics Annotator for visualization...")
372
+ annotator = Annotator(annotated_image_bgr.copy(), line_width=2, example=CAR_PART_CLASSES)
373
+
374
+ # Draw part masks with Ultralytics
375
+ if part_masks_raw.numel() > 0 and im_tensor_gpu_for_annotator is not None:
376
+ try:
377
+ colors_part = [(0, random.randint(100, 200), 0) for _ in part_classes_ids_cpu]
378
+ mask_data_part = part_masks_raw
379
+ if mask_data_part.device != im_tensor_gpu_for_annotator.device:
380
+ mask_data_part = mask_data_part.to(im_tensor_gpu_for_annotator.device)
381
+ annotator.masks(mask_data_part, colors=colors_part, im_gpu=im_tensor_gpu_for_annotator, alpha=0.3)
382
+ for box, cls_id in zip(part_boxes_xyxy_cpu, part_classes_ids_cpu):
383
+ try:
384
+ label = f"{CAR_PART_CLASSES[cls_id]}"
385
+ annotator.box_label(box, label=label, color=(0, 200, 0))
386
+ except IndexError:
387
+ logger.warning(f"Invalid part ID {cls_id}")
388
+ logger.info("Successfully drew part masks with annotator")
389
+ ultralytic_viz_success = True
390
+ except Exception as e_part:
391
+ logger.error(f"Error drawing part masks with annotator: {e_part}", exc_info=True)
392
+
393
+ # Draw damage masks with Ultralytics
394
+ if damage_masks_raw.numel() > 0 and im_tensor_gpu_for_annotator is not None:
395
+ try:
396
+ colors_dmg = [(random.randint(100, 200), 0, 0) for _ in damage_classes_ids_cpu]
397
+ mask_data_dmg = damage_masks_raw
398
+ if mask_data_dmg.device != im_tensor_gpu_for_annotator.device:
399
+ mask_data_dmg = mask_data_dmg.to(im_tensor_gpu_for_annotator.device)
400
+ annotator.masks(mask_data_dmg, colors=colors_dmg, im_gpu=im_tensor_gpu_for_annotator, alpha=0.4)
401
+ for box, cls_id in zip(damage_boxes_xyxy_cpu, damage_classes_ids_cpu):
402
+ try:
403
+ label = f"{DAMAGE_CLASSES[cls_id]}"
404
+ annotator.box_label(box, label=label, color=(200, 0, 0))
405
+ except IndexError:
406
+ logger.warning(f"Invalid damage ID {cls_id}")
407
+ logger.info("Successfully drew damage masks with annotator")
408
+ ultralytic_viz_success = True
409
+ except Exception as e_dmg:
410
+ logger.error(f"Error drawing damage masks with annotator: {e_dmg}", exc_info=True)
411
+
412
+ # Get result from annotator if successful
413
+ if ultralytic_viz_success:
414
+ annotated_image_bgr = annotator.result()
415
+ logger.info("Using Ultralytics annotator visualization")
416
+ else:
417
+ logger.warning("Ultralytics annotator visualization failed, will try direct approach")
418
+ except Exception as e_anno:
419
+ logger.error(f"Error with Ultralytics annotator: {e_anno}", exc_info=True)
420
+ ultralytic_viz_success = False
421
+
422
+ # Second attempt: Direct visualization with OpenCV if Ultralytics failed
423
+ if not ultralytic_viz_success:
424
  try:
425
+ logger.info("Using DirectVisualizer as fallback...")
426
+ direct_viz = DirectVisualizer(image_np_bgr.copy())
427
+
428
+ # Draw part masks first (background layer)
429
+ if part_masks_np.shape[0] > 0:
430
+ logger.info(f"Drawing {part_masks_np.shape[0]} part masks directly")
431
+ direct_viz.draw_masks(part_masks_np, part_classes_ids_cpu, CAR_PART_CLASSES, "part")
432
+
433
+ # Draw damage masks on top
434
+ if damage_masks_np.shape[0] > 0:
435
+ logger.info(f"Drawing {damage_masks_np.shape[0]} damage masks directly")
436
+ direct_viz.draw_masks(damage_masks_np, damage_classes_ids_cpu, DAMAGE_CLASSES, "damage")
437
+
438
+ annotated_image_bgr = direct_viz.result()
439
+ logger.info("Direct visualization successful")
440
+ except Exception as e_direct:
441
+ logger.error(f"Error with direct visualization: {e_direct}", exc_info=True)
442
+ # If direct visualization also fails, use the original image
443
+ annotated_image_bgr = image_np_bgr.copy()
 
 
 
444
 
445
  except Exception as e:
446
  logger.error(f"Error during combined processing: {e}", exc_info=True)
447
  traceback.print_exc()
448
+ final_assignments.append(f"Error during processing: {str(e)}")
449
+ annotated_image_bgr = image_np_bgr.copy()
450
 
451
  assignment_text = "\n".join(final_assignments) if final_assignments else "No damage assignments generated."
452
  final_output_image_rgb = cv2.cvtColor(annotated_image_bgr, cv2.COLOR_BGR2RGB)