abeimam commited on
Commit
f9e4904
·
verified ·
1 Parent(s): f4e6ed5

Upload 12 files

Browse files
.gitattributes ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ cover.png filter=lfs diff=lfs merge=lfs -text
2
+ model/RAFDB_Custom.h5 filter=lfs diff=lfs merge=lfs -text
3
+ static/resources/RTED.png filter=lfs diff=lfs merge=lfs -text
Dockerfile ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use an official lightweight Python image
2
+ FROM python:3.9
3
+
4
+ # Set the working directory
5
+ WORKDIR /app
6
+
7
+ # Install required system libraries (fixes OpenCV OpenGL issue)
8
+ RUN apt-get update && apt-get install -y \
9
+ libgl1-mesa-glx \
10
+ libglib2.0-0 \
11
+ && rm -rf /var/lib/apt/lists/*
12
+
13
+ # Copy required files
14
+ COPY requirements.txt .
15
+ COPY app.py .
16
+ COPY wsgi.py .
17
+ COPY model/ model/
18
+ COPY templates/ templates/
19
+ COPY static/ static/
20
+
21
+ # Install Python dependencies
22
+ RUN pip install --no-cache-dir -r requirements.txt
23
+
24
+ # Set environment variables to avoid TensorFlow warnings
25
+ ENV TF_CPP_MIN_LOG_LEVEL=3
26
+
27
+ # Expose the default Hugging Face Spaces port
28
+ EXPOSE 7860
29
+
30
+ # Start the app using gunicorn with optimizations
31
+ CMD ["gunicorn", "--bind", "0.0.0.0:7860", "--workers=1", "--threads=2", "--timeout=120", "wsgi:application"]
README.md ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: "Real-Time Emotion Detection (RTED)"
3
+ emoji: "😊"
4
+ cover_image: "cover.png"
5
+ sdk: "docker"
6
+ sdk_version: "3.0.0"
7
+ app_file: "app.py"
8
+ python_version: "3.9"
9
+ pinned: false
10
+ ---
11
+
12
+
13
+ # Real-Time Emotion Detection (RTED)
14
+
15
+ This app uses a deep learning model to classify emotions from images and real-time video streams.
16
+
17
+ ### How It Works
18
+ 1. **Choose a Detection Mode:**
19
+ - **Static Detection:** Upload an image.
20
+ - **Real-Time Detection:** (Currently disabled due to Hugging Face webcam restrictions).
21
+
22
+ 2. **Model Predictions:**
23
+ - The model predicts emotions from 7 categories:
24
+ **Angry, Disgust, Fear, Happy, Neutral, Sad, Surprise**
25
+
26
+ 3. **File Structure:**
27
+ - **Model File:** `model/RAFDB_Custom.h5` (Ensure this exists)
28
+ - **Static Files:** Inside `static/`
29
+
30
+ ---
31
+
32
+ ### Running the App Locally
33
+ ```bash
34
+ pip install -r requirements.txt
35
+ python app.py
__pycache__/utils.cpython-311.pyc ADDED
Binary file (851 Bytes). View file
 
app.py ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import base64
2
+ import numpy as np
3
+ import cv2
4
+ from flask import Flask, render_template, request, jsonify
5
+ import tensorflow as tf
6
+ from PIL import Image
7
+ import io
8
+ from mtcnn import MTCNN
9
+ import os
10
+
11
+ app = Flask(__name__, static_folder='static', template_folder='templates')
12
+ app.secret_key = 'shamstabrez'
13
+
14
+ # Load Model
15
+ model_path = os.path.join("model", "RAFDB_Custom.h5")
16
+ if not os.path.exists(model_path):
17
+ raise FileNotFoundError(f"Model file not found: {model_path}")
18
+
19
+ model = tf.keras.models.load_model(model_path)
20
+ class_labels = ['Angry', 'Disgust', 'Fear', 'Happy', 'Neutral', 'Sad', 'Surprise']
21
+ IMG_SIZE = (48, 48)
22
+ detector = MTCNN()
23
+
24
+ streaming = False
25
+
26
+ def detect_and_classify(frame):
27
+ faces = detector.detect_faces(frame)
28
+ detected_faces = []
29
+ if faces:
30
+ for face in faces:
31
+ x, y, w, h = face['box']
32
+ x, y = max(0, x), max(0, y)
33
+ cropped_face = frame[y:y+h, x:x+w]
34
+
35
+ if cropped_face.shape[0] > 0 and cropped_face.shape[1] > 0:
36
+ face_rgb = cv2.resize(cropped_face, IMG_SIZE)
37
+ face_array = tf.keras.preprocessing.image.img_to_array(face_rgb) / 255.0
38
+ face_array = np.expand_dims(face_array, axis=0)
39
+
40
+ predictions = model.predict(face_array)[0]
41
+ top_indices = np.argsort(predictions)[-3:][::-1] # Get top 3 emotions
42
+ top_emotions = [(class_labels[i], round(predictions[i] * 100, 2)) for i in top_indices]
43
+
44
+ cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 1) # Thin border
45
+ for i, (emotion, percentage) in enumerate(top_emotions):
46
+ text = f"{emotion} ({percentage}%)"
47
+ cv2.putText(frame, text, (x, y - (i * 20) - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1)
48
+ detected_faces.extend(top_emotions)
49
+ return frame, detected_faces
50
+
51
+ @app.route('/')
52
+ def index():
53
+ return render_template('index.html', top_emotions=None, img_base64=None, show_upload=True, show_camera=False, initial_image=True)
54
+
55
+ @app.route('/classify', methods=['POST'])
56
+ def classify_image():
57
+ image = request.files['image']
58
+ img = Image.open(image)
59
+ img = np.array(img)
60
+ img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
61
+ processed_frame, top_emotions = detect_and_classify(img)
62
+
63
+ _, buffer = cv2.imencode('.png', processed_frame)
64
+ img_base64 = base64.b64encode(buffer).decode('utf-8')
65
+
66
+ return render_template('index.html', top_emotions=top_emotions, img_base64=img_base64, show_upload=True, show_camera=False, initial_image=False)
67
+
68
+ @app.route('/predict_video', methods=['POST'])
69
+ def predict_video():
70
+ data = request.json
71
+ image_data = data.get("image")
72
+
73
+ if not image_data:
74
+ return jsonify({"error": "No image data received"}), 400
75
+
76
+ image_data = image_data.split(",")[1]
77
+ image_bytes = base64.b64decode(image_data)
78
+ image = Image.open(io.BytesIO(image_bytes))
79
+ frame = np.array(image)
80
+ frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
81
+
82
+ processed_frame, detected_faces = detect_and_classify(frame)
83
+ _, buffer = cv2.imencode(".jpg", processed_frame)
84
+ processed_image_base64 = base64.b64encode(buffer).decode("utf-8")
85
+
86
+ return jsonify({"processed_frame": processed_image_base64, "emotions": detected_faces})
87
+
88
+ if __name__ == '__main__':
89
+ app.run(host='0.0.0.0', port=7860)
cover.png ADDED

Git LFS Details

  • SHA256: b7f08f214843fe03c8fdb10b1f78fe9ca9da19fd288a9accf7a82f01ff0c564c
  • Pointer size: 131 Bytes
  • Size of remote file: 289 kB
gitattributes ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ static/resources/RTED.png filter=lfs diff=lfs merge=lfs -text
37
+ RTED.png filter=lfs diff=lfs merge=lfs -text
38
+ cover.png filter=lfs diff=lfs merge=lfs -text
model/RAFDB_Custom.h5 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:59bf57cef329e4ed0bd204f88a26446836481667f54e62e023f4027645c705b9
3
+ size 50885928
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ Flask==2.3.2
2
+ tensorflow-cpu==2.18.0
3
+ Pillow==10.0.1
4
+ numpy==1.26.4
5
+ gunicorn==20.1.0
6
+ opencv-python-headless==4.9.0.80
7
+ mtcnn==0.1.1
static/resources/Logo.webp ADDED
static/resources/RTED.png ADDED

Git LFS Details

  • SHA256: b7f08f214843fe03c8fdb10b1f78fe9ca9da19fd288a9accf7a82f01ff0c564c
  • Pointer size: 131 Bytes
  • Size of remote file: 289 kB
static/style.css ADDED
@@ -0,0 +1,289 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ * {
2
+ margin: 0;
3
+ padding: 0;
4
+ box-sizing: border-box;
5
+ }
6
+
7
+ /* General Styles */
8
+ body {
9
+ font-family: 'Poppins', sans-serif;
10
+ background-color: #f8fafc;
11
+ color: #333;
12
+ text-align: center;
13
+ display: flex;
14
+ flex-direction: column;
15
+ height: 100vh;
16
+ }
17
+
18
+ /* Header Navbar */
19
+ header {
20
+ width: 100%;
21
+ background-color: #1E293B;
22
+ color: white;
23
+ display: flex;
24
+ align-items: center;
25
+ justify-content: center;
26
+ padding: 15px 20px;
27
+ position: relative;
28
+ }
29
+
30
+ header .sidebar-toggle {
31
+ position: absolute;
32
+ left: 15px;
33
+ min-width: 120px;
34
+ max-width: 150px;
35
+ text-align: center;
36
+ white-space: nowrap;
37
+ background-color: #334155;
38
+ color: white;
39
+ padding: 8px 12px;
40
+ border: none;
41
+ border-radius: 6px;
42
+ font-size: 14px;
43
+ cursor: pointer;
44
+ transition: background 0.3s ease-in-out, transform 0.2s ease-in-out;
45
+ }
46
+
47
+ /* Hover Effect */
48
+ header .sidebar-toggle:hover {
49
+ background-color: #475569;
50
+ transform: scale(1.05); /* Slight zoom effect on hover */
51
+ }
52
+
53
+ /* Mobile Optimization */
54
+ @media (max-width: 400px) {
55
+ header .sidebar-toggle {
56
+ min-width: 90px;
57
+ font-size: 12px;
58
+ padding: 5px 8px;
59
+ }
60
+ }
61
+
62
+ @media (max-width: 320px) {
63
+ header .sidebar-toggle {
64
+ min-width: 85px;
65
+ font-size: 11px;
66
+ padding: 4px 6px;
67
+ }
68
+ }
69
+
70
+
71
+
72
+ .header-content {
73
+ display: flex;
74
+ align-items: center;
75
+ gap: 10px;
76
+ }
77
+
78
+ header img {
79
+ height: 40px;
80
+ }
81
+
82
+ /* Sidebar Navigation */
83
+ nav {
84
+ position: fixed;
85
+ top: 0;
86
+ left: -250px;
87
+ height: 100vh;
88
+ width: 250px;
89
+ background-color: #1E293B;
90
+ color: white;
91
+ display: flex;
92
+ flex-direction: column;
93
+ align-items: center;
94
+ padding: 20px;
95
+ transition: left 0.3s ease-in-out;
96
+ box-shadow: 4px 0 10px rgba(0, 0, 0, 0.2);
97
+ }
98
+
99
+ nav.show {
100
+ left: 0;
101
+ }
102
+
103
+ nav .nav-header {
104
+ display: flex;
105
+ align-items: center;
106
+ gap: 10px;
107
+ margin-bottom: 15px;
108
+ }
109
+
110
+ nav img {
111
+ height: 30px;
112
+ }
113
+
114
+ nav h2 {
115
+ font-size: 20px;
116
+ font-weight: bold;
117
+ }
118
+
119
+ /* Sidebar Buttons */
120
+ nav button, nav a {
121
+ width: 100%;
122
+ padding: 12px;
123
+ font-size: 16px;
124
+ background-color: #334155;
125
+ border: none;
126
+ color: white;
127
+ cursor: pointer;
128
+ transition: background 0.3s;
129
+ margin-bottom: 10px;
130
+ border-radius: 6px;
131
+ font-weight: bold;
132
+ text-align: center;
133
+ text-decoration: none;
134
+ }
135
+
136
+ nav button:hover, nav a:hover {
137
+ background-color: #475569;
138
+ }
139
+
140
+ nav .linkedin-link {
141
+ margin-top: 20px;
142
+ color: #0077B5;
143
+ font-weight: 300;
144
+ text-decoration: none;
145
+ background: none;
146
+ padding: 8px 0;
147
+ border: none;
148
+ display: block;
149
+ text-align: center;
150
+ transition: color 0.2s ease-in-out;
151
+ }
152
+
153
+ nav .linkedin-link:hover {
154
+ text-decoration: underline;
155
+ color: #005582;
156
+ }
157
+
158
+
159
+
160
+ /* Main Content */
161
+ .container {
162
+ flex: 1;
163
+ display: flex;
164
+ flex-direction: column;
165
+ align-items: center;
166
+ justify-content: center;
167
+ padding: 40px;
168
+ }
169
+
170
+ /* Hide Sections Initially */
171
+ #upload-container, #video-container {
172
+ display: none;
173
+ flex-direction: column;
174
+ align-items: center;
175
+ padding: 20px;
176
+ border-radius: 8px;
177
+ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
178
+ background: white;
179
+ max-width: 600px;
180
+ width: 100%;
181
+ }
182
+
183
+ #upload-container.active, #video-container.active {
184
+ display: flex;
185
+ }
186
+
187
+ /* Upload Form */
188
+ .upload-form {
189
+ display: flex;
190
+ flex-direction: column;
191
+ align-items: center;
192
+ gap: 10px;
193
+ }
194
+
195
+ .upload-form input[type="file"] {
196
+ display: none;
197
+ }
198
+
199
+ .upload-title {
200
+ margin-bottom: 15px;
201
+ }
202
+
203
+ .upload-buttons {
204
+ display: flex;
205
+ gap: 15px;
206
+ align-items: center;
207
+ justify-content: center;
208
+ flex-wrap: wrap;
209
+ width: 100%;
210
+ max-width: 400px;
211
+ }
212
+
213
+ .custom-file-upload {
214
+ background-color: #3B82F6;
215
+ color: white;
216
+ padding: 10px 20px;
217
+ border-radius: 6px;
218
+ font-size: 16px;
219
+ font-weight: bold;
220
+ cursor: pointer;
221
+ transition: all 0.3s ease-in-out;
222
+ display: inline-block;
223
+ }
224
+
225
+ .custom-file-upload:hover {
226
+ background-color: #2563EB;
227
+ }
228
+
229
+ .upload-buttons button {
230
+ background-color: #3B82F6;
231
+ color: white;
232
+ padding: 10px 20px;
233
+ border: none;
234
+ border-radius: 6px;
235
+ font-size: 16px;
236
+ cursor: pointer;
237
+ font-weight: bold;
238
+ transition: all 0.3s ease-in-out;
239
+ }
240
+
241
+ .upload-buttons button:hover {
242
+ background-color: #2563EB;
243
+ }
244
+
245
+
246
+
247
+ /* Emotion List */
248
+ .emotion-list {
249
+ list-style: none;
250
+ padding: 0;
251
+ margin-top: 10px;
252
+ font-size: 16px;
253
+ font-family: 'Roboto', sans-serif;
254
+ font-weight: 300;
255
+ line-height: 1.6;
256
+ }
257
+
258
+ /* Video Controls */
259
+ .video-controls {
260
+ display: flex;
261
+ justify-content: center;
262
+ gap: 15px;
263
+ margin-top: 10px;
264
+ }
265
+
266
+ .video-controls button {
267
+ background-color: #3B82F6;
268
+ color: white;
269
+ padding: 10px 20px;
270
+ border: none;
271
+ border-radius: 6px;
272
+ font-size: 16px;
273
+ cursor: pointer;
274
+ font-weight: bold;
275
+ transition: all 0.3s ease-in-out;
276
+ }
277
+
278
+ .video-controls button:hover {
279
+ background-color: #2563EB;
280
+ }
281
+
282
+ /* Styled Images */
283
+ .result-image, video {
284
+ width: 100%;
285
+ max-width: 600px;
286
+ border-radius: 8px;
287
+ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
288
+ margin-top: 10px;
289
+ }
templates/index.html ADDED
@@ -0,0 +1,161 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Real-Time Emotion Detection (RTED)</title>
7
+ <link rel="stylesheet" href="/static/style.css">
8
+ </head>
9
+ <body>
10
+ <header>
11
+ <button onclick="toggleSidebar()" class="sidebar-toggle">Switch Detection</button>
12
+ <div class="header-content">
13
+ <img src="/static/resources/Logo.webp" alt="Logo">
14
+ <h1>Real-Time Emotion Detection</h1>
15
+ </div>
16
+ </header>
17
+
18
+ <nav id="sidebar">
19
+ <button onclick="toggleSidebar()" class="close-btn">✖</button>
20
+ <div class="nav-header">
21
+ <img src="/static/resources/Logo.webp" alt="Logo">
22
+ <h2>RTED</h2>
23
+ </div>
24
+ <button onclick="showUpload()">Static Detection</button>
25
+ <button onclick="showCamera()">Real-Time Detection</button>
26
+ <a href="https://www.linkedin.com/in/shamskhan404/" target="_blank" class="linkedin-link">Connect on LinkedIn</a>
27
+ </nav>
28
+
29
+ <div class="container">
30
+ <div id="upload-container" class="active">
31
+ <h2 class="upload-title">Upload an image to detect emotion</h2>
32
+ <form action="/classify" method="POST" enctype="multipart/form-data" class="upload-form">
33
+ <div class="upload-buttons">
34
+ <label for="file-upload" class="custom-file-upload">Choose File</label>
35
+ <input id="file-upload" type="file" name="image" required>
36
+ <button type="submit">Detect Emotion</button>
37
+ </div>
38
+ </form>
39
+
40
+
41
+ <img id="static-image" src="{% if initial_image %}/static/resources/RTED.png{% else %}data:image/png;base64,{{ img_base64 }}{% endif %}" class="result-image">
42
+ {% if top_emotions %}
43
+ <h2>Detected Emotions:</h2>
44
+ <ul class="emotion-list">
45
+ {% for emotion, percent in top_emotions[:3] %}
46
+ <li>{{ percent }}% {{ emotion }}</li>
47
+ {% endfor %}
48
+ </ul>
49
+ {% endif %}
50
+ </div>
51
+
52
+ <div id="video-container">
53
+ <h2>Use the camera to detect emotions</h2>
54
+ <img id="rted-placeholder" src="/static/resources/RTED.png" alt="RTED Placeholder" style="width: 100%; max-width: 640px; display: block;">
55
+ <video id="video-feed" width="640" height="480" autoplay style="display: none;"></video>
56
+ <img id="processed-video-frame" src="" alt="Processed Frame" style="display: none; width: 100%; max-width: 640px;">
57
+
58
+ <h2 id="emotion-result"></h2>
59
+ <div class="video-controls">
60
+ <button onclick="startCamera()">Start Detection</button>
61
+ <button onclick="stopCamera()">Stop Detection</button>
62
+ </div>
63
+ </div>
64
+ </div>
65
+
66
+ <script>
67
+ function toggleSidebar() {
68
+ let sidebar = document.getElementById("sidebar");
69
+ sidebar.classList.toggle("show");
70
+ }
71
+
72
+ function showUpload() {
73
+ document.getElementById("upload-container").classList.add("active");
74
+ document.getElementById("video-container").classList.remove("active");
75
+ }
76
+
77
+ function showCamera() {
78
+ document.getElementById("upload-container").classList.remove("active");
79
+ document.getElementById("video-container").classList.add("active");
80
+ toggleSidebar();
81
+ }
82
+
83
+ let videoStream = null;
84
+ let video = document.getElementById("video-feed");
85
+ let canvas = document.createElement("canvas");
86
+ let ctx = canvas.getContext("2d");
87
+ let intervalId = null;
88
+
89
+ function startCamera() {
90
+ if (videoStream) {
91
+ stopCamera();
92
+ }
93
+
94
+ navigator.mediaDevices.getUserMedia({ video: true })
95
+ .then(stream => {
96
+ videoStream = stream;
97
+ video.srcObject = stream;
98
+ video.style.display = "block";
99
+ document.getElementById("rted-placeholder").style.display = "none";
100
+ startSendingFrames();
101
+ })
102
+ .catch(err => {
103
+ console.error("Error accessing camera: ", err);
104
+ alert("Could not access the camera. Please allow camera permissions.");
105
+ });
106
+ }
107
+
108
+ function stopCamera() {
109
+ if (videoStream) {
110
+ videoStream.getTracks().forEach(track => track.stop());
111
+ videoStream = null;
112
+ }
113
+
114
+ clearInterval(intervalId);
115
+ intervalId = null;
116
+
117
+ video.srcObject = null;
118
+ video.style.display = "none";
119
+ document.getElementById("processed-video-frame").style.display = "none"; // Hide processed frame
120
+ document.getElementById("processed-video-frame").src = ""; // Clear the last frame
121
+ document.getElementById("rted-placeholder").style.display = "block";
122
+ document.getElementById("emotion-result").innerText = "";
123
+ }
124
+
125
+
126
+ function startSendingFrames() {
127
+ intervalId = setInterval(() => {
128
+ if (!videoStream) return;
129
+
130
+ canvas.width = video.videoWidth;
131
+ canvas.height = video.videoHeight;
132
+ ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
133
+
134
+ let imageData = canvas.toDataURL("image/jpeg");
135
+
136
+ fetch("/predict_video", {
137
+ method: "POST",
138
+ body: JSON.stringify({ image: imageData }),
139
+ headers: { "Content-Type": "application/json" }
140
+ })
141
+ .then(response => response.json())
142
+ .then(data => {
143
+ // Display processed frame with bounding boxes
144
+ document.getElementById("video-feed").style.display = "none"; // Hide original feed
145
+ document.getElementById("rted-placeholder").style.display = "none"; // Hide placeholder
146
+ document.getElementById("processed-video-frame").src = "data:image/jpeg;base64," + data.processed_frame;
147
+ document.getElementById("processed-video-frame").style.display = "block";
148
+
149
+ // Show detected emotions
150
+ if (data.emotions.length > 0) {
151
+ let emotionsText = data.emotions.slice(0, 3).map(e => `${e[0]} (${e[1]}%)`).join(', ');
152
+ document.getElementById("emotion-result").innerText = `Detected: ${emotionsText}`;
153
+ }
154
+ })
155
+ .catch(error => console.error("Error:", error));
156
+ }, 500); // 2 frames 1 FPS
157
+ }
158
+
159
+ </script>
160
+ </body>
161
+ </html>