File size: 7,813 Bytes
6f7ea06
 
 
47a18cf
 
2da6541
3f362e3
6f7ea06
c106e18
6f7ea06
09f6643
6f7ea06
 
 
 
85cd9f1
6f7ea06
0f9c3be
214b52c
 
 
 
 
 
 
d35d2b8
47a18cf
c106e18
 
 
 
 
 
 
 
 
 
7b0fee6
 
c106e18
 
 
 
 
 
 
 
 
 
7b0fee6
 
c106e18
 
d35d2b8
a07b6f9
8ed85df
2fdb451
8ed85df
 
 
2fdb451
c106e18
214b52c
8ed85df
 
a07b6f9
214b52c
0e01890
 
 
 
 
 
 
 
214b52c
a07b6f9
 
2fdb451
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73a0552
2fdb451
 
 
 
 
 
 
 
214b52c
 
 
f5132fc
214b52c
9ffcd9a
 
 
d1fec8d
9ffcd9a
d1fec8d
 
f5132fc
d9ff035
c106e18
 
214b52c
 
c106e18
 
 
 
 
 
 
 
 
dc34de2
d35d2b8
214b52c
 
c106e18
 
 
214b52c
0f2c91d
214b52c
 
0f2c91d
4d48030
0f2c91d
214b52c
 
a07b6f9
6509962
d35d2b8
214b52c
 
 
 
 
 
 
 
 
73a0552
c106e18
 
 
 
 
214b52c
c106e18
 
 
214b52c
c106e18
214b52c
2fdb451
 
 
 
 
c106e18
0f2c91d
214b52c
6509962
c106e18
d35d2b8
73a0552
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
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'
@st.cache_resource
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)