lokesh341 commited on
Commit
a7d0696
1 Parent(s): e24639e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +97 -111
app.py CHANGED
@@ -5,11 +5,12 @@ import time
5
  import json
6
  import random
7
  import logging
8
- import numpy as np # Moved to top to fix NameError
9
  from datetime import datetime
10
  from collections import Counter
11
  from typing import Any, Dict, List, Optional, Tuple
12
- import matplotlib.pyplot as plt # Added for chart generation
 
13
 
14
  # Suppress Ultralytics warning by setting a writable config directory
15
  os.environ["YOLO_CONFIG_DIR"] = "/tmp/Ultralytics"
@@ -53,15 +54,16 @@ logging.basicConfig(
53
 
54
  # Global variables
55
  paused: bool = False
56
- frame_rate: float = 0.1
57
  frame_count: int = 0
58
  log_entries: List[str] = []
59
- crack_counts: List[int] = [] # Track number of cracks per frame
60
- crack_severity_all: List[str] = [] # Track crack severities
61
  last_frame: Optional[np.ndarray] = None
62
  last_detections: Dict[str, Any] = {}
63
  last_timestamp: str = ""
64
  last_detected_images: List[str] = []
 
65
  gps_coordinates: List[List[float]] = []
66
  video_loaded: bool = False
67
  active_service: Optional[str] = None
@@ -75,15 +77,6 @@ os.makedirs(CAPTURED_FRAMES_DIR, exist_ok=True)
75
  os.makedirs(OUTPUT_DIR, exist_ok=True)
76
 
77
  def initialize_video(video_file: Optional[Any] = None) -> str:
78
- """
79
- Initialize the video file for processing.
80
-
81
- Args:
82
- video_file (Optional[Any]): Uploaded video file object (from Gradio).
83
-
84
- Returns:
85
- str: Status message indicating success or failure.
86
- """
87
  global video_loaded, log_entries
88
  release_video()
89
  video_path = DEFAULT_VIDEO_PATH
@@ -106,19 +99,6 @@ def set_active_service(
106
  rs_val: bool,
107
  pl_val: bool
108
  ) -> Tuple[Optional[str], str]:
109
- """
110
- Set the active service category based on user selection.
111
-
112
- Args:
113
- service_name (str): Name of the service being toggled.
114
- uc_val (bool): Under Construction toggle value.
115
- om_val (bool): Operations Maintenance toggle value.
116
- rs_val (bool): Road Safety toggle value.
117
- pl_val (bool): Plantation toggle value.
118
-
119
- Returns:
120
- Tuple[Optional[str], str]: Active service name and status message.
121
- """
122
  global active_service
123
  toggles = {
124
  "under_construction": uc_val,
@@ -146,36 +126,27 @@ def set_active_service(
146
  return None, "No Service Category Enabled"
147
 
148
  def generate_crack_trend_chart() -> Optional[plt.Figure]:
149
- """
150
- Generate a line chart for crack trend over time using Matplotlib.
151
-
152
- Returns:
153
- Optional[plt.Figure]: Matplotlib figure object or None if no data.
154
- """
155
  if not crack_counts:
156
  return None
157
 
158
  data = crack_counts[-50:]
159
  labels = list(range(len(data)))
160
 
161
- fig, ax = plt.subplots(figsize=(6, 4))
162
- ax.plot(labels, data, color="#FF6347", label="Cracks Over Time")
163
- ax.set_title("Crack Trend (Operations Maintenance)")
164
- ax.set_xlabel("Frame")
165
- ax.set_ylabel("Count")
166
  ax.set_ylim(bottom=0)
167
- ax.grid(True)
168
- ax.legend()
 
 
 
169
  plt.tight_layout()
170
  return fig
171
 
172
  def generate_crack_severity_chart() -> Optional[plt.Figure]:
173
- """
174
- Generate a pie chart for crack severity distribution using Matplotlib.
175
-
176
- Returns:
177
- Optional[plt.Figure]: Matplotlib figure object or None if no data.
178
- """
179
  if not crack_severity_all:
180
  return None
181
 
@@ -183,25 +154,24 @@ def generate_crack_severity_chart() -> Optional[plt.Figure]:
183
  labels = list(count.keys())
184
  sizes = list(count.values())
185
 
186
- fig, ax = plt.subplots(figsize=(6, 4))
187
- ax.pie(
 
188
  sizes,
189
  labels=labels,
190
- colors=["#FF6347", "#4682B4", "#FFD700"],
191
- autopct="%1.1f%%",
192
- startangle=90
 
 
193
  )
194
- ax.set_title("Crack Severity (Operations Maintenance)")
 
 
195
  plt.tight_layout()
196
  return fig
197
 
198
  def generate_severity_distribution_chart() -> Optional[plt.Figure]:
199
- """
200
- Generate a bar chart for crack severity distribution using Matplotlib.
201
-
202
- Returns:
203
- Optional[plt.Figure]: Matplotlib figure object or None if no data.
204
- """
205
  if not crack_severity_all:
206
  return None
207
 
@@ -209,12 +179,19 @@ def generate_severity_distribution_chart() -> Optional[plt.Figure]:
209
  labels = list(count.keys())
210
  sizes = list(count.values())
211
 
212
- fig, ax = plt.subplots(figsize=(6, 4))
213
- ax.bar(labels, sizes, color=["#FF6347", "#4682B4", "#FFD700"])
214
- ax.set_title("Severity Distribution (Operations Maintenance)")
215
- ax.set_xlabel("Severity")
216
- ax.set_ylabel("Count")
 
217
  ax.set_ylim(bottom=0)
 
 
 
 
 
 
218
  plt.tight_layout()
219
  return fig
220
 
@@ -227,21 +204,8 @@ def monitor_feed() -> Tuple[
227
  Optional[plt.Figure],
228
  List[str]
229
  ]:
230
- """
231
- Process video frames and perform detections based on the active service.
232
-
233
- Returns:
234
- Tuple containing:
235
- - Processed frame (numpy array or None).
236
- - Detection metrics (JSON string with crack trend and severity data).
237
- - Recent logs (string).
238
- - Crack trend chart (Matplotlib figure or None).
239
- - Crack severity chart (Matplotlib figure or None).
240
- - Severity distribution chart (Matplotlib figure or None).
241
- - List of captured image paths.
242
- """
243
  global paused, frame_count, last_frame, last_detections, last_timestamp
244
- global gps_coordinates, last_detected_images, video_loaded
245
 
246
  if not video_loaded:
247
  log_entries.append("Cannot start streaming: Video not loaded successfully.")
@@ -317,7 +281,7 @@ def monitor_feed() -> Tuple[
317
  frame = shadow_results["frame"]
318
  all_detected_items.extend(shadow_dets)
319
 
320
- # Apply thermal processing if frame is grayscale
321
  if len(frame.shape) == 2:
322
  thermal_results = process_thermal(frame)
323
  thermal_dets = thermal_results["detections"]
@@ -329,9 +293,9 @@ def monitor_feed() -> Tuple[
329
  logging.error(f"Processing error in {active_service}: {str(e)}")
330
  all_detected_items = []
331
 
332
- # Save temporary image for display
333
  try:
334
- cv2.imwrite(TEMP_IMAGE_PATH, frame, [int(cv2.IMWRITE_JPEG_QUALITY), 95])
335
  except Exception as e:
336
  log_entries.append(f"Error saving temp image: {str(e)}")
337
  logging.error(f"Error saving temp image: {str(e)}")
@@ -339,19 +303,25 @@ def monitor_feed() -> Tuple[
339
  # Update detection metrics
340
  metrics = update_metrics(all_detected_items)
341
 
342
- # Generate GPS coordinates with slight variation
343
  gps_coord = [17.385044 + random.uniform(-0.001, 0.001), 78.486671 + frame_count * 0.0001]
344
  gps_coordinates.append(gps_coord)
345
 
346
- # Save frame if detections are present
347
  detection_types = {item.get("type") for item in all_detected_items if "type" in item}
348
  if detection_types:
349
  try:
350
- captured_frame_path = os.path.join(CAPTURED_FRAMES_DIR, f"detected_{frame_count}.jpg")
351
- cv2.imwrite(captured_frame_path, frame)
352
- last_detected_images.append(captured_frame_path)
353
- if len(last_detected_images) > 100:
354
- last_detected_images.pop(0)
 
 
 
 
 
 
355
  except Exception as e:
356
  log_entries.append(f"Error saving captured frame: {str(e)}")
357
  logging.error(f"Error saving captured frame: {str(e)}")
@@ -386,7 +356,7 @@ def monitor_feed() -> Tuple[
386
  last_frame = frame.copy()
387
  last_detections = metrics
388
 
389
- # Track cracks for metrics (for Operations Maintenance only)
390
  crack_detected = len([item for item in all_detected_items if item.get("type") == "crack"]) if active_service == "operations_maintenance" else 0
391
  if active_service == "operations_maintenance":
392
  crack_severity_all.extend([
@@ -417,10 +387,10 @@ def monitor_feed() -> Tuple[
417
  if len(crack_severity_all) > 500:
418
  crack_severity_all.pop(0)
419
 
420
- # Resize frame and add metadata for display
421
- frame = cv2.resize(last_frame, (640, 480))
422
- cv2.putText(frame, f"Frame: {frame_count}", (10, 25), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
423
- cv2.putText(frame, f"{last_timestamp}", (10, 50), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
424
 
425
  # Generate charts for Operations Maintenance only
426
  line_chart = None
@@ -479,7 +449,7 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="green"))
479
 
480
  with gr.Row():
481
  with gr.Column(scale=3):
482
- video_output = gr.Image(label="Live Drone Feed", width=640, height=480, elem_id="live-feed")
483
  with gr.Column(scale=1):
484
  detections_output = gr.Textbox(
485
  label="Detection Metrics",
@@ -497,42 +467,71 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="green"))
497
  bar_output = gr.Plot(label="Severity Distribution (Operations Maintenance Only)")
498
 
499
  with gr.Row():
500
- captured_images = gr.Gallery(label="Detected Frames (Last 100+)", columns=4, rows=25, height="auto")
 
 
 
 
 
 
 
501
 
502
  with gr.Row():
503
  pause_btn = gr.Button("鈴革笍 Pause", variant="secondary")
504
  resume_btn = gr.Button("鈻讹笍 Resume", variant="primary")
505
- frame_slider = gr.Slider(0.05, 1.0, value=0.1, label="Frame Interval (seconds)", step=0.05)
506
 
507
  gr.HTML("""
508
  <style>
509
  #live-feed {
510
  border: 2px solid #4682B4;
511
  border-radius: 10px;
 
512
  }
513
  .gr-button-primary {
514
  background-color: #4682B4 !important;
 
 
 
 
 
515
  }
516
  .gr-button-secondary {
517
  background-color: #FF6347 !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
518
  }
519
  </style>
520
  """)
521
 
522
  def toggle_pause() -> str:
523
- """Pause the video stream."""
524
  global paused
525
  paused = True
526
  return "**Status:** 鈴革笍 Paused"
527
 
528
  def toggle_resume() -> str:
529
- """Resume the video stream."""
530
  global paused
531
  paused = False
532
  return "**Status:** 馃煝 Streaming"
533
 
534
  def set_frame_rate(val: float) -> None:
535
- """Set the frame rate for streaming."""
536
  global frame_rate
537
  frame_rate = val
538
 
@@ -545,18 +544,6 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="green"))
545
  )
546
 
547
  def update_toggles(uc_val: bool, om_val: bool, rs_val: bool, pl_val: bool) -> Tuple[str, str, str, str, str]:
548
- """
549
- Update toggle states and set the active service.
550
-
551
- Args:
552
- uc_val (bool): Under Construction toggle value.
553
- om_val (bool): Operations Maintenance toggle value.
554
- rs_val (bool): Road Safety toggle value.
555
- pl_val (bool): Plantation toggle value.
556
-
557
- Returns:
558
- Tuple[str, str, str, str, str]: Status updates for each toggle and overall status message.
559
- """
560
  active, status_message = set_active_service("toggle", uc_val, om_val, rs_val, pl_val)
561
  uc_status_val = "Enabled" if active == "under_construction" else "Disabled"
562
  om_status_val = "Enabled" if active == "operations_maintenance" else "Disabled"
@@ -578,7 +565,6 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="green"))
578
  frame_slider.change(set_frame_rate, inputs=[frame_slider])
579
 
580
  def streaming_loop():
581
- """Continuous loop to stream video and process frames."""
582
  while True:
583
  if not video_loaded:
584
  yield None, json.dumps({"error": "Video not loaded. Please upload a video file."}, indent=2), "\n".join(log_entries[-10:]), None, None, None, last_detected_images
 
5
  import json
6
  import random
7
  import logging
8
+ import numpy as np
9
  from datetime import datetime
10
  from collections import Counter
11
  from typing import Any, Dict, List, Optional, Tuple
12
+ import matplotlib.pyplot as plt
13
+ from matplotlib import font_manager
14
 
15
  # Suppress Ultralytics warning by setting a writable config directory
16
  os.environ["YOLO_CONFIG_DIR"] = "/tmp/Ultralytics"
 
54
 
55
  # Global variables
56
  paused: bool = False
57
+ frame_rate: float = 0.05 # Reduced for faster processing
58
  frame_count: int = 0
59
  log_entries: List[str] = []
60
+ crack_counts: List[int] = []
61
+ crack_severity_all: List[str] = []
62
  last_frame: Optional[np.ndarray] = None
63
  last_detections: Dict[str, Any] = {}
64
  last_timestamp: str = ""
65
  last_detected_images: List[str] = []
66
+ detected_image_set: set = set() # To avoid duplicates
67
  gps_coordinates: List[List[float]] = []
68
  video_loaded: bool = False
69
  active_service: Optional[str] = None
 
77
  os.makedirs(OUTPUT_DIR, exist_ok=True)
78
 
79
  def initialize_video(video_file: Optional[Any] = None) -> str:
 
 
 
 
 
 
 
 
 
80
  global video_loaded, log_entries
81
  release_video()
82
  video_path = DEFAULT_VIDEO_PATH
 
99
  rs_val: bool,
100
  pl_val: bool
101
  ) -> Tuple[Optional[str], str]:
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  global active_service
103
  toggles = {
104
  "under_construction": uc_val,
 
126
  return None, "No Service Category Enabled"
127
 
128
  def generate_crack_trend_chart() -> Optional[plt.Figure]:
 
 
 
 
 
 
129
  if not crack_counts:
130
  return None
131
 
132
  data = crack_counts[-50:]
133
  labels = list(range(len(data)))
134
 
135
+ fig, ax = plt.subplots(figsize=(6, 4), facecolor='#f0f4f8')
136
+ ax.plot(labels, data, color="#FF6347", linewidth=2.5, label="Cracks Over Time", marker='o', markersize=5)
137
+ ax.set_title("Crack Trend (Operations Maintenance)", fontsize=14, pad=15, fontweight='bold', color='#333333')
138
+ ax.set_xlabel("Frame", fontsize=12, color='#333333')
139
+ ax.set_ylabel("Count", fontsize=12, color='#333333')
140
  ax.set_ylim(bottom=0)
141
+ ax.grid(True, linestyle='--', alpha=0.7)
142
+ ax.set_facecolor('#ffffff')
143
+ ax.legend(frameon=True, facecolor='#ffffff', edgecolor='#333333')
144
+ for spine in ax.spines.values():
145
+ spine.set_edgecolor('#333333')
146
  plt.tight_layout()
147
  return fig
148
 
149
  def generate_crack_severity_chart() -> Optional[plt.Figure]:
 
 
 
 
 
 
150
  if not crack_severity_all:
151
  return None
152
 
 
154
  labels = list(count.keys())
155
  sizes = list(count.values())
156
 
157
+ fig, ax = plt.subplots(figsize=(6, 4), facecolor='#f0f4f8')
158
+ colors = ['#FF6347', '#4682B4', '#FFD700']
159
+ wedges, texts, autotexts = ax.pie(
160
  sizes,
161
  labels=labels,
162
+ colors=colors,
163
+ autopct='%1.1f%%',
164
+ startangle=90,
165
+ shadow=True,
166
+ textprops={'fontsize': 10, 'color': '#333333'}
167
  )
168
+ ax.set_title("Crack Severity (Operations Maintenance)", fontsize=14, pad=15, fontweight='bold', color='#333333')
169
+ for w in wedges:
170
+ w.set_edgecolor('#333333')
171
  plt.tight_layout()
172
  return fig
173
 
174
  def generate_severity_distribution_chart() -> Optional[plt.Figure]:
 
 
 
 
 
 
175
  if not crack_severity_all:
176
  return None
177
 
 
179
  labels = list(count.keys())
180
  sizes = list(count.values())
181
 
182
+ fig, ax = plt.subplots(figsize=(6, 4), facecolor='#f0f4f8')
183
+ colors = ['#FF6347', '#4682B4', '#FFD700']
184
+ bars = ax.bar(labels, sizes, color=colors, edgecolor='#333333')
185
+ ax.set_title("Severity Distribution (Operations Maintenance)", fontsize=14, pad=15, fontweight='bold', color='#333333')
186
+ ax.set_xlabel("Severity", fontsize=12, color='#333333')
187
+ ax.set_ylabel("Count", fontsize=12, color='#333333')
188
  ax.set_ylim(bottom=0)
189
+ ax.set_facecolor('#ffffff')
190
+ for bar in bars:
191
+ height = bar.get_height()
192
+ ax.text(bar.get_x() + bar.get_width()/2, height, f'{int(height)}', ha='center', va='bottom', fontsize=10, color='#333333')
193
+ for spine in ax.spines.values():
194
+ spine.set_edgecolor('#333333')
195
  plt.tight_layout()
196
  return fig
197
 
 
204
  Optional[plt.Figure],
205
  List[str]
206
  ]:
 
 
 
 
 
 
 
 
 
 
 
 
 
207
  global paused, frame_count, last_frame, last_detections, last_timestamp
208
+ global gps_coordinates, last_detected_images, video_loaded, detected_image_set
209
 
210
  if not video_loaded:
211
  log_entries.append("Cannot start streaming: Video not loaded successfully.")
 
281
  frame = shadow_results["frame"]
282
  all_detected_items.extend(shadow_dets)
283
 
284
+ # Skip thermal processing to improve speed (unless grayscale frame is detected)
285
  if len(frame.shape) == 2:
286
  thermal_results = process_thermal(frame)
287
  thermal_dets = thermal_results["detections"]
 
293
  logging.error(f"Processing error in {active_service}: {str(e)}")
294
  all_detected_items = []
295
 
296
+ # Save temporary image for display (lower quality for speed)
297
  try:
298
+ cv2.imwrite(TEMP_IMAGE_PATH, frame, [int(cv2.IMWRITE_JPEG_QUALITY), 80])
299
  except Exception as e:
300
  log_entries.append(f"Error saving temp image: {str(e)}")
301
  logging.error(f"Error saving temp image: {str(e)}")
 
303
  # Update detection metrics
304
  metrics = update_metrics(all_detected_items)
305
 
306
+ # Generate GPS coordinates
307
  gps_coord = [17.385044 + random.uniform(-0.001, 0.001), 78.486671 + frame_count * 0.0001]
308
  gps_coordinates.append(gps_coord)
309
 
310
+ # Save frame if detections are present (avoid duplicates)
311
  detection_types = {item.get("type") for item in all_detected_items if "type" in item}
312
  if detection_types:
313
  try:
314
+ # Create a unique identifier for the frame based on detections
315
+ detection_key = tuple(sorted([(item["type"], tuple(item["coordinates"])) for item in all_detected_items]))
316
+ detection_key_str = str(detection_key)
317
+ if detection_key_str not in detected_image_set:
318
+ captured_frame_path = os.path.join(CAPTURED_FRAMES_DIR, f"detected_{frame_count}.jpg")
319
+ cv2.imwrite(captured_frame_path, frame)
320
+ last_detected_images.insert(0, captured_frame_path) # Add to the beginning (newest first)
321
+ detected_image_set.add(detection_key_str)
322
+ if len(last_detected_images) > 100:
323
+ old_image = last_detected_images.pop()
324
+ detected_image_set.discard(str(old_image))
325
  except Exception as e:
326
  log_entries.append(f"Error saving captured frame: {str(e)}")
327
  logging.error(f"Error saving captured frame: {str(e)}")
 
356
  last_frame = frame.copy()
357
  last_detections = metrics
358
 
359
+ # Track cracks for metrics (Operations Maintenance only)
360
  crack_detected = len([item for item in all_detected_items if item.get("type") == "crack"]) if active_service == "operations_maintenance" else 0
361
  if active_service == "operations_maintenance":
362
  crack_severity_all.extend([
 
387
  if len(crack_severity_all) > 500:
388
  crack_severity_all.pop(0)
389
 
390
+ # Resize frame for display (smaller size for speed)
391
+ frame = cv2.resize(last_frame, (320, 240))
392
+ cv2.putText(frame, f"Frame: {frame_count}", (10, 20), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1)
393
+ cv2.putText(frame, f"{last_timestamp}", (10, 40), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1)
394
 
395
  # Generate charts for Operations Maintenance only
396
  line_chart = None
 
449
 
450
  with gr.Row():
451
  with gr.Column(scale=3):
452
+ video_output = gr.Image(label="Live Drone Feed", width=320, height=240, elem_id="live-feed")
453
  with gr.Column(scale=1):
454
  detections_output = gr.Textbox(
455
  label="Detection Metrics",
 
467
  bar_output = gr.Plot(label="Severity Distribution (Operations Maintenance Only)")
468
 
469
  with gr.Row():
470
+ captured_images = gr.Gallery(
471
+ label="Detected Frames (Last 100+)",
472
+ columns=4,
473
+ rows=5,
474
+ height="auto",
475
+ object_fit="contain",
476
+ preview=True
477
+ )
478
 
479
  with gr.Row():
480
  pause_btn = gr.Button("鈴革笍 Pause", variant="secondary")
481
  resume_btn = gr.Button("鈻讹笍 Resume", variant="primary")
482
+ frame_slider = gr.Slider(0.01, 1.0, value=0.05, label="Frame Interval (seconds)", step=0.01)
483
 
484
  gr.HTML("""
485
  <style>
486
  #live-feed {
487
  border: 2px solid #4682B4;
488
  border-radius: 10px;
489
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
490
  }
491
  .gr-button-primary {
492
  background-color: #4682B4 !important;
493
+ border-radius: 5px;
494
+ transition: background-color 0.3s ease;
495
+ }
496
+ .gr-button-primary:hover {
497
+ background-color: #5a9bd4 !important;
498
  }
499
  .gr-button-secondary {
500
  background-color: #FF6347 !important;
501
+ border-radius: 5px;
502
+ transition: background-color 0.3s ease;
503
+ }
504
+ .gr-button-secondary:hover {
505
+ background-color: #ff826b !important;
506
+ }
507
+ .gr-plot {
508
+ background-color: #f0f4f8;
509
+ border-radius: 10px;
510
+ padding: 10px;
511
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
512
+ }
513
+ .gr-gallery img {
514
+ border: 1px solid #4682B4;
515
+ border-radius: 5px;
516
+ transition: transform 0.3s ease;
517
+ }
518
+ .gr-gallery img:hover {
519
+ transform: scale(1.05);
520
  }
521
  </style>
522
  """)
523
 
524
  def toggle_pause() -> str:
 
525
  global paused
526
  paused = True
527
  return "**Status:** 鈴革笍 Paused"
528
 
529
  def toggle_resume() -> str:
 
530
  global paused
531
  paused = False
532
  return "**Status:** 馃煝 Streaming"
533
 
534
  def set_frame_rate(val: float) -> None:
 
535
  global frame_rate
536
  frame_rate = val
537
 
 
544
  )
545
 
546
  def update_toggles(uc_val: bool, om_val: bool, rs_val: bool, pl_val: bool) -> Tuple[str, str, str, str, str]:
 
 
 
 
 
 
 
 
 
 
 
 
547
  active, status_message = set_active_service("toggle", uc_val, om_val, rs_val, pl_val)
548
  uc_status_val = "Enabled" if active == "under_construction" else "Disabled"
549
  om_status_val = "Enabled" if active == "operations_maintenance" else "Disabled"
 
565
  frame_slider.change(set_frame_rate, inputs=[frame_slider])
566
 
567
  def streaming_loop():
 
568
  while True:
569
  if not video_loaded:
570
  yield None, json.dumps({"error": "Video not loaded. Please upload a video file."}, indent=2), "\n".join(log_entries[-10:]), None, None, None, last_detected_images