xradio 0.0.48__py3-none-any.whl → 0.0.50__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 (32) hide show
  1. xradio/__init__.py +1 -0
  2. xradio/_utils/dict_helpers.py +69 -2
  3. xradio/image/_util/__init__.py +0 -3
  4. xradio/image/_util/_casacore/common.py +0 -13
  5. xradio/image/_util/_casacore/xds_from_casacore.py +102 -97
  6. xradio/image/_util/_casacore/xds_to_casacore.py +36 -24
  7. xradio/image/_util/_fits/xds_from_fits.py +81 -36
  8. xradio/image/_util/_zarr/zarr_low_level.py +3 -3
  9. xradio/image/_util/casacore.py +7 -5
  10. xradio/image/_util/common.py +13 -26
  11. xradio/image/_util/image_factory.py +143 -191
  12. xradio/image/image.py +10 -59
  13. xradio/measurement_set/__init__.py +11 -6
  14. xradio/measurement_set/_utils/_msv2/_tables/read.py +187 -46
  15. xradio/measurement_set/_utils/_msv2/_tables/table_query.py +22 -0
  16. xradio/measurement_set/_utils/_msv2/conversion.py +352 -318
  17. xradio/measurement_set/_utils/_msv2/msv4_info_dicts.py +20 -17
  18. xradio/measurement_set/convert_msv2_to_processing_set.py +46 -6
  19. xradio/measurement_set/load_processing_set.py +100 -53
  20. xradio/measurement_set/measurement_set_xdt.py +319 -0
  21. xradio/measurement_set/open_processing_set.py +122 -86
  22. xradio/measurement_set/processing_set_xdt.py +1552 -0
  23. xradio/measurement_set/schema.py +201 -94
  24. xradio/schema/bases.py +5 -1
  25. xradio/schema/check.py +97 -5
  26. {xradio-0.0.48.dist-info → xradio-0.0.50.dist-info}/METADATA +5 -4
  27. {xradio-0.0.48.dist-info → xradio-0.0.50.dist-info}/RECORD +30 -30
  28. {xradio-0.0.48.dist-info → xradio-0.0.50.dist-info}/WHEEL +1 -1
  29. xradio/measurement_set/measurement_set_xds.py +0 -117
  30. xradio/measurement_set/processing_set.py +0 -803
  31. {xradio-0.0.48.dist-info → xradio-0.0.50.dist-info/licenses}/LICENSE.txt +0 -0
  32. {xradio-0.0.48.dist-info → xradio-0.0.50.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,319 @@
1
+ import pandas as pd
2
+ from xradio._utils.list_and_array import to_list
3
+ import xarray as xr
4
+ import numpy as np
5
+ import numbers
6
+ import os
7
+ from collections.abc import Mapping, Iterable
8
+ from typing import Any, Union
9
+
10
+ MS_DATASET_TYPES = {"visibility", "spectrum", "radiometer"}
11
+
12
+
13
+ class InvalidAccessorLocation(ValueError):
14
+ """
15
+ Raised by MeasurementSetXdt accessor functions called on a wrong DataTree node (not MSv4).
16
+ """
17
+
18
+ pass
19
+
20
+
21
+ @xr.register_datatree_accessor("xr_ms")
22
+ class MeasurementSetXdt:
23
+ """Accessor to the Measurement Set DataTree node. Provides MSv4 specific functionality
24
+ such as:
25
+
26
+ - get_partition_info(): produce an info dict with a general MSv4 description including
27
+ intents, SPW name, field and source names, etc.
28
+ - get_field_and_source_xds() to retrieve the field_and_source_xds for a given data
29
+ group.
30
+ - sel(): select data by dimension labels, for example by data group and polaritzation
31
+
32
+ """
33
+
34
+ _xdt: xr.DataTree
35
+
36
+ def __init__(self, datatree: xr.DataTree):
37
+ """
38
+ Initialize the MeasurementSetXdt instance.
39
+
40
+ Parameters
41
+ ----------
42
+ datatree: xarray.DataTree
43
+ The MSv4 DataTree node to construct a MeasurementSetXdt accessor.
44
+ """
45
+
46
+ self._xdt = datatree
47
+ self.meta = {"summary": {}}
48
+
49
+ def sel(
50
+ self,
51
+ indexers: Union[Mapping[Any, Any], None] = None,
52
+ method: Union[str, None] = None,
53
+ tolerance: Union[int, float, Iterable[Union[int, float]], None] = None,
54
+ drop: bool = False,
55
+ **indexers_kwargs: Any,
56
+ ) -> xr.DataTree:
57
+ """
58
+ Select data along dimension(s) by label. Alternative to `xarray.Dataset.sel <https://xarray.pydata.org/en/stable/generated/xarray.Dataset.sel.html>`__ so that a data group can be selected by name by using the `data_group_name` parameter.
59
+ For more information on data groups see `Data Groups <https://xradio.readthedocs.io/en/latest/measurement_set_overview.html#Data-Groups>`__ section. See `xarray.Dataset.sel <https://xarray.pydata.org/en/stable/generated/xarray.Dataset.sel.html>`__ for parameter descriptions.
60
+
61
+ Returns
62
+ -------
63
+ xarray.DataTree
64
+ xarray DataTree with MeasurementSetXdt accessors
65
+
66
+ Examples
67
+ --------
68
+ >>> # Select data group 'corrected' and polarization 'XX'.
69
+ >>> selected_ms_xdt = ms_xdt.xr_ms.sel(data_group_name='corrected', polarization='XX')
70
+
71
+ >>> # Select data group 'corrected' and polarization 'XX' using a dict.
72
+ >>> selected_ms_xdt = ms_xdt.xr_ms.sel({'data_group_name':'corrected', 'polarization':'XX')
73
+ """
74
+
75
+ if self._xdt.attrs.get("type") not in MS_DATASET_TYPES:
76
+ raise InvalidAccessorLocation(f"{self._xdt.path} is not a MSv4 node.")
77
+
78
+ assert self._xdt.attrs["type"] in [
79
+ "visibility",
80
+ "spectrum",
81
+ "radiometer",
82
+ ], "The type of the xdt must be 'visibility', 'spectrum' or 'radiometer'."
83
+
84
+ if "data_group_name" in indexers_kwargs:
85
+ data_group_name = indexers_kwargs["data_group_name"]
86
+ del indexers_kwargs["data_group_name"]
87
+ elif (indexers is not None) and ("data_group_name" in indexers):
88
+ data_group_name = indexers["data_group_name"]
89
+ del indexers["data_group_name"]
90
+ else:
91
+ data_group_name = None
92
+
93
+ if data_group_name is not None:
94
+ sel_data_group_set = set(
95
+ self._xdt.attrs["data_groups"][data_group_name].values()
96
+ ) - set(["date", "description"])
97
+
98
+ sel_field_and_source_xds = self._xdt.attrs["data_groups"][data_group_name][
99
+ "field_and_source"
100
+ ]
101
+
102
+ data_variables_to_drop = []
103
+ field_and_source_to_drop = []
104
+ for dg_name, dg in self._xdt.attrs["data_groups"].items():
105
+ print(f"Data group: {dg_name}", dg)
106
+ f_and_s = dg["field_and_source"]
107
+ dg_copy = dg.copy()
108
+ dg_copy.pop("date", None)
109
+ dg_copy.pop("description", None)
110
+ dg_copy.pop("field_and_source", None)
111
+ temp_set = set(dg_copy.values()) - sel_data_group_set
112
+ data_variables_to_drop.extend(list(temp_set))
113
+
114
+ if f_and_s != sel_field_and_source_xds:
115
+ field_and_source_to_drop.append(f_and_s)
116
+
117
+ data_variables_to_drop = list(set(data_variables_to_drop))
118
+
119
+ sel_ms_xdt = self._xdt
120
+
121
+ print("Data variables to drop: ", data_variables_to_drop)
122
+ print("Field and source to drop: ", field_and_source_to_drop)
123
+
124
+ sel_corr_xds = self._xdt.ds.sel(
125
+ indexers, method, tolerance, drop, **indexers_kwargs
126
+ ).drop_vars(data_variables_to_drop)
127
+
128
+ sel_ms_xdt.ds = sel_corr_xds
129
+
130
+ sel_ms_xdt.attrs["data_groups"] = {
131
+ data_group_name: self._xdt.attrs["data_groups"][data_group_name]
132
+ }
133
+
134
+ return sel_ms_xdt
135
+ else:
136
+ return self._xdt.sel(indexers, method, tolerance, drop, **indexers_kwargs)
137
+
138
+ def get_field_and_source_xds(self, data_group_name: str = None) -> xr.Dataset:
139
+ """Get the field_and_source_xds associated with data group `data_group_name`.
140
+
141
+ Parameters
142
+ ----------
143
+ data_group_name : str, optional
144
+ The data group to process. Default is "base" or if not found to first data group.
145
+
146
+ Returns
147
+ -------
148
+ xarray.Dataset
149
+ field_and_source_xds associated with the data group.
150
+ """
151
+ if self._xdt.attrs.get("type") not in MS_DATASET_TYPES:
152
+ raise InvalidAccessorLocation(f"{self._xdt.path} is not a MSv4 node.")
153
+
154
+ if data_group_name is None:
155
+ if "base" in self._xdt.attrs["data_groups"].keys():
156
+ data_group_name = "base"
157
+ else:
158
+ data_group_name = list(self._xdt.attrs["data_groups"].keys())[0]
159
+
160
+ field_and_source_xds_name = self._xdt.attrs["data_groups"][data_group_name][
161
+ "field_and_source"
162
+ ]
163
+ return self._xdt[field_and_source_xds_name].ds
164
+
165
+ def get_partition_info(self, data_group_name: str = None) -> dict:
166
+ """
167
+ Generate a partition info dict for an MSv4, with general MSv4 description including
168
+ information such as field and source names, SPW name, scan name, the intents string,
169
+ etc.
170
+
171
+ The information is gathered from various coordinates, secondary datasets, and info
172
+ dicts of the MSv4. For example, the SPW name comes from the attributes of the
173
+ frequency coordinate, whereas field and source related information such as field and
174
+ source names come from the field_and_source_xds (base) dataset of the MSv4.
175
+
176
+ Parameters
177
+ ----------
178
+ data_group_name : str, optional
179
+ The data group to process. Default is "base" or if not found to first data group.
180
+
181
+ Returns
182
+ -------
183
+ dict
184
+ Partition info dict for the MSv4
185
+ """
186
+ if self._xdt.attrs.get("type") not in MS_DATASET_TYPES:
187
+ raise InvalidAccessorLocation(
188
+ f"{self._xdt.path} is not a MSv4 node (type {self._xdt.attrs.get('type')}."
189
+ )
190
+
191
+ if data_group_name is None:
192
+ if "base" in self._xdt.attrs["data_groups"].keys():
193
+ data_group_name = "base"
194
+ else:
195
+ data_group_name = list(self._xdt.attrs["data_groups"].keys())[0]
196
+
197
+ field_and_source_xds = self._xdt.xr_ms.get_field_and_source_xds(data_group_name)
198
+
199
+ if "line_name" in field_and_source_xds.coords:
200
+ line_name = to_list(
201
+ np.unique(np.ravel(field_and_source_xds.line_name.values))
202
+ )
203
+ else:
204
+ line_name = []
205
+
206
+ partition_info = {
207
+ "spectral_window_name": self._xdt.frequency.attrs["spectral_window_name"],
208
+ "field_name": to_list(np.unique(field_and_source_xds.field_name.values)),
209
+ "polarization_setup": to_list(self._xdt.polarization.values),
210
+ "scan_name": to_list(np.unique(self._xdt.scan_name.values)),
211
+ "source_name": to_list(np.unique(field_and_source_xds.source_name.values)),
212
+ "intents": self._xdt.observation_info["intents"],
213
+ "line_name": line_name,
214
+ "data_group_name": data_group_name,
215
+ }
216
+
217
+ return partition_info
218
+
219
+ def add_data_group(
220
+ self,
221
+ new_data_group_name: str,
222
+ correlated_data: str = None,
223
+ weight: str = None,
224
+ flag: str = None,
225
+ uvw: str = None,
226
+ field_and_source_xds: str = None,
227
+ date_time: str = None,
228
+ description: str = None,
229
+ data_group_dv_shared_with: str = None,
230
+ ) -> xr.DataTree:
231
+ """_summary_
232
+
233
+ Parameters
234
+ ----------
235
+ new_data_group_name : str
236
+ _description_
237
+ correlated_data : str, optional
238
+ _description_, by default None
239
+ weights : str, optional
240
+ _description_, by default None
241
+ flag : str, optional
242
+ _description_, by default None
243
+ uvw : str, optional
244
+ _description_, by default None
245
+ field_and_source_xds : str, optional
246
+ _description_, by default None
247
+ date_time : str, optional
248
+ _description_, by default None
249
+ description : str, optional
250
+ _description_, by default None
251
+ data_group_dv_shared_with : str, optional
252
+ _description_, by default "base"
253
+
254
+ Returns
255
+ -------
256
+ xr.DataTree
257
+ _description_
258
+ """
259
+
260
+ if data_group_dv_shared_with is None:
261
+ data_group_dv_shared_with = self._xdt.xr_ms._get_default_data_group_name()
262
+ default_data_group = self._xdt.attrs["data_groups"][data_group_dv_shared_with]
263
+
264
+ new_data_group = {}
265
+
266
+ if correlated_data is None:
267
+ correlated_data = default_data_group["correlated_data"]
268
+ new_data_group["correlated_data"] = correlated_data
269
+ assert (
270
+ correlated_data in self._xdt.ds.data_vars
271
+ ), f"Data variable {correlated_data} not found in dataset."
272
+
273
+ if weight is None:
274
+ weight = default_data_group["weight"]
275
+ new_data_group["weight"] = weight
276
+ assert (
277
+ weight in self._xdt.ds.data_vars
278
+ ), f"Data variable {weight} not found in dataset."
279
+
280
+ if flag is None:
281
+ flag = default_data_group["flag"]
282
+ new_data_group["flag"] = flag
283
+ assert (
284
+ flag in self._xdt.ds.data_vars
285
+ ), f"Data variable {flag} not found in dataset."
286
+
287
+ if self._xdt.attrs["type"] == "visibility":
288
+ if uvw is None:
289
+ uvw = default_data_group["uvw"]
290
+ new_data_group["uvw"] = uvw
291
+ assert (
292
+ uvw in self._xdt.ds.data_vars
293
+ ), f"Data variable {uvw} not found in dataset."
294
+
295
+ if field_and_source_xds is None:
296
+ field_and_source_xds = default_data_group["field_and_source_xds"]
297
+ new_data_group["field_and_source"] = field_and_source_xds
298
+ assert (
299
+ field_and_source_xds in self._xdt.children
300
+ ), f"Data variable {field_and_source_xds} not found in dataset."
301
+
302
+ if date_time is None:
303
+ date_time = datetime.now().isoformat()
304
+ new_data_group["date"] = date_time
305
+
306
+ if description is None:
307
+ description = ""
308
+ new_data_group["description"] = description
309
+
310
+ self._xdt.attrs["data_groups"][new_data_group_name] = new_data_group
311
+
312
+ return self._xdt
313
+
314
+ def _get_default_data_group_name(self):
315
+ if "base" in self._xdt.attrs["data_groups"].keys():
316
+ data_group_name = "base"
317
+ else:
318
+ data_group_name = list(self._xdt.attrs["data_groups"].keys())[0]
319
+ return data_group_name
@@ -1,15 +1,14 @@
1
1
  import os
2
-
3
- from xradio.measurement_set import ProcessingSet
4
2
  import toolviper.utils.logger as logger
5
3
  from xradio._utils.zarr.common import _open_dataset, _get_file_system_and_items
6
4
  import s3fs
5
+ import xarray as xr
7
6
 
8
7
 
9
8
  def open_processing_set(
10
9
  ps_store: str,
11
10
  intents: list = None,
12
- ) -> ProcessingSet:
11
+ ) -> xr.DataTree:
13
12
  """Creates a lazy representation of a Processing Set (only meta-data is loaded into memory).
14
13
 
15
14
  Parameters
@@ -17,96 +16,133 @@ def open_processing_set(
17
16
  ps_store : str
18
17
  String of the path and name of the processing set. For example '/users/user_1/uid___A002_Xf07bba_Xbe5c_target.lsrk.vis.zarr'.
19
18
  intents : list, optional
20
- A list of intents to be open for example ['OBSERVE_TARGET#ON_SOURCE']. The intents in a processing set can be seen by calling processing_set.summary().
21
- By default None, which will open all intents.
19
+ A list of intents to be opened for example ['OBSERVE_TARGET#ON_SOURCE']. The intents in a processing_set_xdt can be seen by calling processing_set_xdt.ps.summary().
20
+ By default None, which will include all intents.
22
21
 
23
22
  Returns
24
23
  -------
25
- processing_set
26
- Lazy representation of processing set (data is represented by Dask.arrays).
24
+ xarray.DataTree
25
+ Lazy representation of processing set (the data arrays of the datasets are
26
+ represented by Dask.arrays).
27
27
  """
28
- from xradio.measurement_set import MeasurementSetXds
29
28
 
30
29
  file_system, ms_store_list = _get_file_system_and_items(ps_store)
31
30
 
32
- ps = ProcessingSet()
33
- data_group = "base"
34
- for ms_name in ms_store_list:
35
- # try:
36
- ms_store = os.path.join(ps_store, ms_name)
37
- correlated_store = os.path.join(ms_store, "correlated_xds")
38
-
39
- xds = _open_dataset(correlated_store, file_system)
40
- data_groups = xds.attrs["data_groups"]
41
-
42
- if (intents is None) or (
43
- bool(set(xds.attrs["partition_info"]["intents"]).intersection(intents))
44
- ):
45
- sub_xds_dict, field_and_source_xds_dict = _open_sub_xds(
46
- ms_store, file_system=file_system, data_groups=data_groups
47
- )
48
-
49
- xds.attrs = {
50
- **xds.attrs,
51
- **sub_xds_dict,
52
- }
53
-
54
- for data_group_name, data_group_vals in data_groups.items():
55
- xds[data_group_vals["correlated_data"]].attrs[
56
- "field_and_source_xds"
57
- ] = field_and_source_xds_dict[data_group_name]
58
-
59
- ps[ms_name] = MeasurementSetXds(xds)
60
- # except Exception as e:
61
- # logger.warning(f"Could not open {ms_name} due to {e}")
62
- # continue
63
-
64
- return ps
65
-
66
-
67
- def _open_sub_xds(ms_store, file_system, data_groups, load=False):
68
- sub_xds_dict = {}
69
- field_and_source_xds_dict = {}
70
-
71
31
  if isinstance(file_system, s3fs.core.S3FileSystem):
72
- file_names = [
73
- bd.split(sep="/")[-1] for bd in file_system.listdir(ms_store, detail=False)
74
- ]
32
+ mapping = s3fs.S3Map(root=ps_store, s3=file_system, check=False)
33
+ ps_xdt = xr.open_datatree(mapping, engine="zarr")
75
34
  else:
76
- file_names = file_system.listdir(ms_store)
77
- file_names = [item for item in file_names if not item.startswith(".")]
78
-
79
- file_names.remove("correlated_xds")
80
-
81
- field_dict = {"field_and_source_xds_" + key: key for key in data_groups.keys()}
82
-
83
- # field_and_source_xds_name_start = "FIELD"
84
- for n in file_names:
85
- xds = _open_dataset(
86
- os.path.join(ms_store, n), load=load, file_system=file_system
87
- )
88
- # Skip empty tables
89
- if not xds.coords and not xds.data_vars:
90
- continue
91
- if n in field_dict.keys():
92
- field_and_source_xds_dict[field_dict[n]] = xds
93
- else:
94
- sub_xds_dict[n] = xds
95
-
96
- return sub_xds_dict, field_and_source_xds_dict
97
-
98
-
99
- def _get_data_name(xds, data_group):
100
- if "visibility" in xds.attrs["data_groups"][data_group]:
101
- data_name = xds.attrs["data_groups"][data_group]["visibility"]
102
- elif "spectrum" in xds.attrs["data_groups"][data_group]:
103
- data_name = xds.attrs["data_groups"][data_group]["spectrum"]
35
+ ps_xdt = xr.open_datatree(ps_store, engine="zarr")
36
+
37
+ # Future work is to add ASDM backend
38
+
39
+ if intents is None:
40
+ return ps_xdt
104
41
  else:
105
- error_message = (
106
- "No Visibility or Spectrum data variable found in data_group "
107
- + data_group
108
- + "."
109
- )
110
- logger.exception(error_message)
111
- raise ValueError(error_message)
112
- return data_name
42
+ return ps_xdt.xr_ps.query(intents=intents)
43
+
44
+
45
+ # def open_processing_set(
46
+ # ps_store: str,
47
+ # intents: list = None,
48
+ # ): #-> ProcessingSet:
49
+ # """Creates a lazy representation of a Processing Set (only meta-data is loaded into memory).
50
+
51
+ # Parameters
52
+ # ----------
53
+ # ps_store : str
54
+ # String of the path and name of the processing set. For example '/users/user_1/uid___A002_Xf07bba_Xbe5c_target.lsrk.vis.zarr'.
55
+ # intents : list, optional
56
+ # A list of intents to be open for example ['OBSERVE_TARGET#ON_SOURCE']. The intents in a processing set can be seen by calling processing_set.summary().
57
+ # By default None, which will open all intents.
58
+
59
+ # Returns
60
+ # -------
61
+ # processing_set
62
+ # Lazy representation of processing set (data is represented by Dask.arrays).
63
+ # """
64
+ # from xradio.measurement_set import MeasurementSetXds
65
+
66
+ # file_system, ms_store_list = _get_file_system_and_items(ps_store)
67
+ # from xradio.measurement_set import ProcessingSet
68
+ # ps = ProcessingSet()
69
+ # data_group = "base"
70
+ # for ms_name in ms_store_list:
71
+ # # try:
72
+ # ms_store = os.path.join(ps_store, ms_name)
73
+ # correlated_store = os.path.join(ms_store, "correlated_xds")
74
+
75
+ # xds = _open_dataset(correlated_store, file_system)
76
+ # data_groups = xds.attrs["data_groups"]
77
+
78
+ # if (intents is None) or (
79
+ # bool(set(xds.attrs["partition_info"]["intents"]).intersection(intents))
80
+ # ):
81
+ # sub_xds_dict, field_and_source_xds_dict = _open_sub_xds(
82
+ # ms_store, file_system=file_system, data_groups=data_groups
83
+ # )
84
+
85
+ # xds.attrs = {
86
+ # **xds.attrs,
87
+ # **sub_xds_dict,
88
+ # }
89
+
90
+ # for data_group_name, data_group_vals in data_groups.items():
91
+ # xds[data_group_vals["correlated_data"]].attrs[
92
+ # "field_and_source_xds"
93
+ # ] = field_and_source_xds_dict[data_group_name]
94
+
95
+ # ps[ms_name] = MeasurementSetXds(xds)
96
+ # # except Exception as e:
97
+ # # logger.warning(f"Could not open {ms_name} due to {e}")
98
+ # # continue
99
+
100
+ # return ps
101
+
102
+
103
+ # def _open_sub_xds(ms_store, file_system, data_groups, load=False):
104
+ # sub_xds_dict = {}
105
+ # field_and_source_xds_dict = {}
106
+
107
+ # if isinstance(file_system, s3fs.core.S3FileSystem):
108
+ # file_names = [
109
+ # bd.split(sep="/")[-1] for bd in file_system.listdir(ms_store, detail=False)
110
+ # ]
111
+ # else:
112
+ # file_names = file_system.listdir(ms_store)
113
+ # file_names = [item for item in file_names if not item.startswith(".")]
114
+
115
+ # file_names.remove("correlated_xds")
116
+
117
+ # field_dict = {"field_and_source_xds_" + key: key for key in data_groups.keys()}
118
+
119
+ # # field_and_source_xds_name_start = "FIELD"
120
+ # for n in file_names:
121
+ # xds = _open_dataset(
122
+ # os.path.join(ms_store, n), load=load, file_system=file_system
123
+ # )
124
+ # # Skip empty tables
125
+ # if not xds.coords and not xds.data_vars:
126
+ # continue
127
+ # if n in field_dict.keys():
128
+ # field_and_source_xds_dict[field_dict[n]] = xds
129
+ # else:
130
+ # sub_xds_dict[n] = xds
131
+
132
+ # return sub_xds_dict, field_and_source_xds_dict
133
+
134
+
135
+ # def _get_data_name(xds, data_group):
136
+ # if "visibility" in xds.attrs["data_groups"][data_group]:
137
+ # data_name = xds.attrs["data_groups"][data_group]["visibility"]
138
+ # elif "spectrum" in xds.attrs["data_groups"][data_group]:
139
+ # data_name = xds.attrs["data_groups"][data_group]["spectrum"]
140
+ # else:
141
+ # error_message = (
142
+ # "No Visibility or Spectrum data variable found in data_group "
143
+ # + data_group
144
+ # + "."
145
+ # )
146
+ # logger.exception(error_message)
147
+ # raise ValueError(error_message)
148
+ # return data_name