cubevis 0.5.2__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.
Files changed (132) hide show
  1. cubevis/LICENSE.rst +500 -0
  2. cubevis/__icons__/20px/fast-backward.svg +13 -0
  3. cubevis/__icons__/20px/fast-forward.svg +13 -0
  4. cubevis/__icons__/20px/step-backward.svg +12 -0
  5. cubevis/__icons__/20px/step-forward.svg +12 -0
  6. cubevis/__icons__/add-chan.png +0 -0
  7. cubevis/__icons__/add-chan.svg +84 -0
  8. cubevis/__icons__/add-cube.png +0 -0
  9. cubevis/__icons__/add-cube.svg +186 -0
  10. cubevis/__icons__/drag.png +0 -0
  11. cubevis/__icons__/drag.svg +109 -0
  12. cubevis/__icons__/mask-selected.png +0 -0
  13. cubevis/__icons__/mask.png +0 -0
  14. cubevis/__icons__/mask.svg +1 -0
  15. cubevis/__icons__/new-layer-sm-selected.png +0 -0
  16. cubevis/__icons__/new-layer-sm-selected.svg +88 -0
  17. cubevis/__icons__/new-layer-sm.png +0 -0
  18. cubevis/__icons__/new-layer-sm.svg +15 -0
  19. cubevis/__icons__/reset.png +0 -0
  20. cubevis/__icons__/reset.svg +11 -0
  21. cubevis/__icons__/sub-chan.png +0 -0
  22. cubevis/__icons__/sub-chan.svg +71 -0
  23. cubevis/__icons__/sub-cube.png +0 -0
  24. cubevis/__icons__/sub-cube.svg +95 -0
  25. cubevis/__icons__/zoom-to-fit.png +0 -0
  26. cubevis/__icons__/zoom-to-fit.svg +21 -0
  27. cubevis/__init__.py +58 -0
  28. cubevis/__js__/bokeh-3.6.1.min.js +728 -0
  29. cubevis/__js__/bokeh-tables-3.6.1.min.js +119 -0
  30. cubevis/__js__/bokeh-widgets-3.6.1.min.js +141 -0
  31. cubevis/__js__/casalib.min.js +1 -0
  32. cubevis/__js__/cubevisjs.min.js +62 -0
  33. cubevis/__version__.py +1 -0
  34. cubevis/apps/__init__.py +44 -0
  35. cubevis/apps/_createmask.py +461 -0
  36. cubevis/apps/_createregion.py +513 -0
  37. cubevis/apps/_interactiveclean.py +3260 -0
  38. cubevis/apps/_interactiveclean_wrappers.py +130 -0
  39. cubevis/apps/_ms_raster.py +815 -0
  40. cubevis/apps/_plotants.py +286 -0
  41. cubevis/apps/_plotbandpass.py +7 -0
  42. cubevis/bokeh/__init__.py +29 -0
  43. cubevis/bokeh/annotations/__init__.py +1 -0
  44. cubevis/bokeh/annotations/_ev_poly_annotation.py +6 -0
  45. cubevis/bokeh/components/__init__.py +28 -0
  46. cubevis/bokeh/format/__init__.py +31 -0
  47. cubevis/bokeh/format/_time_ticks.py +44 -0
  48. cubevis/bokeh/format/_wcs_ticks.py +45 -0
  49. cubevis/bokeh/models/__init__.py +4 -0
  50. cubevis/bokeh/models/_edit_span.py +7 -0
  51. cubevis/bokeh/models/_ev_text_input.py +6 -0
  52. cubevis/bokeh/models/_tip.py +37 -0
  53. cubevis/bokeh/models/_tip_button.py +50 -0
  54. cubevis/bokeh/sources/__init__.py +35 -0
  55. cubevis/bokeh/sources/_data_pipe.py +258 -0
  56. cubevis/bokeh/sources/_image_data_source.py +83 -0
  57. cubevis/bokeh/sources/_image_pipe.py +581 -0
  58. cubevis/bokeh/sources/_spectra_data_source.py +55 -0
  59. cubevis/bokeh/sources/_updatable_data_source.py +189 -0
  60. cubevis/bokeh/state/__init__.py +34 -0
  61. cubevis/bokeh/state/_initialize.py +164 -0
  62. cubevis/bokeh/state/_javascript.py +53 -0
  63. cubevis/bokeh/state/_palette.py +58 -0
  64. cubevis/bokeh/state/_session.py +44 -0
  65. cubevis/bokeh/state/js/bokeh-2.4.1.min.js +596 -0
  66. cubevis/bokeh/state/js/bokeh-gl-2.4.1.min.js +74 -0
  67. cubevis/bokeh/state/js/bokeh-tables-2.4.1.min.js +132 -0
  68. cubevis/bokeh/state/js/bokeh-widgets-2.4.1.min.js +118 -0
  69. cubevis/bokeh/state/js/casaguijs-v0.0.4.0-b2.4.min.js +49 -0
  70. cubevis/bokeh/state/js/casaguijs-v0.0.5.0-b2.4.min.js +49 -0
  71. cubevis/bokeh/state/js/casaguijs-v0.0.6.0-b2.4.min.js +49 -0
  72. cubevis/bokeh/state/js/casalib-v0.0.1.min.js +1 -0
  73. cubevis/bokeh/tools/__init__.py +31 -0
  74. cubevis/bokeh/tools/_cbreset_tool.py +52 -0
  75. cubevis/bokeh/tools/_drag_tool.py +61 -0
  76. cubevis/bokeh/utils/__init__.py +35 -0
  77. cubevis/bokeh/utils/_axes_labels.py +94 -0
  78. cubevis/bokeh/utils/_svg_icon.py +136 -0
  79. cubevis/data/__init__.py +1 -0
  80. cubevis/data/casaimage/__init__.py +114 -0
  81. cubevis/data/measurement_set/__init__.py +7 -0
  82. cubevis/data/measurement_set/_ms_data.py +178 -0
  83. cubevis/data/measurement_set/processing_set/__init__.py +30 -0
  84. cubevis/data/measurement_set/processing_set/_ps_concat.py +98 -0
  85. cubevis/data/measurement_set/processing_set/_ps_coords.py +78 -0
  86. cubevis/data/measurement_set/processing_set/_ps_data.py +213 -0
  87. cubevis/data/measurement_set/processing_set/_ps_io.py +55 -0
  88. cubevis/data/measurement_set/processing_set/_ps_raster_data.py +154 -0
  89. cubevis/data/measurement_set/processing_set/_ps_select.py +91 -0
  90. cubevis/data/measurement_set/processing_set/_ps_stats.py +218 -0
  91. cubevis/data/measurement_set/processing_set/_xds_data.py +149 -0
  92. cubevis/plot/__init__.py +1 -0
  93. cubevis/plot/ms_plot/__init__.py +29 -0
  94. cubevis/plot/ms_plot/_ms_plot.py +242 -0
  95. cubevis/plot/ms_plot/_ms_plot_constants.py +22 -0
  96. cubevis/plot/ms_plot/_ms_plot_selectors.py +348 -0
  97. cubevis/plot/ms_plot/_raster_plot.py +292 -0
  98. cubevis/plot/ms_plot/_raster_plot_inputs.py +116 -0
  99. cubevis/plot/ms_plot/_xds_plot_axes.py +110 -0
  100. cubevis/private/__java__/xml-casa-assembly-1.86.jar +0 -0
  101. cubevis/private/_gclean.py +798 -0
  102. cubevis/private/casashell/createmask.py +332 -0
  103. cubevis/private/casashell/iclean.py +4432 -0
  104. cubevis/private/casatasks/__init__.py +140 -0
  105. cubevis/private/casatasks/createmask.py +86 -0
  106. cubevis/private/casatasks/createregion.py +83 -0
  107. cubevis/private/casatasks/iclean.py +1831 -0
  108. cubevis/readme.rst +16 -0
  109. cubevis/remote/__init__.py +10 -0
  110. cubevis/remote/_gclean.py +61 -0
  111. cubevis/remote/_local.py +287 -0
  112. cubevis/remote/_remote_kernel.py +80 -0
  113. cubevis/toolbox/__init__.py +32 -0
  114. cubevis/toolbox/_app_context.py +74 -0
  115. cubevis/toolbox/_cube.py +3457 -0
  116. cubevis/toolbox/_region_list.py +197 -0
  117. cubevis/utils/_ResourceManager.py +86 -0
  118. cubevis/utils/__init__.py +620 -0
  119. cubevis/utils/_contextmgrchain.py +84 -0
  120. cubevis/utils/_conversion.py +93 -0
  121. cubevis/utils/_copydoc.py +55 -0
  122. cubevis/utils/_docenum.py +25 -0
  123. cubevis/utils/_import_protected_module.py +35 -0
  124. cubevis/utils/_logging.py +85 -0
  125. cubevis/utils/_pkgs.py +77 -0
  126. cubevis/utils/_regions.py +40 -0
  127. cubevis/utils/_static.py +66 -0
  128. cubevis/utils/_tiles.py +167 -0
  129. cubevis-0.5.2.dist-info/METADATA +151 -0
  130. cubevis-0.5.2.dist-info/RECORD +132 -0
  131. cubevis-0.5.2.dist-info/WHEEL +4 -0
  132. cubevis-0.5.2.dist-info/licenses/LICENSE +504 -0
@@ -0,0 +1,78 @@
1
+ '''
2
+ Modify/add xarray Dataset coordinates for plotting.
3
+ '''
4
+
5
+ import numpy as np
6
+ from pandas import to_datetime
7
+
8
+ def set_coordinates(ms_xdt):
9
+ ''' Convert coordinate units and add baseline coordinate for plotting.
10
+ Returns xarray.Dataset
11
+ '''
12
+ _set_coordinate_unit(ms_xdt)
13
+ _set_frequency_unit(ms_xdt)
14
+ return _add_baseline_coordinate(ms_xdt)
15
+
16
+ def set_datetime_coordinate(ms_xds):
17
+ ''' Convert float time to datetime for plotting. '''
18
+ time_attrs = ms_xds.time.attrs
19
+ try:
20
+ ms_xds.coords['time'] = to_datetime(ms_xds.time, unit=time_attrs['units'], origin=time_attrs['format'])
21
+ except TypeError:
22
+ ms_xds.coords['time'] = to_datetime(ms_xds.time, unit=time_attrs['units'][0], origin=time_attrs['format'])
23
+ ms_xds.time.attrs = time_attrs
24
+
25
+ def _set_coordinate_unit(ms_xdt):
26
+ ''' Set coordinate units attribute as string not list for plotting. '''
27
+ for coord in ms_xdt.coords:
28
+ # Plots need unit to be string not list
29
+ if 'units' in ms_xdt.coords[coord].attrs:
30
+ units = ms_xdt.coords[coord].units
31
+ if isinstance(units, list) and len(units) == 1:
32
+ ms_xdt.coords[coord].attrs['units'] = units[0]
33
+
34
+ def set_index_coordinates(ms_xds, coordinates):
35
+ ''' Return ms_xds with new coordinate for string values (name) then replace coordinate with numerical index. '''
36
+ for coordinate in coordinates:
37
+ if coordinate == "polarization":
38
+ ms_xds = ms_xds.assign_coords({"polarization_name": (ms_xds.polarization.dims, ms_xds.polarization.values)})
39
+ ms_xds["polarization"] = np.array(range(ms_xds.polarization.size))
40
+ elif coordinate == "baseline":
41
+ ms_xds = ms_xds.assign_coords({"baseline_name": (ms_xds.baseline.dims, ms_xds.baseline.values)})
42
+ ms_xds["baseline"] = np.array(range(ms_xds.baseline.size))
43
+ elif coordinate == "antenna_name":
44
+ ms_xds = ms_xds.assign_coords({"antenna": (ms_xds.antenna_name.dims, ms_xds.antenna_name.values)})
45
+ ms_xds["antenna_name"] = np.array(range(ms_xds.antenna_name.size))
46
+ return ms_xds
47
+
48
+ def _set_frequency_unit(ms_xdt):
49
+ ''' Convert frequency to GHz. Note attrs (channel_width, reference_frequency) still have Hz units in dict '''
50
+ if ms_xdt.frequency.attrs['units'] == "Hz":
51
+ frequency_xda = ms_xdt.frequency / 1e9
52
+ frequency_attrs = ms_xdt.frequency.attrs
53
+ frequency_attrs['units'] = "GHz"
54
+ frequency_xda = frequency_xda.assign_attrs(frequency_attrs)
55
+ ms_xdt.coords['frequency'] = frequency_xda
56
+
57
+ def _add_baseline_coordinate(ms_xdt):
58
+ '''
59
+ Replace "baseline_id" (int) with "baseline" (string) coordinate "ant1 & ant2".
60
+ Baseline ids are not consistent across ms_xdts.
61
+ '''
62
+ # Cannot assign coords to DataTree.
63
+ baseline_ms_xdt = ms_xdt.to_dataset() # mutable Dataset
64
+
65
+ if 'baseline_id' not in baseline_ms_xdt.coords:
66
+ return baseline_ms_xdt
67
+
68
+ ant1_names = ms_xdt.baseline_antenna1_name.values
69
+ ant2_names = ms_xdt.baseline_antenna2_name.values
70
+ if ant1_names.size == 1:
71
+ baseline_names = f"{ant1_names.item()} & {ant2_names.item()}"
72
+ baseline_ms_xdt = baseline_ms_xdt.assign_coords({"baseline": np.array(baseline_names)})
73
+ else:
74
+ baseline_names = [f"{ant1_names[idx]} & {ant2_names[idx]}" for idx in range(len(ant1_names))]
75
+ baseline_ms_xdt = baseline_ms_xdt.assign_coords({"baseline": ("baseline_id", np.array(baseline_names))})
76
+ baseline_ms_xdt = baseline_ms_xdt.swap_dims({"baseline_id": "baseline"})
77
+ baseline_ms_xdt = baseline_ms_xdt.drop("baseline_id")
78
+ return baseline_ms_xdt
@@ -0,0 +1,213 @@
1
+ '''
2
+ MeasurementSet data backend using xradio Processing Set.
3
+ '''
4
+
5
+ import numpy as np
6
+ import pandas as pd
7
+
8
+ try:
9
+ from cubevis.data.measurement_set.processing_set._ps_io import get_processing_set
10
+ _HAVE_XRADIO = True
11
+ from cubevis.data.measurement_set.processing_set._ps_select import select_ps
12
+ from cubevis.data.measurement_set.processing_set._ps_stats import calculate_ps_stats
13
+ from cubevis.data.measurement_set.processing_set._ps_raster_data import raster_data
14
+ from cubevis.data.measurement_set.processing_set._xds_data import get_correlated_data
15
+ except ImportError as e:
16
+ _HAVE_XRADIO = False
17
+
18
+
19
+ class PsData:
20
+ '''
21
+ Class implementing data backend using xradio Processing Set for accessing and selecting MeasurementSet data.
22
+ '''
23
+
24
+ def __init__(self, ms, logger):
25
+ if not _HAVE_XRADIO:
26
+ raise RuntimeError("xradio package not available for reading MeasurementSet")
27
+
28
+ if not ms:
29
+ raise RuntimeError("MS path not available for reading MeasurementSet")
30
+
31
+ # Open processing set from zarr
32
+ # Converts msv2 if ms path is not zarr
33
+ self._ps_xdt, self._zarr_path = get_processing_set(ms, logger)
34
+
35
+ self._logger = logger
36
+ self._selection = {}
37
+ self._selected_ps_xdt = None # cumulative selection
38
+
39
+ def get_path(self):
40
+ ''' Return path to zarr file (input or converted from msv2) '''
41
+ return self._zarr_path
42
+
43
+ def summary(self, data_group='base', columns=None):
44
+ ''' Print full or selected summary of Processing Set metadata, optionally by ms '''
45
+ ps_summary = self._ps_xdt.xr_ps.summary(data_group=data_group)
46
+ pd.set_option("display.max_rows", len(self._ps_xdt))
47
+ pd.set_option("display.max_columns", len(ps_summary.columns))
48
+ pd.set_option("display.max_colwidth", None)
49
+
50
+ if columns is None:
51
+ print(ps_summary)
52
+ elif columns == "by_ms":
53
+ for row in ps_summary.itertuples(index=False):
54
+ print(f"MSv4 name: {row[0]}")
55
+ print(f"intent: {row[1]}")
56
+ shape = row[2]
57
+ print(f"shape: {shape[0]} times, {shape[1]} baselines, {shape[2]} channels, {shape[3]} polarizations")
58
+ print(f"polarization: {row[3]}")
59
+ scans = [str(scan) for scan in row[4]]
60
+ print(f"scan_name: {scans}")
61
+ print(f"spw_name: {row[5]}")
62
+ fields = [str(field) for field in row[6]]
63
+ print(f"field_name: {fields}")
64
+ sources = [str(source) for source in row[7]]
65
+ print(f"source_name: {sources}")
66
+ lines = [str(line) for line in row[8]]
67
+ print(f"line_name: {lines}")
68
+ field_coords = row[9]
69
+ print(f"field_coords: ({field_coords[0]}) {field_coords[1]} {field_coords[2]}")
70
+ print(f"frequency range: {row[10]:e} - {row[11]:e}")
71
+ print("-----")
72
+ else:
73
+ if isinstance(columns, str):
74
+ columns = [columns]
75
+ col_df = ps_summary[columns]
76
+ print(col_df)
77
+
78
+ def get_summary(self):
79
+ ''' Return summary of original ps '''
80
+ return self._ps_xdt.xr_ps.summary()
81
+
82
+ def get_data_groups(self):
83
+ ''' Returns set of data group names in Processing Set data. '''
84
+ data_groups = []
85
+ for ms_xdt_name in self._ps_xdt:
86
+ data_groups.extend(list(self._ps_xdt[ms_xdt_name].data_groups))
87
+ return set(data_groups)
88
+
89
+ def get_antennas(self, plot_positions=False, label_antennas=False):
90
+ ''' Returns list of antenna names in ProcessingSet antenna_xds.
91
+ plot_positions (bool): show plot of antenna positions.
92
+ label_antennas (bool): label positions with antenna names.
93
+ '''
94
+ if plot_positions:
95
+ self._ps_xdt.xr_ps.plot_antenna_positions(label_antennas)
96
+ return self._ps_xdt.xr_ps.get_combined_antenna_xds().antenna_name.values.tolist()
97
+
98
+ def plot_phase_centers(self, label_all_fields=False, data_group='base'):
99
+ ''' Plot the phase center locations of all fields in the Processing Set (original or selected) and label central field.
100
+ label_all_fields (bool); label all fields on the plot
101
+ data_group (str); data group to use for processing.
102
+ '''
103
+ self._ps_xdt.xr_ps.plot_phase_centers(label_all_fields, data_group)
104
+
105
+ def get_ps_len(self):
106
+ ''' Returns number of ms_xdt in selected ps_xdt (if selected) '''
107
+ return len(self._get_ps_xdt())
108
+
109
+ def get_max_dims(self):
110
+ ''' Returns maximum length of data dimensions in selected ps_xdt (if selected) '''
111
+ ps_xdt = self._get_ps_xdt()
112
+ return ps_xdt.xr_ps.get_max_dims()
113
+
114
+ def get_data_dimensions(self):
115
+ ''' Return the maximum dimensions in selected ps_xdt (if selected) '''
116
+ dims = list(self.get_max_dims().keys())
117
+ if 'uvw_label' in dims:
118
+ dims.remove('uvw_label') # not a VISIBILITY/SPECTRUM data dim
119
+ return dims
120
+
121
+ def get_dimension_values(self, dimension):
122
+ ''' Return sorted list of unique values for input dimension in ProcessingSet. '''
123
+ ps_xdt = self._get_ps_xdt()
124
+ dim_values = []
125
+ for ms_xdt in ps_xdt.values():
126
+ if dimension == 'baseline':
127
+ ant1_names = ms_xdt.baseline_antenna1_name.values
128
+ ant2_names = ms_xdt.baseline_antenna2_name.values
129
+ for baseline_id in ms_xdt.baseline_id:
130
+ dim_values.append(f"{ant1_names[baseline_id]} & {ant2_names[baseline_id]}")
131
+ else:
132
+ try:
133
+ dim_values.extend([value.item() for value in ms_xdt[dimension].values])
134
+ except TypeError:
135
+ dim_values.append(ms_xdt[dimension].values.item())
136
+ return sorted(set(dim_values))
137
+
138
+ def get_dimension_attrs(self, dim):
139
+ ''' Return attributes dict for input dimension in ProcessingSet. '''
140
+ ps_xdt = self._get_ps_xdt()
141
+ return ps_xdt.get(0)[dim].attrs
142
+
143
+ def get_first_spw(self):
144
+ ''' Return first spw name by id '''
145
+ spw_id_names = {}
146
+ ps_xdt = self._get_ps_xdt()
147
+ for ms_xdt in ps_xdt.values():
148
+ freq_xds = ms_xdt.frequency
149
+ spw_id_names[freq_xds.spectral_window_id] = freq_xds.spectral_window_name
150
+
151
+ first_spw_id = min(spw_id_names)
152
+ first_spw_name = spw_id_names[first_spw_id]
153
+
154
+ summary = self.get_summary()
155
+ spw_df = summary[summary['spw_name'] == first_spw_name]
156
+ start_freq = spw_df.at[spw_df.index[0], 'start_frequency']
157
+ end_freq = spw_df.at[spw_df.index[0], 'end_frequency']
158
+ self._logger.info(f"Selecting first spw {first_spw_name} (id {first_spw_id}) with frequency range {start_freq:e} - {end_freq:e}")
159
+ return first_spw_name
160
+
161
+ def select_data(self, selection):
162
+ ''' Apply selection dict to ProcessingSet to create selected ps_xdt.
163
+ If previous selection done, apply to selected ps_xdt.
164
+ Add selection to previous selections. '''
165
+ ps_xdt = self._get_ps_xdt()
166
+ self._selected_ps_xdt = select_ps(ps_xdt, selection, self._logger)
167
+ if self._selection:
168
+ self._selection |= selection
169
+ else:
170
+ self._selection = selection
171
+
172
+ def clear_selection(self):
173
+ ''' Clear previous selections and use original ps_xdt '''
174
+ self._selection = None
175
+ self._selected_ps_xdt = None
176
+
177
+ def get_vis_stats(self, selection, vis_axis):
178
+ ''' Returns statistics (min, max, mean, std) for data selected by selection.
179
+ selection (dict): fields and values to select
180
+ '''
181
+ stats_ps_xdt = select_ps(self._ps_xdt, selection, self._logger)
182
+ data_group = selection['data_group'] if 'data_group' in selection else 'base'
183
+ return calculate_ps_stats(stats_ps_xdt, self._zarr_path, vis_axis, data_group, self._logger)
184
+
185
+ def get_correlated_data(self, data_group):
186
+ ''' Returns name of 'correlated_data' in Processing Set data_group '''
187
+ ps_xdt = self._get_ps_xdt()
188
+ for ms_xdt in ps_xdt.values():
189
+ if data_group in ms_xdt.attrs['data_groups']:
190
+ return get_correlated_data(ms_xdt.ds, data_group)
191
+ raise RuntimeError(f"No correlated data for data group {data_group}")
192
+
193
+ def get_raster_data(self, plot_inputs):
194
+ ''' Returns xarray Dataset after applying plot inputs and raster plane selection '''
195
+ return raster_data(self._get_ps_xdt(),
196
+ plot_inputs,
197
+ self._logger
198
+ )
199
+
200
+ def _get_ps_xdt(self):
201
+ ''' Returns selected ps_xdt if selection has been done, else original ps_xdt '''
202
+ return self._selected_ps_xdt if self._selected_ps_xdt else self._ps_xdt
203
+
204
+ def _get_unique_values(self, df_col):
205
+ ''' Return unique values in pandas Dataframe column, for summary '''
206
+ values = df_col.to_numpy()
207
+ try:
208
+ # numeric arrays
209
+ return np.unique(np.concatenate(values))
210
+ except ValueError:
211
+ # string arrays
212
+ all_values = [row[0] for row in values]
213
+ return np.unique(np.concatenate(all_values))
@@ -0,0 +1,55 @@
1
+ """
2
+ Functions for reading MSv2 or zarr MSv4 into xradio ProcessingSet
3
+ """
4
+
5
+ import os.path
6
+
7
+ from xradio.measurement_set.open_processing_set import open_processing_set
8
+
9
+ try:
10
+ # requires python-casacore
11
+ from xradio.measurement_set.convert_msv2_to_processing_set import convert_msv2_to_processing_set
12
+ __HAVE_CASACORE = True
13
+ except ImportError:
14
+ __HAVE_CASACORE = False
15
+
16
+ def get_processing_set(ms_path, logger):
17
+ '''
18
+ Read msv2 or zarr file into processing set
19
+
20
+ Args:
21
+ ms_path (str): path to MSv2 or MSv4 zarr file
22
+ Returns:
23
+ xradio ProcessingSet
24
+ '''
25
+
26
+ if not os.path.exists(ms_path):
27
+ raise RuntimeError(f"Visibility file {ms_path} does not exist")
28
+
29
+ # Remove trailing / or entire path is basename with no extension
30
+ if ms_path[-1] == '/':
31
+ ms_path = ms_path[:-1]
32
+
33
+ basename, ext = os.path.splitext(ms_path)
34
+ if ext == ".zarr":
35
+ zarr_path = ms_path
36
+ else:
37
+ if not __HAVE_CASACORE:
38
+ raise RuntimeError("Cannot convert MSv2 to xradio zarr file: python-casacore not installed.")
39
+ zarr_path = basename + ".ps.zarr"
40
+ if not os.path.exists(zarr_path):
41
+ logger.info(f"Converting input MS {ms_path} to zarr {zarr_path}")
42
+ convert_msv2_to_processing_set(
43
+ in_file=ms_path,
44
+ out_file=zarr_path
45
+ )
46
+
47
+ if not os.path.exists(zarr_path):
48
+ raise RuntimeError("Zarr file does not exist")
49
+
50
+ ps = open_processing_set(zarr_path)
51
+ if not ps or len(ps) == 0:
52
+ raise RuntimeError("Failed to read measurement set into processing set.")
53
+ logger.info(f"Processing set contains {len(ps)} msv4 datasets.")
54
+
55
+ return ps, zarr_path
@@ -0,0 +1,154 @@
1
+ '''
2
+ Functions to create a raster xarray Dataset from xradio ProcessingSet after applying plot inputs
3
+ '''
4
+
5
+ import numpy as np
6
+
7
+ from xradio.measurement_set._utils._utils.stokes_types import stokes_types
8
+
9
+ from cubevis.data.measurement_set.processing_set._ps_concat import concat_ps_xdt
10
+ from cubevis.data.measurement_set.processing_set._ps_coords import set_datetime_coordinate
11
+ from cubevis.data.measurement_set.processing_set._ps_select import select_ps
12
+ from cubevis.data.measurement_set.processing_set._xds_data import get_axis_data
13
+
14
+ def raster_data(ps_xdt, plot_inputs, logger):
15
+ '''
16
+ Create raster xds: y_axis vs x_axis for vis axis.
17
+ ps_xdt (xarray DataTree): input datasets.
18
+ plot_inputs (dict): user inputs for plot
19
+ logger (graphviper logger): logger
20
+ Returns: selected xarray Dataset of visibility component and updated selection
21
+ '''
22
+ raster_xdt, dim_selection = _select_raster_ps_xdt(ps_xdt, plot_inputs, logger)
23
+ plot_inputs['dim_selection'] = dim_selection
24
+
25
+ # Create xds from concat ms_xds in ps
26
+ raster_xds = concat_ps_xdt(raster_xdt, logger)
27
+ correlated_data = plot_inputs['correlated_data']
28
+ if raster_xds[correlated_data].count() == 0:
29
+ raise RuntimeError("Plot failed: raster plane selection yielded data with all nan values.")
30
+
31
+ # Set complex component of vis data
32
+ raster_xds[correlated_data] = get_axis_data(raster_xds,
33
+ plot_inputs['vis_axis'],
34
+ plot_inputs['selection']['data_group_name']
35
+ )
36
+
37
+ # Convert float time to datetime
38
+ set_datetime_coordinate(raster_xds)
39
+
40
+ # Apply aggregator
41
+ raster_xds = aggregate_data(raster_xds, plot_inputs, logger)
42
+
43
+ logger.debug(f"Plotting visibility data with shape: {raster_xds[correlated_data].shape}")
44
+ return raster_xds
45
+
46
+ def _select_raster_ps_xdt(ps_xdt, plot_inputs, logger):
47
+ ''' Select default dimensions if needed for raster data '''
48
+ # Determine which dims must be selected, add to selection, and do selection
49
+ input_selection = plot_inputs['selection']
50
+ dims_to_select = _get_raster_selection_dims(plot_inputs)
51
+ dim_selection = {} # return value
52
+
53
+ if dims_to_select:
54
+ for dim in dims_to_select:
55
+ # Select first value (by index) and add to dim selection, or apply iter_axis value
56
+ # (user selection would have been applied previously)
57
+ if dim not in input_selection:
58
+ dim_selection[dim] = _get_first_dim_value(ps_xdt, dim, plot_inputs, logger)
59
+ elif dim == plot_inputs['iter_axis']:
60
+ dim_selection[dim] = input_selection[dim]
61
+ if dim_selection:
62
+ logger.info(f"Applying raster plane selection (using first index or iter value): {dim_selection}")
63
+ return select_ps(ps_xdt, dim_selection, logger), dim_selection
64
+ return ps_xdt, dim_selection
65
+
66
+ def _get_raster_selection_dims(plot_inputs):
67
+ ''' Return which dimensions should be selected for raster plot.
68
+ List of dimensions which are not x, y, or agg axis. '''
69
+ data_dims = plot_inputs['data_dims'].copy()
70
+ if plot_inputs['x_axis'] in data_dims:
71
+ data_dims.remove(plot_inputs['x_axis'])
72
+ if plot_inputs['y_axis'] in data_dims:
73
+ data_dims.remove(plot_inputs['y_axis'])
74
+ if plot_inputs['aggregator'] and plot_inputs['agg_axis']:
75
+ for axis in plot_inputs['agg_axis']:
76
+ data_dims.remove(axis)
77
+ return data_dims
78
+
79
+ def _get_first_dim_value(ps_xdt, dim, plot_inputs, logger):
80
+ ''' Return value of first dimension by index for polarization or by value for others. '''
81
+ # If iter_axis, get first dim value after iter value is selected to avoid empty selected ps
82
+ iter_axis = plot_inputs['iter_axis'] if 'iter_axis' in plot_inputs else None
83
+ iter_ps = ps_xdt
84
+ if iter_axis:
85
+ iter_selection = {iter_axis: plot_inputs['selection'][iter_axis]}
86
+ iter_ps = select_ps(ps_xdt, iter_selection, logger)
87
+
88
+ values = []
89
+ if dim == "polarization":
90
+ # Get sorted list of polarization ids used
91
+ stokes = list(stokes_types.values())
92
+ for ms_xdt in iter_ps.values():
93
+ for val in ms_xdt.polarization.values:
94
+ values.append(stokes.index(val))
95
+ sorted_values = sorted(list(set(values)))
96
+ # Get pol name for pol id
97
+ return stokes[sorted_values[0]]
98
+
99
+ if dim == 'baseline':
100
+ for ms_xdt in iter_ps.values():
101
+ ant1_names = ms_xdt.baseline_antenna1_name.values
102
+ ant2_names = ms_xdt.baseline_antenna2_name.values
103
+ for baseline_id in ms_xdt.baseline_id.values:
104
+ values.append(ant1_names[baseline_id] + " & " + ant2_names[baseline_id])
105
+ return sorted(list(set(values)))[0]
106
+
107
+ # Get sorted values list
108
+ for ms_xdt in iter_ps.values():
109
+ values.extend(ms_xdt[dim].values.tolist())
110
+ return sorted(list(set(values)))[0]
111
+
112
+ def _add_index_dimensions(xds):
113
+ ''' Add index coordinates for plotting if coord has dimension '''
114
+ # Baseline/antenna id
115
+ if "baseline" in xds.coords and xds.baseline.dims:
116
+ xds = xds.assign_coords({"baseline_id": (xds.baseline.dims, np.array(range(xds.baseline.size)))})
117
+ xds = xds.swap_dims({"baseline": "baseline_id"})
118
+ elif "antenna_name" in xds.coords and xds.antenna_name.dims:
119
+ xds = xds.assign_coords({"antenna_id": (xds.antenna_name.dims, np.array(range(xds.antenna_name.size)))})
120
+
121
+ # Polarization id
122
+ if "polarization" in xds.coords and xds.polarization.dims:
123
+ xds = xds.assign_coords({"polarization_id": (xds.polarization.dims, np.array(range(xds.polarization.size)))})
124
+
125
+ return xds
126
+
127
+ def aggregate_data(xds, plot_inputs, logger):
128
+ ''' Apply aggregator to agg axis list. '''
129
+ if not plot_inputs['aggregator']:
130
+ return xds
131
+
132
+ aggregator = plot_inputs['aggregator']
133
+ agg_axis = plot_inputs['agg_axis']
134
+ agg_xds = xds
135
+
136
+ # Check if agg axes have been selected (selection or iteration) and are no longer a dimension
137
+ apply_agg_axis = [axis for axis in agg_axis if axis in xds.dims]
138
+ logger.debug(f"Applying {aggregator} to {apply_agg_axis}.")
139
+
140
+ if aggregator == 'max':
141
+ agg_xds = xds.max(dim=apply_agg_axis, keep_attrs=True)
142
+ elif aggregator == 'mean':
143
+ agg_xds = xds.mean(dim=apply_agg_axis, keep_attrs=True)
144
+ elif aggregator == 'median':
145
+ agg_xds = xds.median(dim=apply_agg_axis, keep_attrs=True)
146
+ elif aggregator == 'min':
147
+ agg_xds = xds.min(dim=apply_agg_axis, keep_attrs=True)
148
+ elif aggregator == 'std':
149
+ agg_xds = xds.std(dim=apply_agg_axis, keep_attrs=True)
150
+ elif aggregator == 'sum':
151
+ agg_xds = xds.sum(dim=apply_agg_axis, keep_attrs=True)
152
+ elif aggregator == 'var':
153
+ agg_xds = xds.var(dim=apply_agg_axis, keep_attrs=True)
154
+ return agg_xds
@@ -0,0 +1,91 @@
1
+ ''' Apply selection dict to ProcessingSet and MeasurementSetXds '''
2
+
3
+ def select_ps(ps_xdt, selection, logger):
4
+ '''
5
+ Apply selection dict to Processing Set.
6
+ Select Processing Set first (ps summary columns), then each MeasurementSetXds (data_group etc.).
7
+ Returns dict of selected name, ms_xdt.
8
+ Throws exception for empty Processing Set (null selection).
9
+ '''
10
+ if not selection:
11
+ return ps_xdt
12
+
13
+ # Separate PS selection and MS selection
14
+ ps_summary = ps_xdt.xr_ps.summary()
15
+ ps_selection_keys = list(ps_summary.columns.array)
16
+ ps_selection_keys.append('data_group')
17
+
18
+ first_ms = ps_summary['name'][0]
19
+ ms_selection_keys = list(ps_xdt[first_ms].coords.keys())
20
+
21
+ # Sort selections into categories
22
+ ps_selection = {}
23
+ ms_selection = {}
24
+ antenna_selection = {}
25
+ for key, val in selection.items():
26
+ if key in ps_selection_keys and selection[key]:
27
+ ps_selection[key] = val
28
+ if key in ms_selection_keys and selection[key]:
29
+ ms_selection[key] = val
30
+ if 'antenna1' in key or 'antenna2' in key:
31
+ antenna_selection[key] = val
32
+ elif key == 'baseline':
33
+ ant1, ant2 = val.split('&')
34
+ antenna_selection['antenna1'] = ant1.strip()
35
+ antenna_selection['antenna2'] = ant2.strip()
36
+
37
+ # Do PSXdt selection
38
+ logger.debug(f"Applying selection to ProcessingSet: {ps_selection}")
39
+
40
+ if ps_selection:
41
+ selected_ps_xdt = ps_xdt.xr_ps.query(**ps_selection)
42
+ if len(selected_ps_xdt) == 0:
43
+ raise RuntimeError("Selection failed: ps selection yielded empty processing set.")
44
+ else:
45
+ selected_ps_xdt = ps_xdt.copy()
46
+
47
+ # Do MSXdt selection
48
+ return _select_ms_xdt(selected_ps_xdt, ms_selection, antenna_selection, logger)
49
+
50
+ def _select_ms_xdt(ps_xdt, ms_selection, antenna_selection, logger):
51
+ ''' Apply selection to each MeasurementSetXds and return ProcessingSet.
52
+ Remove ms_xds which do not contain selection.
53
+ '''
54
+ # Done if no selection to apply
55
+ if not ms_selection and not antenna_selection:
56
+ return ps_xdt
57
+
58
+ logger.debug(f"Applying selection to measurement set: {ms_selection}, {antenna_selection}")
59
+
60
+ # Drop ms_xdt where selection fails
61
+ names_to_drop = []
62
+
63
+ for name, ms_xdt in ps_xdt.items():
64
+ try:
65
+ if antenna_selection:
66
+ for antenna, val in antenna_selection.items():
67
+ if antenna == 'antenna1':
68
+ ms_xdt = ms_xdt.sel(baseline_id=ms_xdt.baseline_antenna1_name==val)
69
+ else:
70
+ ms_xdt = ms_xdt.sel(baseline_id=ms_xdt.baseline_antenna2_name==val)
71
+
72
+ if ms_xdt.baseline_id.size == 1:
73
+ # Select baseline_id to remove dimension
74
+ ms_xdt = ms_xdt.sel(baseline_id=ms_xdt.baseline_id.item())
75
+ elif ms_xdt.baseline_id.size == 0:
76
+ names_to_drop.append(name)
77
+ continue
78
+
79
+ ms_xdt = ms_xdt.xr_ms.sel(**ms_selection)
80
+ ps_xdt[name] = ms_xdt
81
+ except KeyError:
82
+ # selection not in this ms_xdt, do not include in returned ps_xdt
83
+ names_to_drop.append(name)
84
+
85
+ if names_to_drop:
86
+ ps_xdt = ps_xdt.drop_nodes(names_to_drop)
87
+
88
+ if len(ps_xdt) == 0:
89
+ raise RuntimeError("Selection failed: ms selection yielded empty processing set.")
90
+
91
+ return ps_xdt