Compare commits
2 Commits
b54da61121
...
bdc171b009
| Author | SHA1 | Date | |
|---|---|---|---|
| bdc171b009 | |||
| 7b9de2b833 |
@@ -49,7 +49,8 @@ python3 test_api.py
|
||||
|
||||
```
|
||||
ds_task_recycling_project/
|
||||
├── main.py # Flask API application
|
||||
├── main.py # Flask API application (main interface)
|
||||
├── api_docs.py # Swagger UI API documentation (developer only)
|
||||
├── train.py # YOLOv8 training script
|
||||
├── inference_utils.py # Detection and visualization utilities
|
||||
├── prepare_dataset.py # Dataset preparation script
|
||||
@@ -57,24 +58,58 @@ ds_task_recycling_project/
|
||||
├── setup.py # Automated setup script
|
||||
├── requirements.txt # Python dependencies
|
||||
├── dataset.yaml # YOLO dataset configuration
|
||||
├── .gitignore # Git ignore file for ML projects
|
||||
├── VALIDATION_CHECKLIST.md # Project validation checklist
|
||||
├── templates/ # Frontend templates
|
||||
│ └── index.html # QA testing web interface
|
||||
├── static/ # Frontend assets
|
||||
│ ├── style.css # Styling for web interface
|
||||
│ └── script.js # JavaScript for web interface
|
||||
├── venv/ # Virtual environment (created by user)
|
||||
├── training/ # Dataset directory
|
||||
│ ├── memory/ # Images with memory modules + labels
|
||||
│ ├── memory/ # Images with memory modules + YOLO labels
|
||||
│ │ ├── out1.png # Sample motherboard image with memory
|
||||
│ │ ├── out1.txt # YOLO format annotation file
|
||||
│ │ └── ... # 19 more image/label pairs
|
||||
│ ├── no_memory/ # Images without memory modules
|
||||
│ ├── train/ # Training split (80%)
|
||||
│ └── val/ # Validation split (20%)
|
||||
│ │ ├── out21.png # Sample motherboard image without memory
|
||||
│ │ └── ... # 19 more images (no labels needed)
|
||||
│ ├── train/ # Training split (80% = 32 images)
|
||||
│ │ ├── images/ # Training images
|
||||
│ │ └── labels/ # Training labels
|
||||
│ └── val/ # Validation split (20% = 8 images)
|
||||
│ ├── images/ # Validation images
|
||||
│ └── labels/ # Validation labels
|
||||
├── uploads/ # Temporary upload directory (created at runtime)
|
||||
└── runs/ # Training outputs (created after training)
|
||||
└── detect/
|
||||
└── memory_module_detection/
|
||||
└── weights/
|
||||
├── best.pt # Best model weights
|
||||
└── last.pt # Last epoch weights
|
||||
├── weights/
|
||||
│ ├── best.pt # Best model weights
|
||||
│ └── last.pt # Last epoch weights
|
||||
├── train_batch*.jpg # Training visualization
|
||||
├── val_batch*.jpg # Validation visualization
|
||||
├── confusion_matrix.png # Model performance metrics
|
||||
├── results.png # Training curves
|
||||
└── args.yaml # Training arguments
|
||||
```
|
||||
|
||||
### **📁 Key Files Description**
|
||||
|
||||
| File/Directory | Purpose | Usage |
|
||||
|----------------|---------|-------|
|
||||
| `main.py` | Main Flask API application | `python3 main.py` |
|
||||
| `api_docs.py` | Swagger UI documentation (developer only) | `python3 api_docs.py` |
|
||||
| `train.py` | YOLOv8 model training | `python3 train.py` |
|
||||
| `inference_utils.py` | Detection utilities and classes | Imported by other scripts |
|
||||
| `test_api.py` | Comprehensive API testing | `python3 test_api.py` |
|
||||
| `setup.py` | Automated project setup | `python3 setup.py` |
|
||||
| `templates/index.html` | Web interface for QA testing | Served by Flask |
|
||||
| `static/` | CSS, JavaScript, and assets | Served by Flask |
|
||||
| `training/` | Complete dataset with annotations | Used by training script |
|
||||
| `runs/` | Model training outputs | Created after training |
|
||||
| `venv/` | Python virtual environment | Created by user |
|
||||
|
||||
## 🤖 Algorithm Choice & Technical Decisions
|
||||
|
||||
### 1. **Algorithm Choice: YOLOv8 Nano**
|
||||
|
||||
-245
@@ -1,245 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Swagger UI API Documentation for Memory Module Detection
|
||||
This creates a separate API documentation interface using Flask-RESTX
|
||||
"""
|
||||
|
||||
from flask import Flask, request, jsonify
|
||||
from flask_restx import Api, Resource, fields, reqparse
|
||||
from flask_cors import CORS
|
||||
from werkzeug.datastructures import FileStorage
|
||||
import os
|
||||
from inference_utils import MemoryModuleDetector
|
||||
|
||||
# Initialize Flask app with Swagger
|
||||
app = Flask(__name__)
|
||||
CORS(app)
|
||||
|
||||
# Configure Swagger UI
|
||||
api = Api(
|
||||
app,
|
||||
version='1.0.0',
|
||||
title='Memory Module Detection API',
|
||||
description='AI-powered memory module detection in motherboard images using YOLOv8',
|
||||
doc='/docs/', # Swagger UI will be available at /docs/
|
||||
prefix='/api/v1'
|
||||
)
|
||||
|
||||
# Create namespaces
|
||||
ns_health = api.namespace('health', description='System health and status')
|
||||
ns_detect = api.namespace('detect', description='Memory module detection operations')
|
||||
|
||||
# Initialize detector
|
||||
MODEL_PATH = 'runs/detect/memory_module_detection/weights/best.pt'
|
||||
detector = MemoryModuleDetector(MODEL_PATH)
|
||||
|
||||
# Define models for Swagger documentation
|
||||
detection_model = api.model('Detection', {
|
||||
'bbox': fields.List(fields.Float, description='Bounding box coordinates [x1, y1, x2, y2]'),
|
||||
'confidence': fields.Float(description='Detection confidence score (0.0-1.0)'),
|
||||
'class': fields.Integer(description='Class ID (0 for memory_module)'),
|
||||
'class_name': fields.String(description='Class name (memory_module)')
|
||||
})
|
||||
|
||||
detection_response = api.model('DetectionResponse', {
|
||||
'success': fields.Boolean(description='Whether detection was successful'),
|
||||
'detections': fields.List(fields.Nested(detection_model), description='List of detected memory modules'),
|
||||
'num_detections': fields.Integer(description='Number of memory modules detected'),
|
||||
'annotated_image': fields.String(description='Base64 encoded annotated image'),
|
||||
'confidence_threshold': fields.Float(description='Confidence threshold used'),
|
||||
'original_filename': fields.String(description='Original filename (for uploads)')
|
||||
})
|
||||
|
||||
health_response = api.model('HealthResponse', {
|
||||
'status': fields.String(description='System health status'),
|
||||
'model_loaded': fields.Boolean(description='Whether the AI model is loaded'),
|
||||
'model_path': fields.String(description='Path to the AI model file')
|
||||
})
|
||||
|
||||
error_response = api.model('ErrorResponse', {
|
||||
'success': fields.Boolean(description='Always false for errors'),
|
||||
'error': fields.String(description='Error message')
|
||||
})
|
||||
|
||||
# Health endpoint
|
||||
@ns_health.route('/')
|
||||
class Health(Resource):
|
||||
@ns_health.doc('health_check')
|
||||
@ns_health.marshal_with(health_response)
|
||||
def get(self):
|
||||
"""Check system health and model status"""
|
||||
return {
|
||||
'status': 'healthy',
|
||||
'model_loaded': detector.model is not None,
|
||||
'model_path': MODEL_PATH
|
||||
}
|
||||
|
||||
# File upload parser
|
||||
upload_parser = reqparse.RequestParser()
|
||||
upload_parser.add_argument('image', location='files', type=FileStorage, required=True,
|
||||
help='Motherboard image file (PNG, JPG, JPEG, GIF, BMP)')
|
||||
upload_parser.add_argument('confidence', type=float, default=0.8,
|
||||
help='Confidence threshold (0.1-1.0, default: 0.8)')
|
||||
|
||||
@ns_detect.route('/upload')
|
||||
class DetectUpload(Resource):
|
||||
@ns_detect.doc('detect_upload')
|
||||
@ns_detect.expect(upload_parser)
|
||||
@ns_detect.marshal_with(detection_response, code=200)
|
||||
@ns_detect.marshal_with(error_response, code=400)
|
||||
@ns_detect.marshal_with(error_response, code=500)
|
||||
def post(self):
|
||||
"""Upload and analyze motherboard image for memory modules"""
|
||||
try:
|
||||
if detector.model is None:
|
||||
return {'success': False, 'error': 'Model not loaded'}, 500
|
||||
|
||||
args = upload_parser.parse_args()
|
||||
file = args['image']
|
||||
confidence = args['confidence']
|
||||
|
||||
if not file:
|
||||
return {'success': False, 'error': 'No image file provided'}, 400
|
||||
|
||||
# Save file temporarily
|
||||
temp_path = f"temp_{file.filename}"
|
||||
file.save(temp_path)
|
||||
|
||||
try:
|
||||
# Run detection
|
||||
detections, annotated_image = detector.detect(temp_path, conf_threshold=confidence)
|
||||
|
||||
# Convert annotated image to base64
|
||||
import io
|
||||
import base64
|
||||
buffer = io.BytesIO()
|
||||
annotated_image.save(buffer, format='PNG')
|
||||
annotated_base64 = base64.b64encode(buffer.getvalue()).decode()
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'detections': detections,
|
||||
'num_detections': len(detections),
|
||||
'annotated_image': annotated_base64,
|
||||
'confidence_threshold': confidence,
|
||||
'original_filename': file.filename
|
||||
}
|
||||
|
||||
finally:
|
||||
# Clean up
|
||||
if os.path.exists(temp_path):
|
||||
os.remove(temp_path)
|
||||
|
||||
except Exception as e:
|
||||
return {'success': False, 'error': str(e)}, 500
|
||||
|
||||
# Hardcoded image parser
|
||||
hardcoded_parser = reqparse.RequestParser()
|
||||
hardcoded_parser.add_argument('confidence', type=float, default=0.8, location='args',
|
||||
help='Confidence threshold (0.1-1.0, default: 0.8)')
|
||||
|
||||
@ns_detect.route('/hardcoded')
|
||||
class DetectHardcoded(Resource):
|
||||
@ns_detect.doc('detect_hardcoded')
|
||||
@ns_detect.expect(hardcoded_parser)
|
||||
@ns_detect.marshal_with(detection_response, code=200)
|
||||
@ns_detect.marshal_with(error_response, code=404)
|
||||
@ns_detect.marshal_with(error_response, code=500)
|
||||
def get(self):
|
||||
"""Analyze predefined test image for memory modules"""
|
||||
try:
|
||||
if detector.model is None:
|
||||
return {'success': False, 'error': 'Model not loaded'}, 500
|
||||
|
||||
args = hardcoded_parser.parse_args()
|
||||
confidence = args['confidence']
|
||||
|
||||
test_image_path = 'training/memory/out1.png'
|
||||
if not os.path.exists(test_image_path):
|
||||
return {'success': False, 'error': f'Test image not found at {test_image_path}'}, 404
|
||||
|
||||
# Run detection
|
||||
detections, annotated_image = detector.detect(test_image_path, conf_threshold=confidence)
|
||||
|
||||
# Convert annotated image to base64
|
||||
import io
|
||||
import base64
|
||||
buffer = io.BytesIO()
|
||||
annotated_image.save(buffer, format='PNG')
|
||||
annotated_base64 = base64.b64encode(buffer.getvalue()).decode()
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'detections': detections,
|
||||
'num_detections': len(detections),
|
||||
'annotated_image': annotated_base64,
|
||||
'confidence_threshold': confidence,
|
||||
'test_image_path': test_image_path
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
return {'success': False, 'error': str(e)}, 500
|
||||
|
||||
# Base64 image model
|
||||
base64_model = api.model('Base64Request', {
|
||||
'image': fields.String(required=True, description='Base64 encoded image data'),
|
||||
'confidence': fields.Float(default=0.8, description='Confidence threshold (0.1-1.0)')
|
||||
})
|
||||
|
||||
@ns_detect.route('/base64')
|
||||
class DetectBase64(Resource):
|
||||
@ns_detect.doc('detect_base64')
|
||||
@ns_detect.expect(base64_model)
|
||||
@ns_detect.marshal_with(detection_response, code=200)
|
||||
@ns_detect.marshal_with(error_response, code=400)
|
||||
@ns_detect.marshal_with(error_response, code=500)
|
||||
def post(self):
|
||||
"""Analyze base64 encoded image for memory modules"""
|
||||
try:
|
||||
if detector.model is None:
|
||||
return {'success': False, 'error': 'Model not loaded'}, 500
|
||||
|
||||
data = request.get_json()
|
||||
if not data or 'image' not in data:
|
||||
return {'success': False, 'error': 'No base64 image data provided'}, 400
|
||||
|
||||
confidence = data.get('confidence', 0.8)
|
||||
|
||||
# Decode base64 image
|
||||
import base64
|
||||
import io
|
||||
from PIL import Image
|
||||
import numpy as np
|
||||
|
||||
try:
|
||||
img_data = base64.b64decode(data['image'])
|
||||
image = Image.open(io.BytesIO(img_data))
|
||||
except Exception as e:
|
||||
return {'success': False, 'error': f'Invalid base64 image data: {str(e)}'}, 400
|
||||
|
||||
# Run detection
|
||||
detections, annotated_image = detector.detect_from_array(np.array(image), conf_threshold=confidence)
|
||||
|
||||
# Convert annotated image to base64
|
||||
buffer = io.BytesIO()
|
||||
annotated_image.save(buffer, format='PNG')
|
||||
annotated_base64 = base64.b64encode(buffer.getvalue()).decode()
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'detections': detections,
|
||||
'num_detections': len(detections),
|
||||
'annotated_image': annotated_base64,
|
||||
'confidence_threshold': confidence
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
return {'success': False, 'error': str(e)}, 500
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("Starting Memory Module Detection API with Swagger UI...")
|
||||
print(f"Model path: {MODEL_PATH}")
|
||||
print(f"Model loaded: {detector.model is not None}")
|
||||
print("Swagger UI available at: http://localhost:5003/docs/")
|
||||
|
||||
app.run(host='0.0.0.0', port=5003, debug=True)
|
||||
@@ -88,63 +88,16 @@ def api_info():
|
||||
'endpoints': {
|
||||
'/': 'GET - Frontend interface or API information',
|
||||
'/api': 'GET - API information (JSON)',
|
||||
'/docs': 'GET - API documentation (Swagger UI)',
|
||||
'/detect': 'POST - Upload image for memory module detection',
|
||||
'/detect/hardcoded': 'GET - Process hardcoded test image',
|
||||
'/detect/base64': 'POST - Process base64 encoded image',
|
||||
'/health': 'GET - Health check'
|
||||
},
|
||||
'model_loaded': detector.model is not None,
|
||||
'supported_formats': list(ALLOWED_EXTENSIONS),
|
||||
'swagger_ui': 'http://localhost:5003/docs/ (run: python3 api_docs.py)'
|
||||
'supported_formats': list(ALLOWED_EXTENSIONS)
|
||||
})
|
||||
|
||||
@app.route('/docs')
|
||||
def api_docs():
|
||||
"""Redirect to API documentation."""
|
||||
return """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>API Documentation</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; text-align: center; padding: 50px; background: #f5f5f5; }
|
||||
.container { max-width: 600px; margin: 0 auto; background: white; padding: 40px; border-radius: 10px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); }
|
||||
.btn { display: inline-block; padding: 12px 24px; background: #007bff; color: white; text-decoration: none; border-radius: 5px; margin: 10px; }
|
||||
.btn:hover { background: #0056b3; }
|
||||
.code { background: #f8f9fa; padding: 10px; border-radius: 5px; margin: 20px 0; font-family: monospace; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>🚀 Memory Module Detection API Documentation</h1>
|
||||
<p>Interactive Swagger UI documentation for all API endpoints</p>
|
||||
|
||||
<h3>📖 Access Swagger UI:</h3>
|
||||
<div class="code">
|
||||
<strong>Step 1:</strong> Start API docs server<br>
|
||||
<code>python3 api_docs.py</code><br><br>
|
||||
<strong>Step 2:</strong> Open Swagger UI<br>
|
||||
<code>http://localhost:5003/docs/</code>
|
||||
</div>
|
||||
|
||||
<a href="http://localhost:5003/docs/" class="btn" target="_blank">
|
||||
📚 Open Swagger UI (if running)
|
||||
</a>
|
||||
|
||||
<h3>📋 Quick API Reference:</h3>
|
||||
<ul style="text-align: left;">
|
||||
<li><strong>POST /detect</strong> - Upload image for detection</li>
|
||||
<li><strong>GET /detect/hardcoded</strong> - Test with predefined image</li>
|
||||
<li><strong>POST /detect/base64</strong> - Process base64 encoded image</li>
|
||||
<li><strong>GET /health</strong> - System health check</li>
|
||||
</ul>
|
||||
|
||||
<p><a href="/">← Back to Main Interface</a></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
@app.route('/health', methods=['GET'])
|
||||
def health_check():
|
||||
|
||||
@@ -39,9 +39,6 @@
|
||||
<button class="btn btn-info" onclick="runAllTests()">
|
||||
<i class="fas fa-play"></i> Run All Tests
|
||||
</button>
|
||||
<a href="/docs" class="btn btn-outline" target="_blank">
|
||||
<i class="fas fa-book"></i> API Documentation
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user