VisionLanguageGroup commited on
Commit
88f9ed6
·
1 Parent(s): 5fb70b0

slide bar

Browse files
Files changed (1) hide show
  1. app.py +154 -75
app.py CHANGED
@@ -199,12 +199,106 @@ def colorize_mask(mask: np.ndarray, num_colors: int = 512) -> np.ndarray:
199
  color_idx = mask % num_colors
200
  return palette_arr[color_idx]
201
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
202
  # @spaces.GPU
203
  def segment_with_choice(use_box_choice, annot_value, overlay_alpha):
204
  """Segmentation handler - supports bounding box, returns colorized overlay and original mask path"""
205
  if annot_value is None or len(annot_value) < 1:
206
  print("❌ No annotation input")
207
- return None, None
208
 
209
  img_path = annot_value[0]
210
  bboxes = annot_value[1] if len(annot_value) > 1 else []
@@ -223,7 +317,7 @@ def segment_with_choice(use_box_choice, annot_value, overlay_alpha):
223
  print("📏 mask shape:", mask.shape, "dtype:", mask.dtype, "unique:", np.unique(mask))
224
  except Exception as e:
225
  print(f"❌ Inference failed: {str(e)}")
226
- return None, None
227
 
228
  temp_mask_file = tempfile.NamedTemporaryFile(delete=False, suffix=".tif")
229
  mask_img = Image.fromarray(mask.astype(np.uint16))
@@ -235,7 +329,7 @@ def segment_with_choice(use_box_choice, annot_value, overlay_alpha):
235
  print("📷 Image mode:", img.mode, "size:", img.size)
236
  except Exception as e:
237
  print(f"❌ Failed to open image: {e}")
238
- return None, None
239
 
240
  try:
241
  img_rgb = img.convert("RGB").resize(mask.shape[::-1], resample=Image.BILINEAR)
@@ -244,7 +338,7 @@ def segment_with_choice(use_box_choice, annot_value, overlay_alpha):
244
  img_np = img_np / 255.0
245
  except Exception as e:
246
  print(f"❌ Error in image conversion/resizing: {e}")
247
- return None, None
248
 
249
  mask_np = np.array(mask)
250
  inst_mask = mask_np.astype(np.int32)
@@ -254,35 +348,18 @@ def segment_with_choice(use_box_choice, annot_value, overlay_alpha):
254
 
255
  if num_instances == 0:
256
  print("⚠️ No instance found, returning dummy red image")
257
- return Image.new("RGB", mask.shape[::-1], (255, 0, 0)), None
258
-
259
- overlay = img_np.copy()
260
- alpha = float(np.clip(overlay_alpha, 0.0, 1.0))
261
-
262
- for inst_id in np.unique(inst_mask):
263
- if inst_id == 0:
264
- continue
265
- binary_mask = (inst_mask == inst_id).astype(np.uint8)
266
- color = get_well_spaced_color(inst_id)
267
- overlay[binary_mask == 1] = (1 - alpha) * overlay[binary_mask == 1] + alpha * color
268
-
269
- contours = measure.find_contours(binary_mask, 0.5)
270
- for contour in contours:
271
- contour = contour.astype(np.int32)
272
- valid_y = np.clip(contour[:, 0], 0, overlay.shape[0] - 1)
273
- valid_x = np.clip(contour[:, 1], 0, overlay.shape[1] - 1)
274
- overlay[valid_y, valid_x] = [1.0, 1.0, 0.0] # 黄色轮廓
275
-
276
- overlay = np.clip(overlay * 255.0, 0, 255).astype(np.uint8)
277
 
278
- return Image.fromarray(overlay), temp_mask_file.name
 
 
279
 
280
 
281
  # @spaces.GPU
282
  def count_cells_handler(use_box_choice, annot_value, overlay_alpha):
283
  """Counting handler - supports bounding box, returns only density map"""
284
  if annot_value is None or len(annot_value) < 1:
285
- return None, "⚠️ Please provide an image."
286
 
287
  image_path = annot_value[0]
288
  bboxes = annot_value[1] if len(annot_value) > 1 else []
@@ -307,7 +384,7 @@ def count_cells_handler(use_box_choice, annot_value, overlay_alpha):
307
  )
308
 
309
  if 'error' in result:
310
- return None, f"❌ Counting failed: {result['error']}"
311
 
312
  count = result['count']
313
  density_map = result['density_map']
@@ -321,7 +398,7 @@ def count_cells_handler(use_box_choice, annot_value, overlay_alpha):
321
  print("📷 Image mode:", img.mode, "size:", img.size)
322
  except Exception as e:
323
  print(f"❌ Failed to open image: {e}")
324
- return None, None
325
 
326
  try:
327
  img_rgb = img.convert("RGB").resize(density_map.shape[::-1], resample=Image.BILINEAR)
@@ -331,31 +408,14 @@ def count_cells_handler(use_box_choice, annot_value, overlay_alpha):
331
  img_np = img_np / 255.0
332
  except Exception as e:
333
  print(f"❌ Error in image conversion/resizing: {e}")
334
- return None, None
335
 
336
 
337
  density_normalized = density_map.copy()
338
  if density_normalized.max() > 0:
339
  density_normalized = (density_normalized - density_normalized.min()) / (density_normalized.max() - density_normalized.min())
340
 
341
- cmap = cm.get_cmap("jet")
342
- alpha = float(np.clip(overlay_alpha, 0.0, 1.0))
343
- density_colored = cmap(density_normalized)[:, :, :3] # RGB only, ignore alpha
344
-
345
- overlay = img_np.copy()
346
-
347
- threshold = 0.01 # Only overlay where density > 1% of max
348
- significant_mask = density_normalized > threshold
349
-
350
- overlay[significant_mask] = (1 - alpha) * overlay[significant_mask] + alpha * density_colored[significant_mask]
351
-
352
- # Clip and convert to uint8
353
- overlay = np.clip(overlay * 255.0, 0, 255).astype(np.uint8)
354
-
355
-
356
-
357
-
358
-
359
  result_text = f"✅ Detected {round(count)} objects"
360
  if use_box_choice == "Yes" and box_array:
361
  result_text += f"\n📦 Using bounding box: {box_array}"
@@ -363,14 +423,15 @@ def count_cells_handler(use_box_choice, annot_value, overlay_alpha):
363
 
364
  print(f"✅ Counting done - Count: {count:.1f}")
365
 
366
- return Image.fromarray(overlay), temp_density_file.name, result_text
 
367
 
368
 
369
  except Exception as e:
370
  print(f"❌ Counting error: {e}")
371
  import traceback
372
  traceback.print_exc()
373
- return None, f"❌ Counting failed: {str(e)}"
374
 
375
 
376
  def find_tif_dir(root_dir):
@@ -664,7 +725,7 @@ def create_tracking_visualization(tif_dir, output_dir, valid_tif_files, overlay_
664
  return valid_tif_files[0]
665
 
666
  # @spaces.GPU
667
- def track_video_handler(use_box_choice, first_frame_annot, zip_file_obj, overlay_alpha):
668
  """
669
  Tracking handler - processes a ZIP of TIF frames, supports bounding box, returns visualization and results ZIP
670
 
@@ -678,8 +739,9 @@ def track_video_handler(use_box_choice, first_frame_annot, zip_file_obj, overlay
678
  Uploaded ZIP file containing TIF sequence
679
  """
680
  if zip_file_obj is None:
681
- return None, "⚠️ Please upload a ZIP file containing video frames (.zip)", None, None
682
 
 
683
  temp_dir = None
684
  output_temp_dir = None
685
 
@@ -727,7 +789,7 @@ def track_video_handler(use_box_choice, first_frame_annot, zip_file_obj, overlay
727
  tif_dir = find_valid_tif_dir(temp_dir)
728
 
729
  if tif_dir is None:
730
- return None, "❌ Did not find valid TIF directory", None, None
731
 
732
  # Validate TIFF files
733
  tif_files = natsorted(glob(os.path.join(tif_dir, "*.tif")) +
@@ -736,7 +798,7 @@ def track_video_handler(use_box_choice, first_frame_annot, zip_file_obj, overlay
736
  if not os.path.basename(f).startswith('._') and is_valid_tiff(f)]
737
 
738
  if len(valid_tif_files) == 0:
739
- return None, "❌ Did not find valid TIF files", None, None
740
 
741
  print(f"📈 Using {len(valid_tif_files)} TIF files")
742
 
@@ -757,7 +819,7 @@ def track_video_handler(use_box_choice, first_frame_annot, zip_file_obj, overlay
757
  )
758
 
759
  if 'error' in result:
760
- return None, f"❌ Tracking failed: {result['error']}", None, None
761
 
762
  # Create visualization video of tracked objects
763
  print("\n🎬 Creating tracking visualization...")
@@ -805,18 +867,17 @@ def track_video_handler(use_box_choice, first_frame_annot, zip_file_obj, overlay
805
 
806
  print(f"\n✅ Tracking completed")
807
 
808
- # Clean up input temp directory (keep output temp for download)
809
- if temp_dir:
810
- try:
811
- shutil.rmtree(temp_dir)
812
- print(f"🗑️ Cleared input temp directory")
813
- except:
814
- pass
815
-
816
- return results_zip, result_text, gr.update(visible=True), tracking_video
817
 
818
  except zipfile.BadZipFile:
819
- return None, "❌ Not a valid ZIP file", None, None
820
  except Exception as e:
821
  import traceback
822
  traceback.print_exc()
@@ -828,7 +889,7 @@ def track_video_handler(use_box_choice, first_frame_annot, zip_file_obj, overlay
828
  shutil.rmtree(d)
829
  except:
830
  pass
831
- return None, f"❌ Tracking failed: {str(e)}", None, None
832
 
833
 
834
 
@@ -890,6 +951,9 @@ with gr.Blocks(
890
  # 全局状态
891
  current_query_id = gr.State(str(uuid.uuid4()))
892
  user_uploaded_examples = gr.State(example_images_seg.copy())
 
 
 
893
 
894
  with gr.Tabs():
895
  # ===== Tab 1: Segmentation =====
@@ -992,14 +1056,19 @@ with gr.Blocks(
992
  run_seg_btn.click(
993
  fn=segment_with_choice,
994
  inputs=[use_box_radio, annotator, seg_alpha_slider],
995
- outputs=[seg_output, download_mask_btn]
 
 
 
 
 
996
  )
997
 
998
  # click event for clear button
999
  clear_btn.click(
1000
- fn=lambda: None,
1001
  inputs=None,
1002
- outputs=annotator
1003
  )
1004
 
1005
  # init Gallery with example images
@@ -1220,14 +1289,19 @@ with gr.Blocks(
1220
  count_btn.click(
1221
  fn=count_cells_handler,
1222
  inputs=[count_use_box_radio, count_annotator, count_alpha_slider],
1223
- outputs=[count_output, download_density_btn, count_status]
 
 
 
 
 
1224
  )
1225
 
1226
  # Clear selection
1227
  clear_btn.click(
1228
- fn=lambda: None,
1229
  inputs=None,
1230
- outputs=count_annotator
1231
  )
1232
 
1233
  # Submit feedback
@@ -1548,15 +1622,20 @@ with gr.Blocks(
1548
  # Run tracking
1549
  track_btn.click(
1550
  fn=track_video_handler,
1551
- inputs=[track_use_box_radio, track_first_frame_annotator, track_zip_upload, track_alpha_slider],
1552
- outputs=[track_download, track_output, track_download, track_first_frame_preview]
 
 
 
 
 
1553
  )
1554
 
1555
  # Clear selection
1556
  clear_btn.click(
1557
- fn=lambda: None,
1558
  inputs=None,
1559
- outputs=track_first_frame_annotator
1560
  )
1561
 
1562
  # Submit feedback
 
199
  color_idx = mask % num_colors
200
  return palette_arr[color_idx]
201
 
202
+
203
+ def render_seg_overlay(img_np, inst_mask, overlay_alpha):
204
+ """Render segmentation overlay from cached image/mask."""
205
+ if img_np is None or inst_mask is None:
206
+ return None
207
+
208
+ overlay = img_np.copy()
209
+ alpha = float(np.clip(overlay_alpha, 0.0, 1.0))
210
+
211
+ for inst_id in np.unique(inst_mask):
212
+ if inst_id == 0:
213
+ continue
214
+ binary_mask = (inst_mask == inst_id).astype(np.uint8)
215
+ color = get_well_spaced_color(inst_id)
216
+ overlay[binary_mask == 1] = (1 - alpha) * overlay[binary_mask == 1] + alpha * color
217
+
218
+ contours = measure.find_contours(binary_mask, 0.5)
219
+ for contour in contours:
220
+ contour = contour.astype(np.int32)
221
+ valid_y = np.clip(contour[:, 0], 0, overlay.shape[0] - 1)
222
+ valid_x = np.clip(contour[:, 1], 0, overlay.shape[1] - 1)
223
+ overlay[valid_y, valid_x] = [1.0, 1.0, 0.0]
224
+
225
+ overlay = np.clip(overlay * 255.0, 0, 255).astype(np.uint8)
226
+ return Image.fromarray(overlay)
227
+
228
+
229
+ def render_count_overlay(img_np, density_normalized, overlay_alpha):
230
+ """Render counting heatmap overlay from cached image/density."""
231
+ if img_np is None or density_normalized is None:
232
+ return None
233
+
234
+ alpha = float(np.clip(overlay_alpha, 0.0, 1.0))
235
+ cmap = cm.get_cmap("jet")
236
+ density_colored = cmap(density_normalized)[:, :, :3]
237
+
238
+ overlay = img_np.copy()
239
+ threshold = 0.01
240
+ significant_mask = density_normalized > threshold
241
+ overlay[significant_mask] = (1 - alpha) * overlay[significant_mask] + alpha * density_colored[significant_mask]
242
+ overlay = np.clip(overlay * 255.0, 0, 255).astype(np.uint8)
243
+ return Image.fromarray(overlay)
244
+
245
+
246
+ def update_seg_overlay_alpha(overlay_alpha, seg_vis_cache):
247
+ """Live update segmentation visualization without rerunning inference."""
248
+ if not seg_vis_cache:
249
+ return None
250
+ return render_seg_overlay(seg_vis_cache.get("img_np"), seg_vis_cache.get("inst_mask"), overlay_alpha)
251
+
252
+
253
+ def update_count_overlay_alpha(overlay_alpha, count_vis_cache):
254
+ """Live update counting visualization without rerunning inference."""
255
+ if not count_vis_cache:
256
+ return None
257
+ return render_count_overlay(count_vis_cache.get("img_np"), count_vis_cache.get("density_normalized"), overlay_alpha)
258
+
259
+
260
+ def update_tracking_overlay_alpha(overlay_alpha, track_vis_cache):
261
+ """Regenerate tracking visualization at new opacity using cached outputs."""
262
+ if not track_vis_cache:
263
+ return None
264
+
265
+ tif_dir = track_vis_cache.get("tif_dir")
266
+ output_dir = track_vis_cache.get("output_dir")
267
+ valid_tif_files = track_vis_cache.get("valid_tif_files")
268
+ if not tif_dir or not output_dir or not valid_tif_files:
269
+ return None
270
+
271
+ try:
272
+ return create_tracking_visualization(
273
+ tif_dir=tif_dir,
274
+ output_dir=output_dir,
275
+ valid_tif_files=valid_tif_files,
276
+ overlay_alpha=overlay_alpha
277
+ )
278
+ except Exception as e:
279
+ print(f"⚠️ Failed to update tracking opacity: {e}")
280
+ return None
281
+
282
+
283
+ def cleanup_tracking_cache(track_vis_cache):
284
+ """Delete cached tracking temp directories from the previous run."""
285
+ if not track_vis_cache:
286
+ return
287
+ for key in ["input_temp_dir", "output_dir"]:
288
+ path = track_vis_cache.get(key)
289
+ if path and os.path.isdir(path):
290
+ try:
291
+ shutil.rmtree(path)
292
+ except Exception:
293
+ pass
294
+
295
+
296
  # @spaces.GPU
297
  def segment_with_choice(use_box_choice, annot_value, overlay_alpha):
298
  """Segmentation handler - supports bounding box, returns colorized overlay and original mask path"""
299
  if annot_value is None or len(annot_value) < 1:
300
  print("❌ No annotation input")
301
+ return None, None, {}
302
 
303
  img_path = annot_value[0]
304
  bboxes = annot_value[1] if len(annot_value) > 1 else []
 
317
  print("📏 mask shape:", mask.shape, "dtype:", mask.dtype, "unique:", np.unique(mask))
318
  except Exception as e:
319
  print(f"❌ Inference failed: {str(e)}")
320
+ return None, None, {}
321
 
322
  temp_mask_file = tempfile.NamedTemporaryFile(delete=False, suffix=".tif")
323
  mask_img = Image.fromarray(mask.astype(np.uint16))
 
329
  print("📷 Image mode:", img.mode, "size:", img.size)
330
  except Exception as e:
331
  print(f"❌ Failed to open image: {e}")
332
+ return None, None, {}
333
 
334
  try:
335
  img_rgb = img.convert("RGB").resize(mask.shape[::-1], resample=Image.BILINEAR)
 
338
  img_np = img_np / 255.0
339
  except Exception as e:
340
  print(f"❌ Error in image conversion/resizing: {e}")
341
+ return None, None, {}
342
 
343
  mask_np = np.array(mask)
344
  inst_mask = mask_np.astype(np.int32)
 
348
 
349
  if num_instances == 0:
350
  print("⚠️ No instance found, returning dummy red image")
351
+ return Image.new("RGB", mask.shape[::-1], (255, 0, 0)), None, {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
352
 
353
+ overlay_img = render_seg_overlay(img_np, inst_mask, overlay_alpha)
354
+ seg_vis_cache = {"img_np": img_np, "inst_mask": inst_mask}
355
+ return overlay_img, temp_mask_file.name, seg_vis_cache
356
 
357
 
358
  # @spaces.GPU
359
  def count_cells_handler(use_box_choice, annot_value, overlay_alpha):
360
  """Counting handler - supports bounding box, returns only density map"""
361
  if annot_value is None or len(annot_value) < 1:
362
+ return None, None, "⚠️ Please provide an image.", {}
363
 
364
  image_path = annot_value[0]
365
  bboxes = annot_value[1] if len(annot_value) > 1 else []
 
384
  )
385
 
386
  if 'error' in result:
387
+ return None, None, f"❌ Counting failed: {result['error']}", {}
388
 
389
  count = result['count']
390
  density_map = result['density_map']
 
398
  print("📷 Image mode:", img.mode, "size:", img.size)
399
  except Exception as e:
400
  print(f"❌ Failed to open image: {e}")
401
+ return None, None, f"❌ Failed to open image: {str(e)}", {}
402
 
403
  try:
404
  img_rgb = img.convert("RGB").resize(density_map.shape[::-1], resample=Image.BILINEAR)
 
408
  img_np = img_np / 255.0
409
  except Exception as e:
410
  print(f"❌ Error in image conversion/resizing: {e}")
411
+ return None, None, f"❌ Error in image conversion/resizing: {str(e)}", {}
412
 
413
 
414
  density_normalized = density_map.copy()
415
  if density_normalized.max() > 0:
416
  density_normalized = (density_normalized - density_normalized.min()) / (density_normalized.max() - density_normalized.min())
417
 
418
+ overlay_img = render_count_overlay(img_np, density_normalized, overlay_alpha)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
419
  result_text = f"✅ Detected {round(count)} objects"
420
  if use_box_choice == "Yes" and box_array:
421
  result_text += f"\n📦 Using bounding box: {box_array}"
 
423
 
424
  print(f"✅ Counting done - Count: {count:.1f}")
425
 
426
+ count_vis_cache = {"img_np": img_np, "density_normalized": density_normalized}
427
+ return overlay_img, temp_density_file.name, result_text, count_vis_cache
428
 
429
 
430
  except Exception as e:
431
  print(f"❌ Counting error: {e}")
432
  import traceback
433
  traceback.print_exc()
434
+ return None, None, f"❌ Counting failed: {str(e)}", {}
435
 
436
 
437
  def find_tif_dir(root_dir):
 
725
  return valid_tif_files[0]
726
 
727
  # @spaces.GPU
728
+ def track_video_handler(use_box_choice, first_frame_annot, zip_file_obj, overlay_alpha, prev_track_vis_cache):
729
  """
730
  Tracking handler - processes a ZIP of TIF frames, supports bounding box, returns visualization and results ZIP
731
 
 
739
  Uploaded ZIP file containing TIF sequence
740
  """
741
  if zip_file_obj is None:
742
+ return None, "⚠️ Please upload a ZIP file containing video frames (.zip)", None, None, {}
743
 
744
+ cleanup_tracking_cache(prev_track_vis_cache)
745
  temp_dir = None
746
  output_temp_dir = None
747
 
 
789
  tif_dir = find_valid_tif_dir(temp_dir)
790
 
791
  if tif_dir is None:
792
+ return None, "❌ Did not find valid TIF directory", None, None, {}
793
 
794
  # Validate TIFF files
795
  tif_files = natsorted(glob(os.path.join(tif_dir, "*.tif")) +
 
798
  if not os.path.basename(f).startswith('._') and is_valid_tiff(f)]
799
 
800
  if len(valid_tif_files) == 0:
801
+ return None, "❌ Did not find valid TIF files", None, None, {}
802
 
803
  print(f"📈 Using {len(valid_tif_files)} TIF files")
804
 
 
819
  )
820
 
821
  if 'error' in result:
822
+ return None, f"❌ Tracking failed: {result['error']}", None, None, {}
823
 
824
  # Create visualization video of tracked objects
825
  print("\n🎬 Creating tracking visualization...")
 
867
 
868
  print(f"\n✅ Tracking completed")
869
 
870
+ track_vis_cache = {
871
+ "tif_dir": tif_dir,
872
+ "valid_tif_files": valid_tif_files,
873
+ "output_dir": output_temp_dir,
874
+ "input_temp_dir": temp_dir,
875
+ }
876
+
877
+ return results_zip, result_text, gr.update(visible=True), tracking_video, track_vis_cache
 
878
 
879
  except zipfile.BadZipFile:
880
+ return None, "❌ Not a valid ZIP file", None, None, {}
881
  except Exception as e:
882
  import traceback
883
  traceback.print_exc()
 
889
  shutil.rmtree(d)
890
  except:
891
  pass
892
+ return None, f"❌ Tracking failed: {str(e)}", None, None, {}
893
 
894
 
895
 
 
951
  # 全局状态
952
  current_query_id = gr.State(str(uuid.uuid4()))
953
  user_uploaded_examples = gr.State(example_images_seg.copy())
954
+ seg_vis_state = gr.State({})
955
+ count_vis_state = gr.State({})
956
+ track_vis_state = gr.State({})
957
 
958
  with gr.Tabs():
959
  # ===== Tab 1: Segmentation =====
 
1056
  run_seg_btn.click(
1057
  fn=segment_with_choice,
1058
  inputs=[use_box_radio, annotator, seg_alpha_slider],
1059
+ outputs=[seg_output, download_mask_btn, seg_vis_state]
1060
+ )
1061
+ seg_alpha_slider.input(
1062
+ fn=update_seg_overlay_alpha,
1063
+ inputs=[seg_alpha_slider, seg_vis_state],
1064
+ outputs=seg_output
1065
  )
1066
 
1067
  # click event for clear button
1068
  clear_btn.click(
1069
+ fn=lambda: (None, {}),
1070
  inputs=None,
1071
+ outputs=[annotator, seg_vis_state]
1072
  )
1073
 
1074
  # init Gallery with example images
 
1289
  count_btn.click(
1290
  fn=count_cells_handler,
1291
  inputs=[count_use_box_radio, count_annotator, count_alpha_slider],
1292
+ outputs=[count_output, download_density_btn, count_status, count_vis_state]
1293
+ )
1294
+ count_alpha_slider.input(
1295
+ fn=update_count_overlay_alpha,
1296
+ inputs=[count_alpha_slider, count_vis_state],
1297
+ outputs=count_output
1298
  )
1299
 
1300
  # Clear selection
1301
  clear_btn.click(
1302
+ fn=lambda: (None, {}),
1303
  inputs=None,
1304
+ outputs=[count_annotator, count_vis_state]
1305
  )
1306
 
1307
  # Submit feedback
 
1622
  # Run tracking
1623
  track_btn.click(
1624
  fn=track_video_handler,
1625
+ inputs=[track_use_box_radio, track_first_frame_annotator, track_zip_upload, track_alpha_slider, track_vis_state],
1626
+ outputs=[track_download, track_output, track_download, track_first_frame_preview, track_vis_state]
1627
+ )
1628
+ track_alpha_slider.change(
1629
+ fn=update_tracking_overlay_alpha,
1630
+ inputs=[track_alpha_slider, track_vis_state],
1631
+ outputs=track_first_frame_preview
1632
  )
1633
 
1634
  # Clear selection
1635
  clear_btn.click(
1636
+ fn=lambda: (None, {}),
1637
  inputs=None,
1638
+ outputs=[track_first_frame_annotator, track_vis_state]
1639
  )
1640
 
1641
  # Submit feedback