File size: 7,071 Bytes
a4af8e6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e8a0692
 
 
 
 
 
a4af8e6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64baacd
 
 
 
 
 
 
 
a4af8e6
 
 
 
64baacd
 
 
 
 
 
 
a4af8e6
 
 
 
 
 
 
 
 
 
64baacd
a4af8e6
 
72cf232
 
 
 
 
 
 
 
 
 
 
 
 
64baacd
 
 
 
 
 
 
 
 
 
 
72cf232
 
64baacd
 
 
 
72cf232
a4af8e6
72cf232
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a4af8e6
 
b36b7cd
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
# app_gradio.py
import gradio as gr
import numpy as np
import os
import yaml
from dotenv import load_dotenv
import io
from scipy.io.wavfile import read as read_wav

# Correctly import from the drive_paddy package structure
from src.detection.factory import get_detector
from src.alerting.alert_system import get_alerter

# --- Load Configuration and Environment Variables ---
# This part is the same as our Streamlit app
load_dotenv()
config_path = 'config.yaml'
with open(config_path, 'r') as f:
    config = yaml.safe_load(f)
secrets = {
    "gemini_api_key": os.getenv("GEMINI_API_KEY"),
}

# --- Initialize Backend Components ---
# We create these once and reuse them.
detector = get_detector(config)
alerter = get_alerter(config, secrets["gemini_api_key"])

geo_settings = config.get('geometric_settings', {})
drowsiness_levels = geo_settings.get('drowsiness_levels', {})
SLIGHTLY_DROWSY_DEFAULT = drowsiness_levels.get('slightly_drowsy_threshold', 0.3)
VERY_DROWSY_DEFAULT = drowsiness_levels.get('very_drowsy_threshold', 0.8)


# --- Audio Processing for Gradio ---
# Gradio's gr.Audio component needs a specific format: (sample_rate, numpy_array)
def process_audio_for_gradio(audio_bytes):
    """Converts in-memory audio bytes to a format Gradio can play."""
    # gTTS creates MP3, so we read it as such
    byte_io = io.BytesIO(audio_bytes)
    # The 'read' function from scipy.io.wavfile expects a WAV file.
    # We need to first convert the MP3 bytes from gTTS to WAV bytes.
    # This requires pydub.
    try:
        from pydub import AudioSegment
        audio = AudioSegment.from_mp3(byte_io)
        wav_byte_io = io.BytesIO()
        audio.export(wav_byte_io, format="wav")
        wav_byte_io.seek(0)
        
        sample_rate, data = read_wav(wav_byte_io)
        return (sample_rate, data)
    except Exception as e:
        print(f"Could not process audio for Gradio: {e}")
        return None

# --- Main Processing Function for Gradio ---
# This function is the core of the app. It takes a webcam frame and returns
# updates for all the output components.
def process_live_frame(frame):
    """
    Takes a single frame from the Gradio webcam input, processes it,
    and returns the processed frame, status text, and any audio alerts.
    """
    if frame is None:
        # Return default values if frame is None
        blank_image = np.zeros((480, 640, 3), dtype=np.uint8)
        return blank_image, "Status: Inactive", None

    # Process the frame using our existing detector
    processed_frame, indicators, _ = detector.process_frame(frame)
    drowsiness_level = indicators.get("drowsiness_level", "Awake")
    lighting = indicators.get("lighting", "Good")
    score = indicators.get("details", {}).get("Score", 0)

    # Build the status text
    # Determine drowsiness level based on the UI slider's value
    drowsiness_level = "Awake"
    if score >= VERY_DROWSY_DEFAULT:  # Use a fixed upper threshold
        drowsiness_level = "Very Drowsy"
    elif score >= sensitivity_threshold: # Use the slider for slight drowsiness
        drowsiness_level = "Slightly Drowsy"

    # Build the status text with explicit details
    status_text = f"Lighting: {lighting}\n"
    if lighting == "Low":
        status_text += "Detection paused due to low light."
    else:
        status_text += f"Status: {drowsiness_level}\nScore: {score:.2f} (Threshold: {sensitivity_threshold:.2f})"
        # Explicitly show what is being detected
        if score > 0:
            if indicators.get('eye_closure'): status_text += "\n- Eyes Closed Detected"
            if indicators.get('yawning'): status_text += "\n- Yawn Detected"
            if indicators.get('head_nod'): status_text += "\n- Head Nod Detected"
            if indicators.get('looking_away'): status_text += "\n- Looking Away Detected"

    # Handle alerts
    audio_output = None
    if drowsiness_level != "Awake":
        audio_data = alerter.trigger_alert(level=drowsiness_level)
        if audio_data:
            audio_output = process_audio_for_gradio(audio_data)
    else:
        alerter.reset_alert()

    # Return all the values needed to update the UI
    return processed_frame, status_text, audio_output

# --- UI Definition for the Live Detection Page ---
def create_live_detection_page():
    """Builds the Gradio UI components for the live detection tab."""
    with gr.Blocks(theme=gr.themes.Default(primary_hue="blue", secondary_hue="blue")) as live_detection_page:
        gr.Markdown("A live test using Gradio's webcam component.")
        with gr.Row():
            with gr.Column():
                webcam_input = gr.Image(sources=["webcam"], streaming=True, label="Live Camera Feed")
            with gr.Column():
                processed_output = gr.Image(label="Processed Feed")
                status_output = gr.Textbox(label="Live Status", lines=3, interactive=False)
                # Audio player is now visible for debugging and user feedback.
                audio_alert_output = gr.Audio(autoplay=True, visible=True, label="Alert Sound")
    # --- Added Sensitivity Slider ---
                sensitivity_slider = gr.Slider(
                    minimum=0.1, 
                    maximum=1.0, 
                    value=SLIGHTLY_DROWSY_DEFAULT, 
                    step=0.05, 
                    label="Alert Sensitivity Threshold",
                    info="Lower value = more sensitive to drowsiness signs."
                )
    
        # Link the inputs (webcam and slider) to the processing function and its outputs
        webcam_input.stream(
            fn=process_live_frame,
            inputs=[webcam_input, sensitivity_slider],
            outputs=[processed_output, status_output, audio_alert_output],
            every=0.1
    )
    return live_detection_page

# --- UI Definition for the Home Page ---
def create_home_page():
    """Builds the Gradio UI components for the home/welcome tab."""
    with gr.Blocks(theme=gr.themes.Default(primary_hue="blue", secondary_hue="blue")) as home_page:
        gr.Markdown(
        """
        <div align="center">
          <img src="https://em-content.zobj.net/source/samsung/380/automobile_1f697.png" alt="Car Emoji" width="100"/>
          <h1>Welcome to Drive Paddy!</h1>
          <p><strong>Your Drowsiness Detection Assistant</strong></p>
        </div>
        
        ---
        
        ### How It Works
        This application uses your webcam to monitor for signs of drowsiness in real-time. Navigate to the **Live Detection** tab to begin.
        
        - **Multi-Signal Analysis**: Detects eye closure, yawning, and head position.
        - **AI-Powered Alerts**: Uses Gemini to generate dynamic audio warnings.
        - **Live Feedback**: Provides instant visual feedback on the video stream and status panel.
        """
        )
    return home_page

# --- Combine Pages into a Tabbed Interface ---
app = gr.TabbedInterface(
    [create_home_page(), create_live_detection_page()],
    ["Home", "Live Detection"]
)

# --- Launch the App ---
app.launch(debug=True)