Spaces:
Build error
Build error
| import os | |
| import cv2 | |
| import gradio as gr | |
| import imageio.v2 as imageio | |
| import numpy as np | |
| from PIL import Image, ImageDraw | |
| from vision import Classifier | |
| from utils import box_iou, nms | |
| def _sample_indices(total, n): | |
| if total <= 0: | |
| return [] | |
| if total <= n: | |
| return list(range(total)) | |
| return np.linspace(0, total - 1, n).astype(int).tolist() | |
| def _select_from_list(frames, n): | |
| if not frames: | |
| return [] | |
| indices = _sample_indices(len(frames), n) | |
| return [frames[i] for i in indices] | |
| def _extract_with_cv2(video_path, n): | |
| cap = cv2.VideoCapture(video_path) | |
| if not cap.isOpened(): | |
| raise ValueError("Could not open video file.") | |
| frames = [] | |
| try: | |
| total = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) | |
| if total > 0: | |
| for idx in _sample_indices(total, n): | |
| cap.set(cv2.CAP_PROP_POS_FRAMES, int(idx)) | |
| ok, frame = cap.read() | |
| if not ok: | |
| continue | |
| frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) | |
| frames.append(Image.fromarray(frame)) | |
| return frames | |
| all_frames = [] | |
| while True: | |
| ok, frame = cap.read() | |
| if not ok: | |
| break | |
| frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) | |
| all_frames.append(Image.fromarray(frame)) | |
| return _select_from_list(all_frames, n) | |
| finally: | |
| cap.release() | |
| def _get_imageio_count(reader): | |
| try: | |
| return reader.count_frames() | |
| except Exception: | |
| pass | |
| try: | |
| meta = reader.get_meta_data() | |
| count = meta.get("nframes") | |
| if count and count != float("inf"): | |
| return int(count) | |
| except Exception: | |
| pass | |
| return None | |
| def _extract_with_imageio(video_path, n): | |
| reader = imageio.get_reader(video_path) | |
| try: | |
| total = _get_imageio_count(reader) | |
| if total: | |
| frames = [] | |
| for idx in _sample_indices(total, n): | |
| frame = reader.get_data(idx) | |
| frames.append(Image.fromarray(frame)) | |
| return frames | |
| all_frames = [Image.fromarray(frame) for frame in reader] | |
| return _select_from_list(all_frames, n) | |
| finally: | |
| reader.close() | |
| def split_video(video_path, n=8): | |
| if not video_path or not os.path.exists(video_path): | |
| return [] | |
| # Prefer OpenCV for random access; use imageio as a fallback path if needed. | |
| try: | |
| return _extract_with_cv2(video_path, n) | |
| except Exception: | |
| return _extract_with_imageio(video_path, n) | |
| model = Classifier(format="onnx") | |
| def _resolve_video_path(video_input): | |
| if not video_input: | |
| return None | |
| if isinstance(video_input, str): | |
| return video_input | |
| if isinstance(video_input, dict): | |
| for key in ("name", "path", "data", "video"): | |
| value = video_input.get(key) | |
| if isinstance(value, str) and os.path.exists(value): | |
| return value | |
| if isinstance(video_input, (list, tuple)): | |
| for value in video_input: | |
| if isinstance(value, str) and os.path.exists(value): | |
| return value | |
| return None | |
| def _draw_detections(pil_img, preds): | |
| img = pil_img.copy() | |
| draw = ImageDraw.Draw(img) | |
| width, height = img.size | |
| color = (255, 80, 0) | |
| preds = np.asarray(preds) | |
| for x1, y1, x2, y2, conf in preds: | |
| x1 = int(max(0.0, min(1.0, float(x1))) * width) | |
| y1 = int(max(0.0, min(1.0, float(y1))) * height) | |
| x2 = int(max(0.0, min(1.0, float(x2))) * width) | |
| y2 = int(max(0.0, min(1.0, float(y2))) * height) | |
| draw.rectangle([x1, y1, x2, y2], outline=color, width=3) | |
| draw.text((x1 + 4, y1 + 4), f"{conf:.2f}", fill=color) | |
| draw.text((6, 6), f"detections: {len(preds)}", fill=color) | |
| return img | |
| def infer(video_file): | |
| video_path = _resolve_video_path(video_file) | |
| frames = split_video(video_path, n=8) | |
| if not frames: | |
| return [] | |
| n_frames = len(frames) | |
| boxes = np.zeros((0, 5), dtype=np.float64) | |
| frame_preds = [] | |
| for frame in frames: | |
| bbox = np.asarray(model(frame), dtype=np.float64) | |
| frame_preds.append(bbox) | |
| if bbox.size > 0: | |
| boxes = np.vstack([boxes, bbox]) | |
| if boxes.size == 0: | |
| return [] | |
| main_bboxes = np.asarray(nms(boxes), dtype=np.float64) | |
| if main_bboxes.size == 0: | |
| return [] | |
| # Keep main boxes that appear in enough frames. | |
| matches_per_main = np.zeros(len(main_bboxes), dtype=int) | |
| for bbox in frame_preds: | |
| if bbox.size == 0: | |
| continue | |
| ious = box_iou(bbox[:, :4], main_bboxes[:, :4]) | |
| matches_per_main += (ious > 0).any(axis=1).astype(int) | |
| keep_main = matches_per_main > n_frames // 2 | |
| kept_main = main_bboxes[keep_main] if np.any(keep_main) else np.zeros((0, 5), dtype=np.float64) | |
| if kept_main.size == 0: | |
| return [] | |
| outputs = [] | |
| for main_box in kept_main: | |
| for frame, bbox in zip(frames, frame_preds): | |
| if bbox.size == 0: | |
| continue | |
| ious = box_iou(bbox[:, :4], main_box[:4].reshape(1, 4)) | |
| if (ious > 0).any(): | |
| match_idx = int(np.argmax(ious[0])) | |
| outputs.append(_draw_detections(frame, bbox[match_idx : match_idx + 1])) | |
| break | |
| return outputs | |
| with gr.Blocks() as demo: | |
| gr.Markdown("## Pyronear Wildfire Detection") | |
| with gr.Row(): | |
| video_in = gr.Video(label="Upload MP4") | |
| gallery_out = gr.Gallery(label="Wildfire detected", columns=2, height=360) | |
| run_btn = gr.Button("Detect") | |
| run_btn.click(fn=infer, inputs=video_in, outputs=gallery_out) | |
| if __name__ == "__main__": | |
| demo.launch() | |