| from __future__ import annotations |
|
|
| from collections.abc import Sequence |
|
|
| from PIL import Image |
|
|
| from src.interfaces.detector import Detector |
| from src.interfaces.reporter import ReportGenerator |
| from src.interfaces.segmenter import Segmenter |
| from src.schemas.detection import DetectionResult |
| from src.schemas.report import PipelineOutput, ReportRequest |
| from src.schemas.segmentation import MaskResult |
|
|
|
|
| class CXRPipeline: |
| """Three-stage chest X-ray analysis pipeline. |
| |
| Stage 1 (required) β Detection & localization via Detector. |
| Stage 2 (optional) β Segmentation via Segmenter. |
| Stage 3 (optional) β Report generation via ReportGenerator. |
| |
| Pass segmenter=None or reporter=None to skip those stages. |
| """ |
|
|
| def __init__( |
| self, |
| detector: Detector, |
| segmenter: Segmenter | None = None, |
| reporter: ReportGenerator | None = None, |
| ) -> None: |
| self.detector = detector |
| self.segmenter = segmenter |
| self.reporter = reporter |
|
|
| |
|
|
| def _run_segmentation( |
| self, |
| detection: DetectionResult, |
| processed_images: list[Image.Image], |
| ) -> list[MaskResult]: |
| assert self.segmenter is not None |
| masks: list[MaskResult] = [] |
|
|
| for localized_finding in detection.findings: |
| for view in localized_finding.localizations: |
| if view.status != "localized" or not view.boxes: |
| continue |
| image = processed_images[view.image_index] |
| |
| bbox = view.boxes[0].box_2d |
| mask_result = self.segmenter.segment( |
| image=image, |
| bbox_normalized=bbox, |
| finding_label=localized_finding.finding, |
| image_path=view.image_path, |
| image_index=view.image_index, |
| ) |
| masks.append(mask_result) |
|
|
| status_str = "ok" if mask_result.status == "success" else f"FAILED: {mask_result.error}" |
| print( |
| f"[Stage 2] {localized_finding.finding} | " |
| f"view {view.image_index} | {status_str}" |
| ) |
|
|
| return masks |
|
|
| |
|
|
| def run( |
| self, |
| images: Sequence[Image.Image], |
| input_images: list[str], |
| case_id: str | None = None, |
| ) -> PipelineOutput: |
| |
| print("\n" + "=" * 60) |
| print("STAGE 1 β Detection & Localization") |
| print("=" * 60) |
| detection, processed_images = self.detector.detect( |
| images, input_images, case_id=case_id |
| ) |
|
|
| |
| masks: list[MaskResult] = [] |
| if self.segmenter is not None: |
| print("\n" + "=" * 60) |
| print("STAGE 2 β Segmentation") |
| print("=" * 60) |
| masks = self._run_segmentation(detection, processed_images) |
|
|
| |
| report = None |
| if self.reporter is not None: |
| print("\n" + "=" * 60) |
| print("STAGE 3 β Report Generation") |
| print("=" * 60) |
| report_request = ReportRequest( |
| case_id=case_id, |
| input_images=input_images, |
| images=list(processed_images), |
| findings=detection.findings, |
| masks=masks, |
| ) |
| report = self.reporter.generate_report(report_request) |
|
|
| return PipelineOutput( |
| detection=detection, |
| masks=masks, |
| report=report, |
| processed_images=processed_images, |
| ) |
|
|