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
File size: 15,716 Bytes
b8b1fb5 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 | 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)
|