vidavis 0.0.5__py3-none-any.whl → 0.0.7__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.5'
1
+ __version__ = '0.0.7'
@@ -7,15 +7,15 @@ import time
7
7
  from bokeh.models.formatters import NumeralTickFormatter
8
8
  import holoviews as hv
9
9
  import numpy as np
10
- import panel as pn
11
10
  from pandas import to_datetime
11
+ import panel as pn
12
12
 
13
13
  from vidavis.bokeh._palette import available_palettes
14
+ from vidavis.data.measurement_set.processing_set._ps_coords import set_index_coordinates
14
15
  from vidavis.plot.ms_plot._time_ticks import get_time_formatter
15
16
  from vidavis.plot.ms_plot._ms_plot import MsPlot
16
- from vidavis.plot.ms_plot._ms_plot_constants import VIS_AXIS_OPTIONS, SPECTRUM_AXIS_OPTIONS, PS_SELECTION_OPTIONS, MS_SELECTION_OPTIONS
17
- from vidavis.plot.ms_plot._ms_plot_selectors import (file_selector, title_selector, style_selector, axis_selector,
18
- aggregation_selector, iteration_selector, selection_selector, plot_starter)
17
+ from vidavis.plot.ms_plot._ms_plot_constants import VIS_AXIS_OPTIONS, SPECTRUM_AXIS_OPTIONS, PS_SELECTION_OPTIONS, MS_SELECTION_OPTIONS, TIME_FORMAT
18
+ from vidavis.plot.ms_plot._raster_plot_gui import create_raster_gui
19
19
  from vidavis.plot.ms_plot._raster_plot_inputs import check_inputs
20
20
  from vidavis.plot.ms_plot._raster_plot import RasterPlot
21
21
 
@@ -43,23 +43,21 @@ class MsRaster(MsPlot):
43
43
  def __init__(self, ms=None, log_level="info", log_to_file=True, show_gui=False):
44
44
  super().__init__(ms, log_level, log_to_file, show_gui, "MsRaster")
45
45
  self._raster_plot = RasterPlot()
46
+ self._plot_data = None
46
47
 
47
48
  # Calculations for color limits
48
49
  self._spw_stats = {}
49
50
  self._spw_color_limits = {}
50
51
 
51
52
  if show_gui:
52
- # GUI based on panel widgets
53
- self._gui_layout = None
54
-
55
- # Check if plot inputs changed and new plot is needed.
56
- # GUI can change subparams which do not change plot
53
+ # For checking if plot inputs or cursor position changed and new plot or cursor location info is needed.
57
54
  self._last_plot_inputs = None
58
55
  self._last_style_inputs = None
59
- # Last plot when no new plot created (plot inputs same) or is iter Layout plot (opened in tab)
60
- self._last_gui_plot = None
56
+ self._last_cursor = None
61
57
 
62
58
  # Return plot for gui DynamicMap:
59
+ # Last plot when no new plot created (plot inputs same) or is iter Layout plot (opened in tab)
60
+ self._last_gui_plot = None
63
61
  # Empty plot when ms not set or plot fails
64
62
  self._empty_plot = self._create_empty_plot()
65
63
 
@@ -165,25 +163,14 @@ class MsRaster(MsPlot):
165
163
  x_axis (str): Plot x-axis. Default 'baseline' ('antenna_name' for spectrum data).
166
164
  y_axis (str): Plot y-axis. Default 'time'.
167
165
  vis_axis (str): Complex visibility component to plot (amp, phase, real, imag). Default 'amp'.
168
- Call data_groups() to see options.
169
- MeasurementSet selection:
170
- 'data_group': name for correlated data, flags, weights, and uvw. Default value 'base'.
171
- Use data_groups() to get data group names.
172
- Dimensions:
173
- Visibility dimensions: 'baseline' 'time', 'frequency', 'polarization'
174
- Spectrum dimensions: 'antenna_name', 'time', 'frequency', 'polarization'
175
- Default value is index 0 (after user selection) for non-axis dimensions unless aggregated.
176
- Use antennas() to get antenna names. Select 'baseline' as "<name1> & <name2>".
177
- Use summary() to list frequencies and polarizations.
178
- TODO: how to select time?
179
- aggregator (str): reduction for rasterization. Default None.
166
+ aggregator (None, str): reduction for rasterization. Default None.
180
167
  Options include 'max', 'mean', 'min', 'std', 'sum', 'var'.
181
- agg_axis (str, list): which dimension to apply aggregator across. Default None.
168
+ agg_axis (None, str, list): which dimension to apply aggregator across. Default None.
182
169
  Options include one or more dimensions.
183
170
  If agg_axis is None and aggregator is set, aggregates over all non-axis dimensions.
184
171
  If one agg_axis is selected, the non-agg dimension will be selected.
185
- iter_axis (str): dimension over which to iterate values (using iter_range).
186
- iter_range (tuple): (start, end) inclusive index values for iteration plots.
172
+ iter_axis (None, str): dimension over which to iterate values (using iter_range).
173
+ iter_range (None, tuple): (start, end) inclusive index values for iteration plots.
187
174
  Default (0, 0) (first iteration only). Use (0, -1) for all iterations.
188
175
  If subplots is a grid, the range is limited by the grid size.
189
176
  If subplots is a single plot, all iteration plots in the range can be saved using export_range in save().
@@ -194,12 +181,12 @@ class MsRaster(MsPlot):
194
181
  Options include None (use data limits), 'auto' (calculate limits for amplitude), and 'manual' (use range in color_range).
195
182
  'auto' is equivalent to None if vis_axis is not 'amp'.
196
183
  When subplots is set, the 'auto' or 'manual' range will be used for all plots.
197
- color_range (tuple): (min, max) of colorbar to use if color_mode is 'manual'.
198
- title (str): Plot title, default None (no title)
184
+ color_range (None, tuple): (min, max) of colorbar to use if color_mode is 'manual'.
185
+ title (None, str): Plot title, default None (no title)
199
186
  Set title='ms' to generate title from ms name and iter_axis value, if any.
200
187
  clear_plots (bool): whether to clear list of plots. Default True.
201
188
 
202
- If not show_gui and plotting is successful, use show() or save() to view/save the plot only.
189
+ If plot is successful, use show() or save() to view/save the plot.
203
190
  '''
204
191
  inputs = locals() # collect arguments into dict (not unused as pylint complains!)
205
192
  if self._plot_inputs['selection']:
@@ -244,10 +231,13 @@ class MsRaster(MsPlot):
244
231
  Args:
245
232
  filename (str): Name of file to save. Default '': the plot will be saved as {ms}_raster.{ext}.
246
233
  If fmt is not set for extension, plot will be saved as .png.
247
- fmt (str): Format of file to save ('png', 'html', or 'gif').
234
+ fmt (str): Format of file to save ('png', 'svg', or 'html').
248
235
  Default 'auto': inferred from filename extension.
249
- width (int): width of exported plot.
250
- height (int): height of exported plot.
236
+ width (int): width of exported plot in pixels.
237
+ height (int): height of exported plot in pixels.
238
+
239
+ If subplots defines a grid layout, width and height describe the size of each plot in the layout.
240
+ The layout plot size will be (width * columns, height * rows) pixels.
251
241
 
252
242
  If iteration plots were created:
253
243
  If subplots is a grid, the layout plot will be saved to a single file.
@@ -270,6 +260,7 @@ class MsRaster(MsPlot):
270
260
 
271
261
  # Select vis_axis data to plot and update selection; returns xarray Dataset
272
262
  raster_data = self._data.get_raster_data(plot_inputs)
263
+ self._plot_data = raster_data
273
264
 
274
265
  # Add params needed for plot: auto color range and ms name
275
266
  self._set_auto_color_range(plot_inputs) # set calculated limits if auto mode
@@ -423,85 +414,34 @@ class MsRaster(MsPlot):
423
414
  ### -----------------------------------------------------------------------
424
415
  def _launch_gui(self):
425
416
  ''' Use Holoviz Panel to create a dashboard for plot inputs. '''
426
- # Select MS
427
- file_selectors = file_selector('Path to MeasurementSet (ms or zarr) for plot', '~' , self._set_filename)
428
-
429
- # Select style - colormaps, colorbar, color limits
430
- style_selectors = style_selector(self._set_style_params, self._set_color_range)
431
-
432
- # Set title
433
- title_input = title_selector(self._set_title)
434
-
435
- # Select x, y, and vis axis
417
+ callbacks = {
418
+ 'filename': self._set_filename,
419
+ 'style': self._set_style_params,
420
+ 'color': self._set_color_range,
421
+ 'axes': self._set_axes,
422
+ 'select_ps': self._set_ps_selection,
423
+ 'select_ms': self._set_ms_selection,
424
+ 'aggregation': self._set_aggregation,
425
+ 'iter_values': self._set_iter_values,
426
+ 'iteration': self._set_iteration,
427
+ 'title': self._set_title,
428
+ 'plot_updating': self._update_plot_spinner,
429
+ 'update_plot': self._update_plot,
430
+ }
431
+ data_dims = self._ms_info['data_dims'] if 'data_dims' in self._ms_info else None
436
432
  x_axis = self._plot_inputs['x_axis']
437
433
  y_axis = self._plot_inputs['y_axis']
438
- data_dims = self._ms_info['data_dims'] if 'data_dims' in self._ms_info else None
439
- axis_selectors = axis_selector(x_axis, y_axis, data_dims, True, self._set_axes)
440
-
441
- # Select from ProcessingSet and MeasurementSet
442
- selection_selectors = selection_selector(self._set_ps_selection, self._set_ms_selection)
443
-
444
- # Generic axis options, updated when ms is set
445
- axis_options = data_dims if data_dims else []
446
-
447
- # Select aggregator and axes to aggregate
448
- agg_selectors = aggregation_selector(axis_options, self._set_aggregation)
449
-
450
- # Select iter_axis and iter value or range
451
- iter_selectors = iteration_selector(axis_options, self._set_iter_values, self._set_iteration)
452
-
453
- # Put user input widgets in accordion with only one card active at a time
454
- selectors = pn.Accordion(
455
- ("Select file", file_selectors), # [0]
456
- ("Plot style", style_selectors), # [1]
457
- ("Data Selection", selection_selectors), # [2]
458
- ("Plot axes", axis_selectors), # [3]
459
- ("Aggregation", agg_selectors), # [4]
460
- ("Iteration", iter_selectors), # [5]
461
- ("Plot title", title_input), # [6]
462
- )
463
- selectors.toggle = True
464
-
465
- # Plot button and spinner while plotting
466
- init_plot = plot_starter(self._update_plot_spinner)
467
-
468
- # Connect plot to filename and selector widgets
469
- dmap = hv.DynamicMap(
470
- pn.bind(
471
- self._update_plot,
472
- ms=file_selectors[0][0],
473
- do_plot=init_plot[0],
474
- ),
475
- )
476
-
477
- # Layout plot and input widgets in a row
478
- self._gui_layout = pn.Row(
479
- pn.Tabs( # [0]
480
- ('Plot', dmap), # [0]
481
- ('Plot Inputs', pn.Column()), # [1]
482
- sizing_mode='stretch_width',
483
- ),
484
- pn.Spacer(width=10), # [1]
485
- pn.Column( # [2]
486
- pn.Spacer(height=25), # [0]
487
- selectors, # [1]
488
- init_plot, # [2]
489
- width_policy='min',
490
- width=400,
491
- sizing_mode='stretch_height',
492
- ),
493
- sizing_mode='stretch_height',
494
- )
495
-
434
+ self._gui_layout = create_raster_gui(callbacks, data_dims, x_axis, y_axis)
496
435
  # Show gui
497
- # print("gui layout:", self._gui_layout) # for debugging
498
436
  self._gui_layout.show(title=self._app_name, threaded=True)
499
437
 
500
438
  ###
501
439
  ### Main callback to create plot if inputs changed
502
440
  ###
503
- def _update_plot(self, ms, do_plot):
504
- ''' Create plot with inputs from GUI. Must return plot, even if empty plot, for DynamicMap. '''
441
+ def _update_plot(self, ms, do_plot, x, y):
442
+ ''' Create plot with inputs from GUI, or update cursor (x, y) information.
443
+ Must return plot, even if empty plot or same plot as before, for DynamicMap.
444
+ '''
505
445
  if self._toast:
506
446
  self._toast.destroy()
507
447
 
@@ -510,12 +450,18 @@ class MsRaster(MsPlot):
510
450
  # Launched GUI with no MS
511
451
  return self._empty_plot
512
452
 
513
- # If not first plot, user has to click Plot button (do_plot=True).
453
+ # If not first plot, user has to click Plot button (do_plot=True) to update plot.
454
+ # Respond to pointer callback only.
514
455
  first_plot = not self._last_gui_plot
515
456
  if not do_plot and not first_plot:
457
+ if not self._last_cursor or (self._last_cursor[0] != x or self._last_cursor[1] != y):
458
+ # Callback is for new cursor location
459
+ self._update_cursor_location(x, y)
460
+ self._last_cursor = (x, y)
516
461
  # Not ready to update plot yet, return last plot.
517
462
  return self._last_gui_plot
518
463
 
464
+ # Callback is for initial plot for input ms, or user clicked Plot button
519
465
  if (self._set_ms(ms) or first_plot) and self._data and self._data.is_valid():
520
466
  # New MS set and is valid
521
467
  self._update_gui_axis_options()
@@ -811,7 +757,6 @@ class MsRaster(MsPlot):
811
757
  #filename_input.width = len(filename[-1])
812
758
  filename_input.value = filename[-1]
813
759
 
814
-
815
760
  def _set_iter_values(self, iter_axis):
816
761
  ''' Set up player with values when iter_axis is selected '''
817
762
  iter_axis = None if iter_axis == 'None' else iter_axis
@@ -876,6 +821,98 @@ class MsRaster(MsPlot):
876
821
  for param in self._plot_params:
877
822
  inputs_column.append(pn.pane.Str(param))
878
823
 
824
+ def _update_cursor_location(self, x, y):
825
+ ''' Show metadata for cursor x,y position '''
826
+ # Convert plot values to selection values to select plot data
827
+ x_axis = self._plot_inputs['x_axis']
828
+ y_axis = self._plot_inputs['y_axis']
829
+ x = round(x) if x_axis == 'baseline' else x
830
+ y = round(y) if y_axis == 'baseline' else y
831
+ position = {x_axis: x, y_axis: y}
832
+ location_values, units = self._get_cursor_location_values(position)
833
+ self._update_cursor_box(location_values, units)
834
+
835
+ def _get_cursor_location_values(self, cursor_position):
836
+ values = cursor_position.copy()
837
+ units = {}
838
+
839
+ if self._plot_data:
840
+ try:
841
+ xds = set_index_coordinates(self._plot_data, tuple(cursor_position.keys()))
842
+ sel_xds = xds.sel(indexers=None, method='nearest', tolerance=None, drop=False, **cursor_position)
843
+ for coord in sel_xds.coords:
844
+ if coord != 'uvw_label':
845
+ val, unit = self._get_xda_val_unit(sel_xds[coord])
846
+ values[coord] = val
847
+ units[coord] = unit
848
+ for data_var in sel_xds.data_vars:
849
+ val, unit = self._get_xda_val_unit(sel_xds[data_var])
850
+ if data_var == 'UVW':
851
+ names = ['U', 'V', 'W']
852
+ for i, name in enumerate(names):
853
+ values[name] = val[i]
854
+ units[name] = unit[i]
855
+ else:
856
+ values[data_var] = val
857
+ units[data_var] = unit
858
+ except KeyError:
859
+ pass
860
+
861
+ # Set complex component name for visibilities
862
+ if 'VISIBILITY' in values:
863
+ values[self._plot_inputs['vis_axis'].capitalize()] = values.pop('VISIBILITY')
864
+ return values, units
865
+
866
+ def _update_cursor_box(self, location_values, units):
867
+ ''' Update cursor location widget box with info in dict '''
868
+ cursor_location_box = self._gui_layout[0][0][1]
869
+ cursor_location_box.clear() # pn.WidgetBox
870
+ location_layout = pn.Column(pn.widgets.StaticText(name="Cursor Location"))
871
+ info_row = pn.Row()
872
+ info_col = pn.Column()
873
+
874
+ for name, value in location_values.items():
875
+ # 4 entries per column; append to row and start new column
876
+ if len(info_col.objects) == 4:
877
+ info_row.append(info_col)
878
+ info_col = pn.Column()
879
+
880
+ if not isinstance(value, str):
881
+ if name == "FLAG":
882
+ value = "nan" if np.isnan(value) else int(value)
883
+ elif isinstance(value, float):
884
+ if np.isnan(value):
885
+ value = "nan"
886
+ elif value < 1e6:
887
+ value = f"{value:.4f}"
888
+ else:
889
+ value = f"{value:.4e}"
890
+ elif isinstance(value, np.datetime64):
891
+ value = to_datetime(np.datetime_as_string(value)).strftime(TIME_FORMAT)
892
+ units.pop(name) # no unit for datetime string
893
+ unit = units[name] if name in units else ""
894
+ info = pn.widgets.StaticText(name=name, value=f"{value} {unit}")
895
+ info.margin = (0, 10) # default (5, 10)
896
+ info_col.append(info)
897
+ info_row.append(info_col)
898
+ location_layout.append(info_row)
899
+ cursor_location_box.append(location_layout)
900
+
901
+ def _get_xda_val_unit(self, xda):
902
+ ''' Get value and unit as str not list '''
903
+ # Value
904
+ val = xda.values
905
+ if isinstance(val, np.ndarray) and val.size == 1:
906
+ val = val.item()
907
+ # Unit
908
+ try:
909
+ unit = xda.attrs['units']
910
+ unit = unit[0] if (isinstance(unit, list) and len(unit) == 1) else unit
911
+ unit = '' if unit == 'unkown' else unit
912
+ except KeyError:
913
+ unit = ''
914
+ return val, unit
915
+
879
916
  ###
880
917
  ### Callbacks for widgets which update plot inputs
881
918
  ###
@@ -5,11 +5,13 @@ Base class for ms plots
5
5
  import os
6
6
  import time
7
7
 
8
- from bokeh.plotting import show
8
+ from bokeh.io import export_png, export_svg
9
+ from bokeh.plotting import save, show
9
10
  import hvplot
10
11
  import holoviews as hv
11
12
  import numpy as np
12
13
  import panel as pn
14
+ from selenium import webdriver
13
15
 
14
16
  try:
15
17
  from toolviper.utils.logger import get_logger, setup_logger
@@ -57,6 +59,9 @@ class MsPlot:
57
59
  self._plots_locked = False
58
60
  self._plots = []
59
61
 
62
+ # Initialize gui
63
+ self._gui_layout = None
64
+
60
65
  # Set data (if ms)
61
66
  self._data = None
62
67
  self._ms_info = {}
@@ -140,27 +145,21 @@ class MsPlot:
140
145
  self._plots_locked = True
141
146
 
142
147
  # Single plot or combine plots into layout using subplots (rows, columns)
143
- # Not layout if subplots is single plot (default if None) or if only one plot saved
144
- subplots = self._plot_inputs['subplots']
145
- layout_plot, is_layout = self._layout_plots(subplots)
146
-
147
- # Render to bokeh figure
148
- if is_layout:
149
- # Show plots in columns
150
- plot = hv.render(layout_plot.cols(subplots[1]))
151
- else:
152
- # Show single plot
153
- plot = hv.render(layout_plot)
148
+ layout_plot = self._layout_plots(self._plot_inputs['subplots'])
149
+
150
+ # Render plot as Bokeh Figure or GridPlot so can show() in script without tying up thread
151
+ bokeh_fig = hv.render(layout_plot)
154
152
 
155
153
  self._plots_locked = False
156
154
  if self._plot_params:
155
+ # Show plot and plot inputs in tabs
157
156
  column = pn.Column()
158
157
  for param in self._plot_params:
159
158
  column.append(pn.pane.Str(param))
160
- tabs = pn.Tabs(('Plot', plot), ('Plot Inputs', column))
159
+ tabs = pn.Tabs(('Plot', bokeh_fig), ('Plot Inputs', column))
161
160
  tabs.show(title=self._app_name, threaded=True)
162
161
  else:
163
- show(plot)
162
+ show(bokeh_fig)
164
163
 
165
164
  def save(self, filename='ms_plot.png', fmt='auto', width=900, height=600):
166
165
  '''
@@ -175,78 +174,103 @@ class MsPlot:
175
174
 
176
175
  start_time = time.time()
177
176
 
178
- # Save single plot or combine plots into layout using subplots (rows, columns).
179
- # Not layout if subplots is single plot or if only one plot saved.
180
- subplots = self._plot_inputs['subplots']
181
- layout_plot, is_layout = self._layout_plots(subplots)
177
+ name, ext = os.path.splitext(filename)
178
+ fmt = ext[1:] if fmt=='auto' else fmt
182
179
 
183
- if is_layout:
184
- # Save plots combined into one layout
185
- hvplot.save(layout_plot.cols(subplots[1]), filename=filename, fmt=fmt)
186
- self._logger.info("Saved plot to %s.", filename)
187
- else:
188
- # Save plots individually, with index appended if exprange='all' and multiple plots.
189
- if self._plot_inputs['iter_axis'] is None:
190
- hvplot.save(layout_plot.opts(width=width, height=height), filename=filename, fmt=fmt)
191
- self._logger.info("Saved plot to %s.", filename)
192
- else:
193
- name, ext = os.path.splitext(filename)
194
- iter_range = self._plot_inputs['iter_range'] # None or (start, end)
195
- plot_idx = 0 if iter_range is None else iter_range[0]
196
-
197
- for plot in self._plots:
198
- exportname = f"{name}_{plot_idx}{ext}"
199
- hvplot.save(plot.opts(width=width, height=height), filename=exportname, fmt=fmt)
200
- self._logger.info("Saved plot to %s.", exportname)
201
- plot_idx += 1
180
+ # Combine plots into layout using subplots (rows, columns) if not single plot.
181
+ # Set fixed size for export.
182
+ layout_plot = self._layout_plots(self._plot_inputs['subplots'], (width, height))
202
183
 
184
+ if not isinstance(layout_plot, hv.Layout) and self._plot_inputs['iter_axis']:
185
+ # Save iterated plots individually, with index appended to filename
186
+ plot_idx = 0 if self._plot_inputs['iter_range'] is None else self._plot_inputs['iter_range'][0]
187
+ for plot in self._plots:
188
+ exportname = f"{name}_{plot_idx}{ext}"
189
+ self._save_plot(plot, exportname, fmt)
190
+ plot_idx += 1
191
+ else:
192
+ self._save_plot(layout_plot, filename, fmt)
203
193
  self._logger.debug("Save elapsed time: %.2fs.", time.time() - start_time)
204
194
 
205
- def _layout_plots(self, subplots):
195
+ def _layout_plots(self, subplots, fixed_size=None):
196
+ ''' Combine plots in a layout, using fixed size for the layout if given '''
206
197
  subplots = (1, 1) if subplots is None else subplots
207
- num_plots = len(self._plots)
208
- num_layout_plots = min(num_plots, np.prod(subplots))
198
+ num_plots = min(len(self._plots), np.prod(subplots))
199
+ plot_width = fixed_size[0] if fixed_size else None
200
+ plot_height = fixed_size[1] if fixed_size else None
209
201
 
210
- if num_layout_plots == 1:
211
- return self._plots[0], False
202
+ if num_plots == 1:
203
+ # Single plot, not layout
204
+ plot = self._plots[0]
205
+ if fixed_size:
206
+ plot = plot.opts(responsive=False, width=plot_width, height=plot_height, clone=True)
207
+ return plot
212
208
 
213
209
  # Set plots in layout
214
- plot_count = 0
215
- layout = None
216
- for i in range(num_layout_plots):
210
+ layout_plot = None
211
+ for i in range(num_plots):
217
212
  plot = self._plots[i]
218
- layout = plot if layout is None else layout + plot
219
- plot_count += 1
220
-
221
- is_layout = plot_count > 1
222
- return layout, is_layout
213
+ if fixed_size:
214
+ plot = plot.opts(responsive=False, width=plot_width, height=plot_height, clone=True)
215
+ layout_plot = plot if layout_plot is None else layout_plot + plot
216
+
217
+ # Layout in columns
218
+ return layout_plot.cols(subplots[1])
219
+
220
+ def _save_plot(self, plot, filename, fmt):
221
+ ''' Save plot using hvplot, else bokeh '''
222
+ # Remove toolbar unless html
223
+ toolbar = 'right' if fmt=='html' else None
224
+ plot = plot.opts(toolbar=toolbar, clone=True)
225
+
226
+ try:
227
+ hvplot.save(plot, filename=filename, fmt=fmt)
228
+ except (Exception, RuntimeError) as exc:
229
+ # Fails if hvplot cannot find web driver or fmt is svg.
230
+ # Render a Bokeh Figure or GridPlot, create webdriver, then use Bokeh to export.
231
+ fig = hv.render(plot)
232
+ if fmt=='html':
233
+ save(fig, filename)
234
+ elif fmt in ['png', 'svg']:
235
+ # Use Chrome web driver
236
+ service = webdriver.ChromeService()
237
+ options = webdriver.ChromeOptions()
238
+ options.add_argument('--headless')
239
+ options.add_argument('--no-sandbox')
240
+
241
+ with webdriver.Chrome(service=service, options=options) as driver:
242
+ if fmt=='png':
243
+ export_png(fig, filename=filename, webdriver=driver)
244
+ elif fmt=='svg':
245
+ export_svg(fig, filename=filename, webdriver=driver)
246
+ else:
247
+ raise ValueError(f"Invalid fmt or filename extension {fmt} for save()") from exc
248
+ self._logger.info("Saved plot to %s.", filename)
223
249
 
224
250
  def _set_ms(self, ms_path):
225
251
  ''' Set MsData and update ms info for input ms filepath (MSv2 or zarr), if set.
226
- Return whether ms changed (false if ms is None). '''
252
+ Return whether ms changed (false if ms_path is None, not set yet), even if error. '''
227
253
  self._ms_info['ms'] = ms_path
228
254
  ms_error = ""
229
- ms_changed = ms_path and (not self._data or not self._data.is_ms_path(ms_path))
230
-
231
- if ms_changed:
232
- try:
233
- # Set new MS data
234
- self._data = MsData(ms_path, self._logger)
235
- data_path = self._data.get_path()
236
- self._ms_info['ms'] = data_path
237
- root, ext = os.path.splitext(os.path.basename(data_path))
238
- while ext != '':
239
- root, ext = os.path.splitext(root)
240
- self._ms_info['basename'] = root
241
- self._ms_info['data_dims'] = self._data.get_data_dimensions()
242
- except RuntimeError as e:
243
- ms_error = str(e)
244
- self._data = None
245
-
255
+ if not ms_path or (self._data and self._data.is_ms_path(ms_path)):
256
+ return False
257
+
258
+ try:
259
+ # Set new MS data
260
+ self._data = MsData(ms_path, self._logger)
261
+ data_path = self._data.get_path()
262
+ self._ms_info['ms'] = data_path
263
+ root, ext = os.path.splitext(os.path.basename(data_path))
264
+ while ext != '':
265
+ root, ext = os.path.splitext(root)
266
+ self._ms_info['basename'] = root
267
+ self._ms_info['data_dims'] = self._data.get_data_dimensions()
268
+ except RuntimeError as e:
269
+ ms_error = str(e)
270
+ self._data = None
246
271
  if ms_error:
247
272
  self._notify(ms_error, 'error', 0)
248
-
249
- return ms_changed
273
+ return True
250
274
 
251
275
  def _notify(self, message, level, duration=3000):
252
276
  ''' Log message. If show_gui, notify user with toast for duration in ms.
@@ -0,0 +1,91 @@
1
+ '''
2
+ Create interactive GUI for ms raster plotting
3
+ '''
4
+
5
+ import holoviews as hv
6
+ import panel as pn
7
+ from vidavis.plot.ms_plot._ms_plot_selectors import (file_selector, title_selector, style_selector,
8
+ axis_selector, aggregation_selector, iteration_selector, selection_selector, plot_starter)
9
+
10
+ def create_raster_gui(callbacks, data_dims, x_axis, y_axis):
11
+ ''' Use Holoviz Panel to create a dashboard for plot inputs and raster plot display. '''
12
+ # ------------------
13
+ # PLOT INPUTS COLUMN
14
+ # ------------------
15
+ # Select MS
16
+ file_selectors = file_selector('Path to MeasurementSet (ms or zarr) for plot', '~' , callbacks['filename'])
17
+
18
+ # Select style - colormaps, colorbar, color limits
19
+ style_selectors = style_selector(callbacks['style'], callbacks['color'])
20
+
21
+ # Select x, y, and vis axis
22
+ axis_selectors = axis_selector(x_axis, y_axis, data_dims, True, callbacks['axes'])
23
+
24
+ # Select from ProcessingSet and MeasurementSet
25
+ selection_selectors = selection_selector(callbacks['select_ps'], callbacks['select_ms'])
26
+
27
+ # Generic axis options, updated when ms is set
28
+ axis_options = data_dims if data_dims else []
29
+
30
+ # Select aggregator and axes to aggregate
31
+ agg_selectors = aggregation_selector(axis_options, callbacks['aggregation'])
32
+
33
+ # Select iter_axis and iter value or range
34
+ iter_selectors = iteration_selector(axis_options, callbacks['iter_values'], callbacks['iteration'])
35
+
36
+ # Set title
37
+ title_input = title_selector(callbacks['title'])
38
+
39
+ # Put user input widgets in accordion with only one card active at a time (toggle)
40
+ selectors = pn.Accordion(
41
+ ("Select file", file_selectors), # [0]
42
+ ("Plot style", style_selectors), # [1]
43
+ ("Data Selection", selection_selectors), # [2]
44
+ ("Plot axes", axis_selectors), # [3]
45
+ ("Aggregation", agg_selectors), # [4]
46
+ ("Iteration", iter_selectors), # [5]
47
+ ("Plot title", title_input), # [6]
48
+ )
49
+ selectors.toggle = True
50
+
51
+ # Plot button and spinner while plotting
52
+ init_plot = plot_starter(callbacks['plot_updating'])
53
+
54
+ # -------------------------
55
+ # PLOT WITH CURSOR POSITION
56
+ # -------------------------
57
+ # Connect plot to filename and plot button; add pointer stream for cursor info
58
+ dmap = hv.DynamicMap(
59
+ pn.bind(
60
+ callbacks['update_plot'],
61
+ ms=file_selectors[0][0],
62
+ do_plot=init_plot[0],
63
+ ),
64
+ streams=[hv.streams.PointerXY()] # for cursor location
65
+ )
66
+
67
+ # ----------------------------------------------
68
+ # GUI LAYOUT OF PLOT TABS AND PLOT INPUTS COLUMN
69
+ # ----------------------------------------------
70
+ return pn.Row(
71
+ pn.Tabs( # Row [0]
72
+ ('Plot',
73
+ pn.Column( # Tabs [0]
74
+ dmap, # [0]
75
+ pn.WidgetBox(), # [1] cursor location
76
+ )
77
+ ),
78
+ ('Plot Inputs', pn.Column()), # Tabs [1]
79
+ sizing_mode='stretch_width',
80
+ ),
81
+ pn.Spacer(width=10), # Row [1]
82
+ pn.Column( # Row [2]
83
+ pn.Spacer(height=25), # Column [0]
84
+ selectors, # Column [1]
85
+ init_plot, # Column [2]
86
+ width_policy='min',
87
+ width=400,
88
+ sizing_mode='stretch_height',
89
+ ),
90
+ sizing_mode='stretch_height',
91
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vidavis
3
- Version: 0.0.5
3
+ Version: 0.0.7
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>
@@ -8,6 +8,8 @@ Requires-Python: >=3.11, <3.14
8
8
  Requires-Dist: bokeh==3.6.1
9
9
  Requires-Dist: graphviper
10
10
  Requires-Dist: hvplot
11
+ Requires-Dist: matplotlib
12
+ Requires-Dist: selenium
11
13
  Description-Content-Type: text/x-rst
12
14
 
13
15
  vidavis - VIsibility DAta VISualization
@@ -46,9 +48,6 @@ Requirements
46
48
  - Optionally `python-casacore <https://pypi.org/project/python-casacore/>`_ or
47
49
  `casatools <https://pypi.org/project/casatools/>`_ for MSv2 conversion
48
50
 
49
- - Optionally `Selenium <https://www.selenium.dev/documentation/en/>`_ along with
50
- a web driver to export to file using ``save()``
51
-
52
51
  Install
53
52
  ```````
54
53
 
@@ -57,33 +56,14 @@ Install
57
56
  MSv2 Conversion
58
57
  ^^^^^^^^^^^^^^^
59
58
 
60
- To enable conversion from MSv2 to MSv4 with **python-casacore** use (this only works for Linux):
59
+ To enable conversion from MSv2 to MSv4 with **python-casacore** for Linux only:
61
60
 
62
61
  - :code:`pip install "xradio[python-casacore]"`
63
62
 
64
- On macOS it is required to pre-install `python-casacore` using:
63
+ On macOS it is required to pre-install **python-casacore**:
65
64
 
66
65
  - :code:`conda install -c conda-forge python-casacore`
67
66
 
68
- Exporting Plots
69
- ^^^^^^^^^^^^^^^
70
-
71
- To enable exporting plots to file without showing the plot, using preferred web
72
- driver:
73
-
74
- **Selenium** with **geckodriver** and **Firefox** (to ensure compatible versions):
75
-
76
- - :code:`conda install -c conda-forge selenium firefox geckodriver`
77
-
78
- **Selenium** with **ChromeDriver** (Chrome), with the executable
79
- **chromedriver** in your PATH:
80
-
81
- - :code:`conda install -c conda-forge selenium python-chromedriver-binary`
82
-
83
- or:
84
-
85
- - :code:`pip install selenium chromedriver-binary`
86
-
87
67
  Simple MsRaster Usage Example
88
68
  `````````````````````````````
89
69
 
@@ -1,7 +1,7 @@
1
1
  vidavis/LICENSE.rst,sha256=qzGpkvhDzf_MgF1PIn6rCmYPrcEhkfrBUchosLJj-U4,26371
2
2
  vidavis/__init__.py,sha256=nhvlqlYhl4NzFO2WPTYfhyZkNdpfkBUTUOgJWKCK4Tc,1681
3
3
  vidavis/apps/__init__.py,sha256=ZQ5v1VFtjn3ztmuOHLOk5WbC1uLNIgL9rbHQ4v0zJwY,87
4
- vidavis/apps/_ms_raster.py,sha256=A7KCJWW2-f6cONMTHjwFfHIqFhAJt00ophe1Zm2XX1E,44996
4
+ vidavis/apps/_ms_raster.py,sha256=z6abcuznCSudJVcBnp0KpF5mwuRzbON8IFZtKhWnWKk,46831
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,10 +18,11 @@ 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/_ms_plot.py,sha256=pS-l6-cxmnsIa4Dm-yyPkgimh7sFSl6qBIpXxK9HV_Y,11414
21
+ vidavis/plot/ms_plot/_ms_plot.py,sha256=FlCeoeqLbvjK5ARae-S2QivJZ1Yig3IhKOvi1FRhXbk,12660
22
22
  vidavis/plot/ms_plot/_ms_plot_constants.py,sha256=cX_TQhKJ3hJzPuRYmuRJxue1sjq82yl_ZN2_w6TshmI,930
23
23
  vidavis/plot/ms_plot/_ms_plot_selectors.py,sha256=BZQwARvMPdk78n6Rh2tOaSc8GenZBrxHZb14oFD9gJM,10785
24
24
  vidavis/plot/ms_plot/_raster_plot.py,sha256=lNa9i_eJ8F8Fc2zcHLRcaxKKOELk3x_QmXT__T76pg8,10999
25
+ vidavis/plot/ms_plot/_raster_plot_gui.py,sha256=ttUC_HW-INm82ToOEZzlNjAAsQGCLpxcZH5-te-W4jk,3448
25
26
  vidavis/plot/ms_plot/_raster_plot_inputs.py,sha256=vPjZPDuB26ENxwv4z7dUa_c5TqByXejScY6hqEnLFLU,4768
26
27
  vidavis/plot/ms_plot/_time_ticks.py,sha256=j-DcPh7RfGE8iX2bPjLQDQPIbiAbmjiEWQnKmdMWA3I,1773
27
28
  vidavis/plot/ms_plot/_xds_plot_axes.py,sha256=EeWvAbiKV33nEWdI8V3M0uwLTnycq4bFYBOyVWkxCu0,4429
@@ -29,8 +30,8 @@ vidavis/toolbox/__init__.py,sha256=jqFa-eziVz_frNnXxwjJFK36qNpz1H38s-VlpBcq-R8,1
29
30
  vidavis/toolbox/_app_context.py,sha256=H7gtF8RrAH46FqDcMobv3KM1Osbnapgu6aTG-m3VCWA,3049
30
31
  vidavis/toolbox/_logging.py,sha256=OEisrd8FM8VTNBMc7neLh9ekelf29ZILYB5pScebly0,2739
31
32
  vidavis/toolbox/_static.py,sha256=HJLMtClppgOJXWAtV6Umn5EqN80u0oZiIouQ1JsB9PM,2346
32
- vidavis/__version__.py,sha256=0Uc3zDF9XadX9j08cOIZbGoZsI0JPHTxemWum3gkos4,21
33
- vidavis-0.0.5.dist-info/WHEEL,sha256=B19PGBCYhWaz2p_UjAoRVh767nYQfk14Sn4TpIZ-nfU,87
34
- vidavis-0.0.5.dist-info/METADATA,sha256=sxHI7zSrrwDcnRaaeZLSmqyDRRsHM-5hSBrHzB-g5TQ,2850
35
- vidavis-0.0.5.dist-info/licenses/LICENSE,sha256=IMF9i4xIpgCADf0U-V1cuf9HBmqWQd3qtI3FSuyW4zE,26526
36
- vidavis-0.0.5.dist-info/RECORD,,
33
+ vidavis/__version__.py,sha256=7sNbee72r3qCAVloFm2RRWCTa9gVjMS7nCJcJ-URzl4,21
34
+ vidavis-0.0.7.dist-info/WHEEL,sha256=B19PGBCYhWaz2p_UjAoRVh767nYQfk14Sn4TpIZ-nfU,87
35
+ vidavis-0.0.7.dist-info/METADATA,sha256=OxbpEuebhkcUmr7Ka5Tx4rK2GMfmw15Ls8yegy3XwRA,2242
36
+ vidavis-0.0.7.dist-info/licenses/LICENSE,sha256=IMF9i4xIpgCADf0U-V1cuf9HBmqWQd3qtI3FSuyW4zE,26526
37
+ vidavis-0.0.7.dist-info/RECORD,,