bernardo-de-almeida commited on
Commit
805e5dd
·
1 Parent(s): 40c2708

fix: search tracks

Browse files
Files changed (1) hide show
  1. app.py +184 -23
app.py CHANGED
@@ -262,16 +262,44 @@ def _rank_search(query: str, names: list[str], limit: int) -> list[str]:
262
  return out[:limit]
263
 
264
 
265
- def search_bigwigs(species: str, query: str):
266
  """Search BigWig tracks and return formatted display names."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
267
  names = _get_bigwig_names(species)
268
  # Search in both track IDs and display names
269
  metadata = _load_track_metadata()
270
- query_lower = query.lower()
 
 
 
 
 
 
 
 
 
 
 
271
 
272
  # Build list of (display_format, track_id) tuples for searching
273
  track_display_pairs = []
274
  for track_id in names:
 
 
 
275
  display_name = metadata.get(track_id, track_id)
276
  display_format = _format_track_for_display(track_id)
277
  track_display_pairs.append((display_format, track_id, display_name))
@@ -284,9 +312,12 @@ def search_bigwigs(species: str, query: str):
284
  query_lower in display_format.lower()):
285
  matching.append(display_format)
286
 
287
- # Limit results
288
  results = matching[:SEARCH_MAX_RESULTS]
289
- return gr.update(choices=results, value=[])
 
 
 
290
 
291
 
292
  def add_selected(current_selected: list[str], to_add: list[str]):
@@ -304,13 +335,16 @@ def add_selected(current_selected: list[str], to_add: list[str]):
304
  cur_ids.append(tid)
305
  cur_display.append(_format_track_for_display(tid))
306
 
307
- return gr.update(choices=cur_display, value=cur_display) # show + keep all checked
 
308
 
309
 
310
  def remove_selected(current_selected: list[str], to_remove: list[str]):
311
  """Remove tracks from selected list."""
312
  cur = [x for x in (current_selected or []) if x not in set(to_remove or [])]
313
- return gr.update(choices=cur, value=cur)
 
 
314
 
315
 
316
  def update_coords_on_species_change(species: str):
@@ -324,17 +358,25 @@ def reset_on_species_change(species: str):
324
  track_ids = _get_bigwig_names(species) # warms cache if available
325
  # Format available tracks for display
326
  formatted_tracks = [_format_track_for_display(tid) for tid in track_ids]
 
 
 
 
 
 
 
 
327
  return (
328
  gr.update(value=""), # query textbox
329
  gr.update(choices=[], value=[]), # results list
330
- gr.update(choices=formatted_tracks, value=[]), # selected list (with formatted names)
331
  )
332
  except (ValueError, AttributeError):
333
  # Species doesn't have bigwigs, that's okay
334
  return (
335
  gr.update(value=""), # query textbox
336
  gr.update(choices=[], value=[]), # results list
337
- gr.update(choices=[], value=[]), # selected list
338
  )
339
 
340
 
@@ -375,11 +417,11 @@ def predict(
375
  if not seq or not seq.strip():
376
  raise gr.Error("seq is required when use_coords=False")
377
  inputs = {"seq": seq.strip(), "species": species}
378
-
379
  # Verify species is in inputs before calling pipeline
380
  if "species" not in inputs:
381
  raise gr.Error(f"Internal error: species not found in inputs dict. Inputs: {list(inputs.keys())}")
382
-
383
  out = pipe(inputs)
384
 
385
  bw_names = out.bigwig_track_names or []
@@ -715,9 +757,9 @@ _init_bed_selected = [elem for elem in DEFAULT_BED_ELEMENTS if elem in _init_bed
715
  # Default coordinates per species
716
  DEFAULT_COORDS = {
717
  "human": {"chrom": "chr19", "start": 6_700_000, "end": 6_831_072},
718
- "mouse": {"chrom": "chr1", "start": 0, "end": 32_768},
719
- "drosophila_melanogaster": {"chrom": "chr2L", "start": 0, "end": 32_768},
720
- "arabidopsis_thaliana": {"chrom": "chr1", "start": 0, "end": 32_768},
721
  }
722
 
723
  # Get default coordinates for default species
@@ -824,7 +866,7 @@ with gr.Blocks(title="NTv3 Tracks Demo") as demo:
824
 
825
  # Get all available species from the pipeline
826
  all_species = sorted(ASSEMBLY_TO_SPECIES.values())
827
-
828
  with gr.Row():
829
  species = gr.Dropdown(
830
  choices=all_species,
@@ -909,6 +951,7 @@ with gr.Blocks(title="NTv3 Tracks Demo") as demo:
909
  choices=_init_bigwig_selected,
910
  value=_init_bigwig_selected,
911
  label="Selected functional tracks (used for prediction)",
 
912
  )
913
 
914
  bigwig_query = gr.Textbox(
@@ -954,23 +997,132 @@ with gr.Blocks(title="NTv3 Tracks Demo") as demo:
954
 
955
  # --- wiring (live search + auto-add) ---
956
 
957
- # Live search on every keystroke
958
  bigwig_query.input(
959
  fn=search_bigwigs,
960
- inputs=[species, bigwig_query],
961
- outputs=[bigwig_results],
 
 
 
 
 
 
962
  )
963
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
964
  # Auto-add: whenever user checks items in results, add them to Selected,
965
  # then clear results selection (so it feels like "click to add")
966
- def _auto_add(selected_now: list[str], results_checked: list[str]):
967
  upd = add_selected(selected_now, results_checked) # reuses your function
968
- # clear checks in results, keep choices
969
- return upd, gr.update(value=[])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
970
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
971
  bigwig_results.change(
972
- fn=_auto_add,
973
- inputs=[bigwig_selected, bigwig_results],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
974
  outputs=[bigwig_selected, bigwig_results],
975
  )
976
 
@@ -1011,10 +1163,19 @@ with gr.Blocks(title="NTv3 Tracks Demo") as demo:
1011
  try:
1012
  track_ids = _get_bigwig_names(species)
1013
  formatted_tracks = [_format_track_for_display(tid) for tid in track_ids]
 
 
 
 
 
1014
  except:
1015
  formatted_tracks = []
 
 
1016
  else:
1017
  formatted_tracks = []
 
 
1018
 
1019
  return (
1020
  gr.update(visible=show_coords, value=coords["chrom"]),
@@ -1023,7 +1184,7 @@ with gr.Blocks(title="NTv3 Tracks Demo") as demo:
1023
  gr.update(value=is_supported, visible=is_supported), # Show/hide and enable use_coords only if supported
1024
  gr.update(visible=show_coords), # Show/hide the row
1025
  gr.update(visible=not has_bigwigs), # Show "no tracks" message if no bigwigs
1026
- gr.update(visible=has_bigwigs, choices=formatted_tracks, value=[]), # Show bigwig selection if available
1027
  gr.update(visible=has_bigwigs), # Show bigwig query if available
1028
  gr.update(visible=has_bigwigs), # Show bigwig results if available
1029
  gr.update(visible=has_bigwigs), # Show bigwig buttons if available
 
262
  return out[:limit]
263
 
264
 
265
+ def search_bigwigs(species: str, query: str, current_selected: list[str]):
266
  """Search BigWig tracks and return formatted display names."""
267
+ # Handle None or empty query
268
+ if query is None:
269
+ query = ""
270
+ query_stripped = query.strip()
271
+
272
+ # If query is empty, return empty results immediately (don't show all tracks)
273
+ if not query_stripped:
274
+ displayed_selected = current_selected or []
275
+ show_selected = bool(displayed_selected)
276
+ return (
277
+ gr.update(choices=[], value=[], interactive=True), # empty results, explicitly clear checked state
278
+ gr.update(visible=show_selected, choices=displayed_selected, value=displayed_selected), # show ALL selected tracks
279
+ )
280
+
281
  names = _get_bigwig_names(species)
282
  # Search in both track IDs and display names
283
  metadata = _load_track_metadata()
284
+ query_lower = query_stripped.lower()
285
+
286
+ # Show selected tracks section if user is typing or has selections
287
+ show_selected = bool(query_stripped) or bool(current_selected)
288
+
289
+ # Show ALL selected tracks (not limited to 20)
290
+ displayed_selected = current_selected or []
291
+
292
+ # Extract track IDs from already selected tracks (to exclude them from results)
293
+ selected_track_ids = set()
294
+ if current_selected:
295
+ selected_track_ids = {_extract_track_id(x) for x in current_selected}
296
 
297
  # Build list of (display_format, track_id) tuples for searching
298
  track_display_pairs = []
299
  for track_id in names:
300
+ # Skip tracks that are already selected
301
+ if track_id in selected_track_ids:
302
+ continue
303
  display_name = metadata.get(track_id, track_id)
304
  display_format = _format_track_for_display(track_id)
305
  track_display_pairs.append((display_format, track_id, display_name))
 
312
  query_lower in display_format.lower()):
313
  matching.append(display_format)
314
 
315
+ # Limit search results
316
  results = matching[:SEARCH_MAX_RESULTS]
317
+ return (
318
+ gr.update(choices=results, value=[], interactive=True), # results - limited to SEARCH_MAX_RESULTS, explicitly clear checked state
319
+ gr.update(visible=show_selected, choices=displayed_selected, value=displayed_selected), # show ALL selected tracks
320
+ )
321
 
322
 
323
  def add_selected(current_selected: list[str], to_add: list[str]):
 
335
  cur_ids.append(tid)
336
  cur_display.append(_format_track_for_display(tid))
337
 
338
+ # Show ALL selected tracks (no limit)
339
+ return gr.update(choices=cur_display, value=cur_display) # show all selected tracks
340
 
341
 
342
  def remove_selected(current_selected: list[str], to_remove: list[str]):
343
  """Remove tracks from selected list."""
344
  cur = [x for x in (current_selected or []) if x not in set(to_remove or [])]
345
+ # Show ALL remaining selected tracks (no limit)
346
+ show_selected = bool(cur)
347
+ return gr.update(choices=cur, value=cur, visible=show_selected)
348
 
349
 
350
  def update_coords_on_species_change(species: str):
 
358
  track_ids = _get_bigwig_names(species) # warms cache if available
359
  # Format available tracks for display
360
  formatted_tracks = [_format_track_for_display(tid) for tid in track_ids]
361
+
362
+ # Get default tracks for this species (filter to what's available)
363
+ default_track_ids = [tid for tid in DEFAULT_BIGWIG_TRACKS if tid in track_ids]
364
+ default_formatted = [_format_track_for_display(tid) for tid in default_track_ids]
365
+
366
+ # Show selected tracks section if there are default tracks
367
+ show_selected = bool(default_formatted)
368
+
369
  return (
370
  gr.update(value=""), # query textbox
371
  gr.update(choices=[], value=[]), # results list
372
+ gr.update(choices=formatted_tracks, value=default_formatted, visible=show_selected), # selected list with defaults
373
  )
374
  except (ValueError, AttributeError):
375
  # Species doesn't have bigwigs, that's okay
376
  return (
377
  gr.update(value=""), # query textbox
378
  gr.update(choices=[], value=[]), # results list
379
+ gr.update(choices=[], value=[], visible=False), # selected list (hidden)
380
  )
381
 
382
 
 
417
  if not seq or not seq.strip():
418
  raise gr.Error("seq is required when use_coords=False")
419
  inputs = {"seq": seq.strip(), "species": species}
420
+
421
  # Verify species is in inputs before calling pipeline
422
  if "species" not in inputs:
423
  raise gr.Error(f"Internal error: species not found in inputs dict. Inputs: {list(inputs.keys())}")
424
+
425
  out = pipe(inputs)
426
 
427
  bw_names = out.bigwig_track_names or []
 
757
  # Default coordinates per species
758
  DEFAULT_COORDS = {
759
  "human": {"chrom": "chr19", "start": 6_700_000, "end": 6_831_072},
760
+ "mouse": {"chrom": "chr1", "start": 9_880_168, "end": 10_142_312},
761
+ "drosophila_melanogaster": {"chrom": "chr2L", "start": 6_700_000, "end": 6_831_072},
762
+ "arabidopsis_thaliana": {"chrom": "chr1", "start": 13_135_095, "end": 13_397_239},
763
  }
764
 
765
  # Get default coordinates for default species
 
866
 
867
  # Get all available species from the pipeline
868
  all_species = sorted(ASSEMBLY_TO_SPECIES.values())
869
+
870
  with gr.Row():
871
  species = gr.Dropdown(
872
  choices=all_species,
 
951
  choices=_init_bigwig_selected,
952
  value=_init_bigwig_selected,
953
  label="Selected functional tracks (used for prediction)",
954
+ visible=bool(_init_bigwig_selected), # Show if there are default tracks, otherwise hidden
955
  )
956
 
957
  bigwig_query = gr.Textbox(
 
997
 
998
  # --- wiring (live search + auto-add) ---
999
 
1000
+ # Live search on every keystroke and when text changes (including deletion)
1001
  bigwig_query.input(
1002
  fn=search_bigwigs,
1003
+ inputs=[species, bigwig_query, bigwig_selected],
1004
+ outputs=[bigwig_results, bigwig_selected],
1005
+ )
1006
+ # Also trigger on change to catch deletions
1007
+ bigwig_query.change(
1008
+ fn=search_bigwigs,
1009
+ inputs=[species, bigwig_query, bigwig_selected],
1010
+ outputs=[bigwig_results, bigwig_selected],
1011
  )
1012
 
1013
+ # Helper function to get search results choices directly (without gr.update wrapper)
1014
+ def _get_search_results_choices(species: str, query: str, current_selected: list[str]) -> list[str]:
1015
+ """Get search results choices as a list, excluding selected tracks."""
1016
+ if query is None:
1017
+ query = ""
1018
+ query_stripped = query.strip()
1019
+
1020
+ if not query_stripped:
1021
+ return []
1022
+
1023
+ names = _get_bigwig_names(species)
1024
+ metadata = _load_track_metadata()
1025
+ query_lower = query_stripped.lower()
1026
+
1027
+ # Extract track IDs from already selected tracks
1028
+ selected_track_ids = set()
1029
+ if current_selected:
1030
+ selected_track_ids = {_extract_track_id(x) for x in current_selected}
1031
+
1032
+ # Build and filter results
1033
+ matching = []
1034
+ for track_id in names:
1035
+ if track_id in selected_track_ids:
1036
+ continue
1037
+ display_name = metadata.get(track_id, track_id)
1038
+ display_format = _format_track_for_display(track_id)
1039
+ if (query_lower in track_id.lower() or
1040
+ query_lower in display_name.lower() or
1041
+ query_lower in display_format.lower()):
1042
+ matching.append(display_format)
1043
+
1044
+ return matching[:SEARCH_MAX_RESULTS]
1045
+
1046
  # Auto-add: whenever user checks items in results, add them to Selected,
1047
  # then clear results selection (so it feels like "click to add")
1048
+ def _auto_add(selected_now: list[str], results_checked: list[str], current_query: str, current_results: list[str], current_species: str):
1049
  upd = add_selected(selected_now, results_checked) # reuses your function
1050
+ # Show selected tracks section if there are selections
1051
+ show_selected = bool(upd["value"])
1052
+
1053
+ # Get the new search results choices directly (excluding all selected tracks)
1054
+ new_choices = _get_search_results_choices(current_species, current_query, upd["value"])
1055
+
1056
+ # Create a completely fresh update with explicit empty value to prevent any checked state
1057
+ # Force Gradio to clear checked state by explicitly setting value to empty list
1058
+ # Use a workaround: set choices to empty first, then to new_choices to force a complete refresh
1059
+ # But since we can only return one update, we'll ensure value is explicitly empty
1060
+ # and that we're not preserving any state from the previous update
1061
+
1062
+ # Ensure no items from results_checked are in new_choices (they should already be filtered, but double-check)
1063
+ checked_track_ids = {_extract_track_id(x) for x in results_checked}
1064
+ new_choices_filtered = [c for c in new_choices if _extract_track_id(c) not in checked_track_ids]
1065
+
1066
+ # Create update with explicit empty value - this should force Gradio to clear all checked items
1067
+ fresh_update = gr.update(
1068
+ choices=new_choices_filtered,
1069
+ value=[], # CRITICAL: Explicitly empty list to clear all checked state
1070
+ )
1071
+
1072
+ return gr.update(**upd, visible=show_selected), fresh_update
1073
 
1074
+ # Use a wrapper that ensures results are cleared before updating
1075
+ def _auto_add_wrapper(selected_now: list[str], results_checked: list[str], current_query: str, current_results: list[str], current_species: str):
1076
+ # First, get the updates
1077
+ selected_update, results_update = _auto_add(selected_now, results_checked, current_query, current_results, current_species)
1078
+
1079
+ # Force the results update to have an explicit empty value
1080
+ # Extract choices from results_update if it's a dict-like object
1081
+ if isinstance(results_update, dict):
1082
+ results_choices = results_update.get("choices", [])
1083
+ else:
1084
+ # If it's a gr.update object, we need to access it differently
1085
+ # Try to get choices from the update
1086
+ try:
1087
+ results_choices = results_update.choices if hasattr(results_update, 'choices') else []
1088
+ except:
1089
+ # Fallback: get choices from the search function directly
1090
+ results_choices = _get_search_results_choices(
1091
+ current_species,
1092
+ current_query,
1093
+ selected_now + results_checked if isinstance(selected_now, list) and isinstance(results_checked, list) else []
1094
+ )
1095
+
1096
+ # Create a completely fresh update with explicit empty value
1097
+ # This should force Gradio to clear all checked items
1098
+ fresh_results_update = gr.update(choices=results_choices, value=[])
1099
+
1100
+ return selected_update, fresh_results_update
1101
+
1102
  bigwig_results.change(
1103
+ fn=_auto_add_wrapper,
1104
+ inputs=[bigwig_selected, bigwig_results, bigwig_query, bigwig_results, species],
1105
+ outputs=[bigwig_selected, bigwig_results],
1106
+ )
1107
+
1108
+ # Update selected tracks immediately when user unchecks items
1109
+ def _update_selected_tracks(selected_value: list[str], current_query: str, current_species: str):
1110
+ """Update selected tracks when user checks/unchecks items directly."""
1111
+ # selected_value contains only the currently checked items
1112
+ # Update choices to match the current selections (so unchecked items are removed)
1113
+ show_selected = bool(selected_value)
1114
+
1115
+ # Also update search results to reflect the new selection (tracks that were unchecked can now appear in results)
1116
+ search_updates = search_bigwigs(current_species, current_query, selected_value)
1117
+
1118
+ return (
1119
+ gr.update(choices=selected_value, value=selected_value, visible=show_selected), # Update selected tracks
1120
+ search_updates[0], # Update search results
1121
+ )
1122
+
1123
+ bigwig_selected.change(
1124
+ fn=_update_selected_tracks,
1125
+ inputs=[bigwig_selected, bigwig_query, species],
1126
  outputs=[bigwig_selected, bigwig_results],
1127
  )
1128
 
 
1163
  try:
1164
  track_ids = _get_bigwig_names(species)
1165
  formatted_tracks = [_format_track_for_display(tid) for tid in track_ids]
1166
+ # Get default tracks for this species (filter to what's available)
1167
+ default_track_ids = [tid for tid in DEFAULT_BIGWIG_TRACKS if tid in track_ids]
1168
+ default_formatted = [_format_track_for_display(tid) for tid in default_track_ids]
1169
+ # Show selected tracks section if there are default tracks
1170
+ show_selected_tracks = bool(default_formatted)
1171
  except:
1172
  formatted_tracks = []
1173
+ default_formatted = []
1174
+ show_selected_tracks = False
1175
  else:
1176
  formatted_tracks = []
1177
+ default_formatted = []
1178
+ show_selected_tracks = False
1179
 
1180
  return (
1181
  gr.update(visible=show_coords, value=coords["chrom"]),
 
1184
  gr.update(value=is_supported, visible=is_supported), # Show/hide and enable use_coords only if supported
1185
  gr.update(visible=show_coords), # Show/hide the row
1186
  gr.update(visible=not has_bigwigs), # Show "no tracks" message if no bigwigs
1187
+ gr.update(visible=show_selected_tracks, choices=formatted_tracks, value=default_formatted), # Show bigwig selection with defaults if available
1188
  gr.update(visible=has_bigwigs), # Show bigwig query if available
1189
  gr.update(visible=has_bigwigs), # Show bigwig results if available
1190
  gr.update(visible=has_bigwigs), # Show bigwig buttons if available