Mhammad Ibrahim commited on
Commit
de02986
Β·
1 Parent(s): 38e614f

Deploy vehicle damage detection system

Browse files
Files changed (7) hide show
  1. .gitignore +65 -0
  2. README.md +79 -5
  3. app.py +15 -0
  4. config.py +22 -0
  5. detector.py +264 -0
  6. requirements.txt +16 -0
  7. ui.py +334 -0
.gitignore ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ develop-eggs/
9
+ dist/
10
+ downloads/
11
+ eggs/
12
+ .eggs/
13
+ lib/
14
+ lib64/
15
+ parts/
16
+ sdist/
17
+ var/
18
+ wheels/
19
+ *.egg-info/
20
+ .installed.cfg
21
+ *.egg
22
+
23
+ # Virtual environments
24
+ autoVenv/
25
+ venv/
26
+ ENV/
27
+ env/
28
+
29
+ # IDE
30
+ .vscode/
31
+ .idea/
32
+ *.swp
33
+ *.swo
34
+ *~
35
+
36
+ # Testing
37
+ .pytest_cache/
38
+ .coverage
39
+ htmlcov/
40
+ test_output/
41
+
42
+ # Gradio
43
+ .gradio/
44
+ flagged/
45
+
46
+ # OS
47
+ .DS_Store
48
+ Thumbs.db
49
+
50
+ # Logs
51
+ *.log
52
+
53
+ # Environment variables
54
+ .env
55
+ .env.local
56
+
57
+ # Model files (if large)
58
+ *.pt
59
+ *.pth
60
+ *.onnx
61
+ *.weights
62
+
63
+ # Temporary files
64
+ *.tmp
65
+ *.bak
README.md CHANGED
@@ -1,12 +1,86 @@
1
  ---
2
  title: Vehicle Damage Detector
3
- emoji: 🏒
4
- colorFrom: indigo
5
- colorTo: gray
6
  sdk: gradio
7
- sdk_version: 5.49.1
8
  app_file: app.py
9
  pinned: false
 
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
  title: Vehicle Damage Detector
3
+ emoji: πŸš—
4
+ colorFrom: blue
5
+ colorTo: red
6
  sdk: gradio
7
+ sdk_version: 5.0.0
8
  app_file: app.py
9
  pinned: false
10
+ license: mit
11
  ---
12
 
13
+ # πŸš— AI-Powered Vehicle Damage Detection System
14
+
15
+ An intelligent system for automated vehicle damage assessment using AI/ML for car rental companies.
16
+
17
+ ## Features
18
+
19
+ - **πŸ“Έ Image Upload**: Upload vehicle images for damage detection
20
+ - **πŸ€– AI Detection**: Powered by YOLOv11 trained on Roboflow dataset
21
+ - **πŸ’° Cost Estimation**: Automatic repair cost calculation
22
+ - **πŸ“Š Comparison**: Side-by-side pickup vs return analysis
23
+ - **🎯 23 Damage Classes**: Comprehensive damage type identification
24
+
25
+ ## Damage Classes
26
+
27
+ The system can detect:
28
+ - Dents (bonnet, door, fender, bumper, etc.)
29
+ - Scratches (door, bumper, etc.)
30
+ - Glass damage (windscreen, windows)
31
+ - Light damage (headlights, taillights, etc.)
32
+ - Paint damage (chips, traces)
33
+
34
+ ## How to Use
35
+
36
+ 1. **Single Image Detection**: Upload a vehicle image to detect damages
37
+ 2. **Compare Images**: Upload pickup and return images to identify new damages
38
+ 3. View annotated results with bounding boxes and damage details
39
+ 4. Get estimated repair costs
40
+
41
+ ## Technical Details
42
+
43
+ - **Model**: YOLOv11 (Roboflow trained)
44
+ - **Dataset**: Custom car damage detection dataset
45
+ - **API**: Roboflow Inference SDK
46
+ - **Framework**: Gradio for UI, FastAPI for REST endpoints
47
+
48
+ ## Local Development
49
+
50
+ ```bash
51
+ # Install dependencies
52
+ pip install -r requirements.txt
53
+
54
+ # Run UI only
55
+ python ui.py
56
+
57
+ # Run API only
58
+ python api.py
59
+
60
+ # Run both
61
+ python main.py
62
+
63
+ # Run tests
64
+ python test_suite.py
65
+ ```
66
+
67
+ ## API Endpoints
68
+
69
+ - `POST /api/detect`: Single image damage detection
70
+ - `POST /api/compare`: Compare pickup vs return images
71
+ - `GET /api/health`: Health check
72
+ - `GET /api/damage-classes`: List all damage classes
73
+
74
+ ## Architecture
75
+
76
+ - `app.py`: Hugging Face Spaces entry point
77
+ - `ui.py`: Gradio web interface
78
+ - `api.py`: FastAPI REST API
79
+ - `detector.py`: Core detection logic
80
+ - `config.py`: Configuration and damage costs
81
+
82
+ ## Credits
83
+
84
+ - Developed for AI-Powered Vehicle Condition Assessment Challenge
85
+ - Model trained on Roboflow platform
86
+ - Powered by Ultralytics YOLOv11
app.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Hugging Face Spaces entry point
3
+ Launches the Gradio UI interface
4
+ """
5
+
6
+ import gradio as gr
7
+ from ui import create_interface
8
+
9
+ if __name__ == "__main__":
10
+ interface = create_interface()
11
+ interface.launch(
12
+ server_name="0.0.0.0",
13
+ server_port=7860,
14
+ share=False
15
+ )
config.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Configuration settings for Vehicle Damage Detection System
3
+ """
4
+
5
+ import os
6
+
7
+ # Roboflow API Configuration
8
+ ROBOFLOW_API_KEY = os.getenv("ROBOFLOW_API_KEY", "DvmKUUSUrM8rQBeil5V2")
9
+ ROBOFLOW_MODEL_ID = os.getenv("ROBOFLOW_MODEL_ID", "car-damage-detection-5ioys-4z3z4/2")
10
+
11
+ # Server Configuration
12
+ HOST = os.getenv("HOST", "127.0.0.1")
13
+ PORT = int(os.getenv("PORT", 7860))
14
+ FASTAPI_PORT = int(os.getenv("FASTAPI_PORT", 8000))
15
+
16
+ # Model Configuration
17
+ CONFIDENCE_THRESHOLD = float(os.getenv("CONFIDENCE_THRESHOLD", 0.25))
18
+
19
+ # Application Settings
20
+ APP_TITLE = "AI-Powered Vehicle Damage Detection"
21
+ APP_DESCRIPTION = "Automated vehicle damage assessment using AI for car rental inspection"
22
+ VERSION = "1.0.0"
detector.py ADDED
@@ -0,0 +1,264 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Vehicle Damage Detection System
3
+ Core detection module using Roboflow API
4
+ """
5
+
6
+ import base64
7
+ import numpy as np
8
+ from PIL import Image, ImageDraw, ImageFont
9
+ from io import BytesIO
10
+ from typing import List, Dict, Tuple
11
+
12
+
13
+ class DamageDetector:
14
+ """Vehicle damage detection using Roboflow API"""
15
+
16
+ # Damage class definitions (23 custom classes)
17
+ DAMAGE_CLASSES = [
18
+ 'bonnet-dent', 'doorouter-dent', 'doorouter-paint-trace', 'doorouter-scratch',
19
+ 'fender-dent', 'front-bumper-dent', 'front-bumper-scratch', 'Front-Windscreen-Damage',
20
+ 'Headlight-Damage', 'Major-Rear-Bumper-Dent', 'medium-Bodypanel-Dent', 'paint-chip',
21
+ 'paint-trace', 'pillar-dent', 'quaterpanel-dent', 'rear-bumper-dent',
22
+ 'rear-bumper-scratch', 'Rear-windscreen-Damage', 'roof-dent', 'RunningBoard-Dent',
23
+ 'Sidemirror-Damage', 'Signlight-Damage', 'Taillight-Damage'
24
+ ]
25
+
26
+ # Repair cost estimation matrix (USD)
27
+ REPAIR_COSTS = {
28
+ # Dents
29
+ 'bonnet-dent': {'minor': 150, 'moderate': 400, 'severe': 800},
30
+ 'doorouter-dent': {'minor': 100, 'moderate': 350, 'severe': 700},
31
+ 'fender-dent': {'minor': 120, 'moderate': 380, 'severe': 750},
32
+ 'front-bumper-dent': {'minor': 100, 'moderate': 300, 'severe': 600},
33
+ 'pillar-dent': {'minor': 200, 'moderate': 500, 'severe': 1000},
34
+ 'quaterpanel-dent': {'minor': 150, 'moderate': 400, 'severe': 800},
35
+ 'rear-bumper-dent': {'minor': 100, 'moderate': 300, 'severe': 600},
36
+ 'roof-dent': {'minor': 200, 'moderate': 600, 'severe': 1200},
37
+ 'medium-Bodypanel-Dent': {'minor': 150, 'moderate': 400, 'severe': 900},
38
+ 'Major-Rear-Bumper-Dent': {'minor': 300, 'moderate': 700, 'severe': 1500},
39
+ 'RunningBoard-Dent': {'minor': 80, 'moderate': 250, 'severe': 500},
40
+
41
+ # Scratches
42
+ 'doorouter-scratch': {'minor': 50, 'moderate': 150, 'severe': 400},
43
+ 'front-bumper-scratch': {'minor': 50, 'moderate': 150, 'severe': 350},
44
+ 'rear-bumper-scratch': {'minor': 50, 'moderate': 150, 'severe': 350},
45
+
46
+ # Paint damage
47
+ 'doorouter-paint-trace': {'minor': 60, 'moderate': 180, 'severe': 450},
48
+ 'paint-chip': {'minor': 40, 'moderate': 120, 'severe': 300},
49
+ 'paint-trace': {'minor': 50, 'moderate': 150, 'severe': 400},
50
+
51
+ # Glass/Light damage
52
+ 'Front-Windscreen-Damage': {'minor': 200, 'moderate': 500, 'severe': 1000},
53
+ 'Rear-windscreen-Damage': {'minor': 200, 'moderate': 500, 'severe': 1000},
54
+ 'Headlight-Damage': {'minor': 150, 'moderate': 400, 'severe': 800},
55
+ 'Taillight-Damage': {'minor': 100, 'moderate': 300, 'severe': 600},
56
+ 'Signlight-Damage': {'minor': 80, 'moderate': 200, 'severe': 400},
57
+ 'Sidemirror-Damage': {'minor': 100, 'moderate': 300, 'severe': 600},
58
+ }
59
+
60
+ def __init__(self, api_key: str, model_id: str):
61
+ """
62
+ Initialize Roboflow damage detector
63
+
64
+ Args:
65
+ api_key: Roboflow API key
66
+ model_id: Roboflow model ID (workspace/project/version)
67
+ """
68
+ self.api_key = api_key
69
+ self.model_id = model_id
70
+ self.client = None
71
+ self._initialize_client()
72
+
73
+ def _initialize_client(self):
74
+ """Initialize Roboflow API client"""
75
+ try:
76
+ from inference_sdk import InferenceHTTPClient
77
+ self.client = InferenceHTTPClient(
78
+ api_url="https://serverless.roboflow.com",
79
+ api_key=self.api_key
80
+ )
81
+ print(f"βœ“ Roboflow API initialized")
82
+ print(f" Model: {self.model_id}")
83
+ except ImportError:
84
+ raise ImportError(
85
+ "inference-sdk not installed. Run: pip install inference-sdk"
86
+ )
87
+ except Exception as e:
88
+ raise RuntimeError(f"Failed to initialize Roboflow client: {e}")
89
+
90
+ def detect_damages(self, image: Image.Image) -> List[Dict]:
91
+ """
92
+ Detect damages in an image using Roboflow API
93
+
94
+ Args:
95
+ image: PIL Image object
96
+
97
+ Returns:
98
+ List of damage detections with format:
99
+ [{
100
+ 'bbox': [x1, y1, x2, y2],
101
+ 'confidence': float,
102
+ 'class': str,
103
+ 'severity': str,
104
+ 'estimated_cost': int
105
+ }]
106
+ """
107
+ # Convert image to base64
108
+ img_byte_arr = BytesIO()
109
+ image.save(img_byte_arr, format='JPEG')
110
+ img_base64 = base64.b64encode(img_byte_arr.getvalue()).decode('utf-8')
111
+
112
+ # Call Roboflow API
113
+ result = self.client.infer(img_base64, model_id=self.model_id)
114
+
115
+ # Parse detections
116
+ detections = []
117
+ img_array = np.array(image)
118
+ h, w = img_array.shape[:2]
119
+
120
+ if 'predictions' in result:
121
+ for pred in result['predictions']:
122
+ # Convert from center coords to corners
123
+ x_center, y_center = pred['x'], pred['y']
124
+ width, height = pred['width'], pred['height']
125
+
126
+ x1 = int(x_center - width / 2)
127
+ y1 = int(y_center - height / 2)
128
+ x2 = int(x_center + width / 2)
129
+ y2 = int(y_center + height / 2)
130
+
131
+ class_name = pred['class']
132
+ confidence = pred['confidence']
133
+
134
+ # Estimate severity
135
+ severity = self._estimate_severity(x1, y1, x2, y2, class_name, (h, w))
136
+
137
+ # Get repair cost
138
+ cost = self.REPAIR_COSTS.get(class_name, {}).get(severity, 100)
139
+
140
+ detections.append({
141
+ 'bbox': [x1, y1, x2, y2],
142
+ 'confidence': confidence,
143
+ 'class': class_name,
144
+ 'severity': severity,
145
+ 'estimated_cost': cost
146
+ })
147
+
148
+ return detections
149
+
150
+ def _estimate_severity(self, x1: int, y1: int, x2: int, y2: int,
151
+ damage_class: str, img_shape: Tuple[int, int]) -> str:
152
+ """
153
+ Estimate damage severity based on size and type
154
+
155
+ Args:
156
+ x1, y1, x2, y2: Bounding box coordinates
157
+ damage_class: Type of damage
158
+ img_shape: (height, width) of image
159
+
160
+ Returns:
161
+ 'minor', 'moderate', or 'severe'
162
+ """
163
+ h, w = img_shape
164
+ bbox_area = (x2 - x1) * (y2 - y1)
165
+ img_area = h * w
166
+ damage_ratio = bbox_area / img_area
167
+
168
+ # Critical damage types
169
+ critical_types = ['Major-Rear-Bumper-Dent', 'Front-Windscreen-Damage',
170
+ 'Rear-windscreen-Damage']
171
+
172
+ if damage_class in critical_types:
173
+ if damage_ratio > 0.05:
174
+ return 'severe'
175
+ elif damage_ratio > 0.02:
176
+ return 'moderate'
177
+ else:
178
+ return 'minor'
179
+
180
+ # Standard damage assessment
181
+ if damage_ratio > 0.08:
182
+ return 'severe'
183
+ elif damage_ratio > 0.03:
184
+ return 'moderate'
185
+ else:
186
+ return 'minor'
187
+
188
+ def draw_detections(self, image: Image.Image, detections: List[Dict],
189
+ color_map: Dict[str, str] = None) -> Image.Image:
190
+ """
191
+ Draw bounding boxes on image
192
+
193
+ Args:
194
+ image: PIL Image
195
+ detections: List of detections from detect_damages()
196
+ color_map: Optional severity color mapping
197
+
198
+ Returns:
199
+ Annotated PIL Image
200
+ """
201
+ if color_map is None:
202
+ color_map = {
203
+ 'minor': 'yellow',
204
+ 'moderate': 'orange',
205
+ 'severe': 'red'
206
+ }
207
+
208
+ img_copy = image.copy()
209
+ draw = ImageDraw.Draw(img_copy)
210
+
211
+ try:
212
+ font = ImageFont.truetype("arial.ttf", 20)
213
+ except:
214
+ font = ImageFont.load_default()
215
+
216
+ for det in detections:
217
+ x1, y1, x2, y2 = det['bbox']
218
+ severity = det['severity']
219
+ class_name = det['class']
220
+ confidence = det['confidence']
221
+
222
+ color = color_map.get(severity, 'red')
223
+
224
+ # Draw bounding box
225
+ draw.rectangle([x1, y1, x2, y2], outline=color, width=3)
226
+
227
+ # Draw label
228
+ label = f"{class_name} ({confidence*100:.1f}%)"
229
+ draw.rectangle([x1, y1-25, x1+len(label)*10, y1], fill=color)
230
+ draw.text((x1+5, y1-22), label, fill='white', font=font)
231
+
232
+ return img_copy
233
+
234
+ def compare_images(self, pickup_img: Image.Image,
235
+ return_img: Image.Image) -> Dict:
236
+ """
237
+ Compare pickup and return images to find new damages
238
+
239
+ Args:
240
+ pickup_img: Image from vehicle pickup
241
+ return_img: Image from vehicle return
242
+
243
+ Returns:
244
+ Dictionary with comparison results
245
+ """
246
+ pickup_damages = self.detect_damages(pickup_img)
247
+ return_damages = self.detect_damages(return_img)
248
+
249
+ # Find new damages (simple heuristic based on class count)
250
+ pickup_classes = [d['class'] for d in pickup_damages]
251
+ new_damages = [d for d in return_damages
252
+ if d['class'] not in pickup_classes or
253
+ pickup_classes.count(d['class']) <
254
+ [d['class'] for d in return_damages].count(d['class'])]
255
+
256
+ total_new_cost = sum(d['estimated_cost'] for d in new_damages)
257
+
258
+ return {
259
+ 'pickup_damages': pickup_damages,
260
+ 'return_damages': return_damages,
261
+ 'new_damages': new_damages,
262
+ 'total_new_cost': total_new_cost,
263
+ 'summary': f"Found {len(new_damages)} new damage(s). Estimated cost: ${total_new_cost}"
264
+ }
requirements.txt ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Core Dependencies
2
+ gradio>=5.0.0
3
+ fastapi>=0.100.0
4
+ uvicorn>=0.23.0
5
+ python-multipart>=0.0.6
6
+
7
+ # AI/ML
8
+ inference-sdk>=0.9.0
9
+ numpy>=1.26.0
10
+ Pillow>=10.1.0
11
+
12
+ # Image Processing
13
+ opencv-python>=4.8.1.78
14
+
15
+ # Utilities
16
+ requests>=2.31.0
ui.py ADDED
@@ -0,0 +1,334 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Gradio Web UI for Vehicle Damage Detection
3
+ User-friendly interface for vehicle inspection
4
+ """
5
+
6
+ import gradio as gr
7
+ import json
8
+ from datetime import datetime
9
+ from PIL import Image
10
+
11
+ from detector import DamageDetector
12
+ from config import (
13
+ ROBOFLOW_API_KEY,
14
+ ROBOFLOW_MODEL_ID,
15
+ HOST,
16
+ PORT,
17
+ APP_TITLE,
18
+ APP_DESCRIPTION
19
+ )
20
+
21
+ # Initialize detector
22
+ detector = DamageDetector(
23
+ api_key=ROBOFLOW_API_KEY,
24
+ model_id=ROBOFLOW_MODEL_ID
25
+ )
26
+
27
+
28
+ def analyze_single_image(image):
29
+ """Analyze a single vehicle image for damages"""
30
+
31
+ if image is None:
32
+ return None, "⚠️ Please upload an image!", None
33
+
34
+ try:
35
+ # Convert to PIL Image
36
+ img = Image.fromarray(image).convert('RGB')
37
+
38
+ # Detect damages
39
+ detections = detector.detect_damages(img)
40
+
41
+ # Draw annotations
42
+ annotated_img = detector.draw_detections(img, detections)
43
+
44
+ # Calculate statistics
45
+ total_cost = sum(d['estimated_cost'] for d in detections)
46
+ severity_counts = {
47
+ 'minor': sum(1 for d in detections if d['severity'] == 'minor'),
48
+ 'moderate': sum(1 for d in detections if d['severity'] == 'moderate'),
49
+ 'severe': sum(1 for d in detections if d['severity'] == 'severe')
50
+ }
51
+
52
+ # Generate report
53
+ report_md = f"""
54
+ # πŸš— Vehicle Damage Analysis Report
55
+ **Date:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
56
+
57
+ ---
58
+
59
+ ## Summary
60
+ {'βœ… **No damages detected!**' if len(detections) == 0 else f'⚠️ **Found {len(detections)} damage(s)**'}
61
+
62
+ ## Statistics
63
+ - **Total Damages:** {len(detections)}
64
+ - **Minor:** {severity_counts['minor']} | **Moderate:** {severity_counts['moderate']} | **Severe:** {severity_counts['severe']}
65
+ - **Estimated Repair Cost:** **${total_cost}**
66
+
67
+ """
68
+
69
+ if detections:
70
+ report_md += "## Detailed Damage List\n\n"
71
+ report_md += "| # | Type | Severity | Confidence | Location | Cost |\n"
72
+ report_md += "|---|------|----------|------------|----------|------|\n"
73
+ for i, det in enumerate(detections, 1):
74
+ x1, y1 = det['bbox'][:2]
75
+ report_md += (
76
+ f"| {i} | {det['class']} | {det['severity']} | "
77
+ f"{det['confidence']*100:.1f}% | ({x1}, {y1}) | "
78
+ f"${det['estimated_cost']} |\n"
79
+ )
80
+
81
+ report_md += "\n---\n*Powered by Roboflow AI Detection*"
82
+
83
+ # JSON output
84
+ json_output = json.dumps({
85
+ 'timestamp': datetime.now().isoformat(),
86
+ 'total_damages': len(detections),
87
+ 'estimated_cost': total_cost,
88
+ 'severity_breakdown': severity_counts,
89
+ 'damages': [
90
+ {
91
+ 'class': d['class'],
92
+ 'severity': d['severity'],
93
+ 'confidence': f"{d['confidence']*100:.1f}%",
94
+ 'location': f"({d['bbox'][0]}, {d['bbox'][1]})",
95
+ 'estimated_cost': d['estimated_cost']
96
+ }
97
+ for d in detections
98
+ ]
99
+ }, indent=2)
100
+
101
+ return annotated_img, report_md, json_output
102
+
103
+ except Exception as e:
104
+ import traceback
105
+ error_msg = f"❌ **Error:** {str(e)}\n\n```\n{traceback.format_exc()}\n```"
106
+ return None, error_msg, None
107
+
108
+
109
+ def compare_images_fn(pickup_image, return_image):
110
+ """Compare pickup and return images to find new damages"""
111
+
112
+ if pickup_image is None or return_image is None:
113
+ return None, "⚠️ Please upload both pickup and return images!", None
114
+
115
+ try:
116
+ # Convert to PIL Images
117
+ pickup_img = Image.fromarray(pickup_image).convert('RGB')
118
+ return_img = Image.fromarray(return_image).convert('RGB')
119
+
120
+ # Compare images
121
+ comparison = detector.compare_images(pickup_img, return_img)
122
+
123
+ # Draw annotations
124
+ pickup_annotated = detector.draw_detections(
125
+ pickup_img,
126
+ comparison['pickup_damages'],
127
+ {'minor': 'green', 'moderate': 'green', 'severe': 'green'}
128
+ )
129
+
130
+ return_annotated = detector.draw_detections(
131
+ return_img,
132
+ comparison['new_damages']
133
+ )
134
+
135
+ # Create side-by-side comparison
136
+ w1, h1 = pickup_annotated.size
137
+ w2, h2 = return_annotated.size
138
+ max_h = max(h1, h2)
139
+
140
+ combined = Image.new('RGB', (w1 + w2, max_h), color='white')
141
+ combined.paste(pickup_annotated, (0, 0))
142
+ combined.paste(return_annotated, (w1, 0))
143
+
144
+ # Generate report
145
+ new_cost = comparison['total_new_cost']
146
+ new_damages = comparison['new_damages']
147
+
148
+ report_md = f"""
149
+ # πŸ”„ Vehicle Comparison Report
150
+ **Date:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
151
+
152
+ ---
153
+
154
+ ## Summary
155
+ - **Pickup Damages:** {len(comparison['pickup_damages'])}
156
+ - **Return Damages:** {len(comparison['return_damages'])}
157
+ - **New Damages:** {len(new_damages)}
158
+ - **New Damage Cost:** **${new_cost}**
159
+
160
+ {comparison['summary']}
161
+
162
+ """
163
+
164
+ if new_damages:
165
+ report_md += "## New Damages Detected\n\n"
166
+ report_md += "| # | Type | Severity | Confidence | Cost |\n"
167
+ report_md += "|---|------|----------|------------|------|\n"
168
+ for i, det in enumerate(new_damages, 1):
169
+ report_md += (
170
+ f"| {i} | {det['class']} | {det['severity']} | "
171
+ f"{det['confidence']*100:.1f}% | ${det['estimated_cost']} |\n"
172
+ )
173
+
174
+ report_md += "\n---\n*Green boxes = Existing | Red/Orange/Yellow = New damages*"
175
+
176
+ # JSON output
177
+ json_output = json.dumps({
178
+ 'timestamp': datetime.now().isoformat(),
179
+ 'pickup_damages': len(comparison['pickup_damages']),
180
+ 'return_damages': len(comparison['return_damages']),
181
+ 'new_damages': len(new_damages),
182
+ 'new_damage_cost': new_cost,
183
+ 'new_damage_details': [
184
+ {
185
+ 'class': d['class'],
186
+ 'severity': d['severity'],
187
+ 'confidence': f"{d['confidence']*100:.1f}%",
188
+ 'estimated_cost': d['estimated_cost']
189
+ }
190
+ for d in new_damages
191
+ ]
192
+ }, indent=2)
193
+
194
+ return combined, report_md, json_output
195
+
196
+ except Exception as e:
197
+ import traceback
198
+ error_msg = f"❌ **Error:** {str(e)}\n\n```\n{traceback.format_exc()}\n```"
199
+ return None, error_msg, None
200
+
201
+
202
+ # =============================================================================
203
+ # BUILD GRADIO INTERFACE
204
+ # =============================================================================
205
+
206
+ with gr.Blocks(title=APP_TITLE, theme=gr.themes.Soft()) as demo:
207
+
208
+ gr.Markdown(f"""
209
+ # πŸš— {APP_TITLE}
210
+
211
+ {APP_DESCRIPTION}
212
+ """)
213
+
214
+ with gr.Tabs() as tabs:
215
+ # Tab 1: Single Image Analysis
216
+ with gr.Tab("πŸ“Έ Single Image Analysis"):
217
+ gr.Markdown("""
218
+ ### Quick Damage Detection
219
+ Upload a vehicle image to detect and analyze all damages instantly.
220
+ Supports multiple angles: front, rear, sides, roof, interior.
221
+ """)
222
+
223
+ with gr.Row():
224
+ with gr.Column():
225
+ single_input = gr.Image(
226
+ label="πŸ“Έ Upload Vehicle Image",
227
+ type="numpy",
228
+ sources=["upload", "webcam", "clipboard"]
229
+ )
230
+ analyze_btn = gr.Button(
231
+ "πŸ” Analyze Damages",
232
+ variant="primary",
233
+ size="lg"
234
+ )
235
+
236
+ with gr.Column():
237
+ single_output_image = gr.Image(
238
+ label="βœ… Detected Damages (Annotated)"
239
+ )
240
+
241
+ single_output_report = gr.Markdown(label="πŸ“Š Analysis Report")
242
+
243
+ with gr.Accordion("πŸ“‹ JSON Output (for API integration)", open=False):
244
+ single_output_json = gr.Code(label="JSON Data", language="json")
245
+
246
+ analyze_btn.click(
247
+ fn=analyze_single_image,
248
+ inputs=[single_input],
249
+ outputs=[single_output_image, single_output_report, single_output_json]
250
+ )
251
+
252
+ # Tab 2: Comparison Mode
253
+ with gr.Tab("πŸ”„ Comparison Mode"):
254
+ gr.Markdown("""
255
+ ### Compare Pickup vs Return
256
+ Upload vehicle photos from pickup and return to identify new damages.
257
+ Side-by-side comparison highlights what changed during rental.
258
+ """)
259
+
260
+ with gr.Row():
261
+ with gr.Column():
262
+ pickup_input = gr.Image(
263
+ label="πŸ“Έ Pickup Photo (Before Rental)",
264
+ type="numpy",
265
+ sources=["upload", "webcam", "clipboard"]
266
+ )
267
+
268
+ with gr.Column():
269
+ return_input = gr.Image(
270
+ label="πŸ“Έ Return Photo (After Rental)",
271
+ type="numpy",
272
+ sources=["upload", "webcam", "clipboard"]
273
+ )
274
+
275
+ compare_btn = gr.Button(
276
+ "πŸ” Compare & Analyze",
277
+ variant="primary",
278
+ size="lg"
279
+ )
280
+
281
+ compare_output_image = gr.Image(
282
+ label="πŸ”„ Side-by-Side Comparison"
283
+ )
284
+
285
+ compare_output_report = gr.Markdown(label="πŸ“Š Comparison Report")
286
+
287
+ with gr.Accordion("πŸ“‹ JSON Output (for API integration)", open=False):
288
+ compare_output_json = gr.Code(label="JSON Data", language="json")
289
+
290
+ compare_btn.click(
291
+ fn=compare_images_fn,
292
+ inputs=[pickup_input, return_input],
293
+ outputs=[compare_output_image, compare_output_report, compare_output_json]
294
+ )
295
+
296
+ gr.Markdown("""
297
+ ---
298
+ ### πŸ”§ Technical Details
299
+
300
+ **AI Model:** YOLOv11 custom-trained on Roboflow
301
+ **Detection Classes:** 23 specialized vehicle damage types
302
+ **API Integration:** REST/GraphQL endpoints available
303
+ **Supported Formats:** JPG, PNG, JPEG
304
+ **Camera Support:** Desktop, tablet, phone cameras via HTML5
305
+
306
+ ### πŸ“– How It Works
307
+
308
+ 1. **Upload/Capture:** Use built-in cameras or upload images
309
+ 2. **AI Detection:** Roboflow API identifies damages with confidence scores
310
+ 3. **Analysis:** Severity estimation (minor/moderate/severe) and cost calculation
311
+ 4. **Report:** Visual annotations + detailed breakdown + JSON export
312
+
313
+ ### πŸ“‘ API Access
314
+
315
+ REST API available at port 8000. See `/api/docs` for interactive documentation.
316
+
317
+ **Endpoints:**
318
+ - `POST /api/detect` - Single image analysis
319
+ - `POST /api/compare` - Pickup vs return comparison
320
+ - `GET /api/damage-classes` - List all detectable damages
321
+ - `GET /api/repair-costs` - Cost estimation matrix
322
+
323
+ ---
324
+
325
+ *Powered by Roboflow AI β€’ Built with Gradio & FastAPI*
326
+ """)
327
+
328
+
329
+ if __name__ == "__main__":
330
+ demo.launch(
331
+ share=False,
332
+ server_name=HOST,
333
+ server_port=PORT
334
+ )