Compare commits

...

25 Commits

Author SHA1 Message Date
Aherobo Ovie Victor 13dee5b8cd code reviewed 2025-07-22 12:28:57 +01:00
Aherobo Ovie Victor 7908b94d40 update 2025-07-21 19:20:44 +01:00
Aherobo Ovie Victor 0b7af4050e Add simple API documentation endpoint
 Added Simple API Documentation:
- Created API_DOCS.md with comprehensive endpoint documentation
- Added /docs endpoint to serve formatted API documentation
- Updated /api endpoint to include documentation reference
- Simple HTML formatting for better readability

 Documentation Features:
- Complete endpoint descriptions with examples
- Request/response formats for all endpoints
- cURL and Python usage examples
- Error response documentation
- Model information and specifications

 Access Points:
- /api - Basic API info (JSON)
- /docs - Detailed documentation (HTML)
- API_DOCS.md - Raw markdown documentation

Simple, clean documentation without complex Swagger UI overhead.
2025-07-12 07:45:56 +01:00
Aherobo Ovie Victor 2426e71ed0 Revert all Swagger UI changes - back to original simple API
 Reverted Changes:
- Removed swagger_app.py and start_docs.py files
- Restored main.py to original state without Flask-RESTX
- Removed all professional Swagger UI documentation
- Back to simple Flask API with basic endpoints

 Current State:
- Simple Flask API on port 5002
- Original web interface for memory module detection
- Basic API endpoints without complex documentation
- Clean, minimal codebase as requested

 Available Endpoints:
- GET / - Web interface
- GET /health - Health check
- GET /api - API info
- POST /detect - Upload image detection
- GET /detect/hardcoded - Test hardcoded image
- POST /detect/base64 - Base64 image detection

Back to the clean, simple API implementation before Swagger UI additions.
2025-07-12 07:41:23 +01:00
Aherobo Ovie Victor 89517c541b Add Professional Swagger UI API Documentation
 Professional API Documentation Added:
- Created comprehensive Swagger UI similar to Mini SpecsComply Pro
- Added Flask-RESTX integration with detailed API models
- Professional styling with emojis and comprehensive descriptions

 Dual Documentation System:
- Main API (port 5002): Built-in Swagger at /docs/
- Professional Docs (port 5003): Enhanced UI with detailed specifications
- Complete API coverage: health, info, detection endpoints

 Enhanced API Features:
- Detailed request/response models with validation
- Comprehensive error handling and status codes
- Professional API descriptions and examples
- Health monitoring with system metrics
- Model performance metrics display

 Developer Experience:
- Interactive API testing interface
- Professional documentation layout
- Easy startup with start_docs.py script
- Comprehensive endpoint documentation

 API Endpoints Documented:
- GET /api/v1/health - Health check with metrics
- GET /api/v1/info - Comprehensive API information
- POST /api/v1/detection/upload - File upload detection
- GET /api/v1/detection/hardcoded - Test image detection
- POST /api/v1/detection/base64 - Base64 image detection

Now provides professional API documentation interface matching enterprise standards
2025-07-12 07:37:01 +01:00
Aherobo Ovie Victor 1d93e4c438 Fix README emoji display issues
 Fixed Emoji Display:
- Fixed broken emoji in Technical Questions Summary section
- Fixed broken emoji in Installation & Setup section
- Cleaned up markdown formatting for better display

 Documentation Improvements:
- Ensured proper section headers display correctly
- Maintained professional README appearance
- Fixed any character encoding issues in headers
2025-07-11 23:36:48 +01:00
Aherobo Ovie Victor 6d90dc9f15 Fix Run All Tests to use last detection result for dynamic messaging
 Fixed Logic:
- Reverted hardcoded image back to memory image (training/memory/out1.png)
- Added lastDetectionResult global variable to track last detection
- Run All Tests now uses the last detection result (from upload or hardcoded test)

 Dynamic Behavior:
- Upload image with memory → Run All Tests shows ' Found X memory modules'
- Upload image without memory → Run All Tests shows ' No memory modules'
- No previous upload → Run All Tests uses hardcoded image (with memory)

 Workflow:
1. User uploads image without memory → detects 0 modules
2. User clicks 'Run All Tests' → shows ' No memory modules'
3. User uploads image with memory → detects X modules
4. User clicks 'Run All Tests' → shows ' Found X memory modules'

 Reset Logic:
- lastDetectionResult reset when file upload is reset
- Clean state management between different uploads

Now the Run All Tests correctly reflects the last detection result
2025-07-11 22:59:36 +01:00
Aherobo Ovie Victor da59c00f5a Change hardcoded test image to no-memory image to demonstrate dynamic messaging
 Changed Test Image:
- Updated HARDCODED_IMAGE_PATH from 'training/memory/out1.png' to 'training/no_memory/out1.png'
- Now uses an image without memory modules for testing
- Demonstrates the dynamic ' No memory modules' message

 Dynamic Behavior:
- Run All Tests will now show ' No memory modules' instead of ' Found 2 memory modules'
- Same JavaScript logic, different result based on actual detection
- No frontend changes needed - purely backend image swap

 Testing Both Scenarios:
- Hardcoded test: Shows ' No memory modules' (no memory image)
- Custom upload: Shows ' Found X memory modules' or ' No memory modules' based on uploaded image
- Perfect demonstration of dynamic messaging system

This change allows you to see the ' No memory modules' message in Run All Tests results.
2025-07-11 22:49:26 +01:00
Aherobo Ovie Victor 5c65a5af89 Fix test results behavior - remove summary from upload results and hide tests on new image
 Fixed Summary Message Placement:
- Removed summary message from custom upload results (displayResults function)
- Keep dynamic message only in 'Run All Tests' results
- Upload results now show only detection stats and details

 Hide Test Results on New Image:
- Test results automatically hidden when new image is uploaded
- Test results hidden when file upload is reset
- User must click 'Run All Tests' again to see results for new image

 Improved User Experience:
- Clear separation between upload results and test results
- Dynamic messages only appear where expected (Run All Tests)
- Clean workflow: upload → test results disappear → click test → see results
- No confusing duplicate summary messages

 Behavior Flow:
1. Upload image → No summary message in upload results
2. Click 'Run All Tests' → Shows ' Found X memory modules' or ' No memory modules'
3. Upload new image → Test results disappear automatically
4. Click 'Run All Tests' again → Fresh results with dynamic message

Perfect test results behavior as requested
2025-07-11 22:41:15 +01:00
Aherobo Ovie Victor 46fb2f5976 Remove Test No Memory Scenario button 2025-07-11 22:32:49 +01:00
Aherobo Ovie Victor 12d953b4a3 Add Test No Memory Scenario button 2025-07-11 22:29:45 +01:00
Aherobo Ovie Victor 40f074cc99 Add smart summary messages to custom upload results 2025-07-11 22:23:56 +01:00
Aherobo Ovie Victor 780e32c412 Simplify Run All Tests - keep 3 tests, just change message for no memory
 Simplified Test Logic:
- Removed unnecessary /detect/no-memory endpoint
- Reverted to original 3 tests structure
- Test 1: API Health Check
- Test 2: Image with Memory Modules
- Test 3: API Information

 Smart Message Display:
- When memory modules found: ' Found X memory modules'
- When no memory modules found: ' No memory modules'
- Same endpoint, different message based on detection results

 Clean Implementation:
- No additional endpoints needed
- Uses existing /detect/hardcoded endpoint
- Simple conditional message logic
- Maintains original test count and structure

Now the test will show the appropriate message whether memory modules are detected or not, using the same hardcoded test image.
2025-07-11 22:16:36 +01:00
Aherobo Ovie Victor b96839d436 Fix Run All Tests to properly test both memory and no-memory scenarios 2025-07-11 22:11:09 +01:00
Aherobo Ovie Victor 4d5ccfd9af Remove QA testing references and API status indicator 2025-07-11 22:04:27 +01:00
Aherobo Ovie Victor 0e49829d23 Fix API port mismatch and improve error handling 2025-07-11 21:43:33 +01:00
Aherobo Ovie Victor bdc171b009 Restore api_docs.py and add comprehensive file structure to README
 API Documentation Restored:
- Kept api_docs.py for developer use (not exposed in frontend)
- Added Flask-RESTX back to requirements.txt
- Swagger UI available at http://localhost:5003/docs/ for developers

 Enhanced README Documentation:
- Added comprehensive project file structure
- Detailed directory tree with all files and folders
- Key files description table with purpose and usage
- Clear separation between user-facing and developer files

 File Organization:
- Documented all 40+ files and directories in the project
- Explained training outputs and model artifacts
- Clarified virtual environment and temporary directories
- Added file size and content descriptions

 Developer vs User Separation:
- main.py: User-facing web interface (port 5002)
- api_docs.py: Developer-only Swagger UI (port 5003)
- Clear documentation of intended usage for each component

The README now provides a complete overview of the project structure for both users and developers.
2025-07-11 21:38:19 +01:00
Aherobo Ovie Victor 7b9de2b833 Remove API documentation from frontend and delete Swagger UI 2025-07-11 21:36:12 +01:00
Aherobo Ovie Victor b54da61121 Add API documentation access and improve docs integration
 Enhanced API Documentation Access:
- Added /docs route to main app with instructions for Swagger UI
- Created helpful documentation page with setup instructions
- Added API Documentation button to web interface
- Updated /api endpoint to include Swagger UI information

 User-Friendly Documentation:
- Clear step-by-step instructions to access Swagger UI
- Direct link to Swagger UI (when running)
- Quick API reference on docs page
- Professional styling for documentation page

 Improved Navigation:
- Added 'API Documentation' button to main interface
- Opens in new tab for easy reference
- Back link to main interface
- Clear visual hierarchy and instructions

Now users can easily access API documentation from the main interface
2025-07-11 21:31:59 +01:00
Aherobo Ovie Victor 403559a2a2 Move footer to bottom of page with proper styling 2025-07-11 21:26:26 +01:00
Aherobo Ovie Victor 55af293bb7 Fix file upload reset issue - complete solution
 File Input Reset Fix:
- Completely recreate file input element on reset to clear all state
- Remove and recreate DOM elements to eliminate cached event listeners
- Add comprehensive logging for debugging file selection issues
- Force clear file input value and recreate with fresh event handlers

 Event Listener Management:
- Clone and replace upload area to remove stale event listeners
- Reinitialize all drag & drop and click event handlers
- Ensure file input click events work after multiple uploads
- Add proper event propagation handling

 Upload Area Reinitialization:
- Create initializeUploadArea() function for complete reset
- Remove existing file input and create brand new element
- Reattach all event listeners to fresh DOM elements
- Add console logging for debugging upload flow

 Robust State Management:
- Clear uploadedFile variable on reset
- Hide upload controls and results sections
- Remove 'Upload Another' buttons properly
- Ensure clean state between file uploads

This should completely resolve the file upload reset issue where users had to reload the page to upload a second file.
2025-07-11 21:21:24 +01:00
Aherobo Ovie Victor db795c5729 Fix file upload issues and add Swagger UI API documentation
 Frontend File Upload Fixes:
- Fixed file upload reset issue - can now upload multiple files without page reload
- Added 'Change File' and 'Upload Another Image' buttons for better UX
- Fixed double-click file selection issue with proper event handling
- Improved drag & drop functionality with proper event propagation
- Added visual feedback for file selection and processing states

 Swagger UI API Documentation:
- Created api_docs.py with comprehensive Swagger UI documentation
- Added Flask-RESTX for professional API documentation interface
- Documented all 3 detection endpoints with request/response models
- Added health check endpoint documentation
- Included detailed parameter descriptions and example responses
- Available at http://localhost:5003/docs/ for interactive API testing

 Enhanced User Experience:
- Seamless file upload workflow without page reloads
- Clear visual indicators for file selection and processing
- Professional API documentation for developers and QA testing
- Consistent 80% confidence threshold across all interfaces

 Technical Improvements:
- Better event handling for file inputs and drag & drop
- Proper cleanup of uploaded files and UI state
- Comprehensive error handling and user feedback
- Interactive API documentation with live testing capabilities
2025-07-11 21:15:41 +01:00
Aherobo Ovie Victor 26a6f6f625 Set confidence threshold to 80% and remove slider from frontend
 Frontend Changes:
- Removed confidence threshold slider from web interface
- Added fixed 80% confidence display with green info box
- Updated JavaScript to use fixed 0.8 threshold
- Removed slider-related CSS styles

 Backend Changes:
- Updated all API endpoints to default to 80% confidence (0.8)
- Modified POST /detect, GET /detect/hardcoded, POST /detect/base64
- Updated comments to reflect new default threshold

 Testing Updates:
- Updated test_api.py to use 80% confidence for all tests
- Ensures consistent testing with new threshold

 Benefits:
- High precision mode (80% confidence) reduces false positives
- Simplified user interface without threshold adjustment
- Consistent detection behavior across all endpoints
2025-07-11 20:47:04 +01:00
Aherobo Ovie Victor c4de90fbec Fix port conflict: Change Flask app port from 5001 to 5002
- Updated main.py to use port 5002 instead of 5001
- Resolves 'Address already in use' error
- Ensures smooth API startup for testing
- Web interface now available at http://localhost:5002
2025-07-11 20:17:47 +01:00
Aherobo Ovie Victor 88df23a311 Enhanced README with comprehensive technical question answers
 Algorithm Choice:
- Detailed explanation of YOLOv8 Nano selection
- Technical advantages and reasoning
- Performance metrics and capabilities

 Hardware Considerations:
- Comprehensive CPU vs GPU analysis
- Training and inference performance comparison
- Implementation strategy with auto-detection

 Video Processing Approach:
- Complete video processing strategy
- Frame extraction and batch processing
- Temporal tracking and optimization techniques
- Code examples and API endpoint design

 Technical Questions Summary:
- All required questions answered comprehensively
- Implementation validated in working system
- Performance metrics documented
2025-07-11 20:13:13 +01:00
123 changed files with 971 additions and 2916 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__/
*.py[cod]
*$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/
# Translations
*.mo
*.pot
# Distribution / packaging
dist/
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
local_settings.py
db.sqlite3
db.sqlite3-journal
.DS_Store
temp/
tmp/
# Flask stuff:
instance/
.webassets-cache
# Debug directories
torchinductor_*/
# Scrapy stuff:
.scrapy
# Cache directories
.cache/
*.cache
# Sphinx documentation
docs/_build/
# Test coverage
coverage_html_report/
.coverage
htmlcov/
test_results/
# PyBuilder
target/
# Compiled files
*.so
*.dll
*.dylib
# Jupyter Notebook
.ipynb_checkpoints
*.ipynb
# IPython
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
# Environment variables
.env
.venv
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
.env.local
+167 -402
View File
@@ -1,429 +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:**
- Image upload via Flask API
- Base64 encoded images
- Hardcoded test image
- **Dataset:** 40 images (20 with memory modules, 20 without)
- **Output:** Annotated images with bounding boxes and confidence scores
- Image upload via the Flask API
- A hardcoded test image (memory_out19.png) for testing purposes
## 🏗️ Project Structure
- **Dataset:**
- 20 pictures of motherboards with memory
- 20 pictures of motherboards without memory
```
ds_task_recycling_project/
├── main.py # Flask API application
├── 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
├── templates/ # Frontend templates
│ └── index.html # QA testing web interface
├── static/ # Frontend assets
│ ├── style.css # Styling for web interface
│ └── script.js # JavaScript for web interface
├── training/ # Dataset directory
│ ├── memory/ # Images with memory modules + labels
│ ├── no_memory/ # Images without memory modules
│ ├── train/ # Training split (80%)
│ └── val/ # Validation split (20%)
└── runs/ # Training outputs (created after training)
└── detect/
└── memory_module_detection/
└── weights/
├── best.pt # Best model weights
└── last.pt # Last epoch weights
```
- **Output:**
- An annotated image with bounding boxes around each detected memory module
- For example, if there are two memory modules, two boxes are drawn; if only one is detected, then one box is drawn
## 🤖 Algorithm Choice & Technical Decisions
- **Annotation Tool:**
- [makesense.ai](https://www.makesense.ai/) was used for manual annotation
### 1. **Algorithm Choice: YOLOv8 Nano**
## Implementation Details
**Why YOLOv8?**
- **State-of-the-art performance:** Latest version of the YOLO family
- **Real-time inference:** Fast detection suitable for API deployment
- **Pre-trained weights:** Transfer learning from COCO dataset
- **Easy integration:** Excellent Python API via ultralytics
- **Small model size:** Nano version balances accuracy and speed
### Algorithm Choice & Rationale
**Advantages:**
- Single-stage detector (faster than R-CNN family)
- Excellent small object detection (important for memory modules)
- Built-in data augmentation and training optimizations
- Active community and regular updates
1. **Which algorithm was chosen?**
- YOLOv8 (specifically YOLOv8n - the nano version) was selected for this task
2. **Why this algorithm?**
- 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
### 2. **Hardware Considerations**
### Hardware Considerations
**CPU vs GPU Impact:**
3. **CPU/GPU Impact:**
- The current implementation runs on CPU for broader accessibility
- Model parameters were optimized for CPU performance:
- Reduced batch size (8)
- Lightweight augmentation
- Early stopping with patience=15
- GPU support is available through YOLO if needed for scaling
- Current performance is suitable for the demo nature of the project
**Training:**
- **GPU Recommended:** Training on 40 images takes ~5-10 minutes on GPU vs 30-60 minutes on CPU
- **Memory Requirements:** 4GB+ GPU memory recommended
- **Fallback:** CPU training works but is significantly slower
### Video Processing Approach
**Inference:**
- **CPU Sufficient:** Real-time inference possible on modern CPUs
- **GPU Advantage:** Batch processing and video streams benefit from GPU
- **Edge Deployment:** Model can run on edge devices with CPU-only
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
**Implementation:**
```python
# Auto-detection in train.py
device = 'cuda' if torch.cuda.is_available() else 'cpu'
```
### 3. **Video Input Approach**
**For video processing, the approach would be:**
1. **Frame Extraction:** Extract frames at regular intervals
2. **Batch Processing:** Process multiple frames simultaneously on GPU
3. **Temporal Consistency:** Apply tracking algorithms (DeepSORT, ByteTrack)
4. **Optimization:** Skip frames with no changes, use optical flow
5. **Output:** Annotated video with consistent object IDs
**Implementation Strategy:**
```python
# Pseudo-code for video processing
def process_video(video_path):
cap = cv2.VideoCapture(video_path)
tracker = DeepSORT()
while cap.isOpened():
ret, frame = cap.read()
detections = detector.detect_from_array(frame)
tracked_objects = tracker.update(detections)
annotated_frame = draw_tracked_objects(frame, tracked_objects)
yield annotated_frame
```
## 🔧 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
```
## API Implementation
### 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
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:**
```json
{
"message": "Memory Module Detection API",
"version": "1.0.0",
"endpoints": {...},
"model_loaded": true,
"supported_formats": ["png", "jpg", "jpeg", "gif", "bmp"]
}
The dataset is organized as follows:
- `training/memory/`: Source directory for images with memory modules
- `training/no_memory/`: Source directory for images without memory modules
- `training/train/`: Training dataset
- `images/`: Contains both memory and no-memory images with appropriate prefixes
- `labels/`: Contains YOLO format annotation files
- `training/val/`: Validation dataset
- `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
git clone http://23.29.118.76:3000/michael/ds_task_recycling_project.git
```
2. Install dependencies:
```bash
pip install -r requirements.txt
```
3. Prepare the dataset:
```bash
python prepare_dataset.py
```
4. Train the model (if not already trained):
```bash
python train.py
```
5. Run the Flask application:
```bash
python run.py
```
6. Access the web interface at `http://localhost:5000`
## Testing
The project includes comprehensive tests for the detector:
- Batch detection testing
- Threshold optimization
- Various confidence/IoU threshold combinations
Run tests with:
```bash
curl http://localhost:5000/health
pytest tests/
```
#### 3. **POST /detect** - Upload Image Detection
```bash
curl -X POST -F "image=@motherboard.png" -F "confidence=0.5" http://localhost:5000/detect
```
## Future Improvements
**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
curl "http://localhost:5000/detect/hardcoded?confidence=0.5"
```
#### 5. **POST /detect/base64** - Base64 Image Detection
```bash
curl -X POST -H "Content-Type: application/json" \
-d '{"image": "base64_string", "confidence": 0.5}' \
http://localhost:5000/detect/base64
```
## 🧪 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
# Basic detection
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
```
### 3. **Command Line Inference**
```bash
# Test single image
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
### Dataset Statistics
- **Total Images:** 40 (20 with memory, 20 without)
- **Training Split:** 32 images (80%)
- **Validation Split:** 8 images (20%)
- **Classes:** 1 (memory_module)
- **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!
1. GPU support for faster processing
2. Video input support
3. Real-time streaming capabilities
4. More sophisticated augmentation techniques
5. Model quantization for improved CPU performance
-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
-274
View File
@@ -1,274 +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:
self.model = YOLO(self.model_path)
print(f"Model loaded successfully from {self.model_path}")
except Exception as e:
print(f"Error loading model: {e}")
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)
-364
View File
@@ -1,364 +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)',
'/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('/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
conf_threshold = float(request.form.get('confidence', 0.5))
# 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.5)
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
conf_threshold = float(request.args.get('confidence', 0.5))
# 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
conf_threshold = float(data.get('confidence', 0.5))
# 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=5001, debug=True)
+5 -29
View File
@@ -1,29 +1,5 @@
# Core ML and Computer Vision
ultralytics==8.0.196
torch>=1.9.0
torchvision>=0.10.0
opencv-python==4.8.1.78
Pillow==10.0.1
# Web Framework
Flask==2.3.3
Flask-CORS==4.0.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
flask
ultralytics
pillow
numpy
python-multipart
+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)
-346
View File
@@ -1,346 +0,0 @@
// Memory Module Detection QA Interface JavaScript
const API_BASE_URL = 'http://localhost:5001';
let uploadedFile = 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);
uploadArea.addEventListener('click', () => document.getElementById('fileInput').click());
// Confidence slider
document.getElementById('confidenceSlider').addEventListener('input', function() {
document.getElementById('confidenceValue').textContent = this.value;
});
}
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' });
}
function handleFileSelect(event) {
const file = event.target.files[0];
if (file) {
handleFile(file);
}
}
function handleDragOver(event) {
event.preventDefault();
event.currentTarget.classList.add('dragover');
}
function handleDragLeave(event) {
event.currentTarget.classList.remove('dragover');
}
function handleDrop(event) {
event.preventDefault();
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;
// Show file info
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>
</div>
`;
// Show controls
document.getElementById('uploadControls').style.display = 'block';
}
async function processUploadedImage() {
if (!uploadedFile) {
alert('Please select an image first');
return;
}
const confidence = document.getElementById('confidenceSlider').value;
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) {
displayResults(result, 'Uploaded Image Detection');
} else {
alert(`Detection failed: ${result.error}`);
}
} catch (error) {
hideLoading();
alert(`Error: ${error.message}`);
}
}
async function testHardcodedImage() {
showLoading('Testing hardcoded image...');
try {
const response = await fetch(`${API_BASE_URL}/detect/hardcoded?confidence=0.5`);
const result = await response.json();
hideLoading();
if (result.success) {
displayResults(result, 'Hardcoded Image Test');
} else {
alert(`Test failed: ${result.error}`);
}
} catch (error) {
hideLoading();
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: Hardcoded Image
try {
const response = await fetch(`${API_BASE_URL}/detect/hardcoded`);
const result = await response.json();
testResults.push({
name: 'Hardcoded Image Detection',
success: result.success,
message: result.success ?
`Found ${result.num_detections} memory modules` :
`Error: ${result.error}`
});
} catch (error) {
testResults.push({
name: 'Hardcoded Image Detection',
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';
}
-377
View File
@@ -1,377 +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;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
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-control {
margin-bottom: 15px;
}
.confidence-control label {
display: block;
margin-bottom: 8px;
font-weight: 500;
}
.confidence-control input[type="range"] {
width: 100%;
height: 6px;
border-radius: 3px;
background: #ddd;
outline: none;
}
.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;
}
footer {
text-align: center;
margin-top: 40px;
color: rgba(255,255,255,0.8);
}
/* 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);
}
}
-103
View File
@@ -1,103 +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>QA Testing Interface for Motherboard Memory Module Detection</p>
<div class="status-indicator" id="apiStatus">
<i class="fas fa-circle"></i> <span>Checking API...</span>
</div>
</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;">
<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-control">
<label for="confidenceSlider">Confidence Threshold: <span id="confidenceValue">0.5</span></label>
<input type="range" id="confidenceSlider" min="0.1" max="1.0" step="0.1" value="0.5">
</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>
<p>&copy; 2024 Memory Module Detection Project - QA Testing Interface</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:5001"
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.5")
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.5'}
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.5
}
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"])
+41 -151
View File
@@ -1,158 +1,48 @@
#!/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
import torch
def check_dataset_structure():
"""Verify that the dataset structure is correct."""
required_paths = [
'training/train/images',
'training/train/labels',
'training/val/images',
'training/val/labels',
'dataset.yaml'
]
def train_model():
# Load YOLOv8n (nano) for faster training with decent accuracy
model = YOLO('yolov8n.pt')
for path in required_paths:
if not os.path.exists(path):
raise FileNotFoundError(f"Required path not found: {path}")
# Train with optimized parameters for speed and quality
results = model.train(
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,
# Effective but lightweight augmentation
degrees=5.0, # Less rotation for speed
scale=0.5,
translate=0.1,
fliplr=0.5,
mosaic=1.0, # Keep mosaic as it's very effective
# Speed-optimized optimization parameters
lr0=0.01,
lrf=0.01,
momentum=0.937,
weight_decay=0.0005,
warmup_epochs=1.0, # Shorter warmup
# Performance parameters
workers=0, # Fewer workers for CPU training
cache='disk', # Changed to disk caching for deterministic results
)
# Check if we have images and labels
train_images = len([f for f in os.listdir('training/train/images') if f.endswith('.png')])
train_labels = len([f for f in os.listdir('training/train/labels') if f.endswith('.txt')])
val_images = len([f for f in os.listdir('training/val/images') if f.endswith('.png')])
val_labels = len([f for f in os.listdir('training/val/labels') if f.endswith('.txt')])
print(f"Dataset structure verified:")
print(f" Training: {train_images} images, {train_labels} labels")
print(f" Validation: {val_images} images, {val_labels} labels")
return True
# Create model directory if it doesn't exist
import os
os.makedirs('model/weights', exist_ok=True)
def train_model(epochs=100, imgsz=640, batch_size=16, device='auto'):
"""
Train YOLOv8 nano model on memory module dataset.
Args:
epochs (int): Number of training epochs
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
# Save the trained model
model.save('model/weights/best.pt')
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()
if __name__ == '__main__':
train_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.574444 0.426914 0.180000 0.557037
0 0.331616 0.424054 0.113032 0.395981
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.568889 0.509877 0.244444 0.564938
0 0.557488 0.563739 0.214812 0.472813
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.557778 0.559259 0.324444 0.481975
0 0.557561 0.587639 0.226064 0.445795
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.568889 0.567160 0.324444 0.560988
0 0.552812 0.583418 0.252660 0.437352
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.550000 0.541481 0.273333 0.509630
0 0.552527 0.595745 0.299202 0.463357
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.554444 0.571111 0.313333 0.410864
0 0.397606 0.407801 0.239362 0.342790
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.572222 0.573086 0.322222 0.454321
0 0.424867 0.382979 0.238032 0.321513
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.553333 0.594815 0.368889 0.497778
0 0.417553 0.373522 0.252660 0.335697
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.526667 0.616543 0.386667 0.383210
0 0.404920 0.359338 0.264628 0.373522
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.513333 0.644198 0.520000 0.454321
0 0.533245 0.682033 0.372340 0.446809
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.540000 0.673827 0.435556 0.402963
0 0.443484 0.401891 0.291223 0.293144
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.580000 0.419012 0.217778 0.533333
0 0.572473 0.470449 0.070479 0.453901
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.535556 0.648148 0.426667 0.414815
0 0.545878 0.667849 0.341755 0.404255
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.552222 0.432840 0.162222 0.616296
0 0.331782 0.440898 0.126330 0.437352
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.541111 0.432840 0.193333 0.553086
0 0.571809 0.486998 0.079787 0.463357
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.550000 0.436790 0.157778 0.553086
0 0.555851 0.515366 0.095745 0.505910
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.550000 0.478272 0.180000 0.541235
0 0.543218 0.547281 0.139628 0.517730
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.537778 0.474321 0.177778 0.588642
0 0.533245 0.539007 0.162234 0.520095
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.538889 0.531605 0.273333 0.529383
0 0.542553 0.554374 0.183511 0.508274
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.346667 0.381481 0.262222 0.489877
0 0.559840 0.528369 0.210106 0.527187
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