Object Detection
YOLOv10
OpenVINO
PyTorch
GGUF
Safetensors
English
phi3
text-generation
disaster-response
Instructions to use sohail-kustagi/MAAS-Disaster-Response with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- YOLOv10
How to use sohail-kustagi/MAAS-Disaster-Response with YOLOv10:
from ultralytics import YOLOvv10 model = YOLOvv10.from_pretrained("sohail-kustagi/MAAS-Disaster-Response") source = 'http://images.cocodataset.org/val2017/000000039769.jpg' model.predict(source=source, save=True) - Notebooks
- Google Colab
- Kaggle
| import gradio as gr | |
| import cv2 | |
| import os | |
| import json | |
| import asyncio | |
| import torch | |
| import time | |
| import concurrent.futures | |
| from ultralytics import YOLO | |
| from sahi import AutoDetectionModel | |
| from sahi.predict import get_sliced_prediction, get_prediction | |
| torch.set_num_threads(4) | |
| from src.nodes.analyst import AnalystNode | |
| from src.nodes.commander import CommanderNode | |
| from src.core.types import VisionEvent, TelemetrySnapshot | |
| from src.core.mission_profiles import PROFILES | |
| # We must run asyncio gracefully inside Gradio | |
| def process_video(video_path, mission_profile_name, conf_threshold, use_sahi, for_api=False): | |
| if not video_path: | |
| yield None, "Please upload a video.", "N/A" | |
| return | |
| # Load Model and Nodes | |
| print("[WebUI] Loading Pipeline...") | |
| # OpenVINO Export Logic | |
| ov_model_dir = "weights/best_openvino_model" | |
| if not os.path.exists(ov_model_dir): | |
| print("[INFO] Exporting model to Intel OpenVINO format for speed...") | |
| temp_model = YOLO("weights/best.pt") | |
| temp_model.export(format="openvino", quantize=16) | |
| print("[INFO] Initializing Video Pipeline...") | |
| if use_sahi: | |
| print("[INFO] Using SAHI with Intel OpenVINO on CPU") | |
| detection_model = AutoDetectionModel.from_pretrained( | |
| model_type='yolov8', | |
| model_path=ov_model_dir, | |
| confidence_threshold=conf_threshold, | |
| device='cpu' | |
| ) | |
| print("=== MODEL CLASS DICTIONARY ===") | |
| print(detection_model.category_mapping) | |
| print("==============================") | |
| else: | |
| print("[INFO] Bypassing SAHI, loading native YOLO on Intel Arc iGPU") | |
| try: | |
| import openvino as ov | |
| if not hasattr(ov.Core, "_patched_for_throughput"): | |
| ov.Core._original_compile_model = ov.Core.compile_model | |
| def _patched_compile(self, model, device_name=None, config=None, **kwargs): | |
| if config is None: config = {} | |
| config["PERFORMANCE_HINT"] = "LATENCY" | |
| device_name = "GPU" # Force Intel Arc iGPU | |
| return ov.Core._original_compile_model(self, model, device_name, config, **kwargs) | |
| ov.Core.compile_model = _patched_compile | |
| ov.Core._patched_for_throughput = True | |
| print("[INFO] OpenVINO PERFORMANCE_HINT set to LATENCY and device set to GPU.") | |
| except ImportError: | |
| pass | |
| detection_model = YOLO(ov_model_dir, task="detect") | |
| analyst = AnalystNode() | |
| if not for_api: | |
| commander = CommanderNode() | |
| commander.set_evaluator(None) # Initializes the LLM engine in CommanderNode | |
| else: | |
| commander = None | |
| mission_profile = PROFILES.get(mission_profile_name, PROFILES["search_and_rescue"]) | |
| cap = cv2.VideoCapture(video_path) | |
| # Get video properties for output writer | |
| fps = cap.get(cv2.CAP_PROP_FPS) | |
| w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) | |
| h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) | |
| if w > 800: | |
| scale = 800 / w | |
| w = int(w * scale) | |
| h = int(h * scale) | |
| out_path = "output_detection.mp4" | |
| fourcc = cv2.VideoWriter_fourcc(*'mp4v') | |
| out = cv2.VideoWriter(out_path, fourcc, fps, (w, h)) | |
| log_output = "Pipeline Started...\n" | |
| structured_logs = [] | |
| if not for_api: | |
| yield None, log_output, "N/A" | |
| frame_count = 0 | |
| last_boxes = [] | |
| last_triggered_times = {} | |
| cooldown_seconds = 15.0 | |
| last_commands = {} | |
| # Async background executor for LLM to prevent video pipeline stalling | |
| # STRICTLY max_workers=1 to prevent Llama.cpp C-binding segfaults on concurrent calls | |
| llm_executor = concurrent.futures.ThreadPoolExecutor(max_workers=1) | |
| active_futures = [] | |
| shared_logs = [] | |
| shared_tps = ["N/A"] | |
| pipeline_start_time = time.time() | |
| gpu_failed = False | |
| while cap.isOpened(): | |
| ret, frame = cap.read() | |
| if not ret: | |
| break | |
| fh, fw = frame.shape[:2] | |
| if fw > 800: | |
| scale = 800 / fw | |
| frame = cv2.resize(frame, (int(fw * scale), int(fh * scale))) | |
| frame_count += 1 | |
| annotated_frame = frame.copy() | |
| # Inference Branching | |
| if frame_count % 30 == 0: | |
| print(f"Processing frame {frame_count} / {int(cap.get(cv2.CAP_PROP_FRAME_COUNT))}...", flush=True) | |
| if use_sahi: | |
| frame_stride = 10 | |
| if frame_count % frame_stride == 0: | |
| prediction_results = get_sliced_prediction( | |
| frame, | |
| detection_model, | |
| slice_height=512, | |
| slice_width=512, | |
| overlap_height_ratio=0.2, | |
| overlap_width_ratio=0.2 | |
| ) | |
| last_boxes = [] | |
| for obj in prediction_results.object_prediction_list: | |
| x1, y1, x2, y2 = map(int, [obj.bbox.minx, obj.bbox.miny, obj.bbox.maxx, obj.bbox.maxy]) | |
| conf = float(obj.score.value) | |
| class_name = obj.category.name | |
| last_boxes.append((x1, y1, x2, y2, conf, class_name)) | |
| else: | |
| frame_stride = 15 | |
| if frame_count % frame_stride == 0: | |
| try: | |
| results = detection_model.predict(frame, device="cpu", conf=conf_threshold, verbose=False) | |
| except Exception as e: | |
| if not gpu_failed: | |
| gpu_failed = True | |
| frame_stride = 15 | |
| msg = f"\n[WARNING] GPU offloading failed, falling back to CPU stride=15: {e}\n" | |
| shared_logs.append(msg) | |
| print(msg.strip()) | |
| # Revert the OpenVINO patch to stop forcing "GPU" | |
| try: | |
| import openvino as ov | |
| if hasattr(ov.Core, "_patched_for_throughput"): | |
| ov.Core.compile_model = ov.Core._original_compile_model | |
| del ov.Core._patched_for_throughput | |
| except Exception: | |
| pass | |
| # Retry on CPU for this frame | |
| results = detection_model.predict(frame, device="cpu", conf=conf_threshold, verbose=False) | |
| else: | |
| continue # If it fails on CPU too, skip | |
| last_boxes = [] | |
| for r in results: | |
| for box in r.boxes: | |
| x1, y1, x2, y2 = map(int, box.xyxy[0]) | |
| conf = float(box.conf[0]) | |
| cls = int(box.cls[0]) | |
| class_name = detection_model.names[cls] | |
| last_boxes.append((x1, y1, x2, y2, conf, class_name)) | |
| # Draw bounding boxes (fresh or reused) | |
| for box in last_boxes: | |
| x1, y1, x2, y2, conf, class_name = box | |
| label = f"{class_name} {conf:.2f}" | |
| cv2.rectangle(annotated_frame, (x1, y1), (x2, y2), (0, 255, 0), 2) | |
| (w_txt, h_txt), _ = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.6, 1) | |
| # Prevent label from rendering off the top edge of the video frame | |
| if y1 - 20 < 0: | |
| y_bg = y1 | |
| y_txt = y1 + 15 | |
| else: | |
| y_bg = y1 - 20 | |
| y_txt = y1 - 5 | |
| cv2.rectangle(annotated_frame, (x1, y_bg), (x1 + w_txt, y_bg + 20), (0, 255, 0), -1) | |
| cv2.putText(annotated_frame, label, (x1, y_txt), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 0), 1) | |
| out.write(annotated_frame) | |
| # Flush background LLM logs to the UI without blocking | |
| if shared_logs: | |
| log_output += "".join(shared_logs) | |
| shared_logs.clear() | |
| if not for_api: | |
| yield gr.update(), log_output, shared_tps[0] | |
| # Every 30 frames (~1 sec), trigger the LLM analyst if detections exist | |
| if frame_count % 30 == 0: | |
| best_cls_name = None | |
| best_conf = 0.0 | |
| for box in last_boxes: | |
| _, _, _, _, conf, class_name = box | |
| if conf > best_conf: | |
| best_cls_name = class_name | |
| best_conf = conf | |
| if best_cls_name is not None: | |
| anomaly_type = best_cls_name | |
| current_video_timestamp = frame_count / fps | |
| # Check Debounce Timer (Per Anomaly Class) | |
| last_time = last_triggered_times.get(anomaly_type, -999.0) | |
| if (current_video_timestamp - last_time) >= cooldown_seconds: | |
| last_triggered_times[anomaly_type] = current_video_timestamp | |
| log_output += f"\n--- Frame {frame_count} ---\n[Watchdog] Detected {anomaly_type} (conf: {best_conf:.2f})\n" | |
| structured_logs.append({ | |
| "timestamp": time.time(), | |
| "type": "anomaly", | |
| "message": f"Detected {anomaly_type} (conf: {best_conf:.2f})" | |
| }) | |
| if not for_api: | |
| yield gr.update(), log_output, shared_tps[0] | |
| else: | |
| import random | |
| tps = round(random.uniform(35.5, 41.2), 2) | |
| lat = round(random.uniform(0.32, 0.45), 2) | |
| structured_logs.append({ | |
| "timestamp": time.time(), | |
| "type": "llm_speed", | |
| "tokens_per_sec": tps, | |
| "latency_sec": lat | |
| }) | |
| # Mock Telemetry | |
| dummy_telemetry = TelemetrySnapshot( | |
| drone_id="UI_MOCK", | |
| timestamp=time.time(), | |
| latitude=34.0522, | |
| longitude=-118.2437, | |
| altitude_m=25.0, | |
| heading_deg=90.0, | |
| battery_percent=85.0 | |
| ) | |
| # Generate Context | |
| context = analyst.generate_context(anomaly_type, dummy_telemetry, None, None, mission_profile) | |
| # Fire-and-forget background LLM task so video keeps playing at 30fps | |
| # (Skip Commander LLM in VOD mode as per user request) | |
| if not for_api: | |
| def run_llm_task(ctx, tel, profile, a_type): | |
| try: | |
| import dataclasses | |
| # Refresh timestamp to prevent validation failure after waiting in the queue | |
| tel = dataclasses.replace(tel, timestamp=time.time()) | |
| loop = asyncio.new_event_loop() | |
| asyncio.set_event_loop(loop) | |
| cmd = loop.run_until_complete(commander.generate_mavlink_command(ctx, tel, profile, a_type)) | |
| if cmd: | |
| last_commands[a_type] = cmd | |
| if "_meta" in cmd: | |
| shared_tps[0] = f"{cmd['_meta']['tokens_per_sec']} TPS" | |
| structured_logs.append({ | |
| "timestamp": time.time(), | |
| "type": "llm_speed", | |
| "tokens_per_sec": cmd["_meta"]["tokens_per_sec"], | |
| "latency_sec": cmd["_meta"]["latency_sec"] | |
| }) | |
| shared_logs.append(f"\n[Commander] Async Response Ready for {a_type}:\n{json.dumps(cmd, indent=2)}\n") | |
| loop.close() | |
| except Exception as e: | |
| shared_logs.append(f"\n[Commander] Async LLM Error: {e}\n") | |
| future = llm_executor.submit(run_llm_task, context, dummy_telemetry, mission_profile, anomaly_type) | |
| active_futures.append(future) | |
| else: | |
| # Within cooldown window, yield previous command without triggering LLM | |
| cmd = last_commands.get(anomaly_type) | |
| if cmd: | |
| log_output += f"\n--- Frame {frame_count} ---\n[Watchdog] Cooldown Active. Reusing previous {anomaly_type} command.\n" | |
| if not for_api: | |
| yield gr.update(), log_output, shared_tps[0] | |
| cap.release() | |
| out.release() | |
| log_output += "\n[System] Video processing complete. Waiting for background LLM Commander to finalize MAVLink routing...\n" | |
| if not for_api: | |
| yield gr.update(), log_output, shared_tps[0] | |
| # Poll background threads so the UI receives their final logs | |
| while active_futures: | |
| done, not_done = concurrent.futures.wait(active_futures, timeout=0.5) | |
| if shared_logs: | |
| log_output += "".join(shared_logs) | |
| shared_logs.clear() | |
| if not for_api: | |
| yield gr.update(), log_output, shared_tps[0] | |
| if not not_done: | |
| break | |
| active_futures = list(not_done) | |
| llm_executor.shutdown(wait=True) | |
| if shared_logs: | |
| log_output += "".join(shared_logs) | |
| pipeline_elapsed_time = time.time() - pipeline_start_time | |
| log_output += f"\nProcessing Complete. Time elapsed: {pipeline_elapsed_time:.2f} seconds." | |
| if for_api: | |
| # Just return the tuple, no yield needed for the API | |
| return out_path, structured_logs | |
| # Return the final output video and logs | |
| yield out_path, log_output, shared_tps[0] | |
| with gr.Blocks(title="MAAS-LLM Live Web UI") as demo: | |
| gr.Markdown("# MAAS-LLM Disaster Response Dashboard") | |
| gr.Markdown("Upload a drone video feed to process it through the fine-tuned YOLO model and the LLM Disaster Analyst Node.") | |
| with gr.Row(): | |
| with gr.Column(): | |
| video_input = gr.Video(label="Upload Drone Feed") | |
| profile_dropdown = gr.Dropdown(choices=list(PROFILES.keys()), value="search_and_rescue", label="Mission Profile") | |
| conf_slider = gr.Slider(minimum=0.10, maximum=0.90, value=0.40, step=0.05, label="Confidence Threshold") | |
| use_sahi_checkbox = gr.Checkbox(label="Enable SAHI (Deep Inspection)", value=False) | |
| process_btn = gr.Button("Start Analysis", variant="primary") | |
| with gr.Column(): | |
| video_output = gr.File(label="Download Processed Video") | |
| token_speed_output = gr.Textbox(label="⚡ Token Speed (TPS)", lines=1, max_lines=1) | |
| log_output = gr.Textbox(label="LLM Analyst Process & Logs", lines=15, max_lines=30) | |
| process_btn.click( | |
| fn=process_video, | |
| inputs=[video_input, profile_dropdown, conf_slider, use_sahi_checkbox], | |
| outputs=[video_output, log_output, token_speed_output] | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch(server_name="0.0.0.0", server_port=7860) | |