Testys commited on
Commit
deefd7f
Β·
verified Β·
1 Parent(s): 2d02b61

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +91 -123
app.py CHANGED
@@ -1,4 +1,4 @@
1
- # app_gradio.py
2
  import gradio as gr
3
  import numpy as np
4
  import os
@@ -6,13 +6,16 @@ import yaml
6
  from dotenv import load_dotenv
7
  import io
8
  from scipy.io.wavfile import read as read_wav
 
 
 
 
9
 
10
  # Correctly import from the drive_paddy package structure
11
- from src.detection.factory import get_detector
12
- from src.alerting.alert_system import get_alerter
13
 
14
  # --- Load Configuration and Environment Variables ---
15
- # This part is the same as our Streamlit app
16
  load_dotenv()
17
  config_path = 'config.yaml'
18
  with open(config_path, 'r') as f:
@@ -22,151 +25,116 @@ secrets = {
22
  }
23
 
24
  # --- Initialize Backend Components ---
25
- # We create these once and reuse them.
26
  detector = get_detector(config)
27
  alerter = get_alerter(config, secrets["gemini_api_key"])
28
-
29
  geo_settings = config.get('geometric_settings', {})
30
  drowsiness_levels = geo_settings.get('drowsiness_levels', {})
31
  SLIGHTLY_DROWSY_DEFAULT = drowsiness_levels.get('slightly_drowsy_threshold', 0.3)
32
  VERY_DROWSY_DEFAULT = drowsiness_levels.get('very_drowsy_threshold', 0.8)
33
 
 
 
 
 
34
 
35
- # --- Audio Processing for Gradio ---
36
- # Gradio's gr.Audio component needs a specific format: (sample_rate, numpy_array)
37
- def process_audio_for_gradio(audio_bytes):
38
- """Converts in-memory audio bytes to a format Gradio can play."""
39
- # gTTS creates MP3, so we read it as such
40
- byte_io = io.BytesIO(audio_bytes)
41
- # The 'read' function from scipy.io.wavfile expects a WAV file.
42
- # We need to first convert the MP3 bytes from gTTS to WAV bytes.
43
- # This requires pydub.
44
- try:
45
- from pydub import AudioSegment
46
- audio = AudioSegment.from_mp3(byte_io)
47
- wav_byte_io = io.BytesIO()
48
- audio.export(wav_byte_io, format="wav")
49
- wav_byte_io.seek(0)
50
-
51
- sample_rate, data = read_wav(wav_byte_io)
52
- return (sample_rate, data)
53
- except Exception as e:
54
- print(f"Could not process audio for Gradio: {e}")
55
- return None
56
-
57
- # --- Main Processing Function for Gradio ---
58
- # This function is the core of the app. It takes a webcam frame and returns
59
- # updates for all the output components.
60
- def process_live_frame(frame):
61
  """
62
- Takes a single frame from the Gradio webcam input, processes it,
63
- and returns the processed frame, status text, and any audio alerts.
64
  """
 
 
65
  if frame is None:
66
- # Return default values if frame is None
67
- blank_image = np.zeros((480, 640, 3), dtype=np.uint8)
68
- return blank_image, "Status: Inactive", None
69
 
70
- # Process the frame using our existing detector
 
71
  processed_frame, indicators, _ = detector.process_frame(frame)
72
  drowsiness_level = indicators.get("drowsiness_level", "Awake")
73
- lighting = indicators.get("lighting", "Good")
74
- score = indicators.get("details", {}).get("Score", 0)
75
-
76
- # Build the status text
77
- # Determine drowsiness level based on the UI slider's value
78
- drowsiness_level = "Awake"
79
- if score >= VERY_DROWSY_DEFAULT: # Use a fixed upper threshold
80
- drowsiness_level = "Very Drowsy"
81
- elif score >= sensitivity_threshold: # Use the slider for slight drowsiness
82
- drowsiness_level = "Slightly Drowsy"
83
-
84
- # Build the status text with explicit details
85
- status_text = f"Lighting: {lighting}\n"
86
- if lighting == "Low":
87
- status_text += "Detection paused due to low light."
88
- else:
89
- status_text += f"Status: {drowsiness_level}\nScore: {score:.2f} (Threshold: {sensitivity_threshold:.2f})"
90
- # Explicitly show what is being detected
91
- if score > 0:
92
- if indicators.get('eye_closure'): status_text += "\n- Eyes Closed Detected"
93
- if indicators.get('yawning'): status_text += "\n- Yawn Detected"
94
- if indicators.get('head_nod'): status_text += "\n- Head Nod Detected"
95
- if indicators.get('looking_away'): status_text += "\n- Looking Away Detected"
96
-
97
- # Handle alerts
98
- audio_output = None
99
  if drowsiness_level != "Awake":
100
  audio_data = alerter.trigger_alert(level=drowsiness_level)
101
  if audio_data:
102
- audio_output = process_audio_for_gradio(audio_data)
 
 
 
 
 
 
 
 
 
 
 
103
  else:
104
  alerter.reset_alert()
105
 
106
- # Return all the values needed to update the UI
107
- return processed_frame, status_text, audio_output
108
-
109
- # --- UI Definition for the Live Detection Page ---
110
- def create_live_detection_page():
111
- """Builds the Gradio UI components for the live detection tab."""
112
- with gr.Blocks(theme=gr.themes.Default(primary_hue="blue", secondary_hue="blue")) as live_detection_page:
113
- gr.Markdown("A live test using Gradio's webcam component.")
114
- with gr.Row():
115
- with gr.Column():
116
- webcam_input = gr.Image(sources=["webcam"], streaming=True, label="Live Camera Feed")
117
- with gr.Column():
118
- processed_output = gr.Image(label="Processed Feed")
119
- status_output = gr.Textbox(label="Live Status", lines=3, interactive=False)
120
- # Audio player is now visible for debugging and user feedback.
121
- audio_alert_output = gr.Audio(autoplay=True, visible=True, label="Alert Sound")
122
- # --- Added Sensitivity Slider ---
123
- sensitivity_slider = gr.Slider(
124
- minimum=0.1,
125
- maximum=1.0,
126
- value=SLIGHTLY_DROWSY_DEFAULT,
127
- step=0.05,
128
- label="Alert Sensitivity Threshold",
129
- info="Lower value = more sensitive to drowsiness signs."
130
- )
131
-
132
- # Link the inputs (webcam and slider) to the processing function and its outputs
133
- webcam_input.stream(
134
- fn=process_live_frame,
135
- inputs=[webcam_input, sensitivity_slider],
136
- outputs=[processed_output, status_output, audio_alert_output],
137
- every=0.1
138
- )
139
- return live_detection_page
140
 
141
- # --- UI Definition for the Home Page ---
142
- def create_home_page():
143
- """Builds the Gradio UI components for the home/welcome tab."""
144
- with gr.Blocks(theme=gr.themes.Default(primary_hue="blue", secondary_hue="blue")) as home_page:
145
- gr.Markdown(
 
 
 
 
 
 
 
 
 
 
146
  """
147
  <div align="center">
148
- <img src="https://em-content.zobj.net/source/samsung/380/automobile_1f697.png" alt="Car Emoji" width="100"/>
149
- <h1>Welcome to Drive Paddy!</h1>
150
- <p><strong>Your Drowsiness Detection Assistant</strong></p>
151
  </div>
152
-
153
- ---
154
-
155
- ### How It Works
156
- This application uses your webcam to monitor for signs of drowsiness in real-time. Navigate to the **Live Detection** tab to begin.
157
-
158
- - **Multi-Signal Analysis**: Detects eye closure, yawning, and head position.
159
- - **AI-Powered Alerts**: Uses Gemini to generate dynamic audio warnings.
160
- - **Live Feedback**: Provides instant visual feedback on the video stream and status panel.
161
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
  )
163
- return home_page
164
 
165
- # --- Combine Pages into a Tabbed Interface ---
166
- app = gr.TabbedInterface(
167
- [create_home_page(), create_live_detection_page()],
168
- ["Home", "Live Detection"]
169
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170
 
171
  # --- Launch the App ---
172
- app.launch(debug=True)
 
 
1
+ # app_webrtc.py
2
  import gradio as gr
3
  import numpy as np
4
  import os
 
6
  from dotenv import load_dotenv
7
  import io
8
  from scipy.io.wavfile import read as read_wav
9
+ from pydub import AudioSegment
10
+ import cv2
11
+ import time
12
+ from gradio_webrtc import WebRTC
13
 
14
  # Correctly import from the drive_paddy package structure
15
+ from drive_paddy.detection.factory import get_detector
16
+ from drive_paddy.alerting.alert_system import get_alerter
17
 
18
  # --- Load Configuration and Environment Variables ---
 
19
  load_dotenv()
20
  config_path = 'config.yaml'
21
  with open(config_path, 'r') as f:
 
25
  }
26
 
27
  # --- Initialize Backend Components ---
 
28
  detector = get_detector(config)
29
  alerter = get_alerter(config, secrets["gemini_api_key"])
 
30
  geo_settings = config.get('geometric_settings', {})
31
  drowsiness_levels = geo_settings.get('drowsiness_levels', {})
32
  SLIGHTLY_DROWSY_DEFAULT = drowsiness_levels.get('slightly_drowsy_threshold', 0.3)
33
  VERY_DROWSY_DEFAULT = drowsiness_levels.get('very_drowsy_threshold', 0.8)
34
 
35
+ # --- Global state for audio (simpler than queues for this component) ---
36
+ # We use a global variable to hold the audio data, which the UI will poll.
37
+ # This is a common pattern in simple Gradio streaming apps.
38
+ latest_audio_alert = None
39
 
40
+ # --- Main Processing Function ---
41
+ def process_stream(frame: np.ndarray, sensitivity_threshold: float) -> np.ndarray:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  """
43
+ This is the core function. It takes a frame and returns the processed frame.
44
+ All logic, including status drawing and alert triggering, happens here.
45
  """
46
+ global latest_audio_alert
47
+
48
  if frame is None:
49
+ return np.zeros((480, 640, 3), dtype=np.uint8)
 
 
50
 
51
+ # Process the frame using our existing detector.
52
+ # The detector already draws landmarks and status overlays.
53
  processed_frame, indicators, _ = detector.process_frame(frame)
54
  drowsiness_level = indicators.get("drowsiness_level", "Awake")
55
+
56
+ # Handle audio alerts
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  if drowsiness_level != "Awake":
58
  audio_data = alerter.trigger_alert(level=drowsiness_level)
59
  if audio_data:
60
+ # Convert audio for Gradio and store it in the global variable
61
+ try:
62
+ byte_io = io.BytesIO(audio_data)
63
+ audio = AudioSegment.from_mp3(byte_io)
64
+ wav_byte_io = io.BytesIO()
65
+ audio.export(wav_byte_io, format="wav")
66
+ wav_byte_io.seek(0)
67
+ sample_rate, data = read_wav(wav_byte_io)
68
+ latest_audio_alert = (sample_rate, data)
69
+ except Exception as e:
70
+ print(f"Audio processing error: {e}")
71
+ latest_audio_alert = None
72
  else:
73
  alerter.reset_alert()
74
 
75
+ return processed_frame
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
 
77
+ # --- Function to check for and return audio alerts ---
78
+ def get_audio_update():
79
+ """
80
+ This function is polled by the UI to check for new audio alerts.
81
+ """
82
+ global latest_audio_alert
83
+ if latest_audio_alert:
84
+ audio_to_play = latest_audio_alert
85
+ latest_audio_alert = None # Clear the alert after sending it
86
+ return audio_to_play
87
+ return None
88
+
89
+ # --- Gradio UI Definition ---
90
+ with gr.Blocks(theme=gr.themes.Default(primary_hue="blue", secondary_hue="blue")) as app:
91
+ gr.HTML(
92
  """
93
  <div align="center">
94
+ <img src="https://em-content.zobj.net/source/samsung/380/automobile_1f697.png" alt="Car Emoji" width="100"/>
95
+ <h1>Drive Paddyn</h1>
 
96
  </div>
 
 
 
 
 
 
 
 
 
97
  """
98
+ )
99
+
100
+ with gr.Row():
101
+ # The WebRTC component now directly shows the processed output
102
+ webrtc_output = WebRTC(
103
+ label="Live Detection Feed",
104
+ video_source="webcam",
105
+ )
106
+
107
+ with gr.Row():
108
+ sensitivity_slider = gr.Slider(
109
+ minimum=0.1,
110
+ maximum=1.0,
111
+ value=SLIGHTLY_DROWSY_DEFAULT,
112
+ step=0.05,
113
+ label="Alert Sensitivity Threshold",
114
+ info="Lower value = more sensitive to drowsiness signs."
115
  )
 
116
 
117
+ # Hidden audio component for playing alerts
118
+ audio_player = gr.Audio(autoplay=True, visible=False)
119
+
120
+ # Connect the WebRTC stream to the processing function
121
+ webrtc_output.stream(
122
+ fn=process_stream,
123
+ inputs=[webrtc_output, sensitivity_slider],
124
+ outputs=[webrtc_output],
125
+ # The 'every' parameter is not needed for this component; it streams as fast as possible.
126
+ )
127
+
128
+ # Use a separate loop to poll for audio updates.
129
+ # This is more stable than returning multiple values in a high-frequency stream.
130
+ app.load(
131
+ fn=get_audio_update,
132
+ inputs=None,
133
+ outputs=[audio_player],
134
+ every=1 # Check for a new audio alert every 1 second
135
+ )
136
+
137
 
138
  # --- Launch the App ---
139
+ if __name__ == "__main__":
140
+ app.launch(debug=True)