vidavis 0.0.11__py3-none-any.whl → 0.0.13__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
vidavis/__version__.py CHANGED
@@ -1 +1 @@
1
- __version__ = '0.0.11'
1
+ __version__ = '0.0.13'
@@ -12,13 +12,14 @@ import panel as pn
12
12
 
13
13
  from vidavis.bokeh._palette import available_palettes
14
14
  from vidavis.data.measurement_set.processing_set._ps_coords import set_index_coordinates
15
- from vidavis.plot.ms_plot._time_ticks import get_time_formatter
16
- from vidavis.plot.ms_plot._locate_points import locate_point, locate_box
15
+ from vidavis.plot.ms_plot._check_raster_inputs import check_inputs
16
+ from vidavis.plot.ms_plot._locate_points import cursor_changed, points_changed, box_changed, locate_point, locate_box
17
17
  from vidavis.plot.ms_plot._ms_plot import MsPlot
18
18
  from vidavis.plot.ms_plot._ms_plot_constants import VIS_AXIS_OPTIONS, SPECTRUM_AXIS_OPTIONS, PS_SELECTION_OPTIONS, MS_SELECTION_OPTIONS
19
+ from vidavis.plot.ms_plot._plot_inputs import inputs_changed
19
20
  from vidavis.plot.ms_plot._raster_plot_gui import create_raster_gui
20
- from vidavis.plot.ms_plot._raster_plot_inputs import check_inputs
21
21
  from vidavis.plot.ms_plot._raster_plot import RasterPlot
22
+ from vidavis.plot.ms_plot._time_ticks import get_time_formatter
22
23
 
23
24
  class MsRaster(MsPlot):
24
25
  '''
@@ -44,21 +45,13 @@ class MsRaster(MsPlot):
44
45
  def __init__(self, ms=None, log_level="info", log_to_file=True, show_gui=False):
45
46
  super().__init__(ms, log_level, log_to_file, show_gui, "MsRaster")
46
47
  self._raster_plot = RasterPlot()
47
- self._plot_data = None
48
48
 
49
49
  # Calculations for color limits
50
50
  self._spw_stats = {}
51
51
  self._spw_color_limits = {}
52
52
 
53
53
  if show_gui:
54
- # For checking if plot inputs or cursor position changed and new plot or cursor location info is needed.
55
- self._last_plot_inputs = None
56
- self._last_style_inputs = None
57
- self._last_cursor = None
58
- self._last_box = None
59
-
60
- # Return plot for gui DynamicMap:
61
- # Empty plot when ms not set or plot fails
54
+ # Return plot for gui DynamicMap: empty plot when ms not set or plot fails
62
55
  self._empty_plot = self._create_empty_plot()
63
56
 
64
57
  # Set default style and plot inputs to use when launching gui
@@ -66,7 +59,7 @@ class MsRaster(MsPlot):
66
59
  self.plot()
67
60
  self._launch_gui()
68
61
 
69
- # Set filename TextInput to input ms, which triggers default plot
62
+ # Set filename TextInput widget to input ms, which triggers default plot
70
63
  if 'ms' in self._ms_info and self._ms_info['ms']:
71
64
  self._set_filename([self._ms_info['ms']]) # function expects list from FileBrowser widget
72
65
 
@@ -97,10 +90,8 @@ class MsRaster(MsPlot):
97
90
  string_exact_match (bool): whether to require exact matches for string and string list columns (default True) or partial matches (False).
98
91
  query (str): a Pandas query string to apply additional filtering.
99
92
  **kwargs (dict): keyword arguments representing summary column names and values.
100
- See summary(data_group) and data_groups() for selection options. Use keyword 'data_group_name' for data group selection.
101
- Summary column names include 'name', 'intents', 'shape', 'polarization', 'scan_name', 'spw_name',
102
- 'field_name', 'source_name', 'field_coords', 'start_frequency', 'end_frequency'.
103
- The selection is applied to the data within the MeasurementSets where applicable (polarization, scan_name, field_name)
93
+ See data_groups() and summary(data_group) and for selection keyword options. Use keyword 'data_group_name' for data group selection.
94
+ The selection is also applied to the data within the MeasurementSets where keyword is a coordinate (polarization, scan_name, field_name) and
104
95
  string_exact_match=True, else the entire MS is selected.
105
96
  Selections are cumulative until clear_selection() is called.
106
97
  Raises exception with message if selection fails.
@@ -156,8 +147,8 @@ class MsRaster(MsPlot):
156
147
  iter_axis=None, iter_range=None, subplots=None, color_mode=None, color_range=None, title=None, clear_plots=True):
157
148
  '''
158
149
  Create a raster plot of vis_axis data.
159
- Plot axes include data dimensions (time, baseline/antenna, frequency, polarization).
160
- The first spectral window (by id) and dimensions not set as plot axes (first value) will be automatically selected if no user selection has been made, unless aggregated.
150
+ Plot axes include data dimensions (time, baseline/antenna_name, frequency, polarization).
151
+ The first spectral window by time and dimensions not set as plot axes will be automatically selected by first value if no user selection has been made, unless aggregated.
161
152
 
162
153
  Args:
163
154
  x_axis (str): Plot x-axis. Default 'baseline' ('antenna_name' for spectrum data).
@@ -253,14 +244,20 @@ class MsRaster(MsPlot):
253
244
  for key, val in plot_inputs.items():
254
245
  self._plot_inputs[key] = val
255
246
 
256
- def _do_plot(self, plot_inputs):
247
+ def _do_plot(self, plot_inputs, is_gui_plot=False):
257
248
  ''' Create plot using plot inputs '''
258
249
  if not self._plot_init:
259
250
  self._init_plot(plot_inputs)
260
251
 
261
252
  # Select vis_axis data to plot and update selection; returns xarray Dataset
262
253
  raster_data = self._data.get_raster_data(plot_inputs)
263
- self._plot_data = raster_data
254
+
255
+ # Save plot data for plot location callbacks unless layout (location not supported)
256
+ if not self._is_layout():
257
+ if is_gui_plot:
258
+ self._gui_plot_data = set_index_coordinates(raster_data, (self._plot_inputs['x_axis'], self._plot_inputs['y_axis']))
259
+ else:
260
+ self._plot_data = set_index_coordinates(raster_data, (self._plot_inputs['x_axis'], self._plot_inputs['y_axis']))
264
261
 
265
262
  # Add params needed for plot: auto color range and ms name
266
263
  self._set_auto_color_range(plot_inputs) # set calculated limits if auto mode
@@ -301,10 +298,11 @@ class MsRaster(MsPlot):
301
298
  num_subplots = np.prod(subplots) if subplots else 1
302
299
  num_iter_plots = min(num_iter_plots, num_subplots) if num_subplots > 1 else num_iter_plots
303
300
  end_idx = start_idx + num_iter_plots
301
+ self._plot_inputs['iter_range'] = (start_idx, end_idx)
304
302
 
305
- # Set each iter value in ms_selection
306
- if 'ms_selection' not in plot_inputs:
307
- plot_inputs['ms_selection'] = {}
303
+ # Set each iter value in selection
304
+ if 'selection' not in plot_inputs:
305
+ plot_inputs['selection'] = {}
308
306
 
309
307
  for i in range(start_idx, end_idx):
310
308
  # Select iteration value and make plot
@@ -347,6 +345,22 @@ class MsRaster(MsPlot):
347
345
  self._logger.info("Maximum dimensions for selected spw: %s", self._data.get_max_data_dims())
348
346
  self._plot_init = True
349
347
 
348
+ def _is_layout(self):
349
+ ''' Determine if plot is a layout using plot inputs '''
350
+ # Check if subplots is a layout
351
+ if self._plot_inputs['subplots'] is None or self._plot_inputs['subplots'] == (1, 1):
352
+ return False
353
+
354
+ # Check if iteration set and iter_range more than one plot
355
+ iter_length = 0
356
+ if self._plot_inputs['iter_axis'] is not None:
357
+ iter_range = self._plot_inputs['iter_range']
358
+ iter_range = (0, 0) if iter_range is None else iter_range
359
+ iter_length = len(range(iter_range[0], iter_range[1] + 1))
360
+
361
+ # Also check if previous plot(s) not cleared
362
+ return iter_length > 1 or len(self._plots) > 0
363
+
350
364
  def _set_auto_color_range(self, plot_inputs):
351
365
  ''' Calculate stats for color limits for non-gui amplitude plots. '''
352
366
  color_mode = plot_inputs['color_mode']
@@ -438,13 +452,14 @@ class MsRaster(MsPlot):
438
452
  ### Main callback to create plot if inputs changed
439
453
  ###
440
454
  # pylint: disable=too-many-arguments, too-many-positional-arguments
441
- def _update_plot(self, ms, do_plot, x, y, bounds):
455
+ def _update_plot(self, ms, do_plot, x, y, data, bounds):
442
456
  ''' Create plot with inputs from GUI, or update cursor/box location.
443
457
  Callbacks:
444
458
  ms (first plot) - return plot with default inputs
445
459
  do_plot (Plot button clicked) - return plot with inputs from GUI
446
- x, y (cursor position) - update cursor location box
447
- bounds (box select) - update location of points in box in tab and log
460
+ x, y (PointerXY) - update cursor location box
461
+ data (PointDraw from point_draw tool) - update location of drawn points in tab and log
462
+ bounds (Bounds from box_select tool) - update location of points in box in tab and log
448
463
  This function *must* return plot, even if empty plot or last plot, for DynamicMap.
449
464
  '''
450
465
  # Remove toast notification and collapse selection accordion
@@ -458,13 +473,18 @@ class MsRaster(MsPlot):
458
473
 
459
474
  # User changed inputs without clicking Plot button, or callback for cursor/box.
460
475
  if not do_plot and not self._first_gui_plot:
461
- if self._cursor_changed(x, y):
476
+ if cursor_changed(x, y, self._last_cursor):
462
477
  # new cursor position - update cursor location box
463
478
  self._update_cursor_location(x, y)
464
479
  self._last_cursor = (x, y)
465
480
 
466
- if self._box_changed(bounds):
467
- # new box_select position - update location for points in box
481
+ if points_changed(data, self._last_points):
482
+ # new points position - update selected points location tab
483
+ self._update_points_location(data)
484
+ self._last_points = data
485
+
486
+ if box_changed(bounds, self._last_box):
487
+ # new box_select position - update selected box location tab
468
488
  self._update_box_location(bounds)
469
489
  self._last_box = bounds
470
490
 
@@ -484,9 +504,9 @@ class MsRaster(MsPlot):
484
504
  self.clear_selection()
485
505
  gui_plot = None
486
506
 
507
+ # Make plot if first plot or changed plot
487
508
  style_inputs = self._raster_plot.get_plot_params()['style']
488
- if self._inputs_changed(style_inputs):
489
- # First plot or changed plot
509
+ if inputs_changed(self._plot_inputs, self._last_plot_inputs) or inputs_changed(style_inputs, self._last_style_inputs):
490
510
  try:
491
511
  # Check inputs from GUI then plot
492
512
  self._plot_inputs['data_dims'] = self._ms_info['data_dims']
@@ -510,60 +530,25 @@ class MsRaster(MsPlot):
510
530
  self._last_style_inputs = style_inputs.copy()
511
531
  self._last_plot_inputs['ps_selection'] = self._plot_inputs['ps_selection'].copy()
512
532
  self._last_plot_inputs['ms_selection'] = self._plot_inputs['ms_selection'].copy()
533
+
534
+ # Save plot for return value if plot update not requested
535
+ self._last_gui_plot = gui_plot
536
+
537
+ # Remove selection not from user
513
538
  if self._first_gui_plot and not do_plot:
514
539
  self._plot_inputs['ps_selection'].clear()
515
540
  self._plot_inputs['ms_selection'].clear()
516
541
 
517
- # Save plot for return value if plot update not requested
518
- self._last_gui_plot = gui_plot
519
542
  self._first_gui_plot = False
520
543
 
521
- # Add plot inputs, change plot button to outline, and stop spinner
522
- self._update_plot_inputs()
544
+ # Add plot inputs to GUI, change plot button to outline, and stop spinner
545
+ self._show_plot_inputs()
523
546
  self._update_plot_status(False)
524
547
  self._update_plot_spinner(False)
525
548
 
526
549
  return gui_plot
527
550
  # pylint: enable=too-many-arguments, too-many-positional-arguments
528
551
 
529
- def _cursor_changed(self, x, y):
530
- ''' Check whether cursor position changed '''
531
- if not x and not y:
532
- return False # not cursor callback
533
- if self._last_cursor and self._last_cursor == (x, y):
534
- return False # same cursor
535
- return True # new cursor or cursor changed
536
-
537
- def _box_changed(self, bounds):
538
- ''' Check whether box position changed '''
539
- if not bounds:
540
- return False # no data, not box select callback
541
- if self._last_box and self._last_box == bounds:
542
- return False # same box
543
- return True # new box or box changed
544
-
545
- def _inputs_changed(self, style_inputs):
546
- ''' Check if inputs changed and need new plot '''
547
- if not self._last_plot_inputs:
548
- return True
549
-
550
- for key, val in self._plot_inputs.items():
551
- if not self._values_equal(val, self._last_plot_inputs[key]):
552
- return True
553
-
554
- for key, val in style_inputs.items():
555
- if not self._values_equal(val, self._last_style_inputs[key]):
556
- return True
557
- return False
558
-
559
- def _values_equal(self, val1, val2):
560
- ''' Test if values are set and equal, or not set (cannot compare value with None) '''
561
- if val1 is not None and val2 is not None: # both set
562
- return val1 == val2
563
- if val1 is None and val2 is None: # both None
564
- return True
565
- return False # one set and other is None
566
-
567
552
  def _do_gui_selection(self):
568
553
  ''' Apply selections selected in GUI '''
569
554
  if self._plot_inputs['ps_selection']:
@@ -594,7 +579,7 @@ class MsRaster(MsPlot):
594
579
  return layout_plot
595
580
 
596
581
  # Make single Overlay raster plot for DynamicMap
597
- plot = self._do_plot(self._plot_inputs)
582
+ plot = self._do_plot(self._plot_inputs, True)
598
583
  plot_params = self._raster_plot.get_plot_params()
599
584
 
600
585
  # Update color limits in gui with data range
@@ -606,7 +591,11 @@ class MsRaster(MsPlot):
606
591
 
607
592
  self._logger.info("Plot update complete")
608
593
  return plot.opts(
609
- hv.opts.QuadMesh(tools=['hover', 'box_select'])
594
+ hv.opts.QuadMesh(
595
+ tools=['hover', 'box_select'],
596
+ selection_fill_alpha=0.5, # dim selected areas of plot
597
+ nonselection_fill_alpha=1.0, # do not dim unselected areas of plot
598
+ )
610
599
  )
611
600
  except RuntimeError as e:
612
601
  error = f"Plot failed: {str(e)}"
@@ -631,7 +620,11 @@ class MsRaster(MsPlot):
631
620
  )
632
621
  )
633
622
  return plot.opts(
634
- hv.opts.QuadMesh(tools=['hover', 'box_select'])
623
+ hv.opts.QuadMesh(
624
+ tools=['hover', 'box_select'],
625
+ selection_fill_alpha=0.5, # dim selected areas of plot
626
+ nonselection_fill_alpha=1.0, # do not dim unselected areas of plot
627
+ )
635
628
  )
636
629
 
637
630
  def _set_plot_colorbar(self, plot, plot_params, plot_type):
@@ -834,15 +827,15 @@ class MsRaster(MsPlot):
834
827
  spinner = self._gui_layout[2][2][1]
835
828
  spinner.value = plot_clicked
836
829
 
837
- def _update_plot_status(self, inputs_changed):
838
- ''' Change button color when inputs change. '''
830
+ def _update_plot_status(self, plot_changed):
831
+ ''' Change button color when plot inputs change. '''
839
832
  if self._gui_layout:
840
833
  # Set button color
841
834
  button = self._gui_layout[2][2][0]
842
- button.button_style = 'solid' if inputs_changed else 'outline'
835
+ button.button_style = 'solid' if plot_changed else 'outline'
843
836
 
844
- def _update_plot_inputs(self):
845
- ''' Show inputs for raster plot in GUI '''
837
+ def _show_plot_inputs(self):
838
+ ''' Show inputs for raster plot in GUI tab '''
846
839
  if self._plot_params:
847
840
  inputs_column = self._gui_layout[0][1]
848
841
  inputs_column.clear()
@@ -856,14 +849,10 @@ class MsRaster(MsPlot):
856
849
  # Convert plot values to selection values to select plot data
857
850
  x_axis = self._plot_inputs['x_axis']
858
851
  y_axis = self._plot_inputs['y_axis']
859
- plot_data = set_index_coordinates(self._plot_data, (x_axis, y_axis))
860
852
  cursor_position = {x_axis: x, y_axis: y}
861
- cursor_location = locate_point(plot_data, cursor_position, self._plot_inputs['vis_axis'])
862
- self._update_cursor_box(cursor_location)
853
+ cursor_location = locate_point(self._gui_plot_data, cursor_position, self._plot_inputs['vis_axis'])
863
854
 
864
- def _update_cursor_box(self, cursor_location):
865
- ''' Update cursor location widget box with list of Panel StaticText widgets '''
866
- cursor_location_box = self._gui_layout[0][0][1] # row 0 tab 0 row 1 in column
855
+ cursor_location_box = self._gui_layout[0][0][1] # row[0] tabs[0] column[1]
867
856
  cursor_location_box.clear() # pn.WidgetBox
868
857
  location_layout = pn.Column(pn.widgets.StaticText(name="Cursor Location"))
869
858
  location_row = self._layout_point_location(cursor_location)
@@ -890,29 +879,54 @@ class MsRaster(MsPlot):
890
879
  location_row.append(location_col)
891
880
  return location_row
892
881
 
882
+ def _update_points_location(self, data):
883
+ ''' Show data values for points in point_draw in tab and log '''
884
+ points_locate_column = self._gui_layout[0][2] # row[0] tabs[2]
885
+ points_locate_column.clear()
886
+ points = list(zip(data['x'], data['y']))
887
+
888
+ if points:
889
+ self._logger.info("Locate selected points:")
890
+ x_axis = self._plot_inputs['x_axis']
891
+ y_axis = self._plot_inputs['y_axis']
892
+
893
+ for point in list(zip(data['x'], data['y'])):
894
+ # Locate point
895
+ point_position = {x_axis: point[0], y_axis: point[1]}
896
+ point_location = locate_point(self._gui_plot_data, point_position, self._plot_inputs['vis_axis'])
897
+ # Format location and add to points locate column
898
+ location_layout = self._layout_point_location(point_location)
899
+ points_locate_column.append(location_layout)
900
+ points_locate_column.append(pn.layout.Divider())
901
+
902
+ # Format and add to log
903
+ location_list = [f"{static_text.name}={static_text.value}" for static_text in point_location]
904
+ self._logger.info(", ".join(location_list))
905
+
893
906
  def _update_box_location(self, bounds):
894
- ''' Show data values for points in box in box location tab and log '''
895
- x_axis = self._plot_inputs['x_axis']
896
- y_axis = self._plot_inputs['y_axis']
897
- plot_data = set_index_coordinates(self._plot_data, (x_axis, y_axis))
898
- box_bounds = {x_axis: (bounds[0], bounds[2]), y_axis: (bounds[1], bounds[3])}
899
- npoints, point_locations = locate_box(plot_data, box_bounds, self._plot_inputs['vis_axis'])
900
-
901
- locate_column = self._gui_layout[0][2] # row 0 tab 2
902
- locate_column.clear()
903
- message = f"Locate {npoints} points"
904
- message += " (only first 100 shown):" if npoints > 100 else ":"
905
- self._logger.info(message)
906
-
907
- for point in point_locations:
908
- # Format and add to locate column
909
- location_layout = self._layout_point_location(point)
910
- locate_column.append(location_layout)
911
- locate_column.append(pn.layout.Divider())
912
-
913
- # Format and add to log
914
- location_list = [f"{static_text.name}={static_text.value}" for static_text in point]
915
- self._logger.info(", ".join(location_list))
907
+ ''' Show data values for points in box_select in tab and log '''
908
+ box_locate_column = self._gui_layout[0][3] # row[0] tabs[3]
909
+ box_locate_column.clear()
910
+ if bounds:
911
+ x_axis = self._plot_inputs['x_axis']
912
+ y_axis = self._plot_inputs['y_axis']
913
+ box_bounds = {x_axis: (bounds[0], bounds[2]), y_axis: (bounds[1], bounds[3])}
914
+ npoints, point_locations = locate_box(self._gui_plot_data, box_bounds, self._plot_inputs['vis_axis'])
915
+
916
+ message = f"Locate {npoints} points"
917
+ message += " (only first 100 shown):" if npoints > 100 else ":"
918
+ self._logger.info(message)
919
+ box_locate_column.append(pn.pane.Str(message))
920
+
921
+ for point in point_locations:
922
+ # Format and add to box locate column
923
+ location_layout = self._layout_point_location(point)
924
+ box_locate_column.append(location_layout)
925
+ box_locate_column.append(pn.layout.Divider())
926
+
927
+ # Format and add to log
928
+ location_list = [f"{static_text.name}={static_text.value}" for static_text in point]
929
+ self._logger.info(", ".join(location_list))
916
930
 
917
931
  ###
918
932
  ### Callbacks for widgets which update plot inputs
@@ -968,9 +982,7 @@ class MsRaster(MsPlot):
968
982
  else:
969
983
  self._plot_inputs['iter_range'] = None
970
984
  self._update_plot_status(True) # Change plot button to solid
971
- # pylint: enable=too-many-arguments, too-many-positional-arguments
972
985
 
973
- # pylint: disable=too-many-arguments, too-many-positional-arguments, unused-argument
974
986
  def _set_ps_selection(self, query, name, intents, scan_name, spw_name, field_name, source_name, line_name):
975
987
  ''' Select ProcessingSet from gui using summary columns '''
976
988
  inputs = locals()
@@ -5,7 +5,7 @@ Check inputs to MsRaster plot() or its GUI
5
5
  from vidavis.plot.ms_plot._ms_plot_constants import VIS_AXIS_OPTIONS, AGGREGATOR_OPTIONS
6
6
 
7
7
  def check_inputs(inputs):
8
- ''' Check plot input types, and axis input values. '''
8
+ ''' Check plot input types, and axis (plot, agg, iter) input values. '''
9
9
  _set_baseline_antenna_axis(inputs)
10
10
  _check_axis_inputs(inputs)
11
11
  _check_agg_inputs(inputs)
@@ -10,6 +10,32 @@ import panel as pn
10
10
 
11
11
  from vidavis.plot.ms_plot._ms_plot_constants import TIME_FORMAT
12
12
 
13
+ def cursor_changed(x, y, last_cursor):
14
+ ''' Check whether cursor position changed '''
15
+ if not x and not y:
16
+ return False # not cursor callback
17
+ if last_cursor and last_cursor == (x, y):
18
+ return False # same cursor
19
+ return True # new cursor or cursor changed
20
+
21
+ def points_changed(data, last_points):
22
+ ''' Check whether point positions changed '''
23
+ # No data = {'x': [], 'y': []}
24
+ if len(data['x']) == 0 and len(data['y']) == 0:
25
+ return False # not points callback
26
+ if last_points and last_points == data:
27
+ return False # same points
28
+ return True # new points, points changed, or points deleted
29
+
30
+ def box_changed(bounds, last_box):
31
+ ''' Check whether box position changed '''
32
+ # No bounds = None
33
+ if not bounds:
34
+ return False # no data, not box select callback
35
+ if last_box and last_box == bounds:
36
+ return False # same box
37
+ return True # new box, box changed, or box deleted
38
+
13
39
  def locate_point(xds, position, vis_axis):
14
40
  '''
15
41
  Get cursor location as values of coordinates and data vars.
@@ -47,7 +73,7 @@ def locate_box(xds, bounds, vis_axis):
47
73
  selection = {}
48
74
  for coord, val in bounds.items():
49
75
  # Round index values to int for selection
50
- selection[coord] = slice(_round_index_value(coord, val[0]), _round_index_value(coord, val[1]))
76
+ selection[coord] = slice(_get_selection_value(coord, val[0]), _get_selection_value(coord, val[1]))
51
77
  sel_xds = xds.sel(indexers=None, method=None, tolerance=None, drop=False, **selection)
52
78
 
53
79
  x_coord, y_coord = bounds.keys()
@@ -81,9 +107,9 @@ def _get_point_location(xds, position, vis_axis):
81
107
 
82
108
  if xds:
83
109
  try:
84
- # Round index coordinates to int for selection
85
110
  for coord, value in position.items():
86
- position[coord] = _round_index_value(coord, value)
111
+ # Round index coordinates to int and convert time to datetime if float for selection
112
+ position[coord] = _get_selection_value(coord, value)
87
113
 
88
114
  sel_xds = xds.sel(indexers=None, method='nearest', tolerance=None, drop=False, **position)
89
115
  for coord in sel_xds.coords:
@@ -100,7 +126,7 @@ def _get_point_location(xds, position, vis_axis):
100
126
  names = ['U', 'V', 'W']
101
127
  for i, name in enumerate(names):
102
128
  values[name] = val[i]
103
- units[name] = unit[i]
129
+ units[name] = unit
104
130
  else:
105
131
  values[data_var] = val
106
132
  units[data_var] = unit
@@ -112,9 +138,15 @@ def _get_point_location(xds, position, vis_axis):
112
138
  values[vis_axis.upper()] = values.pop('VISIBILITY')
113
139
  return values, units
114
140
 
115
- def _round_index_value(coord, value):
116
- ''' Round index coordinates to int for selecction '''
117
- return round(value) if coord in ['baseline', 'antenna_name', 'polarization'] else value
141
+ def _get_selection_value(coord, value):
142
+ ''' Convert index coordinates to int and float time coordinate to datetime '''
143
+ if coord in ['baseline', 'antenna_name', 'polarization']:
144
+ # Round index coordinates to int for selecction
145
+ value = round(value)
146
+ elif coord == 'time' and isinstance(value, float):
147
+ # Bokeh datetime values are floating-point numbers: milliseconds since the Unix epoch
148
+ value = to_datetime(value, unit='ms', origin='unix')
149
+ return value
118
150
 
119
151
  def _get_xda_val_unit(xda):
120
152
  ''' Return value and unit of xda (selected so only one value) '''
@@ -6,7 +6,7 @@ import os
6
6
  import time
7
7
 
8
8
  from bokeh.io import export_png, export_svg
9
- from bokeh.plotting import save, show
9
+ from bokeh.plotting import save
10
10
  import hvplot
11
11
  import holoviews as hv
12
12
  import numpy as np
@@ -50,6 +50,19 @@ class MsPlot:
50
50
  pn.config.notifications = True
51
51
  self._toast = None # for destroy() with new plot or new notification
52
52
 
53
+ # Initialize gui panel for callbacks
54
+ self._gui_layout = None
55
+ self._first_gui_plot = True
56
+ self._last_gui_plot = None
57
+ self._gui_plot_data = None
58
+
59
+ # For _update_plot callback: check which inputs and point positions changed
60
+ self._last_plot_inputs = None
61
+ self._last_style_inputs = None
62
+ self._last_cursor = None
63
+ self._last_points = None
64
+ self._last_box = None
65
+
53
66
  # Initialize plot inputs and params
54
67
  self._plot_inputs = {'selection': {}}
55
68
  self._plot_params = None
@@ -59,11 +72,9 @@ class MsPlot:
59
72
  self._plots_locked = False
60
73
  self._plots = []
61
74
 
62
- # Initialize gui
63
- if show_gui:
64
- self._gui_layout = None
65
- self._first_gui_plot = True
66
- self._last_gui_plot = None
75
+ # Initialize show() panel for callbacks
76
+ self._show_layout = None
77
+ self._plot_data = None
67
78
 
68
79
  # Set data (if ms)
69
80
  self._data = None
@@ -159,10 +170,10 @@ class MsPlot:
159
170
  column = pn.Column()
160
171
  for param in self._plot_params:
161
172
  column.append(pn.pane.Str(param))
162
- tabs = pn.Tabs(('Plot', bokeh_fig), ('Plot Inputs', column))
163
- tabs.show(title=self._app_name, threaded=True)
173
+ self._show_layout = pn.Tabs(('Plot', bokeh_fig), ('Plot Inputs', column))
164
174
  else:
165
- show(bokeh_fig)
175
+ self._show_layout = pn.pane.Bokeh(bokeh_fig)
176
+ self._show_layout.show(title=self._app_name, threaded=True)
166
177
 
167
178
  def save(self, filename='ms_plot.png', fmt='auto', width=900, height=600):
168
179
  '''
@@ -0,0 +1,19 @@
1
+ ''' Utilities for inputs to MeasurementSet plots '''
2
+
3
+ def inputs_changed(plot_inputs, last_plot_inputs):
4
+ ''' Check if inputs changed and need new plot '''
5
+ if not last_plot_inputs:
6
+ return True
7
+
8
+ for key, val in plot_inputs.items():
9
+ if not _values_equal(val, last_plot_inputs[key]):
10
+ return True
11
+ return False
12
+
13
+ def _values_equal(val1, val2):
14
+ ''' Test if values are set and equal, or not set (cannot compare value with None) '''
15
+ if val1 is not None and val2 is not None: # both set
16
+ return val1 == val2
17
+ if val1 is None and val2 is None: # both None
18
+ return True
19
+ return False # one set and other is None
@@ -9,9 +9,42 @@ from vidavis.plot.ms_plot._ms_plot_selectors import (file_selector, title_select
9
9
 
10
10
  def create_raster_gui(callbacks, data_dims, x_axis, y_axis):
11
11
  ''' Use Holoviz Panel to create a dashboard for plot inputs and raster plot display. '''
12
- # ------------------
13
- # PLOT INPUTS COLUMN
14
- # ------------------
12
+ # Accordion of widgets for plot inputs
13
+ selectors = get_plot_input_selectors(callbacks, data_dims, x_axis, y_axis)
14
+
15
+ # Plot button and spinner while plotting
16
+ init_plot = plot_starter(callbacks['plot_updating'])
17
+
18
+ # Dynamic map for plot, with callback when inputs change or location needed
19
+ dmap, points = get_plot_dmap(callbacks, selectors, init_plot)
20
+
21
+ return pn.Row(
22
+ pn.Tabs( # Row [0]
23
+ ('Plot', # Tabs[0]
24
+ pn.Column(
25
+ dmap * points, # [0] plot with hv.Points overlay for point_draw
26
+ pn.WidgetBox(), # [1] cursor location
27
+ )
28
+ ),
29
+ ('Plot Inputs', pn.Column()), # Tabs[1]
30
+ ('Locate Selected Points', pn.Column()), # Tabs[2]
31
+ ('Locate Selected Box', pn.Column()), # Tabs[3]
32
+ sizing_mode='stretch_width',
33
+ ),
34
+ pn.Spacer(width=10), # Row [1]
35
+ pn.Column( # Row [2]
36
+ pn.Spacer(height=25), # Column[0]
37
+ selectors, # Column[1]
38
+ init_plot, # Column[2]
39
+ width_policy='min',
40
+ width=400,
41
+ sizing_mode='stretch_height',
42
+ ),
43
+ sizing_mode='stretch_height',
44
+ )
45
+
46
+ def get_plot_input_selectors(callbacks, data_dims, x_axis, y_axis):
47
+ ''' Create accordion of widgets for plot inputs selection '''
15
48
  # Select MS
16
49
  file_selectors = file_selector('Path to MeasurementSet (ms or zarr) for plot', '~' , callbacks['filename'])
17
50
 
@@ -47,50 +80,26 @@ def create_raster_gui(callbacks, data_dims, x_axis, y_axis):
47
80
  ("Plot title", title_input), # [6]
48
81
  )
49
82
  selectors.toggle = True
83
+ return selectors
50
84
 
51
- # Plot button and spinner while plotting
52
- init_plot = plot_starter(callbacks['plot_updating'])
53
-
54
- # ----------------------------------------
55
- # PLOT WITH CURSOR POSITION AND BOX SELECT
56
- # ----------------------------------------
57
- # Connect plot to filename and plot button; add streams for cursor position and selected box
58
- # 'update_plot' callback must have parameters (ms, do_plot, x, y, data)
85
+ def get_plot_dmap(callbacks, selectors, init_plot):
86
+ ''' Dynamic map for updating plot from callback function '''
87
+ # Connect plot to filename and plot button; add streams for cursor position, drawn points, and selected box
88
+ # 'update_plot' callback must have parameters (ms, do_plot, x, y, data, bounds)
89
+ points = hv.Points([]).opts(
90
+ size=5,
91
+ fill_color='white'
92
+ )
59
93
  dmap = hv.DynamicMap(
60
94
  pn.bind(
61
95
  callbacks['update_plot'],
62
- ms=file_selectors[0][0],
96
+ ms=selectors[0][0][0],
63
97
  do_plot=init_plot[0],
64
98
  ),
65
99
  streams=[
66
- hv.streams.PointerXY(), # cursor location (x, y)
67
- hv.streams.BoundsXY() # box location (bounds)
100
+ hv.streams.PointerXY(), # cursor location (x, y)
101
+ hv.streams.PointDraw(source=points), # fixed cursor location (data)
102
+ hv.streams.BoundsXY() # box location (bounds)
68
103
  ]
69
104
  )
70
-
71
- # ----------------------------------------------
72
- # GUI LAYOUT OF PLOT TABS AND PLOT INPUTS COLUMN
73
- # ----------------------------------------------
74
- return pn.Row(
75
- pn.Tabs( # Row [0]
76
- ('Plot',
77
- pn.Column( # Tabs [0]
78
- dmap, # [0]
79
- pn.WidgetBox(), # [1] cursor location
80
- )
81
- ),
82
- ('Plot Inputs', pn.Column()), # Tabs [1]
83
- ('Locate Selected Box', pn.Column()), # Tabs [2]
84
- sizing_mode='stretch_width',
85
- ),
86
- pn.Spacer(width=10), # Row [1]
87
- pn.Column( # Row [2]
88
- pn.Spacer(height=25), # Column [0]
89
- selectors, # Column [1]
90
- init_plot, # Column [2]
91
- width_policy='min',
92
- width=400,
93
- sizing_mode='stretch_height',
94
- ),
95
- sizing_mode='stretch_height',
96
- )
105
+ return dmap, points
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vidavis
3
- Version: 0.0.11
3
+ Version: 0.0.13
4
4
  Summary: Radio astronomy visibility data visualization
5
5
  License: LGPL
6
6
  Author-email: Darrell Schiebel <darrell@schiebel.us>,Pam Harris <pharris@nrao.edu>
@@ -1,7 +1,7 @@
1
1
  vidavis/LICENSE.rst,sha256=qzGpkvhDzf_MgF1PIn6rCmYPrcEhkfrBUchosLJj-U4,26371
2
2
  vidavis/__init__.py,sha256=FVM92yTXUplR7tVHiGao0Y3Hd80pRTrwVIYDLjzICws,1709
3
3
  vidavis/apps/__init__.py,sha256=ZQ5v1VFtjn3ztmuOHLOk5WbC1uLNIgL9rbHQ4v0zJwY,87
4
- vidavis/apps/_ms_raster.py,sha256=lbq9GRGeyIV1a_kyS8nU0XfadMwQbOC7JZj6Zcp2uo8,47260
4
+ vidavis/apps/_ms_raster.py,sha256=bWe7N7lbwNYzPl1n-_Y_qBErDddxVe8jnPRYqOxylSI,48397
5
5
  vidavis/bokeh/__init__.py,sha256=gdPPxBCe0enCSjvPFxkgMlhpVnAMFXKcw9GN28tVdXM,95
6
6
  vidavis/bokeh/_palette.py,sha256=gzfJHuUgqxd8hJpZe-gQPFTCPq9f5I8uLEkHAK5FNDM,2480
7
7
  vidavis/data/__init__.py,sha256=-RDRe0PYK6vPlhdRV2Dy1vGbnDGoXWDATmfxaR-gXcE,48
@@ -18,21 +18,22 @@ vidavis/data/measurement_set/processing_set/_ps_stats.py,sha256=4uLImKCANXLUM8jO
18
18
  vidavis/data/measurement_set/processing_set/_xds_data.py,sha256=qLO2VkLINkSAQ7CGRFmpWQYpHrP4XoJJkwA4pp9DO8M,5253
19
19
  vidavis/plot/__init__.py,sha256=thxe5vAGdpEiqoKPHLJoWUqKMVrUVx0ajpsGf5pVP98,95
20
20
  vidavis/plot/ms_plot/__init__.py,sha256=wY0_7gY9M6K1D6tKQsr89L_uSs3seJlD-uicx7dx5Mo,74
21
- vidavis/plot/ms_plot/_locate_points.py,sha256=Phzj-6wQtbLcuzM0syouG80Sk0fjPKG1TWboOmiLRrw,6164
22
- vidavis/plot/ms_plot/_ms_plot.py,sha256=AZe4WbW5QCjX5hIir1W4H8Nd69NvxBQUiJhIHPCOTGE,12764
21
+ vidavis/plot/ms_plot/_check_raster_inputs.py,sha256=a7u5wlDKTxWYW36-Xp3xd4c756SbYURdFkGHbUaX440,4786
22
+ vidavis/plot/ms_plot/_locate_points.py,sha256=GvuS5kDhIYancc314ofLoYtoRhT_jagG8HpzchhdPds,7474
23
+ vidavis/plot/ms_plot/_ms_plot.py,sha256=RShzvWzHq7iA9awuaWO8GiB1NkWuYkcNOMdToLWPnes,13246
23
24
  vidavis/plot/ms_plot/_ms_plot_constants.py,sha256=cX_TQhKJ3hJzPuRYmuRJxue1sjq82yl_ZN2_w6TshmI,930
24
25
  vidavis/plot/ms_plot/_ms_plot_selectors.py,sha256=BZQwARvMPdk78n6Rh2tOaSc8GenZBrxHZb14oFD9gJM,10785
26
+ vidavis/plot/ms_plot/_plot_inputs.py,sha256=pZL63n9FHSoo9cntf7lppQj44nkpiwnCz6VH-9Oh6ho,671
25
27
  vidavis/plot/ms_plot/_raster_plot.py,sha256=lNa9i_eJ8F8Fc2zcHLRcaxKKOELk3x_QmXT__T76pg8,10999
26
- vidavis/plot/ms_plot/_raster_plot_gui.py,sha256=wpVFnnX9jL4G4b9OhCxgGpqrqUq4cvDLy4IIzYRMR18,3738
27
- vidavis/plot/ms_plot/_raster_plot_inputs.py,sha256=vPjZPDuB26ENxwv4z7dUa_c5TqByXejScY6hqEnLFLU,4768
28
+ vidavis/plot/ms_plot/_raster_plot_gui.py,sha256=Kb0BLmkwtYHCqlVaSipbTTStst8RYggqwf1Wf5v8F4Q,4261
28
29
  vidavis/plot/ms_plot/_time_ticks.py,sha256=j-DcPh7RfGE8iX2bPjLQDQPIbiAbmjiEWQnKmdMWA3I,1773
29
30
  vidavis/plot/ms_plot/_xds_plot_axes.py,sha256=EeWvAbiKV33nEWdI8V3M0uwLTnycq4bFYBOyVWkxCu0,4429
30
31
  vidavis/toolbox/__init__.py,sha256=jqFa-eziVz_frNnXxwjJFK36qNpz1H38s-VlpBcq-R8,1402
31
32
  vidavis/toolbox/_app_context.py,sha256=H7gtF8RrAH46FqDcMobv3KM1Osbnapgu6aTG-m3VCWA,3049
32
33
  vidavis/toolbox/_logging.py,sha256=OEisrd8FM8VTNBMc7neLh9ekelf29ZILYB5pScebly0,2739
33
34
  vidavis/toolbox/_static.py,sha256=HJLMtClppgOJXWAtV6Umn5EqN80u0oZiIouQ1JsB9PM,2346
34
- vidavis/__version__.py,sha256=BkvGdgIByn5SRGserbTFWSVZfsbl4zCa8iLXMuX-QKg,22
35
- vidavis-0.0.11.dist-info/WHEEL,sha256=B19PGBCYhWaz2p_UjAoRVh767nYQfk14Sn4TpIZ-nfU,87
36
- vidavis-0.0.11.dist-info/METADATA,sha256=ovNa8Kje7toJxrjMGDmf3h_gudJRpck9XPT5rtGIp1g,2238
37
- vidavis-0.0.11.dist-info/licenses/LICENSE,sha256=IMF9i4xIpgCADf0U-V1cuf9HBmqWQd3qtI3FSuyW4zE,26526
38
- vidavis-0.0.11.dist-info/RECORD,,
35
+ vidavis/__version__.py,sha256=6iFz7cldtJZlw_FSwnt97X1e8jfQ-fdVhcVMqnAiWh8,22
36
+ vidavis-0.0.13.dist-info/WHEEL,sha256=B19PGBCYhWaz2p_UjAoRVh767nYQfk14Sn4TpIZ-nfU,87
37
+ vidavis-0.0.13.dist-info/METADATA,sha256=7fBkDctO-V9veQD-m1BbnimzfHgH-PXvEvmhNE_7z3I,2238
38
+ vidavis-0.0.13.dist-info/licenses/LICENSE,sha256=IMF9i4xIpgCADf0U-V1cuf9HBmqWQd3qtI3FSuyW4zE,26526
39
+ vidavis-0.0.13.dist-info/RECORD,,