This commit is contained in:
Aherobo Ovie Victor
2025-07-21 19:20:44 +01:00
parent 0b7af4050e
commit 7908b94d40
124 changed files with 968 additions and 3499 deletions
+49 -206
View File
@@ -1,222 +1,65 @@
# Byte-compiled / optimized / DLL files # Python virtual environment
venv/
env/
.env/
.venv/
# Python cache files
__pycache__/ __pycache__/
*.py[cod] *.py[cod]
*$py.class *$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/ .pytest_cache/
# Translations # Distribution / packaging
*.mo dist/
*.pot build/
*.egg-info/
# Django stuff: # IDE specific files
.idea/
.vscode/
*.swp
*.swo
# YOLO specific
runs/
*.pt
weights/
# Project specific
torch_compile_debug/
training/train/
training/val/
dataset.yaml
# Logs and temporary files
*.log *.log
local_settings.py .DS_Store
db.sqlite3 temp/
db.sqlite3-journal tmp/
# Flask stuff: # Debug directories
instance/ torchinductor_*/
.webassets-cache
# Scrapy stuff: # Cache directories
.scrapy .cache/
*.cache
# Sphinx documentation # Test coverage
docs/_build/ coverage_html_report/
.coverage
htmlcov/
test_results/
# PyBuilder # Compiled files
target/ *.so
*.dll
*.dylib
# Jupyter Notebook # Jupyter Notebook
.ipynb_checkpoints .ipynb_checkpoints
*.ipynb
# IPython # Environment variables
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env .env
.venv .env.local
env/
venv/
ENV/
env.bak/
venv.bak/
myenv/
memory_detection_env/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# Machine Learning specific
# Model weights and checkpoints
*.pt
*.pth
*.ckpt
*.h5
*.pkl
*.joblib
runs/
wandb/
mlruns/
.neptune/
# YOLOv8 specific
yolov8*.pt
best.pt
last.pt
exp*/
# Dataset cache
*.cache
.fiftyone/
# Tensorboard logs
logs/
tb_logs/
# Jupyter notebook checkpoints
.ipynb_checkpoints/
# IDE specific
.vscode/
.idea/
*.swp
*.swo
*~
# OS specific
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# Project specific
uploads/
temp/
tmp/
annotated_*.png
test_*_result.png
# Large files that shouldn't be committed
*.zip
*.tar.gz
*.rar
# API keys and secrets
.env
config.ini
secrets.json
# Temporary files
*.tmp
*.temp
*.bak
*.backup
# Log files
*.log
logs/
# Coverage reports
htmlcov/
.coverage
# Virtual environment (additional patterns)
pyvenv.cfg
pip-selfcheck.json
# PyTorch specific
*.pth.tar
# Conda
.conda/
# Local configuration
local_config.py
config_local.py
-214
View File
@@ -1,214 +0,0 @@
# Memory Module Detection API Documentation
## Base URL
```
http://localhost:5002
```
## Endpoints
### 1. Health Check
**GET** `/health`
Check if the API is running and model is loaded.
**Response:**
```json
{
"status": "healthy",
"model_loaded": true,
"timestamp": "2025-07-12T07:41:46.123456"
}
```
### 2. API Information
**GET** `/api`
Get basic API information and available endpoints.
**Response:**
```json
{
"name": "Memory Module Detection API",
"version": "1.0",
"description": "AI-powered memory module detection using YOLOv8",
"model_loaded": true,
"endpoints": ["/health", "/api", "/detect", "/detect/hardcoded", "/detect/base64"]
}
```
### 3. Upload Image Detection
**POST** `/detect`
Upload an image file for memory module detection.
**Request:**
- Content-Type: `multipart/form-data`
- Body: Form data with `file` field containing image
- Optional: `confidence` parameter (default: 0.8)
**Response:**
```json
{
"success": true,
"detections": [
{
"x1": 100.5,
"y1": 150.2,
"x2": 200.8,
"y2": 250.6,
"confidence": 0.95,
"class": "memory_module"
}
],
"num_detections": 1,
"annotated_image": "base64_encoded_image_string",
"confidence_threshold": 0.8
}
```
### 4. Hardcoded Test Image
**GET** `/detect/hardcoded`
Process the hardcoded test image for detection.
**Query Parameters:**
- `confidence` (optional): Confidence threshold (default: 0.8)
**Example:**
```
GET /detect/hardcoded?confidence=0.9
```
**Response:**
```json
{
"success": true,
"detections": [...],
"num_detections": 2,
"annotated_image": "base64_encoded_image_string",
"confidence_threshold": 0.9,
"test_image_path": "training/memory/out1.png"
}
```
### 5. Base64 Image Detection
**POST** `/detect/base64`
Process a base64 encoded image for detection.
**Request:**
```json
{
"image_data": "base64_encoded_image_string",
"confidence": 0.8
}
```
**Response:**
```json
{
"success": true,
"detections": [...],
"num_detections": 1,
"annotated_image": "base64_encoded_image_string",
"confidence_threshold": 0.8
}
```
## Error Responses
All endpoints return error responses in this format:
```json
{
"error": "Error message description",
"success": false
}
```
Common HTTP status codes:
- `200` - Success
- `400` - Bad Request (invalid file, missing parameters)
- `404` - Not Found (endpoint or file not found)
- `413` - File Too Large (max 16MB)
- `500` - Internal Server Error (model not loaded, processing error)
## Supported Image Formats
- PNG
- JPG/JPEG
- GIF
- BMP
## File Size Limits
- Maximum upload size: 16MB
## Detection Response Format
Each detection object contains:
- `x1, y1`: Top-left corner coordinates
- `x2, y2`: Bottom-right corner coordinates
- `confidence`: Detection confidence score (0.0-1.0)
- `class`: Detected object class ("memory_module")
## Usage Examples
### cURL Examples
**Health Check:**
```bash
curl http://localhost:5002/health
```
**Upload Image:**
```bash
curl -X POST -F "file=@image.jpg" -F "confidence=0.8" http://localhost:5002/detect
```
**Hardcoded Test:**
```bash
curl "http://localhost:5002/detect/hardcoded?confidence=0.9"
```
### Python Examples
**Health Check:**
```python
import requests
response = requests.get('http://localhost:5002/health')
print(response.json())
```
**Upload Image:**
```python
import requests
with open('image.jpg', 'rb') as f:
files = {'file': f}
data = {'confidence': 0.8}
response = requests.post('http://localhost:5002/detect', files=files, data=data)
print(response.json())
```
**Base64 Detection:**
```python
import requests
import base64
with open('image.jpg', 'rb') as f:
image_data = base64.b64encode(f.read()).decode()
payload = {
'image_data': image_data,
'confidence': 0.8
}
response = requests.post('http://localhost:5002/detect/base64', json=payload)
print(response.json())
```
## Model Information
- **Architecture:** YOLOv8 Nano
- **Classes:** memory_module
- **Input Size:** 640x640
- **Accuracy:** 99.5% mAP50
- **Inference Time:** ~37ms on CPU
+155 -506
View File
@@ -1,545 +1,194 @@
# DS Task Recycling Project - Memory Module Detection # DS Task Recycling Project
This project is a complete implementation of a Flask API that processes motherboard images and detects memory modules using YOLOv8. The API returns annotated images with bounding boxes drawn around each detected memory module. This project is a Flask API that processes images of motherboards to detect memory modules. It uses computer vision to identify and draw bounding boxes around memory modules present in the input images.
## 🚀 Quick Start ## Project Overview
### 1. Install Dependencies
```bash
pip install -r requirements.txt
```
### 2. Train the Model
```bash
python3 train.py --epochs 100 --batch 16
```
### 3. Start the API
```bash
python3 main.py
```
### 4. Test the API
```bash
# Option 1: Use the Web Interface (Recommended for QA)
# Open browser and go to: http://localhost:5000
# Option 2: Use command line
# Test with hardcoded image
curl http://localhost:5000/detect/hardcoded
# Upload an image
curl -X POST -F "image=@your_image.png" http://localhost:5000/detect
# Option 3: Run automated tests
python3 test_api.py
```
## 📋 Project Overview
- **Algorithm Used:** YOLOv8 Nano (ultralytics)
- **Input Types:** - **Input Types:**
- Image upload via Flask API - Image upload via the Flask API
- Base64 encoded images - A hardcoded test image (memory_out19.png) for testing purposes
- Hardcoded test image
- **Dataset:** 40 images (20 with memory modules, 20 without)
- **Output:** Annotated images with bounding boxes and confidence scores
## 🏗️ Project Structure - **Dataset:**
- 20 pictures of motherboards with memory
- 20 pictures of motherboards without memory
``` - **Output:**
ds_task_recycling_project/ - An annotated image with bounding boxes around each detected memory module
├── main.py # Flask API application (main interface) - For example, if there are two memory modules, two boxes are drawn; if only one is detected, then one box is drawn
├── 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
├── test_api.py # API testing script
├── 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 + 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
│ │ ├── 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
├── 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** - **Annotation Tool:**
- [makesense.ai](https://www.makesense.ai/) was used for manual annotation
| File/Directory | Purpose | Usage | ## Implementation Details
|----------------|---------|-------|
| `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 ### Algorithm Choice & Rationale
### 1. **Algorithm Choice: YOLOv8 Nano** 1. **Which algorithm was chosen?**
- YOLOv8 (specifically YOLOv8n - the nano version) was selected for this task
**Which algorithm will you use for detecting the memory modules?** 2. **Why this algorithm?**
- **Answer:** YOLOv8 Nano (You Only Look Once version 8, Nano variant) - Fast inference speed suitable for real-time applications
- Good balance between accuracy and computational requirements
- Built-in support for transfer learning
- Excellent performance on object detection tasks
- Easy integration with Python/Flask applications
- Robust community support and documentation
**Why do you choose this particular algorithm?** ### Hardware Considerations
**Primary Reasons:** 3. **CPU/GPU Impact:**
- **State-of-the-art performance:** Latest evolution of YOLO family with superior accuracy - The current implementation runs on CPU for broader accessibility
- **Real-time inference:** 37ms processing time, single-stage detector - Model parameters were optimized for CPU performance:
- **Small object detection:** Excellent at detecting memory modules on motherboards - Reduced batch size (8)
- **Pre-trained weights:** Leverages COCO dataset for transfer learning - Lightweight augmentation
- **Easy integration:** Ultralytics library with excellent Python API - Early stopping with patience=15
- **Model efficiency:** Nano variant balances 99.5% mAP50 accuracy with speed - GPU support is available through YOLO if needed for scaling
- **Production ready:** Proven architecture used in industrial applications - Current performance is suitable for the demo nature of the project
**Technical Advantages:** ### Video Processing Approach
- **Anchor-free design:** Eliminates anchor box tuning complexity
- **Advanced augmentation:** Built-in data augmentation strategies
- **Multi-scale detection:** Handles objects of different sizes effectively
- **Export flexibility:** ONNX, TensorRT support for deployment optimization
- **Active community:** Regular updates and extensive documentation
### 2. **Hardware Considerations** 4. **Handling Video Input:**
- While not currently implemented, video processing would involve:
- Frame extraction
- Batch processing of frames
- Real-time detection using YOLO's video processing capabilities
- Optional frame skipping for performance optimization
- The current architecture can be extended for video by:
- Adding a video upload endpoint
- Implementing frame-by-frame processing
- Returning annotated video or real-time stream
**Does CPU or GPU have an impact on your decision? Please explain.** ## API Implementation
**Yes, hardware significantly impacts the implementation strategy:**
**Training Phase:**
- **GPU Impact:** Critical for training efficiency
- **GPU Training:** 5-10 minutes for 50 epochs (recommended)
- **CPU Training:** 30-60 minutes for same epochs
- **Memory Requirements:** 4GB+ GPU memory recommended
- **Batch Size:** GPU allows larger batches (16-32) vs CPU (4-8)
**Inference Phase:**
- **CPU Performance:** 37ms per image on modern CPU (Intel i5/i7, M1/M2)
- **GPU Performance:** 10-15ms per image, better for batch processing
- **Memory Usage:** CPU: 2-4GB RAM, GPU: 1-2GB VRAM
- **Edge Deployment:** Model runs efficiently on CPU-only devices
**Decision Impact:**
- **Algorithm Choice:** YOLOv8 Nano chosen specifically for CPU compatibility
- **Deployment Flexibility:** No expensive GPU required for production
- **Cost Efficiency:** Reduces infrastructure costs
- **Scalability:** GPU enables high-throughput batch processing
**Implementation:**
```python
# Auto-detection with fallback in train.py
device = 'cuda' if torch.cuda.is_available() else 'cpu'
print(f"Using device: {device}")
```
### 3. **Video Input Approach**
**What if a video is provided instead of single images?**
**Does your approach change when processing videos? Please describe your approach.**
**Yes, the approach would change significantly for video processing:**
**Video Processing Strategy:**
**1. Frame Extraction & Sampling**
```python
def process_video(video_path, fps_sample=5):
cap = cv2.VideoCapture(video_path)
frame_rate = cap.get(cv2.CAP_PROP_FPS)
frame_interval = int(frame_rate / fps_sample) # Sample every N frames
frames = []
frame_count = 0
while cap.isOpened():
ret, frame = cap.read()
if not ret:
break
if frame_count % frame_interval == 0:
frames.append(frame)
frame_count += 1
return frames
```
**2. Batch Processing for Efficiency**
```python
def batch_detect_video(frames, batch_size=8):
results = []
for i in range(0, len(frames), batch_size):
batch = frames[i:i+batch_size]
batch_results = model(batch) # Process multiple frames at once
results.extend(batch_results)
return results
```
**3. Temporal Consistency & Tracking**
```python
def apply_temporal_tracking(detections, frames):
tracker = DeepSORT() # Or ByteTrack for better performance
tracked_results = []
for frame_detections, frame in zip(detections, frames):
tracked_objects = tracker.update(frame_detections)
tracked_results.append(tracked_objects)
return tracked_results
```
**4. Optimization Strategies**
- **Motion Detection:** Skip frames with no significant changes
- **Optical Flow:** Track objects between frames to reduce processing
- **Keyframe Selection:** Process only important frames
- **Parallel Processing:** Use multiple CPU cores/GPU streams
- **Memory Management:** Process in chunks to avoid overflow
**5. Video-Specific Considerations**
- **Temporal Smoothing:** Apply filters to reduce detection jitter
- **Performance Scaling:** GPU becomes more critical for video processing
- **Storage Requirements:** Annotated videos require significant storage
- **Real-time Processing:** Streaming vs batch processing trade-offs
**Potential API Endpoint:**
```python
@app.route('/detect/video', methods=['POST'])
def detect_video():
# Upload video file
# Extract frames at specified FPS
# Batch process frames with YOLOv8
# Apply temporal tracking for consistency
# Return annotated video or frame-by-frame results
```
## **Technical Questions Summary**
The project successfully addresses all required technical questions:
1. **✅ Algorithm Choice:** YOLOv8 Nano selected for optimal balance of accuracy (99.5% mAP50), speed (37ms), and deployment flexibility
2. **✅ Hardware Considerations:** Comprehensive CPU/GPU analysis with auto-detection and fallback strategies for maximum compatibility
3. **✅ Video Processing:** Complete video processing strategy with frame extraction, batch processing, temporal tracking, and optimization techniques
All technical decisions are implemented and validated in the working system.
## Installation & Setup
### Prerequisites
- Python 3.8+
- pip or conda
### Step-by-Step Installation
1. **Clone/Download the project**
```bash
cd ds_task_recycling_project
```
2. **Install dependencies**
```bash
pip install -r requirements.txt
```
3. **Prepare dataset (if not already done)**
```bash
python3 prepare_dataset.py
```
4. **Train the model**
```bash
# Basic training (recommended)
python3 train.py
# Custom training parameters
python3 train.py --epochs 150 --batch 8 --device cuda
```
5. **Start the Flask API**
```bash
python3 main.py
```
The API will be available at `http://localhost:5000`
## 🌐 Web Interface for QA Testing
We've included a comprehensive web interface for easy QA testing:
### Features:
- **Drag & Drop Image Upload** - Easy image selection
- **Real-time API Status** - Shows if API and model are loaded
- **Multiple Test Options:**
- Test hardcoded image
- Upload custom images
- Run comprehensive API tests
- **Interactive Results** - View annotated images with detection details
- **Confidence Threshold Control** - Adjust detection sensitivity
- **Responsive Design** - Works on desktop and mobile
### Access:
1. Start the API: `python3 main.py`
2. Open browser: `http://localhost:5000`
3. Use the interface to test detection functionality
### QA Testing Workflow:
1. **Check API Status** - Verify green "API Online" indicator
2. **Test Hardcoded Image** - Click "Test Hardcoded Image" button
3. **Upload Custom Images** - Drag/drop or select motherboard images
4. **Adjust Confidence** - Use slider to test different thresholds
5. **Run All Tests** - Comprehensive API endpoint testing
6. **Review Results** - Check detection accuracy and annotations
## 📡 API Documentation
### Base URL
```
http://localhost:5000
```
### Endpoints ### Endpoints
#### 1. **GET /** - API Information 1. **Image Upload (`/detect`):**
```http
POST /detect
Content-Type: multipart/form-data
```
- Accepts image uploads
- Returns annotated image with detection boxes
2. **Test Detection (`/detect/test`):**
```http
GET /detect/test
```
- Uses a hardcoded test image (memory_out19.png)
- Returns annotated image with detection boxes
### Processing Workflow
1. Image Reception:
- Via file upload or hardcoded test image
2. Detection:
- YOLOv8 processes the image
- Confidence threshold: 0.25
- IoU threshold: 0.45
3. Annotation:
- Bounding boxes drawn around detected modules
4. Response:
- Annotated image returned in PNG format
## Model Training
The model was trained with the following parameters:
- 50 epochs
- Image size: 640x640
- Batch size: 8
- Early stopping patience: 15
- Augmentations:
- Rotation (±5°)
- Scale (0.5)
- Translation (0.1)
- Horizontal flip (0.5)
- Mosaic (1.0)
## Dataset Preparation
```bash ```bash
curl http://localhost:5000/ training/
├── memory/
│ └── (images with memory modules) #You have this
├── no_memory/
│ └── (images without memory modules) #You have this as well
├── train/
│ ├── images/
│ │ ├── memory_*.png
│ │ └── no_memory_*.png
│ └── labels/
│ ├── memory_*.txt
│ └── no_memory_*.txt
└── val/
├── images/
│ ├── memory_*.png
│ └── no_memory_*.png
└── labels/
├── memory_*.txt
└── no_memory_*.txt
dataset.yaml
``` ```
**Response:** The dataset is organized as follows:
```json - `training/memory/`: Source directory for images with memory modules
{ - `training/no_memory/`: Source directory for images without memory modules
"message": "Memory Module Detection API", - `training/train/`: Training dataset
"version": "1.0.0", - `images/`: Contains both memory and no-memory images with appropriate prefixes
"endpoints": {...}, - `labels/`: Contains YOLO format annotation files
"model_loaded": true, - `training/val/`: Validation dataset
"supported_formats": ["png", "jpg", "jpeg", "gif", "bmp"] - `images/`: Contains both memory and no-memory images with appropriate prefixes
} - `labels/`: Contains YOLO format annotation files
The `dataset.yaml` file contains:
```yaml
path: training # dataset root dir
train: train/images # train images
val: val/images # validation images
nc: 1 # number of classes
names: ['memory_module'] # class names
``` ```
#### 2. **GET /health** - Health Check ## Getting Started
1. Clone the repository:
```bash ```bash
curl http://localhost:5000/health git clone http://23.29.118.76:3000/michael/ds_task_recycling_project.git
``` ```
2. Install dependencies:
#### 3. **POST /detect** - Upload Image Detection
```bash ```bash
curl -X POST -F "image=@motherboard.png" -F "confidence=0.5" http://localhost:5000/detect pip install -r requirements.txt
``` ```
3. Prepare the dataset:
**Response:**
```json
{
"success": true,
"detections": [
{
"bbox": [100, 150, 200, 250],
"confidence": 0.85,
"class": 0,
"class_name": "memory_module"
}
],
"num_detections": 1,
"annotated_image": "base64_encoded_image...",
"confidence_threshold": 0.5
}
```
#### 4. **GET /detect/hardcoded** - Test with Hardcoded Image
```bash ```bash
curl "http://localhost:5000/detect/hardcoded?confidence=0.5" python prepare_dataset.py
``` ```
4. Train the model (if not already trained):
#### 5. **POST /detect/base64** - Base64 Image Detection
```bash ```bash
curl -X POST -H "Content-Type: application/json" \ python train.py
-d '{"image": "base64_string", "confidence": 0.5}' \
http://localhost:5000/detect/base64
``` ```
5. Run the Flask application:
## 🧪 Testing & Usage Examples
### 1. **Test with Python requests**
```python
import requests
import base64
# Test hardcoded image
response = requests.get('http://localhost:5000/detect/hardcoded')
result = response.json()
print(f"Found {result['num_detections']} memory modules")
# Upload image
with open('test_image.png', 'rb') as f:
files = {'image': f}
response = requests.post('http://localhost:5000/detect', files=files)
result = response.json()
```
### 2. **Test with curl**
```bash ```bash
# Basic detection python run.py
curl -X POST -F "image=@training/memory/out1.png" http://localhost:5000/detect
# With custom confidence
curl -X POST -F "image=@training/memory/out1.png" -F "confidence=0.3" http://localhost:5000/detect
``` ```
6. Access the web interface at `http://localhost:5000`
### 3. **Command Line Inference** ## Testing
The project includes comprehensive tests for the detector:
- Batch detection testing
- Threshold optimization
- Various confidence/IoU threshold combinations
Run tests with:
```bash ```bash
# Test single image pytest tests/
python3 inference_utils.py --image training/memory/out1.png --conf 0.5
# Validate trained model
python3 train.py --validate --model runs/detect/memory_module_detection/weights/best.pt
``` ```
## 📊 Training Details ## Future Improvements
### Dataset Statistics 1. GPU support for faster processing
- **Total Images:** 40 (20 with memory, 20 without) 2. Video input support
- **Training Split:** 32 images (80%) 3. Real-time streaming capabilities
- **Validation Split:** 8 images (20%) 4. More sophisticated augmentation techniques
- **Classes:** 1 (memory_module) 5. Model quantization for improved CPU performance
- **Annotation Format:** YOLO (normalized coordinates)
### Training Configuration
```python
# Default training parameters
epochs = 100
batch_size = 16
image_size = 640
confidence_threshold = 0.5
iou_threshold = 0.45
```
### Expected Training Time
- **GPU (RTX 3060+):** 5-10 minutes
- **CPU (Modern):** 30-60 minutes
- **Memory Usage:** 2-4GB RAM
### Model Performance
After training, you should see:
- **mAP50:** >0.8 (80%+ accuracy at 50% IoU)
- **Precision:** >0.85
- **Recall:** >0.80
## 🐛 Troubleshooting
### Common Issues
#### 1. **Model Not Found Error**
```
Error: Model not found at runs/detect/memory_module_detection/weights/best.pt
```
**Solution:** Train the model first
```bash
python3 train.py
```
#### 2. **CUDA Out of Memory**
```
RuntimeError: CUDA out of memory
```
**Solutions:**
- Reduce batch size: `python3 train.py --batch 8`
- Use CPU: `python3 train.py --device cpu`
- Close other GPU applications
#### 3. **Import Error: ultralytics**
```
ModuleNotFoundError: No module named 'ultralytics'
```
**Solution:**
```bash
pip install ultralytics
```
#### 4. **Flask Port Already in Use**
```
OSError: [Errno 48] Address already in use
```
**Solution:**
```bash
# Kill process using port 5000
lsof -ti:5000 | xargs kill -9
# Or use different port
python3 main.py # Edit main.py to change port
```
#### 5. **Low Detection Accuracy**
**Solutions:**
- Increase training epochs: `python3 train.py --epochs 200`
- Lower confidence threshold: `confidence=0.3`
- Check image quality and lighting
- Verify annotations are correct
### Performance Optimization
#### For Better Accuracy:
1. **More Training Data:** Add more annotated images
2. **Data Augmentation:** Already included in YOLOv8
3. **Hyperparameter Tuning:** Adjust learning rate, batch size
4. **Model Size:** Use YOLOv8s or YOLOv8m for better accuracy
#### For Faster Inference:
1. **Model Quantization:** Convert to TensorRT or ONNX
2. **Batch Processing:** Process multiple images together
3. **Image Resizing:** Use smaller input size (320x320)
## 📁 File Descriptions
- **`main.py`** - Flask API with all endpoints
- **`train.py`** - YOLOv8 training script with validation
- **`inference_utils.py`** - Detection utilities and visualization
- **`prepare_dataset.py`** - Dataset preparation and splitting
- **`requirements.txt`** - Python dependencies
- **`dataset.yaml`** - YOLO dataset configuration
## 🔮 Future Enhancements
1. **Video Processing:** Add video upload and processing endpoints
2. **Model Ensemble:** Combine multiple models for better accuracy
3. **Real-time Streaming:** WebSocket support for live camera feeds
4. **Database Integration:** Store detection results and statistics
5. **Web Interface:** HTML frontend for easier testing
6. **Docker Deployment:** Containerized deployment
7. **Model Versioning:** Support multiple model versions
8. **Batch Processing:** Process multiple images simultaneously
## 📄 License
This project is for educational and training purposes.
## 🤝 Contributing
This is a toy project for training purposes. Feel free to experiment and improve!
-189
View File
@@ -1,189 +0,0 @@
# Project Validation Checklist
## ✅ README Requirements Validation
### Original Requirements from README:
1. **Flask API that processes motherboard images**
2. **Detects memory modules present on motherboards**
3. **Returns image with bounding boxes around detected memory modules**
4. **Image upload via Flask API**
5. **Hardcoded image for testing purposes**
6. **Dataset: 20 pictures with memory, 20 without memory**
7. **Annotation tool suggestion: makesense.ai** ✅ (Already annotated)
### Additional Features Implemented:
-**Web Frontend for QA Testing** (Beyond requirements)
-**Base64 image processing endpoint**
-**Comprehensive API testing suite**
-**Automated setup script**
-**Complete documentation**
## 🔧 Technical Implementation Validation
### Algorithm Choice Questions Answered:
1. **Which algorithm for detecting memory modules?**
-**Answer: YOLOv8 Nano**
-**Reasoning: State-of-the-art performance, real-time inference, pre-trained weights, easy integration**
2. **Hardware considerations (CPU vs GPU impact)?**
-**Training: GPU recommended (5-10 min vs 30-60 min CPU)**
-**Inference: CPU sufficient for real-time, GPU better for batch processing**
-**Implementation: Auto-detection with fallback**
3. **Video input approach?**
-**Approach described: Frame extraction + batch processing + temporal tracking**
-**Implementation strategy provided with pseudo-code**
## 📁 File Structure Validation
### Required Files:
-`main.py` - Flask API application
-`train.py` - YOLOv8 training script
-`inference_utils.py` - Detection and visualization utilities
-`prepare_dataset.py` - Dataset preparation script
-`requirements.txt` - Python dependencies
-`dataset.yaml` - YOLO dataset configuration
-`README.md` - Complete documentation
### Additional Files Created:
-`test_api.py` - API testing script
-`setup.py` - Automated setup script
-`templates/index.html` - Web interface
-`static/style.css` - Frontend styling
-`static/script.js` - Frontend functionality
-`VALIDATION_CHECKLIST.md` - This validation document
### Dataset Structure:
-`training/memory/` - 20 images with memory modules + YOLO labels
-`training/no_memory/` - 20 images without memory modules
-`training/train/` - Training split (80% = 32 images)
-`training/val/` - Validation split (20% = 8 images)
## 🚀 API Endpoints Validation
### Required Endpoints:
1.**Image upload endpoint** - `POST /detect`
2.**Hardcoded image endpoint** - `GET /detect/hardcoded`
### Additional Endpoints:
3.**API information** - `GET /` (serves frontend) & `GET /api` (JSON)
4.**Health check** - `GET /health`
5.**Base64 processing** - `POST /detect/base64`
6.**Error handlers** - 404, 413, 500
## 🧪 Testing Validation
### Test Coverage:
-**API health check testing**
-**Hardcoded image detection testing**
-**File upload testing**
-**Base64 image testing**
-**Error handling testing**
-**Web interface testing**
### Test Scripts:
-`test_api.py` - Comprehensive API testing
- ✅ Web interface - Interactive QA testing
-`setup.py` - Automated setup validation
## 📦 Dependencies Validation
### Core Dependencies:
-`ultralytics` - YOLOv8 implementation
-`torch` & `torchvision` - PyTorch for ML
-`opencv-python` - Image processing
-`Pillow` - Image handling
-`Flask` & `Flask-CORS` - Web framework
-`numpy` - Numerical operations
-`PyYAML` - Configuration files
### Additional Dependencies:
-`Werkzeug` - Flask utilities
-`requests` - HTTP testing
-`tqdm` - Progress bars
-`matplotlib` & `seaborn` - Visualization (optional)
## 🎯 Functional Requirements Validation
### Input Processing:
-**Accepts PNG, JPG, JPEG, GIF, BMP formats**
-**File size limit: 16MB**
-**Drag & drop support in web interface**
-**Base64 encoding support**
-**Confidence threshold adjustment**
### Output Generation:
-**Bounding boxes around detected memory modules**
-**Confidence scores for each detection**
-**Annotated images returned as base64**
-**JSON response with detection details**
-**Visual feedback in web interface**
### Model Performance:
-**Single class detection: 'memory_module'**
-**YOLO format annotations**
-**Transfer learning from COCO dataset**
-**Configurable confidence and IoU thresholds**
## 🌐 Web Interface Validation
### QA Testing Features:
-**Real-time API status indicator**
-**Drag & drop image upload**
-**Confidence threshold slider**
-**Multiple testing options**
-**Interactive results display**
-**Responsive design**
-**Error handling and feedback**
### User Experience:
-**Intuitive interface design**
-**Clear visual feedback**
-**Loading indicators**
-**Result visualization**
-**Mobile compatibility**
## 📚 Documentation Validation
### README Completeness:
-**Quick start guide**
-**Installation instructions**
-**API documentation**
-**Usage examples**
-**Troubleshooting guide**
-**Technical decisions explained**
-**Project structure documented**
### Code Documentation:
-**Docstrings in all functions**
-**Inline comments for complex logic**
-**Type hints where appropriate**
-**Error handling documented**
## 🔄 Setup & Deployment Validation
### Setup Options:
-**Manual setup with step-by-step instructions**
-**Automated setup script (`setup.py`)**
-**Requirements file for dependencies**
-**Dataset preparation script**
### Deployment Readiness:
-**Production-ready Flask configuration**
-**Error handling and logging**
-**CORS support for frontend**
-**File upload security**
-**Model loading validation**
## 🎉 Final Validation Summary
### ✅ **ALL ORIGINAL REQUIREMENTS MET**
### ✅ **ADDITIONAL FEATURES IMPLEMENTED**
### ✅ **COMPREHENSIVE TESTING SUITE**
### ✅ **PRODUCTION-READY CODE**
### ✅ **EXCELLENT DOCUMENTATION**
### ✅ **QA-FRIENDLY WEB INTERFACE**
## 🚀 Ready for QA Testing!
The project is complete and ready for quality assurance testing. All original requirements have been met and exceeded with additional features for better usability and testing.
+14
View File
@@ -0,0 +1,14 @@
from flask import Flask
def create_app():
app = Flask(__name__)
# Register blueprints
from app.routes import main_bp
app.register_blueprint(main_bp)
# Ensure the static folder is properly set
app.static_folder = 'static'
app.template_folder = 'templates'
return app
+57
View File
@@ -0,0 +1,57 @@
from flask import Blueprint, request, jsonify, send_file, render_template
from app.utils.detector import MemoryDetector
import os
from PIL import Image
import io
main_bp = Blueprint('main', __name__)
detector = MemoryDetector()
@main_bp.route('/')
def index():
return render_template('index.html')
@main_bp.route('/detect', methods=['POST'])
def detect_memory():
if 'image' not in request.files:
return jsonify({'error': 'No image provided'}), 400
file = request.files['image']
# Read the image
img = Image.open(file.stream)
# Process the image and get annotated image and detections
annotated_img, detections = detector.detect(img)
# Convert PIL image to bytes
img_byte_arr = io.BytesIO()
annotated_img.save(img_byte_arr, format='PNG')
img_byte_arr.seek(0)
return send_file(
img_byte_arr,
mimetype='image/png'
)
@main_bp.route('/detect/test', methods=['GET'])
def detect_test():
"""Endpoint for testing with a hardcoded image"""
# Using an existing image from the validation set
test_image_path = os.path.join('training', 'val', 'images', 'memory_out19.png')
if not os.path.exists(test_image_path):
return jsonify({'error': f'Test image not found at {test_image_path}'}), 404
img = Image.open(test_image_path)
# Get both the annotated image and detections
annotated_img, detections = detector.detect(img)
img_byte_arr = io.BytesIO()
annotated_img.save(img_byte_arr, format='PNG')
img_byte_arr.seek(0)
return send_file(
img_byte_arr,
mimetype='image/png'
)
+159
View File
@@ -0,0 +1,159 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
line-height: 1.6;
background-color: #f5f5f5;
color: #333;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
}
h1 {
text-align: center;
margin-bottom: 2rem;
color: #2c3e50;
}
.upload-section {
display: flex;
flex-direction: column;
align-items: center;
gap: 1rem;
margin-bottom: 2rem;
}
.upload-box {
width: 100%;
max-width: 500px;
height: 200px;
border: 2px dashed #3498db;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: border-color 0.3s ease;
background-color: #fff;
}
.upload-box:hover {
border-color: #2980b9;
}
.upload-content {
text-align: center;
}
.upload-icon {
width: 64px;
height: 64px;
margin-bottom: 1rem;
}
.browse-text {
color: #3498db;
text-decoration: underline;
cursor: pointer;
}
button {
padding: 0.8rem 1.5rem;
font-size: 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s ease;
}
#detectButton {
background-color: #3498db;
color: white;
}
#detectButton:disabled {
background-color: #bdc3c7;
cursor: not-allowed;
}
#testButton {
background-color: #2ecc71;
color: white;
}
#detectButton:hover:not(:disabled) {
background-color: #2980b9;
}
#testButton:hover {
background-color: #27ae60;
}
.results-section {
margin-top: 2rem;
}
.image-container {
display: flex;
gap: 2rem;
justify-content: center;
flex-wrap: wrap;
}
.image-box {
flex: 1;
min-width: 300px;
max-width: 500px;
background-color: white;
padding: 1rem;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.image-box h3 {
margin-bottom: 1rem;
text-align: center;
}
.image-box img {
width: 100%;
height: auto;
border-radius: 4px;
display: none;
}
.loading-spinner {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(255,255,255,0.8);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 1000;
}
.spinner {
width: 50px;
height: 50px;
border: 5px solid #f3f3f3;
border-top: 5px solid #3498db;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}

Before

Width:  |  Height:  |  Size: 2.9 MiB

After

Width:  |  Height:  |  Size: 2.9 MiB

+5
View File
@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="#3498db" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
<polyline points="17 8 12 3 7 8"/>
<line x1="12" y1="3" x2="12" y2="15"/>
</svg>

After

Width:  |  Height:  |  Size: 323 B

+100
View File
@@ -0,0 +1,100 @@
document.addEventListener('DOMContentLoaded', function() {
const dropZone = document.getElementById('dropZone');
const fileInput = document.getElementById('fileInput');
const detectButton = document.getElementById('detectButton');
const testButton = document.getElementById('testButton');
const originalImage = document.getElementById('originalImage');
const resultImage = document.getElementById('resultImage');
const loading = document.getElementById('loading');
// Handle drag and drop
dropZone.addEventListener('dragover', (e) => {
e.preventDefault();
dropZone.style.borderColor = '#2980b9';
});
dropZone.addEventListener('dragleave', (e) => {
e.preventDefault();
dropZone.style.borderColor = '#3498db';
});
dropZone.addEventListener('drop', (e) => {
e.preventDefault();
dropZone.style.borderColor = '#3498db';
const file = e.dataTransfer.files[0];
if (file && file.type.startsWith('image/')) {
handleImageSelection(file);
}
});
// Handle click to upload
dropZone.addEventListener('click', () => {
fileInput.click();
});
fileInput.addEventListener('change', (e) => {
const file = e.target.files[0];
if (file) {
handleImageSelection(file);
}
});
function handleImageSelection(file) {
const reader = new FileReader();
reader.onload = function(e) {
originalImage.src = e.target.result;
originalImage.style.display = 'block';
detectButton.disabled = false;
};
reader.readAsDataURL(file);
}
// Handle detect button click
detectButton.addEventListener('click', async () => {
const formData = new FormData();
formData.append('image', fileInput.files[0]);
try {
loading.style.display = 'flex';
const response = await fetch('/detect', {
method: 'POST',
body: formData
});
if (response.ok) {
const blob = await response.blob();
resultImage.src = URL.createObjectURL(blob);
resultImage.style.display = 'block';
} else {
alert('Error processing image');
}
} catch (error) {
console.error('Error:', error);
alert('Error processing image');
} finally {
loading.style.display = 'none';
}
});
// Handle test button click
testButton.addEventListener('click', async () => {
try {
loading.style.display = 'flex';
const response = await fetch('/detect/test');
if (response.ok) {
const blob = await response.blob();
resultImage.src = URL.createObjectURL(blob);
resultImage.style.display = 'block';
} else {
alert('Error running test detection');
}
} catch (error) {
console.error('Error:', error);
alert('Error running test detection');
} finally {
loading.style.display = 'none';
}
});
});
+47
View File
@@ -0,0 +1,47 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Memory Module Detector</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
</head>
<body>
<div class="container">
<h1>Memory Module Detector</h1>
<div class="upload-section">
<div class="upload-box" id="dropZone">
<input type="file" id="fileInput" accept="image/*" hidden>
<div class="upload-content">
<img src="{{ url_for('static', filename='images/upload-icon.svg') }}" alt="Upload" class="upload-icon">
<p>Drag and drop an image or <span class="browse-text">browse</span></p>
</div>
</div>
<button id="detectButton" disabled>Detect Memory Modules</button>
<button id="testButton">Run Test Detection</button>
</div>
<div class="results-section">
<div class="image-container">
<div class="image-box">
<h3>Original Image</h3>
<img id="originalImage" src="" alt="Original image will appear here">
</div>
<div class="image-box">
<h3>Detected Results</h3>
<img id="resultImage" src="" alt="Detection results will appear here">
</div>
</div>
</div>
<div id="loading" class="loading-spinner" style="display: none;">
<div class="spinner"></div>
<p>Processing...</p>
</div>
</div>
<script src="{{ url_for('static', filename='js/main.js') }}"></script>
</body>
</html>
+99
View File
@@ -0,0 +1,99 @@
from ultralytics import YOLO
from PIL import Image
import numpy as np
from typing import Tuple, List, Dict
class MemoryDetector:
def __init__(self,
model_path='model/weights/best.pt',
conf_threshold=0.25,
iou_threshold=0.45):
"""
Initialize the detector with the trained model.
Args:
model_path (str): Path to the trained model weights
conf_threshold (float): Confidence threshold for detections
iou_threshold (float): IoU threshold for NMS
"""
self.model = YOLO(model_path)
self.conf_threshold = conf_threshold
self.iou_threshold = iou_threshold
def detect(self,
image: Image.Image,
conf_threshold: float = None,
iou_threshold: float = None) -> Tuple[Image.Image, List[Dict]]:
"""
Detect memory modules in the given image.
Args:
image (PIL.Image): Input image to process
conf_threshold (float, optional): Override default confidence threshold
iou_threshold (float, optional): Override default IoU threshold
Returns:
Tuple[PIL.Image, List[Dict]]: Annotated image and list of detections
"""
# Use provided thresholds or defaults
conf = conf_threshold if conf_threshold is not None else self.conf_threshold
iou = iou_threshold if iou_threshold is not None else self.iou_threshold
# Run inference
results = self.model.predict(
source=image,
conf=conf,
iou=iou,
max_det=10,
verbose=False
)
# Get the annotated image
annotated_img = results[0].plot()
# Extract detection information
detections = []
for box in results[0].boxes:
detection = {
'xyxy': box.xyxy[0].tolist(), # Bounding box coordinates
'confidence': float(box.conf[0]), # Detection confidence
'class': int(box.cls[0]) # Class ID
}
detections.append(detection)
return Image.fromarray(annotated_img), detections
def optimize_thresholds(self, validation_images: List[Image.Image]) -> Tuple[float, float]:
"""
Find optimal confidence and IoU thresholds using validation images.
Args:
validation_images (List[Image.Image]): List of validation images
Returns:
Tuple[float, float]: Optimal confidence and IoU thresholds
"""
best_conf = 0.25
best_iou = 0.45
# Grid search for best parameters
conf_range = [0.15, 0.2, 0.25, 0.3, 0.35]
iou_range = [0.35, 0.4, 0.45, 0.5, 0.55]
best_score = 0
for conf in conf_range:
for iou in iou_range:
total_score = 0
for img in validation_images:
_, detections = self.detect(img, conf, iou)
# Score based on number of detections and confidence
score = sum([d['confidence'] for d in detections])
total_score += score
if total_score > best_score:
best_score = total_score
best_conf = conf
best_iou = iou
return best_conf, best_iou
-5
View File
@@ -1,5 +0,0 @@
path: training # dataset root dir
train: train/images # train images
val: val/images # validation images
nc: 1 # number of classes
names: ['memory_module'] # class names
-287
View File
@@ -1,287 +0,0 @@
#!/usr/bin/env python3
"""
Inference utilities for memory module detection.
Contains functions for model loading, inference, and visualization.
"""
import cv2
import numpy as np
from PIL import Image, ImageDraw, ImageFont
import os
from ultralytics import YOLO
import torch
class MemoryModuleDetector:
"""Memory module detector using YOLOv8."""
def __init__(self, model_path='runs/detect/memory_module_detection/weights/best.pt'):
"""
Initialize the detector.
Args:
model_path (str): Path to the trained YOLOv8 model
"""
self.model_path = model_path
self.model = None
self.class_names = ['memory_module']
self.colors = [(0, 255, 0)] # Green for memory modules
# Load model if it exists
if os.path.exists(model_path):
self.load_model()
else:
print(f"Warning: Model not found at {model_path}")
print("Please train the model first using train.py")
def load_model(self):
"""Load the trained YOLOv8 model."""
try:
# Fix for PyTorch 2.6+ weights_only issue
import torch
# Use weights_only=False for compatibility
with torch.serialization.safe_globals(['ultralytics.nn.tasks.DetectionModel']):
self.model = YOLO(self.model_path)
print(f"Model loaded successfully from {self.model_path}")
except Exception as e:
try:
# Fallback: try loading with weights_only=False
import torch
original_load = torch.load
torch.load = lambda *args, **kwargs: original_load(*args, **kwargs, weights_only=False)
self.model = YOLO(self.model_path)
torch.load = original_load
print(f"Model loaded successfully from {self.model_path} (fallback method)")
except Exception as e2:
print(f"Error loading model: {e2}")
self.model = None
def detect(self, image_path, conf_threshold=0.5, iou_threshold=0.45):
"""
Detect memory modules in an image.
Args:
image_path (str): Path to the input image
conf_threshold (float): Confidence threshold for detections
iou_threshold (float): IoU threshold for NMS
Returns:
tuple: (detections, annotated_image)
"""
if self.model is None:
raise ValueError("Model not loaded. Please check model path.")
# Run inference
results = self.model(image_path, conf=conf_threshold, iou=iou_threshold)
# Extract detections
detections = []
if len(results) > 0 and results[0].boxes is not None:
boxes = results[0].boxes
for i in range(len(boxes)):
box = boxes.xyxy[i].cpu().numpy() # x1, y1, x2, y2
conf = boxes.conf[i].cpu().numpy()
cls = int(boxes.cls[i].cpu().numpy())
detection = {
'bbox': box.tolist(),
'confidence': float(conf),
'class': int(cls),
'class_name': self.class_names[cls] if cls < len(self.class_names) else 'unknown'
}
detections.append(detection)
# Create annotated image
annotated_image = self.draw_detections(image_path, detections)
return detections, annotated_image
def draw_detections(self, image_path, detections):
"""
Draw bounding boxes on the image.
Args:
image_path (str): Path to the input image
detections (list): List of detection dictionaries
Returns:
PIL.Image: Annotated image
"""
# Load image
image = Image.open(image_path).convert('RGB')
draw = ImageDraw.Draw(image)
# Try to load a font
try:
font = ImageFont.truetype("arial.ttf", 16)
except:
font = ImageFont.load_default()
# Draw each detection
for detection in detections:
bbox = detection['bbox']
confidence = detection['confidence']
class_name = detection['class_name']
# Extract coordinates
x1, y1, x2, y2 = bbox
# Draw bounding box
color = self.colors[0] # Green for memory modules
draw.rectangle([x1, y1, x2, y2], outline=color, width=3)
# Draw label
label = f"{class_name}: {confidence:.2f}"
# Get text size for background
bbox_text = draw.textbbox((0, 0), label, font=font)
text_width = bbox_text[2] - bbox_text[0]
text_height = bbox_text[3] - bbox_text[1]
# Draw background for text
draw.rectangle([x1, y1 - text_height - 4, x1 + text_width + 4, y1],
fill=color, outline=color)
# Draw text
draw.text((x1 + 2, y1 - text_height - 2), label, fill=(255, 255, 255), font=font)
return image
def detect_from_array(self, image_array, conf_threshold=0.5, iou_threshold=0.45):
"""
Detect memory modules from a numpy array.
Args:
image_array (np.ndarray): Input image as numpy array
conf_threshold (float): Confidence threshold for detections
iou_threshold (float): IoU threshold for NMS
Returns:
tuple: (detections, annotated_image)
"""
if self.model is None:
raise ValueError("Model not loaded. Please check model path.")
# Convert numpy array to PIL Image if needed
if isinstance(image_array, np.ndarray):
if image_array.dtype != np.uint8:
image_array = (image_array * 255).astype(np.uint8)
image = Image.fromarray(image_array)
else:
image = image_array
# Run inference
results = self.model(image, conf=conf_threshold, iou=iou_threshold)
# Extract detections
detections = []
if len(results) > 0 and results[0].boxes is not None:
boxes = results[0].boxes
for i in range(len(boxes)):
box = boxes.xyxy[i].cpu().numpy() # x1, y1, x2, y2
conf = boxes.conf[i].cpu().numpy()
cls = int(boxes.cls[i].cpu().numpy())
detection = {
'bbox': box.tolist(),
'confidence': float(conf),
'class': int(cls),
'class_name': self.class_names[cls] if cls < len(self.class_names) else 'unknown'
}
detections.append(detection)
# Create annotated image
annotated_image = self.draw_detections_on_image(image, detections)
return detections, annotated_image
def draw_detections_on_image(self, image, detections):
"""
Draw bounding boxes on a PIL Image.
Args:
image (PIL.Image): Input image
detections (list): List of detection dictionaries
Returns:
PIL.Image: Annotated image
"""
# Make a copy to avoid modifying the original
annotated_image = image.copy()
draw = ImageDraw.Draw(annotated_image)
# Try to load a font
try:
font = ImageFont.truetype("arial.ttf", 16)
except:
font = ImageFont.load_default()
# Draw each detection
for detection in detections:
bbox = detection['bbox']
confidence = detection['confidence']
class_name = detection['class_name']
# Extract coordinates
x1, y1, x2, y2 = bbox
# Draw bounding box
color = self.colors[0] # Green for memory modules
draw.rectangle([x1, y1, x2, y2], outline=color, width=3)
# Draw label
label = f"{class_name}: {confidence:.2f}"
# Get text size for background
bbox_text = draw.textbbox((0, 0), label, font=font)
text_width = bbox_text[2] - bbox_text[0]
text_height = bbox_text[3] - bbox_text[1]
# Draw background for text
draw.rectangle([x1, y1 - text_height - 4, x1 + text_width + 4, y1],
fill=color, outline=color)
# Draw text
draw.text((x1 + 2, y1 - text_height - 2), label, fill=(255, 255, 255), font=font)
return annotated_image
def test_inference(image_path, model_path='runs/detect/memory_module_detection/weights/best.pt'):
"""
Test inference on a single image.
Args:
image_path (str): Path to test image
model_path (str): Path to trained model
"""
detector = MemoryModuleDetector(model_path)
if detector.model is None:
print("Cannot run inference without a trained model.")
return
print(f"Running inference on: {image_path}")
detections, annotated_image = detector.detect(image_path)
print(f"Found {len(detections)} memory modules:")
for i, detection in enumerate(detections):
print(f" {i+1}. {detection['class_name']} (confidence: {detection['confidence']:.3f})")
# Save annotated image
output_path = f"annotated_{os.path.basename(image_path)}"
annotated_image.save(output_path)
print(f"Annotated image saved as: {output_path}")
return detections, annotated_image
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description='Test memory module detection')
parser.add_argument('--image', type=str, required=True, help='Path to test image')
parser.add_argument('--model', type=str, default='runs/detect/memory_module_detection/weights/best.pt',
help='Path to trained model')
parser.add_argument('--conf', type=float, default=0.5, help='Confidence threshold')
args = parser.parse_args()
test_inference(args.image, args.model)
-408
View File
@@ -1,408 +0,0 @@
#!/usr/bin/env python3
"""
Flask API for Memory Module Detection
This API processes motherboard images and detects memory modules using YOLOv8.
"""
import os
import io
import base64
from flask import Flask, request, jsonify, send_file, render_template
from flask_cors import CORS
from PIL import Image
import numpy as np
from werkzeug.utils import secure_filename
import tempfile
import logging
from inference_utils import MemoryModuleDetector
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Initialize Flask app
app = Flask(__name__)
CORS(app)
# Configuration
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB max file size
UPLOAD_FOLDER = 'uploads'
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif', 'bmp'}
# Create upload folder if it doesn't exist
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
# Initialize detector
MODEL_PATH = 'runs/detect/memory_module_detection/weights/best.pt'
detector = MemoryModuleDetector(MODEL_PATH)
# Hardcoded test image path
HARDCODED_IMAGE_PATH = 'training/memory/out1.png'
def allowed_file(filename):
"""Check if file extension is allowed."""
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
def image_to_base64(image):
"""Convert PIL Image to base64 string."""
buffer = io.BytesIO()
image.save(buffer, format='PNG')
img_str = base64.b64encode(buffer.getvalue()).decode()
return img_str
def base64_to_image(base64_string):
"""Convert base64 string to PIL Image."""
img_data = base64.b64decode(base64_string)
image = Image.open(io.BytesIO(img_data))
return image
@app.route('/', methods=['GET'])
def home():
"""Home endpoint - serve frontend or API information based on Accept header."""
# Check if request is from a browser (wants HTML)
if 'text/html' in request.headers.get('Accept', ''):
return render_template('index.html')
# Otherwise return JSON API information
return jsonify({
'message': 'Memory Module Detection API',
'version': '1.0.0',
'endpoints': {
'/': 'GET - Frontend interface or API information',
'/api': 'GET - API information (JSON)',
'/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)
})
@app.route('/api', methods=['GET'])
def api_info():
"""API information endpoint (always returns JSON)."""
return jsonify({
'message': 'Memory Module Detection API',
'version': '1.0.0',
'endpoints': {
'/': 'GET - Frontend interface or API information',
'/api': 'GET - API information (JSON)',
'/docs': 'GET - Detailed API documentation',
'/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),
'documentation': 'Visit /docs for detailed API documentation'
})
@app.route('/docs')
def api_docs():
"""Serve API documentation."""
try:
with open('API_DOCS.md', 'r') as f:
docs_content = f.read()
# Convert markdown to HTML for better display
html_content = f"""
<!DOCTYPE html>
<html>
<head>
<title>Memory Module Detection API - Documentation</title>
<style>
body {{ font-family: Arial, sans-serif; max-width: 1200px; margin: 0 auto; padding: 20px; line-height: 1.6; }}
pre {{ background: #f4f4f4; padding: 15px; border-radius: 5px; overflow-x: auto; }}
code {{ background: #f4f4f4; padding: 2px 4px; border-radius: 3px; }}
h1, h2, h3 {{ color: #333; }}
.nav {{ background: #e8f5e8; padding: 10px; margin-bottom: 20px; border-radius: 5px; }}
.nav a {{ margin-right: 15px; text-decoration: none; color: #0066cc; }}
.nav a:hover {{ text-decoration: underline; }}
</style>
</head>
<body>
<div class="nav">
<a href="/">🏠 Web Interface</a>
<a href="/api">📊 API Info</a>
<a href="/health">💚 Health Check</a>
</div>
<pre>{docs_content}</pre>
</body>
</html>
"""
return html_content
except FileNotFoundError:
return jsonify({
'error': 'API documentation file not found',
'message': 'Please ensure API_DOCS.md exists in the project directory'
}), 404
@app.route('/health', methods=['GET'])
def health_check():
"""Health check endpoint."""
return jsonify({
'status': 'healthy',
'model_loaded': detector.model is not None,
'model_path': MODEL_PATH
})
@app.route('/detect', methods=['POST'])
def detect_memory_modules():
"""
Detect memory modules in uploaded image.
Expected input:
- File upload with key 'image'
- Optional: confidence threshold as form data
Returns:
- JSON with detections and annotated image (base64)
"""
try:
# Check if model is loaded
if detector.model is None:
return jsonify({
'error': 'Model not loaded. Please train the model first.',
'success': False
}), 500
# Check if file is present
if 'image' not in request.files:
return jsonify({
'error': 'No image file provided',
'success': False
}), 400
file = request.files['image']
# Check if file is selected
if file.filename == '':
return jsonify({
'error': 'No file selected',
'success': False
}), 400
# Check file extension
if not allowed_file(file.filename):
return jsonify({
'error': f'File type not allowed. Supported formats: {ALLOWED_EXTENSIONS}',
'success': False
}), 400
# Get confidence threshold from form data (default 80%)
conf_threshold = float(request.form.get('confidence', 0.8))
# Save uploaded file temporarily
filename = secure_filename(file.filename)
temp_path = os.path.join(UPLOAD_FOLDER, filename)
file.save(temp_path)
try:
# Run detection
detections, annotated_image = detector.detect(
temp_path,
conf_threshold=conf_threshold
)
# Convert annotated image to base64
annotated_base64 = image_to_base64(annotated_image)
# Prepare response
response_data = {
'success': True,
'detections': detections,
'num_detections': len(detections),
'annotated_image': annotated_base64,
'confidence_threshold': conf_threshold,
'original_filename': filename
}
logger.info(f"Processed {filename}: found {len(detections)} memory modules")
return jsonify(response_data)
finally:
# Clean up temporary file
if os.path.exists(temp_path):
os.remove(temp_path)
except Exception as e:
logger.error(f"Error processing image: {str(e)}")
return jsonify({
'error': f'Error processing image: {str(e)}',
'success': False
}), 500
@app.route('/detect/hardcoded', methods=['GET'])
def detect_hardcoded_image():
"""
Process hardcoded test image for memory module detection.
Optional query parameters:
- confidence: confidence threshold (default: 0.8)
Returns:
- JSON with detections and annotated image (base64)
"""
try:
# Check if model is loaded
if detector.model is None:
return jsonify({
'error': 'Model not loaded. Please train the model first.',
'success': False
}), 500
# Check if hardcoded image exists
if not os.path.exists(HARDCODED_IMAGE_PATH):
return jsonify({
'error': f'Hardcoded test image not found at {HARDCODED_IMAGE_PATH}',
'success': False
}), 404
# Get confidence threshold from query parameters (default 80%)
conf_threshold = float(request.args.get('confidence', 0.8))
# Run detection
detections, annotated_image = detector.detect(
HARDCODED_IMAGE_PATH,
conf_threshold=conf_threshold
)
# Convert annotated image to base64
annotated_base64 = image_to_base64(annotated_image)
# Prepare response
response_data = {
'success': True,
'detections': detections,
'num_detections': len(detections),
'annotated_image': annotated_base64,
'confidence_threshold': conf_threshold,
'test_image_path': HARDCODED_IMAGE_PATH
}
logger.info(f"Processed hardcoded image: found {len(detections)} memory modules")
return jsonify(response_data)
except Exception as e:
logger.error(f"Error processing hardcoded image: {str(e)}")
return jsonify({
'error': f'Error processing hardcoded image: {str(e)}',
'success': False
}), 500
@app.route('/detect/base64', methods=['POST'])
def detect_base64_image():
"""
Detect memory modules in base64 encoded image.
Expected JSON input:
{
"image": "base64_encoded_image_string",
"confidence": 0.5 // optional
}
Returns:
- JSON with detections and annotated image (base64)
"""
try:
# Check if model is loaded
if detector.model is None:
return jsonify({
'error': 'Model not loaded. Please train the model first.',
'success': False
}), 500
# Get JSON data
data = request.get_json()
if not data or 'image' not in data:
return jsonify({
'error': 'No base64 image data provided',
'success': False
}), 400
# Get confidence threshold (default 80%)
conf_threshold = float(data.get('confidence', 0.8))
# Decode base64 image
try:
image = base64_to_image(data['image'])
except Exception as e:
return jsonify({
'error': f'Invalid base64 image data: {str(e)}',
'success': False
}), 400
# Run detection
detections, annotated_image = detector.detect_from_array(
np.array(image),
conf_threshold=conf_threshold
)
# Convert annotated image to base64
annotated_base64 = image_to_base64(annotated_image)
# Prepare response
response_data = {
'success': True,
'detections': detections,
'num_detections': len(detections),
'annotated_image': annotated_base64,
'confidence_threshold': conf_threshold
}
logger.info(f"Processed base64 image: found {len(detections)} memory modules")
return jsonify(response_data)
except Exception as e:
logger.error(f"Error processing base64 image: {str(e)}")
return jsonify({
'error': f'Error processing base64 image: {str(e)}',
'success': False
}), 500
@app.errorhandler(413)
def too_large(e):
"""Handle file too large error."""
return jsonify({
'error': 'File too large. Maximum size is 16MB.',
'success': False
}), 413
@app.errorhandler(404)
def not_found(e):
"""Handle 404 errors."""
return jsonify({
'error': 'Endpoint not found',
'success': False
}), 404
@app.errorhandler(500)
def internal_error(e):
"""Handle internal server errors."""
return jsonify({
'error': 'Internal server error',
'success': False
}), 500
if __name__ == '__main__':
# Check if model exists
if not os.path.exists(MODEL_PATH):
print(f"Warning: Model not found at {MODEL_PATH}")
print("Please train the model first using: python3 train.py")
print("The API will still start but detection endpoints will return errors.")
# Start the Flask app
print("Starting Memory Module Detection API...")
print(f"Model path: {MODEL_PATH}")
print(f"Model loaded: {detector.model is not None}")
print(f"Hardcoded test image: {HARDCODED_IMAGE_PATH}")
app.run(host='0.0.0.0', port=5002, debug=True)
+5 -30
View File
@@ -1,30 +1,5 @@
# Core ML and Computer Vision flask
ultralytics==8.0.196 ultralytics
torch>=1.9.0 pillow
torchvision>=0.10.0 numpy
opencv-python==4.8.1.78 python-multipart
Pillow==10.0.1
# Web Framework
Flask==2.3.3
Flask-CORS==4.0.0
Flask-RESTX==1.3.0
Werkzeug==2.3.7
# Data Processing
numpy==1.24.3
pandas==2.0.3
# Image Processing and Visualization
matplotlib==3.7.2
seaborn==0.12.2
# Utilities
PyYAML==6.0.1
requests==2.31.0
tqdm==4.66.1
pathlib2>=2.3.0;python_version<"3.4"
# Optional GPU support (uncomment if using CUDA)
# torch==2.0.1+cu118 --index-url https://download.pytorch.org/whl/cu118
# torchvision==0.15.2+cu118 --index-url https://download.pytorch.org/whl/cu118
+6
View File
@@ -0,0 +1,6 @@
from app import create_app
app = create_app()
if __name__ == '__main__':
app.run(debug=True)
-133
View File
@@ -1,133 +0,0 @@
#!/usr/bin/env python3
"""
Setup script for Memory Module Detection Project
This script helps users set up the project quickly.
"""
import os
import sys
import subprocess
import time
def run_command(command, description):
"""Run a command and handle errors."""
print(f"🔄 {description}...")
try:
result = subprocess.run(command, shell=True, check=True, capture_output=True, text=True)
print(f"{description} completed successfully")
return True
except subprocess.CalledProcessError as e:
print(f"{description} failed:")
print(f" Command: {command}")
print(f" Error: {e.stderr}")
return False
def check_python_version():
"""Check if Python version is compatible."""
print("🐍 Checking Python version...")
version = sys.version_info
if version.major == 3 and version.minor >= 8:
print(f"✅ Python {version.major}.{version.minor}.{version.micro} is compatible")
return True
else:
print(f"❌ Python {version.major}.{version.minor}.{version.micro} is not compatible")
print(" Please use Python 3.8 or higher")
return False
def check_files():
"""Check if required files exist."""
print("📁 Checking project files...")
required_files = [
'requirements.txt',
'main.py',
'train.py',
'inference_utils.py',
'prepare_dataset.py',
'dataset.yaml',
'training/memory',
'training/no_memory'
]
missing_files = []
for file in required_files:
if not os.path.exists(file):
missing_files.append(file)
if missing_files:
print(f"❌ Missing files: {missing_files}")
return False
else:
print("✅ All required files found")
return True
def install_dependencies():
"""Install Python dependencies."""
if not run_command("pip install -r requirements.txt", "Installing dependencies"):
print(" Try using: pip3 install -r requirements.txt")
return run_command("pip3 install -r requirements.txt", "Installing dependencies with pip3")
return True
def prepare_dataset():
"""Prepare the dataset structure."""
if os.path.exists('training/train/images') and os.path.exists('training/val/images'):
print("✅ Dataset already prepared")
return True
return run_command("python3 prepare_dataset.py", "Preparing dataset structure")
def train_model():
"""Train the YOLOv8 model."""
model_path = 'runs/detect/memory_module_detection/weights/best.pt'
if os.path.exists(model_path):
print("✅ Model already trained")
return True
print("🤖 Training YOLOv8 model...")
print(" This may take 5-60 minutes depending on your hardware...")
return run_command("python3 train.py --epochs 50 --batch 8", "Training YOLOv8 model")
def test_setup():
"""Test the setup by running a quick inference."""
print("🧪 Testing setup...")
return run_command("python3 test_api.py", "Running API tests")
def main():
"""Main setup function."""
print("🚀 Memory Module Detection Project Setup")
print("=" * 50)
# Check prerequisites
if not check_python_version():
return False
if not check_files():
return False
# Setup steps
steps = [
("Install Dependencies", install_dependencies),
("Prepare Dataset", prepare_dataset),
("Train Model", train_model)
]
for step_name, step_func in steps:
print(f"\n📋 Step: {step_name}")
if not step_func():
print(f"❌ Setup failed at step: {step_name}")
return False
print("\n" + "=" * 50)
print("🎉 Setup completed successfully!")
print("\n📖 Next steps:")
print("1. Start the API:")
print(" python3 main.py")
print("\n2. Test the API (in another terminal):")
print(" python3 test_api.py")
print("\n3. Or test manually:")
print(" curl http://localhost:5000/detect/hardcoded")
return True
if __name__ == "__main__":
success = main()
sys.exit(0 if success else 1)
-505
View File
@@ -1,505 +0,0 @@
// Memory Module Detection QA Interface JavaScript
const API_BASE_URL = 'http://localhost:5002';
let uploadedFile = null;
let lastDetectionResult = null;
// Initialize the application
document.addEventListener('DOMContentLoaded', function() {
initializeApp();
setupEventListeners();
});
function initializeApp() {
checkApiStatus();
loadApiInfo();
}
function setupEventListeners() {
// File input change
document.getElementById('fileInput').addEventListener('change', handleFileSelect);
// Drag and drop
const uploadArea = document.getElementById('uploadArea');
uploadArea.addEventListener('dragover', handleDragOver);
uploadArea.addEventListener('dragleave', handleDragLeave);
uploadArea.addEventListener('drop', handleDrop);
// Click to upload (only on the upload area, not buttons inside it)
uploadArea.addEventListener('click', function(event) {
// Only trigger file input if clicking on the upload area itself, not buttons
if (event.target === uploadArea || (event.target.closest('.upload-content') && !event.target.closest('button'))) {
console.log('Upload area clicked, triggering file input');
const fileInput = document.getElementById('fileInput');
if (fileInput) {
fileInput.click();
}
}
});
}
async function checkApiStatus() {
const statusElement = document.getElementById('apiStatus');
try {
const response = await fetch(`${API_BASE_URL}/health`);
if (response.ok) {
const data = await response.json();
statusElement.className = 'status-indicator online';
statusElement.innerHTML = '<i class="fas fa-circle"></i> <span>API Online</span>';
} else {
throw new Error('API not responding');
}
} catch (error) {
statusElement.className = 'status-indicator offline';
statusElement.innerHTML = '<i class="fas fa-circle"></i> <span>API Offline</span>';
}
}
async function loadApiInfo() {
const apiInfoElement = document.getElementById('apiInfo');
try {
const response = await fetch(`${API_BASE_URL}/api`);
if (response.ok) {
const data = await response.json();
displayApiInfo(data);
} else {
throw new Error('Failed to load API info');
}
} catch (error) {
apiInfoElement.innerHTML = '<div class="error">Failed to load API information</div>';
}
}
function displayApiInfo(data) {
const apiInfoElement = document.getElementById('apiInfo');
apiInfoElement.innerHTML = `
<div class="info-item">
<h3>API Status</h3>
<p>${data.message}</p>
</div>
<div class="info-item">
<h3>Version</h3>
<p>${data.version}</p>
</div>
<div class="info-item">
<h3>Model Status</h3>
<p>${data.model_loaded ? 'Loaded ✅' : 'Not Loaded ❌'}</p>
</div>
<div class="info-item">
<h3>Supported Formats</h3>
<p>${data.supported_formats.join(', ')}</p>
</div>
`;
}
function showUploadSection() {
document.getElementById('uploadSection').style.display = 'block';
document.getElementById('uploadSection').scrollIntoView({ behavior: 'smooth' });
// Ensure the upload area is properly initialized
initializeUploadArea();
}
function initializeUploadArea() {
const uploadArea = document.getElementById('uploadArea');
let fileInput = document.getElementById('fileInput');
// Completely recreate the file input element
if (fileInput) {
fileInput.remove();
}
// Create a brand new file input
const newFileInput = document.createElement('input');
newFileInput.type = 'file';
newFileInput.id = 'fileInput';
newFileInput.accept = 'image/*';
newFileInput.style.display = 'none';
newFileInput.multiple = false;
// Insert the new file input into the DOM
uploadArea.parentNode.insertBefore(newFileInput, uploadArea);
// Clear any existing event listeners on upload area by cloning
const newUploadArea = uploadArea.cloneNode(true);
uploadArea.parentNode.replaceChild(newUploadArea, uploadArea);
// Re-attach all event listeners to the new elements
newFileInput.addEventListener('change', handleFileSelect);
newUploadArea.addEventListener('dragover', handleDragOver);
newUploadArea.addEventListener('dragleave', handleDragLeave);
newUploadArea.addEventListener('drop', handleDrop);
newUploadArea.addEventListener('click', function(event) {
if (event.target === newUploadArea || (event.target.closest('.upload-content') && !event.target.closest('button'))) {
console.log('Upload area clicked, triggering file input');
const currentFileInput = document.getElementById('fileInput');
if (currentFileInput) {
currentFileInput.click();
}
}
});
console.log('Upload area completely reinitialized with fresh file input');
}
function handleFileSelect(event) {
console.log('File select event triggered');
const file = event.target.files[0];
if (file) {
console.log('File selected:', file.name);
handleFile(file);
} else {
console.log('No file selected');
}
}
function handleDragOver(event) {
event.preventDefault();
event.stopPropagation();
event.currentTarget.classList.add('dragover');
}
function handleDragLeave(event) {
event.preventDefault();
event.stopPropagation();
event.currentTarget.classList.remove('dragover');
}
function handleDrop(event) {
event.preventDefault();
event.stopPropagation();
event.currentTarget.classList.remove('dragover');
const files = event.dataTransfer.files;
if (files.length > 0) {
handleFile(files[0]);
}
}
function handleFile(file) {
if (!file.type.startsWith('image/')) {
alert('Please select an image file');
return;
}
uploadedFile = file;
// Hide test results when new image is uploaded
const testResultsSection = document.getElementById('testResultsSection');
if (testResultsSection) {
testResultsSection.style.display = 'none';
}
// Show file info with change file option
const uploadArea = document.getElementById('uploadArea');
uploadArea.innerHTML = `
<div class="upload-content">
<i class="fas fa-check-circle upload-icon" style="color: #28a745;"></i>
<p><strong>File selected:</strong> ${file.name}</p>
<p class="upload-hint">Size: ${(file.size / 1024 / 1024).toFixed(2)} MB</p>
<button class="btn btn-outline" onclick="resetFileUpload()" style="margin-top: 10px;">
<i class="fas fa-sync-alt"></i> Change File
</button>
</div>
`;
// Show controls
document.getElementById('uploadControls').style.display = 'block';
}
function resetFileUpload() {
uploadedFile = null;
lastDetectionResult = null; // Reset last detection result
// Reset upload area HTML
const uploadArea = document.getElementById('uploadArea');
uploadArea.innerHTML = `
<div class="upload-content">
<i class="fas fa-cloud-upload-alt upload-icon"></i>
<p>Drag and drop an image here or click to select</p>
<p class="upload-hint">Supported formats: PNG, JPG, JPEG, GIF, BMP</p>
<button class="btn btn-outline" onclick="document.getElementById('fileInput').click()">
Select Image
</button>
</div>
`;
// Hide controls
const uploadControls = document.getElementById('uploadControls');
uploadControls.style.display = 'none';
// Remove the "Upload Another" button if it exists
const uploadAnotherBtn = uploadControls.querySelector('.upload-another');
if (uploadAnotherBtn) {
uploadAnotherBtn.remove();
}
// Hide results if showing
document.getElementById('resultsSection').style.display = 'none';
// Hide test results when file is reset
const testResultsSection = document.getElementById('testResultsSection');
if (testResultsSection) {
testResultsSection.style.display = 'none';
}
// Reinitialize the upload area with fresh event listeners
initializeUploadArea();
console.log('File upload reset completed');
}
async function processUploadedImage() {
if (!uploadedFile) {
alert('Please select an image first');
return;
}
const confidence = 0.8; // Fixed 80% threshold
showLoading('Processing uploaded image...');
try {
const formData = new FormData();
formData.append('image', uploadedFile);
formData.append('confidence', confidence);
const response = await fetch(`${API_BASE_URL}/detect`, {
method: 'POST',
body: formData
});
const result = await response.json();
hideLoading();
if (result.success) {
// Store the last detection result for Run All Tests
lastDetectionResult = result;
displayResults(result, 'Uploaded Image Detection');
// Add option to upload another file
addUploadAnotherOption();
} else {
alert(`Detection failed: ${result.error}`);
}
} catch (error) {
hideLoading();
alert(`Error: ${error.message}`);
}
}
function addUploadAnotherOption() {
const uploadControls = document.getElementById('uploadControls');
if (!uploadControls.querySelector('.upload-another')) {
const uploadAnotherBtn = document.createElement('button');
uploadAnotherBtn.className = 'btn btn-secondary upload-another';
uploadAnotherBtn.style.marginLeft = '10px';
uploadAnotherBtn.innerHTML = '<i class="fas fa-plus"></i> Upload Another Image';
uploadAnotherBtn.onclick = resetFileUpload;
uploadControls.appendChild(uploadAnotherBtn);
}
}
async function testHardcodedImage() {
showLoading('Testing hardcoded image...');
try {
console.log(`Making request to: ${API_BASE_URL}/detect/hardcoded?confidence=0.8`);
const response = await fetch(`${API_BASE_URL}/detect/hardcoded?confidence=0.8`);
console.log('Response status:', response.status);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const result = await response.json();
console.log('Response data:', result);
hideLoading();
if (result.success) {
// Store the last detection result for Run All Tests
lastDetectionResult = result;
displayResults(result, 'Hardcoded Image Test');
} else {
alert(`Test failed: ${result.error}`);
}
} catch (error) {
hideLoading();
console.error('Hardcoded test error:', error);
alert(`Error: ${error.message}`);
}
}
function displayResults(result, title) {
const resultsSection = document.getElementById('resultsSection');
const resultsContent = document.getElementById('resultsContent');
let detectionsHtml = '';
if (result.detections && result.detections.length > 0) {
detectionsHtml = result.detections.map((detection, index) => `
<div class="detection-item">
<span>Detection ${index + 1}: ${detection.class_name}</span>
<span class="detection-confidence">${(detection.confidence * 100).toFixed(1)}%</span>
</div>
`).join('');
} else {
detectionsHtml = '<div class="detection-item">No memory modules detected</div>';
}
resultsContent.innerHTML = `
<div class="result-item">
<div class="result-header">
<h3>${title}</h3>
<span class="badge">${new Date().toLocaleTimeString()}</span>
</div>
<div class="result-stats">
<div class="stat-item">
<div class="stat-value">${result.num_detections}</div>
<div class="stat-label">Detections</div>
</div>
<div class="stat-item">
<div class="stat-value">${(result.confidence_threshold * 100).toFixed(0)}%</div>
<div class="stat-label">Confidence</div>
</div>
<div class="stat-item">
<div class="stat-value">${result.success ? 'Success' : 'Failed'}</div>
<div class="stat-label">Status</div>
</div>
</div>
<div class="detection-list">
<h4>Detected Memory Modules:</h4>
${detectionsHtml}
</div>
${result.annotated_image ? `
<div class="image-container">
<h4>Annotated Image:</h4>
<img src="data:image/png;base64,${result.annotated_image}"
alt="Annotated Result" class="result-image">
</div>
` : ''}
</div>
`;
resultsSection.style.display = 'block';
resultsSection.scrollIntoView({ behavior: 'smooth' });
}
async function runAllTests() {
showLoading('Running comprehensive tests...');
const testResults = [];
// Test 1: API Health
try {
const response = await fetch(`${API_BASE_URL}/health`);
const result = await response.json();
testResults.push({
name: 'API Health Check',
success: response.ok && result.status === 'healthy',
message: response.ok ? 'API is healthy' : 'API health check failed'
});
} catch (error) {
testResults.push({
name: 'API Health Check',
success: false,
message: `Error: ${error.message}`
});
}
// Test 2: Image with Memory Modules (use last detection result if available)
if (lastDetectionResult) {
// Use the last detection result from uploaded/tested image
testResults.push({
name: 'Image with Memory Modules',
success: lastDetectionResult.success,
message: lastDetectionResult.success ?
(lastDetectionResult.num_detections > 0 ?
`✅ Found ${lastDetectionResult.num_detections} memory modules` :
`❌ No memory modules`) :
`❌ Error: ${lastDetectionResult.error}`
});
} else {
// Fallback to hardcoded test if no previous detection
try {
const response = await fetch(`${API_BASE_URL}/detect/hardcoded`);
const result = await response.json();
lastDetectionResult = result; // Store for future use
testResults.push({
name: 'Image with Memory Modules',
success: result.success,
message: result.success ?
(result.num_detections > 0 ?
`✅ Found ${result.num_detections} memory modules` :
`❌ No memory modules`) :
`❌ Error: ${result.error}`
});
} catch (error) {
testResults.push({
name: 'Image with Memory Modules',
success: false,
message: `❌ Error: ${error.message}`
});
}
}
// Test 3: API Information
try {
const response = await fetch(`${API_BASE_URL}/api`);
const result = await response.json();
testResults.push({
name: 'API Information',
success: response.ok && result.message,
message: response.ok ? 'API info loaded successfully' : 'Failed to load API info'
});
} catch (error) {
testResults.push({
name: 'API Information',
success: false,
message: `Error: ${error.message}`
});
}
hideLoading();
displayTestResults(testResults);
}
function displayTestResults(testResults) {
const testResultsSection = document.getElementById('testResultsSection');
const testResultsContent = document.getElementById('testResults');
const successCount = testResults.filter(test => test.success).length;
const totalTests = testResults.length;
const testsHtml = testResults.map(test => `
<div class="test-item ${test.success ? 'success' : 'error'}">
<h3>
<i class="fas ${test.success ? 'fa-check-circle' : 'fa-times-circle'}"></i>
${test.name}
</h3>
<p>${test.message}</p>
</div>
`).join('');
testResultsContent.innerHTML = `
<div class="test-summary">
<h3>Test Summary: ${successCount}/${totalTests} tests passed</h3>
</div>
${testsHtml}
`;
testResultsSection.style.display = 'block';
testResultsSection.scrollIntoView({ behavior: 'smooth' });
}
function showLoading(message) {
document.getElementById('loadingText').textContent = message;
document.getElementById('loadingOverlay').style.display = 'flex';
}
function hideLoading() {
document.getElementById('loadingOverlay').style.display = 'none';
}
-413
View File
@@ -1,413 +0,0 @@
/* Memory Module Detection QA Interface Styles */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
color: #333;
display: flex;
flex-direction: column;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
flex: 1;
display: flex;
flex-direction: column;
}
header {
text-align: center;
margin-bottom: 30px;
color: white;
}
header h1 {
font-size: 2.5rem;
margin-bottom: 10px;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
}
header p {
font-size: 1.2rem;
opacity: 0.9;
margin-bottom: 20px;
}
.status-indicator {
display: inline-flex;
align-items: center;
gap: 8px;
background: rgba(255,255,255,0.2);
padding: 8px 16px;
border-radius: 20px;
backdrop-filter: blur(10px);
}
.status-indicator.online {
background: rgba(76, 175, 80, 0.3);
}
.status-indicator.offline {
background: rgba(244, 67, 54, 0.3);
}
.panel {
background: white;
border-radius: 12px;
padding: 25px;
margin-bottom: 20px;
box-shadow: 0 8px 32px rgba(0,0,0,0.1);
backdrop-filter: blur(10px);
}
.panel h2 {
color: #333;
margin-bottom: 20px;
font-size: 1.5rem;
display: flex;
align-items: center;
gap: 10px;
}
.panel h2 i {
color: #667eea;
}
.api-info {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 15px;
}
.info-item {
background: #f8f9fa;
padding: 15px;
border-radius: 8px;
border-left: 4px solid #667eea;
}
.info-item h3 {
color: #333;
margin-bottom: 8px;
font-size: 1rem;
}
.info-item p {
color: #666;
font-size: 0.9rem;
}
.test-options {
display: flex;
gap: 15px;
flex-wrap: wrap;
}
.btn {
padding: 12px 24px;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 1rem;
font-weight: 500;
transition: all 0.3s ease;
display: inline-flex;
align-items: center;
gap: 8px;
text-decoration: none;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
}
.btn-primary {
background: #667eea;
color: white;
}
.btn-secondary {
background: #6c757d;
color: white;
}
.btn-info {
background: #17a2b8;
color: white;
}
.btn-success {
background: #28a745;
color: white;
}
.btn-outline {
background: transparent;
color: #667eea;
border: 2px solid #667eea;
}
.upload-area {
border: 3px dashed #ddd;
border-radius: 12px;
padding: 40px;
text-align: center;
transition: all 0.3s ease;
cursor: pointer;
}
.upload-area:hover,
.upload-area.dragover {
border-color: #667eea;
background: rgba(102, 126, 234, 0.05);
}
.upload-icon {
font-size: 3rem;
color: #667eea;
margin-bottom: 15px;
}
.upload-hint {
color: #666;
font-size: 0.9rem;
margin-top: 10px;
}
.upload-controls {
margin-top: 20px;
padding: 20px;
background: #f8f9fa;
border-radius: 8px;
}
.confidence-info {
margin-bottom: 15px;
padding: 10px;
background: #e8f5e8;
border-radius: 6px;
border-left: 4px solid #28a745;
}
.confidence-info p {
margin: 0;
color: #155724;
font-size: 0.9rem;
}
.results-content {
display: grid;
gap: 20px;
}
.result-item {
background: #f8f9fa;
border-radius: 8px;
padding: 20px;
border-left: 4px solid #28a745;
}
.result-header {
display: flex;
justify-content: between;
align-items: center;
margin-bottom: 15px;
}
.result-stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 15px;
margin-bottom: 20px;
}
.stat-item {
text-align: center;
padding: 10px;
background: white;
border-radius: 6px;
}
.stat-value {
font-size: 1.5rem;
font-weight: bold;
color: #667eea;
}
.stat-label {
font-size: 0.9rem;
color: #666;
}
.image-container {
text-align: center;
margin-top: 20px;
}
.result-image {
max-width: 100%;
height: auto;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.detection-list {
margin-top: 15px;
}
.detection-item {
background: white;
padding: 10px 15px;
margin-bottom: 8px;
border-radius: 6px;
display: flex;
justify-content: space-between;
align-items: center;
}
.detection-confidence {
background: #28a745;
color: white;
padding: 4px 8px;
border-radius: 12px;
font-size: 0.8rem;
font-weight: bold;
}
.test-results {
display: grid;
gap: 15px;
}
.test-item {
background: #f8f9fa;
padding: 15px;
border-radius: 8px;
border-left: 4px solid #ddd;
}
.test-item.success {
border-left-color: #28a745;
}
.test-item.error {
border-left-color: #dc3545;
}
.test-item h3 {
margin-bottom: 8px;
display: flex;
align-items: center;
gap: 8px;
}
.loading {
text-align: center;
padding: 40px;
color: #666;
}
.loading-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.7);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.loading-content {
background: white;
padding: 30px;
border-radius: 12px;
text-align: center;
}
.loading-content i {
font-size: 2rem;
color: #667eea;
margin-bottom: 15px;
}
main {
flex: 1;
}
.summary-message {
padding: 15px;
margin: 15px 0;
border-radius: 8px;
font-weight: 600;
font-size: 1.1rem;
text-align: center;
}
.summary-message.success {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.summary-message.no-memory {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.summary-message.error {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.footer {
text-align: center;
margin-top: auto;
padding: 20px 0;
color: rgba(255,255,255,0.8);
background: rgba(0,0,0,0.1);
backdrop-filter: blur(10px);
border-top: 1px solid rgba(255,255,255,0.1);
}
/* Responsive Design */
@media (max-width: 768px) {
.container {
padding: 10px;
}
header h1 {
font-size: 2rem;
}
.test-options {
flex-direction: column;
}
.btn {
width: 100%;
justify-content: center;
}
.api-info {
grid-template-columns: 1fr;
}
.result-stats {
grid-template-columns: repeat(2, 1fr);
}
}
-101
View File
@@ -1,101 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Memory Module Detection - QA Testing Interface</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
<header>
<h1><i class="fas fa-microchip"></i> Memory Module Detection</h1>
<p>AI-Powered Motherboard Memory Module Detection</p>
</header>
<main>
<!-- API Information Panel -->
<section class="panel" id="apiInfoPanel">
<h2><i class="fas fa-info-circle"></i> API Information</h2>
<div class="api-info" id="apiInfo">
<div class="loading">Loading API information...</div>
</div>
</section>
<!-- Test Options -->
<section class="panel">
<h2><i class="fas fa-vial"></i> Test Options</h2>
<div class="test-options">
<button class="btn btn-primary" onclick="testHardcodedImage()">
<i class="fas fa-image"></i> Test Hardcoded Image
</button>
<button class="btn btn-secondary" onclick="showUploadSection()">
<i class="fas fa-upload"></i> Upload Custom Image
</button>
<button class="btn btn-info" onclick="runAllTests()">
<i class="fas fa-play"></i> Run All Tests
</button>
</div>
</section>
<!-- Image Upload Section -->
<section class="panel" id="uploadSection" style="display: none;">
<h2><i class="fas fa-cloud-upload-alt"></i> Upload Image</h2>
<div class="upload-area" id="uploadArea">
<div class="upload-content">
<i class="fas fa-cloud-upload-alt upload-icon"></i>
<p>Drag and drop an image here or click to select</p>
<p class="upload-hint">Supported formats: PNG, JPG, JPEG, GIF, BMP</p>
<input type="file" id="fileInput" accept="image/*" style="display: none;" multiple="false">
<button class="btn btn-outline" onclick="document.getElementById('fileInput').click()">
Select Image
</button>
</div>
</div>
<div class="upload-controls" style="display: none;" id="uploadControls">
<div class="confidence-info">
<p><strong>Confidence Threshold:</strong> 80% (High Precision Mode)</p>
</div>
<button class="btn btn-success" onclick="processUploadedImage()">
<i class="fas fa-search"></i> Detect Memory Modules
</button>
</div>
</section>
<!-- Results Section -->
<section class="panel" id="resultsSection" style="display: none;">
<h2><i class="fas fa-chart-bar"></i> Detection Results</h2>
<div class="results-content" id="resultsContent">
<!-- Results will be populated here -->
</div>
</section>
<!-- Test Results Section -->
<section class="panel" id="testResultsSection" style="display: none;">
<h2><i class="fas fa-clipboard-check"></i> Test Results</h2>
<div class="test-results" id="testResults">
<!-- Test results will be populated here -->
</div>
</section>
</main>
<footer class="footer">
<p>&copy; 2025 Memory Module Detection Project</p>
</footer>
</div>
<!-- Loading Overlay -->
<div class="loading-overlay" id="loadingOverlay" style="display: none;">
<div class="loading-content">
<i class="fas fa-spinner fa-spin"></i>
<p id="loadingText">Processing...</p>
</div>
</div>
<script src="{{ url_for('static', filename='script.js') }}"></script>
</body>
</html>
-257
View File
@@ -1,257 +0,0 @@
#!/usr/bin/env python3
"""
Test script for Memory Module Detection API
This script tests all API endpoints and provides usage examples.
"""
import requests
import json
import base64
import os
from PIL import Image
import io
# API base URL
BASE_URL = "http://localhost:5002"
def test_api_info():
"""Test the API info endpoint."""
print("🔍 Testing API Info...")
try:
response = requests.get(f"{BASE_URL}/")
if response.status_code == 200:
data = response.json()
print(f"✅ API Info: {data['message']}")
print(f" Model loaded: {data['model_loaded']}")
print(f" Supported formats: {data['supported_formats']}")
return True
else:
print(f"❌ API Info failed: {response.status_code}")
return False
except Exception as e:
print(f"❌ API Info error: {e}")
return False
def test_health_check():
"""Test the health check endpoint."""
print("\n🏥 Testing Health Check...")
try:
response = requests.get(f"{BASE_URL}/health")
if response.status_code == 200:
data = response.json()
print(f"✅ Health: {data['status']}")
print(f" Model loaded: {data['model_loaded']}")
return True
else:
print(f"❌ Health check failed: {response.status_code}")
return False
except Exception as e:
print(f"❌ Health check error: {e}")
return False
def test_hardcoded_detection():
"""Test detection with hardcoded image."""
print("\n🖼️ Testing Hardcoded Image Detection...")
try:
response = requests.get(f"{BASE_URL}/detect/hardcoded?confidence=0.8")
if response.status_code == 200:
data = response.json()
if data['success']:
print(f"✅ Hardcoded detection successful!")
print(f" Found {data['num_detections']} memory modules")
for i, detection in enumerate(data['detections']):
print(f" Detection {i+1}: {detection['class_name']} "
f"(confidence: {detection['confidence']:.3f})")
# Save annotated image
if 'annotated_image' in data:
save_base64_image(data['annotated_image'], 'test_hardcoded_result.png')
print(" Annotated image saved as: test_hardcoded_result.png")
return True
else:
print(f"❌ Hardcoded detection failed: {data.get('error', 'Unknown error')}")
return False
else:
print(f"❌ Hardcoded detection failed: {response.status_code}")
if response.text:
print(f" Response: {response.text}")
return False
except Exception as e:
print(f"❌ Hardcoded detection error: {e}")
return False
def test_file_upload():
"""Test detection with file upload."""
print("\n📤 Testing File Upload Detection...")
# Find a test image
test_image_path = None
possible_paths = [
'training/memory/out1.png',
'training/memory/out2.png',
'training/val/images/memory_out8.png'
]
for path in possible_paths:
if os.path.exists(path):
test_image_path = path
break
if not test_image_path:
print("❌ No test image found. Skipping file upload test.")
return False
try:
with open(test_image_path, 'rb') as f:
files = {'image': f}
data = {'confidence': '0.8'}
response = requests.post(f"{BASE_URL}/detect", files=files, data=data)
if response.status_code == 200:
result = response.json()
if result['success']:
print(f"✅ File upload detection successful!")
print(f" Test image: {test_image_path}")
print(f" Found {result['num_detections']} memory modules")
for i, detection in enumerate(result['detections']):
print(f" Detection {i+1}: {detection['class_name']} "
f"(confidence: {detection['confidence']:.3f})")
# Save annotated image
if 'annotated_image' in result:
save_base64_image(result['annotated_image'], 'test_upload_result.png')
print(" Annotated image saved as: test_upload_result.png")
return True
else:
print(f"❌ File upload detection failed: {result.get('error', 'Unknown error')}")
return False
else:
print(f"❌ File upload detection failed: {response.status_code}")
if response.text:
print(f" Response: {response.text}")
return False
except Exception as e:
print(f"❌ File upload detection error: {e}")
return False
def test_base64_detection():
"""Test detection with base64 encoded image."""
print("\n🔢 Testing Base64 Detection...")
# Find a test image
test_image_path = None
possible_paths = [
'training/memory/out1.png',
'training/memory/out2.png'
]
for path in possible_paths:
if os.path.exists(path):
test_image_path = path
break
if not test_image_path:
print("❌ No test image found. Skipping base64 test.")
return False
try:
# Convert image to base64
with open(test_image_path, 'rb') as f:
image_data = f.read()
base64_string = base64.b64encode(image_data).decode('utf-8')
# Send request
payload = {
'image': base64_string,
'confidence': 0.8
}
response = requests.post(
f"{BASE_URL}/detect/base64",
json=payload,
headers={'Content-Type': 'application/json'}
)
if response.status_code == 200:
result = response.json()
if result['success']:
print(f"✅ Base64 detection successful!")
print(f" Test image: {test_image_path}")
print(f" Found {result['num_detections']} memory modules")
for i, detection in enumerate(result['detections']):
print(f" Detection {i+1}: {detection['class_name']} "
f"(confidence: {detection['confidence']:.3f})")
# Save annotated image
if 'annotated_image' in result:
save_base64_image(result['annotated_image'], 'test_base64_result.png')
print(" Annotated image saved as: test_base64_result.png")
return True
else:
print(f"❌ Base64 detection failed: {result.get('error', 'Unknown error')}")
return False
else:
print(f"❌ Base64 detection failed: {response.status_code}")
if response.text:
print(f" Response: {response.text}")
return False
except Exception as e:
print(f"❌ Base64 detection error: {e}")
return False
def save_base64_image(base64_string, filename):
"""Save base64 encoded image to file."""
try:
image_data = base64.b64decode(base64_string)
image = Image.open(io.BytesIO(image_data))
image.save(filename)
except Exception as e:
print(f" Warning: Could not save image {filename}: {e}")
def main():
"""Run all API tests."""
print("🧪 Memory Module Detection API Test Suite")
print("=" * 50)
# Check if API is running
try:
response = requests.get(f"{BASE_URL}/health", timeout=5)
except requests.exceptions.ConnectionError:
print("❌ API is not running!")
print(" Please start the API first: python3 main.py")
return
except Exception as e:
print(f"❌ Cannot connect to API: {e}")
return
# Run tests
tests = [
test_api_info,
test_health_check,
test_hardcoded_detection,
test_file_upload,
test_base64_detection
]
passed = 0
total = len(tests)
for test in tests:
if test():
passed += 1
print("\n" + "=" * 50)
print(f"🏁 Test Results: {passed}/{total} tests passed")
if passed == total:
print("🎉 All tests passed! The API is working correctly.")
else:
print("⚠️ Some tests failed. Check the output above for details.")
if passed == 0:
print(" Make sure the model is trained: python3 train.py")
if __name__ == "__main__":
main()
+182
View File
@@ -0,0 +1,182 @@
import pytest
from pathlib import Path
import numpy as np
from PIL import Image
import matplotlib.pyplot as plt
from app.utils.detector import MemoryDetector
import os
import json
from typing import List, Dict, Tuple
import logging
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class TestMemoryDetector:
@pytest.fixture(scope="class")
def results_dir(self):
"""Create and return results directory"""
dir_path = Path("test_results")
dir_path.mkdir(exist_ok=True)
logger.info(f"Created results directory: {dir_path}")
return dir_path
@pytest.fixture(scope="class")
def detector(self):
"""Initialize detector once for all tests"""
logger.info("Initializing MemoryDetector...")
return MemoryDetector()
@pytest.fixture(scope="class")
def test_images(self):
"""Load test images from validation directory"""
val_dir = Path('training/val/images')
assert val_dir.exists(), f"Validation directory not found: {val_dir}"
logger.info(f"Loading test images from {val_dir}")
images = []
for img_path in val_dir.glob('memory_*.png'):
images.append({
'path': str(img_path),
'image': Image.open(img_path)
})
logger.info(f"Loaded {len(images)} test images")
assert len(images) > 0, "No test images found"
return images
def test_detector_initialization(self, detector):
"""Test detector initialization and default parameters"""
logger.info("Testing detector initialization...")
assert detector.conf_threshold == 0.25
assert detector.iou_threshold == 0.45
assert detector.model is not None
logger.info("Detector initialization test passed")
def test_single_image_detection(self, detector, test_images, results_dir):
"""Test detection on a single image"""
logger.info("Testing single image detection...")
test_case = test_images[0]
result_img, detections = detector.detect(test_case['image'])
# Save the result
output_path = results_dir / "single_detection_test.png"
result_img.save(output_path)
logger.info(f"Saved detection result to {output_path}")
# Verify result type and content
assert isinstance(result_img, Image.Image)
assert isinstance(detections, list)
assert all(isinstance(d, dict) for d in detections)
# Log detection results
logger.info(f"Number of detections: {len(detections)}")
if len(detections) > 0:
for i, det in enumerate(detections):
logger.info(f"Detection {i+1}: confidence={det['confidence']:.3f}")
def test_batch_detection(self, detector, test_images, results_dir):
"""Test detection on multiple images"""
logger.info("Testing batch detection...")
results = []
for i, test_case in enumerate(test_images):
logger.info(f"Processing image {i+1}/{len(test_images)}")
result_img, detections = detector.detect(test_case['image'])
# Save each result
output_path = results_dir / f"batch_detection_{i}.png"
result_img.save(output_path)
results.append({
'path': test_case['path'],
'detections': len(detections),
'confidences': [d['confidence'] for d in detections]
})
# Save detailed results
results_path = results_dir / "batch_results.json"
with open(results_path, 'w') as f:
json.dump(results, f, indent=2)
logger.info(f"Saved batch results to {results_path}")
# Log statistics
total_detections = sum(r['detections'] for r in results)
avg_confidence = np.mean([conf for r in results for conf in r['confidences']]) if total_detections > 0 else 0
logger.info("\nBatch Detection Statistics:")
logger.info(f"Total images processed: {len(results)}")
logger.info(f"Total detections: {total_detections}")
logger.info(f"Average confidence: {avg_confidence:.3f}")
assert total_detections > 0, "No detections found in any test image"
def test_threshold_optimization(self, detector, test_images):
"""Test threshold optimization functionality"""
images = [tc['image'] for tc in test_images]
best_conf, best_iou = detector.optimize_thresholds(images)
# Verify threshold bounds
assert 0 <= best_conf <= 1, f"Invalid confidence threshold: {best_conf}"
assert 0 <= best_iou <= 1, f"Invalid IoU threshold: {best_iou}"
# Test detection with optimized thresholds
test_case = test_images[0]
result_img, detections = detector.detect(
test_case['image'],
conf_threshold=best_conf,
iou_threshold=best_iou
)
print(f"\nOptimized Thresholds:")
print(f"Confidence: {best_conf:.3f}")
print(f"IoU: {best_iou:.3f}")
@pytest.mark.parametrize("conf_threshold,iou_threshold", [
(0.1, 0.1),
(0.5, 0.5),
(0.9, 0.9)
])
def test_different_thresholds(self, detector, test_images, conf_threshold, iou_threshold):
"""Test detection with different threshold combinations"""
test_case = test_images[0]
result_img, detections = detector.detect(
test_case['image'],
conf_threshold=conf_threshold,
iou_threshold=iou_threshold
)
print(f"\nThreshold Test (conf={conf_threshold}, iou={iou_threshold}):")
print(f"Detections found: {len(detections)}")
def test_visualization(self, detector, test_images, results_dir):
"""Test detection visualization and save results"""
logger.info("Testing visualization...")
# Process and visualize a batch of images
fig, axes = plt.subplots(2, 2, figsize=(12, 12))
axes = axes.ravel()
for idx, test_case in enumerate(test_images[:4]):
logger.info(f"Processing image {idx+1}/4 for visualization")
result_img, detections = detector.detect(test_case['image'])
# Save individual result
result_path = results_dir / f"visualization_{idx}.png"
result_img.save(result_path)
logger.info(f"Saved individual result to {result_path}")
# Plot result
axes[idx].imshow(result_img)
axes[idx].set_title(f"Detections: {len(detections)}")
axes[idx].axis('off')
# Save summary plot
summary_path = results_dir / "summary.png"
plt.tight_layout()
plt.savefig(summary_path)
plt.close()
logger.info(f"Saved summary visualization to {summary_path}")
if __name__ == "__main__":
# Run with output capture disabled
pytest.main([__file__, "-v", "-s"])
+35 -150
View File
@@ -1,158 +1,43 @@
#!/usr/bin/env python3
"""
YOLOv8 Training Script for Memory Module Detection
This script trains a YOLOv8 nano model to detect memory modules in motherboard images.
"""
import os
import sys
from pathlib import Path
import yaml
from ultralytics import YOLO from ultralytics import YOLO
import torch
def check_dataset_structure(): def train_model():
"""Verify that the dataset structure is correct.""" # Load YOLOv8n (nano) for faster training with decent accuracy
required_paths = [ model = YOLO('yolov8n.pt')
'training/train/images',
'training/train/labels',
'training/val/images',
'training/val/labels',
'dataset.yaml'
]
for path in required_paths: # Train with optimized parameters for speed and quality
if not os.path.exists(path): results = model.train(
raise FileNotFoundError(f"Required path not found: {path}") data='dataset.yaml',
epochs=50, # Reduced number of epochs
imgsz=640, # Standard image size for faster processing
batch=8, # Smaller batch size for less memory usage
name='memory_detector_fast',
save=True,
device='cpu',
patience=15, # Shorter patience for earlier stopping
save_period=5, # Save every 5 epochs
verbose=True,
# Check if we have images and labels # Effective but lightweight augmentation
train_images = len([f for f in os.listdir('training/train/images') if f.endswith('.png')]) degrees=5.0, # Less rotation for speed
train_labels = len([f for f in os.listdir('training/train/labels') if f.endswith('.txt')]) scale=0.5,
val_images = len([f for f in os.listdir('training/val/images') if f.endswith('.png')]) translate=0.1,
val_labels = len([f for f in os.listdir('training/val/labels') if f.endswith('.txt')]) fliplr=0.5,
mosaic=1.0, # Keep mosaic as it's very effective
print(f"Dataset structure verified:") # Speed-optimized optimization parameters
print(f" Training: {train_images} images, {train_labels} labels") lr0=0.01,
print(f" Validation: {val_images} images, {val_labels} labels") lrf=0.01,
momentum=0.937,
weight_decay=0.0005,
warmup_epochs=1.0, # Shorter warmup
return True # Performance parameters
workers=0, # Fewer workers for CPU training
cache='disk', # Changed to disk caching for deterministic results
)
def train_model(epochs=100, imgsz=640, batch_size=16, device='auto'): # Save the trained model
""" model.save('model/weights/best.pt')
Train YOLOv8 nano model on memory module dataset.
Args: if __name__ == '__main__':
epochs (int): Number of training epochs train_model()
imgsz (int): Image size for training
batch_size (int): Batch size for training
device (str): Device to use ('auto', 'cpu', 'cuda', or specific GPU id)
"""
# Check dataset structure
check_dataset_structure()
# Initialize YOLOv8 nano model
print("Initializing YOLOv8 nano model...")
model = YOLO('yolov8n.pt') # Load pretrained YOLOv8 nano model
# Check available device
if device == 'auto':
device = 'cuda' if torch.cuda.is_available() else 'cpu'
print(f"Using device: {device}")
print(f"CUDA available: {torch.cuda.is_available()}")
if torch.cuda.is_available():
print(f"CUDA device: {torch.cuda.get_device_name()}")
# Training configuration
train_config = {
'data': 'dataset.yaml',
'epochs': epochs,
'imgsz': imgsz,
'batch': batch_size,
'device': device,
'project': 'runs/detect',
'name': 'memory_module_detection',
'save': True,
'save_period': 10, # Save checkpoint every 10 epochs
'cache': False, # Don't cache images (saves RAM)
'workers': 4,
'patience': 50, # Early stopping patience
'optimizer': 'AdamW',
'lr0': 0.01, # Initial learning rate
'lrf': 0.01, # Final learning rate factor
'momentum': 0.937,
'weight_decay': 0.0005,
'warmup_epochs': 3,
'warmup_momentum': 0.8,
'warmup_bias_lr': 0.1,
'box': 7.5, # Box loss gain
'cls': 0.5, # Class loss gain
'dfl': 1.5, # DFL loss gain
'pose': 12.0, # Pose loss gain
'kobj': 1.0, # Keypoint obj loss gain
'label_smoothing': 0.0,
'nbs': 64, # Nominal batch size
'hsv_h': 0.015, # Image HSV-Hue augmentation
'hsv_s': 0.7, # Image HSV-Saturation augmentation
'hsv_v': 0.4, # Image HSV-Value augmentation
'degrees': 0.0, # Image rotation (+/- deg)
'translate': 0.1, # Image translation (+/- fraction)
'scale': 0.5, # Image scale (+/- gain)
'shear': 0.0, # Image shear (+/- deg)
'perspective': 0.0, # Image perspective (+/- fraction)
'flipud': 0.0, # Image flip up-down (probability)
'fliplr': 0.5, # Image flip left-right (probability)
'mosaic': 1.0, # Image mosaic (probability)
'mixup': 0.0, # Image mixup (probability)
'copy_paste': 0.0, # Segment copy-paste (probability)
}
print("Starting training...")
print(f"Configuration: {train_config}")
# Train the model
results = model.train(**train_config)
# Print training results
print("\nTraining completed!")
print(f"Best model saved at: runs/detect/memory_module_detection/weights/best.pt")
print(f"Last model saved at: runs/detect/memory_module_detection/weights/last.pt")
return results
def validate_model(model_path='runs/detect/memory_module_detection/weights/best.pt'):
"""Validate the trained model."""
if not os.path.exists(model_path):
print(f"Model not found at {model_path}")
return None
print(f"Validating model: {model_path}")
model = YOLO(model_path)
# Run validation
results = model.val(data='dataset.yaml')
print("Validation completed!")
return results
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description='Train YOLOv8 for memory module detection')
parser.add_argument('--epochs', type=int, default=100, help='Number of training epochs')
parser.add_argument('--imgsz', type=int, default=640, help='Image size for training')
parser.add_argument('--batch', type=int, default=16, help='Batch size')
parser.add_argument('--device', type=str, default='auto', help='Device to use (auto, cpu, cuda)')
parser.add_argument('--validate', action='store_true', help='Only run validation')
parser.add_argument('--model', type=str, default='runs/detect/memory_module_detection/weights/best.pt',
help='Model path for validation')
args = parser.parse_args()
if args.validate:
validate_model(args.model)
else:
train_model(epochs=args.epochs, imgsz=args.imgsz, batch_size=args.batch, device=args.device)
# Also run validation after training
validate_model()
BIN
View File
Binary file not shown.
+2 -2
View File
@@ -1,2 +1,2 @@
0 0.353333 0.415062 0.164444 0.549136 0 0.331616 0.424054 0.113032 0.395981
0 0.574444 0.426914 0.180000 0.557037 0 0.569149 0.459811 0.093085 0.446809
+2 -2
View File
@@ -1,2 +1,2 @@
0 0.353333 0.387407 0.253333 0.454321 0 0.557488 0.563739 0.214812 0.472813
0 0.568889 0.509877 0.244444 0.564938 0 0.372852 0.415530 0.203560 0.361884
+2 -2
View File
@@ -1,2 +1,2 @@
0 0.383333 0.359753 0.233333 0.438519 0 0.557561 0.587639 0.226064 0.445795
0 0.557778 0.559259 0.324444 0.481975 0 0.373290 0.415400 0.212766 0.351233
+2 -2
View File
@@ -1,2 +1,2 @@
0 0.368889 0.395309 0.271111 0.391111 0 0.552812 0.583418 0.252660 0.437352
0 0.568889 0.567160 0.324444 0.560988 0 0.378989 0.402736 0.226064 0.366430
+2 -2
View File
@@ -1,2 +1,2 @@
0 0.402222 0.316296 0.253333 0.470123 0 0.552527 0.595745 0.299202 0.463357
0 0.550000 0.541481 0.273333 0.509630 0 0.380319 0.388889 0.247340 0.338061
+2 -2
View File
@@ -1,2 +1,2 @@
0 0.365556 0.381481 0.304444 0.355556 0 0.397606 0.407801 0.239362 0.342790
0 0.554444 0.571111 0.313333 0.410864 0 0.556516 0.606383 0.299202 0.437352
+2 -2
View File
@@ -1,2 +1,2 @@
0 0.412222 0.369630 0.317778 0.292346 0 0.424867 0.382979 0.238032 0.321513
0 0.572222 0.573086 0.322222 0.454321 0 0.571144 0.601655 0.267287 0.432624
+2 -2
View File
@@ -1,2 +1,2 @@
0 0.413333 0.340000 0.315556 0.375309 0 0.417553 0.373522 0.252660 0.335697
0 0.553333 0.594815 0.368889 0.497778 0 0.561170 0.613475 0.284574 0.427896
+2 -2
View File
@@ -1,2 +1,2 @@
0 0.404444 0.320247 0.368889 0.383210 0 0.404920 0.359338 0.264628 0.373522
0 0.526667 0.616543 0.386667 0.383210 0 0.541223 0.613475 0.303191 0.404255
+2 -2
View File
@@ -1,2 +1,2 @@
0 0.400000 0.365679 0.368889 0.363457 0 0.533245 0.682033 0.372340 0.446809
0 0.513333 0.644198 0.520000 0.454321 0 0.410904 0.375887 0.287234 0.321513
+2 -2
View File
@@ -1,2 +1,2 @@
0 0.423333 0.369630 0.393333 0.308148 0 0.443484 0.401891 0.291223 0.293144
0 0.540000 0.673827 0.435556 0.402963 0 0.547872 0.708038 0.356383 0.408983
+2 -2
View File
@@ -1,2 +1,2 @@
0 0.332222 0.401235 0.171111 0.513580 0 0.572473 0.470449 0.070479 0.453901
0 0.580000 0.419012 0.217778 0.533333 0 0.336436 0.470449 0.117021 0.458629
+2 -2
View File
@@ -1,2 +1,2 @@
0 0.450000 0.343951 0.371111 0.304198 0 0.545878 0.667849 0.341755 0.404255
0 0.535556 0.648148 0.426667 0.414815 0 0.444149 0.390071 0.297872 0.260047
+2 -2
View File
@@ -1,2 +1,2 @@
0 0.327778 0.401235 0.162222 0.553086 0 0.331782 0.440898 0.126330 0.437352
0 0.552222 0.432840 0.162222 0.616296 0 0.569149 0.471631 0.079787 0.442080
+2 -2
View File
@@ -1,2 +1,2 @@
0 0.338889 0.424938 0.171111 0.576790 0 0.571809 0.486998 0.079787 0.463357
0 0.541111 0.432840 0.193333 0.553086 0 0.333112 0.456265 0.128989 0.425532
+2 -2
View File
@@ -1,2 +1,2 @@
0 0.315556 0.411111 0.151111 0.557037 0 0.555851 0.515366 0.095745 0.505910
0 0.550000 0.436790 0.157778 0.553086 0 0.310505 0.465721 0.158245 0.406619
+2 -2
View File
@@ -1,2 +1,2 @@
0 0.324444 0.460494 0.200000 0.513580 0 0.543218 0.547281 0.139628 0.517730
0 0.550000 0.478272 0.180000 0.541235 0 0.310505 0.491726 0.168883 0.505910
+2 -2
View File
@@ -1,2 +1,2 @@
0 0.321111 0.417037 0.197778 0.529383 0 0.533245 0.539007 0.162234 0.520095
0 0.537778 0.474321 0.177778 0.588642 0 0.318484 0.438534 0.174202 0.465721
+2 -2
View File
@@ -1,2 +1,2 @@
0 0.307778 0.438765 0.286667 0.414815 0 0.542553 0.554374 0.183511 0.508274
0 0.538889 0.531605 0.273333 0.529383 0 0.327128 0.446809 0.194149 0.444444
+2 -2
View File
@@ -1,2 +1,2 @@
0 0.562222 0.541481 0.235556 0.525432 0 0.559840 0.528369 0.210106 0.527187
0 0.346667 0.381481 0.262222 0.489877 0 0.345745 0.407801 0.226064 0.475177
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 MiB

-2
View File
@@ -1,2 +0,0 @@
0 0.353333 0.415062 0.164444 0.549136
0 0.574444 0.426914 0.180000 0.557037
-2
View File
@@ -1,2 +0,0 @@
0 0.353333 0.387407 0.253333 0.454321
0 0.568889 0.509877 0.244444 0.564938
-2
View File
@@ -1,2 +0,0 @@
0 0.383333 0.359753 0.233333 0.438519
0 0.557778 0.559259 0.324444 0.481975
-2
View File
@@ -1,2 +0,0 @@
0 0.365556 0.381481 0.304444 0.355556
0 0.554444 0.571111 0.313333 0.410864
-2
View File
@@ -1,2 +0,0 @@
0 0.412222 0.369630 0.317778 0.292346
0 0.572222 0.573086 0.322222 0.454321
-2
View File
@@ -1,2 +0,0 @@
0 0.413333 0.340000 0.315556 0.375309
0 0.553333 0.594815 0.368889 0.497778
-2
View File
@@ -1,2 +0,0 @@
0 0.404444 0.320247 0.368889 0.383210
0 0.526667 0.616543 0.386667 0.383210
-2
View File
@@ -1,2 +0,0 @@
0 0.400000 0.365679 0.368889 0.363457
0 0.513333 0.644198 0.520000 0.454321
-2
View File
@@ -1,2 +0,0 @@
0 0.332222 0.401235 0.171111 0.513580
0 0.580000 0.419012 0.217778 0.533333
-2
View File
@@ -1,2 +0,0 @@
0 0.450000 0.343951 0.371111 0.304198
0 0.535556 0.648148 0.426667 0.414815
-2
View File
@@ -1,2 +0,0 @@
0 0.327778 0.401235 0.162222 0.553086
0 0.552222 0.432840 0.162222 0.616296
-2
View File
@@ -1,2 +0,0 @@
0 0.338889 0.424938 0.171111 0.576790
0 0.541111 0.432840 0.193333 0.553086
-2
View File
@@ -1,2 +0,0 @@
0 0.315556 0.411111 0.151111 0.557037
0 0.550000 0.436790 0.157778 0.553086
-2
View File
@@ -1,2 +0,0 @@
0 0.324444 0.460494 0.200000 0.513580
0 0.550000 0.478272 0.180000 0.541235
-2
View File
@@ -1,2 +0,0 @@
0 0.321111 0.417037 0.197778 0.529383
0 0.537778 0.474321 0.177778 0.588642
-2
View File
@@ -1,2 +0,0 @@
0 0.562222 0.541481 0.235556 0.525432
0 0.346667 0.381481 0.262222 0.489877

Some files were not shown because too many files have changed in this diff Show More