Mahiruoshi commited on
Commit
8d68b15
·
verified ·
1 Parent(s): 689227c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +823 -722
app.py CHANGED
@@ -1,722 +1,823 @@
1
- from flask import Flask, request, jsonify, render_template_string, send_from_directory, send_file
2
- from flask_cors import CORS
3
- import os
4
- import importlib.util
5
- import time
6
- import zipfile
7
- import shutil
8
- import json
9
- from datetime import datetime
10
- from predict_task1 import Predictor
11
- from id_mapping import mapping
12
- from show_stitched import *
13
- import cv2
14
- import supervision as sv
15
- from ultralytics import YOLO
16
-
17
- app = Flask(__name__)
18
- CORS(app)
19
-
20
- # Load configuration
21
- config_dir = os.path.abspath(os.path.dirname(__file__))
22
- config_path = os.path.join(config_dir, 'PC_CONFIG.py')
23
- spec = importlib.util.spec_from_file_location("PC_CONFIG", config_path)
24
- PC_CONFIG = importlib.util.module_from_spec(spec)
25
- spec.loader.exec_module(PC_CONFIG)
26
-
27
- HOST = PC_CONFIG.HOST
28
- PORT = PC_CONFIG.IMAGE_REC_PORT
29
- UPLOAD_FOLDER = os.path.join(PC_CONFIG.FILE_DIRECTORY, "image-rec", "images")
30
- DATASET_FOLDER = os.path.join(PC_CONFIG.BASE_DIR, "yolo_dataset")
31
- ANNOTATED_FOLDER = os.path.join(DATASET_FOLDER, "annotated_images")
32
- LABELS_FOLDER = os.path.join(DATASET_FOLDER, "labels")
33
- IMAGES_FOLDER = os.path.join(DATASET_FOLDER, "images")
34
- CLASS_MAPPING_FILE = os.path.join(DATASET_FOLDER, "classes.json")
35
-
36
- app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
37
-
38
- # Initialize predictor
39
- predictor = Predictor()
40
-
41
- # Ensure directories exist
42
- os.makedirs(UPLOAD_FOLDER, exist_ok=True)
43
- os.makedirs(DATASET_FOLDER, exist_ok=True)
44
- os.makedirs(ANNOTATED_FOLDER, exist_ok=True)
45
- os.makedirs(LABELS_FOLDER, exist_ok=True)
46
- os.makedirs(IMAGES_FOLDER, exist_ok=True)
47
-
48
- # Initialize class mapping file
49
- if not os.path.exists(CLASS_MAPPING_FILE):
50
- # Create initial class mapping from id_mapping.py
51
- reverse_mapping = {str(v): k for k, v in mapping.items() if v != -1 and k is not None}
52
- with open(CLASS_MAPPING_FILE, 'w', encoding='utf-8') as f:
53
- json.dump(reverse_mapping, f, indent=2, ensure_ascii=False)
54
-
55
- def load_class_mapping():
56
- """Load class mapping from JSON file"""
57
- try:
58
- with open(CLASS_MAPPING_FILE, 'r', encoding='utf-8') as f:
59
- return json.load(f)
60
- except:
61
- return {}
62
-
63
- def save_class_mapping(class_mapping):
64
- """Save class mapping to JSON file"""
65
- with open(CLASS_MAPPING_FILE, 'w', encoding='utf-8') as f:
66
- json.dump(class_mapping, f, indent=2, ensure_ascii=False)
67
-
68
- def generate_yolo_annotation(results, detection_id, image_width, image_height, class_name):
69
- """Generate YOLO format annotation string"""
70
- if not results or detection_id >= len(results[0].boxes):
71
- return ""
72
-
73
- # Get class mapping
74
- class_mapping = load_class_mapping()
75
-
76
- # Get class ID from mapping, if not found, add it
77
- class_id = None
78
- for id_str, name in class_mapping.items():
79
- if name == class_name:
80
- class_id = int(id_str)
81
- break
82
-
83
- if class_id is None:
84
- # Add new class to mapping
85
- max_id = max([int(k) for k in class_mapping.keys()]) if class_mapping else -1
86
- class_id = max_id + 1
87
- class_mapping[str(class_id)] = class_name
88
- save_class_mapping(class_mapping)
89
-
90
- # Get bounding box
91
- box = results[0].boxes.xyxy[detection_id]
92
- x1, y1, x2, y2 = box.tolist()
93
-
94
- # Convert to YOLO format (normalized)
95
- x_center = ((x1 + x2) / 2) / image_width
96
- y_center = ((y1 + y2) / 2) / image_height
97
- width = (x2 - x1) / image_width
98
- height = (y2 - y1) / image_height
99
-
100
- confidence = results[0].boxes.conf[detection_id].item()
101
-
102
- # YOLO format: class_id x_center y_center width height confidence
103
- return f"{class_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f} {confidence:.6f}"
104
-
105
- def save_annotated_image(image, results, detection_id, filename):
106
- """Save annotated image with bounding boxes"""
107
- if not results or not results[0].boxes:
108
- return None
109
-
110
- # Create supervision annotators
111
- bounding_box_annotator = sv.BoundingBoxAnnotator()
112
- label_annotator = sv.LabelAnnotator()
113
-
114
- # Convert YOLOv8 results to supervision format
115
- boxes = results[0].boxes.xyxy.cpu().numpy()
116
- confidences = results[0].boxes.conf.cpu().numpy()
117
- class_ids = results[0].boxes.cls.cpu().numpy().astype(int)
118
-
119
- # Get class names
120
- class_names = [results[0].names[class_id] for class_id in class_ids]
121
-
122
- # Create detections
123
- detections = sv.Detections(
124
- xyxy=boxes,
125
- confidence=confidences,
126
- class_id=class_ids
127
- )
128
-
129
- # Annotate image
130
- annotated_image = bounding_box_annotator.annotate(scene=image.copy(), detections=detections)
131
- annotated_image = label_annotator.annotate(
132
- scene=annotated_image,
133
- detections=detections,
134
- labels=[f"{class_names[i]} {confidences[i]:.2f}" for i in range(len(class_names))]
135
- )
136
-
137
- # Save annotated image
138
- annotated_path = os.path.join(ANNOTATED_FOLDER, f"annotated_{filename}")
139
- cv2.imwrite(annotated_path, annotated_image)
140
-
141
- return annotated_path
142
-
143
- def process_file(file_path, direction, task_type):
144
- """Process uploaded file and generate predictions"""
145
- print("File received and saved successfully.")
146
- print(f"Direction received: {direction}")
147
- print(f"Task type received: {task_type}")
148
-
149
- startTime = datetime.now()
150
-
151
- # Load image
152
- image = cv2.imread(file_path)
153
- if image is None:
154
- return None
155
-
156
- # Perform prediction
157
- class_name, results, detection_id = predictor.predict_id(file_path, task_type)
158
- class_id = str(mapping.get(class_name, -1))
159
-
160
- if class_name and results:
161
- # Generate filename
162
- timestamp = int(time.time())
163
- base_filename = f"{class_name}_{timestamp}"
164
-
165
- # Save original image to dataset
166
- image_filename = f"{base_filename}.jpg"
167
- dataset_image_path = os.path.join(IMAGES_FOLDER, image_filename)
168
- shutil.copy2(file_path, dataset_image_path)
169
-
170
- # Generate and save YOLO annotation
171
- h, w = image.shape[:2]
172
- yolo_annotation = generate_yolo_annotation(results, detection_id, w, h, class_name)
173
- if yolo_annotation:
174
- txt_filename = f"{base_filename}.txt"
175
- txt_path = os.path.join(LABELS_FOLDER, txt_filename)
176
- with open(txt_path, 'w') as f:
177
- f.write(yolo_annotation)
178
-
179
- # Save annotated image
180
- save_annotated_image(image, results, detection_id, image_filename)
181
-
182
- endTime = datetime.now()
183
- totalTime = (endTime - startTime).total_seconds()
184
- print(f"Predicted ID: {class_id}")
185
- print(f"Time taken for Predicting Image = {totalTime} s")
186
-
187
- return class_id
188
-
189
- # HTML template for the frontend
190
- HTML_TEMPLATE = """
191
- <!DOCTYPE html>
192
- <html lang="en">
193
- <head>
194
- <meta charset="UTF-8">
195
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
196
- <title>YOLO Image Recognition System</title>
197
- <style>
198
- body {
199
- font-family: Arial, sans-serif;
200
- max-width: 1200px;
201
- margin: 0 auto;
202
- padding: 20px;
203
- background-color: #f5f5f5;
204
- }
205
- .header {
206
- text-align: center;
207
- margin-bottom: 30px;
208
- }
209
- .container {
210
- display: grid;
211
- grid-template-columns: 1fr 1fr;
212
- gap: 20px;
213
- margin-bottom: 30px;
214
- }
215
- .card {
216
- background-color: white;
217
- padding: 20px;
218
- border-radius: 10px;
219
- box-shadow: 0 2px 10px rgba(0,0,0,0.1);
220
- }
221
- .image-display {
222
- text-align: center;
223
- }
224
- .image-display img {
225
- max-width: 100%;
226
- max-height: 400px;
227
- border: 2px solid #ddd;
228
- border-radius: 8px;
229
- }
230
- .status {
231
- padding: 10px;
232
- border-radius: 5px;
233
- margin-bottom: 15px;
234
- font-weight: bold;
235
- }
236
- .status.waiting {
237
- background-color: #fff3cd;
238
- color: #856404;
239
- }
240
- .status.updated {
241
- background-color: #d4edda;
242
- color: #155724;
243
- }
244
- .upload-section {
245
- text-align: center;
246
- }
247
- .upload-form {
248
- display: inline-block;
249
- text-align: left;
250
- }
251
- .form-group {
252
- margin-bottom: 15px;
253
- }
254
- .form-group label {
255
- display: block;
256
- margin-bottom: 5px;
257
- font-weight: bold;
258
- }
259
- .form-group input, .form-group select {
260
- width: 300px;
261
- padding: 8px;
262
- border: 1px solid #ddd;
263
- border-radius: 4px;
264
- }
265
- .btn {
266
- background-color: #007bff;
267
- color: white;
268
- border: none;
269
- padding: 12px 24px;
270
- border-radius: 5px;
271
- font-size: 16px;
272
- cursor: pointer;
273
- transition: background-color 0.3s;
274
- }
275
- .btn:hover {
276
- background-color: #0056b3;
277
- }
278
- .btn:disabled {
279
- background-color: #6c757d;
280
- cursor: not-allowed;
281
- }
282
- .dataset-info {
283
- grid-column: span 2;
284
- }
285
- .stats-grid {
286
- display: grid;
287
- grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
288
- gap: 15px;
289
- margin-top: 15px;
290
- }
291
- .stat-card {
292
- background-color: #f8f9fa;
293
- padding: 15px;
294
- border-radius: 5px;
295
- text-align: center;
296
- }
297
- .stat-number {
298
- font-size: 24px;
299
- font-weight: bold;
300
- color: #007bff;
301
- }
302
- .timestamp {
303
- color: #666;
304
- font-size: 12px;
305
- margin-top: 10px;
306
- }
307
- .class-mapping {
308
- max-height: 200px;
309
- overflow-y: auto;
310
- background-color: #f8f9fa;
311
- padding: 10px;
312
- border-radius: 5px;
313
- font-family: monospace;
314
- font-size: 12px;
315
- }
316
- </style>
317
- </head>
318
- <body>
319
- <div class="header">
320
- <h1>YOLO Image Recognition System</h1>
321
- <p>Real-time image processing with dataset management</p>
322
- </div>
323
-
324
- <div class="container">
325
- <div class="card">
326
- <h3>Latest Recognition Result</h3>
327
- <div id="status" class="status waiting">Waiting for recognition results...</div>
328
- <div class="image-display">
329
- <div id="result" style="display: none;">
330
- <img id="resultImage" />
331
- <div id="timestamp" class="timestamp"></div>
332
- </div>
333
- <div id="noResult" style="color: #666; padding: 40px;">
334
- No recognition results yet
335
- </div>
336
- </div>
337
- </div>
338
-
339
- <div class="card">
340
- <h3>Upload Image for Recognition</h3>
341
- <div class="upload-section">
342
- <form id="uploadForm" class="upload-form" enctype="multipart/form-data">
343
- <div class="form-group">
344
- <label for="file">Select Image:</label>
345
- <input type="file" id="file" name="file" accept="image/*" required>
346
- </div>
347
- <div class="form-group">
348
- <label for="direction">Direction:</label>
349
- <select id="direction" name="direction" required>
350
- <option value="north">North</option>
351
- <option value="south">South</option>
352
- <option value="east">East</option>
353
- <option value="west">West</option>
354
- </select>
355
- </div>
356
- <div class="form-group">
357
- <label for="task_type">Task Type:</label>
358
- <select id="task_type" name="task_type" required>
359
- <option value="TASK_1">Task 1</option>
360
- <option value="TASK_2">Task 2</option>
361
- </select>
362
- </div>
363
- <button type="submit" class="btn">Upload and Predict</button>
364
- </form>
365
- <div id="uploadResult" style="margin-top: 15px;"></div>
366
- </div>
367
- </div>
368
-
369
- <div class="card dataset-info">
370
- <h3>Dataset Information</h3>
371
- <div class="stats-grid">
372
- <div class="stat-card">
373
- <div class="stat-number" id="totalImages">0</div>
374
- <div>Total Images</div>
375
- </div>
376
- <div class="stat-card">
377
- <div class="stat-number" id="totalClasses">0</div>
378
- <div>Total Classes</div>
379
- </div>
380
- <div class="stat-card">
381
- <div class="stat-number" id="annotatedImages">0</div>
382
- <div>Annotated Images</div>
383
- </div>
384
- <div class="stat-card">
385
- <button id="downloadBtn" class="btn" onclick="downloadDataset()">
386
- Download Dataset
387
- </button>
388
- </div>
389
- </div>
390
- <div style="margin-top: 20px;">
391
- <h4>Class Mapping:</h4>
392
- <div id="classMapping" class="class-mapping">Loading...</div>
393
- </div>
394
- <div id="downloadInfo" style="margin-top: 10px; color: #666;"></div>
395
- </div>
396
- </div>
397
-
398
- <script>
399
- let lastImagePath = '';
400
-
401
- // Check for latest results
402
- async function checkLatestResult() {
403
- try {
404
- const response = await fetch('/latest-result');
405
- const data = await response.json();
406
-
407
- if (data.success && data.image_path) {
408
- const newImagePath = data.image_path;
409
-
410
- if (newImagePath !== lastImagePath) {
411
- lastImagePath = newImagePath;
412
-
413
- const resultDiv = document.getElementById('result');
414
- const noResultDiv = document.getElementById('noResult');
415
- const resultImg = document.getElementById('resultImage');
416
- const timestampDiv = document.getElementById('timestamp');
417
- const statusDiv = document.getElementById('status');
418
-
419
- // Update image and timestamp
420
- resultImg.src = '/annotated/' + newImagePath + '?t=' + new Date().getTime();
421
- timestampDiv.textContent = 'Last updated: ' + new Date().toLocaleString();
422
-
423
- // Show result
424
- noResultDiv.style.display = 'none';
425
- resultDiv.style.display = 'block';
426
-
427
- // Update status
428
- statusDiv.className = 'status updated';
429
- statusDiv.textContent = 'New recognition result available';
430
-
431
- setTimeout(() => {
432
- statusDiv.className = 'status waiting';
433
- statusDiv.textContent = 'Waiting for next result...';
434
- }, 3000);
435
- }
436
- }
437
- } catch (error) {
438
- console.error('Failed to check latest result:', error);
439
- }
440
- }
441
-
442
- // Update dataset statistics
443
- async function updateDatasetStats() {
444
- try {
445
- const response = await fetch('/dataset-stats');
446
- const data = await response.json();
447
-
448
- document.getElementById('totalImages').textContent = data.total_images || 0;
449
- document.getElementById('totalClasses').textContent = data.total_classes || 0;
450
- document.getElementById('annotatedImages').textContent = data.annotated_images || 0;
451
-
452
- // Update class mapping
453
- const mappingDiv = document.getElementById('classMapping');
454
- if (data.class_mapping) {
455
- let mappingText = '';
456
- for (const [id, name] of Object.entries(data.class_mapping)) {
457
- mappingText += `${id}: ${name}\\n`;
458
- }
459
- mappingDiv.textContent = mappingText || 'No classes defined yet';
460
- } else {
461
- mappingDiv.textContent = 'No classes defined yet';
462
- }
463
- } catch (error) {
464
- console.error('Failed to update dataset stats:', error);
465
- }
466
- }
467
-
468
- // Handle file upload
469
- document.getElementById('uploadForm').addEventListener('submit', async function(e) {
470
- e.preventDefault();
471
-
472
- const formData = new FormData(this);
473
- const uploadResult = document.getElementById('uploadResult');
474
- const submitBtn = this.querySelector('button[type="submit"]');
475
-
476
- submitBtn.disabled = true;
477
- submitBtn.textContent = 'Processing...';
478
- uploadResult.innerHTML = '<div style="color: #007bff;">Processing image...</div>';
479
-
480
- try {
481
- const response = await fetch('/upload', {
482
- method: 'POST',
483
- body: formData
484
- });
485
-
486
- const result = await response.json();
487
-
488
- if (response.ok) {
489
- uploadResult.innerHTML = `
490
- <div style="color: #28a745;">
491
- <strong>Success!</strong><br>
492
- Predicted ID: ${result.predicted_id}
493
- </div>
494
- `;
495
- // Refresh dataset stats
496
- updateDatasetStats();
497
- } else {
498
- uploadResult.innerHTML = `<div style="color: #dc3545;">Error: ${result.error}</div>`;
499
- }
500
- } catch (error) {
501
- uploadResult.innerHTML = `<div style="color: #dc3545;">Error: ${error.message}</div>`;
502
- } finally {
503
- submitBtn.disabled = false;
504
- submitBtn.textContent = 'Upload and Predict';
505
- }
506
- });
507
-
508
- // Download dataset
509
- async function downloadDataset() {
510
- const downloadBtn = document.getElementById('downloadBtn');
511
- const downloadInfo = document.getElementById('downloadInfo');
512
-
513
- try {
514
- downloadBtn.disabled = true;
515
- downloadBtn.textContent = 'Preparing...';
516
- downloadInfo.textContent = 'Creating ZIP file, please wait...';
517
- downloadInfo.style.color = '#007bff';
518
-
519
- const response = await fetch('/download-dataset');
520
-
521
- if (response.ok) {
522
- const blob = await response.blob();
523
- const url = window.URL.createObjectURL(blob);
524
- const a = document.createElement('a');
525
- a.href = url;
526
- a.download = `yolo_dataset_${new Date().toISOString().slice(0,10)}.zip`;
527
- document.body.appendChild(a);
528
- a.click();
529
- window.URL.revokeObjectURL(url);
530
- document.body.removeChild(a);
531
-
532
- downloadInfo.textContent = 'Dataset downloaded successfully!';
533
- downloadInfo.style.color = '#28a745';
534
- } else {
535
- const errorData = await response.json();
536
- downloadInfo.textContent = 'Error: ' + (errorData.message || 'Failed to download');
537
- downloadInfo.style.color = '#dc3545';
538
- }
539
- } catch (error) {
540
- downloadInfo.textContent = 'Error: ' + error.message;
541
- downloadInfo.style.color = '#dc3545';
542
- } finally {
543
- downloadBtn.disabled = false;
544
- downloadBtn.textContent = 'Download Dataset';
545
- setTimeout(() => { downloadInfo.textContent = ''; }, 5000);
546
- }
547
- }
548
-
549
- // Initialize
550
- checkLatestResult();
551
- updateDatasetStats();
552
-
553
- // Auto-refresh every 3 seconds
554
- setInterval(checkLatestResult, 3000);
555
- setInterval(updateDatasetStats, 10000);
556
- </script>
557
- </body>
558
- </html>
559
- """
560
-
561
- # Routes
562
- @app.route('/')
563
- def index():
564
- """Home page with web interface"""
565
- return render_template_string(HTML_TEMPLATE)
566
-
567
- @app.route('/status', methods=['GET'])
568
- def server_status():
569
- """Health check endpoint"""
570
- return jsonify({'status': 'OK'})
571
-
572
- @app.route('/upload', methods=['POST'])
573
- def upload_file():
574
- """Handle file upload and prediction"""
575
- if 'file' not in request.files:
576
- return jsonify({'error': 'No file part'}), 400
577
-
578
- file = request.files['file']
579
- direction = request.form.get('direction', 'north')
580
- task_type = request.form.get('task_type', 'TASK_1')
581
-
582
- if file.filename == '':
583
- return jsonify({'error': 'No selected file'}), 400
584
-
585
- if file:
586
- filename = os.path.basename(file.filename)
587
- file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
588
- file.save(file_path)
589
-
590
- # Process the file and predict
591
- class_id = process_file(file_path, direction, task_type)
592
-
593
- if class_id is not None:
594
- return jsonify({
595
- 'message': 'File successfully uploaded and processed',
596
- 'predicted_id': class_id,
597
- 'direction': direction,
598
- 'task_type': task_type
599
- }), 200
600
- else:
601
- return jsonify({'error': 'Failed to process image'}), 500
602
-
603
- @app.route('/latest-result')
604
- def get_latest_result():
605
- """Get the latest annotated image"""
606
- if not os.path.exists(ANNOTATED_FOLDER):
607
- return jsonify({"success": False, "message": "Annotated folder not found"})
608
-
609
- # Get all annotated images
610
- annotated_files = []
611
- for filename in os.listdir(ANNOTATED_FOLDER):
612
- if filename.startswith('annotated_') and filename.lower().endswith(('.png', '.jpg', '.jpeg')):
613
- filepath = os.path.join(ANNOTATED_FOLDER, filename)
614
- mtime = os.path.getmtime(filepath)
615
- annotated_files.append((filename, mtime))
616
-
617
- if not annotated_files:
618
- return jsonify({"success": False, "message": "No annotated images found"})
619
-
620
- # Sort by modification time, get latest
621
- annotated_files.sort(key=lambda x: x[1], reverse=True)
622
- latest_file = annotated_files[0][0]
623
-
624
- return jsonify({
625
- "success": True,
626
- "image_path": latest_file,
627
- "timestamp": annotated_files[0][1]
628
- })
629
-
630
- @app.route('/annotated/<filename>')
631
- def serve_annotated_image(filename):
632
- """Serve annotated images"""
633
- return send_from_directory(ANNOTATED_FOLDER, filename)
634
-
635
- @app.route('/dataset-stats')
636
- def get_dataset_stats():
637
- """Get dataset statistics"""
638
- stats = {
639
- 'total_images': 0,
640
- 'total_classes': 0,
641
- 'annotated_images': 0,
642
- 'class_mapping': {}
643
- }
644
-
645
- # Count images
646
- if os.path.exists(IMAGES_FOLDER):
647
- stats['total_images'] = len([f for f in os.listdir(IMAGES_FOLDER) if f.lower().endswith(('.png', '.jpg', '.jpeg'))])
648
-
649
- # Count annotated images
650
- if os.path.exists(ANNOTATED_FOLDER):
651
- stats['annotated_images'] = len([f for f in os.listdir(ANNOTATED_FOLDER) if f.lower().endswith(('.png', '.jpg', '.jpeg'))])
652
-
653
- # Load class mapping
654
- stats['class_mapping'] = load_class_mapping()
655
- stats['total_classes'] = len(stats['class_mapping'])
656
-
657
- return jsonify(stats)
658
-
659
- @app.route('/download-dataset')
660
- def download_dataset():
661
- """Download the complete YOLO dataset as ZIP"""
662
- if not os.path.exists(DATASET_FOLDER):
663
- return jsonify({"success": False, "message": "Dataset folder not found"}), 404
664
-
665
- # Check if there are files to download
666
- has_files = False
667
- for folder in [IMAGES_FOLDER, LABELS_FOLDER, ANNOTATED_FOLDER]:
668
- if os.path.exists(folder) and os.listdir(folder):
669
- has_files = True
670
- break
671
-
672
- if not has_files:
673
- return jsonify({"success": False, "message": "No files found in dataset"}), 404
674
-
675
- # Create timestamp for filename
676
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
677
- zip_filename = f"yolo_dataset_{timestamp}.zip"
678
- zip_path = os.path.join(UPLOAD_FOLDER, zip_filename)
679
-
680
- try:
681
- with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
682
- # Add all files from dataset structure
683
- for root, dirs, files in os.walk(DATASET_FOLDER):
684
- for file in files:
685
- file_path = os.path.join(root, file)
686
- arcname = os.path.relpath(file_path, DATASET_FOLDER)
687
- zipf.write(file_path, arcname)
688
-
689
- return send_file(zip_path, as_attachment=True, download_name=zip_filename)
690
-
691
- except Exception as e:
692
- return jsonify({"success": False, "message": f"Error creating zip: {str(e)}"}), 500
693
-
694
- finally:
695
- # Clean up temporary file
696
- try:
697
- if os.path.exists(zip_path):
698
- os.remove(zip_path)
699
- except:
700
- pass
701
-
702
- @app.route('/display_stitched', methods=['POST'])
703
- def display_stitched():
704
- """Display stitched images"""
705
- try:
706
- showAnnotatedStitched()
707
- return jsonify({'display_stitched': 'OK'})
708
- except Exception as e:
709
- return jsonify({'error': str(e)}), 500
710
-
711
- if __name__ == '__main__':
712
- print()
713
- print(f"UPLOAD FOLDER: {UPLOAD_FOLDER}")
714
- print(f"DATASET FOLDER: {DATASET_FOLDER}")
715
- print(f"Starting Enhanced Image Recognition Server...")
716
- print(f"Web interface available at: http://{HOST}:{PORT}")
717
-
718
- try:
719
- app.run(host=HOST, port=PORT, debug=False)
720
- except:
721
- print('Unable to connect to configured host and port. Switching to localhost:4000.')
722
- app.run(host='0.0.0.0', port=4000, debug=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, request, jsonify, render_template_string, send_from_directory, send_file
2
+ from flask_cors import CORS
3
+ import os
4
+ import importlib.util
5
+ import time
6
+ import zipfile
7
+ import shutil
8
+ import json
9
+ from datetime import datetime
10
+ from predict_task1 import Predictor
11
+ from id_mapping import mapping
12
+ from show_stitched import *
13
+ import cv2
14
+ import supervision as sv
15
+ from ultralytics import YOLO
16
+
17
+ app = Flask(__name__)
18
+ CORS(app)
19
+
20
+ # Load configuration
21
+ config_dir = os.path.abspath(os.path.dirname(__file__))
22
+ config_path = os.path.join(config_dir, 'PC_CONFIG.py')
23
+ spec = importlib.util.spec_from_file_location("PC_CONFIG", config_path)
24
+ PC_CONFIG = importlib.util.module_from_spec(spec)
25
+ spec.loader.exec_module(PC_CONFIG)
26
+
27
+ HOST = PC_CONFIG.HOST
28
+ PORT = 7860 # Changed from PC_CONFIG.IMAGE_REC_PORT to 7860
29
+ UPLOAD_FOLDER = os.path.join(PC_CONFIG.FILE_DIRECTORY, "image-rec", "images")
30
+ DATASET_FOLDER = os.path.join(PC_CONFIG.BASE_DIR, "yolo_dataset")
31
+ ANNOTATED_FOLDER = os.path.join(DATASET_FOLDER, "annotated_images")
32
+ LABELS_FOLDER = os.path.join(DATASET_FOLDER, "labels")
33
+ IMAGES_FOLDER = os.path.join(DATASET_FOLDER, "images")
34
+ CLASS_MAPPING_FILE = os.path.join(DATASET_FOLDER, "classes.json")
35
+
36
+ app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
37
+
38
+ # Initialize predictor
39
+ predictor = Predictor()
40
+
41
+ # Ensure directories exist
42
+ os.makedirs(UPLOAD_FOLDER, exist_ok=True)
43
+ os.makedirs(DATASET_FOLDER, exist_ok=True)
44
+ os.makedirs(ANNOTATED_FOLDER, exist_ok=True)
45
+ os.makedirs(LABELS_FOLDER, exist_ok=True)
46
+ os.makedirs(IMAGES_FOLDER, exist_ok=True)
47
+
48
+ # Initialize class mapping file
49
+ if not os.path.exists(CLASS_MAPPING_FILE):
50
+ # Create initial class mapping from id_mapping.py
51
+ reverse_mapping = {str(v): k for k, v in mapping.items() if v != -1 and k is not None}
52
+ with open(CLASS_MAPPING_FILE, 'w', encoding='utf-8') as f:
53
+ json.dump(reverse_mapping, f, indent=2, ensure_ascii=False)
54
+
55
+ def load_class_mapping():
56
+ """Load class mapping from JSON file"""
57
+ try:
58
+ with open(CLASS_MAPPING_FILE, 'r', encoding='utf-8') as f:
59
+ return json.load(f)
60
+ except:
61
+ return {}
62
+
63
+ def save_class_mapping(class_mapping):
64
+ """Save class mapping to JSON file"""
65
+ with open(CLASS_MAPPING_FILE, 'w', encoding='utf-8') as f:
66
+ json.dump(class_mapping, f, indent=2, ensure_ascii=False)
67
+
68
+ def generate_yolo_annotation(results, detection_id, image_width, image_height, class_name):
69
+ """Generate YOLO format annotation string"""
70
+ if not results or detection_id >= len(results[0].boxes):
71
+ return ""
72
+
73
+ # Get class mapping
74
+ class_mapping = load_class_mapping()
75
+
76
+ # Get class ID from mapping, if not found, add it
77
+ class_id = None
78
+ for id_str, name in class_mapping.items():
79
+ if name == class_name:
80
+ class_id = int(id_str)
81
+ break
82
+
83
+ if class_id is None:
84
+ # Add new class to mapping
85
+ max_id = max([int(k) for k in class_mapping.keys()]) if class_mapping else -1
86
+ class_id = max_id + 1
87
+ class_mapping[str(class_id)] = class_name
88
+ save_class_mapping(class_mapping)
89
+
90
+ # Get bounding box
91
+ box = results[0].boxes.xyxy[detection_id]
92
+ x1, y1, x2, y2 = box.tolist()
93
+
94
+ # Convert to YOLO format (normalized)
95
+ x_center = ((x1 + x2) / 2) / image_width
96
+ y_center = ((y1 + y2) / 2) / image_height
97
+ width = (x2 - x1) / image_width
98
+ height = (y2 - y1) / image_height
99
+
100
+ confidence = results[0].boxes.conf[detection_id].item()
101
+
102
+ # YOLO format: class_id x_center y_center width height confidence
103
+ return f"{class_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f} {confidence:.6f}"
104
+
105
+ def save_annotated_image(image, results, detection_id, filename):
106
+ """Save annotated image with bounding boxes"""
107
+ if not results or not results[0].boxes:
108
+ return None
109
+
110
+ # Create supervision annotators
111
+ bounding_box_annotator = sv.BoundingBoxAnnotator()
112
+ label_annotator = sv.LabelAnnotator()
113
+
114
+ # Convert YOLOv8 results to supervision format
115
+ boxes = results[0].boxes.xyxy.cpu().numpy()
116
+ confidences = results[0].boxes.conf.cpu().numpy()
117
+ class_ids = results[0].boxes.cls.cpu().numpy().astype(int)
118
+
119
+ # Get class names
120
+ class_names = [results[0].names[class_id] for class_id in class_ids]
121
+
122
+ # Create detections
123
+ detections = sv.Detections(
124
+ xyxy=boxes,
125
+ confidence=confidences,
126
+ class_id=class_ids
127
+ )
128
+
129
+ # Annotate image
130
+ annotated_image = bounding_box_annotator.annotate(scene=image.copy(), detections=detections)
131
+ annotated_image = label_annotator.annotate(
132
+ scene=annotated_image,
133
+ detections=detections,
134
+ labels=[f"{class_names[i]} {confidences[i]:.2f}" for i in range(len(class_names))]
135
+ )
136
+
137
+ # Save annotated image
138
+ annotated_path = os.path.join(ANNOTATED_FOLDER, f"annotated_{filename}")
139
+ cv2.imwrite(annotated_path, annotated_image)
140
+
141
+ return annotated_path
142
+
143
+ def process_file(file_path, direction, task_type, filename):
144
+ """Process uploaded file and generate predictions"""
145
+ print("File received and saved successfully.")
146
+ print(f"Direction received: {direction}")
147
+ print(f"Task type received: {task_type}")
148
+
149
+ startTime = datetime.now()
150
+
151
+ # Load image
152
+ image = cv2.imread(file_path)
153
+ if image is None:
154
+ return None
155
+
156
+ # Perform prediction
157
+ class_name, results, detection_id = predictor.predict_id(file_path, task_type)
158
+ class_id = str(mapping.get(class_name, -1))
159
+
160
+ detection_result = None
161
+ if class_name and results:
162
+ # Generate filename
163
+ timestamp = int(time.time())
164
+ base_filename = f"{class_name}_{timestamp}"
165
+
166
+ # Save original image to dataset
167
+ image_filename = f"{base_filename}.jpg"
168
+ dataset_image_path = os.path.join(IMAGES_FOLDER, image_filename)
169
+ shutil.copy2(file_path, dataset_image_path)
170
+
171
+ # Generate and save YOLO annotation
172
+ h, w = image.shape[:2]
173
+ yolo_annotation = generate_yolo_annotation(results, detection_id, w, h, class_name)
174
+ txt_path = None
175
+ if yolo_annotation:
176
+ txt_filename = f"{base_filename}.txt"
177
+ txt_path = os.path.join(LABELS_FOLDER, txt_filename)
178
+ with open(txt_path, 'w') as f:
179
+ f.write(yolo_annotation)
180
+
181
+ # Save annotated image
182
+ annotated_path = save_annotated_image(image, results, detection_id, image_filename)
183
+
184
+ # Get bounding box and confidence for compatibility
185
+ box = results[0].boxes.xyxy[detection_id]
186
+ confidence = results[0].boxes.conf[detection_id].item()
187
+ x1, y1, x2, y2 = box.tolist()
188
+
189
+ # Create detection result in compatible format
190
+ detection_result = {
191
+ "image_id": class_id,
192
+ "label": class_name,
193
+ "confidence": confidence,
194
+ "bbox": [x1, y1, x2, y2],
195
+ "original_image_path": dataset_image_path,
196
+ "marked_image_path": annotated_path,
197
+ "txt_file_path": txt_path
198
+ }
199
+
200
+ endTime = datetime.now()
201
+ totalTime = (endTime - startTime).total_seconds()
202
+ print(f"Predicted ID: {class_id}")
203
+ print(f"Time taken for Predicting Image = {totalTime} s")
204
+
205
+ return class_id, detection_result
206
+
207
+ # HTML template for the frontend
208
+ HTML_TEMPLATE = """
209
+ <!DOCTYPE html>
210
+ <html lang="en">
211
+ <head>
212
+ <meta charset="UTF-8">
213
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
214
+ <title>YOLO Image Recognition System</title>
215
+ <style>
216
+ body {
217
+ font-family: Arial, sans-serif;
218
+ max-width: 1200px;
219
+ margin: 0 auto;
220
+ padding: 20px;
221
+ background-color: #f5f5f5;
222
+ }
223
+ .header {
224
+ text-align: center;
225
+ margin-bottom: 30px;
226
+ }
227
+ .container {
228
+ display: grid;
229
+ grid-template-columns: 1fr 1fr;
230
+ gap: 20px;
231
+ margin-bottom: 30px;
232
+ }
233
+ .card {
234
+ background-color: white;
235
+ padding: 20px;
236
+ border-radius: 10px;
237
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
238
+ }
239
+ .image-display {
240
+ text-align: center;
241
+ }
242
+ .image-display img {
243
+ max-width: 100%;
244
+ max-height: 400px;
245
+ border: 2px solid #ddd;
246
+ border-radius: 8px;
247
+ }
248
+ .status {
249
+ padding: 10px;
250
+ border-radius: 5px;
251
+ margin-bottom: 15px;
252
+ font-weight: bold;
253
+ }
254
+ .status.waiting {
255
+ background-color: #fff3cd;
256
+ color: #856404;
257
+ }
258
+ .status.updated {
259
+ background-color: #d4edda;
260
+ color: #155724;
261
+ }
262
+ .upload-section {
263
+ text-align: center;
264
+ }
265
+ .upload-form {
266
+ display: inline-block;
267
+ text-align: left;
268
+ }
269
+ .form-group {
270
+ margin-bottom: 15px;
271
+ }
272
+ .form-group label {
273
+ display: block;
274
+ margin-bottom: 5px;
275
+ font-weight: bold;
276
+ }
277
+ .form-group input, .form-group select {
278
+ width: 300px;
279
+ padding: 8px;
280
+ border: 1px solid #ddd;
281
+ border-radius: 4px;
282
+ }
283
+ .btn {
284
+ background-color: #007bff;
285
+ color: white;
286
+ border: none;
287
+ padding: 12px 24px;
288
+ border-radius: 5px;
289
+ font-size: 16px;
290
+ cursor: pointer;
291
+ transition: background-color 0.3s;
292
+ }
293
+ .btn:hover {
294
+ background-color: #0056b3;
295
+ }
296
+ .btn:disabled {
297
+ background-color: #6c757d;
298
+ cursor: not-allowed;
299
+ }
300
+ .dataset-info {
301
+ grid-column: span 2;
302
+ }
303
+ .stats-grid {
304
+ display: grid;
305
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
306
+ gap: 15px;
307
+ margin-top: 15px;
308
+ }
309
+ .stat-card {
310
+ background-color: #f8f9fa;
311
+ padding: 15px;
312
+ border-radius: 5px;
313
+ text-align: center;
314
+ }
315
+ .stat-number {
316
+ font-size: 24px;
317
+ font-weight: bold;
318
+ color: #007bff;
319
+ }
320
+ .timestamp {
321
+ color: #666;
322
+ font-size: 12px;
323
+ margin-top: 10px;
324
+ }
325
+ .class-mapping {
326
+ max-height: 200px;
327
+ overflow-y: auto;
328
+ background-color: #f8f9fa;
329
+ padding: 10px;
330
+ border-radius: 5px;
331
+ font-family: monospace;
332
+ font-size: 12px;
333
+ }
334
+ </style>
335
+ </head>
336
+ <body>
337
+ <div class="header">
338
+ <h1>YOLO Image Recognition System</h1>
339
+ <p>Real-time image processing with dataset management</p>
340
+ </div>
341
+
342
+ <div class="container">
343
+ <div class="card">
344
+ <h3>Latest Recognition Result</h3>
345
+ <div id="status" class="status waiting">Waiting for recognition results...</div>
346
+ <div class="image-display">
347
+ <div id="result" style="display: none;">
348
+ <img id="resultImage" />
349
+ <div id="timestamp" class="timestamp"></div>
350
+ </div>
351
+ <div id="noResult" style="color: #666; padding: 40px;">
352
+ No recognition results yet
353
+ </div>
354
+ </div>
355
+ </div>
356
+
357
+ <div class="card">
358
+ <h3>Upload Image for Recognition</h3>
359
+ <div class="upload-section">
360
+ <form id="uploadForm" class="upload-form" enctype="multipart/form-data">
361
+ <div class="form-group">
362
+ <label for="file">Select Image:</label>
363
+ <input type="file" id="file" name="file" accept="image/*" required>
364
+ </div>
365
+ <div class="form-group">
366
+ <label for="direction">Direction:</label>
367
+ <select id="direction" name="direction" required>
368
+ <option value="north">North</option>
369
+ <option value="south">South</option>
370
+ <option value="east">East</option>
371
+ <option value="west">West</option>
372
+ </select>
373
+ </div>
374
+ <div class="form-group">
375
+ <label for="task_type">Task Type:</label>
376
+ <select id="task_type" name="task_type" required>
377
+ <option value="TASK_1">Task 1</option>
378
+ <option value="TASK_2">Task 2</option>
379
+ </select>
380
+ </div>
381
+ <button type="submit" class="btn">Upload and Predict</button>
382
+ </form>
383
+ <div id="uploadResult" style="margin-top: 15px;"></div>
384
+ </div>
385
+ </div>
386
+
387
+ <div class="card dataset-info">
388
+ <h3>Dataset Information</h3>
389
+ <div class="stats-grid">
390
+ <div class="stat-card">
391
+ <div class="stat-number" id="totalImages">0</div>
392
+ <div>Total Images</div>
393
+ </div>
394
+ <div class="stat-card">
395
+ <div class="stat-number" id="totalClasses">0</div>
396
+ <div>Total Classes</div>
397
+ </div>
398
+ <div class="stat-card">
399
+ <div class="stat-number" id="annotatedImages">0</div>
400
+ <div>Annotated Images</div>
401
+ </div>
402
+ <div class="stat-card">
403
+ <button id="downloadBtn" class="btn" onclick="downloadDataset()">
404
+ Download Dataset
405
+ </button>
406
+ </div>
407
+ </div>
408
+ <div style="margin-top: 20px;">
409
+ <h4>Class Mapping:</h4>
410
+ <div id="classMapping" class="class-mapping">Loading...</div>
411
+ </div>
412
+ <div id="downloadInfo" style="margin-top: 10px; color: #666;"></div>
413
+ </div>
414
+ </div>
415
+
416
+ <script>
417
+ let lastImagePath = '';
418
+
419
+ // Check for latest results
420
+ async function checkLatestResult() {
421
+ try {
422
+ const response = await fetch('/latest-result');
423
+ const data = await response.json();
424
+
425
+ if (data.success && data.image_path) {
426
+ const newImagePath = data.image_path;
427
+
428
+ if (newImagePath !== lastImagePath) {
429
+ lastImagePath = newImagePath;
430
+
431
+ const resultDiv = document.getElementById('result');
432
+ const noResultDiv = document.getElementById('noResult');
433
+ const resultImg = document.getElementById('resultImage');
434
+ const timestampDiv = document.getElementById('timestamp');
435
+ const statusDiv = document.getElementById('status');
436
+
437
+ // Update image and timestamp
438
+ resultImg.src = '/annotated/' + newImagePath + '?t=' + new Date().getTime();
439
+ timestampDiv.textContent = 'Last updated: ' + new Date().toLocaleString();
440
+
441
+ // Show result
442
+ noResultDiv.style.display = 'none';
443
+ resultDiv.style.display = 'block';
444
+
445
+ // Update status
446
+ statusDiv.className = 'status updated';
447
+ statusDiv.textContent = 'New recognition result available';
448
+
449
+ setTimeout(() => {
450
+ statusDiv.className = 'status waiting';
451
+ statusDiv.textContent = 'Waiting for next result...';
452
+ }, 3000);
453
+ }
454
+ }
455
+ } catch (error) {
456
+ console.error('Failed to check latest result:', error);
457
+ }
458
+ }
459
+
460
+ // Update dataset statistics
461
+ async function updateDatasetStats() {
462
+ try {
463
+ const response = await fetch('/dataset-stats');
464
+ const data = await response.json();
465
+
466
+ document.getElementById('totalImages').textContent = data.total_images || 0;
467
+ document.getElementById('totalClasses').textContent = data.total_classes || 0;
468
+ document.getElementById('annotatedImages').textContent = data.annotated_images || 0;
469
+
470
+ // Update class mapping
471
+ const mappingDiv = document.getElementById('classMapping');
472
+ if (data.class_mapping) {
473
+ let mappingText = '';
474
+ for (const [id, name] of Object.entries(data.class_mapping)) {
475
+ mappingText += `${id}: ${name}\\n`;
476
+ }
477
+ mappingDiv.textContent = mappingText || 'No classes defined yet';
478
+ } else {
479
+ mappingDiv.textContent = 'No classes defined yet';
480
+ }
481
+ } catch (error) {
482
+ console.error('Failed to update dataset stats:', error);
483
+ }
484
+ }
485
+
486
+ // Handle file upload
487
+ document.getElementById('uploadForm').addEventListener('submit', async function(e) {
488
+ e.preventDefault();
489
+
490
+ const formData = new FormData(this);
491
+ const uploadResult = document.getElementById('uploadResult');
492
+ const submitBtn = this.querySelector('button[type="submit"]');
493
+
494
+ submitBtn.disabled = true;
495
+ submitBtn.textContent = 'Processing...';
496
+ uploadResult.innerHTML = '<div style="color: #007bff;">Processing image...</div>';
497
+
498
+ try {
499
+ const response = await fetch('/image', {
500
+ method: 'POST',
501
+ body: formData
502
+ });
503
+
504
+ const result = await response.json();
505
+
506
+ if (response.ok) {
507
+ // Handle both legacy format (predicted_id) and new format (result.image_id)
508
+ const predictedId = result.predicted_id || result.image_id || result.result?.image_id;
509
+ const obstacleId = result.obstacle_id || result.result?.obstacle_id;
510
+
511
+ uploadResult.innerHTML = `
512
+ <div style="color: #28a745;">
513
+ <strong>Success!</strong><br>
514
+ ${obstacleId ? `Obstacle ID: ${obstacleId}<br>` : ''}
515
+ Predicted ID: ${predictedId}
516
+ </div>
517
+ `;
518
+ // Refresh dataset stats
519
+ updateDatasetStats();
520
+ } else {
521
+ uploadResult.innerHTML = `<div style="color: #dc3545;">Error: ${result.error}</div>`;
522
+ }
523
+ } catch (error) {
524
+ uploadResult.innerHTML = `<div style="color: #dc3545;">Error: ${error.message}</div>`;
525
+ } finally {
526
+ submitBtn.disabled = false;
527
+ submitBtn.textContent = 'Upload and Predict';
528
+ }
529
+ });
530
+
531
+ // Download dataset
532
+ async function downloadDataset() {
533
+ const downloadBtn = document.getElementById('downloadBtn');
534
+ const downloadInfo = document.getElementById('downloadInfo');
535
+
536
+ try {
537
+ downloadBtn.disabled = true;
538
+ downloadBtn.textContent = 'Preparing...';
539
+ downloadInfo.textContent = 'Creating ZIP file, please wait...';
540
+ downloadInfo.style.color = '#007bff';
541
+
542
+ const response = await fetch('/download-dataset');
543
+
544
+ if (response.ok) {
545
+ const blob = await response.blob();
546
+ const url = window.URL.createObjectURL(blob);
547
+ const a = document.createElement('a');
548
+ a.href = url;
549
+ a.download = `yolo_dataset_${new Date().toISOString().slice(0,10)}.zip`;
550
+ document.body.appendChild(a);
551
+ a.click();
552
+ window.URL.revokeObjectURL(url);
553
+ document.body.removeChild(a);
554
+
555
+ downloadInfo.textContent = 'Dataset downloaded successfully!';
556
+ downloadInfo.style.color = '#28a745';
557
+ } else {
558
+ const errorData = await response.json();
559
+ downloadInfo.textContent = 'Error: ' + (errorData.message || 'Failed to download');
560
+ downloadInfo.style.color = '#dc3545';
561
+ }
562
+ } catch (error) {
563
+ downloadInfo.textContent = 'Error: ' + error.message;
564
+ downloadInfo.style.color = '#dc3545';
565
+ } finally {
566
+ downloadBtn.disabled = false;
567
+ downloadBtn.textContent = 'Download Dataset';
568
+ setTimeout(() => { downloadInfo.textContent = ''; }, 5000);
569
+ }
570
+ }
571
+
572
+ // Initialize
573
+ checkLatestResult();
574
+ updateDatasetStats();
575
+
576
+ // Auto-refresh every 3 seconds
577
+ setInterval(checkLatestResult, 3000);
578
+ setInterval(updateDatasetStats, 10000);
579
+ </script>
580
+ </body>
581
+ </html>
582
+ """
583
+
584
+ # Routes
585
+ @app.route('/')
586
+ def index():
587
+ """Home page with web interface"""
588
+ return render_template_string(HTML_TEMPLATE)
589
+
590
+ @app.route('/status', methods=['GET'])
591
+ def server_status():
592
+ """Health check endpoint"""
593
+ return jsonify({'status': 'OK'})
594
+
595
+ @app.route('/upload', methods=['POST'])
596
+ def upload_file():
597
+ """Handle file upload and prediction (legacy endpoint)"""
598
+ if 'file' not in request.files:
599
+ return jsonify({'error': 'No file part'}), 400
600
+
601
+ file = request.files['file']
602
+ direction = request.form.get('direction', 'north')
603
+ task_type = request.form.get('task_type', 'TASK_1')
604
+
605
+ if file.filename == '':
606
+ return jsonify({'error': 'No selected file'}), 400
607
+
608
+ if file:
609
+ filename = os.path.basename(file.filename)
610
+ file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
611
+ file.save(file_path)
612
+
613
+ # Process the file and predict
614
+ class_id, detection_result = process_file(file_path, direction, task_type, filename)
615
+
616
+ if class_id is not None:
617
+ return jsonify({
618
+ 'message': 'File successfully uploaded and processed',
619
+ 'predicted_id': class_id,
620
+ 'direction': direction,
621
+ 'task_type': task_type
622
+ }), 200
623
+ else:
624
+ return jsonify({'error': 'Failed to process image'}), 500
625
+
626
+ @app.route('/image', methods=['POST'])
627
+ def image_predict():
628
+ """
629
+ This is the main endpoint for the image prediction algorithm
630
+ :return: a json object with a key "result" and value a dictionary with keys "obstacle_id" and "image_id"
631
+ """
632
+ if 'file' not in request.files:
633
+ return jsonify({'error': 'No file part'}), 400
634
+
635
+ file = request.files['file']
636
+ filename = file.filename
637
+
638
+ if filename == '':
639
+ return jsonify({'error': 'No selected file'}), 400
640
+
641
+ # Save to uploads folder first
642
+ file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
643
+ file.save(file_path)
644
+
645
+ # Try to parse filename format: "<timestamp>_<obstacle_id>_<signal>.jpeg"
646
+ # But be flexible with different formats
647
+ constituents = file.filename.split("_")
648
+
649
+ # Default values
650
+ obstacle_id = "unknown"
651
+ signal = "C" # Default to center
652
+
653
+ # Try to extract obstacle_id and signal if available
654
+ try:
655
+ if len(constituents) >= 2:
656
+ obstacle_id = constituents[1]
657
+ if len(constituents) >= 3:
658
+ # Remove file extension from signal
659
+ signal_part = constituents[2]
660
+ # Handle both .jpg and .png extensions
661
+ for ext in ['.jpg', '.jpeg', '.png', '.JPG', '.JPEG', '.PNG']:
662
+ if signal_part.endswith(ext):
663
+ signal = signal_part[:-len(ext)]
664
+ break
665
+ else:
666
+ signal = signal_part
667
+ except IndexError:
668
+ # Use default values if parsing fails
669
+ pass
670
+
671
+ # Check for optional preference parameter
672
+ prefer_close = request.form.get('prefer_close_objects', 'true').lower() == 'true'
673
+ task_type = request.form.get('task_type', 'TASK_1')
674
+
675
+ # Process the file and predict
676
+ class_id, detection_result = process_file(file_path, signal, task_type, filename)
677
+
678
+ if detection_result is None:
679
+ return jsonify({'error': 'Failed to process image'}), 500
680
+
681
+ # Extract image_id from detection result
682
+ image_id = detection_result["image_id"]
683
+
684
+ print(f"Original image saved to: {detection_result.get('original_image_path', 'N/A')}")
685
+ print(f"Annotated image saved to: {detection_result['marked_image_path']}")
686
+ print(f"YOLO txt file saved to: {detection_result.get('txt_file_path', 'N/A')}")
687
+
688
+ # Return detailed detection information in compatible format
689
+ result = {
690
+ "obstacle_id": obstacle_id,
691
+ "image_id": image_id,
692
+ "detection": {
693
+ "label": detection_result["label"],
694
+ "confidence": detection_result["confidence"],
695
+ "bbox_coordinates": detection_result["bbox"],
696
+ "original_image_path": detection_result.get("original_image_path"),
697
+ "annotated_image_path": detection_result["marked_image_path"],
698
+ "txt_file_path": detection_result.get("txt_file_path")
699
+ }
700
+ }
701
+ return jsonify(result)
702
+
703
+ @app.route('/latest-result')
704
+ def get_latest_result():
705
+ """Get the latest annotated image"""
706
+ if not os.path.exists(ANNOTATED_FOLDER):
707
+ return jsonify({"success": False, "message": "Annotated folder not found"})
708
+
709
+ # Get all annotated images
710
+ annotated_files = []
711
+ for filename in os.listdir(ANNOTATED_FOLDER):
712
+ if filename.startswith('annotated_') and filename.lower().endswith(('.png', '.jpg', '.jpeg')):
713
+ filepath = os.path.join(ANNOTATED_FOLDER, filename)
714
+ mtime = os.path.getmtime(filepath)
715
+ annotated_files.append((filename, mtime))
716
+
717
+ if not annotated_files:
718
+ return jsonify({"success": False, "message": "No annotated images found"})
719
+
720
+ # Sort by modification time, get latest
721
+ annotated_files.sort(key=lambda x: x[1], reverse=True)
722
+ latest_file = annotated_files[0][0]
723
+
724
+ return jsonify({
725
+ "success": True,
726
+ "image_path": latest_file,
727
+ "timestamp": annotated_files[0][1]
728
+ })
729
+
730
+ @app.route('/annotated/<filename>')
731
+ def serve_annotated_image(filename):
732
+ """Serve annotated images"""
733
+ return send_from_directory(ANNOTATED_FOLDER, filename)
734
+
735
+ @app.route('/dataset-stats')
736
+ def get_dataset_stats():
737
+ """Get dataset statistics"""
738
+ stats = {
739
+ 'total_images': 0,
740
+ 'total_classes': 0,
741
+ 'annotated_images': 0,
742
+ 'class_mapping': {}
743
+ }
744
+
745
+ # Count images
746
+ if os.path.exists(IMAGES_FOLDER):
747
+ stats['total_images'] = len([f for f in os.listdir(IMAGES_FOLDER) if f.lower().endswith(('.png', '.jpg', '.jpeg'))])
748
+
749
+ # Count annotated images
750
+ if os.path.exists(ANNOTATED_FOLDER):
751
+ stats['annotated_images'] = len([f for f in os.listdir(ANNOTATED_FOLDER) if f.lower().endswith(('.png', '.jpg', '.jpeg'))])
752
+
753
+ # Load class mapping
754
+ stats['class_mapping'] = load_class_mapping()
755
+ stats['total_classes'] = len(stats['class_mapping'])
756
+
757
+ return jsonify(stats)
758
+
759
+ @app.route('/download-dataset')
760
+ def download_dataset():
761
+ """Download the complete YOLO dataset as ZIP"""
762
+ if not os.path.exists(DATASET_FOLDER):
763
+ return jsonify({"success": False, "message": "Dataset folder not found"}), 404
764
+
765
+ # Check if there are files to download
766
+ has_files = False
767
+ for folder in [IMAGES_FOLDER, LABELS_FOLDER, ANNOTATED_FOLDER]:
768
+ if os.path.exists(folder) and os.listdir(folder):
769
+ has_files = True
770
+ break
771
+
772
+ if not has_files:
773
+ return jsonify({"success": False, "message": "No files found in dataset"}), 404
774
+
775
+ # Create timestamp for filename
776
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
777
+ zip_filename = f"yolo_dataset_{timestamp}.zip"
778
+ zip_path = os.path.join(UPLOAD_FOLDER, zip_filename)
779
+
780
+ try:
781
+ with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
782
+ # Add all files from dataset structure
783
+ for root, dirs, files in os.walk(DATASET_FOLDER):
784
+ for file in files:
785
+ file_path = os.path.join(root, file)
786
+ arcname = os.path.relpath(file_path, DATASET_FOLDER)
787
+ zipf.write(file_path, arcname)
788
+
789
+ return send_file(zip_path, as_attachment=True, download_name=zip_filename)
790
+
791
+ except Exception as e:
792
+ return jsonify({"success": False, "message": f"Error creating zip: {str(e)}"}), 500
793
+
794
+ finally:
795
+ # Clean up temporary file
796
+ try:
797
+ if os.path.exists(zip_path):
798
+ os.remove(zip_path)
799
+ except:
800
+ pass
801
+
802
+ @app.route('/display_stitched', methods=['POST'])
803
+ def display_stitched():
804
+ """Display stitched images"""
805
+ try:
806
+ showAnnotatedStitched()
807
+ return jsonify({'display_stitched': 'OK'})
808
+ except Exception as e:
809
+ return jsonify({'error': str(e)}), 500
810
+
811
+ if __name__ == '__main__':
812
+ print()
813
+ print(f"UPLOAD FOLDER: {UPLOAD_FOLDER}")
814
+ print(f"DATASET FOLDER: {DATASET_FOLDER}")
815
+ print(f"Starting Enhanced Image Recognition Server...")
816
+ print(f"Web interface available at: http://{HOST}:{PORT}")
817
+ print(f"API endpoints: /image (main), /upload (legacy)")
818
+
819
+ try:
820
+ app.run(host=HOST, port=PORT, debug=False)
821
+ except:
822
+ print('Unable to connect to configured host and port. Switching to localhost:7860.')
823
+ app.run(host='0.0.0.0', port=7860, debug=True)