vidavis 0.1.3__tar.gz → 0.1.5__tar.gz

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.
Files changed (39) hide show
  1. {vidavis-0.1.3 → vidavis-0.1.5}/PKG-INFO +1 -1
  2. {vidavis-0.1.3 → vidavis-0.1.5}/pyproject.toml +1 -1
  3. {vidavis-0.1.3 → vidavis-0.1.5}/src/vidavis/apps/_ms_raster.py +12 -10
  4. {vidavis-0.1.3 → vidavis-0.1.5}/src/vidavis/data/measurement_set/processing_set/_ps_raster_data.py +1 -1
  5. {vidavis-0.1.3 → vidavis-0.1.5}/src/vidavis/plot/ms_plot/_locate_points.py +60 -65
  6. {vidavis-0.1.3 → vidavis-0.1.5}/src/vidavis/plot/ms_plot/_ms_plot.py +100 -37
  7. {vidavis-0.1.3 → vidavis-0.1.5}/src/vidavis/plot/ms_plot/_ms_plot_selectors.py +2 -1
  8. {vidavis-0.1.3 → vidavis-0.1.5}/src/vidavis/plot/ms_plot/_raster_plot.py +47 -47
  9. {vidavis-0.1.3 → vidavis-0.1.5}/src/vidavis/plot/ms_plot/_raster_plot_gui.py +4 -4
  10. {vidavis-0.1.3 → vidavis-0.1.5}/LICENSE +0 -0
  11. {vidavis-0.1.3 → vidavis-0.1.5}/readme.rst +0 -0
  12. {vidavis-0.1.3 → vidavis-0.1.5}/src/vidavis/LICENSE.rst +0 -0
  13. {vidavis-0.1.3 → vidavis-0.1.5}/src/vidavis/__init__.py +0 -0
  14. {vidavis-0.1.3 → vidavis-0.1.5}/src/vidavis/apps/__init__.py +0 -0
  15. {vidavis-0.1.3 → vidavis-0.1.5}/src/vidavis/bokeh/__init__.py +0 -0
  16. {vidavis-0.1.3 → vidavis-0.1.5}/src/vidavis/bokeh/_palette.py +0 -0
  17. {vidavis-0.1.3 → vidavis-0.1.5}/src/vidavis/data/__init__.py +0 -0
  18. {vidavis-0.1.3 → vidavis-0.1.5}/src/vidavis/data/measurement_set/__init__.py +0 -0
  19. {vidavis-0.1.3 → vidavis-0.1.5}/src/vidavis/data/measurement_set/_ms_data.py +0 -0
  20. {vidavis-0.1.3 → vidavis-0.1.5}/src/vidavis/data/measurement_set/processing_set/__init__.py +0 -0
  21. {vidavis-0.1.3 → vidavis-0.1.5}/src/vidavis/data/measurement_set/processing_set/_ps_concat.py +0 -0
  22. {vidavis-0.1.3 → vidavis-0.1.5}/src/vidavis/data/measurement_set/processing_set/_ps_coords.py +0 -0
  23. {vidavis-0.1.3 → vidavis-0.1.5}/src/vidavis/data/measurement_set/processing_set/_ps_data.py +0 -0
  24. {vidavis-0.1.3 → vidavis-0.1.5}/src/vidavis/data/measurement_set/processing_set/_ps_io.py +0 -0
  25. {vidavis-0.1.3 → vidavis-0.1.5}/src/vidavis/data/measurement_set/processing_set/_ps_select.py +0 -0
  26. {vidavis-0.1.3 → vidavis-0.1.5}/src/vidavis/data/measurement_set/processing_set/_ps_stats.py +0 -0
  27. {vidavis-0.1.3 → vidavis-0.1.5}/src/vidavis/data/measurement_set/processing_set/_xds_data.py +0 -0
  28. {vidavis-0.1.3 → vidavis-0.1.5}/src/vidavis/plot/__init__.py +0 -0
  29. {vidavis-0.1.3 → vidavis-0.1.5}/src/vidavis/plot/ms_plot/__init__.py +0 -0
  30. {vidavis-0.1.3 → vidavis-0.1.5}/src/vidavis/plot/ms_plot/_check_raster_inputs.py +0 -0
  31. {vidavis-0.1.3 → vidavis-0.1.5}/src/vidavis/plot/ms_plot/_ms_plot_constants.py +0 -0
  32. {vidavis-0.1.3 → vidavis-0.1.5}/src/vidavis/plot/ms_plot/_plot_inputs.py +0 -0
  33. {vidavis-0.1.3 → vidavis-0.1.5}/src/vidavis/plot/ms_plot/_raster_plot_inputs.py +0 -0
  34. {vidavis-0.1.3 → vidavis-0.1.5}/src/vidavis/plot/ms_plot/_time_ticks.py +0 -0
  35. {vidavis-0.1.3 → vidavis-0.1.5}/src/vidavis/plot/ms_plot/_xds_plot_axes.py +0 -0
  36. {vidavis-0.1.3 → vidavis-0.1.5}/src/vidavis/toolbox/__init__.py +0 -0
  37. {vidavis-0.1.3 → vidavis-0.1.5}/src/vidavis/toolbox/_app_context.py +0 -0
  38. {vidavis-0.1.3 → vidavis-0.1.5}/src/vidavis/toolbox/_logging.py +0 -0
  39. {vidavis-0.1.3 → vidavis-0.1.5}/src/vidavis/toolbox/_static.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vidavis
3
- Version: 0.1.3
3
+ Version: 0.1.5
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>
@@ -16,7 +16,7 @@ dependencies = [
16
16
  ]
17
17
  requires-python = ">=3.11, <3.14"
18
18
  readme = "readme.rst"
19
- version = "0.1.3"
19
+ version = "0.1.5"
20
20
 
21
21
  [project.license]
22
22
  text = "LGPL"
@@ -521,15 +521,16 @@ class MsRaster(MsPlot):
521
521
  # Add plot inputs to GUI, change plot button to outline, and stop spinner
522
522
  self._update_plot_status(False)
523
523
  self._update_plot_spinner(False)
524
-
525
524
  # pylint: enable=too-many-arguments, too-many-positional-arguments
526
525
 
527
- def _locate_gui_points(self, x, y, data, bounds):
526
+ def _locate_gui_points(self, x, y, data, boxes):
528
527
  ''' Callback for locate streams '''
529
528
  if self._gui_plot_data:
529
+ if super()._locate_points(data, self._gui_plot_data, self._gui_panel[2]):
530
+ return self._last_gui_plot
531
+ if super()._locate_boxes(boxes, self._gui_plot_data, self._gui_panel[3]):
532
+ return self._last_gui_plot
530
533
  super()._locate_cursor(x, y, self._gui_plot_data, self._gui_panel[0][1])
531
- super()._locate_points(data, self._gui_plot_data, self._gui_panel[2])
532
- super()._locate_box(bounds, self._gui_plot_data, self._gui_panel[3])
533
534
  return self._last_gui_plot
534
535
 
535
536
  def _do_gui_selection(self):
@@ -578,17 +579,18 @@ class MsRaster(MsPlot):
578
579
  return self._empty_plot
579
580
 
580
581
  def _create_empty_plot(self):
581
- ''' Create empty Overlay plot for DynamicMap with colormap params and required tools enabled '''
582
+ ''' Create empty Overlay plot for DynamicMap with colormap params enabled '''
582
583
  plot_params = self._raster_plot.get_plot_params()
583
584
  self._empty_plot = hv.Overlay(
584
585
  hv.QuadMesh([]).opts(
585
- colorbar=plot_params['style']['show_flagged_colorbar'],
586
- cmap=plot_params['style']['flagged_cmap'],
587
- responsive=True,
588
- ) * hv.QuadMesh([]).opts(
589
586
  colorbar=plot_params['style']['show_colorbar'],
590
- colorbar_position='left',
591
587
  cmap=plot_params['style']['unflagged_cmap'],
588
+ colorbar_position='left',
589
+ responsive=True,
590
+ ) * hv.QuadMesh([]).opts(
591
+ colorbar=plot_params['style']['show_flagged_colorbar'],
592
+ colorbar_position='right',
593
+ cmap=plot_params['style']['flagged_cmap'],
592
594
  responsive=True,
593
595
  )
594
596
  )
@@ -30,7 +30,7 @@ def raster_data(ps_xdt, plot_inputs, logger):
30
30
  raise RuntimeError("Plot failed: raster plane selection yielded data with all nan values.")
31
31
 
32
32
  # Compute complex component of vis data
33
- raster_xds[correlated_data] = get_axis_data(raster_xds, plot_inputs['vis_axis'], data_group).compute()
33
+ raster_xds[correlated_data] = get_axis_data(raster_xds, plot_inputs['vis_axis'], data_group)
34
34
 
35
35
  # Convert float time to datetime
36
36
  set_datetime_coordinate(raster_xds)
@@ -10,32 +10,41 @@ 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(cursor, last_cursor):
14
- ''' Check whether cursor position changed '''
15
- x, y = cursor
16
- if not x and not y:
17
- return False # not cursor callback
18
- if last_cursor and last_cursor == (x, y):
19
- return False # same cursor
20
- return True # new cursor or cursor changed
21
-
22
- def points_changed(data, last_points):
23
- ''' Check whether point positions changed '''
24
- # No data = {'x': [], 'y': []}
25
- if not data or (len(data['x']) == 0 and len(data['y']) == 0):
26
- return False # not points callback
27
- if last_points and last_points == data:
28
- return False # same points
29
- return True # new points, points changed, or points deleted
30
-
31
- def box_changed(bounds, last_box):
32
- ''' Check whether box position changed '''
33
- # No bounds = None
34
- if not bounds:
35
- return False # no data, not box select callback
36
- if last_box and last_box == bounds:
37
- return False # same box
38
- return True # new box, box changed, or box deleted
13
+ def get_locate_value(xds, coord, value):
14
+ ''' Convert index coordinates to int and float time coordinate to datetime. Select nearest value <= value. '''
15
+ if coord in ['baseline', 'antenna_name', 'polarization']:
16
+ # Convert float to int index value
17
+ return round(value)
18
+
19
+ if coord in ['time', 'frequency']:
20
+ if coord=='time' and isinstance(value, float):
21
+ # Convert float to datetime value
22
+ # Bokeh datetime values are floating point values that represent milliseconds-since-epoch (unix time)
23
+ value = to_datetime(value, unit='ms')
24
+ value_sel = {coord: value}
25
+ nearest_value = xds[coord].sel(indexers=None, method='nearest', tolerance=None, drop=False, **value_sel).values
26
+ return nearest_value
27
+
28
+ return value
29
+
30
+ def data_changed(data, last_data):
31
+ ''' Check whether data changed, input as list of tuples '''
32
+ if not data:
33
+ return False # not callback for this data
34
+ if last_data and len(last_data) == len(data) and last_data == data:
35
+ return False # same data
36
+ return True # new data, data changed, or data removed
37
+
38
+ def get_new_data(data, last_data):
39
+ ''' Return data not in last_data, input as list of tuples '''
40
+ new_data = []
41
+ if last_data:
42
+ for info in data:
43
+ if info not in last_data:
44
+ new_data.append(info)
45
+ else:
46
+ new_data = data
47
+ return new_data
39
48
 
40
49
  def update_cursor_location(cursor, plot_axes, xds, cursor_locate_box):
41
50
  ''' Show data values for cursor x,y position in cursor location box (pn.WidgetBox) '''
@@ -47,42 +56,40 @@ def update_cursor_location(cursor, plot_axes, xds, cursor_locate_box):
47
56
  cursor_position = {x_axis: x, y_axis: y}
48
57
  cursor_location = _locate_point(xds, cursor_position, vis_axis)
49
58
 
50
- location_column = pn.Column(pn.widgets.StaticText(name="CURSOR LOCATION"))
51
59
  # Add row of columns to column layout
60
+ location_column = pn.Column(pn.widgets.StaticText(name="CURSOR LOCATION"))
52
61
  location_row = _layout_point_location(cursor_location)
53
62
  location_column.append(location_row)
63
+
54
64
  # Add location column to widget box
55
65
  cursor_locate_box.append(location_column)
56
66
 
57
- def update_points_location(data, plot_axes, xds, points_tab_feed):
67
+ def update_points_location(points, plot_axes, xds, points_tab_feed):
58
68
  ''' Show data values for points in point_draw in tab and log '''
59
- points_tab_feed.clear()
60
69
  locate_log = []
61
- if data:
62
- x_axis, y_axis, vis_axis = plot_axes
63
- message = f"Locate {len(data['x'])} points:"
64
- locate_log.append(message)
65
- for point in list(zip(data['x'], data['y'])):
66
- # Locate point
67
- point_position = {x_axis: point[0], y_axis: point[1]}
68
- point_location = _locate_point(xds, point_position, vis_axis)
69
- # Format location and add to points locate column
70
- location_layout = _layout_point_location(point_location)
71
- points_tab_feed.append(location_layout)
72
- points_tab_feed.append(pn.layout.Divider())
73
-
74
- # Format and add to log
75
- location_list = [f"{static_text.name}={static_text.value}" for static_text in point_location]
76
- locate_log.append(", ".join(location_list))
70
+ x_axis, y_axis, vis_axis = plot_axes
71
+ for point in points:
72
+ # Locate point
73
+ point_position = {x_axis: point[0], y_axis: point[1]}
74
+ point_location = _locate_point(xds, point_position, vis_axis)
75
+
76
+ # Format location and add to points locate column
77
+ location_layout = _layout_point_location(point_location)
78
+ points_tab_feed.append(location_layout)
79
+ points_tab_feed.append(pn.layout.Divider())
80
+
81
+ # Format and add to log
82
+ location_list = [f"{static_text.name}={static_text.value}" for static_text in point_location]
83
+ locate_log.append(", ".join(location_list))
77
84
  return locate_log
78
85
 
79
- def update_box_location(bounds, plot_axes, xds, box_tab_feed):
86
+ # pylint: disable=too-many-locals
87
+ def update_boxes_location(boxes, plot_axes, xds, box_tab_feed):
80
88
  ''' Show data values for points in box_select in tab and log '''
81
- box_tab_feed.clear()
82
89
  locate_log = []
83
- if bounds:
84
- x_axis, y_axis, vis_axis = plot_axes
85
- box_bounds = {x_axis: (bounds[0], bounds[2]), y_axis: (bounds[1], bounds[3])}
90
+ x_axis, y_axis, vis_axis = plot_axes
91
+ for box in boxes:
92
+ box_bounds = {x_axis: (box[0], box[2]), y_axis: (box[1], box[3])}
86
93
  npoints, point_locations = _locate_box(xds, box_bounds, vis_axis)
87
94
 
88
95
  message = f"Locate {npoints} points"
@@ -100,6 +107,7 @@ def update_box_location(bounds, plot_axes, xds, box_tab_feed):
100
107
  location_list = [f"{static_text.name}={static_text.value}" for static_text in point]
101
108
  locate_log.append(", ".join(location_list))
102
109
  return locate_log
110
+ # pylint: enable=too-many-locals
103
111
 
104
112
  def _locate_point(xds, position, vis_axis):
105
113
  '''
@@ -141,8 +149,9 @@ def _locate_box(xds, bounds, vis_axis):
141
149
  selection = {}
142
150
  for coord, val in bounds.items():
143
151
  # Round index values to int for selection
144
- selection[coord] = slice(_get_selection_value(coord, val[0]), _get_selection_value(coord, val[1]))
152
+ selection[coord] = slice(get_locate_value(xds, coord, val[0]), get_locate_value(xds, coord, val[1]))
145
153
  sel_xds = xds.sel(indexers=None, method=None, tolerance=None, drop=False, **selection)
154
+ sel_xds.compute()
146
155
 
147
156
  x_coord, y_coord = bounds.keys()
148
157
  npoints = sel_xds.sizes[x_coord] * sel_xds.sizes[y_coord]
@@ -175,10 +184,6 @@ def _get_point_location(xds, position, vis_axis):
175
184
 
176
185
  if xds:
177
186
  try:
178
- for coord, value in position.items():
179
- # Round index coordinates to int and convert time to datetime if float for selection
180
- position[coord] = _get_selection_value(coord, value)
181
-
182
187
  sel_xds = xds.sel(indexers=None, method='nearest', tolerance=None, drop=False, **position)
183
188
  for coord in sel_xds.coords:
184
189
  if coord == 'uvw_label' or ('baseline_antenna' in coord and 'baseline_name' in sel_xds.coords):
@@ -206,16 +211,6 @@ def _get_point_location(xds, position, vis_axis):
206
211
  values[vis_axis.upper()] = values.pop('VISIBILITY')
207
212
  return values, units
208
213
 
209
- def _get_selection_value(coord, value):
210
- ''' Convert index coordinates to int and float time coordinate to datetime '''
211
- if coord in ['baseline', 'antenna_name', 'polarization']:
212
- # Round index coordinates to int for selecction
213
- value = round(value)
214
- elif coord == 'time' and isinstance(value, float):
215
- # Bokeh datetime values are floating-point numbers: milliseconds since the Unix epoch
216
- value = to_datetime(value, unit='ms', origin='unix')
217
- return value
218
-
219
214
  def _get_xda_val_unit(xda):
220
215
  ''' Return value and unit of xda (selected so only one value) '''
221
216
  # Value
@@ -17,7 +17,7 @@ from selenium import webdriver
17
17
  from toolviper.utils.logger import setup_logger
18
18
 
19
19
  from vidavis.data.measurement_set._ms_data import MsData
20
- from vidavis.plot.ms_plot._locate_points import cursor_changed, points_changed, box_changed, update_cursor_location, update_points_location, update_box_location
20
+ from vidavis.plot.ms_plot._locate_points import get_locate_value, data_changed, get_new_data, update_cursor_location, update_points_location, update_boxes_location
21
21
  from vidavis.toolbox import AppContext
22
22
 
23
23
  class MsPlot:
@@ -73,9 +73,9 @@ class MsPlot:
73
73
  self._plot_axes = None
74
74
  self._last_cursor = None
75
75
  self._last_points = None
76
- self._last_box = None
76
+ self._last_boxes = None
77
77
  self._locate_plot_options = {
78
- 'tools': ['box_select', 'hover'],
78
+ 'muted_alpha': 0,
79
79
  'selection_fill_alpha': 0.2, # dim selected areas of plot
80
80
  'nonselection_fill_alpha': 1.0, # do not dim unselected areas of plot
81
81
  }
@@ -189,10 +189,11 @@ class MsPlot:
189
189
  if inputs_column:
190
190
  self._show_panel.append(('Plot Inputs', inputs_column))
191
191
  else:
192
- plot = plot.opts(
192
+ plot = plot.options(
193
193
  hv.opts.QuadMesh(**self._locate_plot_options),
194
- hv.opts.Scatter(**self._locate_plot_options)
194
+ hv.opts.Scatter(**self._locate_plot_options),
195
195
  )
196
+
196
197
  # Add DynamicMap for streams for single plot
197
198
  dmap = self._get_locate_dmap(self._locate)
198
199
 
@@ -210,8 +211,11 @@ class MsPlot:
210
211
  # Add tabs for inputs and locate
211
212
  if inputs_column:
212
213
  self._show_panel.append(('Plot Inputs', inputs_column))
213
- self._show_panel.append(('Locate Selected Points', pn.Feed(height_policy='max')))
214
- self._show_panel.append(('Locate Selected Box', pn.Feed(height_policy='max')))
214
+ self._show_panel.append(('Locate Points', pn.Feed(height_policy='max')))
215
+ self._show_panel.append(('Locate Box', pn.Feed(height_policy='max')))
216
+
217
+ # Compute coordinate values for locate
218
+ self._compute_plot_metadata(self._plot_data)
215
219
 
216
220
  # return value for locate callback
217
221
  self._last_plot = plot
@@ -397,21 +401,28 @@ class MsPlot:
397
401
  str_pane.margin = (0, 10)
398
402
  inputs_tab_column.append(str_pane)
399
403
 
404
+ def _compute_plot_metadata(self, xds):
405
+ ''' Compute coordinate dask arrays to numpy arrays in memory '''
406
+ for coord in xds.coords:
407
+ xds[coord] = xds[coord].compute()
408
+
400
409
  def _get_locate_dmap(self, callback):
401
410
  ''' Return DynamicMap with streams callback to locate points '''
402
411
  points = hv.Points([]).opts(
403
412
  size=5,
404
413
  fill_color='white'
405
414
  )
415
+ boxes = hv.Rectangles([])
406
416
  dmap = hv.DynamicMap(
407
417
  callback,
408
418
  streams=[
409
- hv.streams.PointerXY(), # cursor location (x, y)
410
- hv.streams.PointDraw(source=points), # fixed points location (data)
411
- hv.streams.BoundsXY() # box location (bounds)
419
+ hv.streams.PointerXY(), # cursor location (x, y)
420
+ hv.streams.PointDraw(source=points), # fixed points location (data)
421
+ hv.streams.BoxEdit(source=boxes).rename(data='boxes') # box location (boxes)
412
422
  ]
413
423
  )
414
- return dmap * points
424
+ return (dmap * points * boxes).options(
425
+ hv.opts.Rectangles(fill_alpha=0.5, line_color='white'))
415
426
 
416
427
  def _unlink_plot_locate(self):
417
428
  ''' Disconnect streams when plot data is going to be replaced '''
@@ -432,45 +443,97 @@ class MsPlot:
432
443
  self._plot_axes = (x_axis, y_axis, vis_axis)
433
444
  return self._plot_axes
434
445
 
435
- def _locate(self, x, y, data, bounds):
446
+ def _locate(self, x, y, data, boxes):
436
447
  ''' Callback for all show plot streams '''
448
+ if self._locate_points(data, self._plot_data, self._show_panel[2]):
449
+ return self._last_plot
450
+
451
+ if self._locate_boxes(boxes, self._plot_data, self._show_panel[3]):
452
+ return self._last_plot
453
+
437
454
  self._locate_cursor(x, y, self._plot_data, self._show_panel[0][1])
438
- self._locate_points(data, self._plot_data, self._show_panel[2])
439
- self._locate_box(bounds, self._plot_data, self._show_panel[3])
440
455
  return self._last_plot
441
456
 
442
457
  def _locate_cursor(self, x, y, plot_data, cursor_box):
443
458
  ''' Show location from cursor position in cursor locate box '''
459
+ if not x and not y: # not cursor callback
460
+ return False
461
+ plot_axes = self._get_plot_axes()
462
+ x = get_locate_value(plot_data, plot_axes[0], x)
463
+ y = get_locate_value(plot_data, plot_axes[1], y)
444
464
  cursor = (x, y)
445
- if cursor_changed(cursor, self._last_cursor):
446
- # new cursor position - update cursor location box
447
- update_cursor_location(cursor, self._get_plot_axes(), plot_data, cursor_box)
448
- self._last_cursor = cursor
465
+
466
+ if not data_changed(cursor, self._last_cursor):
467
+ return False
468
+
469
+ # new cursor position - update cursor location box
470
+ update_cursor_location(cursor, plot_axes, plot_data, cursor_box)
471
+ self._last_cursor = cursor
472
+ return True
449
473
 
450
474
  def _locate_points(self, point_data, plot_data, points_tab):
451
475
  ''' Show points locations from point_draw tool '''
452
- if points_changed(point_data, self._last_points):
453
- # update selected points location tab
454
- location_info = update_points_location(point_data, self._get_plot_axes(), plot_data, points_tab)
476
+ if not point_data or len(point_data['x']) == 0: # not points callback
477
+ return False
478
+
479
+ plot_axes = self._get_plot_axes()
480
+ x_vals = point_data['x']
481
+ y_vals = point_data['y']
482
+ point_data['x'] = [get_locate_value(plot_data, plot_axes[0], x) for x in x_vals]
483
+ point_data['y'] = [get_locate_value(plot_data, plot_axes[1], y) for y in y_vals]
484
+ data_points = list(zip(point_data['x'], point_data['y']))
485
+
486
+ if not data_changed(data_points, self._last_points):
487
+ return False
488
+
489
+ # update selected points location tab with new points
490
+ points_to_locate = get_new_data(data_points, self._last_points)
491
+ self._last_points = data_points
455
492
 
456
- # log to file only
457
- self._logger.removeHandler(self._stdout_handler)
458
- for info in location_info:
459
- self._logger.info(info)
460
- self._logger.addHandler(self._stdout_handler)
493
+ if not points_to_locate: # point deleted
494
+ return False
461
495
 
462
- self._last_points = point_data
496
+ if len(points_to_locate) == len(data_points):
497
+ points_tab.clear() # clear for locating all points
463
498
 
464
- def _locate_box(self, box_bounds, plot_data, box_tab):
499
+ location_info = update_points_location(points_to_locate, self._get_plot_axes(), plot_data, points_tab)
500
+ self._log_to_file_only(location_info)
501
+ return True
502
+
503
+ def _locate_boxes(self, boxes, plot_data, box_tab):
465
504
  ''' Show points locations in box from box_select tool '''
466
- if box_changed(box_bounds, self._last_box):
467
- # update selected box location tab
468
- location_info = update_box_location(box_bounds, self._get_plot_axes(), plot_data, box_tab)
505
+ if not boxes or len(boxes['x0']) == 0: # not box callback
506
+ return False
469
507
 
470
- # log to file only
471
- self._logger.removeHandler(self._stdout_handler)
472
- for info in location_info:
473
- self._logger.info(info)
474
- self._logger.addHandler(self._stdout_handler)
508
+ plot_axes = self._get_plot_axes()
509
+ x0_vals = boxes['x0']
510
+ y0_vals = boxes['y0']
511
+ x1_vals = boxes['x1']
512
+ y1_vals = boxes['y1']
513
+ boxes['x0'] = [get_locate_value(plot_data, plot_axes[0], x0) for x0 in x0_vals]
514
+ boxes['y0'] = [get_locate_value(plot_data, plot_axes[1], y0) for y0 in y0_vals]
515
+ boxes['x1'] = [get_locate_value(plot_data, plot_axes[0], x1) for x1 in x1_vals]
516
+ boxes['y1'] = [get_locate_value(plot_data, plot_axes[1], y1) for y1 in y1_vals]
517
+ box_list = list(zip(boxes['x0'], boxes['y0'], boxes['x1'], boxes['y1']))
518
+
519
+ if not data_changed(box_list, self._last_boxes):
520
+ return False
475
521
 
476
- self._last_box = box_bounds
522
+ # update selected box location tab with new boxes
523
+ boxes_to_locate = get_new_data(box_list, self._last_boxes)
524
+ self._last_boxes = box_list
525
+
526
+ if not boxes_to_locate: # box deleted
527
+ return False
528
+
529
+ box_tab.clear()
530
+ location_info = update_boxes_location(boxes_to_locate, self._get_plot_axes(), plot_data, box_tab)
531
+ self._log_to_file_only(location_info)
532
+ return True
533
+
534
+ def _log_to_file_only(self, messages):
535
+ ''' log messages to file only '''
536
+ self._logger.removeHandler(self._stdout_handler)
537
+ for message in messages:
538
+ self._logger.info(message)
539
+ self._logger.addHandler(self._stdout_handler)
@@ -80,7 +80,8 @@ def style_selector(style_callback, color_range_callback):
80
80
  )
81
81
 
82
82
  color_range_slider = pn.widgets.RangeSlider(
83
- name="Colorbar range",
83
+ name="Set manual color range",
84
+ align=('start', 'end'), # (horizontal, vertical) to place left (h=start) and bottom (v=end)
84
85
  )
85
86
 
86
87
  select_color_range = pn.bind(color_range_callback, color_mode_selector, color_range_slider)
@@ -95,31 +95,31 @@ class RasterPlot:
95
95
  return None
96
96
 
97
97
  data_params = self._plot_params['data']
98
- plot_params = self._plot_params['plot']
99
-
100
- x_axis = plot_params['axis_labels']['x']['axis']
101
- y_axis = plot_params['axis_labels']['y']['axis']
102
- c_axis = plot_params['axis_labels']['c']['axis']
103
-
104
- # Set plot axes to numeric coordinates if needed
105
- xds = set_index_coordinates(data, (x_axis, y_axis))
98
+ axis_labels = self._plot_params['plot']['axis_labels']
106
99
 
107
100
  # Prefix c_axis name with aggregator
108
- xda_name = c_axis
101
+ xda_name = axis_labels['c']['axis']
109
102
  if data_params['aggregator']:
110
103
  xda_name = "_".join([data_params['aggregator'], xda_name])
111
104
 
112
- # Plot unflagged and flagged data
113
- xda = xds[data_params['correlated_data']].where(xds.FLAG == 0.0).rename(xda_name)
114
- unflagged_plot = self._plot_xda(xda)
115
- flagged_xda = xds[data_params['correlated_data']].where(xds.FLAG == 1.0).rename("flagged_" + xda_name)
116
- flagged_plot = self._plot_xda(flagged_xda, True)
117
-
105
+ # Set plot axes to numeric coordinates if needed
106
+ xds = set_index_coordinates(data, (axis_labels['x']['axis'], axis_labels['y']['axis']))
107
+ xda = xds[data_params['correlated_data']].rename(xda_name)
108
+
109
+ # Calculate data range for flagged and unflagged data for color limits
110
+ unflagged_xda = xda.where(xds.FLAG == 0.0)
111
+ flagged_xda = xda.where(xds.FLAG == 1.0).rename("flagged " + xda_name)
112
+ unflagged_data_range = (unflagged_xda.min().values.item(), unflagged_xda.max().values.item())
113
+ flagged_data_range = (flagged_xda.min().values.item(), flagged_xda.max().values.item())
118
114
  if is_gui: # update data range for colorbar
119
- self._plot_params['data']['data_range'] = (xda.min().values.item(), xda.max().values.item())
115
+ self._plot_params['data']['data_range'] = unflagged_data_range
116
+
117
+ # Plot all data as unflagged (with hover tool), then overlay flagged data
118
+ unflagged_plot = self._plot_xda(xda, False, unflagged_data_range)
119
+ flagged_plot = self._plot_xda(flagged_xda, True, flagged_data_range)
120
120
 
121
121
  # Make Overlay plot
122
- return flagged_plot * unflagged_plot
122
+ return unflagged_plot.opts(tools=['hover']) * flagged_plot.opts(tools=[])
123
123
 
124
124
  def _get_plot_title(self, data, plot_inputs, ms_name):
125
125
  ''' Form string containing ms name and selected values using data (xArray Dataset) '''
@@ -172,34 +172,34 @@ class RasterPlot:
172
172
  self._plot_params['plot']['axis_labels'][axis]['label'] = axis_labels[1]
173
173
  self._plot_params['plot']['axis_labels'][axis]['ticks'] = axis_labels[2]
174
174
 
175
- def _plot_xda(self, xda, is_flagged=False):
176
- # Returns Quadmesh plot if raster 2D data, Scatter plot if raster 1D data, or None if no data
177
- plot_params = self._plot_params['plot']
178
- style_params = self._plot_params['style']
175
+ def _plot_xda(self, xda, is_flagged=False, data_range=None):
176
+ ''' Return Quadmesh plot if raster 2D data, Scatter plot if raster 1D data, or None if no data '''
177
+ # Color limits
178
+ c_lim = self._plot_params['plot']['color_limits']
179
+ c_lim = data_range if c_lim is None else c_lim
179
180
 
180
- x_axis = plot_params['axis_labels']['x']['axis']
181
- y_axis = plot_params['axis_labels']['y']['axis']
182
- c_label = plot_params['axis_labels']['c']['label']
183
- c_lim = plot_params['color_limits']
181
+ axis_labels = self._plot_params['plot']['axis_labels']
182
+ x_axis = axis_labels['x']['axis']
183
+ y_axis = axis_labels['y']['axis']
184
+ c_label = axis_labels['c']['label']
184
185
 
186
+ # Set time formatter
185
187
  x_formatter = get_time_formatter() if x_axis == 'time' else None
186
188
  y_formatter = get_time_formatter() if y_axis == 'time' else None
187
189
 
190
+ # Show colorbar
191
+ show_colorbar = False
188
192
  if xda.count().values > 0:
189
- if is_flagged:
190
- show_colorbar = style_params['show_flagged_colorbar']
191
- else :
192
- show_colorbar = style_params['show_colorbar']
193
- else:
194
- show_colorbar = False
193
+ show_colorbar = self._plot_params['style']['show_flagged_colorbar'] if is_flagged else self._plot_params['style']['show_colorbar']
195
194
 
195
+ # Set colorbar label and map
196
196
  if is_flagged:
197
197
  c_label = "Flagged " + c_label
198
- colormap = style_params['flagged_cmap']
199
- plot_params['flagged_colorbar'] = show_colorbar
198
+ colormap = self._plot_params['style']['flagged_cmap']
199
+ self._plot_params['plot']['flagged_colorbar'] = show_colorbar
200
200
  else:
201
- colormap = style_params['unflagged_cmap']
202
- plot_params['unflagged_colorbar'] = show_colorbar
201
+ colormap = self._plot_params['style']['unflagged_cmap']
202
+ self._plot_params['plot']['unflagged_colorbar'] = show_colorbar
203
203
 
204
204
  if xda[x_axis].size > 1 and xda[y_axis].size > 1:
205
205
  # Raster 2D data
@@ -209,16 +209,16 @@ class RasterPlot:
209
209
  clim=c_lim,
210
210
  cmap=colormap,
211
211
  clabel=c_label,
212
- title=plot_params['title'],
213
- xlabel=plot_params['axis_labels']['x']['label'],
214
- ylabel=plot_params['axis_labels']['y']['label'],
212
+ title=self._plot_params['plot']['title'],
213
+ xlabel=axis_labels['x']['label'],
214
+ ylabel=axis_labels['y']['label'],
215
215
  xformatter=x_formatter,
216
216
  yformatter=y_formatter,
217
- xticks=plot_params['axis_labels']['x']['ticks'],
218
- yticks=plot_params['axis_labels']['y']['ticks'],
217
+ xticks=axis_labels['x']['ticks'],
218
+ yticks=axis_labels['y']['ticks'],
219
219
  rot=45, # angle for x axis labels
220
220
  colorbar=show_colorbar,
221
- responsive=True, # resize to fill browser window if True
221
+ responsive=True, # resize to fill browser window
222
222
  )
223
223
  else:
224
224
  # Cannot raster 1D data, use scatter from pandas dataframe
@@ -227,16 +227,16 @@ class RasterPlot:
227
227
  x=x_axis,
228
228
  y=y_axis,
229
229
  c=xda.name,
230
- clim=plot_params['color_limits'],
230
+ clim=c_lim,
231
231
  cmap=colormap,
232
232
  clabel=c_label,
233
- title=plot_params['title'],
234
- xlabel=plot_params['axis_labels']['x']['label'],
235
- ylabel=plot_params['axis_labels']['y']['label'],
233
+ title=self._plot_params['plot']['title'],
234
+ xlabel=axis_labels['x']['label'],
235
+ ylabel=axis_labels['y']['label'],
236
236
  xformatter=x_formatter,
237
237
  yformatter=y_formatter,
238
- xticks=plot_params['axis_labels']['x']['ticks'],
239
- yticks=plot_params['axis_labels']['y']['ticks'],
238
+ xticks=axis_labels['x']['ticks'],
239
+ yticks=axis_labels['y']['ticks'],
240
240
  rot=45,
241
241
  marker='s', # square
242
242
  responsive=True,
@@ -26,10 +26,10 @@ def create_raster_gui(callbacks, plot_info, empty_plot):
26
26
  pn.pane.HoloViews(empty_plot), # Row[0] plot
27
27
  pn.WidgetBox(sizing_mode='stretch_width'), # Row[1] cursor location
28
28
  )),
29
- ('Plot Inputs', pn.Column()), # Tabs[1]
30
- ('Locate Selected Points', pn.Feed(sizing_mode='stretch_height')), # Tabs[2]
31
- ('Locate Selected Box', pn.Feed(sizing_mode='stretch_height')), # Tabs[3]
32
- ('Plot Settings', pn.Column( # Tabs[4]
29
+ ('Plot Inputs', pn.Column()), # Tabs[1]
30
+ ('Locate Points', pn.Feed(sizing_mode='stretch_height')), # Tabs[2]
31
+ ('Locate Box', pn.Feed(sizing_mode='stretch_height')), # Tabs[3]
32
+ ('Plot Settings', pn.Column( # Tabs[4]
33
33
  pn.Spacer(height=25), # Column[0]
34
34
  selectors, # Column[1] selectors
35
35
  init_plot, # Column[2] plot button and spinner
File without changes
File without changes
File without changes
File without changes