diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 4391c8e..ddc8784 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -2,11 +2,11 @@ ## Project Overview -A desktop application for detecting organelles and membrane branching structures in microscopy images using YOLOv8s, with comprehensive training, validation, and visualization capabilities. +A desktop application for detecting and segmenting organelles and membrane branching structures in microscopy images using YOLOv8s-seg, with comprehensive training, validation, and visualization capabilities including pixel-accurate segmentation masks. ## Technology Stack -- **ML Framework**: Ultralytics YOLOv8 (YOLOv8s.pt model) +- **ML Framework**: Ultralytics YOLOv8 (YOLOv8s-seg.pt segmentation model) - **GUI Framework**: PySide6 (Qt6 for Python) - **Visualization**: pyqtgraph - **Database**: SQLite3 @@ -110,6 +110,7 @@ erDiagram float x_max float y_max float confidence + text segmentation_mask datetime detected_at json metadata } @@ -122,6 +123,7 @@ erDiagram float y_min float x_max float y_max + text segmentation_mask string annotator datetime created_at boolean verified @@ -139,7 +141,7 @@ Stores information about trained models and their versions. | model_name | TEXT | NOT NULL | User-friendly model name | | model_version | TEXT | NOT NULL | Version string (e.g., "v1.0") | | model_path | TEXT | NOT NULL | Path to model weights file | -| base_model | TEXT | NOT NULL | Base model used (e.g., "yolov8s.pt") | +| base_model | TEXT | NOT NULL | Base model used (e.g., "yolov8s-seg.pt") | | created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | Model creation timestamp | | training_params | JSON | | Training hyperparameters | | metrics | JSON | | Validation metrics (mAP, precision, recall) | @@ -159,7 +161,7 @@ Stores metadata about microscopy images. | checksum | TEXT | | MD5 hash for integrity verification | #### **detections** table -Stores object detection results. +Stores object detection results with optional segmentation masks. | Column | Type | Constraints | Description | |--------|------|-------------|-------------| @@ -172,11 +174,12 @@ Stores object detection results. | x_max | REAL | NOT NULL | Bounding box right coordinate (normalized 0-1) | | y_max | REAL | NOT NULL | Bounding box bottom coordinate (normalized 0-1) | | confidence | REAL | NOT NULL | Detection confidence score (0-1) | +| segmentation_mask | TEXT | | JSON array of polygon coordinates [[x1,y1], [x2,y2], ...] (normalized 0-1) | | detected_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | When detection was performed | | metadata | JSON | | Additional metadata (processing time, etc.) | #### **annotations** table -Stores manual annotations for training data (future feature). +Stores manual annotations for training data with optional segmentation masks (future feature). | Column | Type | Constraints | Description | |--------|------|-------------|-------------| @@ -187,6 +190,7 @@ Stores manual annotations for training data (future feature). | y_min | REAL | NOT NULL | Bounding box top coordinate (normalized) | | x_max | REAL | NOT NULL | Bounding box right coordinate (normalized) | | y_max | REAL | NOT NULL | Bounding box bottom coordinate (normalized) | +| segmentation_mask | TEXT | | JSON array of polygon coordinates [[x1,y1], [x2,y2], ...] (normalized 0-1) | | annotator | TEXT | | Name of person who created annotation | | created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | Annotation timestamp | | verified | BOOLEAN | DEFAULT 0 | Whether annotation is verified | @@ -245,8 +249,9 @@ graph TB ### Key Components #### 1. **YOLO Wrapper** ([`src/model/yolo_wrapper.py`](src/model/yolo_wrapper.py)) -Encapsulates YOLOv8 operations: -- Load pre-trained YOLOv8s model +Encapsulates YOLOv8-seg operations: +- Load pre-trained YOLOv8s-seg segmentation model +- Extract pixel-accurate segmentation masks - Fine-tune on custom microscopy dataset - Export trained models - Provide training progress callbacks @@ -255,10 +260,10 @@ Encapsulates YOLOv8 operations: **Key Methods:** ```python class YOLOWrapper: - def __init__(self, model_path: str = "yolov8s.pt") + def __init__(self, model_path: str = "yolov8s-seg.pt") def train(self, data_yaml: str, epochs: int, callbacks: dict) def validate(self, data_yaml: str) -> dict - def predict(self, image_path: str, conf: float) -> list + def predict(self, image_path: str, conf: float) -> list # Returns detections with segmentation masks def export_model(self, format: str, output_path: str) ``` @@ -435,7 +440,7 @@ image_repository: allowed_extensions: [".jpg", ".jpeg", ".png", ".tif", ".tiff"] models: - default_base_model: "yolov8s.pt" + default_base_model: "yolov8s-seg.pt" models_directory: "data/models" training: diff --git a/BUILD.md b/BUILD.md new file mode 100644 index 0000000..ab19583 --- /dev/null +++ b/BUILD.md @@ -0,0 +1,178 @@ +# Building and Publishing Guide + +This guide explains how to build and publish the microscopy-object-detection package. + +## Prerequisites + +```bash +pip install build twine +``` + +## Building the Package + +### 1. Clean Previous Builds + +```bash +rm -rf build/ dist/ *.egg-info +``` + +### 2. Build Distribution Archives + +```bash +python -m build +``` + +This will create both wheel (`.whl`) and source distribution (`.tar.gz`) in the `dist/` directory. + +### 3. Verify the Build + +```bash +ls dist/ +# Should show: +# microscopy_object_detection-1.0.0-py3-none-any.whl +# microscopy_object_detection-1.0.0.tar.gz +``` + +## Testing the Package Locally + +### Install in Development Mode + +```bash +pip install -e . +``` + +### Install from Built Package + +```bash +pip install dist/microscopy_object_detection-1.0.0-py3-none-any.whl +``` + +### Test the Installation + +```bash +# Test CLI +microscopy-detect --version + +# Test GUI launcher +microscopy-detect-gui +``` + +## Publishing to PyPI + +### 1. Configure PyPI Credentials + +Create or update `~/.pypirc`: + +```ini +[pypi] +username = __token__ +password = pypi-YOUR-API-TOKEN-HERE +``` + +### 2. Upload to Test PyPI (Recommended First) + +```bash +python -m twine upload --repository testpypi dist/* +``` + +Then test installation: + +```bash +pip install --index-url https://test.pypi.org/simple/ microscopy-object-detection +``` + +### 3. Upload to PyPI + +```bash +python -m twine upload dist/* +``` + +## Version Management + +Update version in multiple files: +- `setup.py`: Update `version` parameter +- `pyproject.toml`: Update `version` field +- `src/__init__.py`: Update `__version__` variable + +## Git Tags + +After publishing, tag the release: + +```bash +git tag -a v1.0.0 -m "Release version 1.0.0" +git push origin v1.0.0 +``` + +## Package Structure + +The built package includes: +- All Python source files in `src/` +- Configuration files in `config/` +- Database schema file (`src/database/schema.sql`) +- Documentation files (README.md, LICENSE, etc.) +- Entry points for CLI and GUI + +## Troubleshooting + +### Import Errors +If you get import errors, ensure: +- All `__init__.py` files are present +- Package structure follows the setup configuration +- Dependencies are listed in `requirements.txt` + +### Missing Files +If files are missing in the built package: +- Check `MANIFEST.in` includes the required patterns +- Check `pyproject.toml` package-data configuration +- Rebuild with `python -m build --no-isolation` for debugging + +### Version Conflicts +If version conflicts occur: +- Ensure version is consistent across all files +- Clear build artifacts and rebuild +- Check for cached installations: `pip list | grep microscopy` + +## CI/CD Integration + +### GitHub Actions Example + +```yaml +name: Build and Publish + +on: + release: + types: [created] + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + with: + python-version: '3.8' + - name: Install dependencies + run: | + pip install build twine + - name: Build package + run: python -m build + - name: Publish to PyPI + env: + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} + run: twine upload dist/* +``` + +## Best Practices + +1. **Version Bumping**: Use semantic versioning (MAJOR.MINOR.PATCH) +2. **Testing**: Always test on Test PyPI before publishing to PyPI +3. **Documentation**: Update README.md and CHANGELOG.md for each release +4. **Git Tags**: Tag releases in git for easy reference +5. **Dependencies**: Keep requirements.txt updated and specify version ranges + +## Resources + +- [Python Packaging Guide](https://packaging.python.org/) +- [setuptools Documentation](https://setuptools.pypa.io/) +- [PyPI Publishing Guide](https://packaging.python.org/tutorials/packaging-projects/) \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f7df838 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Your Name + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..6246c45 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,37 @@ +# Include documentation files +include README.md +include LICENSE +include ARCHITECTURE.md +include IMPLEMENTATION_GUIDE.md +include QUICKSTART.md +include PLAN_SUMMARY.md + +# Include requirements +include requirements.txt + +# Include configuration files +recursive-include config *.yaml +recursive-include config *.yml + +# Include database schema +recursive-include src/database *.sql + +# Include tests +recursive-include tests *.py + +# Exclude compiled Python files +global-exclude *.pyc +global-exclude *.pyo +global-exclude __pycache__ +global-exclude *.so +global-exclude .DS_Store + +# Exclude git and IDE files +global-exclude .git* +global-exclude .vscode +global-exclude .idea + +# Exclude build artifacts +prune build +prune dist +prune *.egg-info \ No newline at end of file diff --git a/QUICKSTART.md b/QUICKSTART.md index e211216..0c97a79 100644 --- a/QUICKSTART.md +++ b/QUICKSTART.md @@ -38,7 +38,7 @@ This will install: - OpenCV and Pillow (image processing) - And other dependencies -**Note:** The first run will automatically download the YOLOv8s.pt model (~22MB). +**Note:** The first run will automatically download the YOLOv8s-seg.pt segmentation model (~23MB). ### 4. Verify Installation @@ -84,11 +84,11 @@ In the Settings dialog: ### Single Image Detection 1. Go to the **Detection** tab -2. Select a model from the dropdown (default: Base Model yolov8s.pt) +2. Select a model from the dropdown (default: Base Model yolov8s-seg.pt) 3. Adjust confidence threshold with the slider 4. Click "Detect Single Image" 5. Select an image file -6. View results in the results panel +6. View results with segmentation masks overlaid on the image ### Batch Detection @@ -108,9 +108,18 @@ Detection results include: - **Class names**: Types of objects detected (e.g., organelle, membrane_branch) - **Confidence scores**: Detection confidence (0-1) - **Bounding boxes**: Object locations (stored in database) +- **Segmentation masks**: Pixel-accurate polygon coordinates for each detected object All results are stored in the SQLite database at [`data/detections.db`](data/detections.db). +### Segmentation Visualization + +The application automatically displays segmentation masks when available: +- Semi-transparent colored overlay (30% opacity) showing the exact shape of detected objects +- Polygon contours outlining each segmentation +- Color-coded by object class +- Toggle-able in future versions + ## Database The application uses SQLite to store: @@ -176,7 +185,7 @@ sudo apt-get install libxcb-xinerama0 ### Detection Not Working **No models available** -- The base YOLOv8s model will be downloaded automatically on first use +- The base YOLOv8s-seg segmentation model will be downloaded automatically on first use - Make sure you have internet connection for the first run **Images not found** diff --git a/README.md b/README.md index 61f376f..f12cb79 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Microscopy Object Detection Application -A desktop application for detecting organelles and membrane branching structures in microscopy images using YOLOv8, featuring comprehensive training, validation, and visualization capabilities. +A desktop application for detecting and segmenting organelles and membrane branching structures in microscopy images using YOLOv8-seg, featuring comprehensive training, validation, and visualization capabilities with pixel-accurate segmentation masks. ![Python](https://img.shields.io/badge/python-3.8+-blue.svg) ![PySide6](https://img.shields.io/badge/PySide6-6.5+-green.svg) @@ -8,8 +8,8 @@ A desktop application for detecting organelles and membrane branching structures ## Features -- **🎯 Object Detection**: Real-time and batch detection of microscopy objects -- **🎓 Model Training**: Fine-tune YOLOv8s on custom microscopy datasets +- **🎯 Object Detection & Segmentation**: Real-time and batch detection with pixel-accurate segmentation masks +- **🎓 Model Training**: Fine-tune YOLOv8s-seg on custom microscopy datasets - **📊 Validation & Metrics**: Comprehensive model validation with visualization - **💾 Database Storage**: SQLite database for detection results and metadata - **📈 Visualization**: Interactive plots and charts using pyqtgraph @@ -34,14 +34,24 @@ A desktop application for detecting organelles and membrane branching structures ## Installation -### 1. Clone the Repository +### Option 1: Install from PyPI (Recommended) + +```bash +pip install microscopy-object-detection +``` + +This will install the package and all its dependencies. + +### Option 2: Install from Source + +#### 1. Clone the Repository ```bash git clone cd object_detection ``` -### 2. Create Virtual Environment +#### 2. Create Virtual Environment ```bash # Linux/Mac @@ -53,25 +63,44 @@ python -m venv venv venv\Scripts\activate ``` -### 3. Install Dependencies +#### 3. Install in Development Mode ```bash -pip install -r requirements.txt +# Install in editable mode with dev dependencies +pip install -e ".[dev]" + +# Or install just the package +pip install . ``` ### 4. Download Base Model -The application will automatically download the YOLOv8s.pt model on first use, or you can download it manually: +The application will automatically download the YOLOv8s-seg.pt segmentation model on first use, or you can download it manually: ```bash # The model will be downloaded automatically by ultralytics # Or download manually from: https://github.com/ultralytics/assets/releases ``` +**Note:** YOLOv8s-seg is a segmentation model that provides pixel-accurate masks for detected objects, enabling more precise analysis than standard bounding box detection. + ## Quick Start ### 1. Launch the Application +After installation, you can launch the application in two ways: + +**Using the GUI launcher:** +```bash +microscopy-detect-gui +``` + +**Or using Python directly:** +```bash +python -m microscopy_object_detection +``` + +**If installed from source:** ```bash python main.py ``` @@ -85,11 +114,12 @@ python main.py ### 3. Perform Detection 1. Navigate to the **Detection** tab -2. Select a model (default: yolov8s.pt) +2. Select a model (default: yolov8s-seg.pt) 3. Choose an image or folder 4. Set confidence threshold 5. Click **Detect** -6. View results and save to database +6. View results with segmentation masks overlaid +7. Save results to database ### 4. Train Custom Model @@ -212,8 +242,8 @@ The application uses SQLite with the following main tables: - **models**: Stores trained model information and metrics - **images**: Stores image metadata and paths -- **detections**: Stores detection results with bounding boxes -- **annotations**: Stores manual annotations (future feature) +- **detections**: Stores detection results with bounding boxes and segmentation masks (polygon coordinates) +- **annotations**: Stores manual annotations with optional segmentation masks (future feature) See [`ARCHITECTURE.md`](ARCHITECTURE.md) for detailed schema information. @@ -230,7 +260,7 @@ image_repository: allowed_extensions: [".jpg", ".jpeg", ".png", ".tif", ".tiff"] models: - default_base_model: "yolov8s.pt" + default_base_model: "yolov8s-seg.pt" models_directory: "data/models" training: @@ -258,7 +288,7 @@ visualization: from src.model.yolo_wrapper import YOLOWrapper # Initialize wrapper -yolo = YOLOWrapper("yolov8s.pt") +yolo = YOLOWrapper("yolov8s-seg.pt") # Train model results = yolo.train( @@ -393,10 +423,10 @@ make html **Issue**: Model not found error -**Solution**: Ensure YOLOv8s.pt is downloaded. Run: +**Solution**: Ensure YOLOv8s-seg.pt is downloaded. Run: ```python from ultralytics import YOLO -model = YOLO('yolov8s.pt') # Will auto-download +model = YOLO('yolov8s-seg.pt') # Will auto-download ``` diff --git a/config/app_config.yaml b/config/app_config.yaml index e47c74a..be8d2d1 100644 --- a/config/app_config.yaml +++ b/config/app_config.yaml @@ -10,7 +10,7 @@ image_repository: - .tiff - .bmp models: - default_base_model: yolov8s.pt + default_base_model: yolov8s-seg.pt models_directory: data/models training: default_epochs: 100 diff --git a/main.py b/main.py index 77b6a30..652272f 100644 --- a/main.py +++ b/main.py @@ -6,12 +6,13 @@ Main entry point for the application. import sys from pathlib import Path -# Add src directory to path +# Add src directory to path for development mode sys.path.insert(0, str(Path(__file__).parent)) from PySide6.QtWidgets import QApplication from PySide6.QtCore import Qt +from src import __version__ from src.gui.main_window import MainWindow from src.utils.logger import setup_logging from src.utils.config_manager import ConfigManager @@ -37,7 +38,7 @@ def main(): app = QApplication(sys.argv) app.setApplicationName("Microscopy Object Detection") app.setOrganizationName("MicroscopyLab") - app.setApplicationVersion("1.0.0") + app.setApplicationVersion(__version__) # Set application style app.setStyle("Fusion") diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..d5993d6 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,103 @@ +[build-system] +requires = ["setuptools>=45", "wheel", "setuptools_scm[toml]>=6.2"] +build-backend = "setuptools.build_meta" + +[project] +name = "microscopy-object-detection" +version = "1.0.0" +description = "Desktop application for detecting and segmenting organelles in microscopy images using YOLOv8-seg" +readme = "README.md" +requires-python = ">=3.8" +license = { text = "MIT" } +authors = [{ name = "Your Name", email = "your.email@example.com" }] +keywords = [ + "microscopy", + "yolov8", + "object-detection", + "segmentation", + "computer-vision", + "deep-learning", +] +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Science/Research", + "Topic :: Scientific/Engineering :: Image Recognition", + "Topic :: Scientific/Engineering :: Bio-Informatics", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Operating System :: OS Independent", +] + +dependencies = [ + "ultralytics>=8.0.0", + "PySide6>=6.5.0", + "pyqtgraph>=0.13.0", + "numpy>=1.24.0", + "opencv-python>=4.8.0", + "Pillow>=10.0.0", + "PyYAML>=6.0", + "pandas>=2.0.0", + "openpyxl>=3.1.0", +] + +[project.optional-dependencies] +dev = [ + "pytest>=7.0.0", + "pytest-cov>=4.0.0", + "black>=23.0.0", + "pylint>=2.17.0", + "mypy>=1.0.0", +] + +[project.urls] +Homepage = "https://github.com/yourusername/object_detection" +Documentation = "https://github.com/yourusername/object_detection/blob/main/README.md" +Repository = "https://github.com/yourusername/object_detection" +"Bug Tracker" = "https://github.com/yourusername/object_detection/issues" + +[project.scripts] +microscopy-detect = "src.cli:main" + +[project.gui-scripts] +microscopy-detect-gui = "main:main" + +[tool.setuptools] +package-dir = { "" = "." } +packages = [ + "src", + "src.database", + "src.model", + "src.gui", + "src.gui.tabs", + "src.gui.dialogs", + "src.gui.widgets", + "src.utils", +] + +[tool.setuptools.package-data] +src = ["database/*.sql"] +"" = ["config/*.yaml"] + +[tool.black] +line-length = 88 +target-version = ['py38', 'py39', 'py310', 'py311'] +include = '\.pyi?$' + +[tool.pylint.messages_control] +max-line-length = 88 + +[tool.mypy] +python_version = "3.8" +warn_return_any = true +warn_unused_configs = true +disallow_untyped_defs = false + +[tool.pytest.ini_options] +testpaths = ["tests"] +python_files = ["test_*.py"] +python_functions = ["test_*"] +addopts = "-v --cov=src --cov-report=term-missing" diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..59a43a1 --- /dev/null +++ b/setup.py @@ -0,0 +1,56 @@ +"""Setup script for Microscopy Object Detection Application.""" + +from setuptools import setup, find_packages +from pathlib import Path + +# Read the contents of README file +this_directory = Path(__file__).parent +long_description = (this_directory / "README.md").read_text(encoding="utf-8") + +# Read requirements +requirements = (this_directory / "requirements.txt").read_text().splitlines() +requirements = [ + req.strip() for req in requirements if req.strip() and not req.startswith("#") +] + +setup( + name="microscopy-object-detection", + version="1.0.0", + author="Your Name", + author_email="your.email@example.com", + description="Desktop application for detecting and segmenting organelles in microscopy images using YOLOv8-seg", + long_description=long_description, + long_description_content_type="text/markdown", + url="https://github.com/yourusername/object_detection", + packages=find_packages(exclude=["tests", "tests.*", "docs"]), + include_package_data=True, + install_requires=requirements, + python_requires=">=3.8", + classifiers=[ + "Development Status :: 4 - Beta", + "Intended Audience :: Science/Research", + "Topic :: Scientific/Engineering :: Image Recognition", + "Topic :: Scientific/Engineering :: Bio-Informatics", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Operating System :: OS Independent", + ], + entry_points={ + "console_scripts": [ + "microscopy-detect=src.cli:main", + ], + "gui_scripts": [ + "microscopy-detect-gui=main:main", + ], + }, + keywords="microscopy yolov8 object-detection segmentation computer-vision deep-learning", + project_urls={ + "Bug Reports": "https://github.com/yourusername/object_detection/issues", + "Source": "https://github.com/yourusername/object_detection", + "Documentation": "https://github.com/yourusername/object_detection/blob/main/README.md", + }, +) diff --git a/src/__init__.py b/src/__init__.py index e69de29..424aa41 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -0,0 +1,19 @@ +""" +Microscopy Object Detection Application + +A desktop application for detecting and segmenting organelles and membrane +branching structures in microscopy images using YOLOv8-seg. +""" + +__version__ = "1.0.0" +__author__ = "Your Name" +__email__ = "your.email@example.com" +__license__ = "MIT" + +# Package metadata +__all__ = [ + "__version__", + "__author__", + "__email__", + "__license__", +] diff --git a/src/cli.py b/src/cli.py new file mode 100644 index 0000000..482ee52 --- /dev/null +++ b/src/cli.py @@ -0,0 +1,61 @@ +""" +Command-line interface for microscopy object detection application. +""" + +import sys +import argparse +from pathlib import Path + +from src import __version__ + + +def main(): + """Main CLI entry point.""" + parser = argparse.ArgumentParser( + description="Microscopy Object Detection Application - CLI Interface", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +Examples: + # Launch GUI + microscopy-detect-gui + + # Show version + microscopy-detect --version + + # Get help + microscopy-detect --help + """, + ) + + parser.add_argument( + "--version", + action="version", + version=f"microscopy-object-detection {__version__}", + ) + + parser.add_argument( + "--gui", + action="store_true", + help="Launch the GUI application (same as microscopy-detect-gui)", + ) + + args = parser.parse_args() + + if args.gui: + # Launch GUI + try: + from main import main as gui_main + + gui_main() + except Exception as e: + print(f"Error launching GUI: {e}", file=sys.stderr) + sys.exit(1) + else: + # Show help if no arguments provided + parser.print_help() + print("\nTo launch the GUI, use: microscopy-detect-gui") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/src/database/db_manager.py b/src/database/db_manager.py index 4329499..5dcf5c2 100644 --- a/src/database/db_manager.py +++ b/src/database/db_manager.py @@ -6,7 +6,7 @@ Handles all database operations including CRUD operations, queries, and exports. import sqlite3 import json from datetime import datetime -from typing import List, Dict, Optional, Tuple, Any +from typing import List, Dict, Optional, Tuple, Any, Union from pathlib import Path import csv import hashlib @@ -56,7 +56,7 @@ class DatabaseManager: model_name: str, model_version: str, model_path: str, - base_model: str = "yolov8s.pt", + base_model: str = "yolov8s-seg.pt", training_params: Optional[Dict] = None, metrics: Optional[Dict] = None, ) -> int: @@ -243,6 +243,7 @@ class DatabaseManager: class_name: str, bbox: Tuple[float, float, float, float], # (x_min, y_min, x_max, y_max) confidence: float, + segmentation_mask: Optional[List[List[float]]] = None, metadata: Optional[Dict] = None, ) -> int: """ @@ -254,6 +255,7 @@ class DatabaseManager: class_name: Detected object class bbox: Bounding box coordinates (normalized 0-1) confidence: Detection confidence score + segmentation_mask: Polygon coordinates for segmentation [[x1,y1], [x2,y2], ...] metadata: Additional metadata Returns: @@ -265,8 +267,8 @@ class DatabaseManager: x_min, y_min, x_max, y_max = bbox cursor.execute( """ - INSERT INTO detections (image_id, model_id, class_name, x_min, y_min, x_max, y_max, confidence, metadata) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + INSERT INTO detections (image_id, model_id, class_name, x_min, y_min, x_max, y_max, confidence, segmentation_mask, metadata) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( image_id, @@ -277,6 +279,7 @@ class DatabaseManager: x_max, y_max, confidence, + json.dumps(segmentation_mask) if segmentation_mask else None, json.dumps(metadata) if metadata else None, ), ) @@ -302,8 +305,8 @@ class DatabaseManager: bbox = det["bbox"] cursor.execute( """ - INSERT INTO detections (image_id, model_id, class_name, x_min, y_min, x_max, y_max, confidence, metadata) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + INSERT INTO detections (image_id, model_id, class_name, x_min, y_min, x_max, y_max, confidence, segmentation_mask, metadata) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( det["image_id"], @@ -314,6 +317,11 @@ class DatabaseManager: bbox[2], bbox[3], det["confidence"], + ( + json.dumps(det.get("segmentation_mask")) + if det.get("segmentation_mask") + else None + ), ( json.dumps(det.get("metadata")) if det.get("metadata") @@ -385,9 +393,11 @@ class DatabaseManager: detections = [] for row in cursor.fetchall(): det = dict(row) - # Parse JSON metadata + # Parse JSON fields if det.get("metadata"): det["metadata"] = json.loads(det["metadata"]) + if det.get("segmentation_mask"): + det["segmentation_mask"] = json.loads(det["segmentation_mask"]) detections.append(det) return detections @@ -538,6 +548,7 @@ class DatabaseManager: "x_max", "y_max", "confidence", + "segmentation_mask", "detected_at", ] writer = csv.DictWriter(csvfile, fieldnames=fieldnames) @@ -545,6 +556,11 @@ class DatabaseManager: for det in detections: row = {k: det[k] for k in fieldnames if k in det} + # Convert segmentation mask list to JSON string for CSV + if row.get("segmentation_mask") and isinstance( + row["segmentation_mask"], list + ): + row["segmentation_mask"] = json.dumps(row["segmentation_mask"]) writer.writerow(row) return True @@ -580,6 +596,7 @@ class DatabaseManager: class_name: str, bbox: Tuple[float, float, float, float], annotator: str, + segmentation_mask: Optional[List[List[float]]] = None, verified: bool = False, ) -> int: """Add manual annotation.""" @@ -589,10 +606,20 @@ class DatabaseManager: x_min, y_min, x_max, y_max = bbox cursor.execute( """ - INSERT INTO annotations (image_id, class_name, x_min, y_min, x_max, y_max, annotator, verified) - VALUES (?, ?, ?, ?, ?, ?, ?, ?) + INSERT INTO annotations (image_id, class_name, x_min, y_min, x_max, y_max, segmentation_mask, annotator, verified) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) """, - (image_id, class_name, x_min, y_min, x_max, y_max, annotator, verified), + ( + image_id, + class_name, + x_min, + y_min, + x_max, + y_max, + json.dumps(segmentation_mask) if segmentation_mask else None, + annotator, + verified, + ), ) conn.commit() return cursor.lastrowid diff --git a/src/database/models.py b/src/database/models.py index cdcd237..6659954 100644 --- a/src/database/models.py +++ b/src/database/models.py @@ -5,7 +5,7 @@ These dataclasses represent the database entities. from dataclasses import dataclass from datetime import datetime -from typing import Optional, Dict, Tuple +from typing import Optional, Dict, Tuple, List @dataclass @@ -46,6 +46,9 @@ class Detection: class_name: str bbox: Tuple[float, float, float, float] # (x_min, y_min, x_max, y_max) confidence: float + segmentation_mask: Optional[ + List[List[float]] + ] # List of polygon coordinates [[x1,y1], [x2,y2], ...] detected_at: datetime metadata: Optional[Dict] @@ -58,6 +61,9 @@ class Annotation: image_id: int class_name: str bbox: Tuple[float, float, float, float] # (x_min, y_min, x_max, y_max) + segmentation_mask: Optional[ + List[List[float]] + ] # List of polygon coordinates [[x1,y1], [x2,y2], ...] annotator: str created_at: datetime verified: bool diff --git a/src/database/schema.sql b/src/database/schema.sql index f6080f7..b09ffee 100644 --- a/src/database/schema.sql +++ b/src/database/schema.sql @@ -37,6 +37,7 @@ CREATE TABLE IF NOT EXISTS detections ( x_max REAL NOT NULL CHECK(x_max >= 0 AND x_max <= 1), y_max REAL NOT NULL CHECK(y_max >= 0 AND y_max <= 1), confidence REAL NOT NULL CHECK(confidence >= 0 AND confidence <= 1), + segmentation_mask TEXT, -- JSON string of polygon coordinates [[x1,y1], [x2,y2], ...] detected_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, metadata TEXT, -- JSON string for additional metadata FOREIGN KEY (image_id) REFERENCES images (id) ON DELETE CASCADE, @@ -52,6 +53,7 @@ CREATE TABLE IF NOT EXISTS annotations ( y_min REAL NOT NULL CHECK(y_min >= 0 AND y_min <= 1), x_max REAL NOT NULL CHECK(x_max >= 0 AND x_max <= 1), y_max REAL NOT NULL CHECK(y_max >= 0 AND y_max <= 1), + segmentation_mask TEXT, -- JSON string of polygon coordinates [[x1,y1], [x2,y2], ...] annotator TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, verified BOOLEAN DEFAULT 0, diff --git a/src/gui/dialogs/config_dialog.py b/src/gui/dialogs/config_dialog.py index 9abfe1b..27ed4a7 100644 --- a/src/gui/dialogs/config_dialog.py +++ b/src/gui/dialogs/config_dialog.py @@ -121,7 +121,7 @@ class ConfigDialog(QDialog): models_layout.addRow("Models Directory:", self.models_dir_edit) self.base_model_edit = QLineEdit() - self.base_model_edit.setPlaceholderText("yolov8s.pt") + self.base_model_edit.setPlaceholderText("yolov8s-seg.pt") models_layout.addRow("Default Base Model:", self.base_model_edit) models_group.setLayout(models_layout) @@ -232,7 +232,7 @@ class ConfigDialog(QDialog): self.config_manager.get("models.models_directory", "data/models") ) self.base_model_edit.setText( - self.config_manager.get("models.default_base_model", "yolov8s.pt") + self.config_manager.get("models.default_base_model", "yolov8s-seg.pt") ) # Training settings diff --git a/src/gui/tabs/detection_tab.py b/src/gui/tabs/detection_tab.py index 4fe71ce..01a3861 100644 --- a/src/gui/tabs/detection_tab.py +++ b/src/gui/tabs/detection_tab.py @@ -159,7 +159,7 @@ class DetectionTab(QWidget): # Add base model option base_model = self.config_manager.get( - "models.default_base_model", "yolov8s.pt" + "models.default_base_model", "yolov8s-seg.pt" ) self.model_combo.addItem( f"Base Model ({base_model})", {"id": 0, "path": base_model} @@ -256,7 +256,7 @@ class DetectionTab(QWidget): if model_id == 0: # Create database entry for base model base_model = self.config_manager.get( - "models.default_base_model", "yolov8s.pt" + "models.default_base_model", "yolov8s-seg.pt" ) model_id = self.db_manager.add_model( model_name="Base Model", diff --git a/src/model/inference.py b/src/model/inference.py index 1fc5ab8..2a3780b 100644 --- a/src/model/inference.py +++ b/src/model/inference.py @@ -87,6 +87,7 @@ class InferenceEngine: "class_name": det["class_name"], "bbox": tuple(bbox_normalized), "confidence": det["confidence"], + "segmentation_mask": det.get("segmentation_mask"), "metadata": {"class_id": det["class_id"]}, } detection_records.append(record) @@ -160,6 +161,7 @@ class InferenceEngine: conf: float = 0.25, bbox_thickness: int = 2, bbox_colors: Optional[Dict[str, str]] = None, + draw_masks: bool = True, ) -> tuple: """ Detect objects and return annotated image. @@ -169,6 +171,7 @@ class InferenceEngine: conf: Confidence threshold bbox_thickness: Thickness of bounding boxes bbox_colors: Dictionary mapping class names to hex colors + draw_masks: Whether to draw segmentation masks (if available) Returns: Tuple of (detections, annotated_image_array) @@ -189,12 +192,8 @@ class InferenceEngine: bbox_colors = {} default_color = self._hex_to_bgr(bbox_colors.get("default", "#00FF00")) - # Draw bounding boxes + # Draw detections for det in detections: - # Get absolute coordinates - bbox_abs = det["bbox_absolute"] - x1, y1, x2, y2 = [int(v) for v in bbox_abs] - # Get color for this class class_name = det["class_name"] color_hex = bbox_colors.get( @@ -202,7 +201,33 @@ class InferenceEngine: ) color = self._hex_to_bgr(color_hex) - # Draw box + # Draw segmentation mask if available and requested + if draw_masks and det.get("segmentation_mask"): + mask_normalized = det["segmentation_mask"] + if mask_normalized and len(mask_normalized) > 0: + # Convert normalized coordinates to absolute pixels + mask_points = np.array( + [ + [int(pt[0] * width), int(pt[1] * height)] + for pt in mask_normalized + ], + dtype=np.int32, + ) + + # Create a semi-transparent overlay + overlay = img.copy() + cv2.fillPoly(overlay, [mask_points], color) + # Blend with original image (30% opacity) + cv2.addWeighted(overlay, 0.3, img, 0.7, 0, img) + + # Draw mask contour + cv2.polylines(img, [mask_points], True, color, bbox_thickness) + + # Get absolute coordinates for bounding box + bbox_abs = det["bbox_absolute"] + x1, y1, x2, y2 = [int(v) for v in bbox_abs] + + # Draw bounding box cv2.rectangle(img, (x1, y1), (x2, y2), color, bbox_thickness) # Prepare label diff --git a/src/model/yolo_wrapper.py b/src/model/yolo_wrapper.py index d4a8050..fa1fd8a 100644 --- a/src/model/yolo_wrapper.py +++ b/src/model/yolo_wrapper.py @@ -16,7 +16,7 @@ logger = get_logger(__name__) class YOLOWrapper: """Wrapper for YOLOv8 model operations.""" - def __init__(self, model_path: str = "yolov8s.pt"): + def __init__(self, model_path: str = "yolov8s-seg.pt"): """ Initialize YOLO model. @@ -282,6 +282,10 @@ class YOLOWrapper: boxes = result.boxes image_path = str(result.path) orig_shape = result.orig_shape # (height, width) + height, width = orig_shape + + # Check if this is a segmentation model with masks + has_masks = hasattr(result, "masks") and result.masks is not None for i in range(len(boxes)): # Get normalized coordinates @@ -299,6 +303,33 @@ class YOLOWrapper: float(v) for v in boxes.xyxy[i].cpu().numpy() ], # Absolute pixels } + + # Extract segmentation mask if available + if has_masks: + try: + # Get the mask for this detection + mask_data = result.masks.xy[ + i + ] # Polygon coordinates in absolute pixels + + # Convert to normalized coordinates + if len(mask_data) > 0: + mask_normalized = [] + for point in mask_data: + x_norm = float(point[0]) / width + y_norm = float(point[1]) / height + mask_normalized.append([x_norm, y_norm]) + detection["segmentation_mask"] = mask_normalized + else: + detection["segmentation_mask"] = None + except Exception as mask_error: + logger.warning( + f"Error extracting mask for detection {i}: {mask_error}" + ) + detection["segmentation_mask"] = None + else: + detection["segmentation_mask"] = None + detections.append(detection) return detections diff --git a/src/utils/config_manager.py b/src/utils/config_manager.py index 385d2a9..a31516d 100644 --- a/src/utils/config_manager.py +++ b/src/utils/config_manager.py @@ -56,7 +56,7 @@ class ConfigManager: ], }, "models": { - "default_base_model": "yolov8s.pt", + "default_base_model": "yolov8s-seg.pt", "models_directory": "data/models", }, "training": {