Spaces:
Sleeping
Sleeping
Update app.py
Browse files
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 = "./
|
| 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 |
-
|
| 111 |
-
print(f"
|
| 112 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
| 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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
| 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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 196 |
-
|
| 197 |
-
return
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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"
|
| 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 |
-
#
|
| 278 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 279 |
try:
|
| 280 |
-
logger.info("
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
if
|
| 285 |
-
logger.
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
except Exception as
|
| 296 |
-
logger.error(f"Error
|
| 297 |
-
|
| 298 |
-
|
| 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
|
|
|
|
| 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)
|