xradio 0.0.40__py3-none-any.whl → 0.0.42__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 (65) hide show
  1. xradio/_utils/coord_math.py +100 -0
  2. xradio/_utils/list_and_array.py +49 -4
  3. xradio/_utils/schema.py +36 -16
  4. xradio/image/_util/_casacore/xds_from_casacore.py +5 -5
  5. xradio/image/_util/_casacore/xds_to_casacore.py +12 -11
  6. xradio/image/_util/_fits/xds_from_fits.py +18 -17
  7. xradio/image/_util/_zarr/zarr_low_level.py +29 -12
  8. xradio/image/_util/common.py +1 -1
  9. xradio/image/_util/image_factory.py +1 -1
  10. xradio/measurement_set/__init__.py +18 -0
  11. xradio/measurement_set/_utils/__init__.py +5 -0
  12. xradio/{vis/_vis_utils/_ms → measurement_set/_utils/_msv2}/_tables/load_main_table.py +1 -1
  13. xradio/{vis/_vis_utils/_ms → measurement_set/_utils/_msv2}/_tables/read.py +15 -1
  14. xradio/{vis/_vis_utils/_ms → measurement_set/_utils/_msv2}/conversion.py +186 -84
  15. xradio/measurement_set/_utils/_msv2/create_antenna_xds.py +535 -0
  16. xradio/{vis/_vis_utils/_ms → measurement_set/_utils/_msv2}/create_field_and_source_xds.py +146 -58
  17. xradio/measurement_set/_utils/_msv2/msv4_info_dicts.py +203 -0
  18. xradio/measurement_set/_utils/_msv2/msv4_sub_xdss.py +550 -0
  19. xradio/{vis/_vis_utils/_ms → measurement_set/_utils/_msv2}/subtables.py +1 -1
  20. xradio/{vis/_vis_utils → measurement_set/_utils}/_utils/xds_helper.py +1 -1
  21. xradio/{vis/_vis_utils/ms.py → measurement_set/_utils/msv2.py} +4 -4
  22. xradio/{vis/_vis_utils → measurement_set/_utils}/zarr.py +3 -3
  23. xradio/{vis → measurement_set}/convert_msv2_to_processing_set.py +9 -2
  24. xradio/{vis → measurement_set}/load_processing_set.py +16 -20
  25. xradio/measurement_set/measurement_set_xds.py +83 -0
  26. xradio/{vis/read_processing_set.py → measurement_set/open_processing_set.py} +25 -34
  27. xradio/measurement_set/processing_set.py +777 -0
  28. xradio/measurement_set/schema.py +1979 -0
  29. xradio/schema/check.py +42 -22
  30. xradio/schema/dataclass.py +56 -6
  31. xradio/sphinx/__init__.py +12 -0
  32. xradio/sphinx/schema_table.py +351 -0
  33. {xradio-0.0.40.dist-info → xradio-0.0.42.dist-info}/METADATA +17 -15
  34. xradio-0.0.42.dist-info/RECORD +76 -0
  35. {xradio-0.0.40.dist-info → xradio-0.0.42.dist-info}/WHEEL +1 -1
  36. xradio/_utils/common.py +0 -101
  37. xradio/vis/__init__.py +0 -14
  38. xradio/vis/_processing_set.py +0 -302
  39. xradio/vis/_vis_utils/__init__.py +0 -5
  40. xradio/vis/_vis_utils/_ms/create_antenna_xds.py +0 -482
  41. xradio/vis/_vis_utils/_ms/msv4_infos.py +0 -0
  42. xradio/vis/_vis_utils/_ms/msv4_sub_xdss.py +0 -306
  43. xradio/vis/schema.py +0 -1102
  44. xradio-0.0.40.dist-info/RECORD +0 -73
  45. /xradio/{vis/_vis_utils/_ms → measurement_set/_utils/_msv2}/_tables/load.py +0 -0
  46. /xradio/{vis/_vis_utils/_ms → measurement_set/_utils/_msv2}/_tables/read_main_table.py +0 -0
  47. /xradio/{vis/_vis_utils/_ms → measurement_set/_utils/_msv2}/_tables/read_subtables.py +0 -0
  48. /xradio/{vis/_vis_utils/_ms → measurement_set/_utils/_msv2}/_tables/table_query.py +0 -0
  49. /xradio/{vis/_vis_utils/_ms → measurement_set/_utils/_msv2}/_tables/write.py +0 -0
  50. /xradio/{vis/_vis_utils/_ms → measurement_set/_utils/_msv2}/_tables/write_exp_api.py +0 -0
  51. /xradio/{vis/_vis_utils/_ms → measurement_set/_utils/_msv2}/chunks.py +0 -0
  52. /xradio/{vis/_vis_utils/_ms → measurement_set/_utils/_msv2}/descr.py +0 -0
  53. /xradio/{vis/_vis_utils/_ms → measurement_set/_utils/_msv2}/msv2_msv3.py +0 -0
  54. /xradio/{vis/_vis_utils/_ms → measurement_set/_utils/_msv2}/msv2_to_msv4_meta.py +0 -0
  55. /xradio/{vis/_vis_utils/_ms → measurement_set/_utils/_msv2}/optimised_functions.py +0 -0
  56. /xradio/{vis/_vis_utils/_ms → measurement_set/_utils/_msv2}/partition_queries.py +0 -0
  57. /xradio/{vis/_vis_utils/_ms → measurement_set/_utils/_msv2}/partitions.py +0 -0
  58. /xradio/{vis/_vis_utils → measurement_set/_utils}/_utils/cds.py +0 -0
  59. /xradio/{vis/_vis_utils → measurement_set/_utils}/_utils/partition_attrs.py +0 -0
  60. /xradio/{vis/_vis_utils → measurement_set/_utils}/_utils/stokes_types.py +0 -0
  61. /xradio/{vis/_vis_utils → measurement_set/_utils}/_zarr/encoding.py +0 -0
  62. /xradio/{vis/_vis_utils → measurement_set/_utils}/_zarr/read.py +0 -0
  63. /xradio/{vis/_vis_utils → measurement_set/_utils}/_zarr/write.py +0 -0
  64. {xradio-0.0.40.dist-info → xradio-0.0.42.dist-info}/LICENSE.txt +0 -0
  65. {xradio-0.0.40.dist-info → xradio-0.0.42.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,550 @@
1
+ import toolviper.utils.logger as logger
2
+ import os
3
+ import time
4
+ from typing import Tuple, Union
5
+
6
+ import numpy as np
7
+ import xarray as xr
8
+
9
+ from xradio._utils.coord_math import convert_to_si_units
10
+ from xradio._utils.schema import (
11
+ column_description_casacore_to_msv4_measure,
12
+ convert_generic_xds_to_xradio_schema,
13
+ )
14
+ from .subtables import subt_rename_ids
15
+ from ._tables.read import (
16
+ load_generic_table,
17
+ make_taql_where_between_min_max,
18
+ table_exists,
19
+ table_has_column,
20
+ )
21
+
22
+
23
+ def interpolate_to_time(
24
+ xds: xr.Dataset,
25
+ interp_time: Union[xr.DataArray, None],
26
+ message_prefix: str,
27
+ time_name: str = "time",
28
+ ) -> xr.Dataset:
29
+ """
30
+ Interpolate the time coordinate of the input xarray dataset to the
31
+ a data array. This can be used for example to interpolate a pointing_xds
32
+ to the time coord of the (main) MSv4, or similarly the ephemeris
33
+ data variables of a field_and_source_xds.
34
+
35
+ Uses interpolation method "linear", unless the source number of points is
36
+ 1 in which case "nearest" is used, to avoid divide-by-zero issues.
37
+
38
+ Parameters:
39
+ ----------
40
+ xds : xr.Dataset
41
+ Xarray dataset to interpolate (presumably a pointing_xds or an xds of
42
+ ephemeris variables)
43
+ interp_time : Union[xr.DataArray, None]
44
+ Time axis to interpolate the dataset to (usually main MSv4 time)
45
+ message_prefix: str
46
+ A prefix for info/debug/etc. messages
47
+
48
+ Returns:
49
+ -------
50
+ interpolated_xds : xr.Dataset
51
+ xarray dataset with time axis interpolated to interp_time.
52
+ """
53
+ if interp_time is not None:
54
+ points_before = xds[time_name].size
55
+ if points_before > 1:
56
+ method = "linear"
57
+ else:
58
+ method = "nearest"
59
+ xds = xds.interp({time_name: interp_time}, method=method, assume_sorted=True)
60
+ # scan_number sneaks in as a coordinate of the main time axis, drop it
61
+ if "scan_number" in xds.coords:
62
+ xds = xds.drop_vars("scan_number")
63
+ points_after = xds[time_name].size
64
+ logger.debug(
65
+ f"{message_prefix}: interpolating the time coordinate "
66
+ f"from {points_before} to {points_after} points"
67
+ )
68
+
69
+ return xds
70
+
71
+
72
+ def make_taql_where_weather(
73
+ in_file: str, ant_xds_station_name_ids: xr.DataArray
74
+ ) -> str:
75
+ """
76
+ The use of taql_where with WEATHER is complicated because of ALMA and its
77
+ (ab)use of NS_WX_STATION_ID vs. ANTENNA_ID (all -1) (see read.py). We
78
+ cannot simply use a 'WHERE ANTENNA_ID=...'. This function produces a TaQL
79
+ where string that uses ANTENNA_ID/NS_WX_STATION_ID depending on waht is
80
+ found in the WEATHER subtable.
81
+
82
+ Parameters
83
+ ----------
84
+ in_file : str
85
+ Input MS name.
86
+ ant_xds_station_name_ids : xr.DataArray
87
+ station name data array from antenna_xds, with name/id information
88
+
89
+ Returns
90
+ -------
91
+ taql_where: str
92
+ WHERE substring safe to use in taql_where when loading WEATHER subtable
93
+ """
94
+ weather_path = os.path.join(in_file, "WEATHER")
95
+ if table_exists(weather_path) and table_has_column(
96
+ weather_path, "NS_WX_STATION_ID"
97
+ ):
98
+ taql_where = f"WHERE (NS_WX_STATION_ID IN [{','.join(map(str, ant_xds_station_name_ids.antenna_id.values))}])"
99
+ else:
100
+ taql_where = f"WHERE (ANTENNA_ID IN [{','.join(map(str, ant_xds_station_name_ids.antenna_id.values))}])"
101
+
102
+ return taql_where
103
+
104
+
105
+ def create_weather_xds(in_file: str, ant_xds_station_name_ids: xr.DataArray):
106
+ """
107
+ Creates a Weather Xarray Dataset from a MS v2 WEATHER table.
108
+
109
+ Parameters
110
+ ----------
111
+ in_file : str
112
+ Input MS name.
113
+ ant_xds_station_name_ids : xr.DataArray
114
+ station name data array from antenna_xds, with name/id information
115
+
116
+ Returns
117
+ -------
118
+ xr.Dataset
119
+ Weather Xarray Dataset.
120
+ """
121
+
122
+ try:
123
+ taql_where = make_taql_where_weather(in_file, ant_xds_station_name_ids)
124
+ generic_weather_xds = load_generic_table(
125
+ in_file,
126
+ "WEATHER",
127
+ rename_ids=subt_rename_ids["WEATHER"],
128
+ taql_where=taql_where,
129
+ )
130
+ except ValueError as _exc:
131
+ return None
132
+
133
+ if not generic_weather_xds.data_vars:
134
+ # for example when the weather subtable only has info for antennas/stations
135
+ # not present in the MSv4 (no overlap between antennas loaded in ant_xds and weather)
136
+ return None
137
+
138
+ weather_xds = xr.Dataset(attrs={"type": "weather"})
139
+ stations_present = ant_xds_station_name_ids.sel(
140
+ antenna_id=generic_weather_xds["ANTENNA_ID"]
141
+ )
142
+ coords = {
143
+ "station_name": stations_present.data,
144
+ "antenna_name": stations_present.coords["antenna_name"].data,
145
+ }
146
+ weather_xds = weather_xds.assign_coords(coords)
147
+
148
+ dims_station_time = ["station_name", "time_weather"]
149
+ to_new_data_variables = {
150
+ "H20": ["H2O", dims_station_time],
151
+ "IONOS_ELECTRON": ["IONOS_ELECTRON", dims_station_time],
152
+ "PRESSURE": ["PRESSURE", dims_station_time],
153
+ "REL_HUMIDITY": ["REL_HUMIDITY", dims_station_time],
154
+ "TEMPERATURE": ["TEMPERATURE", dims_station_time],
155
+ "DEW_POINT": ["DEW_POINT", dims_station_time],
156
+ "WIND_DIRECTION": ["WIND_DIRECTION", dims_station_time],
157
+ "WIND_SPEED": ["WIND_SPEED", dims_station_time],
158
+ }
159
+
160
+ to_new_coords = {
161
+ "TIME": ["time_weather", ["time_weather"]],
162
+ }
163
+
164
+ weather_xds = convert_generic_xds_to_xradio_schema(
165
+ generic_weather_xds, weather_xds, to_new_data_variables, to_new_coords
166
+ )
167
+
168
+ # TODO: option to interpolate to main time
169
+
170
+ # PRESSURE: hPa in MSv2 specs and some MSs => Pa
171
+ weather_xds = convert_to_si_units(weather_xds)
172
+
173
+ # correct expected types (for example "IONOS_ELECTRON", "PRESSURE" can be float32)
174
+ for data_var in weather_xds:
175
+ if weather_xds.data_vars[data_var].dtype != np.float64:
176
+ weather_xds[data_var] = weather_xds[data_var].astype(np.float64)
177
+
178
+ return weather_xds
179
+
180
+
181
+ def correct_generic_pointing_xds(
182
+ generic_pointing_xds: xr.Dataset, to_new_data_variables: dict[str, list]
183
+ ) -> xr.Dataset:
184
+ """
185
+ Takes a (generic) pointing_xds as read from a POINTING subtable of an MSv2
186
+ and tries to correct several deviations from the MSv2 specs seen in
187
+ common test data.
188
+ The problems fixed here include wrong dimensions:
189
+ - for example transposed dimensions with respect to the MSv2 specs (output
190
+ from CASA simulator),
191
+ - missing/additional unexpected dimensions when some of the columns are
192
+ empty (in the sense of "empty casacore cells").
193
+
194
+ This function modifies the data arrays of the data vars affected by such
195
+ issues.
196
+
197
+ Parameters
198
+ ----------
199
+ generic_pointing_xds: xr.Dataset
200
+ The generic pointing dataset (loaded from MSv2) to be fixed
201
+ to_new_data_variables: dict
202
+ The dict used for convert_generic_xds_to_xradio_schema, which gives all
203
+ the data variables relevant for the final MSv4 dataset.
204
+
205
+ Returns:
206
+ --------
207
+ xr.Dataset
208
+ Corrected dataset with dimensions conforming to MSv2 specs.
209
+ """
210
+
211
+ correct_pointing_xds = generic_pointing_xds.copy()
212
+
213
+ for data_var_name in generic_pointing_xds:
214
+ if data_var_name in to_new_data_variables:
215
+ # Corrects dim sizes of "empty cell" variables, such as empty DIRECTION, TARGET, etc.
216
+ if (
217
+ "dim_2" in generic_pointing_xds.sizes
218
+ and generic_pointing_xds.sizes["dim_2"] == 0
219
+ ):
220
+ # When all direction variables are "empty"
221
+ data_var_data = xr.DataArray(
222
+ [[[[np.nan, np.nan]]]],
223
+ dims=generic_pointing_xds.dims,
224
+ ).isel(n_polynomial=0, drop=True)
225
+ correct_pointing_xds[data_var_name].data = data_var_data
226
+
227
+ elif (
228
+ "dir" in generic_pointing_xds.sizes
229
+ and generic_pointing_xds.sizes["dir"] == 0
230
+ ):
231
+ # When some direction variables are "empty" but some are populated properly
232
+ if "dim_2" in generic_pointing_xds[key].sizes:
233
+ data_var_data = xr.DataArray(
234
+ generic_pointing_xds[key].values,
235
+ dims=generic_pointing_xds[key].dims,
236
+ )
237
+ else:
238
+ shape = tuple(
239
+ generic_pointing_xds.sizes[dim]
240
+ for dim in ["TIME", "ANTENNA_ID"]
241
+ ) + (2,)
242
+ data_var_data = xr.DataArray(
243
+ np.full(shape, np.nan),
244
+ dims=generic_pointing_xds[key].dims,
245
+ )
246
+ correct_pointing_xds[data_var_name].data = data_var_data
247
+
248
+ return correct_pointing_xds
249
+
250
+
251
+ def create_pointing_xds(
252
+ in_file: str,
253
+ ant_xds_name_ids: xr.DataArray,
254
+ time_min_max: Union[Tuple[np.float64, np.float64], None],
255
+ interp_time: Union[xr.DataArray, None] = None,
256
+ ) -> xr.Dataset:
257
+ """
258
+ Creates a Pointing Xarray Dataset from an MS v2 POINTING (sub)table.
259
+
260
+ WIP: details of a few direction variables (and possibly moving some to attributes) to be
261
+ settled (see MSv4 spreadsheet).
262
+
263
+ Parameters
264
+ ----------
265
+ in_file : str
266
+ Input MS name.
267
+ ant_xds_name_ids : xr.DataArray
268
+ antenna_name data array from antenna_xds, with name/id information
269
+ time_min_max : tuple
270
+ min / max times values to constrain loading (from the TIME column)
271
+ interp_time : Union[xr.DataArray, None] (Default value = None)
272
+ interpolate time to this (presumably main dataset time)
273
+
274
+ Returns
275
+ -------
276
+ xr.Dataset
277
+ Pointing Xarray dataset
278
+ """
279
+ start = time.time()
280
+
281
+ taql_time_range = make_taql_where_between_min_max(
282
+ time_min_max, in_file, "POINTING", "TIME"
283
+ )
284
+
285
+ if taql_time_range is None:
286
+ taql_where = f"WHERE (ANTENNA_ID IN [{','.join(map(str, ant_xds_name_ids.antenna_id.values))}])"
287
+ else:
288
+ taql_where = (
289
+ taql_time_range
290
+ + f" AND (ANTENNA_ID IN [{','.join(map(str, ant_xds_name_ids.antenna_id.values))}])"
291
+ )
292
+ # Read POINTING table into a Xarray Dataset.
293
+ generic_pointing_xds = load_generic_table(
294
+ in_file,
295
+ "POINTING",
296
+ rename_ids=subt_rename_ids["POINTING"],
297
+ taql_where=taql_where,
298
+ )
299
+
300
+ if not generic_pointing_xds.data_vars:
301
+ # apparently empty MS/POINTING table => produce empty xds
302
+ return xr.Dataset()
303
+
304
+ # Checking a simple way of using only the one single coefficient of the polynomials
305
+ if "n_polynomial" in generic_pointing_xds.sizes:
306
+ size = generic_pointing_xds.sizes["n_polynomial"]
307
+ if size == 1:
308
+ generic_pointing_xds = generic_pointing_xds.sel({"n_polynomial": 0})
309
+ elif size == 0:
310
+ generic_pointing_xds = generic_pointing_xds.drop_dims("n_polynomial")
311
+
312
+ time_ant_dims = ["time", "antenna_name"]
313
+ time_ant_dir_dims = time_ant_dims + ["local_sky_dir_label"]
314
+ to_new_data_variables = {
315
+ "DIRECTION": ["POINTING_BEAM", time_ant_dir_dims],
316
+ "ENCODER": ["POINTING_DISH_MEASURED", time_ant_dir_dims],
317
+ "OVER_THE_TOP": ["POINTING_OVER_THE_TOP", time_ant_dims],
318
+ }
319
+
320
+ to_new_coords = {
321
+ "TIME": ["time", ["time"]],
322
+ "dim_2": ["local_sky_dir_label", ["local_sky_dir_label"]],
323
+ }
324
+
325
+ generic_pointing_xds = correct_generic_pointing_xds(
326
+ generic_pointing_xds, to_new_data_variables
327
+ )
328
+ pointing_xds = xr.Dataset(attrs={"type": "pointing"})
329
+ coords = {
330
+ "antenna_name": ant_xds_name_ids.sel(
331
+ antenna_id=generic_pointing_xds["ANTENNA_ID"]
332
+ ).data,
333
+ "local_sky_dir_label": ["az", "alt"],
334
+ }
335
+ pointing_xds = pointing_xds.assign_coords(coords)
336
+ pointing_xds = convert_generic_xds_to_xradio_schema(
337
+ generic_pointing_xds, pointing_xds, to_new_data_variables, to_new_coords
338
+ )
339
+
340
+ pointing_xds = interpolate_to_time(pointing_xds, interp_time, "pointing_xds")
341
+
342
+ logger.debug(f"create_pointing_xds() execution time {time.time() - start:0.2f} s")
343
+
344
+ return pointing_xds
345
+
346
+
347
+ def prepare_generic_sys_cal_xds(generic_sys_cal_xds: xr.Dataset) -> xr.Dataset:
348
+ """
349
+ A generic_sys_cal_xds loaded with load_generic_table() cannot be easily
350
+ used in convert_generic_xds_to_xradio_schema() to produce an MSv4
351
+ sys_cal_xds dataset, as their structure differs in dimensions and order
352
+ of dimensions.
353
+ This function performs various prepareation steps, such as:
354
+ - filter out dimensions not neeed for an individual MSv4 (SPW, FEED),
355
+ - drop variables loaded from columns with all items set to empty array,
356
+ - transpose the dimensions frequency,receptor
357
+ - fix dimension names (and order) when needed.
358
+
359
+ Parameters
360
+ ----------
361
+ generic_sys_cal_xds : xr.Dataset
362
+ generic dataset read from an MSv2 SYSCAL subtable
363
+
364
+ Returns
365
+ -------
366
+ generic_sys_cal_xds: xr.Dataset
367
+ System calibration Xarray Dataset prepared for generic conversion
368
+ to MSv4.
369
+ """
370
+
371
+ # drop SPW and feed dims
372
+ generic_sys_cal_xds = generic_sys_cal_xds.isel(SPECTRAL_WINDOW_ID=0, drop=True)
373
+ generic_sys_cal_xds = generic_sys_cal_xds.isel(FEED_ID=0, drop=True)
374
+
375
+ # Often some of the T*_SPECTRUM are present but all the cells are populated
376
+ # with empty arrays
377
+ empty_arrays_vars = []
378
+ for data_var in generic_sys_cal_xds.data_vars:
379
+ if generic_sys_cal_xds[data_var].size == 0:
380
+ empty_arrays_vars.append(data_var)
381
+ if empty_arrays_vars:
382
+ generic_sys_cal_xds = generic_sys_cal_xds.drop_vars(empty_arrays_vars)
383
+
384
+ # Re-arrange receptor and frequency dims depending on input structure
385
+ if (
386
+ "receptor" in generic_sys_cal_xds.sizes
387
+ and "frequency" in generic_sys_cal_xds.sizes
388
+ ):
389
+ # dim_3 can be created for example when the T*_SPECTRUM have varying # channels!
390
+ # more generaly, could transpose with ... to avoid errors with additional spurious dimensions
391
+ if "dim_3" in generic_sys_cal_xds.dims:
392
+ generic_sys_cal_xds = generic_sys_cal_xds.drop_dims("dim_3")
393
+ # From MSv2 tables we get (...,frequency, receptor)
394
+ # -> transpose to (...,receptor,frequency) ready for MSv4 sys_cal_xds
395
+ generic_sys_cal_xds = generic_sys_cal_xds.transpose(
396
+ "ANTENNA_ID", "TIME", "receptor", "frequency"
397
+ )
398
+ elif (
399
+ "frequency" in generic_sys_cal_xds.sizes
400
+ and not "dim_3" in generic_sys_cal_xds.sizes
401
+ ):
402
+ # because order is (...,frequency,receptor), when frequency is missing
403
+ # receptor can get wrongly labeled as frequency
404
+ generic_sys_cal_xds = generic_sys_cal_xds.rename_dims({"frequency": "receptor"})
405
+ elif (
406
+ "frequency" not in generic_sys_cal_xds.sizes
407
+ and "receptor" in generic_sys_cal_xds.sizes
408
+ and "dim_3" in generic_sys_cal_xds.sizes
409
+ ):
410
+ # different *_SPECTRUM array sizes + some empty arrays can create an additional spurious
411
+ # generic dimension, which should have been "receptor"
412
+ generic_sys_cal_xds = generic_sys_cal_xds.rename_dims({"receptor": "frequency"})
413
+ generic_sys_cal_xds = generic_sys_cal_xds.rename_dims({"dim_3": "receptor"})
414
+ generic_sys_cal_xds = generic_sys_cal_xds.transpose(
415
+ "ANTENNA_ID", "TIME", "receptor", "frequency"
416
+ )
417
+ else:
418
+ raise RuntimeError(
419
+ "Cannot understand the arrangement of dimensions of {generic_sys_cal_xds=}"
420
+ )
421
+
422
+ return generic_sys_cal_xds
423
+
424
+
425
+ def create_system_calibration_xds(
426
+ in_file: str,
427
+ main_xds_frequency: xr.DataArray,
428
+ ant_xds_name_ids: xr.DataArray,
429
+ sys_cal_interp_time: Union[xr.DataArray, None] = None,
430
+ ):
431
+ """
432
+ Creates a system calibration Xarray Dataset from a MSv2 SYSCAL table.
433
+
434
+ Parameters
435
+ ----------
436
+ in_file: str
437
+ Input MS name.
438
+ main_xds_frequency: xr.DataArray
439
+ frequency array of the main xds (MSv4), containing among other things
440
+ spectral_window_id and measures metadata
441
+ ant_xds_name_ids : xr.Dataset
442
+ antenna_name data array from antenna_xds, with name/id information
443
+ sys_cal_interp_time: Union[xr.DataArray, None] = None,
444
+ Time axis to interpolate the data vars to (usually main MSv4 time)
445
+
446
+ Returns
447
+ -------
448
+ sys_cal_xds: xr.Dataset
449
+ System calibration Xarray Dataset.
450
+ """
451
+
452
+ spectral_window_id = main_xds_frequency.attrs["spectral_window_id"]
453
+ try:
454
+ generic_sys_cal_xds = load_generic_table(
455
+ in_file,
456
+ "SYSCAL",
457
+ rename_ids=subt_rename_ids["SYSCAL"],
458
+ taql_where=(
459
+ f" where (SPECTRAL_WINDOW_ID = {spectral_window_id})"
460
+ f" AND (ANTENNA_ID IN [{','.join(map(str, ant_xds_name_ids.antenna_id.values))}])"
461
+ ),
462
+ )
463
+ except ValueError as _exc:
464
+ return None
465
+
466
+ if not generic_sys_cal_xds.data_vars:
467
+ # even though SYSCAL is an optional subtable, some write it empty
468
+ return None
469
+
470
+ generic_sys_cal_xds = prepare_generic_sys_cal_xds(generic_sys_cal_xds)
471
+
472
+ mandatory_dimensions = ["antenna_name", "time_cal", "receptor_label"]
473
+ if "frequency" not in generic_sys_cal_xds.sizes:
474
+ dims_all = mandatory_dimensions
475
+ else:
476
+ dims_all = mandatory_dimensions + ["frequency_cal"]
477
+
478
+ to_new_data_variables = {
479
+ "PHASE_DIFF": ["PHASE_DIFFERENCE", ["antenna_name", "time_cal"]],
480
+ "TCAL": ["TCAL", dims_all],
481
+ "TCAL_SPECTRUM": ["TCAL", dims_all],
482
+ "TRX": ["TRX", dims_all],
483
+ "TRX_SPECTRUM": ["TRX", dims_all],
484
+ "TSKY": ["TSKY", dims_all],
485
+ "TSKY_SPECTRUM": ["TSKY", dims_all],
486
+ "TSYS": ["TSYS", dims_all],
487
+ "TSYS_SPECTRUM": ["TSYS", dims_all],
488
+ "TANT": ["TANT", dims_all],
489
+ "TANT_SPECTRUM": ["TANT", dims_all],
490
+ "TAN_TSYS": ["TAN_TSYS", dims_all],
491
+ "TANT_SYS_SPECTRUM": ["TANT_TSYS", dims_all],
492
+ }
493
+
494
+ to_new_coords = {
495
+ "TIME": ["time_cal", ["time_cal"]],
496
+ "receptor": ["receptor_label", ["receptor_label"]],
497
+ "frequency": ["frequency_cal", ["frequency_cal"]],
498
+ }
499
+
500
+ sys_cal_xds = xr.Dataset(attrs={"type": "system_calibration"})
501
+ coords = {
502
+ "antenna_name": ant_xds_name_ids.sel(
503
+ antenna_id=generic_sys_cal_xds["ANTENNA_ID"]
504
+ ).data,
505
+ "receptor_label": generic_sys_cal_xds.coords["receptor"].data,
506
+ }
507
+ sys_cal_xds = sys_cal_xds.assign_coords(coords)
508
+ sys_cal_xds = convert_generic_xds_to_xradio_schema(
509
+ generic_sys_cal_xds, sys_cal_xds, to_new_data_variables, to_new_coords
510
+ )
511
+
512
+ # Add frequency coord and its measures data, if present
513
+ if "frequency_cal" in dims_all:
514
+ frequency_coord = {
515
+ "frequency_cal": generic_sys_cal_xds.coords["frequency"].data
516
+ }
517
+ sys_cal_xds = sys_cal_xds.assign_coords(frequency_coord)
518
+ frequency_measure = {
519
+ "type": main_xds_frequency.attrs["type"],
520
+ "units": main_xds_frequency.attrs["units"],
521
+ "observer": main_xds_frequency.attrs["observer"],
522
+ }
523
+ sys_cal_xds.coords["frequency_cal"].attrs.update(frequency_measure)
524
+
525
+ if sys_cal_interp_time is not None:
526
+ sys_cal_xds = interpolate_to_time(
527
+ sys_cal_xds,
528
+ sys_cal_interp_time,
529
+ "system_calibration_xds",
530
+ time_name="time_cal",
531
+ )
532
+
533
+ time_coord_attrs = {
534
+ "type": "time",
535
+ "units": ["s"],
536
+ "scale": "utc",
537
+ "format": "unix",
538
+ }
539
+ # If interpolating time, rename time_cal => time
540
+ time_coord = {"time": ("time_cal", sys_cal_interp_time.data)}
541
+ sys_cal_xds = sys_cal_xds.assign_coords(time_coord)
542
+ sys_cal_xds.coords["time"].attrs.update(time_coord_attrs)
543
+ sys_cal_xds = sys_cal_xds.swap_dims({"time_cal": "time"}).drop_vars("time_cal")
544
+
545
+ # correct expected types
546
+ for data_var in sys_cal_xds:
547
+ if sys_cal_xds.data_vars[data_var].dtype != np.float64:
548
+ sys_cal_xds[data_var] = sys_cal_xds[data_var].astype(np.float64)
549
+
550
+ return sys_cal_xds
@@ -23,7 +23,7 @@ subt_rename_ids = {
23
23
  "SPECTRAL_WINDOW": {"row": "spectral_window_id", "dim_1": "chan"},
24
24
  "SOURCE": {"dim_1": "ra/dec", "dim_2": "line"},
25
25
  "STATE": {"row": "state_id"},
26
- "SYSCAL": {"dim_1": "channel", "dim_2": "receiver"},
26
+ "SYSCAL": {"dim_1": "frequency", "dim_2": "receptor"},
27
27
  # Would make sense for non-std "WS_NX_STATION_POSITION"
28
28
  "WEATHER": {"dim_1": "xyz"},
29
29
  }
@@ -7,7 +7,7 @@ import xarray as xr
7
7
 
8
8
  from .cds import CASAVisSet
9
9
  from .stokes_types import stokes_types
10
- from ...._utils.common import get_pad_value
10
+ from xradio._utils.list_and_array import get_pad_value
11
11
 
12
12
 
13
13
  def make_coords(
@@ -2,14 +2,14 @@ import os
2
2
  import toolviper.utils.logger as logger
3
3
  from typing import List, Tuple, Union
4
4
 
5
- from ._utils.cds import CASAVisSet
6
- from ._ms.partitions import (
5
+ from xradio.measurement_set._utils._utils.cds import CASAVisSet
6
+ from xradio.measurement_set._utils._msv2.partitions import (
7
7
  finalize_partitions,
8
8
  read_ms_ddi_partitions,
9
9
  read_ms_scan_subscan_partitions,
10
10
  )
11
- from ._ms.subtables import read_ms_subtables
12
- from ._utils.xds_helper import vis_xds_packager_cds
11
+ from xradio.measurement_set._utils._msv2.subtables import read_ms_subtables
12
+ from xradio.measurement_set._utils._utils.xds_helper import vis_xds_packager_cds
13
13
 
14
14
 
15
15
  def read_ms(
@@ -10,7 +10,7 @@ from ._zarr.read import read_part_keys, read_partitions, read_subtables
10
10
  from ._zarr.write import write_metainfo, write_part_keys, write_partitions
11
11
 
12
12
 
13
- def is_zarr_vis(inpath: str) -> bool:
13
+ def is_zarr_cor(inpath: str) -> bool:
14
14
  """
15
15
  Check if a given path has a visibilities dataset in Zarr format
16
16
 
@@ -32,7 +32,7 @@ def is_zarr_vis(inpath: str) -> bool:
32
32
  return False
33
33
 
34
34
 
35
- def read_vis(
35
+ def read_cor(
36
36
  inpath: str,
37
37
  subtables: bool = True,
38
38
  asdm_subtables: bool = False,
@@ -83,7 +83,7 @@ def read_vis(
83
83
  return cds
84
84
 
85
85
 
86
- def write_vis(
86
+ def write_cor(
87
87
  cds: CASAVisSet,
88
88
  outpath: str,
89
89
  chunks_on_disk: Union[Dict, None] = None,
@@ -4,8 +4,8 @@ from typing import Dict, Union
4
4
 
5
5
  import dask
6
6
 
7
- from xradio.vis._vis_utils._ms.partition_queries import create_partitions
8
- from xradio.vis._vis_utils._ms.conversion import convert_and_write_partition
7
+ from xradio.measurement_set._utils._msv2.partition_queries import create_partitions
8
+ from xradio.measurement_set._utils._msv2.conversion import convert_and_write_partition
9
9
 
10
10
 
11
11
  def convert_msv2_to_processing_set(
@@ -18,6 +18,7 @@ def convert_msv2_to_processing_set(
18
18
  pointing_interpolate: bool = False,
19
19
  ephemeris_interpolate: bool = False,
20
20
  phase_cal_interpolate: bool = False,
21
+ sys_cal_interpolate: bool = False,
21
22
  use_table_iter: bool = False,
22
23
  compressor: numcodecs.abc.Codec = numcodecs.Zstd(level=2),
23
24
  storage_backend: str = "zarr",
@@ -47,6 +48,10 @@ def convert_msv2_to_processing_set(
47
48
  Whether to interpolate the time axis of the pointing sub-dataset to the time axis of the main dataset
48
49
  ephemeris_interpolate : bool, optional
49
50
  Whether to interpolate the time axis of the ephemeris data variables (of the field_and_source sub-dataset) to the time axis of the main dataset
51
+ phase_cal_interpolate : bool, optional
52
+ Whether to interpolate the time axis of the phase calibration data variables to the time axis of the main dataset
53
+ sys_cal_interpolate : bool, optional
54
+ Whether to interpolate the time axis of the system calibration data variables (sys_cal_xds) to the time axis of the main dataset
50
55
  use_table_iter : bool, optional
51
56
  Whether to use the table iterator to read the main table of the MS v2. This should be set to True when reading datasets with large number of rows and few partitions, by default False.
52
57
  compressor : numcodecs.abc.Codec, optional
@@ -96,6 +101,7 @@ def convert_msv2_to_processing_set(
96
101
  pointing_interpolate=pointing_interpolate,
97
102
  ephemeris_interpolate=ephemeris_interpolate,
98
103
  phase_cal_interpolate=phase_cal_interpolate,
104
+ sys_cal_interpolate=sys_cal_interpolate,
99
105
  compressor=compressor,
100
106
  overwrite=overwrite,
101
107
  )
@@ -114,6 +120,7 @@ def convert_msv2_to_processing_set(
114
120
  pointing_interpolate=pointing_interpolate,
115
121
  ephemeris_interpolate=ephemeris_interpolate,
116
122
  phase_cal_interpolate=phase_cal_interpolate,
123
+ sys_cal_interpolate=sys_cal_interpolate,
117
124
  compressor=compressor,
118
125
  overwrite=overwrite,
119
126
  )