astronolan commited on
Commit
792eeb2
Β·
1 Parent(s): 9d65a12

Update app configuration and enhance search functionality

Browse files

- Refactor search interface to toggle between basic and advanced modes
- Improve callback functions for handling search queries and results
- Add new features for advanced search with multiple text and image queries
- Update UI components for better user experience and accessibility

Files changed (3) hide show
  1. app.py +1 -5
  2. src/callbacks.py +150 -89
  3. src/components.py +143 -97
app.py CHANGED
@@ -94,7 +94,7 @@ def main():
94
  parser.add_argument(
95
  '--checkpoint',
96
  type=str,
97
- default='aionsearchmodel.pt',
98
  help='Path to CLIP model checkpoint'
99
  )
100
  parser.add_argument(
@@ -129,9 +129,5 @@ def main():
129
  )
130
 
131
 
132
- # For gunicorn/production deployment (Hugging Face Spaces)
133
- app = create_app('aionsearchmodel.pt')
134
- server = app.server
135
-
136
  if __name__ == "__main__":
137
  main()
 
94
  parser.add_argument(
95
  '--checkpoint',
96
  type=str,
97
+ default='checkpoint_epoch_20.pt',
98
  help='Path to CLIP model checkpoint'
99
  )
100
  parser.add_argument(
 
129
  )
130
 
131
 
 
 
 
 
132
  if __name__ == "__main__":
133
  main()
src/callbacks.py CHANGED
@@ -38,22 +38,31 @@ def register_callbacks(app, search_service: SearchService):
38
  def update_galaxy_count(_):
39
  """Update the galaxy count display."""
40
  if search_service and config.TOTAL_GALAXIES > 0:
41
- return f"{config.TOTAL_GALAXIES:,} galaxies"
42
  else:
43
  return "loading..."
44
 
45
  @app.callback(
46
- [Output("vector-collapse", "is_open"),
47
- Output("vector-arrow", "className")],
 
 
48
  Input("vector-toggle", "n_clicks"),
49
- State("vector-collapse", "is_open"),
 
50
  prevent_initial_call=True
51
  )
52
- def toggle_vector_section(n_clicks, is_open):
53
- """Toggle the vector addition section."""
54
- new_state = not is_open
55
- arrow_class = "fas fa-chevron-up" if new_state else "fas fa-chevron-down"
56
- return new_state, arrow_class
 
 
 
 
 
 
57
 
58
  @app.callback(
59
  Output("vector-inputs", "children", allow_duplicate=True),
@@ -118,6 +127,19 @@ def register_callbacks(app, search_service: SearchService):
118
 
119
  return dash.no_update, dash.no_update
120
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
  @app.callback(
122
  [Output({"type": "text-input-container", "index": dash.dependencies.ALL}, "style"),
123
  Output({"type": "image-input-container", "index": dash.dependencies.ALL}, "style")],
@@ -141,57 +163,88 @@ def register_callbacks(app, search_service: SearchService):
141
 
142
  @app.callback(
143
  [Output("search-button", "n_clicks"),
144
- Output("search-input", "value")],
 
145
  [Input("example-1", "n_clicks"),
146
  Input("example-2", "n_clicks"),
147
  Input("example-3", "n_clicks"),
148
- Input("example-4", "n_clicks"),
149
  Input("example-5", "n_clicks"),
150
  Input("example-6", "n_clicks"),
151
  Input("example-7", "n_clicks")],
152
  [State("search-button", "n_clicks")],
153
  prevent_initial_call=True
154
  )
155
- def trigger_search_from_examples(click1, click2, click3, click4, click5, click6, click7, current_clicks):
156
  """Trigger search when example buttons are clicked."""
157
  ctx = callback_context
158
  if not ctx.triggered:
159
- return dash.no_update, dash.no_update
160
 
161
  button_id = ctx.triggered[0]["prop_id"].split(".")[0]
162
 
163
  example_queries = {
164
  "example-1": "Merging edge-on galaxy",
165
  "example-2": "A peculiar interacting galaxy system featuring plenty of tidal tails and a disturbed morphology",
166
- "example-3": "a faint tidal stream wrapping around",
167
- "example-4": "Strong gravitational lens",
168
  "example-5": "A violent merger in progress with visible tidal features",
169
  "example-6": "Low surface brightness",
170
- "example-7": "Ring galaxy"
171
  }
172
 
173
  search_query = example_queries.get(button_id, "")
174
 
175
  if search_query:
176
- return (current_clicks or 0) + 1, search_query
177
 
178
- return dash.no_update, dash.no_update
179
 
180
  @app.callback(
181
  [Output("search-time", "children"),
182
  Output("search-results", "children"),
183
  Output("search-data", "data"),
184
- Output("download-button", "disabled")],
 
185
  [Input("search-button", "n_clicks"),
186
- Input("search-input", "n_submit")],
 
187
  [State("search-input", "value"),
188
- State("rmag-slider", "value")],
 
 
 
 
 
 
 
 
 
 
 
189
  prevent_initial_call=True
190
  )
191
- def perform_search(n_clicks, n_submit, query, rmag_range):
192
- """Perform text search."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
193
  if not query or not query.strip():
194
- return "", dbc.Alert("Please enter a search query", color="warning"), None, True
195
 
196
  try:
197
  # Extract min and max from slider range
@@ -236,13 +289,13 @@ def register_callbacks(app, search_service: SearchService):
236
  load_more_button
237
  ])
238
 
239
- return "", results_container, search_data, False
240
 
241
  except Exception as e:
242
  error_msg = dbc.Alert(f"Search failed: {str(e)}", color="danger")
243
  logger.error(f"Search error: {e}")
244
  logger.error(f"Full traceback:\n{traceback.format_exc()}")
245
- return "", error_msg, None, True
246
 
247
  @app.callback(
248
  [Output("galaxy-modal", "is_open"),
@@ -360,9 +413,10 @@ def register_callbacks(app, search_service: SearchService):
360
  return dash.no_update, dash.no_update
361
 
362
  @app.callback(
363
- [Output("vector-inputs", "children", allow_duplicate=True),
 
 
364
  Output("vector-inputs-count", "data", allow_duplicate=True),
365
- Output("vector-collapse", "is_open", allow_duplicate=True),
366
  Output("galaxy-modal", "is_open", allow_duplicate=True)],
367
  Input("add-to-advanced-search", "n_clicks"),
368
  [State("current-galaxy-data", "data"),
@@ -371,16 +425,16 @@ def register_callbacks(app, search_service: SearchService):
371
  prevent_initial_call=True
372
  )
373
  def add_galaxy_to_advanced_search(n_clicks, galaxy_data, current_children, count):
374
- """Add the current galaxy's RA/Dec to advanced search."""
375
  if not n_clicks or not galaxy_data:
376
- return dash.no_update, dash.no_update, dash.no_update, dash.no_update
377
 
378
  # Extract galaxy coordinates
379
  ra = galaxy_data.get('ra')
380
  dec = galaxy_data.get('dec')
381
 
382
  if ra is None or dec is None:
383
- return dash.no_update, dash.no_update, dash.no_update, dash.no_update
384
 
385
  # Create a new image input row with the galaxy's RA/Dec pre-filled
386
  new_row = create_vector_input_row(
@@ -393,29 +447,49 @@ def register_callbacks(app, search_service: SearchService):
393
 
394
  current_children.append(new_row)
395
 
396
- # Return updated children, incremented count, open vector panel, close modal
397
- return current_children, count + 1, True, False
 
 
 
 
 
 
398
 
399
  @app.callback(
400
- [Output("search-time", "children", allow_duplicate=True),
401
- Output("search-results", "children", allow_duplicate=True),
402
- Output("search-data", "data", allow_duplicate=True),
403
- Output("download-button", "disabled", allow_duplicate=True)],
404
- Input("vector-search-button", "n_clicks"),
405
- [State({"type": "vector-query-type", "index": dash.dependencies.ALL}, "value"),
406
- State({"type": "vector-text", "index": dash.dependencies.ALL}, "value"),
407
- State({"type": "vector-ra", "index": dash.dependencies.ALL}, "value"),
408
- State({"type": "vector-dec", "index": dash.dependencies.ALL}, "value"),
409
- State({"type": "vector-fov", "index": dash.dependencies.ALL}, "value"),
410
- State({"type": "vector-operation", "index": dash.dependencies.ALL}, "value"),
411
- State("rmag-slider", "value")],
412
  prevent_initial_call=True
413
  )
414
- def perform_vector_search(n_clicks, query_types, text_values, ra_values, dec_values, fov_values, operations, rmag_range):
415
- """Perform advanced vector search with multiple text and/or image queries."""
416
- if not n_clicks:
417
- return dash.no_update, dash.no_update, dash.no_update, dash.no_update
 
 
 
 
 
 
 
 
 
 
 
418
 
 
 
 
 
 
 
 
 
 
 
 
419
  def operation_to_weight(op_str):
420
  """Convert operation string to float weight."""
421
  if op_str == "+":
@@ -423,7 +497,6 @@ def register_callbacks(app, search_service: SearchService):
423
  elif op_str == "-":
424
  return -1.0
425
  else:
426
- # For magnitude values like "+2", "-5", etc.
427
  return float(op_str)
428
 
429
  def weight_to_display(weight):
@@ -437,37 +510,52 @@ def register_callbacks(app, search_service: SearchService):
437
  else:
438
  return str(int(weight))
439
 
440
- # Parse inputs to separate text and image queries
441
  text_queries = []
442
  text_weights = []
443
  image_queries = []
444
  image_weights = []
445
 
446
- for i, query_type in enumerate(query_types):
447
- operation = operations[i]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
448
  weight = operation_to_weight(operation)
449
 
450
  if query_type == "text":
451
- text_value = text_values[i]
452
  if text_value and text_value.strip():
453
  text_queries.append(text_value.strip())
454
  text_weights.append(weight)
455
  else: # image
456
- ra = ra_values[i]
457
- dec = dec_values[i]
458
- fov = fov_values[i] if fov_values[i] else 0.025
459
 
460
  if ra is not None and dec is not None:
461
  image_queries.append({
462
  'ra': float(ra),
463
  'dec': float(dec),
464
- 'fov': float(fov)
465
  })
466
  image_weights.append(weight)
467
 
468
  # Validate that we have at least one query
469
  if not text_queries and not image_queries:
470
- return "", dbc.Alert("Please enter at least one text or image query", color="warning"), None, True
471
 
472
  try:
473
  # Extract min and max from slider range
@@ -569,40 +657,13 @@ def register_callbacks(app, search_service: SearchService):
569
  load_more_button
570
  ])
571
 
572
- return "", results_container, search_data, False
573
 
574
  except Exception as e:
575
  error_msg = dbc.Alert(f"Advanced search failed: {str(e)}", color="danger")
576
  logger.error(f"Advanced search error: {e}")
577
  logger.error(f"Full traceback:\n{traceback.format_exc()}")
578
- return "", error_msg, None, True
579
-
580
- @app.callback(
581
- Output("download-csv", "data"),
582
- Input("download-button", "n_clicks"),
583
- State("search-data", "data"),
584
- prevent_initial_call=True
585
- )
586
- def download_csv(n_clicks, search_data):
587
- """Download search results as CSV."""
588
- if n_clicks and search_data:
589
- # Create DataFrame with the search results
590
- df = pd.DataFrame({
591
- ZILLIZ_PRIMARY_KEY: search_data[ZILLIZ_PRIMARY_KEY],
592
- 'ra': search_data['ra'],
593
- 'dec': search_data['dec'],
594
- 'r_mag': search_data['r_mag'],
595
- 'distance': search_data['distance'],
596
- 'cutout_url': search_data['cutout_url']
597
- })
598
-
599
- # Create CSV string
600
- csv_string = df.to_csv(index=False)
601
-
602
- # Return download data
603
- return dict(content=csv_string, filename="galaxy_search_results.csv")
604
-
605
- return dash.no_update
606
 
607
 
608
  # Helper functions for callbacks
@@ -745,7 +806,7 @@ def build_modal_content(galaxy_info: dict) -> tuple:
745
  html.Span(f"r_mag: {galaxy_info['r_mag']:.2f}", className="d-inline-block mb-0",
746
  style={"color": "rgba(245, 245, 247, 0.7)", "font-size": "0.9rem"}),
747
  html.Span(" β€’ ", className="mx-2", style={"color": "rgba(245, 245, 247, 0.5)"}),
748
- html.Span(f"Distance: {galaxy_info['distance']:.4f}", className="d-inline-block mb-0",
749
  style={"color": "rgba(245, 245, 247, 0.7)", "font-size": "0.9rem"}),
750
  ], className="mb-3"),
751
  ])
 
38
  def update_galaxy_count(_):
39
  """Update the galaxy count display."""
40
  if search_service and config.TOTAL_GALAXIES > 0:
41
+ return f"{config.TOTAL_GALAXIES:,} GALAXIES FROM LEGACY SURVEY DR10"
42
  else:
43
  return "loading..."
44
 
45
  @app.callback(
46
+ [Output("basic-search-bar", "style"),
47
+ Output("advanced-search-interface", "style"),
48
+ Output("vector-arrow", "className"),
49
+ Output("search-input-advanced", "value")],
50
  Input("vector-toggle", "n_clicks"),
51
+ [State("advanced-search-interface", "style"),
52
+ State("search-input", "value")],
53
  prevent_initial_call=True
54
  )
55
+ def toggle_search_mode(n_clicks, advanced_style, basic_text):
56
+ """Toggle between basic and advanced search interfaces."""
57
+ # Check if advanced mode is currently shown
58
+ is_advanced_shown = advanced_style.get("display") == "block"
59
+
60
+ if is_advanced_shown:
61
+ # Switch to basic mode
62
+ return {"display": "block"}, {"display": "none"}, "fas fa-chevron-down me-2", dash.no_update
63
+ else:
64
+ # Switch to advanced mode - copy text from basic to advanced
65
+ return {"display": "none"}, {"display": "block"}, "fas fa-chevron-up me-2", basic_text or ""
66
 
67
  @app.callback(
68
  Output("vector-inputs", "children", allow_duplicate=True),
 
127
 
128
  return dash.no_update, dash.no_update
129
 
130
+ @app.callback(
131
+ [Output("main-text-input-container", "style"),
132
+ Output("main-image-input-container", "style")],
133
+ Input("main-query-type", "value"),
134
+ prevent_initial_call=False
135
+ )
136
+ def toggle_main_query_type(query_type):
137
+ """Toggle visibility of main query inputs based on query type."""
138
+ if query_type == "image":
139
+ return {"display": "none"}, {"display": "block"}
140
+ else:
141
+ return {"display": "block"}, {"display": "none"}
142
+
143
  @app.callback(
144
  [Output({"type": "text-input-container", "index": dash.dependencies.ALL}, "style"),
145
  Output({"type": "image-input-container", "index": dash.dependencies.ALL}, "style")],
 
163
 
164
  @app.callback(
165
  [Output("search-button", "n_clicks"),
166
+ Output("search-input", "value"),
167
+ Output("search-input-advanced", "value", allow_duplicate=True)],
168
  [Input("example-1", "n_clicks"),
169
  Input("example-2", "n_clicks"),
170
  Input("example-3", "n_clicks"),
 
171
  Input("example-5", "n_clicks"),
172
  Input("example-6", "n_clicks"),
173
  Input("example-7", "n_clicks")],
174
  [State("search-button", "n_clicks")],
175
  prevent_initial_call=True
176
  )
177
+ def trigger_search_from_examples(click1, click2, click3, click5, click6, click7, current_clicks):
178
  """Trigger search when example buttons are clicked."""
179
  ctx = callback_context
180
  if not ctx.triggered:
181
+ return dash.no_update, dash.no_update, dash.no_update
182
 
183
  button_id = ctx.triggered[0]["prop_id"].split(".")[0]
184
 
185
  example_queries = {
186
  "example-1": "Merging edge-on galaxy",
187
  "example-2": "A peculiar interacting galaxy system featuring plenty of tidal tails and a disturbed morphology",
188
+ "example-3": "galaxy with stream",
 
189
  "example-5": "A violent merger in progress with visible tidal features",
190
  "example-6": "Low surface brightness",
191
+ "example-7": "face-on ring galaxy"
192
  }
193
 
194
  search_query = example_queries.get(button_id, "")
195
 
196
  if search_query:
197
+ return (current_clicks or 0) + 1, search_query, search_query
198
 
199
+ return dash.no_update, dash.no_update, dash.no_update
200
 
201
  @app.callback(
202
  [Output("search-time", "children"),
203
  Output("search-results", "children"),
204
  Output("search-data", "data"),
205
+ Output("download-button", "disabled"),
206
+ Output("download-button-advanced", "disabled")],
207
  [Input("search-button", "n_clicks"),
208
+ Input("search-input", "n_submit"),
209
+ Input("search-button-advanced", "n_clicks")],
210
  [State("search-input", "value"),
211
+ State("search-input-advanced", "value"),
212
+ State("rmag-slider", "value"),
213
+ State("advanced-search-interface", "style"),
214
+ State("main-vector-operation", "value"),
215
+ State("main-query-type", "value"),
216
+ State("main-vector-ra", "value"),
217
+ State("main-vector-dec", "value"),
218
+ State({"type": "vector-query-type", "index": dash.dependencies.ALL}, "value"),
219
+ State({"type": "vector-text", "index": dash.dependencies.ALL}, "value"),
220
+ State({"type": "vector-ra", "index": dash.dependencies.ALL}, "value"),
221
+ State({"type": "vector-dec", "index": dash.dependencies.ALL}, "value"),
222
+ State({"type": "vector-operation", "index": dash.dependencies.ALL}, "value")],
223
  prevent_initial_call=True
224
  )
225
+ def perform_search(n_clicks_basic, n_submit, n_clicks_advanced, query_basic, query_advanced,
226
+ rmag_range, advanced_style, main_operation, main_query_type,
227
+ main_ra, main_dec,
228
+ additional_query_types, additional_text_values, additional_ra_values,
229
+ additional_dec_values, additional_operations):
230
+ """Perform text search or advanced search based on mode."""
231
+ # Check if advanced mode is active
232
+ is_advanced_mode = advanced_style.get("display") == "block"
233
+
234
+ # If advanced mode is active, perform advanced search
235
+ if is_advanced_mode:
236
+ return perform_advanced_search_logic(
237
+ query_advanced, rmag_range, main_operation, main_query_type,
238
+ main_ra, main_dec,
239
+ additional_query_types, additional_text_values,
240
+ additional_ra_values, additional_dec_values,
241
+ additional_operations
242
+ )
243
+
244
+ # Otherwise perform basic search
245
+ query = query_basic
246
  if not query or not query.strip():
247
+ return "", dbc.Alert("Please enter a search query", color="warning"), None, True, True
248
 
249
  try:
250
  # Extract min and max from slider range
 
289
  load_more_button
290
  ])
291
 
292
+ return "", results_container, search_data, False, False
293
 
294
  except Exception as e:
295
  error_msg = dbc.Alert(f"Search failed: {str(e)}", color="danger")
296
  logger.error(f"Search error: {e}")
297
  logger.error(f"Full traceback:\n{traceback.format_exc()}")
298
+ return "", error_msg, None, True, True
299
 
300
  @app.callback(
301
  [Output("galaxy-modal", "is_open"),
 
413
  return dash.no_update, dash.no_update
414
 
415
  @app.callback(
416
+ [Output("advanced-search-interface", "style", allow_duplicate=True),
417
+ Output("basic-search-bar", "style", allow_duplicate=True),
418
+ Output("vector-inputs", "children", allow_duplicate=True),
419
  Output("vector-inputs-count", "data", allow_duplicate=True),
 
420
  Output("galaxy-modal", "is_open", allow_duplicate=True)],
421
  Input("add-to-advanced-search", "n_clicks"),
422
  [State("current-galaxy-data", "data"),
 
425
  prevent_initial_call=True
426
  )
427
  def add_galaxy_to_advanced_search(n_clicks, galaxy_data, current_children, count):
428
+ """Add the current galaxy's RA/Dec as a new query in advanced search."""
429
  if not n_clicks or not galaxy_data:
430
+ return dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update
431
 
432
  # Extract galaxy coordinates
433
  ra = galaxy_data.get('ra')
434
  dec = galaxy_data.get('dec')
435
 
436
  if ra is None or dec is None:
437
+ return dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update
438
 
439
  # Create a new image input row with the galaxy's RA/Dec pre-filled
440
  new_row = create_vector_input_row(
 
447
 
448
  current_children.append(new_row)
449
 
450
+ # Switch to advanced mode and add the new query
451
+ return (
452
+ {"display": "block"}, # Show advanced interface
453
+ {"display": "none"}, # Hide basic search bar
454
+ current_children, # Updated children with new query
455
+ count + 1, # Incremented count
456
+ False # Close modal
457
+ )
458
 
459
  @app.callback(
460
+ Output("download-csv", "data"),
461
+ [Input("download-button", "n_clicks"),
462
+ Input("download-button-advanced", "n_clicks")],
463
+ State("search-data", "data"),
 
 
 
 
 
 
 
 
464
  prevent_initial_call=True
465
  )
466
+ def download_csv(n_clicks_basic, n_clicks_advanced, search_data):
467
+ """Download search results as CSV."""
468
+ if (n_clicks_basic or n_clicks_advanced) and search_data:
469
+ # Create DataFrame with the search results
470
+ df = pd.DataFrame({
471
+ ZILLIZ_PRIMARY_KEY: search_data[ZILLIZ_PRIMARY_KEY],
472
+ 'ra': search_data['ra'],
473
+ 'dec': search_data['dec'],
474
+ 'r_mag': search_data['r_mag'],
475
+ 'distance': search_data['distance'],
476
+ 'cutout_url': search_data['cutout_url']
477
+ })
478
+
479
+ # Create CSV string
480
+ csv_string = df.to_csv(index=False)
481
 
482
+ # Return download data
483
+ return dict(content=csv_string, filename="galaxy_search_results.csv")
484
+
485
+ return dash.no_update
486
+
487
+ def perform_advanced_search_logic(query, rmag_range, main_operation, main_query_type,
488
+ main_ra, main_dec,
489
+ additional_query_types, additional_text_values,
490
+ additional_ra_values, additional_dec_values,
491
+ additional_operations):
492
+ """Perform advanced search combining main query with additional queries."""
493
  def operation_to_weight(op_str):
494
  """Convert operation string to float weight."""
495
  if op_str == "+":
 
497
  elif op_str == "-":
498
  return -1.0
499
  else:
 
500
  return float(op_str)
501
 
502
  def weight_to_display(weight):
 
510
  else:
511
  return str(int(weight))
512
 
513
+ # Parse main query
514
  text_queries = []
515
  text_weights = []
516
  image_queries = []
517
  image_weights = []
518
 
519
+ main_weight = operation_to_weight(main_operation)
520
+
521
+ if main_query_type == "text":
522
+ if query and query.strip():
523
+ text_queries.append(query.strip())
524
+ text_weights.append(main_weight)
525
+ else: # image
526
+ if main_ra is not None and main_dec is not None:
527
+ image_queries.append({
528
+ 'ra': float(main_ra),
529
+ 'dec': float(main_dec),
530
+ 'fov': 0.025
531
+ })
532
+ image_weights.append(main_weight)
533
+
534
+ # Parse additional queries
535
+ for i, query_type in enumerate(additional_query_types):
536
+ operation = additional_operations[i]
537
  weight = operation_to_weight(operation)
538
 
539
  if query_type == "text":
540
+ text_value = additional_text_values[i]
541
  if text_value and text_value.strip():
542
  text_queries.append(text_value.strip())
543
  text_weights.append(weight)
544
  else: # image
545
+ ra = additional_ra_values[i]
546
+ dec = additional_dec_values[i]
 
547
 
548
  if ra is not None and dec is not None:
549
  image_queries.append({
550
  'ra': float(ra),
551
  'dec': float(dec),
552
+ 'fov': 0.025
553
  })
554
  image_weights.append(weight)
555
 
556
  # Validate that we have at least one query
557
  if not text_queries and not image_queries:
558
+ return "", dbc.Alert("Please enter at least one text or image query", color="warning"), None, True, True
559
 
560
  try:
561
  # Extract min and max from slider range
 
657
  load_more_button
658
  ])
659
 
660
+ return "", results_container, search_data, False, False
661
 
662
  except Exception as e:
663
  error_msg = dbc.Alert(f"Advanced search failed: {str(e)}", color="danger")
664
  logger.error(f"Advanced search error: {e}")
665
  logger.error(f"Full traceback:\n{traceback.format_exc()}")
666
+ return "", error_msg, None, True, True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
667
 
668
 
669
  # Helper functions for callbacks
 
806
  html.Span(f"r_mag: {galaxy_info['r_mag']:.2f}", className="d-inline-block mb-0",
807
  style={"color": "rgba(245, 245, 247, 0.7)", "font-size": "0.9rem"}),
808
  html.Span(" β€’ ", className="mx-2", style={"color": "rgba(245, 245, 247, 0.5)"}),
809
+ html.Span(f"Cosine Similarity to Query: {galaxy_info['distance']:.4f}", className="d-inline-block mb-0",
810
  style={"color": "rgba(245, 245, 247, 0.7)", "font-size": "0.9rem"}),
811
  ], className="mb-3"),
812
  ])
src/components.py CHANGED
@@ -483,6 +483,9 @@ def create_header():
483
  dbc.Col([
484
  html.Div([
485
  html.H1("galaxy semantic search", className="galaxy-title text-center mb-1"),
 
 
 
486
  html.Div(id="galaxy-count", className="galaxy-count text-center")
487
  ], className="text-center mb-3")
488
  ])
@@ -545,8 +548,6 @@ def create_search_container():
545
  id="example-2", className="example-button me-2 mb-2", size="sm", color="light"),
546
  dbc.Button([html.I(className="fas fa-stream me-2"), "Stream"],
547
  id="example-3", className="example-button me-2 mb-2", size="sm", color="light"),
548
- dbc.Button([html.I(className="fas fa-glasses me-2"), "Gravitational lens"],
549
- id="example-4", className="example-button me-2 mb-2", size="sm", color="light"),
550
  dbc.Button([html.I(className="fas fa-explosion me-2"), "A violent merger"],
551
  id="example-5", className="example-button me-2 mb-2", size="sm", color="light"),
552
  dbc.Button([html.I(className="fas fa-moon me-2"), "Low surface brightness"],
@@ -556,76 +557,146 @@ def create_search_container():
556
  ], className="text-center")
557
  ], className="mb-3"),
558
 
559
- # Search input
560
- dbc.InputGroup([
561
- dbc.InputGroupText(html.I(className="fas fa-search")),
562
- dbc.Input(
563
- id="search-input",
564
- placeholder="Describe the galaxy you're looking for...",
565
- type="text",
566
- n_submit=0
567
- ),
568
- dbc.Button("Search",
569
- id="search-button", color="primary", n_clicks=0),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
570
  dbc.Button([
571
- html.I(className="fas fa-download")
572
- ], id="download-button", color="secondary", n_clicks=0,
573
- className="download-button", size="sm",
574
- disabled=True)
575
- ])
576
  ], className="search-container", style={"position": "relative"}),
577
 
578
  # r_mag filter
579
- create_rmag_filter_panel(),
580
-
581
- # Vector Addition toggle button
582
- dbc.Row([
583
- dbc.Col([
584
- html.Button([
585
- html.I(className="fas fa-chevron-down", id="vector-arrow"),
586
- "Advanced Search (Vector Addition / Images)"
587
- ], id="vector-toggle", className="refinement-toggle w-100")
588
- ], width=12)
589
- ], className="mt-3"),
590
-
591
- # Vector Addition UI - Collapsible section
592
- create_vector_addition_panel()
593
  ], width=12, lg=11, className="mx-auto")
594
  ], className="mb-3")
595
 
596
 
597
- def create_vector_addition_panel():
598
- """Create the advanced search (vector addition) collapsible panel."""
599
- return dbc.Collapse([
600
- html.Div([
601
- html.P("Advanced Search: Combine multiple text and/or image queries using vector addition/subtraction:", className="refinement-label"),
602
- html.Div(id="vector-inputs", children=[
603
- # Initial input
604
- create_vector_input_row(0)
605
- ]),
606
- dbc.Row([
607
- dbc.Col([
608
- dbc.Button(
609
- [html.I(className="fas fa-plus me-2"), "Add Query"],
610
- id="add-vector-input",
611
- color="secondary",
612
- size="sm",
613
- className="me-2 btn-add-vector"
614
- )
615
- ], width=6),
616
- dbc.Col([
617
- dbc.Button(
618
- "Advanced Search",
619
- id="vector-search-button",
620
- className="btn-primary w-100",
621
- n_clicks=0
622
- )
623
- ], width=6)
624
- ], className="mt-3")
625
- ], className="refinement-container")
626
- ], id="vector-collapse", is_open=False)
627
-
628
-
629
  def create_vector_input_row(index: int, query_type: str = "text", ra: float = None, dec: float = None, fov: float = 0.025):
630
  """Create a single vector input row with operation selector, query type toggle, and conditional inputs.
631
 
@@ -692,30 +763,21 @@ def create_vector_input_row(index: int, query_type: str = "text", ra: float = No
692
  dbc.Col([
693
  dbc.Input(
694
  id={"type": "vector-ra", "index": index},
695
- placeholder="ra:",
696
  type="number",
697
  step="any",
698
  value=ra
699
  )
700
- ], width=4),
701
  dbc.Col([
702
  dbc.Input(
703
  id={"type": "vector-dec", "index": index},
704
- placeholder="dec:",
705
  type="number",
706
  step="any",
707
  value=dec
708
  )
709
- ], width=4),
710
- dbc.Col([
711
- dbc.Input(
712
- id={"type": "vector-fov", "index": index},
713
- placeholder="fov:",
714
- type="number",
715
- value=fov,
716
- step="any"
717
- )
718
- ], width=4)
719
  ])
720
  ], id={"type": "image-input-container", "index": index}, style=image_display)
721
  ], width=8),
@@ -778,26 +840,10 @@ def create_info_modal():
778
  return dbc.Modal([
779
  dbc.ModalHeader(dbc.ModalTitle([html.I(className="fas fa-info-circle me-2"), "About Galaxy Search"])),
780
  dbc.ModalBody([
781
- html.P("This app performs semantic search over galaxy images using CLIP embeddings and BigQuery.",
782
- style={"color": "rgba(245, 245, 247, 0.8)", "margin-bottom": "1rem", "font-size": "0.9rem"}),
783
- html.Div([
784
- html.P("The search uses contrastive language-image pre-training (CLIP) to match text descriptions with galaxy images. "
785
- "The model was trained on galaxy descriptions and can understand various astronomical features and characteristics.",
786
- style={"margin-bottom": "1rem", "color": "rgba(245, 245, 247, 0.7)"}),
787
-
788
- html.H6("Search Tips:", style={"color": "#F5F5F7", "font-weight": "500", "margin-bottom": "0.5rem"}),
789
- html.Ul([
790
- html.Li("Describe morphological features (spiral, elliptical, irregular, merging)",
791
- style={"color": "rgba(245, 245, 247, 0.6)", "margin-bottom": "0.3rem"}),
792
- html.Li("Mention specific features (tidal tails, dust lanes, star-forming regions)",
793
- style={"color": "rgba(245, 245, 247, 0.6)", "margin-bottom": "0.3rem"}),
794
- html.Li("Use color descriptions or brightness characteristics",
795
- style={"color": "rgba(245, 245, 247, 0.6)", "margin-bottom": "0.3rem"}),
796
- html.Li("Combine multiple features for more specific results",
797
- style={"color": "rgba(245, 245, 247, 0.6)"}),
798
- ], style={"margin-left": "1rem"}),
799
- ], style={"background": "rgba(255, 255, 255, 0.05)", "padding": "1.5rem", "border-radius": "12px",
800
- "border": "0.5px solid rgba(255, 255, 255, 0.1)", "color": "rgba(245, 245, 247, 0.7)", "font-size": "0.9rem"})
801
  ]),
802
  dbc.ModalFooter(
803
  dbc.Button("Close", id="close-info-modal", className="ms-auto")
 
483
  dbc.Col([
484
  html.Div([
485
  html.H1("galaxy semantic search", className="galaxy-title text-center mb-1"),
486
+ html.P("powered by AION-Search", className="text-center mb-2",
487
+ style={"color": "rgba(245, 245, 247, 0.5)", "font-weight": "300",
488
+ "font-size": "0.8rem", "letter-spacing": "0.05em"}),
489
  html.Div(id="galaxy-count", className="galaxy-count text-center")
490
  ], className="text-center mb-3")
491
  ])
 
548
  id="example-2", className="example-button me-2 mb-2", size="sm", color="light"),
549
  dbc.Button([html.I(className="fas fa-stream me-2"), "Stream"],
550
  id="example-3", className="example-button me-2 mb-2", size="sm", color="light"),
 
 
551
  dbc.Button([html.I(className="fas fa-explosion me-2"), "A violent merger"],
552
  id="example-5", className="example-button me-2 mb-2", size="sm", color="light"),
553
  dbc.Button([html.I(className="fas fa-moon me-2"), "Low surface brightness"],
 
557
  ], className="text-center")
558
  ], className="mb-3"),
559
 
560
+ # Basic search input (shown when NOT in advanced mode)
561
+ html.Div([
562
+ dbc.InputGroup([
563
+ dbc.InputGroupText(html.I(className="fas fa-search")),
564
+ dbc.Input(
565
+ id="search-input",
566
+ placeholder="Describe the galaxy you're looking for...",
567
+ type="text",
568
+ n_submit=0
569
+ ),
570
+ dbc.Button("Search",
571
+ id="search-button", color="primary", n_clicks=0),
572
+ dbc.Button([
573
+ html.I(className="fas fa-download")
574
+ ], id="download-button", color="secondary", n_clicks=0,
575
+ className="download-button", size="sm",
576
+ disabled=True)
577
+ ])
578
+ ], id="basic-search-bar"),
579
+
580
+ # Advanced search interface (shown when in advanced mode)
581
+ html.Div([
582
+ html.P("Combine multiple text and/or image queries using vector addition/subtraction:",
583
+ className="refinement-label mb-3"),
584
+
585
+ # Main query row
586
+ dbc.Row([
587
+ # Operation selector
588
+ dbc.Col([
589
+ dbc.Select(
590
+ id="main-vector-operation",
591
+ options=[
592
+ {"label": "+10", "value": "+10"},
593
+ {"label": "+5", "value": "+5"},
594
+ {"label": "+2", "value": "+2"},
595
+ {"label": "+", "value": "+"},
596
+ {"label": "-", "value": "-"},
597
+ {"label": "-2", "value": "-2"},
598
+ {"label": "-5", "value": "-5"},
599
+ {"label": "-10", "value": "-10"}
600
+ ],
601
+ value="+",
602
+ style={"width": "70px"},
603
+ className="d-inline-block vector-operation-select"
604
+ )
605
+ ], width=1),
606
+ # Query type selector
607
+ dbc.Col([
608
+ dbc.Select(
609
+ id="main-query-type",
610
+ options=[
611
+ {"label": "Text", "value": "text"},
612
+ {"label": "Image", "value": "image"}
613
+ ],
614
+ value="text",
615
+ style={"width": "100px"},
616
+ className="d-inline-block vector-query-type-select"
617
+ )
618
+ ], width=2),
619
+ # Input area
620
+ dbc.Col([
621
+ # Text input (shown when type is "text")
622
+ html.Div([
623
+ dbc.Input(
624
+ id="search-input-advanced",
625
+ placeholder="Enter text query...",
626
+ type="text"
627
+ )
628
+ ], id="main-text-input-container", style={"display": "block"}),
629
+ # Image inputs (shown when type is "image")
630
+ html.Div([
631
+ dbc.Row([
632
+ dbc.Col([
633
+ dbc.Input(
634
+ id="main-vector-ra",
635
+ placeholder="RA:",
636
+ type="number",
637
+ step="any"
638
+ )
639
+ ], width=6),
640
+ dbc.Col([
641
+ dbc.Input(
642
+ id="main-vector-dec",
643
+ placeholder="Dec:",
644
+ type="number",
645
+ step="any"
646
+ )
647
+ ], width=6)
648
+ ])
649
+ ], id="main-image-input-container", style={"display": "none"})
650
+ ], width=9)
651
+ ], className="mb-2"),
652
+
653
+ # Additional queries container
654
+ html.Div(id="vector-inputs", children=[]),
655
+
656
+ # Buttons row
657
+ dbc.Row([
658
+ dbc.Col([
659
+ dbc.Button(
660
+ [html.I(className="fas fa-plus me-2"), "Add Query"],
661
+ id="add-vector-input",
662
+ color="secondary",
663
+ size="sm",
664
+ className="btn-add-vector"
665
+ )
666
+ ], width=6),
667
+ dbc.Col([
668
+ dbc.Button(
669
+ "Search",
670
+ id="search-button-advanced",
671
+ className="btn-primary",
672
+ n_clicks=0,
673
+ style={"margin-right": "0.5rem"}
674
+ ),
675
+ dbc.Button([
676
+ html.I(className="fas fa-download")
677
+ ], id="download-button-advanced", color="secondary", n_clicks=0,
678
+ className="download-button", size="sm",
679
+ disabled=True)
680
+ ], width=6, className="text-end")
681
+ ], className="mt-3")
682
+ ], id="advanced-search-interface", style={"display": "none"}),
683
+
684
+ # Toggle button (below search bar)
685
+ html.Div([
686
  dbc.Button([
687
+ html.I(className="fas fa-chevron-down me-2", id="vector-arrow"),
688
+ "Text + Image search with vector addition"
689
+ ], id="vector-toggle", color="link", className="refinement-toggle",
690
+ style={"white-space": "nowrap", "padding": "0.5rem 0.75rem", "margin-top": "0.5rem"})
691
+ ], className="d-flex")
692
  ], className="search-container", style={"position": "relative"}),
693
 
694
  # r_mag filter
695
+ create_rmag_filter_panel()
 
 
 
 
 
 
 
 
 
 
 
 
 
696
  ], width=12, lg=11, className="mx-auto")
697
  ], className="mb-3")
698
 
699
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
700
  def create_vector_input_row(index: int, query_type: str = "text", ra: float = None, dec: float = None, fov: float = 0.025):
701
  """Create a single vector input row with operation selector, query type toggle, and conditional inputs.
702
 
 
763
  dbc.Col([
764
  dbc.Input(
765
  id={"type": "vector-ra", "index": index},
766
+ placeholder="RA:",
767
  type="number",
768
  step="any",
769
  value=ra
770
  )
771
+ ], width=6),
772
  dbc.Col([
773
  dbc.Input(
774
  id={"type": "vector-dec", "index": index},
775
+ placeholder="Dec:",
776
  type="number",
777
  step="any",
778
  value=dec
779
  )
780
+ ], width=6)
 
 
 
 
 
 
 
 
 
781
  ])
782
  ], id={"type": "image-input-container", "index": index}, style=image_display)
783
  ], width=8),
 
840
  return dbc.Modal([
841
  dbc.ModalHeader(dbc.ModalTitle([html.I(className="fas fa-info-circle me-2"), "About Galaxy Search"])),
842
  dbc.ModalBody([
843
+ html.P("This app searches ~19 million Legacy Survey galaxies with r-mag brighter than 20 magnitude. "
844
+ "These galaxy images were converted to a text-searchable space using AION-Search, which was trained on "
845
+ "~275k text descriptions of galaxy images generated by GPT-4.1-mini.",
846
+ style={"color": "rgba(245, 245, 247, 0.8)", "margin-bottom": "1rem", "font-size": "0.9rem"})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
847
  ]),
848
  dbc.ModalFooter(
849
  dbc.Button("Close", id="close-info-modal", className="ms-auto")