vijul.shah commited on
Commit
3733e70
1 Parent(s): 5f721d1

Video Frames Drift Bug Solved, Added diff colors for charts

Browse files
Files changed (2) hide show
  1. app.py +54 -30
  2. app_utils.py +75 -100
app.py CHANGED
@@ -38,18 +38,6 @@ LABEL_MAP = ["left_pupil", "right_pupil"]
38
 
39
  def main():
40
  st.set_page_config(page_title="Pupil Diameter Estimator", layout="wide")
41
- st.markdown(
42
- """
43
- <style>
44
- /* Remove the top margin/padding */
45
- .block-container {
46
- padding-top: 0rem;
47
- padding-bottom: 1rem; /* Adjust this as needed */
48
- }
49
- </style>
50
- """,
51
- unsafe_allow_html=True,
52
- )
53
  st.title("EyeDentify Playground")
54
  cols = st.columns((1, 1))
55
  cols[0].header("Input")
@@ -93,6 +81,8 @@ def main():
93
 
94
  blink_detection = st.sidebar.checkbox("Detect Blinks")
95
 
 
 
96
  if st.sidebar.button("Predict Diameter & Compute CAM"):
97
  if uploaded_file is None:
98
  st.sidebar.error("Please upload an image or video")
@@ -146,7 +136,8 @@ def main():
146
  # Create a layout for the charts
147
  cols = st.columns(num_columns)
148
 
149
- colors = ["#2ca02c", "#d62728", "#1f77b4", "#ff7f0e"] # Green, Red, Blue, Orange
 
150
 
151
  # Iterate through categories and assign charts to columns
152
  for i, (category, values) in enumerate(predicted_diameters.items()):
@@ -165,9 +156,9 @@ def main():
165
  max_value = max(filter(lambda x: x is not None, values), default=None)
166
 
167
  # Create an Altair chart with y-axis limits
168
- chart = (
169
  alt.Chart(df)
170
- .mark_line(point=True, color=colors[i])
171
  .encode(
172
  x=alt.X("Frame:Q", title="Frame Number"),
173
  y=alt.Y(
@@ -176,50 +167,83 @@ def main():
176
  scale=alt.Scale(domain=[min_value, max_value]),
177
  ),
178
  tooltip=[
179
- alt.Tooltip("Frame:Q", title="Frame Number"),
180
  alt.Tooltip(f"{category}:Q", title="Diameter"),
181
  ],
182
  )
183
- .properties(title=f"{category} - Predicted Diameters")
184
- .configure_axis(grid=True)
185
  )
 
 
 
 
 
 
 
186
 
187
  # Display the Altair chart
188
- st.altair_chart(chart, use_container_width=True)
189
 
190
  if eyes_ratios is not None and len(eyes_ratios) > 0:
191
- df = pd.DataFrame(eyes_ratios, columns=["Eyes Aspect Ratio"])
192
  df["Frame"] = range(1, len(eyes_ratios) + 1) # Create a frame column starting from 1
193
 
194
  # Create an Altair chart for eyes_ratios
195
  line_chart = (
196
  alt.Chart(df)
197
- .mark_line(point=True, color=colors[-1]) # Set color of the line
198
  .encode(
199
  x=alt.X("Frame:Q", title="Frame Number"),
200
- y=alt.Y("Eyes Aspect Ratio:Q", title="Eyes Aspect Ratio"),
201
- tooltip=[
202
- alt.Tooltip("Frame:Q", title="Frame Number"),
203
- alt.Tooltip("Eyes Aspect Ratio:Q", title="Eyes Aspect Ratio"),
204
- ],
205
  )
206
  # .properties(title="Eyes Aspect Ratios (EARs)")
207
  # .configure_axis(grid=True)
208
  )
 
209
 
210
  # Create a horizontal rule at y=0.22
211
  line1 = alt.Chart(pd.DataFrame({"y": [0.22]})).mark_rule(color="red").encode(y="y:Q")
212
 
213
- line2 = alt.Chart(pd.DataFrame({"y": [0.25]})).mark_rule(color="blue").encode(y="y:Q")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
214
 
215
- # Combine line chart and horizontal line, and apply configuration
216
- final_chart = line_chart.properties(title="Eyes Aspect Ratios (EARs)") + line1 + line2
 
 
 
 
 
 
 
 
217
 
218
  # Configure axis properties at the chart level
219
  final_chart = final_chart.configure_axis(grid=True)
220
 
221
  # Display the Altair chart
222
- st.subheader("Eyes Aspect Ratios (EARs)")
223
  st.altair_chart(final_chart, use_container_width=True)
224
 
225
 
 
38
 
39
  def main():
40
  st.set_page_config(page_title="Pupil Diameter Estimator", layout="wide")
 
 
 
 
 
 
 
 
 
 
 
 
41
  st.title("EyeDentify Playground")
42
  cols = st.columns((1, 1))
43
  cols[0].header("Input")
 
81
 
82
  blink_detection = st.sidebar.checkbox("Detect Blinks")
83
 
84
+ st.markdown("<style>#vg-tooltip-element{z-index: 1000051}</style>", unsafe_allow_html=True)
85
+
86
  if st.sidebar.button("Predict Diameter & Compute CAM"):
87
  if uploaded_file is None:
88
  st.sidebar.error("Please upload an image or video")
 
136
  # Create a layout for the charts
137
  cols = st.columns(num_columns)
138
 
139
+ # colors = ["#2ca02c", "#d62728", "#1f77b4", "#ff7f0e"] # Green, Red, Blue, Orange
140
+ colors = ["#1f77b4", "#ff7f0e", "#636363"] # Blue, Orange, Gray
141
 
142
  # Iterate through categories and assign charts to columns
143
  for i, (category, values) in enumerate(predicted_diameters.items()):
 
156
  max_value = max(filter(lambda x: x is not None, values), default=None)
157
 
158
  # Create an Altair chart with y-axis limits
159
+ line_chart = (
160
  alt.Chart(df)
161
+ .mark_line(color=colors[i])
162
  .encode(
163
  x=alt.X("Frame:Q", title="Frame Number"),
164
  y=alt.Y(
 
167
  scale=alt.Scale(domain=[min_value, max_value]),
168
  ),
169
  tooltip=[
170
+ "Frame",
171
  alt.Tooltip(f"{category}:Q", title="Diameter"),
172
  ],
173
  )
174
+ # .properties(title=f"{category} - Predicted Diameters")
175
+ # .configure_axis(grid=True)
176
  )
177
+ points_chart = line_chart.mark_point(color=colors[i], filled=True)
178
+
179
+ final_chart = (
180
+ line_chart.properties(title=f"{category} - Predicted Diameters") + points_chart
181
+ ).interactive()
182
+
183
+ final_chart = final_chart.configure_axis(grid=True)
184
 
185
  # Display the Altair chart
186
+ st.altair_chart(final_chart, use_container_width=True)
187
 
188
  if eyes_ratios is not None and len(eyes_ratios) > 0:
189
+ df = pd.DataFrame(eyes_ratios, columns=["EAR"])
190
  df["Frame"] = range(1, len(eyes_ratios) + 1) # Create a frame column starting from 1
191
 
192
  # Create an Altair chart for eyes_ratios
193
  line_chart = (
194
  alt.Chart(df)
195
+ .mark_line(color=colors[-1]) # Set color of the line
196
  .encode(
197
  x=alt.X("Frame:Q", title="Frame Number"),
198
+ y=alt.Y("EAR:Q", title="Eyes Aspect Ratio"),
199
+ tooltip=["Frame", "EAR"],
 
 
 
200
  )
201
  # .properties(title="Eyes Aspect Ratios (EARs)")
202
  # .configure_axis(grid=True)
203
  )
204
+ points_chart = line_chart.mark_point(color=colors[-1], filled=True)
205
 
206
  # Create a horizontal rule at y=0.22
207
  line1 = alt.Chart(pd.DataFrame({"y": [0.22]})).mark_rule(color="red").encode(y="y:Q")
208
 
209
+ line2 = alt.Chart(pd.DataFrame({"y": [0.25]})).mark_rule(color="green").encode(y="y:Q")
210
+
211
+ # Add text annotations for the lines
212
+ text1 = (
213
+ alt.Chart(pd.DataFrame({"y": [0.22], "label": ["Definite Blinks (<=0.22)"]}))
214
+ .mark_text(align="left", dx=100, dy=9, color="red", size=16)
215
+ .encode(y="y:Q", text="label:N")
216
+ )
217
+
218
+ text2 = (
219
+ alt.Chart(pd.DataFrame({"y": [0.25], "label": ["No Blinks (>=0.25)"]}))
220
+ .mark_text(align="left", dx=-150, dy=-9, color="green", size=16)
221
+ .encode(y="y:Q", text="label:N")
222
+ )
223
+
224
+ # Add gray area text for the region between red and green lines
225
+ gray_area_text = (
226
+ alt.Chart(pd.DataFrame({"y": [0.235], "label": ["Gray Area"]}))
227
+ .mark_text(align="left", dx=0, dy=0, color="gray", size=16)
228
+ .encode(y="y:Q", text="label:N")
229
+ )
230
 
231
+ # Combine all elements: line chart, points, rules, and text annotations
232
+ final_chart = (
233
+ line_chart.properties(title="Eyes Aspect Ratios (EARs)")
234
+ + points_chart
235
+ + line1
236
+ + line2
237
+ + text1
238
+ + text2
239
+ + gray_area_text
240
+ ).interactive()
241
 
242
  # Configure axis properties at the chart level
243
  final_chart = final_chart.configure_axis(grid=True)
244
 
245
  # Display the Altair chart
246
+ # st.subheader("Eyes Aspect Ratios (EARs)")
247
  st.altair_chart(final_chart, use_container_width=True)
248
 
249
 
app_utils.py CHANGED
@@ -82,6 +82,18 @@ def is_video(file_extension):
82
  return file_extension.lower() in ["mp4", "avi", "mov", "mkv", "webm"]
83
 
84
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  def display_results(input_image, cam_frame, pupil_diameter, cols):
86
  """Displays the input image and overlayed CAM result."""
87
  fig, axs = plt.subplots(1, 2, figsize=(10, 5))
@@ -141,6 +153,7 @@ def setup(cols, pupil_selection, tv_model, output_path):
141
  output_frames = {}
142
  input_frames = {}
143
  predicted_diameters = {}
 
144
 
145
  if pupil_selection == "both":
146
  selected_eyes = ["left_eye", "right_eye"]
@@ -163,37 +176,30 @@ def setup(cols, pupil_selection, tv_model, output_path):
163
  output_frames[eye_type] = []
164
  input_frames[eye_type] = []
165
  predicted_diameters[eye_type] = []
 
166
  else:
167
  right_pupil_model = load_model(model_configs)
168
  right_pupil_cam_extractor = None
169
  output_frames[eye_type] = []
170
  input_frames[eye_type] = []
171
  predicted_diameters[eye_type] = []
 
172
 
173
- video_input_placeholders = {}
174
- video_output_placeholders = {}
175
- video_predictions_placeholders = {}
176
 
177
  if output_path:
178
  video_cols = cols[1].columns(len(input_frames.keys()))
179
 
180
  for i, eye_type in enumerate(list(input_frames.keys())):
181
- video_input_placeholders[eye_type] = video_cols[i].empty()
182
-
183
- for i, eye_type in enumerate(list(input_frames.keys())):
184
- video_output_placeholders[eye_type] = video_cols[i].empty()
185
-
186
- for i, eye_type in enumerate(list(input_frames.keys())):
187
- video_predictions_placeholders[eye_type] = video_cols[i].empty()
188
 
189
  return (
190
  selected_eyes,
191
  input_frames,
192
  output_frames,
193
  predicted_diameters,
194
- video_input_placeholders,
195
- video_output_placeholders,
196
- video_predictions_placeholders,
197
  left_pupil_model,
198
  left_pupil_cam_extractor,
199
  right_pupil_model,
@@ -214,9 +220,8 @@ def process_frames(
214
  input_frames,
215
  output_frames,
216
  predicted_diameters,
217
- video_input_placeholders,
218
- video_output_placeholders,
219
- video_predictions_placeholders,
220
  left_pupil_model,
221
  left_pupil_cam_extractor,
222
  right_pupil_model,
@@ -287,7 +292,6 @@ def process_frames(
287
  for i, eye_type in enumerate(selected_eyes):
288
 
289
  if blinked:
290
-
291
  if left_eye is not None and eye_type == "left_eye":
292
  _, height, width = left_eye.squeeze(0).shape
293
  input_image_pil = to_pil_image(left_eye.squeeze(0))
@@ -360,19 +364,20 @@ def process_frames(
360
  else:
361
  text = predicted_diameter
362
  frame = overlay_text_on_frame(frame, text)
 
 
 
363
 
364
- video_input_placeholders[eye_type].image(input_img_np, use_column_width=True)
365
- video_output_placeholders[eye_type].image(output_img_np, use_column_width=True)
366
- video_predictions_placeholders[eye_type].image(frame, use_column_width=True)
367
 
368
  st.session_state.current_frame = idx + 1
369
  txt = f"<p style='font-size:20px;'> Number of Frames Processed: <strong>{st.session_state.current_frame} / {st.session_state.total_frames}</strong> </p>"
370
  st.session_state.frame_placeholder.markdown(txt, unsafe_allow_html=True)
371
 
372
  if output_path:
373
- show_input_frames(input_frames, output_path, codec, video_input_placeholders)
374
- show_cam_frames(output_frames, output_path, codec, video_output_placeholders)
375
- show_pred_text_frames(output_frames, output_path, predicted_diameters, codec, video_predictions_placeholders)
376
 
377
  return input_frames, output_frames, predicted_diameters, face_frames, eyes_ratios
378
 
@@ -387,83 +392,6 @@ def display_video_with_autoplay(video_col, video_path):
387
  video_col.markdown(video_html, unsafe_allow_html=True)
388
 
389
 
390
- def get_codec_and_extension(file_format):
391
- """Return codec and file extension based on the format."""
392
- if file_format == "mp4":
393
- return "H264", ".mp4"
394
- elif file_format == "avi":
395
- return "MJPG", ".avi"
396
- elif file_format == "webm":
397
- return "VP80", ".webm"
398
- else:
399
- return "MJPG", ".avi"
400
-
401
-
402
- def show_input_frames(input_frames, output_path, codec, video_cols):
403
- for i, eye_type in enumerate(input_frames.keys()):
404
- in_frames = input_frames[eye_type]
405
- height, width, _ = in_frames[0].shape
406
- fourcc = cv2.VideoWriter_fourcc(*codec)
407
- fps = 10.0
408
- out = cv2.VideoWriter(output_path, fourcc, fps, (width, height))
409
- for frame in in_frames:
410
- out.write(cv2.cvtColor(frame, cv2.COLOR_RGB2BGR))
411
- out.release()
412
-
413
- with open(output_path, "rb") as video_file:
414
- video_bytes = video_file.read()
415
- video_base64 = base64.b64encode(video_bytes).decode("utf-8")
416
- display_video_with_autoplay(video_cols[eye_type], video_base64)
417
-
418
- os.remove(output_path)
419
-
420
-
421
- def show_cam_frames(output_frames, output_path, codec, video_cols):
422
- for i, eye_type in enumerate(output_frames.keys()):
423
- out_frames = output_frames[eye_type]
424
- height, width, _ = out_frames[0].shape
425
- fourcc = cv2.VideoWriter_fourcc(*codec)
426
- fps = 10.0
427
- out = cv2.VideoWriter(output_path, fourcc, fps, (width, height))
428
- for j, frame in enumerate(out_frames):
429
- out.write(cv2.cvtColor(frame, cv2.COLOR_RGB2BGR))
430
- out.release()
431
-
432
- with open(output_path, "rb") as video_file:
433
- video_bytes = video_file.read()
434
- video_base64 = base64.b64encode(video_bytes).decode("utf-8")
435
- display_video_with_autoplay(video_cols[eye_type], video_base64)
436
-
437
- os.remove(output_path)
438
-
439
-
440
- def show_pred_text_frames(output_frames, output_path, predicted_diameters, codec, video_cols):
441
- for i, eye_type in enumerate(output_frames.keys()):
442
-
443
- out_frames = output_frames[eye_type]
444
- height, width, _ = out_frames[0].shape
445
- fourcc = cv2.VideoWriter_fourcc(*codec)
446
- fps = 10.0
447
- out = cv2.VideoWriter(output_path, fourcc, fps, (width, height))
448
-
449
- for diameter in predicted_diameters[eye_type]:
450
- frame = np.zeros((height, width, 3), dtype=np.uint8)
451
- if not isinstance(diameter, str):
452
- text = f"{diameter:.2f}"
453
- else:
454
- text = diameter
455
- frame = overlay_text_on_frame(frame, text)
456
- out.write(frame)
457
- out.release()
458
-
459
- with open(output_path, "rb") as video_file:
460
- video_bytes = video_file.read()
461
- video_base64 = base64.b64encode(video_bytes).decode("utf-8")
462
- display_video_with_autoplay(video_cols[eye_type], video_base64)
463
-
464
- os.remove(output_path)
465
-
466
-
467
  def process_video(cols, video_frames, tv_model, pupil_selection, output_path, cam_method, blink_detection=False):
468
 
469
  resized_frames = []
@@ -487,3 +415,50 @@ def convert_diameter(value):
487
  return float(value)
488
  except (ValueError, TypeError):
489
  return None # Return None if conversion fails
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  return file_extension.lower() in ["mp4", "avi", "mov", "mkv", "webm"]
83
 
84
 
85
+ def get_codec_and_extension(file_format):
86
+ """Return codec and file extension based on the format."""
87
+ if file_format == "mp4":
88
+ return "H264", ".mp4"
89
+ elif file_format == "avi":
90
+ return "MJPG", ".avi"
91
+ elif file_format == "webm":
92
+ return "VP80", ".webm"
93
+ else:
94
+ return "MJPG", ".avi"
95
+
96
+
97
  def display_results(input_image, cam_frame, pupil_diameter, cols):
98
  """Displays the input image and overlayed CAM result."""
99
  fig, axs = plt.subplots(1, 2, figsize=(10, 5))
 
153
  output_frames = {}
154
  input_frames = {}
155
  predicted_diameters = {}
156
+ pred_diameters_frames = {}
157
 
158
  if pupil_selection == "both":
159
  selected_eyes = ["left_eye", "right_eye"]
 
176
  output_frames[eye_type] = []
177
  input_frames[eye_type] = []
178
  predicted_diameters[eye_type] = []
179
+ pred_diameters_frames[eye_type] = []
180
  else:
181
  right_pupil_model = load_model(model_configs)
182
  right_pupil_cam_extractor = None
183
  output_frames[eye_type] = []
184
  input_frames[eye_type] = []
185
  predicted_diameters[eye_type] = []
186
+ pred_diameters_frames[eye_type] = []
187
 
188
+ video_placeholders = {}
 
 
189
 
190
  if output_path:
191
  video_cols = cols[1].columns(len(input_frames.keys()))
192
 
193
  for i, eye_type in enumerate(list(input_frames.keys())):
194
+ video_placeholders[eye_type] = video_cols[i].empty()
 
 
 
 
 
 
195
 
196
  return (
197
  selected_eyes,
198
  input_frames,
199
  output_frames,
200
  predicted_diameters,
201
+ pred_diameters_frames,
202
+ video_placeholders,
 
203
  left_pupil_model,
204
  left_pupil_cam_extractor,
205
  right_pupil_model,
 
220
  input_frames,
221
  output_frames,
222
  predicted_diameters,
223
+ pred_diameters_frames,
224
+ video_placeholders,
 
225
  left_pupil_model,
226
  left_pupil_cam_extractor,
227
  right_pupil_model,
 
292
  for i, eye_type in enumerate(selected_eyes):
293
 
294
  if blinked:
 
295
  if left_eye is not None and eye_type == "left_eye":
296
  _, height, width = left_eye.squeeze(0).shape
297
  input_image_pil = to_pil_image(left_eye.squeeze(0))
 
364
  else:
365
  text = predicted_diameter
366
  frame = overlay_text_on_frame(frame, text)
367
+ pred_diameters_frames[eye_type].append(frame)
368
+
369
+ combined_frame = np.vstack((input_img_np, output_img_np, frame))
370
 
371
+ video_placeholders[eye_type].image(combined_frame, use_column_width=True)
 
 
372
 
373
  st.session_state.current_frame = idx + 1
374
  txt = f"<p style='font-size:20px;'> Number of Frames Processed: <strong>{st.session_state.current_frame} / {st.session_state.total_frames}</strong> </p>"
375
  st.session_state.frame_placeholder.markdown(txt, unsafe_allow_html=True)
376
 
377
  if output_path:
378
+ combine_and_show_frames(
379
+ input_frames, output_frames, pred_diameters_frames, output_path, codec, video_placeholders
380
+ )
381
 
382
  return input_frames, output_frames, predicted_diameters, face_frames, eyes_ratios
383
 
 
392
  video_col.markdown(video_html, unsafe_allow_html=True)
393
 
394
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
395
  def process_video(cols, video_frames, tv_model, pupil_selection, output_path, cam_method, blink_detection=False):
396
 
397
  resized_frames = []
 
415
  return float(value)
416
  except (ValueError, TypeError):
417
  return None # Return None if conversion fails
418
+
419
+
420
+ def combine_and_show_frames(input_frames, cam_frames, pred_diameters_frames, output_path, codec, video_cols):
421
+ # Assuming all frames have the same keys (eye types)
422
+ eye_types = input_frames.keys()
423
+
424
+ for i, eye_type in enumerate(eye_types):
425
+ in_frames = input_frames[eye_type]
426
+ cam_out_frames = cam_frames[eye_type]
427
+ pred_diameters_text_frames = pred_diameters_frames[eye_type]
428
+
429
+ # Get frame properties (assuming all frames have the same dimensions)
430
+ height, width, _ = in_frames[0].shape
431
+ fourcc = cv2.VideoWriter_fourcc(*codec)
432
+ fps = 10.0
433
+ out = cv2.VideoWriter(output_path, fourcc, fps, (width, height * 3)) # Width is tripled for concatenation
434
+
435
+ # Loop through each set of frames and concatenate them
436
+ for j in range(len(in_frames)):
437
+ input_frame = in_frames[j]
438
+ cam_frame = cam_out_frames[j]
439
+ pred_frame = pred_diameters_text_frames[j]
440
+
441
+ # Convert frames to BGR if necessary
442
+ input_frame_bgr = cv2.cvtColor(input_frame, cv2.COLOR_RGB2BGR)
443
+ cam_frame_bgr = cv2.cvtColor(cam_frame, cv2.COLOR_RGB2BGR)
444
+ pred_frame_bgr = cv2.cvtColor(pred_frame, cv2.COLOR_RGB2BGR)
445
+
446
+ # Concatenate frames horizontally (input, cam, pred)
447
+ combined_frame = np.vstack((input_frame_bgr, cam_frame_bgr, pred_frame_bgr))
448
+
449
+ # Write the combined frame to the video
450
+ out.write(combined_frame)
451
+
452
+ # Release the video writer
453
+ out.release()
454
+
455
+ # Read the video and encode it in base64 for displaying
456
+ with open(output_path, "rb") as video_file:
457
+ video_bytes = video_file.read()
458
+ video_base64 = base64.b64encode(video_bytes).decode("utf-8")
459
+
460
+ # Display the combined video
461
+ display_video_with_autoplay(video_cols[eye_type], video_base64)
462
+
463
+ # Clean up
464
+ os.remove(output_path)