Spaces:
Sleeping
Sleeping
Update pages/1_Live_Detection.py
Browse files- pages/1_Live_Detection.py +64 -94
pages/1_Live_Detection.py
CHANGED
|
@@ -6,45 +6,31 @@ import av
|
|
| 6 |
import os
|
| 7 |
from dotenv import load_dotenv
|
| 8 |
import base64
|
| 9 |
-
import requests
|
| 10 |
import queue
|
| 11 |
import time
|
| 12 |
-
from typing import List, Dict, Union
|
| 13 |
|
| 14 |
-
# Correctly import from the drive_paddy package structure
|
| 15 |
from src.detection.factory import get_detector
|
| 16 |
from src.alerting.alert_system import get_alerter
|
| 17 |
|
| 18 |
-
# --- Initialize Session State at the TOP of the script ---
|
| 19 |
-
# This is the single source of truth for our queues and must run on every page load.
|
| 20 |
-
if "status_queue" not in st.session_state:
|
| 21 |
-
st.session_state.status_queue = queue.Queue()
|
| 22 |
-
if "audio_queue" not in st.session_state:
|
| 23 |
-
st.session_state.audio_queue = queue.Queue()
|
| 24 |
-
if "last_status" not in st.session_state:
|
| 25 |
-
st.session_state.last_status = {"drowsiness_level": "Awake", "lighting": "Good"}
|
| 26 |
-
|
| 27 |
-
|
| 28 |
# --- Load Configuration and Environment Variables ---
|
| 29 |
@st.cache_resource
|
| 30 |
def load_app_config():
|
| 31 |
"""Loads config from yaml and .env files."""
|
| 32 |
load_dotenv()
|
|
|
|
| 33 |
# Navigate up to the root to find the config file
|
| 34 |
-
config_path = "
|
| 35 |
with open(config_path, 'r') as f:
|
| 36 |
config = yaml.safe_load(f)
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
config, secrets = load_app_config()
|
| 48 |
|
| 49 |
# --- Client-Side Audio Playback Function ---
|
| 50 |
def autoplay_audio(audio_bytes: bytes):
|
|
@@ -59,83 +45,62 @@ def autoplay_audio(audio_bytes: bytes):
|
|
| 59 |
|
| 60 |
# --- WebRTC Video Processor ---
|
| 61 |
class VideoProcessor(VideoProcessorBase):
|
| 62 |
-
# The __init__ method now accepts the queues as arguments
|
| 63 |
def __init__(self):
|
| 64 |
-
# It uses the queues passed in from session_state, not new ones.
|
| 65 |
-
self.status_queue = queue.Queue
|
| 66 |
-
self.audio_queue = queue.Queue
|
| 67 |
self._detector = get_detector(config)
|
| 68 |
-
self._alerter = get_alerter(config,
|
| 69 |
|
| 70 |
def recv(self, frame: av.VideoFrame) -> av.VideoFrame:
|
| 71 |
img = frame.to_ndarray(format="bgr24")
|
| 72 |
|
| 73 |
strategy = config.get('detection_strategy')
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
if
|
| 83 |
-
audio_data = self._alerter.trigger_alert(
|
| 84 |
if audio_data:
|
| 85 |
-
|
| 86 |
-
self.audio_queue.put(audio_data)
|
| 87 |
else:
|
| 88 |
self._alerter.reset_alert()
|
| 89 |
|
| 90 |
return av.VideoFrame.from_ndarray(processed_frame, format="bgr24")
|
| 91 |
|
| 92 |
# --- Page UI ---
|
|
|
|
|
|
|
| 93 |
st.title("📹 Live Drowsiness Detection")
|
| 94 |
st.info("Press 'START' to activate your camera and begin monitoring.")
|
| 95 |
|
|
|
|
|
|
|
| 96 |
RTC_CONFIGURATION = RTCConfiguration({
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
"urls": ["stun:
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
"username": "086986a440b20fe48229738b",
|
| 104 |
-
"credential": "mOC7fVSg00zjlsTD",
|
| 105 |
-
},
|
| 106 |
-
{
|
| 107 |
-
"urls": ["turn:global.relay.metered.ca:80?transport=tcp"],
|
| 108 |
-
"username": "086986a440b20fe48229738b",
|
| 109 |
-
"credential": "mOC7fVSg00zjlsTD",
|
| 110 |
-
},
|
| 111 |
-
{
|
| 112 |
-
"urls": ["turn:global.relay.metered.ca:443"],
|
| 113 |
-
"username": "086986a440b20fe48229738b",
|
| 114 |
-
"credential": "mOC7fVSg00zjlsTD",
|
| 115 |
-
},
|
| 116 |
-
{
|
| 117 |
-
"urls": ["turns:global.relay.metered.ca:443?transport=tcp"],
|
| 118 |
-
"username": "086986a440b20fe48229738b",
|
| 119 |
-
"credential": "mOC7fVSg00zjlsTD",
|
| 120 |
-
},
|
| 121 |
-
]
|
| 122 |
})
|
| 123 |
|
|
|
|
| 124 |
col1, col2 = st.columns([3, 1])
|
| 125 |
|
| 126 |
with col1:
|
| 127 |
webrtc_ctx = webrtc_streamer(
|
| 128 |
key="drowsiness-detection",
|
| 129 |
-
# The factory now correctly passes the queues from session_state
|
| 130 |
video_processor_factory=VideoProcessor,
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
)
|
| 135 |
|
| 136 |
with col2:
|
| 137 |
st.header("System Status")
|
| 138 |
-
audio_placeholder = st.empty()
|
| 139 |
if not webrtc_ctx.state.playing:
|
| 140 |
st.warning("System Inactive.")
|
| 141 |
else:
|
|
@@ -143,41 +108,46 @@ with col2:
|
|
| 143 |
|
| 144 |
st.subheader("Live Status:")
|
| 145 |
status_placeholder = st.empty()
|
|
|
|
| 146 |
|
| 147 |
if webrtc_ctx.state.playing:
|
|
|
|
| 148 |
try:
|
| 149 |
-
# This now reads from the correct queue that the processor is writing to.
|
| 150 |
status_result = st.session_state.status_queue.get(timeout=0.1)
|
| 151 |
-
st.session_state.last_status = status_result
|
| 152 |
except queue.Empty:
|
| 153 |
-
|
| 154 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 155 |
with status_placeholder.container():
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
score = last_status.get("details", {}).get("Score", 0)
|
| 160 |
-
|
| 161 |
-
st.metric(label="Lighting Condition", value=lighting)
|
| 162 |
-
if lighting == "Low":
|
| 163 |
-
st.warning("Detection paused due to low light.")
|
| 164 |
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
elif
|
| 170 |
-
st.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 171 |
|
| 172 |
-
|
| 173 |
-
audio_data = st.session_state.audio_queue.get(timeout=0.1)
|
| 174 |
with audio_placeholder.container():
|
| 175 |
autoplay_audio(audio_data)
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
time.sleep(0.1)
|
| 180 |
st.rerun()
|
|
|
|
| 181 |
else:
|
| 182 |
with status_placeholder.container():
|
| 183 |
st.info("✔️ Driver is Awake")
|
|
|
|
| 6 |
import os
|
| 7 |
from dotenv import load_dotenv
|
| 8 |
import base64
|
|
|
|
| 9 |
import queue
|
| 10 |
import time
|
|
|
|
| 11 |
|
|
|
|
| 12 |
from src.detection.factory import get_detector
|
| 13 |
from src.alerting.alert_system import get_alerter
|
| 14 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
# --- Load Configuration and Environment Variables ---
|
| 16 |
@st.cache_resource
|
| 17 |
def load_app_config():
|
| 18 |
"""Loads config from yaml and .env files."""
|
| 19 |
load_dotenv()
|
| 20 |
+
gemini_api_key = os.getenv("GEMINI_API_KEY")
|
| 21 |
# Navigate up to the root to find the config file
|
| 22 |
+
config_path = "/config.yaml" if os.path.exists("/config.yaml") else "config.yaml"
|
| 23 |
with open(config_path, 'r') as f:
|
| 24 |
config = yaml.safe_load(f)
|
| 25 |
+
return config, gemini_api_key
|
| 26 |
+
|
| 27 |
+
config, gemini_api_key = load_app_config()
|
| 28 |
+
|
| 29 |
+
# --- Initialize Session State (if not already done in main.py) ---
|
| 30 |
+
if "play_audio" not in st.session_state:
|
| 31 |
+
st.session_state.play_audio = None
|
| 32 |
+
if "active_alerts" not in st.session_state:
|
| 33 |
+
st.session_state.active_alerts = {"status": "Awake"}
|
|
|
|
|
|
|
| 34 |
|
| 35 |
# --- Client-Side Audio Playback Function ---
|
| 36 |
def autoplay_audio(audio_bytes: bytes):
|
|
|
|
| 45 |
|
| 46 |
# --- WebRTC Video Processor ---
|
| 47 |
class VideoProcessor(VideoProcessorBase):
|
|
|
|
| 48 |
def __init__(self):
|
|
|
|
|
|
|
|
|
|
| 49 |
self._detector = get_detector(config)
|
| 50 |
+
self._alerter = get_alerter(config, gemini_api_key)
|
| 51 |
|
| 52 |
def recv(self, frame: av.VideoFrame) -> av.VideoFrame:
|
| 53 |
img = frame.to_ndarray(format="bgr24")
|
| 54 |
|
| 55 |
strategy = config.get('detection_strategy')
|
| 56 |
+
if strategy == 'hybrid':
|
| 57 |
+
processed_frame, alert_triggered, active_alerts = self._detector.process_frame(img)
|
| 58 |
+
st.session_state.active_alerts = active_alerts if alert_triggered else {"status": "Awake"}
|
| 59 |
+
else: # Fallback for simpler strategies
|
| 60 |
+
processed_frame, indicators = self._detector.process_frame(img)
|
| 61 |
+
alert_triggered = any(indicators.values())
|
| 62 |
+
st.session_state.active_alerts = indicators if alert_triggered else {"status": "Awake"}
|
| 63 |
+
|
| 64 |
+
if alert_triggered:
|
| 65 |
+
audio_data = self._alerter.trigger_alert()
|
| 66 |
if audio_data:
|
| 67 |
+
st.session_state.play_audio = audio_data
|
|
|
|
| 68 |
else:
|
| 69 |
self._alerter.reset_alert()
|
| 70 |
|
| 71 |
return av.VideoFrame.from_ndarray(processed_frame, format="bgr24")
|
| 72 |
|
| 73 |
# --- Page UI ---
|
| 74 |
+
# The st.set_page_config() call has been removed from this file.
|
| 75 |
+
# The configuration from main.py will apply to this page.
|
| 76 |
st.title("📹 Live Drowsiness Detection")
|
| 77 |
st.info("Press 'START' to activate your camera and begin monitoring.")
|
| 78 |
|
| 79 |
+
# --- Robust RTC Configuration ---
|
| 80 |
+
# Provide a list of STUN servers for better reliability.
|
| 81 |
RTC_CONFIGURATION = RTCConfiguration({
|
| 82 |
+
"iceServers": [
|
| 83 |
+
{"urls": ["stun:stun.l.google.com:19302"]},
|
| 84 |
+
{"urls": ["stun:stun1.l.google.com:19302"]},
|
| 85 |
+
{"urls": ["stun:stun2.l.google.com:19302"]},
|
| 86 |
+
{"urls": ["stun:stun.services.mozilla.com:3478"]},
|
| 87 |
+
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
})
|
| 89 |
|
| 90 |
+
|
| 91 |
col1, col2 = st.columns([3, 1])
|
| 92 |
|
| 93 |
with col1:
|
| 94 |
webrtc_ctx = webrtc_streamer(
|
| 95 |
key="drowsiness-detection",
|
|
|
|
| 96 |
video_processor_factory=VideoProcessor,
|
| 97 |
+
rtc_configuration=RTC_CONFIGURATION, # Use the new robust configuration
|
| 98 |
+
media_stream_constraints={"video": True, "audio": False},
|
| 99 |
+
async_processing=True,
|
| 100 |
)
|
| 101 |
|
| 102 |
with col2:
|
| 103 |
st.header("System Status")
|
|
|
|
| 104 |
if not webrtc_ctx.state.playing:
|
| 105 |
st.warning("System Inactive.")
|
| 106 |
else:
|
|
|
|
| 108 |
|
| 109 |
st.subheader("Live Status:")
|
| 110 |
status_placeholder = st.empty()
|
| 111 |
+
audio_placeholder = st.empty()
|
| 112 |
|
| 113 |
if webrtc_ctx.state.playing:
|
| 114 |
+
# --- Polling Loop ---
|
| 115 |
try:
|
|
|
|
| 116 |
status_result = st.session_state.status_queue.get(timeout=0.1)
|
|
|
|
| 117 |
except queue.Empty:
|
| 118 |
+
status_result = None
|
| 119 |
|
| 120 |
+
# Check for new audio alerts
|
| 121 |
+
try:
|
| 122 |
+
audio_data = st.session_state.audio_queue.get(timeout=0.1)
|
| 123 |
+
except queue.Empty:
|
| 124 |
+
audio_data = None
|
| 125 |
+
|
| 126 |
with status_placeholder.container():
|
| 127 |
+
# Persist the last known status if there's no new one
|
| 128 |
+
if status_result:
|
| 129 |
+
st.session_state.last_status = status_result
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 130 |
|
| 131 |
+
last_status = getattr(st.session_state, 'last_status', {"status": "Awake"})
|
| 132 |
+
|
| 133 |
+
if last_status.get("Low Light"):
|
| 134 |
+
st.warning("⚠️ Low Light Detected! Accuracy may be affected.")
|
| 135 |
+
elif last_status.get("status") == "Awake":
|
| 136 |
+
st.info("✔️ Driver is Awake")
|
| 137 |
+
else:
|
| 138 |
+
st.error("🚨 DROWSINESS DETECTED!")
|
| 139 |
+
for key, value in last_status.items():
|
| 140 |
+
if key != "Low Light":
|
| 141 |
+
st.warning(f"-> {key}: {value:.2f}" if isinstance(value, float) else f"-> {key}")
|
| 142 |
|
| 143 |
+
if audio_data:
|
|
|
|
| 144 |
with audio_placeholder.container():
|
| 145 |
autoplay_audio(audio_data)
|
| 146 |
+
|
| 147 |
+
# Force a rerun to keep the polling active
|
|
|
|
| 148 |
time.sleep(0.1)
|
| 149 |
st.rerun()
|
| 150 |
+
|
| 151 |
else:
|
| 152 |
with status_placeholder.container():
|
| 153 |
st.info("✔️ Driver is Awake")
|