Spaces:
Sleeping
Sleeping
| import cv2 | |
| import streamlit as st | |
| from ultralytics import YOLO | |
| import time | |
| import numpy as np | |
| from datetime import datetime | |
| import pytz | |
| # Page config and header | |
| st.set_page_config( | |
| page_title="Fire Watch: AI-Powered Fire and Smoke Detection", | |
| page_icon="🔥", | |
| layout="wide", | |
| initial_sidebar_state="expanded" | |
| ) | |
| st.title("Fire Watch: Fire Detection with an AI Vision Model") | |
| # --- Session State Initialization --- | |
| if "streams" not in st.session_state: | |
| st.session_state.streams = [] | |
| if "num_streams" not in st.session_state: | |
| st.session_state.num_streams = 1 | |
| if "confidence" not in st.session_state: | |
| st.session_state.confidence = 0.30 # default 30% | |
| if "target_fps" not in st.session_state: | |
| st.session_state.target_fps = 1.0 # default 1 FPS | |
| # --- Default URLs and Names for 10 Streams --- | |
| default_m3u8_urls = [ | |
| "https://publicstreamer2.cotrip.org/rtplive/070E27555CAM1RP1/playlist.m3u8", # EB @ York St Denver | |
| "https://publicstreamer1.cotrip.org/rtplive/225N00535CAM1RP1/playlist.m3u8", # NB at Iliff Denver | |
| "https://publicstreamer2.cotrip.org/rtplive/070W28220CAM1RHS/playlist.m3u8", # WB Half Mile West of I225 Denver | |
| "https://publicstreamer1.cotrip.org/rtplive/070W26805CAM1RHS/playlist.m3u8", # 1 mile E of Kipling Denver | |
| "https://publicstreamer4.cotrip.org/rtplive/076W03150CAM1RP1/playlist.m3u8", # Main St Hudson | |
| "https://publicstreamer2.cotrip.org/rtplive/070E27660CAM1NEC/playlist.m3u8", # EB Colorado Blvd i70 Denver | |
| "https://publicstreamer2.cotrip.org/rtplive/070W27475CAM1RHS/playlist.m3u8", # E of Washington St Denver | |
| "https://publicstreamer3.cotrip.org/rtplive/070W28155CAM1RHS/playlist.m3u8", # WB Peroia St Underpass Denver | |
| "https://publicstreamer3.cotrip.org/rtplive/070E11660CAM1RHS/playlist.m3u8", # Grand Ave Glenwood | |
| "https://publicstreamer4.cotrip.org/rtplive/070E27890CAM1RHS/playlist.m3u8" # EB at i270 | |
| ] | |
| default_names = [ | |
| "EB @ York St Denver", | |
| "NB at Iliff Denver", | |
| "WB Half Mile West of I225 Denver", | |
| "1 mile E of Kipling Denver", | |
| "Main St Hudson", | |
| "EB Colorado Blvd i70 Denver", | |
| "E of Washington St Denver", | |
| "WB Peroia St Underpass Denver", | |
| "Grand Ave Glenwood", | |
| "EB at i270" | |
| ] | |
| # --- Sidebar Settings --- | |
| with st.sidebar: | |
| st.header("Stream Settings") | |
| # Custom configuration for stream 1 only. | |
| custom_m3u8 = st.text_input("Custom M3U8 URL for Stream 1 (optional)", value="", key="custom_m3u8") | |
| custom_name = st.text_input("Custom Webcam Name for Stream 1 (optional)", value="", key="custom_name") | |
| # Choose number of streams (1 to 10) | |
| num_streams = st.selectbox("Number of Streams", list(range(1, 11)), index=0) | |
| st.session_state.num_streams = num_streams | |
| # Global settings for confidence and processing rate. | |
| confidence = float(st.slider("Confidence Threshold", 5, 100, 30)) / 100 | |
| st.session_state.confidence = confidence | |
| fps_options = { | |
| "1 FPS": 1, | |
| "1 frame/2s": 0.5, | |
| "1 frame/3s": 0.3333, | |
| "1 frame/5s": 0.2, | |
| "1 frame/15s": 0.0667, | |
| "1 frame/30s": 0.0333 | |
| } | |
| video_option = st.selectbox("Processing Rate", list(fps_options.keys()), index=3) | |
| st.session_state.target_fps = fps_options[video_option] | |
| # Update or initialize the streams using defaults (with custom override for stream 1). | |
| if len(st.session_state.streams) != st.session_state.num_streams: | |
| st.session_state.streams = [] | |
| for i in range(st.session_state.num_streams): | |
| if i == 0: | |
| url = custom_m3u8.strip() if custom_m3u8.strip() else default_m3u8_urls[0] | |
| display_name = custom_name.strip() if custom_name.strip() else default_names[0] | |
| else: | |
| url = default_m3u8_urls[i] if i < len(default_m3u8_urls) else "" | |
| display_name = default_names[i] if i < len(default_names) else f"Stream {i+1}" | |
| st.session_state.streams.append({ | |
| "current_m3u8_url": url, | |
| "processed_frame": np.zeros((480, 640, 3), dtype=np.uint8), | |
| "start_time": time.time(), | |
| "processed_count": 0, | |
| "detected_frames": [], | |
| "last_processed_time": 0, | |
| "stats_text": "Processing FPS: 0.00\nFrame Delay: 0.00 sec\nTensor Results: No detections", | |
| "highest_match": 0.0, | |
| "display_name": display_name | |
| }) | |
| else: | |
| if st.session_state.num_streams > 0: | |
| url = custom_m3u8.strip() if custom_m3u8.strip() else default_m3u8_urls[0] | |
| display_name = custom_name.strip() if custom_name.strip() else default_names[0] | |
| st.session_state.streams[0]["current_m3u8_url"] = url | |
| st.session_state.streams[0]["display_name"] = display_name | |
| confidence = st.session_state.confidence | |
| target_fps = st.session_state.target_fps | |
| # --- Load Model --- | |
| model_path = 'https://huggingface.co/spaces/tstone87/ccr-colorado/resolve/main/best.pt' | |
| def load_model(): | |
| return YOLO(model_path) | |
| try: | |
| model = load_model() | |
| except Exception as ex: | |
| st.error(f"Model loading failed: {str(ex)}") | |
| st.stop() | |
| # --- Create Placeholders for Streams in a 2-Column Grid --- | |
| num_streams = st.session_state.num_streams | |
| feed_placeholders = [] | |
| stats_placeholders = [] | |
| cols = st.columns(2) | |
| for i in range(num_streams): | |
| col_index = i % 2 | |
| if i >= 2 and col_index == 0: | |
| cols = st.columns(2) | |
| feed_placeholders.append(cols[col_index].empty()) | |
| stats_placeholders.append(cols[col_index].empty()) | |
| if num_streams == 1: | |
| _ = st.columns(2) | |
| def update_stream(i): | |
| current_time = time.time() | |
| sleep_time = 1.0 / target_fps | |
| stream_state = st.session_state.streams[i] | |
| if current_time - stream_state["last_processed_time"] >= sleep_time: | |
| url = stream_state["current_m3u8_url"] | |
| cap = cv2.VideoCapture(url) | |
| if not cap.isOpened(): | |
| stats_placeholders[i].text("Failed to open M3U8 stream.") | |
| return | |
| ret, frame = cap.read() | |
| cap.release() | |
| if not ret: | |
| stats_placeholders[i].text("Stream interrupted or ended.") | |
| return | |
| res = model.predict(frame, conf=confidence) | |
| processed_frame = res[0].plot()[:, :, ::-1] | |
| # Extract detection results. | |
| tensor_info = "No detections" | |
| max_conf = 0.0 | |
| try: | |
| boxes = res[0].boxes | |
| if boxes is not None and len(boxes) > 0: | |
| max_conf = float(boxes.conf.max()) | |
| tensor_info = f"Detections: {len(boxes)} | Max Confidence: {max_conf:.2f}" | |
| except Exception as ex: | |
| tensor_info = f"Error extracting detections: {ex}" | |
| # Only update if new detection's confidence is >= current highest. | |
| if max_conf >= stream_state["highest_match"]: | |
| stream_state["highest_match"] = max_conf | |
| stream_state["detected_frames"].append(processed_frame) | |
| stream_state["processed_count"] += 1 | |
| stream_state["last_processed_time"] = current_time | |
| mt_time = datetime.now(pytz.timezone('America/Denver')).strftime('%Y-%m-%d %H:%M:%S MT') | |
| stream_state["processed_frame"] = processed_frame | |
| stream_state["stats_text"] = ( | |
| f"Processing FPS: {stream_state['processed_count'] / (current_time - stream_state['start_time']):.2f}\n" | |
| f"{tensor_info}\n" | |
| f"Highest Match: {stream_state['highest_match']:.2f}" | |
| ) | |
| feed_placeholders[i].image( | |
| processed_frame, | |
| caption=f"Stream {i+1} - {stream_state['display_name']} - {mt_time}", | |
| use_container_width=True | |
| ) | |
| stats_placeholders[i].text(stream_state["stats_text"]) | |
| # --- Continuous Processing Loop --- | |
| while True: | |
| for i in range(num_streams): | |
| update_stream(i) | |
| # time.sleep(1.0 / target_fps) | |