| """ |
| TriagePipeline: the single entrypoint the UI (or any batch script / API) |
| calls. Wires together: |
| |
| image -> YOLO11 (grounding) |
| -> Qwen2.5-VL (reasoning, grounded by YOLO context) |
| -> TriageResult (validated, clamped, defaulted) |
| -> OpenCV annotated image |
| |
| Models are loaded once (lazy singletons) so repeated Gradio calls are fast. |
| """ |
|
|
| import cv2 |
| import numpy as np |
|
|
| from .object_detector import DisasterObjectDetector |
| from .vlm_engine import DisasterVLM |
| from .annotator import annotate |
| from .schema import TriageResult |
|
|
| _detector = None |
| _vlm = None |
|
|
|
|
| def get_detector() -> DisasterObjectDetector: |
| global _detector |
| if _detector is None: |
| _detector = DisasterObjectDetector() |
| return _detector |
|
|
|
|
| def get_vlm() -> DisasterVLM: |
| global _vlm |
| if _vlm is None: |
| _vlm = DisasterVLM() |
| return _vlm |
|
|
|
|
| def run_triage(image_path: str): |
| """ |
| Returns: |
| annotated_image_rgb (np.ndarray, HxWx3, RGB) - for Gradio display |
| result (TriageResult) |
| """ |
| detector = get_detector() |
| vlm = get_vlm() |
|
|
| |
| detections = detector.detect(image_path) |
| counts = detector.summarize(detections) |
| yolo_context = detector.to_context_string(counts) |
|
|
| |
| parsed_json, raw_text = vlm.analyze(image_path, yolo_context=yolo_context) |
| result = TriageResult.from_dict(parsed_json, yolo_detections=counts, raw_model_output=raw_text) |
|
|
| |
| if result.people_visible_count == 0 and counts.get("people", 0) > 0: |
| result.people_visible_count = counts["people"] |
|
|
| |
| image_bgr = cv2.imread(image_path) |
| annotated_bgr = annotate( |
| image_bgr, detections, result.risk_score, result.risk_level, result.risk_color |
| ) |
| annotated_rgb = cv2.cvtColor(annotated_bgr, cv2.COLOR_BGR2RGB) |
|
|
| return annotated_rgb, result |
|
|