Adding python files

This commit is contained in:
2025-12-05 09:50:50 +02:00
parent c6143cd11a
commit 6bd2b100ca
24 changed files with 3076 additions and 0 deletions

0
src/gui/__init__.py Normal file
View File

View File

View File

@@ -0,0 +1,291 @@
"""
Configuration dialog for the microscopy object detection application.
"""
from PySide6.QtWidgets import (
QDialog,
QVBoxLayout,
QHBoxLayout,
QFormLayout,
QPushButton,
QLineEdit,
QSpinBox,
QDoubleSpinBox,
QFileDialog,
QTabWidget,
QWidget,
QLabel,
QGroupBox,
)
from PySide6.QtCore import Qt
from src.utils.config_manager import ConfigManager
from src.utils.logger import get_logger
logger = get_logger(__name__)
class ConfigDialog(QDialog):
"""Configuration dialog window."""
def __init__(self, config_manager: ConfigManager, parent=None):
super().__init__(parent)
self.config_manager = config_manager
self.setWindowTitle("Settings")
self.setMinimumWidth(500)
self.setMinimumHeight(400)
self._setup_ui()
self._load_settings()
def _setup_ui(self):
"""Setup user interface."""
layout = QVBoxLayout()
# Create tab widget for different setting categories
self.tab_widget = QTabWidget()
# General settings tab
general_tab = self._create_general_tab()
self.tab_widget.addTab(general_tab, "General")
# Training settings tab
training_tab = self._create_training_tab()
self.tab_widget.addTab(training_tab, "Training")
# Detection settings tab
detection_tab = self._create_detection_tab()
self.tab_widget.addTab(detection_tab, "Detection")
layout.addWidget(self.tab_widget)
# Buttons
button_layout = QHBoxLayout()
button_layout.addStretch()
self.save_button = QPushButton("Save")
self.save_button.clicked.connect(self.accept)
button_layout.addWidget(self.save_button)
self.cancel_button = QPushButton("Cancel")
self.cancel_button.clicked.connect(self.reject)
button_layout.addWidget(self.cancel_button)
layout.addLayout(button_layout)
self.setLayout(layout)
def _create_general_tab(self) -> QWidget:
"""Create general settings tab."""
widget = QWidget()
layout = QVBoxLayout()
# Image repository group
repo_group = QGroupBox("Image Repository")
repo_layout = QFormLayout()
# Repository path
path_layout = QHBoxLayout()
self.repo_path_edit = QLineEdit()
self.repo_path_edit.setPlaceholderText("Path to image repository")
path_layout.addWidget(self.repo_path_edit)
browse_button = QPushButton("Browse...")
browse_button.clicked.connect(self._browse_repository)
path_layout.addWidget(browse_button)
repo_layout.addRow("Base Path:", path_layout)
repo_group.setLayout(repo_layout)
layout.addWidget(repo_group)
# Database group
db_group = QGroupBox("Database")
db_layout = QFormLayout()
self.db_path_edit = QLineEdit()
self.db_path_edit.setPlaceholderText("Path to database file")
db_layout.addRow("Database Path:", self.db_path_edit)
db_group.setLayout(db_layout)
layout.addWidget(db_group)
# Models group
models_group = QGroupBox("Models")
models_layout = QFormLayout()
self.models_dir_edit = QLineEdit()
self.models_dir_edit.setPlaceholderText("Directory for saved models")
models_layout.addRow("Models Directory:", self.models_dir_edit)
self.base_model_edit = QLineEdit()
self.base_model_edit.setPlaceholderText("yolov8s.pt")
models_layout.addRow("Default Base Model:", self.base_model_edit)
models_group.setLayout(models_layout)
layout.addWidget(models_group)
layout.addStretch()
widget.setLayout(layout)
return widget
def _create_training_tab(self) -> QWidget:
"""Create training settings tab."""
widget = QWidget()
layout = QVBoxLayout()
form_layout = QFormLayout()
# Epochs
self.epochs_spin = QSpinBox()
self.epochs_spin.setRange(1, 1000)
self.epochs_spin.setValue(100)
form_layout.addRow("Default Epochs:", self.epochs_spin)
# Batch size
self.batch_size_spin = QSpinBox()
self.batch_size_spin.setRange(1, 128)
self.batch_size_spin.setValue(16)
form_layout.addRow("Default Batch Size:", self.batch_size_spin)
# Image size
self.imgsz_spin = QSpinBox()
self.imgsz_spin.setRange(320, 1280)
self.imgsz_spin.setSingleStep(32)
self.imgsz_spin.setValue(640)
form_layout.addRow("Default Image Size:", self.imgsz_spin)
# Patience
self.patience_spin = QSpinBox()
self.patience_spin.setRange(1, 200)
self.patience_spin.setValue(50)
form_layout.addRow("Default Patience:", self.patience_spin)
# Learning rate
self.lr_spin = QDoubleSpinBox()
self.lr_spin.setRange(0.0001, 0.1)
self.lr_spin.setSingleStep(0.001)
self.lr_spin.setDecimals(4)
self.lr_spin.setValue(0.01)
form_layout.addRow("Default Learning Rate:", self.lr_spin)
layout.addLayout(form_layout)
layout.addStretch()
widget.setLayout(layout)
return widget
def _create_detection_tab(self) -> QWidget:
"""Create detection settings tab."""
widget = QWidget()
layout = QVBoxLayout()
form_layout = QFormLayout()
# Confidence threshold
self.conf_spin = QDoubleSpinBox()
self.conf_spin.setRange(0.0, 1.0)
self.conf_spin.setSingleStep(0.05)
self.conf_spin.setDecimals(2)
self.conf_spin.setValue(0.25)
form_layout.addRow("Default Confidence:", self.conf_spin)
# IoU threshold
self.iou_spin = QDoubleSpinBox()
self.iou_spin.setRange(0.0, 1.0)
self.iou_spin.setSingleStep(0.05)
self.iou_spin.setDecimals(2)
self.iou_spin.setValue(0.45)
form_layout.addRow("Default IoU:", self.iou_spin)
# Max batch size
self.max_batch_spin = QSpinBox()
self.max_batch_spin.setRange(1, 1000)
self.max_batch_spin.setValue(100)
form_layout.addRow("Max Batch Size:", self.max_batch_spin)
layout.addLayout(form_layout)
layout.addStretch()
widget.setLayout(layout)
return widget
def _browse_repository(self):
"""Browse for image repository directory."""
directory = QFileDialog.getExistingDirectory(
self, "Select Image Repository", self.repo_path_edit.text()
)
if directory:
self.repo_path_edit.setText(directory)
def _load_settings(self):
"""Load current settings into dialog."""
# General settings
self.repo_path_edit.setText(
self.config_manager.get("image_repository.base_path", "")
)
self.db_path_edit.setText(
self.config_manager.get("database.path", "data/detections.db")
)
self.models_dir_edit.setText(
self.config_manager.get("models.models_directory", "data/models")
)
self.base_model_edit.setText(
self.config_manager.get("models.default_base_model", "yolov8s.pt")
)
# Training settings
self.epochs_spin.setValue(
self.config_manager.get("training.default_epochs", 100)
)
self.batch_size_spin.setValue(
self.config_manager.get("training.default_batch_size", 16)
)
self.imgsz_spin.setValue(self.config_manager.get("training.default_imgsz", 640))
self.patience_spin.setValue(
self.config_manager.get("training.default_patience", 50)
)
self.lr_spin.setValue(self.config_manager.get("training.default_lr0", 0.01))
# Detection settings
self.conf_spin.setValue(
self.config_manager.get("detection.default_confidence", 0.25)
)
self.iou_spin.setValue(self.config_manager.get("detection.default_iou", 0.45))
self.max_batch_spin.setValue(
self.config_manager.get("detection.max_batch_size", 100)
)
def accept(self):
"""Save settings and close dialog."""
logger.info("Saving configuration")
# Save general settings
self.config_manager.set(
"image_repository.base_path", self.repo_path_edit.text()
)
self.config_manager.set("database.path", self.db_path_edit.text())
self.config_manager.set("models.models_directory", self.models_dir_edit.text())
self.config_manager.set(
"models.default_base_model", self.base_model_edit.text()
)
# Save training settings
self.config_manager.set("training.default_epochs", self.epochs_spin.value())
self.config_manager.set(
"training.default_batch_size", self.batch_size_spin.value()
)
self.config_manager.set("training.default_imgsz", self.imgsz_spin.value())
self.config_manager.set("training.default_patience", self.patience_spin.value())
self.config_manager.set("training.default_lr0", self.lr_spin.value())
# Save detection settings
self.config_manager.set("detection.default_confidence", self.conf_spin.value())
self.config_manager.set("detection.default_iou", self.iou_spin.value())
self.config_manager.set("detection.max_batch_size", self.max_batch_spin.value())
# Save to file
self.config_manager.save_config()
super().accept()

282
src/gui/main_window.py Normal file
View File

@@ -0,0 +1,282 @@
"""
Main window for the microscopy object detection application.
"""
from PySide6.QtWidgets import (
QMainWindow,
QTabWidget,
QMenuBar,
QMenu,
QStatusBar,
QMessageBox,
QWidget,
QVBoxLayout,
QLabel,
)
from PySide6.QtCore import Qt, QTimer
from PySide6.QtGui import QAction, QKeySequence
from src.database.db_manager import DatabaseManager
from src.utils.config_manager import ConfigManager
from src.utils.logger import get_logger
from src.gui.dialogs.config_dialog import ConfigDialog
from src.gui.tabs.detection_tab import DetectionTab
from src.gui.tabs.training_tab import TrainingTab
from src.gui.tabs.validation_tab import ValidationTab
from src.gui.tabs.results_tab import ResultsTab
from src.gui.tabs.annotation_tab import AnnotationTab
logger = get_logger(__name__)
class MainWindow(QMainWindow):
"""Main application window."""
def __init__(self):
super().__init__()
# Initialize managers
self.config_manager = ConfigManager()
db_path = self.config_manager.get_database_path()
self.db_manager = DatabaseManager(db_path)
logger.info("Main window initializing")
# Setup UI
self.setWindowTitle("Microscopy Object Detection")
self.setMinimumSize(1200, 800)
self._create_menu_bar()
self._create_tab_widget()
self._create_status_bar()
# Center window on screen
self._center_window()
logger.info("Main window initialized")
def _create_menu_bar(self):
"""Create application menu bar."""
menubar = self.menuBar()
# File menu
file_menu = menubar.addMenu("&File")
settings_action = QAction("&Settings", self)
settings_action.setShortcut(QKeySequence("Ctrl+,"))
settings_action.triggered.connect(self._show_settings)
file_menu.addAction(settings_action)
file_menu.addSeparator()
exit_action = QAction("E&xit", self)
exit_action.setShortcut(QKeySequence("Ctrl+Q"))
exit_action.triggered.connect(self.close)
file_menu.addAction(exit_action)
# View menu
view_menu = menubar.addMenu("&View")
refresh_action = QAction("&Refresh", self)
refresh_action.setShortcut(QKeySequence("F5"))
refresh_action.triggered.connect(self._refresh_current_tab)
view_menu.addAction(refresh_action)
# Tools menu
tools_menu = menubar.addMenu("&Tools")
db_stats_action = QAction("Database &Statistics", self)
db_stats_action.triggered.connect(self._show_database_stats)
tools_menu.addAction(db_stats_action)
# Help menu
help_menu = menubar.addMenu("&Help")
about_action = QAction("&About", self)
about_action.triggered.connect(self._show_about)
help_menu.addAction(about_action)
docs_action = QAction("&Documentation", self)
docs_action.triggered.connect(self._show_documentation)
help_menu.addAction(docs_action)
def _create_tab_widget(self):
"""Create main tab widget with all tabs."""
self.tab_widget = QTabWidget()
self.tab_widget.setTabPosition(QTabWidget.North)
# Create tabs
try:
self.detection_tab = DetectionTab(self.db_manager, self.config_manager)
self.training_tab = TrainingTab(self.db_manager, self.config_manager)
self.validation_tab = ValidationTab(self.db_manager, self.config_manager)
self.results_tab = ResultsTab(self.db_manager, self.config_manager)
self.annotation_tab = AnnotationTab(self.db_manager, self.config_manager)
# Add tabs to widget
self.tab_widget.addTab(self.detection_tab, "Detection")
self.tab_widget.addTab(self.training_tab, "Training")
self.tab_widget.addTab(self.validation_tab, "Validation")
self.tab_widget.addTab(self.results_tab, "Results")
self.tab_widget.addTab(self.annotation_tab, "Annotation (Future)")
# Connect tab change signal
self.tab_widget.currentChanged.connect(self._on_tab_changed)
except Exception as e:
logger.error(f"Error creating tabs: {e}")
# Create placeholder
placeholder = QWidget()
layout = QVBoxLayout()
layout.addWidget(QLabel(f"Error creating tabs: {e}"))
placeholder.setLayout(layout)
self.tab_widget.addTab(placeholder, "Error")
self.setCentralWidget(self.tab_widget)
def _create_status_bar(self):
"""Create status bar."""
self.status_bar = QStatusBar()
self.setStatusBar(self.status_bar)
# Add permanent widgets to status bar
self.status_label = QLabel("Ready")
self.status_bar.addWidget(self.status_label)
# Initial status message
self._update_status("Ready")
def _center_window(self):
"""Center window on screen."""
screen = self.screen().geometry()
size = self.geometry()
self.move(
(screen.width() - size.width()) // 2, (screen.height() - size.height()) // 2
)
def _show_settings(self):
"""Show settings dialog."""
logger.info("Opening settings dialog")
dialog = ConfigDialog(self.config_manager, self)
if dialog.exec():
self._apply_settings()
self._update_status("Settings saved")
def _apply_settings(self):
"""Apply changed settings."""
logger.info("Applying settings changes")
# Reload configuration in all tabs if needed
try:
if hasattr(self, "detection_tab"):
self.detection_tab.refresh()
if hasattr(self, "training_tab"):
self.training_tab.refresh()
if hasattr(self, "results_tab"):
self.results_tab.refresh()
except Exception as e:
logger.error(f"Error applying settings: {e}")
def _refresh_current_tab(self):
"""Refresh the current tab."""
current_widget = self.tab_widget.currentWidget()
if hasattr(current_widget, "refresh"):
current_widget.refresh()
self._update_status("Tab refreshed")
def _on_tab_changed(self, index: int):
"""Handle tab change event."""
tab_name = self.tab_widget.tabText(index)
logger.debug(f"Switched to tab: {tab_name}")
self._update_status(f"Viewing: {tab_name}")
def _show_database_stats(self):
"""Show database statistics dialog."""
try:
stats = self.db_manager.get_detection_statistics()
message = f"""
<h3>Database Statistics</h3>
<p><b>Total Detections:</b> {stats.get('total_detections', 0)}</p>
<p><b>Average Confidence:</b> {stats.get('average_confidence', 0):.2%}</p>
<p><b>Classes:</b></p>
<ul>
"""
for class_name, count in stats.get("class_counts", {}).items():
message += f"<li>{class_name}: {count}</li>"
message += "</ul>"
QMessageBox.information(self, "Database Statistics", message)
except Exception as e:
logger.error(f"Error getting database stats: {e}")
QMessageBox.warning(
self, "Error", f"Failed to get database statistics:\n{str(e)}"
)
def _show_about(self):
"""Show about dialog."""
about_text = """
<h2>Microscopy Object Detection Application</h2>
<p><b>Version:</b> 1.0.0</p>
<p>A desktop application for detecting organelles and membrane branching
structures in microscopy images using YOLOv8.</p>
<p><b>Features:</b></p>
<ul>
<li>Object detection with YOLOv8</li>
<li>Model training and validation</li>
<li>Detection results storage</li>
<li>Interactive visualization</li>
<li>Export capabilities</li>
</ul>
<p><b>Technologies:</b></p>
<ul>
<li>Ultralytics YOLOv8</li>
<li>PySide6</li>
<li>pyqtgraph</li>
<li>SQLite</li>
</ul>
"""
QMessageBox.about(self, "About", about_text)
def _show_documentation(self):
"""Show documentation."""
QMessageBox.information(
self,
"Documentation",
"Please refer to README.md and ARCHITECTURE.md files in the project directory.",
)
def _update_status(self, message: str, timeout: int = 5000):
"""
Update status bar message.
Args:
message: Status message to display
timeout: Time in milliseconds to show message (0 for permanent)
"""
self.status_label.setText(message)
if timeout > 0:
QTimer.singleShot(timeout, lambda: self.status_label.setText("Ready"))
def closeEvent(self, event):
"""Handle window close event."""
reply = QMessageBox.question(
self,
"Confirm Exit",
"Are you sure you want to exit?",
QMessageBox.Yes | QMessageBox.No,
QMessageBox.No,
)
if reply == QMessageBox.Yes:
logger.info("Application closing")
event.accept()
else:
event.ignore()

0
src/gui/tabs/__init__.py Normal file
View File

View File

@@ -0,0 +1,48 @@
"""
Annotation tab for the microscopy object detection application.
Future feature for manual annotation.
"""
from PySide6.QtWidgets import QWidget, QVBoxLayout, QLabel, QGroupBox
from src.database.db_manager import DatabaseManager
from src.utils.config_manager import ConfigManager
class AnnotationTab(QWidget):
"""Annotation tab placeholder (future feature)."""
def __init__(
self, db_manager: DatabaseManager, config_manager: ConfigManager, parent=None
):
super().__init__(parent)
self.db_manager = db_manager
self.config_manager = config_manager
self._setup_ui()
def _setup_ui(self):
"""Setup user interface."""
layout = QVBoxLayout()
group = QGroupBox("Annotation Tool (Future Feature)")
group_layout = QVBoxLayout()
label = QLabel(
"Annotation functionality will be implemented in future version.\n\n"
"Planned Features:\n"
"- Image browser\n"
"- Drawing tools for bounding boxes\n"
"- Class label assignment\n"
"- Export annotations to YOLO format\n"
"- Annotation verification"
)
group_layout.addWidget(label)
group.setLayout(group_layout)
layout.addWidget(group)
layout.addStretch()
self.setLayout(layout)
def refresh(self):
"""Refresh the tab."""
pass

View File

@@ -0,0 +1,344 @@
"""
Detection tab for the microscopy object detection application.
Handles single image and batch detection.
"""
from PySide6.QtWidgets import (
QWidget,
QVBoxLayout,
QHBoxLayout,
QPushButton,
QLabel,
QComboBox,
QSlider,
QFileDialog,
QMessageBox,
QProgressBar,
QTextEdit,
QGroupBox,
QFormLayout,
)
from PySide6.QtCore import Qt, QThread, Signal
from pathlib import Path
from src.database.db_manager import DatabaseManager
from src.utils.config_manager import ConfigManager
from src.utils.logger import get_logger
from src.utils.file_utils import get_image_files
from src.model.inference import InferenceEngine
logger = get_logger(__name__)
class DetectionWorker(QThread):
"""Worker thread for running detection."""
progress = Signal(int, int, str) # current, total, message
finished = Signal(list) # results
error = Signal(str) # error message
def __init__(self, engine, image_paths, repo_root, conf):
super().__init__()
self.engine = engine
self.image_paths = image_paths
self.repo_root = repo_root
self.conf = conf
def run(self):
"""Run detection in background thread."""
try:
results = self.engine.detect_batch(
self.image_paths, self.repo_root, self.conf, self.progress.emit
)
self.finished.emit(results)
except Exception as e:
logger.error(f"Detection error: {e}")
self.error.emit(str(e))
class DetectionTab(QWidget):
"""Detection tab for single image and batch detection."""
def __init__(
self, db_manager: DatabaseManager, config_manager: ConfigManager, parent=None
):
super().__init__(parent)
self.db_manager = db_manager
self.config_manager = config_manager
self.inference_engine = None
self.current_model_id = None
self._setup_ui()
self._connect_signals()
self._load_models()
def _setup_ui(self):
"""Setup user interface."""
layout = QVBoxLayout()
# Model selection group
model_group = QGroupBox("Model Selection")
model_layout = QFormLayout()
self.model_combo = QComboBox()
self.model_combo.addItem("No models available", None)
model_layout.addRow("Model:", self.model_combo)
model_group.setLayout(model_layout)
layout.addWidget(model_group)
# Detection settings group
settings_group = QGroupBox("Detection Settings")
settings_layout = QFormLayout()
# Confidence threshold
conf_layout = QHBoxLayout()
self.conf_slider = QSlider(Qt.Horizontal)
self.conf_slider.setRange(0, 100)
self.conf_slider.setValue(25)
self.conf_slider.setTickPosition(QSlider.TicksBelow)
self.conf_slider.setTickInterval(10)
conf_layout.addWidget(self.conf_slider)
self.conf_label = QLabel("0.25")
conf_layout.addWidget(self.conf_label)
settings_layout.addRow("Confidence:", conf_layout)
settings_group.setLayout(settings_layout)
layout.addWidget(settings_group)
# Action buttons
button_layout = QHBoxLayout()
self.single_image_btn = QPushButton("Detect Single Image")
self.single_image_btn.clicked.connect(self._detect_single_image)
button_layout.addWidget(self.single_image_btn)
self.batch_btn = QPushButton("Detect Batch (Folder)")
self.batch_btn.clicked.connect(self._detect_batch)
button_layout.addWidget(self.batch_btn)
layout.addLayout(button_layout)
# Progress bar
self.progress_bar = QProgressBar()
self.progress_bar.setVisible(False)
layout.addWidget(self.progress_bar)
# Results display
results_group = QGroupBox("Detection Results")
results_layout = QVBoxLayout()
self.results_text = QTextEdit()
self.results_text.setReadOnly(True)
self.results_text.setMaximumHeight(200)
results_layout.addWidget(self.results_text)
results_group.setLayout(results_layout)
layout.addWidget(results_group)
layout.addStretch()
self.setLayout(layout)
def _connect_signals(self):
"""Connect signals and slots."""
self.conf_slider.valueChanged.connect(self._update_confidence_label)
self.model_combo.currentIndexChanged.connect(self._on_model_changed)
def _load_models(self):
"""Load available models from database."""
try:
models = self.db_manager.get_models()
self.model_combo.clear()
if not models:
self.model_combo.addItem("No models available", None)
self._set_buttons_enabled(False)
return
# Add base model option
base_model = self.config_manager.get(
"models.default_base_model", "yolov8s.pt"
)
self.model_combo.addItem(
f"Base Model ({base_model})", {"id": 0, "path": base_model}
)
# Add trained models
for model in models:
display_name = f"{model['model_name']} v{model['model_version']}"
self.model_combo.addItem(display_name, model)
self._set_buttons_enabled(True)
except Exception as e:
logger.error(f"Error loading models: {e}")
QMessageBox.warning(self, "Error", f"Failed to load models:\n{str(e)}")
def _on_model_changed(self, index: int):
"""Handle model selection change."""
model_data = self.model_combo.itemData(index)
if model_data and model_data["id"] != 0:
self.current_model_id = model_data["id"]
else:
self.current_model_id = None
def _update_confidence_label(self, value: int):
"""Update confidence label."""
conf = value / 100.0
self.conf_label.setText(f"{conf:.2f}")
def _detect_single_image(self):
"""Detect objects in a single image."""
# Get image file
repo_path = self.config_manager.get_image_repository_path()
start_dir = repo_path if repo_path else ""
file_path, _ = QFileDialog.getOpenFileName(
self,
"Select Image",
start_dir,
"Images (*.jpg *.jpeg *.png *.tif *.tiff *.bmp)",
)
if not file_path:
return
# Run detection
self._run_detection([file_path])
def _detect_batch(self):
"""Detect objects in batch (folder)."""
# Get folder
repo_path = self.config_manager.get_image_repository_path()
start_dir = repo_path if repo_path else ""
folder_path = QFileDialog.getExistingDirectory(self, "Select Folder", start_dir)
if not folder_path:
return
# Get all image files
allowed_ext = self.config_manager.get_allowed_extensions()
image_files = get_image_files(folder_path, allowed_ext, recursive=False)
if not image_files:
QMessageBox.information(
self, "No Images", "No image files found in selected folder."
)
return
# Confirm batch processing
reply = QMessageBox.question(
self,
"Confirm Batch Detection",
f"Process {len(image_files)} images?",
QMessageBox.Yes | QMessageBox.No,
)
if reply == QMessageBox.Yes:
self._run_detection(image_files)
def _run_detection(self, image_paths: list):
"""Run detection on image list."""
try:
# Get selected model
model_data = self.model_combo.currentData()
if not model_data:
QMessageBox.warning(self, "No Model", "Please select a model first.")
return
model_path = model_data["path"]
model_id = model_data["id"]
# Ensure we have a valid model ID (create entry for base model if needed)
if model_id == 0:
# Create database entry for base model
base_model = self.config_manager.get(
"models.default_base_model", "yolov8s.pt"
)
model_id = self.db_manager.add_model(
model_name="Base Model",
model_version="pretrained",
model_path=base_model,
base_model=base_model,
)
# Create inference engine
self.inference_engine = InferenceEngine(
model_path, self.db_manager, model_id
)
# Get confidence threshold
conf = self.conf_slider.value() / 100.0
# Get repository root
repo_root = self.config_manager.get_image_repository_path()
if not repo_root:
repo_root = str(Path(image_paths[0]).parent)
# Show progress bar
self.progress_bar.setVisible(True)
self.progress_bar.setMaximum(len(image_paths))
self._set_buttons_enabled(False)
# Create and start worker thread
self.worker = DetectionWorker(
self.inference_engine, image_paths, repo_root, conf
)
self.worker.progress.connect(self._on_progress)
self.worker.finished.connect(self._on_detection_finished)
self.worker.error.connect(self._on_detection_error)
self.worker.start()
except Exception as e:
logger.error(f"Error starting detection: {e}")
QMessageBox.critical(self, "Error", f"Failed to start detection:\n{str(e)}")
self._set_buttons_enabled(True)
def _on_progress(self, current: int, total: int, message: str):
"""Handle progress update."""
self.progress_bar.setValue(current)
self.results_text.append(f"[{current}/{total}] {message}")
def _on_detection_finished(self, results: list):
"""Handle detection completion."""
self.progress_bar.setVisible(False)
self._set_buttons_enabled(True)
# Calculate statistics
total_detections = sum(r["count"] for r in results)
successful = sum(1 for r in results if r.get("success", False))
summary = f"\n=== Detection Complete ===\n"
summary += f"Processed: {len(results)} images\n"
summary += f"Successful: {successful}\n"
summary += f"Total detections: {total_detections}\n"
self.results_text.append(summary)
QMessageBox.information(
self,
"Detection Complete",
f"Processed {len(results)} images\n{total_detections} objects detected",
)
def _on_detection_error(self, error_msg: str):
"""Handle detection error."""
self.progress_bar.setVisible(False)
self._set_buttons_enabled(True)
self.results_text.append(f"\nERROR: {error_msg}")
QMessageBox.critical(self, "Detection Error", error_msg)
def _set_buttons_enabled(self, enabled: bool):
"""Enable/disable action buttons."""
self.single_image_btn.setEnabled(enabled)
self.batch_btn.setEnabled(enabled)
self.model_combo.setEnabled(enabled)
def refresh(self):
"""Refresh the tab."""
self._load_models()
self.results_text.clear()

View File

@@ -0,0 +1,46 @@
"""
Results tab for the microscopy object detection application.
"""
from PySide6.QtWidgets import QWidget, QVBoxLayout, QLabel, QGroupBox
from src.database.db_manager import DatabaseManager
from src.utils.config_manager import ConfigManager
class ResultsTab(QWidget):
"""Results tab placeholder."""
def __init__(
self, db_manager: DatabaseManager, config_manager: ConfigManager, parent=None
):
super().__init__(parent)
self.db_manager = db_manager
self.config_manager = config_manager
self._setup_ui()
def _setup_ui(self):
"""Setup user interface."""
layout = QVBoxLayout()
group = QGroupBox("Results")
group_layout = QVBoxLayout()
label = QLabel(
"Results viewer will be implemented here.\n\n"
"Features:\n"
"- Detection history browser\n"
"- Advanced filtering\n"
"- Statistics dashboard\n"
"- Export functionality"
)
group_layout.addWidget(label)
group.setLayout(group_layout)
layout.addWidget(group)
layout.addStretch()
self.setLayout(layout)
def refresh(self):
"""Refresh the tab."""
pass

View File

@@ -0,0 +1,52 @@
"""
Training tab for the microscopy object detection application.
Handles model training with YOLO.
"""
from PySide6.QtWidgets import QWidget, QVBoxLayout, QLabel, QGroupBox
from src.database.db_manager import DatabaseManager
from src.utils.config_manager import ConfigManager
from src.utils.logger import get_logger
logger = get_logger(__name__)
class TrainingTab(QWidget):
"""Training tab for model training."""
def __init__(
self, db_manager: DatabaseManager, config_manager: ConfigManager, parent=None
):
super().__init__(parent)
self.db_manager = db_manager
self.config_manager = config_manager
self._setup_ui()
def _setup_ui(self):
"""Setup user interface."""
layout = QVBoxLayout()
# Placeholder
group = QGroupBox("Training")
group_layout = QVBoxLayout()
label = QLabel(
"Training functionality will be implemented here.\n\n"
"Features:\n"
"- Dataset selection\n"
"- Training parameter configuration\n"
"- Real-time training progress\n"
"- Loss and metric visualization"
)
group_layout.addWidget(label)
group.setLayout(group_layout)
layout.addWidget(group)
layout.addStretch()
self.setLayout(layout)
def refresh(self):
"""Refresh the tab."""
pass

View File

@@ -0,0 +1,46 @@
"""
Validation tab for the microscopy object detection application.
"""
from PySide6.QtWidgets import QWidget, QVBoxLayout, QLabel, QGroupBox
from src.database.db_manager import DatabaseManager
from src.utils.config_manager import ConfigManager
class ValidationTab(QWidget):
"""Validation tab placeholder."""
def __init__(
self, db_manager: DatabaseManager, config_manager: ConfigManager, parent=None
):
super().__init__(parent)
self.db_manager = db_manager
self.config_manager = config_manager
self._setup_ui()
def _setup_ui(self):
"""Setup user interface."""
layout = QVBoxLayout()
group = QGroupBox("Validation")
group_layout = QVBoxLayout()
label = QLabel(
"Validation functionality will be implemented here.\n\n"
"Features:\n"
"- Model validation\n"
"- Metrics visualization\n"
"- Confusion matrix\n"
"- Precision-Recall curves"
)
group_layout.addWidget(label)
group.setLayout(group_layout)
layout.addWidget(group)
layout.addStretch()
self.setLayout(layout)
def refresh(self):
"""Refresh the tab."""
pass

View File