Spaces:
Running
Running
Commit
·
51aa863
1
Parent(s):
2bf63f0
Update main.py
Browse files
main.py
CHANGED
@@ -1,17 +1,18 @@
|
|
1 |
import io
|
2 |
-
import
|
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 =
|
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(
|
|
|
|
|
|
|
|
|
72 |
"""
|
73 |
-
|
74 |
|
75 |
Args:
|
76 |
-
file:
|
77 |
-
|
78 |
-
Returns:
|
79 |
-
Detection results with bounding boxes and path to visualized image
|
80 |
"""
|
81 |
try:
|
82 |
-
#
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
87 |
|
88 |
-
#
|
89 |
-
|
|
|
90 |
|
91 |
-
|
92 |
-
|
93 |
|
94 |
-
|
95 |
-
|
|
|
|
|
|
|
|
|
96 |
|
97 |
-
|
|
|
|
|
98 |
|
99 |
-
|
100 |
-
cv2.imwrite(str(output_path), visualized)
|
101 |
|
102 |
-
|
103 |
-
|
|
|
|
|
|
|
104 |
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
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(
|
125 |
-
before_2: UploadFile = File(
|
126 |
-
before_3: UploadFile = File(
|
127 |
-
before_4: UploadFile = File(
|
128 |
-
before_5: UploadFile = File(
|
129 |
-
before_6: UploadFile = File(
|
130 |
# After delivery images (6 positions)
|
131 |
-
after_1: UploadFile = File(
|
132 |
-
after_2: UploadFile = File(
|
133 |
-
after_3: UploadFile = File(
|
134 |
-
after_4: UploadFile = File(
|
135 |
-
after_5: UploadFile = File(
|
136 |
-
after_6: UploadFile = File(
|
|
|
|
|
137 |
):
|
138 |
"""
|
139 |
-
|
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
|
|
|
|
|
|
|
183 |
image_pairs.append((before_bgr, after_bgr))
|
184 |
|
185 |
# Detect damages
|
186 |
-
before_detections =
|
187 |
-
after_detections =
|
188 |
|
189 |
-
|
190 |
-
|
191 |
|
192 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
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
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
234 |
overall_case = "CASE_3_SUCCESS"
|
235 |
overall_message = "Successful delivery - No damage detected"
|
236 |
|
237 |
-
if
|
238 |
overall_case = "CASE_2_NEW_DAMAGE"
|
239 |
-
overall_message = f"Error during
|
240 |
-
elif
|
241 |
overall_case = "CASE_1_EXISTING"
|
242 |
-
overall_message = "
|
243 |
|
244 |
-
# Create summary grid
|
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
|
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,
|
280 |
"recommendations": {
|
281 |
-
"action_required":
|
282 |
-
"suggested_action": "Investigate delivery process" if
|
|
|
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(
|