locule_worker.py 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. """
  2. Locule Worker Module
  3. Worker thread for async locule segmentation and counting.
  4. """
  5. from typing import Optional
  6. import logging
  7. from PyQt5.QtCore import pyqtSignal
  8. from PyQt5.QtGui import QImage
  9. from workers.base_worker import BaseWorker, WorkerSignals
  10. from models.locule_model import LoculeModel
  11. from utils.config import get_device
  12. logger = logging.getLogger(__name__)
  13. class LoculeWorkerSignals(WorkerSignals):
  14. """
  15. Signals specific to locule counting.
  16. Signals:
  17. result_ready: Emitted when counting is complete
  18. (annotated_image: QImage, locule_count: int, detections: list)
  19. """
  20. result_ready = pyqtSignal(QImage, int, list)
  21. class LoculeWorker(BaseWorker):
  22. """
  23. Worker for processing images and counting locules.
  24. Runs LoculeModel inference in a background thread without blocking UI.
  25. Attributes:
  26. image_path: Path to image file
  27. model: LoculeModel instance
  28. signals: LoculeWorkerSignals for emitting results
  29. """
  30. def __init__(self, image_path: str, model: Optional[LoculeModel] = None):
  31. """
  32. Initialize the locule worker.
  33. Args:
  34. image_path: Path to cross-section image to process
  35. model: LoculeModel instance (if None, creates new one)
  36. """
  37. super().__init__()
  38. self.image_path = image_path
  39. self.model = model
  40. # Replace base signals with locule-specific signals
  41. self.signals = LoculeWorkerSignals()
  42. # If no model provided, create and load one
  43. if self.model is None:
  44. device = get_device()
  45. self.model = LoculeModel(device=device)
  46. self.model.load()
  47. logger.info(f"LoculeWorker created for: {image_path}")
  48. def process(self):
  49. """
  50. Process the image and count locules.
  51. Emits result_ready signal with counting results.
  52. """
  53. if self.is_cancelled():
  54. logger.info("LoculeWorker cancelled before processing")
  55. return
  56. # Update progress
  57. self.emit_progress(10, "Loading image...")
  58. if not self.model.is_loaded:
  59. logger.warning("Model not loaded, loading now...")
  60. self.emit_progress(30, "Loading model...")
  61. if not self.model.load():
  62. raise RuntimeError("Failed to load locule model")
  63. # Process image
  64. self.emit_progress(50, "Segmenting locules...")
  65. result = self.model.predict(self.image_path)
  66. if self.is_cancelled():
  67. logger.info("LoculeWorker cancelled during processing")
  68. return
  69. if not result['success']:
  70. raise RuntimeError(result['error'])
  71. self.emit_progress(90, "Finalizing results...")
  72. # Make a copy of the QImage to ensure thread safety
  73. # QImage data can be garbage collected if not copied
  74. annotated_image_copy = result['annotated_image'].copy()
  75. # Emit results
  76. self.signals.result_ready.emit(
  77. annotated_image_copy,
  78. result['locule_count'],
  79. result['detections']
  80. )
  81. self.emit_progress(100, "Complete!")
  82. logger.info(f"LoculeWorker completed: {result['locule_count']} locules detected")