minh9972t12 commited on
Commit
51aa863
·
1 Parent(s): 2bf63f0

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +269 -100
main.py CHANGED
@@ -1,17 +1,18 @@
1
  import io
2
- import shutil
3
 
4
  import uvicorn
5
  import numpy as np
6
  import uuid
7
  from datetime import datetime
8
  from pathlib import Path
9
- from fastapi import FastAPI, UploadFile, File, HTTPException
10
  from fastapi.responses import JSONResponse, FileResponse
11
  from fastapi.middleware.cors import CORSMiddleware
12
  from fastapi.staticfiles import StaticFiles
13
  from PIL import Image
14
  import cv2
 
15
  from src.detection import YOLOv11Detector
16
  from src.comparison import DamageComparator
17
  from src.visualization import DamageVisualizer
@@ -32,10 +33,34 @@ app.add_middleware(
32
  )
33
 
34
  # Initialize components
35
- detector = YOLOv11Detector()
36
  comparator = DamageComparator()
37
  visualizer = DamageVisualizer()
38
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  # Create necessary directories
40
  UPLOADS_DIR = Path("uploads")
41
  RESULTS_DIR = Path("results")
@@ -66,88 +91,200 @@ async def health_check():
66
  """Health check endpoint"""
67
  return {"status": "healthy", "model": "YOLOv11"}
68
 
69
-
70
  @app.post("/detect")
71
- async def detect_single_image(file: UploadFile = File(...)):
 
 
 
 
72
  """
73
- Detect damages in a single image
74
 
75
  Args:
76
- file: Image file
77
-
78
- Returns:
79
- Detection results with bounding boxes and path to visualized image
80
  """
81
  try:
82
- # Read and process image
83
- contents = await file.read()
84
- image = Image.open(io.BytesIO(contents)).convert("RGB")
85
- image_np = np.array(image)
86
- image_bgr = cv2.cvtColor(image_np, cv2.COLOR_RGB2BGR)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
 
88
- # Perform detection
89
- detections = detector.detect(image_bgr)
 
90
 
91
- # Create visualization
92
- visualized = visualizer.draw_detections(image_bgr, detections, 'new_damage')
93
 
94
- # Generate unique filename
95
- filename = f"detection_{datetime.now().strftime('%Y%m%d_%H%M%S')}_{uuid.uuid4().hex[:8]}.jpg"
 
 
 
 
96
 
97
- output_path = UPLOADS_DIR / filename
 
 
98
 
99
- # Save visualization image
100
- cv2.imwrite(str(output_path), visualized)
101
 
102
- # Generate URL for accessing the image
103
- image_url = f"http://localhost:8000/uploads/{filename}"
 
 
 
104
 
105
- return JSONResponse({
106
- "status": "success",
107
- "detections": detections,
108
- "statistics": {
109
- "total_damages": len(detections['boxes']),
110
- "damage_types": list(set(detections['classes']))
111
- },
112
- "visualized_image_path": f"uploads/{filename}",
113
- "visualized_image_url": image_url,
114
- "filename": filename
115
- })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
 
117
  except Exception as e:
118
  raise HTTPException(status_code=500, detail=f"Detection failed: {str(e)}")
119
 
120
 
 
 
121
  @app.post("/compare")
122
  async def compare_vehicle_damages(
123
  # Before delivery images (6 positions)
124
- before_1: UploadFile = File(..., description="Before - Position 1"),
125
- before_2: UploadFile = File(..., description="Before - Position 2"),
126
- before_3: UploadFile = File(..., description="Before - Position 3"),
127
- before_4: UploadFile = File(..., description="Before - Position 4"),
128
- before_5: UploadFile = File(..., description="Before - Position 5"),
129
- before_6: UploadFile = File(..., description="Before - Position 6"),
130
  # After delivery images (6 positions)
131
- after_1: UploadFile = File(..., description="After - Position 1"),
132
- after_2: UploadFile = File(..., description="After - Position 2"),
133
- after_3: UploadFile = File(..., description="After - Position 3"),
134
- after_4: UploadFile = File(..., description="After - Position 4"),
135
- after_5: UploadFile = File(..., description="After - Position 5"),
136
- after_6: UploadFile = File(..., description="After - Position 6"),
 
 
137
  ):
138
  """
139
- Compare vehicle damages before and after delivery
140
-
141
- Analyzes 6 pairs of images (before/after) from different positions
142
- and determines the damage status according to 3 cases:
143
- - Case 1: Existing damages (from before) -> Delivery completed
144
- - Case 2: New damages detected -> Error during delivery
145
- - Case 3: No damages -> Successful delivery
146
-
147
- Returns:
148
- Detailed comparison results for each position and overall status
149
  """
150
  try:
 
 
 
 
 
 
 
151
  before_images = [before_1, before_2, before_3, before_4, before_5, before_6]
152
  after_images = [after_1, after_2, after_3, after_4, after_5, after_6]
153
 
@@ -155,18 +292,22 @@ async def compare_vehicle_damages(
155
  all_visualizations = []
156
  image_pairs = []
157
 
 
 
 
 
 
 
158
  # Overall statistics
159
  total_new_damages = 0
160
  total_existing_damages = 0
161
  total_matched_damages = 0
162
 
163
- # Generate unique session ID for this comparison
164
  session_id = str(uuid.uuid4())[:8]
165
  timestamp_str = datetime.now().strftime("%Y%m%d_%H%M%S")
166
 
167
  # Process each position pair
168
  for i in range(6):
169
- # Read images
170
  before_contents = await before_images[i].read()
171
  after_contents = await after_images[i].read()
172
 
@@ -179,22 +320,31 @@ async def compare_vehicle_damages(
179
  before_bgr = cv2.cvtColor(before_np, cv2.COLOR_RGB2BGR)
180
  after_bgr = cv2.cvtColor(after_np, cv2.COLOR_RGB2BGR)
181
 
182
- # Store image pairs for grid visualization
 
 
 
183
  image_pairs.append((before_bgr, after_bgr))
184
 
185
  # Detect damages
186
- before_detections = detector.detect(before_bgr)
187
- after_detections = detector.detect(after_bgr)
188
 
189
- # Compare damages
190
- comparison = comparator.analyze_damage_status(before_detections, after_detections)
191
 
192
- # Update overall statistics
 
 
 
 
 
 
193
  total_new_damages += len(comparison['new_damages'])
194
  total_existing_damages += len(comparison['repaired_damages'])
195
  total_matched_damages += len(comparison['matched_damages'])
196
 
197
- # Create visualization for this position
198
  vis_img = visualizer.create_comparison_visualization(
199
  before_bgr, after_bgr,
200
  before_detections, after_detections,
@@ -202,20 +352,13 @@ async def compare_vehicle_damages(
202
  )
203
 
204
  vis_filename = f"comparison_{timestamp_str}_{session_id}_pos{i + 1}.jpg"
205
- vis_path = UPLOADS_DIR / vis_filename # Use consistent relative path
206
-
207
- # Save the image
208
- success = cv2.imwrite(str(vis_path), vis_img)
209
- if not success:
210
- print(f"⚠️ Warning: Failed to save visualization for position {i + 1}")
211
- else:
212
- print(f" Saved: {vis_path}")
213
 
214
- # Generate URL for accessing the image
215
  vis_url = f"http://localhost:8000/uploads/{vis_filename}"
216
  all_visualizations.append(vis_url)
217
 
218
- # Store position result
219
  position_results.append({
220
  f"position_{i + 1}": {
221
  "case": comparison['case'],
@@ -224,62 +367,89 @@ async def compare_vehicle_damages(
224
  "new_damages": comparison['new_damages'],
225
  "matched_damages": comparison['matched_damages'],
226
  "repaired_damages": comparison['repaired_damages'],
 
227
  "visualization_path": f"uploads/{vis_filename}",
228
  "visualization_url": vis_url,
229
  "filename": vis_filename
230
  }
231
  })
232
 
233
- # Determine overall case
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
234
  overall_case = "CASE_3_SUCCESS"
235
  overall_message = "Successful delivery - No damage detected"
236
 
237
- if total_new_damages > 0:
238
  overall_case = "CASE_2_NEW_DAMAGE"
239
- overall_message = f"Error during vehicle delivery - Detection {total_new_damages} new damage"
240
- elif total_matched_damages > 0 and total_new_damages == 0:
241
  overall_case = "CASE_1_EXISTING"
242
- overall_message = "Error from the beginning, not during the delivery process -> Delivery completed"
243
 
244
- # Create summary grid visualization
245
  grid_results = [res[f"position_{i + 1}"] for i, res in enumerate(position_results)]
246
  grid_img = visualizer.create_summary_grid(grid_results, image_pairs)
247
 
248
  grid_filename = f"summary_grid_{timestamp_str}_{session_id}.jpg"
249
- grid_path = UPLOADS_DIR / grid_filename # Already correct in original
250
-
251
- # Save the grid image
252
- grid_success = cv2.imwrite(str(grid_path), grid_img)
253
- if not grid_success:
254
- print(f" Warning: Failed to save grid summary")
255
- else:
256
- print(f" Saved grid: {grid_path}")
257
 
258
  grid_url = f"http://localhost:8000/uploads/{grid_filename}"
259
 
260
- # Generate timestamp for tracking
261
  timestamp = datetime.now().isoformat()
262
 
263
  return JSONResponse({
264
  "status": "success",
265
  "session_id": session_id,
266
  "timestamp": timestamp,
 
267
  "overall_result": {
268
  "case": overall_case,
269
  "message": overall_message,
270
  "statistics": {
271
- "total_new_damages": total_new_damages,
272
- "total_matched_damages": total_matched_damages,
273
- "total_repaired_damages": total_existing_damages
 
 
 
 
274
  }
275
  },
 
 
 
 
 
 
 
 
 
 
276
  "position_results": position_results,
277
  "summary_visualization_path": f"uploads/{grid_filename}",
278
  "summary_visualization_url": grid_url,
279
- "all_visualizations": all_visualizations, # Added for convenience
280
  "recommendations": {
281
- "action_required": total_new_damages > 0,
282
- "suggested_action": "Investigate delivery process" if total_new_damages > 0 else "Proceed with delivery completion"
 
283
  }
284
  })
285
 
@@ -287,7 +457,6 @@ async def compare_vehicle_damages(
287
  raise HTTPException(status_code=500, detail=f"Comparison failed: {str(e)}")
288
 
289
 
290
-
291
  if __name__ == "__main__":
292
  import os
293
  uvicorn.run(
 
1
  import io
2
+ from typing import List
3
 
4
  import uvicorn
5
  import numpy as np
6
  import uuid
7
  from datetime import datetime
8
  from pathlib import Path
9
+ from fastapi import FastAPI, UploadFile, File, HTTPException, Form
10
  from fastapi.responses import JSONResponse, FileResponse
11
  from fastapi.middleware.cors import CORSMiddleware
12
  from fastapi.staticfiles import StaticFiles
13
  from PIL import Image
14
  import cv2
15
+ import yaml
16
  from src.detection import YOLOv11Detector
17
  from src.comparison import DamageComparator
18
  from src.visualization import DamageVisualizer
 
33
  )
34
 
35
  # Initialize components
36
+ detector = None
37
  comparator = DamageComparator()
38
  visualizer = DamageVisualizer()
39
 
40
+ # Model paths mapping
41
+ MODEL_PATHS = {
42
+ 0: "models_small/best.pt",
43
+ 1: "models_medium/best.pt",
44
+ 2: "models_large/best.pt"
45
+ }
46
+
47
+ def load_detector(select_models: int = 2):
48
+ """Load detector with specified model"""
49
+ global detector
50
+ # Update config with selected model path
51
+ with open('config.yaml', 'r') as f:
52
+ config = yaml.safe_load(f)
53
+
54
+ config['model']['path'] = MODEL_PATHS.get(select_models, MODEL_PATHS[2])
55
+
56
+ with open('config.yaml', 'w') as f:
57
+ yaml.dump(config, f, default_flow_style=False)
58
+ detector = YOLOv11Detector()
59
+ # Reload detector with new config
60
+ return detector
61
+
62
+ detector = load_detector(2)
63
+
64
  # Create necessary directories
65
  UPLOADS_DIR = Path("uploads")
66
  RESULTS_DIR = Path("results")
 
91
  """Health check endpoint"""
92
  return {"status": "healthy", "model": "YOLOv11"}
93
 
 
94
  @app.post("/detect")
95
+ async def detect_single_image(
96
+ file: UploadFile = File(None),
97
+ files: List[UploadFile] = File(None),
98
+ select_models: int = Form(2)
99
+ ):
100
  """
101
+ - Multi-view detection với deduplication
102
 
103
  Args:
104
+ file: Single image (backward compatibility)
105
+ select_models: Model selection (0=small, 1=medium, 2=large)
 
 
106
  """
107
  try:
108
+ # Validate select_models
109
+ if select_models not in [0, 1, 2]:
110
+ raise HTTPException(status_code=400, detail="select_models must be 0, 1, or 2")
111
+
112
+ # Load appropriate detector
113
+ current_detector = load_detector(select_models)
114
+
115
+ # Case 1: Single image (backward compatibility)
116
+ if file is not None:
117
+ contents = await file.read()
118
+ image = Image.open(io.BytesIO(contents)).convert("RGB")
119
+ image_np = np.array(image)
120
+ image_bgr = cv2.cvtColor(image_np, cv2.COLOR_RGB2BGR)
121
+
122
+ # Perform detection
123
+ detections = current_detector.detect(image_bgr)
124
+
125
+ # Create visualization
126
+ visualized = visualizer.draw_detections(image_bgr, detections, 'new_damage')
127
+
128
+ # Save and return
129
+ filename = f"detection_{datetime.now().strftime('%Y%m%d_%H%M%S')}_{uuid.uuid4().hex[:8]}.jpg"
130
+ output_path = UPLOADS_DIR / filename
131
+ cv2.imwrite(str(output_path), visualized)
132
+
133
+ return JSONResponse({
134
+ "status": "success",
135
+ "detections": detections,
136
+ "statistics": {
137
+ "total_damages": len(detections['boxes']),
138
+ "damage_types": list(set(detections['classes']))
139
+ },
140
+ "visualized_image_path": f"uploads/{filename}",
141
+ "visualized_image_url": f"http://localhost:8000/uploads/{filename}",
142
+ "filename": filename
143
+ })
144
 
145
+ # Case 2: Multiple images - MULTI-VIEW DETECTION với ReID
146
+ elif files is not None and len(files) > 0:
147
+ print(f"\nMulti-view detection with {len(files)} images")
148
 
149
+ images_list = []
150
+ detections_list = []
151
 
152
+ # Process all images
153
+ for idx, img_file in enumerate(files):
154
+ contents = await img_file.read()
155
+ image = Image.open(io.BytesIO(contents)).convert("RGB")
156
+ image_np = np.array(image)
157
+ image_bgr = cv2.cvtColor(image_np, cv2.COLOR_RGB2BGR)
158
 
159
+ images_list.append(image_bgr)
160
+ detections = current_detector.detect(image_bgr)
161
+ detections_list.append(detections)
162
 
163
+ print(f" View {idx + 1}: {len(detections['boxes'])} detections")
 
164
 
165
+ # DEDUPLICATION using ReID
166
+ print("\nPerforming cross-view deduplication...")
167
+ unique_damages = comparator.deduplicate_detections_across_views(
168
+ detections_list, images_list
169
+ )
170
 
171
+ # Create combined visualization
172
+ combined_height = max(img.shape[0] for img in images_list)
173
+ combined_width = sum(img.shape[1] for img in images_list)
174
+ combined_img = np.ones((combined_height, combined_width, 3), dtype=np.uint8) * 255
175
+
176
+ x_offset = 0
177
+ for img_idx, (image, detections) in enumerate(zip(images_list, detections_list)):
178
+ # Resize if needed
179
+ h, w = image.shape[:2]
180
+ if h != combined_height:
181
+ scale = combined_height / h
182
+ new_w = int(w * scale)
183
+ image = cv2.resize(image, (new_w, combined_height))
184
+ w = new_w
185
+
186
+ # Draw on combined image
187
+ combined_img[:, x_offset:x_offset + w] = image
188
+
189
+ # Draw detections with unique IDs
190
+ for det_idx, bbox in enumerate(detections['boxes']):
191
+ # Find unique damage ID for this detection
192
+ damage_id = None
193
+ for uid, damage_info in unique_damages.items():
194
+ for d in damage_info['detections']:
195
+ if d['view_idx'] == img_idx and d['bbox'] == bbox:
196
+ damage_id = uid
197
+ break
198
+
199
+ # Draw with unique ID
200
+ x1, y1, x2, y2 = bbox
201
+ x1 += x_offset
202
+ x2 += x_offset
203
+
204
+ # Color based on unique ID
205
+ if damage_id:
206
+ color_hash = int(damage_id[-6:], 16)
207
+ color = ((color_hash >> 16) & 255, (color_hash >> 8) & 255, color_hash & 255)
208
+ else:
209
+ color = (0, 0, 255)
210
+
211
+ cv2.rectangle(combined_img, (x1, y1), (x2, y2), color, 2)
212
+
213
+ # Label
214
+ label = f"{damage_id[:8] if damage_id else 'Unknown'}"
215
+ cv2.putText(combined_img, label, (x1, y1 - 5),
216
+ cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2)
217
+
218
+ x_offset += w
219
+
220
+ # Save combined visualization
221
+ filename = f"multiview_{datetime.now().strftime('%Y%m%d_%H%M%S')}_{uuid.uuid4().hex[:8]}.jpg"
222
+ output_path = UPLOADS_DIR / filename
223
+ cv2.imwrite(str(output_path), combined_img)
224
+
225
+ # Return results
226
+ total_detections = sum(len(d['boxes']) for d in detections_list)
227
+
228
+ return JSONResponse({
229
+ "status": "success",
230
+ "mode": "multi_view_with_reid",
231
+ "total_detections_all_views": total_detections,
232
+ "unique_damages_count": len(unique_damages),
233
+ "unique_damages": {
234
+ damage_id: {
235
+ "appears_in_views": info['views'],
236
+ "class": info['class'],
237
+ "avg_confidence": float(info['avg_confidence']),
238
+ "detection_count": len(info['detections'])
239
+ }
240
+ for damage_id, info in unique_damages.items()
241
+ },
242
+ "reduction_rate": f"{(1 - len(unique_damages) / total_detections) * 100:.1f}%" if total_detections > 0 else "0%",
243
+ "visualized_image_path": f"uploads/{filename}",
244
+ "visualized_image_url": f"http://localhost:8000/uploads/{filename}",
245
+ "message": f"Detected {total_detections} damages across {len(files)} views, "
246
+ f"identified {len(unique_damages)} unique damages using ReID"
247
+ })
248
+
249
+ else:
250
+ raise HTTPException(status_code=400, detail="No image provided")
251
 
252
  except Exception as e:
253
  raise HTTPException(status_code=500, detail=f"Detection failed: {str(e)}")
254
 
255
 
256
+
257
+
258
  @app.post("/compare")
259
  async def compare_vehicle_damages(
260
  # Before delivery images (6 positions)
261
+ before_1: UploadFile = File(...),
262
+ before_2: UploadFile = File(...),
263
+ before_3: UploadFile = File(...),
264
+ before_4: UploadFile = File(...),
265
+ before_5: UploadFile = File(...),
266
+ before_6: UploadFile = File(...),
267
  # After delivery images (6 positions)
268
+ after_1: UploadFile = File(...),
269
+ after_2: UploadFile = File(...),
270
+ after_3: UploadFile = File(...),
271
+ after_4: UploadFile = File(...),
272
+ after_5: UploadFile = File(...),
273
+ after_6: UploadFile = File(...),
274
+ # Model selection
275
+ select_models: int = Form(2),
276
  ):
277
  """
278
+ Enhanced comparison với ReID
 
 
 
 
 
 
 
 
 
279
  """
280
  try:
281
+ # Validate select_models
282
+ if select_models not in [0, 1, 2]:
283
+ raise HTTPException(status_code=400, detail="select_models must be 0, 1, or 2")
284
+
285
+ # Load appropriate detector
286
+ current_detector = load_detector(select_models)
287
+
288
  before_images = [before_1, before_2, before_3, before_4, before_5, before_6]
289
  after_images = [after_1, after_2, after_3, after_4, after_5, after_6]
290
 
 
292
  all_visualizations = []
293
  image_pairs = []
294
 
295
+ # Collect all before/after images and detections
296
+ all_before_images = []
297
+ all_after_images = []
298
+ all_before_detections = []
299
+ all_after_detections = []
300
+
301
  # Overall statistics
302
  total_new_damages = 0
303
  total_existing_damages = 0
304
  total_matched_damages = 0
305
 
 
306
  session_id = str(uuid.uuid4())[:8]
307
  timestamp_str = datetime.now().strftime("%Y%m%d_%H%M%S")
308
 
309
  # Process each position pair
310
  for i in range(6):
 
311
  before_contents = await before_images[i].read()
312
  after_contents = await after_images[i].read()
313
 
 
320
  before_bgr = cv2.cvtColor(before_np, cv2.COLOR_RGB2BGR)
321
  after_bgr = cv2.cvtColor(after_np, cv2.COLOR_RGB2BGR)
322
 
323
+ # Store for multi-view analysis
324
+ all_before_images.append(before_bgr)
325
+ all_after_images.append(after_bgr)
326
+
327
  image_pairs.append((before_bgr, after_bgr))
328
 
329
  # Detect damages
330
+ before_detections = current_detector.detect(before_bgr)
331
+ after_detections = current_detector.detect(after_bgr)
332
 
333
+ all_before_detections.append(before_detections)
334
+ all_after_detections.append(after_detections)
335
 
336
+ # Enhanced comparison with ReID (pass images for feature extraction)
337
+ comparison = comparator.analyze_damage_status(
338
+ before_detections, after_detections,
339
+ before_bgr, after_bgr
340
+ )
341
+
342
+ # Update statistics
343
  total_new_damages += len(comparison['new_damages'])
344
  total_existing_damages += len(comparison['repaired_damages'])
345
  total_matched_damages += len(comparison['matched_damages'])
346
 
347
+ # Create visualization
348
  vis_img = visualizer.create_comparison_visualization(
349
  before_bgr, after_bgr,
350
  before_detections, after_detections,
 
352
  )
353
 
354
  vis_filename = f"comparison_{timestamp_str}_{session_id}_pos{i + 1}.jpg"
355
+ vis_path = UPLOADS_DIR / vis_filename
356
+ cv2.imwrite(str(vis_path), vis_img)
 
 
 
 
 
 
357
 
 
358
  vis_url = f"http://localhost:8000/uploads/{vis_filename}"
359
  all_visualizations.append(vis_url)
360
 
361
+ # Store position result with ReID info
362
  position_results.append({
363
  f"position_{i + 1}": {
364
  "case": comparison['case'],
 
367
  "new_damages": comparison['new_damages'],
368
  "matched_damages": comparison['matched_damages'],
369
  "repaired_damages": comparison['repaired_damages'],
370
+ "using_reid": comparison['statistics'].get('using_reid', True),
371
  "visualization_path": f"uploads/{vis_filename}",
372
  "visualization_url": vis_url,
373
  "filename": vis_filename
374
  }
375
  })
376
 
377
+
378
+ # Deduplicate BEFORE damages across all 6 views
379
+ unique_before = comparator.deduplicate_detections_across_views(
380
+ all_before_detections, all_before_images
381
+ )
382
+
383
+ # Deduplicate AFTER damages across all 6 views
384
+ unique_after = comparator.deduplicate_detections_across_views(
385
+ all_after_detections, all_after_images
386
+ )
387
+
388
+ print(
389
+ f"Before: {sum(len(d['boxes']) for d in all_before_detections)} detections → {len(unique_before)} unique")
390
+ print(f"After: {sum(len(d['boxes']) for d in all_after_detections)} detections → {len(unique_after)} unique")
391
+
392
+ # Determine overall case with deduplication
393
+ actual_new_damages = len(unique_after) - len(unique_before)
394
+
395
  overall_case = "CASE_3_SUCCESS"
396
  overall_message = "Successful delivery - No damage detected"
397
 
398
+ if actual_new_damages > 0:
399
  overall_case = "CASE_2_NEW_DAMAGE"
400
+ overall_message = f"Error during delivery - {actual_new_damages} new unique damage(s) detected"
401
+ elif len(unique_before) > 0 and actual_new_damages <= 0:
402
  overall_case = "CASE_1_EXISTING"
403
+ overall_message = "Existing damages from beginning -> Delivery completed"
404
 
405
+ # Create summary grid
406
  grid_results = [res[f"position_{i + 1}"] for i, res in enumerate(position_results)]
407
  grid_img = visualizer.create_summary_grid(grid_results, image_pairs)
408
 
409
  grid_filename = f"summary_grid_{timestamp_str}_{session_id}.jpg"
410
+ grid_path = UPLOADS_DIR / grid_filename
411
+ cv2.imwrite(str(grid_path), grid_img)
 
 
 
 
 
 
412
 
413
  grid_url = f"http://localhost:8000/uploads/{grid_filename}"
414
 
 
415
  timestamp = datetime.now().isoformat()
416
 
417
  return JSONResponse({
418
  "status": "success",
419
  "session_id": session_id,
420
  "timestamp": timestamp,
421
+ "reid_enabled": True,
422
  "overall_result": {
423
  "case": overall_case,
424
  "message": overall_message,
425
  "statistics": {
426
+ "total_new_damages": int(total_new_damages),
427
+ "total_matched_damages": int(total_matched_damages),
428
+ "total_repaired_damages": int(total_existing_damages),
429
+ "unique_damages_before": int(len(unique_before)),
430
+ "unique_damages_after": int(len(unique_after)),
431
+ "actual_new_unique_damages": int(max(0, len(unique_after) - len(unique_before)))
432
+
433
  }
434
  },
435
+ "deduplication_info": {
436
+ "before_total_detections": int(sum(len(d['boxes']) for d in all_before_detections)),
437
+
438
+ "before_unique_damages": int(len(unique_before)),
439
+ "after_total_detections": int(sum(len(d['boxes']) for d in all_after_detections)),
440
+
441
+ "after_unique_damages": int(len(unique_after)),
442
+ "duplicate_reduction_rate": f"{(1 - len(unique_after) / sum(len(d['boxes']) for d in all_after_detections)) * 100:.1f}%"
443
+ if sum(len(d['boxes']) for d in all_after_detections) > 0 else "0%"
444
+ },
445
  "position_results": position_results,
446
  "summary_visualization_path": f"uploads/{grid_filename}",
447
  "summary_visualization_url": grid_url,
448
+ "all_visualizations": all_visualizations,
449
  "recommendations": {
450
+ "action_required": bool(actual_new_damages > 0),
451
+ "suggested_action": "Investigate delivery process" if actual_new_damages > 0
452
+ else "Proceed with delivery completion"
453
  }
454
  })
455
 
 
457
  raise HTTPException(status_code=500, detail=f"Comparison failed: {str(e)}")
458
 
459
 
 
460
  if __name__ == "__main__":
461
  import os
462
  uvicorn.run(