vidavis 0.0.13__py3-none-any.whl → 0.0.15__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.
@@ -15,6 +15,8 @@ def set_coordinates(ms_xdt):
15
15
 
16
16
  def set_datetime_coordinate(ms_xds):
17
17
  ''' Convert float time to datetime for plotting. '''
18
+ if 'time' not in ms_xds.coords:
19
+ return
18
20
  time_attrs = ms_xds.time.attrs
19
21
  try:
20
22
  ms_xds.coords['time'] = to_datetime(ms_xds.time, unit=time_attrs['units'], origin=time_attrs['format'])
@@ -47,6 +49,8 @@ def set_index_coordinates(ms_xds, coordinates):
47
49
 
48
50
  def _set_frequency_unit(ms_xdt):
49
51
  ''' Convert frequency to GHz. Note attrs (channel_width, reference_frequency) still have Hz units in dict '''
52
+ if 'frequency' not in ms_xdt.coords:
53
+ return
50
54
  if ms_xdt.frequency.attrs['units'] == "Hz":
51
55
  frequency_xda = ms_xdt.frequency / 1e9
52
56
  frequency_attrs = ms_xdt.frequency.attrs
@@ -41,12 +41,12 @@ def raster_data(ps_xdt, plot_inputs, logger):
41
41
  # Apply aggregator
42
42
  raster_xds = aggregate_data(raster_xds, plot_inputs, logger)
43
43
 
44
- logger.debug(f"Plotting visibility data with shape: {raster_xds[correlated_data].shape}")
44
+ logger.debug(f"Plotting visibility data with shape: {dict(raster_xds[correlated_data].sizes)}")
45
45
  return raster_xds
46
46
 
47
47
  def _select_ms(ps_xdt, logger, **selection):
48
- ''' Select ProcessingSet MeasurementSets for raster data '''
49
- return select_ms(ps_xdt, logger, indexers=None, method=None, tolerance=None, drop=False, **selection)
48
+ ''' Select ProcessingSet MeasurementSets for raster data. '''
49
+ return select_ms(ps_xdt, logger, indexers=None, method=None, tolerance=None, **selection)
50
50
 
51
51
  def _select_raster_dimensions(ps_xdt, plot_inputs, logger):
52
52
  ''' Select default dimensions if needed for raster data '''
@@ -64,6 +64,9 @@ def _select_raster_dimensions(ps_xdt, plot_inputs, logger):
64
64
  dim_selection[dim] = _get_first_dim_value(ps_xdt, dim, plot_inputs, logger)
65
65
  elif 'iter_axis' in plot_inputs and dim == plot_inputs['iter_axis']:
66
66
  dim_selection[dim] = selection[dim]
67
+ elif selection and dim in selection and isinstance(selection[dim], list):
68
+ dim_selection[dim] = selection[dim][0]
69
+
67
70
  if dim_selection:
68
71
  # Remove from selection for next plot
69
72
  if 'iter_axis' in plot_inputs and plot_inputs['iter_axis']:
@@ -192,7 +192,7 @@ def _select_time(ms_xdt, selection, method, tolerance, drop):
192
192
  else:
193
193
  # Select str or slice
194
194
  ms_xdt = ms_xdt.xr_ms.sel(indexers=None, method=time_method, tolerance=time_tolerance, drop=drop, **selection)
195
- if ms_xdt.time.size == 0:
195
+ if 'time' in ms_xdt.coords and ms_xdt.time.size == 0:
196
196
  return ms_xdt, False
197
197
  return ms_xdt, True
198
198
 
@@ -10,8 +10,9 @@ 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):
13
+ def cursor_changed(cursor, last_cursor):
14
14
  ''' Check whether cursor position changed '''
15
+ x, y = cursor
15
16
  if not x and not y:
16
17
  return False # not cursor callback
17
18
  if last_cursor and last_cursor == (x, y):
@@ -21,7 +22,7 @@ def cursor_changed(x, y, last_cursor):
21
22
  def points_changed(data, last_points):
22
23
  ''' Check whether point positions changed '''
23
24
  # No data = {'x': [], 'y': []}
24
- if len(data['x']) == 0 and len(data['y']) == 0:
25
+ if not data or (len(data['x']) == 0 and len(data['y']) == 0):
25
26
  return False # not points callback
26
27
  if last_points and last_points == data:
27
28
  return False # same points
@@ -36,7 +37,67 @@ def box_changed(bounds, last_box):
36
37
  return False # same box
37
38
  return True # new box, box changed, or box deleted
38
39
 
39
- def locate_point(xds, position, vis_axis):
40
+ def update_cursor_location(cursor, plot_axes, xds, cursor_locate_box):
41
+ ''' Show data values for cursor x,y position in cursor location box (pn.WidgetBox) '''
42
+ # Convert plot values to selection values to select plot data
43
+ cursor_locate_box.clear()
44
+
45
+ x, y = cursor
46
+ x_axis, y_axis, vis_axis = plot_axes
47
+ cursor_position = {x_axis: x, y_axis: y}
48
+ cursor_location = _locate_point(xds, cursor_position, vis_axis)
49
+
50
+ location_column = pn.Column(pn.widgets.StaticText(name="Cursor Location"))
51
+ # Add row of columns to column layout
52
+ location_row = _layout_point_location(cursor_location)
53
+ location_column.append(location_row)
54
+ # Add location column to widget box
55
+ cursor_locate_box.append(location_column)
56
+
57
+ def update_points_location(data, plot_axes, xds, points_tab_column, logger):
58
+ ''' Show data values for points in point_draw in tab and log '''
59
+ points_tab_column.clear()
60
+ if data:
61
+ x_axis, y_axis, vis_axis = plot_axes
62
+ logger.info("Locate selected points:")
63
+
64
+ for point in list(zip(data['x'], data['y'])):
65
+ # Locate point
66
+ point_position = {x_axis: point[0], y_axis: point[1]}
67
+ point_location = _locate_point(xds, point_position, vis_axis)
68
+ # Format location and add to points locate column
69
+ location_layout = _layout_point_location(point_location)
70
+ points_tab_column.append(location_layout)
71
+ points_tab_column.append(pn.layout.Divider())
72
+
73
+ # Format and add to log
74
+ location_list = [f"{static_text.name}={static_text.value}" for static_text in point_location]
75
+ logger.info(", ".join(location_list))
76
+
77
+ def update_box_location(bounds, plot_axes, xds, box_tab_column, logger):
78
+ ''' Show data values for points in box_select in tab and log '''
79
+ box_tab_column.clear()
80
+ if bounds:
81
+ x_axis, y_axis, vis_axis = plot_axes
82
+ box_bounds = {x_axis: (bounds[0], bounds[2]), y_axis: (bounds[1], bounds[3])}
83
+ npoints, point_locations = _locate_box(xds, box_bounds, vis_axis)
84
+
85
+ message = f"Locate {npoints} points"
86
+ message += " (only first 100 shown):" if npoints > 100 else ":"
87
+ logger.info(message)
88
+ box_tab_column.append(pn.pane.Str(message))
89
+
90
+ for point in point_locations:
91
+ # Format and add to box locate column
92
+ location_layout = _layout_point_location(point)
93
+ box_tab_column.append(location_layout)
94
+ box_tab_column.append(pn.layout.Divider())
95
+
96
+ # Format and add to log
97
+ location_list = [f"{static_text.name}={static_text.value}" for static_text in point]
98
+ logger.info(", ".join(location_list))
99
+
100
+ def _locate_point(xds, position, vis_axis):
40
101
  '''
41
102
  Get cursor location as values of coordinates and data vars.
42
103
  xds (Xarray Dataset): data for plot
@@ -48,15 +109,18 @@ def locate_point(xds, position, vis_axis):
48
109
  static_text_list = []
49
110
  values, units = _get_point_location(xds, position, vis_axis)
50
111
 
51
- # Rename baseline coordinate names to not confuse user for selection
52
- baseline_names = {'baseline': 'baseline_index', 'baseline_name': 'baseline', 'antenna_name': 'antenna_index', 'antenna': 'antenna_name'}
112
+ # List indexed coordinate int value with with str value
113
+ index_coords = {'baseline': 'baseline_name', 'antenna_name': 'antenna', 'polarization': 'polarization_name'}
53
114
  for name, value in values.items():
54
- name = baseline_names[name] if name in baseline_names else name
115
+ if name in index_coords.values():
116
+ continue
117
+ if name in index_coords and isinstance(value, int):
118
+ value = f"{values[index_coords[name]]} ({value})" # append name to index
55
119
  static_text = _get_location_text(name, value, units)
56
120
  static_text_list.append(static_text)
57
121
  return static_text_list
58
122
 
59
- def locate_box(xds, bounds, vis_axis):
123
+ def _locate_box(xds, bounds, vis_axis):
60
124
  '''
61
125
  Get location of each point in box bounds as values of coordinate and data vars.
62
126
  xds (Xarray Dataset): data for plot
@@ -83,7 +147,7 @@ def locate_box(xds, bounds, vis_axis):
83
147
  for y in sel_xds[y_coord].values:
84
148
  for x in sel_xds[x_coord].values:
85
149
  position = {x_coord: x, y_coord: y}
86
- points.append(locate_point(sel_xds, position, vis_axis))
150
+ points.append(_locate_point(sel_xds, position, vis_axis))
87
151
  counter += 1
88
152
  if counter == 100:
89
153
  break
@@ -183,3 +247,21 @@ def _get_location_text(name, value, units):
183
247
  units.pop(name) # no unit for datetime string
184
248
  unit = units[name] if name in units else ""
185
249
  return pn.widgets.StaticText(name=name, value=f"{value} {unit}")
250
+
251
+ def _layout_point_location(text_list):
252
+ ''' Layout list of StaticText in row of columns containing 3 rows '''
253
+ location_row = pn.Row()
254
+ location_col = pn.Column()
255
+
256
+ for static_text in text_list:
257
+ # 3 entries per column; append to row and start new column
258
+ if len(location_col.objects) == 3:
259
+ location_row.append(location_col)
260
+ location_col = pn.Column()
261
+
262
+ static_text.margin = (0, 5) # default (5, 10)
263
+ location_col.append(static_text)
264
+
265
+ # Add last column
266
+ location_row.append(location_col)
267
+ return location_row
@@ -12,15 +12,11 @@ import holoviews as hv
12
12
  import numpy as np
13
13
  import panel as pn
14
14
  from selenium import webdriver
15
-
16
- try:
17
- from toolviper.utils.logger import get_logger, setup_logger
18
- _HAVE_TOOLVIPER = True
19
- except ImportError:
20
- _HAVE_TOOLVIPER = False
15
+ from toolviper.utils.logger import setup_logger
21
16
 
22
17
  from vidavis.data.measurement_set._ms_data import MsData
23
- from vidavis.toolbox import AppContext, get_logger
18
+ from vidavis.plot.ms_plot._locate_points import cursor_changed, points_changed, box_changed, update_cursor_location, update_points_location, update_box_location
19
+ from vidavis.toolbox import AppContext
24
20
 
25
21
  class MsPlot:
26
22
 
@@ -32,11 +28,8 @@ class MsPlot:
32
28
  raise RuntimeError("Must provide ms/zarr path if gui not shown.")
33
29
 
34
30
  # Set logger: use toolviper logger else casalog else python logger
35
- if _HAVE_TOOLVIPER:
36
- self._logger = setup_logger(app_name, log_to_term=True, log_to_file=log_to_file, log_file=app_name.lower(), log_level=log_level.upper())
37
- else:
38
- self._logger = get_logger()
39
- self._logger.setLevel(log_level.upper())
31
+ self._logger = setup_logger(app_name, log_to_term=True, log_to_file=log_to_file, log_file=app_name.lower(), log_level=log_level.upper())
32
+ self._logger.propagate = False # avoid repeating unformatted log messages in console
40
33
 
41
34
  # Save parameters; ms set below
42
35
  self._show_gui = show_gui
@@ -45,39 +38,47 @@ class MsPlot:
45
38
  # Set up temp dir for output html files
46
39
  self._app_context = AppContext(app_name)
47
40
 
41
+ # Initialize plot inputs and params
42
+ self._plot_inputs = None # object to manage plot inputs
43
+
44
+ # Initialize plots
45
+ self._plot_init = False
46
+ self._plots = []
47
+ self._last_plot = None
48
+ self._plot_params = [] # for plot inputs tab
49
+
48
50
  if show_gui:
49
51
  # Enable "toast" notifications
50
52
  pn.config.notifications = True
51
53
  self._toast = None # for destroy() with new plot or new notification
52
54
 
53
55
  # Initialize gui panel for callbacks
54
- self._gui_layout = None
55
- self._first_gui_plot = True
56
- self._last_gui_plot = None
57
56
  self._gui_plot_data = None
57
+ self._gui_selection = {}
58
58
 
59
- # For _update_plot callback: check which inputs and point positions changed
59
+ # For _update_plot callback: check if inputs changed
60
60
  self._last_plot_inputs = None
61
61
  self._last_style_inputs = None
62
- self._last_cursor = None
63
- self._last_points = None
64
- self._last_box = None
65
-
66
- # Initialize plot inputs and params
67
- self._plot_inputs = {'selection': {}}
68
- self._plot_params = None
69
-
70
- # Initialize plots
71
- self._plot_init = False
72
- self._plots_locked = False
73
- self._plots = []
74
-
75
- # Initialize show() panel for callbacks
76
- self._show_layout = None
62
+ self._last_gui_plot = None # return value
63
+
64
+ # For locate callback: check if points changed
65
+ self._plot_axes = None
66
+ self._last_cursor = None
67
+ self._last_points = None
68
+ self._last_box = None
69
+ self._locate_plot_options = {
70
+ 'tools': ['hover', 'box_select'],
71
+ 'selection_fill_alpha': 0.2, # dim selected areas of plot
72
+ 'nonselection_fill_alpha': 1.0, # do not dim unselected areas of plot
73
+ }
74
+
75
+ # Initialize panels for callbacks
76
+ self._gui_panel = None
77
+ self._show_panel = None
77
78
  self._plot_data = None
78
79
 
79
80
  # Set data (if ms)
80
- self._data = None
81
+ self._ms_data = None
81
82
  self._ms_info = {}
82
83
  self._set_ms(ms)
83
84
  # pylint: enable=too-many-arguments, too-many-positional-arguments
@@ -94,15 +95,15 @@ class MsPlot:
94
95
  'field_name', 'source_name', 'field_coords', 'start_frequency', 'end_frequency'
95
96
  Returns: list of unique values when single column is requested, else None
96
97
  '''
97
- if self._data:
98
- self._data.summary(data_group, columns)
98
+ if self._ms_data:
99
+ self._ms_data.summary(data_group, columns)
99
100
  else:
100
101
  self._logger.error("Error: MS path has not been set")
101
102
 
102
103
  def data_groups(self):
103
104
  ''' Returns set of data groups from all ProcessingSet ms_xds. '''
104
- if self._data:
105
- return self._data.data_groups()
105
+ if self._ms_data:
106
+ return self._ms_data.data_groups()
106
107
  self._logger.error("Error: MS path has not been set")
107
108
  return None
108
109
 
@@ -111,8 +112,8 @@ class MsPlot:
111
112
  Dimension options include 'time', 'baseline' (for visibility data), 'antenna' (for spectrum data), 'antenna1',
112
113
  'antenna2', 'frequency', 'polarization'.
113
114
  '''
114
- if self._data:
115
- return self._data.get_dimension_values(dimension)
115
+ if self._ms_data:
116
+ return self._ms_data.get_dimension_values(dimension)
116
117
  self._logger.error("Error: MS path has not been set")
117
118
  return None
118
119
 
@@ -120,8 +121,8 @@ class MsPlot:
120
121
  ''' Plot antenna positions.
121
122
  label_antennas (bool): label positions with antenna names.
122
123
  '''
123
- if self._data:
124
- self._data.plot_antennas(label_antennas)
124
+ if self._ms_data:
125
+ self._ms_data.plot_antennas(label_antennas)
125
126
  else:
126
127
  self._logger.error("Error: MS path has not been set")
127
128
 
@@ -130,50 +131,86 @@ class MsPlot:
130
131
  data_group (str): data group to use for field and source xds.
131
132
  label_fields (bool): label all fields on the plot if True, else label central field only
132
133
  '''
133
- if self._data:
134
- self._data.plot_phase_centers(data_group, label_fields)
134
+ if self._ms_data:
135
+ self._ms_data.plot_phase_centers(data_group, label_fields)
135
136
  else:
136
137
  self._logger.error("Error: MS path has not been set")
137
138
 
138
139
  def clear_plots(self):
139
140
  ''' Clear plot list '''
140
- while self._plots_locked:
141
- time.sleep(1)
142
141
  self._plots.clear()
142
+ self._plot_params.clear()
143
+ self._plot_axes = None
144
+ if self._gui_panel is not None:
145
+ self._gui_panel[0][2].clear() # locate points
146
+ self._gui_panel[0][3].clear() # locate box
147
+
148
+ def unlink_plot_streams(self):
149
+ ''' Disconnect streams when plot data is going to be replaced '''
150
+ if self._show_panel and len(self._show_panel.objects) == 4:
151
+ # Remove dmap (streams with callback) from previous plot
152
+ self._show_panel[0][0] = self._last_plot.opts(tools=['hover'])
153
+ # Remove locate widgets
154
+ self._show_panel[0].pop(1) # cursor locate box
155
+ self._show_panel.pop(3) # box locate tab
156
+ self._show_panel.pop(2) # points locate tab
143
157
 
144
158
  def clear_selection(self):
145
159
  ''' Clear data selection and restore original ProcessingSet '''
146
- if self._data:
147
- self._data.clear_selection()
148
-
149
- self._plot_inputs['selection'] = {}
160
+ if self._ms_data:
161
+ self._ms_data.clear_selection()
162
+ self._plot_inputs.remove_input('selection')
150
163
 
151
164
  def show(self):
152
165
  '''
153
- Show interactive Bokeh plots in a browser. Plot tools include pan, zoom, hover, and save.
166
+ Show interactive Bokeh plots in a browser.
154
167
  '''
155
168
  if not self._plots:
156
169
  raise RuntimeError("No plots to show. Run plot() to create plot.")
157
170
 
158
- # Do not delete plot list until rendered
159
- self._plots_locked = True
160
-
161
171
  # Single plot or combine plots into layout using subplots (rows, columns)
162
- layout_plot = self._layout_plots(self._plot_inputs['subplots'])
163
-
164
- # Render plot as Bokeh Figure or GridPlot so can show() in script without tying up thread
165
- bokeh_fig = hv.render(layout_plot)
172
+ subplots = self._plot_inputs.get_input('subplots')
173
+ plot = self._layout_plots(subplots)
166
174
 
167
- self._plots_locked = False
175
+ # Add plot inputs column tab
176
+ inputs_column = None
168
177
  if self._plot_params:
169
- # Show plot and plot inputs in tabs
170
- column = pn.Column()
171
- for param in self._plot_params:
172
- column.append(pn.pane.Str(param))
173
- self._show_layout = pn.Tabs(('Plot', bokeh_fig), ('Plot Inputs', column))
178
+ inputs_column = pn.Column()
179
+ self._fill_inputs_column(inputs_column)
180
+
181
+ # Show plots and plot inputs in tabs
182
+ if self._plot_inputs.is_layout():
183
+ self._show_panel = pn.Tabs(('Plot', plot))
184
+ if inputs_column:
185
+ self._show_panel.append(('Plot Inputs', inputs_column))
174
186
  else:
175
- self._show_layout = pn.pane.Bokeh(bokeh_fig)
176
- self._show_layout.show(title=self._app_name, threaded=True)
187
+ plot = plot.opts(
188
+ hv.opts.QuadMesh(**self._locate_plot_options),
189
+ hv.opts.Scatter(**self._locate_plot_options)
190
+ )
191
+ # Add DynamicMap for streams for single plot
192
+ dmap = self._get_locate_dmap(self._locate)
193
+
194
+ # Create panel layout
195
+ self._show_panel = pn.Tabs(
196
+ ('Plot',
197
+ pn.Column(
198
+ plot * dmap,
199
+ pn.WidgetBox(), # cursor info
200
+ )
201
+ ),
202
+ sizing_mode='stretch_width',
203
+ )
204
+ if inputs_column:
205
+ self._show_panel.append(('Plot Inputs', inputs_column))
206
+ self._show_panel.append(('Locate Selected Points', pn.Column()))
207
+ self._show_panel.append(('Locate Selected Box', pn.Column()))
208
+
209
+ # return value for locate callback
210
+ self._last_plot = plot
211
+
212
+ # Show panel layout
213
+ self._show_panel.show(title=self._app_name, threaded=True)
177
214
 
178
215
  def save(self, filename='ms_plot.png', fmt='auto', width=900, height=600):
179
216
  '''
@@ -193,17 +230,20 @@ class MsPlot:
193
230
 
194
231
  # Combine plots into layout using subplots (rows, columns) if not single plot.
195
232
  # Set fixed size for export.
196
- layout_plot = self._layout_plots(self._plot_inputs['subplots'], (width, height))
233
+ subplots = self._plot_inputs.get_input('subplots')
234
+ plot = self._layout_plots(subplots, (width, height))
197
235
 
198
- if not isinstance(layout_plot, hv.Layout) and self._plot_inputs['iter_axis']:
236
+ iter_axis = self._plot_inputs.get_input('iter_axis')
237
+ if not isinstance(plot, hv.Layout) and iter_axis:
199
238
  # Save iterated plots individually, with index appended to filename
200
- plot_idx = 0 if self._plot_inputs['iter_range'] is None else self._plot_inputs['iter_range'][0]
239
+ iter_range = self._plot_inputs.get_input('iter_range')
240
+ plot_idx = 0 if iter_range is None else iter_range[0]
201
241
  for plot in self._plots:
202
242
  exportname = f"{name}_{plot_idx}{ext}"
203
243
  self._save_plot(plot, exportname, fmt)
204
244
  plot_idx += 1
205
245
  else:
206
- self._save_plot(layout_plot, filename, fmt)
246
+ self._save_plot(plot, filename, fmt)
207
247
  self._logger.debug("Save elapsed time: %.2fs.", time.time() - start_time)
208
248
 
209
249
  def _layout_plots(self, subplots, fixed_size=None):
@@ -266,22 +306,22 @@ class MsPlot:
266
306
  Return whether ms changed (false if ms_path is None, not set yet), even if error. '''
267
307
  self._ms_info['ms'] = ms_path
268
308
  ms_error = ""
269
- if not ms_path or (self._data and self._data.is_ms_path(ms_path)):
309
+ if not ms_path or (self._ms_data and self._ms_data.is_ms_path(ms_path)):
270
310
  return False
271
311
 
272
312
  try:
273
313
  # Set new MS data
274
- self._data = MsData(ms_path, self._logger)
275
- data_path = self._data.get_path()
314
+ self._ms_data = MsData(ms_path, self._logger)
315
+ data_path = self._ms_data.get_path()
276
316
  self._ms_info['ms'] = data_path
277
317
  root, ext = os.path.splitext(os.path.basename(data_path))
278
318
  while ext != '':
279
319
  root, ext = os.path.splitext(root)
280
320
  self._ms_info['basename'] = root
281
- self._ms_info['data_dims'] = self._data.get_data_dimensions()
321
+ self._ms_info['data_dims'] = self._ms_data.get_data_dimensions()
282
322
  except RuntimeError as e:
283
323
  ms_error = str(e)
284
- self._data = None
324
+ self._ms_data = None
285
325
  if ms_error:
286
326
  self._notify(ms_error, 'error', 0)
287
327
  return True
@@ -320,4 +360,86 @@ class MsPlot:
320
360
  del plot_inputs[key]
321
361
  except KeyError:
322
362
  pass
323
- self._plot_params = sorted([f"{key}={value}" for key, value in plot_inputs.items()])
363
+ if not self._plot_params:
364
+ self._plot_params = plot_inputs
365
+ else:
366
+ for param, value in self._plot_params.items():
367
+ if plot_inputs[param] != value:
368
+ if isinstance(value, list):
369
+ # append new value to existing list if not repeat
370
+ if plot_inputs[param] != value[-1]:
371
+ value.append(plot_inputs[param])
372
+ else:
373
+ # make list to include new value
374
+ value = [value, plot_inputs[param]]
375
+ self._plot_params[param] = value
376
+
377
+ def _fill_inputs_column(self, inputs_tab_column):
378
+ ''' Format plot inputs and list in Panel column '''
379
+ if self._plot_params:
380
+ inputs_tab_column.clear()
381
+ plot_params = sorted([f"{key}={value}" for key, value in self._plot_params.items()])
382
+ for param in plot_params:
383
+ str_pane = pn.pane.Str(param)
384
+ str_pane.margin = (0, 10)
385
+ inputs_tab_column.append(str_pane)
386
+
387
+ def _get_locate_dmap(self, callback):
388
+ ''' Return DynamicMap with streams callback to locate points '''
389
+ points = hv.Points([]).opts(
390
+ size=5,
391
+ fill_color='white'
392
+ )
393
+ dmap = hv.DynamicMap(
394
+ callback,
395
+ streams=[
396
+ hv.streams.PointerXY(), # cursor location (x, y)
397
+ hv.streams.PointDraw(source=points), # fixed points location (data)
398
+ hv.streams.BoundsXY() # box location (bounds)
399
+ ]
400
+ )
401
+ return dmap * points
402
+
403
+ def _get_plot_axes(self):
404
+ ''' Return x, y, vis axes '''
405
+ if not self._plot_axes:
406
+ x_axis = self._plot_inputs.get_input('x_axis')
407
+ y_axis = self._plot_inputs.get_input('y_axis')
408
+ vis_axis = self._plot_inputs.get_input('vis_axis')
409
+ self._plot_axes = (x_axis, y_axis, vis_axis)
410
+ return self._plot_axes
411
+
412
+ def _locate(self, x, y, data, bounds):
413
+ ''' Callback for all show plot streams '''
414
+ self._locate_cursor(x, y, self._plot_data, self._show_panel)
415
+ self._locate_points(data, self._plot_data, self._show_panel)
416
+ self._locate_box(bounds, self._plot_data, self._show_panel)
417
+ return self._last_plot
418
+
419
+ def _locate_cursor(self, x, y, plot_data, tabs):
420
+ ''' Show location from cursor position in cursor locate box '''
421
+ cursor = (x, y)
422
+ if cursor_changed(cursor, self._last_cursor):
423
+ # new cursor position - update cursor location box
424
+ plot_axes = self._get_plot_axes()
425
+ cursor_box = tabs[0][1]
426
+ update_cursor_location(cursor, plot_axes, plot_data, cursor_box)
427
+ self._last_cursor = cursor
428
+
429
+ def _locate_points(self, point_data, plot_data, tabs):
430
+ ''' Show points locations from point_draw tool '''
431
+ if points_changed(point_data, self._last_points):
432
+ # new points position - update selected points location tab
433
+ plot_axes = self._get_plot_axes()
434
+ points_tab = tabs[2]
435
+ update_points_location(point_data, plot_axes, plot_data, points_tab, self._logger)
436
+ self._last_points = point_data
437
+
438
+ def _locate_box(self, box_bounds, plot_data, tabs):
439
+ ''' Show points locations in box from box_select tool '''
440
+ if box_changed(box_bounds, self._last_box):
441
+ # new box_select position - update selected box location tab
442
+ plot_axes = self._get_plot_axes()
443
+ box_tab = tabs[3]
444
+ update_box_location(box_bounds, plot_axes, plot_data, box_tab, self._logger)
445
+ self._last_box = box_bounds
@@ -7,21 +7,23 @@ import panel as pn
7
7
  from vidavis.bokeh._palette import available_palettes
8
8
  from vidavis.plot.ms_plot._ms_plot_constants import VIS_AXIS_OPTIONS, AGGREGATOR_OPTIONS, PS_SELECTION_OPTIONS, MS_SELECTION_OPTIONS, DEFAULT_UNFLAGGED_CMAP, DEFAULT_FLAGGED_CMAP
9
9
 
10
- def file_selector(description, start_dir, callback):
10
+ def file_selector(callbacks, ms):
11
11
  ''' Return a layout for file selection with input description and start directory.
12
12
  Includes a TextInput and a FileSelector, with a callback to set TextInput from FileSelector.
13
13
  '''
14
- filename = pn.widgets.TextInput(
15
- description=description,
14
+ input_filename = pn.widgets.TextInput(
15
+ description='Path to MeasurementSet (ms or zarr)',
16
16
  name="Filename",
17
17
  placeholder='Enter filename or use file browser below',
18
+ value=ms,
18
19
  sizing_mode='stretch_width',
19
20
  )
21
+ set_file = pn.bind(callbacks['set_filename'], input_filename)
20
22
 
21
23
  file_select = pn.widgets.FileSelector(
22
- start_dir,
24
+ '~',
23
25
  )
24
- select_file = pn.bind(callback, file_select)
26
+ select_file = pn.bind(callbacks['select_filename'], file_select)
25
27
 
26
28
  fs_card = pn.Card(
27
29
  file_select,
@@ -33,8 +35,9 @@ def file_selector(description, start_dir, callback):
33
35
 
34
36
  return pn.Column(
35
37
  pn.Row( # [0]
36
- filename, # [0]
37
- select_file # [1]
38
+ input_filename, # [0]
39
+ set_file, # [1]
40
+ select_file # [2]
38
41
  ),
39
42
  fs_card, # [1]
40
43
  width_policy='min',
@@ -90,18 +93,22 @@ def style_selector(style_callback, color_range_callback):
90
93
  pn.Row( # [1]
91
94
  colorbar_checkbox, # [0]
92
95
  flagged_colorbar_checkbox, # [1]
96
+ select_style, # [2]
93
97
  ),
94
- select_style, # [2]
95
- pn.Row( # [3]
98
+ pn.Row( # [2]
96
99
  color_mode_selector, # [0]
97
100
  color_range_slider, # [1]
101
+ select_color_range, # [2]
98
102
  ),
99
- select_color_range, # [4]
100
103
  width_policy='min',
101
104
  )
102
105
 
103
- def axis_selector(x_axis, y_axis, axis_options, include_vis, callback):
106
+ def axis_selector(plot_info, include_vis, callback):
104
107
  ''' Return layout of selectors for x-axis, y-axis, and vis-axis '''
108
+ axis_options = plot_info['data_dims'] if 'data_dims' in plot_info else []
109
+ x_axis = plot_info['x_axis'] if 'x_axis' in plot_info else ''
110
+ y_axis = plot_info['y_axis'] if 'y_axis' in plot_info else ''
111
+
105
112
  x_options = axis_options if axis_options else [x_axis]
106
113
  x_selector = pn.widgets.Select(
107
114
  name="X Axis",
@@ -2,7 +2,7 @@
2
2
 
3
3
  def inputs_changed(plot_inputs, last_plot_inputs):
4
4
  ''' Check if inputs changed and need new plot '''
5
- if not last_plot_inputs:
5
+ if plot_inputs and not last_plot_inputs:
6
6
  return True
7
7
 
8
8
  for key, val in plot_inputs.items():