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.
- vidavis/__version__.py +1 -1
- vidavis/apps/_ms_raster.py +237 -396
- vidavis/data/measurement_set/processing_set/_ps_coords.py +4 -0
- vidavis/data/measurement_set/processing_set/_ps_raster_data.py +6 -3
- vidavis/data/measurement_set/processing_set/_ps_select.py +1 -1
- vidavis/plot/ms_plot/_locate_points.py +90 -8
- vidavis/plot/ms_plot/_ms_plot.py +196 -74
- vidavis/plot/ms_plot/_ms_plot_selectors.py +18 -11
- vidavis/plot/ms_plot/_plot_inputs.py +1 -1
- vidavis/plot/ms_plot/_raster_plot.py +2 -6
- vidavis/plot/ms_plot/_raster_plot_gui.py +15 -33
- vidavis/plot/ms_plot/_raster_plot_inputs.py +113 -0
- {vidavis-0.0.13.dist-info → vidavis-0.0.15.dist-info}/METADATA +1 -1
- {vidavis-0.0.13.dist-info → vidavis-0.0.15.dist-info}/RECORD +16 -15
- {vidavis-0.0.13.dist-info → vidavis-0.0.15.dist-info}/WHEEL +0 -0
- {vidavis-0.0.13.dist-info → vidavis-0.0.15.dist-info}/licenses/LICENSE +0 -0
|
@@ -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].
|
|
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,
|
|
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(
|
|
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
|
|
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
|
-
#
|
|
52
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
vidavis/plot/ms_plot/_ms_plot.py
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
36
|
-
|
|
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
|
|
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.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
self.
|
|
68
|
-
self.
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
# Initialize
|
|
76
|
-
self.
|
|
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.
|
|
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.
|
|
98
|
-
self.
|
|
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.
|
|
105
|
-
return self.
|
|
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.
|
|
115
|
-
return self.
|
|
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.
|
|
124
|
-
self.
|
|
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.
|
|
134
|
-
self.
|
|
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.
|
|
147
|
-
self.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
175
|
+
# Add plot inputs column tab
|
|
176
|
+
inputs_column = None
|
|
168
177
|
if self._plot_params:
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
176
|
-
|
|
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
|
-
|
|
233
|
+
subplots = self._plot_inputs.get_input('subplots')
|
|
234
|
+
plot = self._layout_plots(subplots, (width, height))
|
|
197
235
|
|
|
198
|
-
|
|
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
|
-
|
|
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(
|
|
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.
|
|
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.
|
|
275
|
-
data_path = self.
|
|
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.
|
|
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.
|
|
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
|
|
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(
|
|
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
|
-
|
|
15
|
-
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
|
-
|
|
24
|
+
'~',
|
|
23
25
|
)
|
|
24
|
-
select_file = pn.bind(
|
|
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
|
-
|
|
37
|
-
|
|
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
|
-
|
|
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(
|
|
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",
|