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,535 @@
1
+ import toolviper.utils.logger as logger
2
+ import time
3
+ from typing import Tuple, Union
4
+
5
+ import numpy as np
6
+ import xarray as xr
7
+ import os
8
+
9
+ from xradio.measurement_set._utils._msv2.subtables import subt_rename_ids
10
+ from xradio.measurement_set._utils._msv2._tables.read import (
11
+ load_generic_table,
12
+ convert_casacore_time,
13
+ convert_casacore_time_to_mjd,
14
+ make_taql_where_between_min_max,
15
+ table_exists,
16
+ )
17
+ from xradio._utils.schema import convert_generic_xds_to_xradio_schema
18
+ from xradio.measurement_set._utils._msv2.msv4_sub_xdss import interpolate_to_time
19
+
20
+ from xradio._utils.list_and_array import (
21
+ check_if_consistent,
22
+ unique_1d,
23
+ to_list,
24
+ to_np_array,
25
+ )
26
+
27
+
28
+ def create_antenna_xds(
29
+ in_file: str,
30
+ spectral_window_id: int,
31
+ antenna_id: list,
32
+ feed_id: list,
33
+ telescope_name: str,
34
+ partition_polarization: xr.DataArray,
35
+ ) -> xr.Dataset:
36
+ """
37
+ Create an Xarray Dataset containing antenna information.
38
+
39
+ Parameters
40
+ ----------
41
+ in_file : str
42
+ Path to the input MSv2.
43
+ spectral_window_id : int
44
+ Spectral window ID.
45
+ antenna_id : list
46
+ List of antenna IDs.
47
+ feed_id : list
48
+ List of feed IDs.
49
+ telescope_name : str
50
+ Name of the telescope.
51
+ partition_polarization: xr.DataArray
52
+ Polarization labels of this partition, needed if that info is not present in FEED
53
+
54
+ Returns
55
+ ----------
56
+ xr.Dataset: Xarray Dataset containing the antenna information.
57
+ """
58
+ ant_xds = xr.Dataset(attrs={"type": "antenna"})
59
+
60
+ ant_xds = extract_antenna_info(ant_xds, in_file, antenna_id, telescope_name)
61
+
62
+ ant_xds = extract_feed_info(
63
+ ant_xds, in_file, antenna_id, feed_id, spectral_window_id
64
+ )
65
+ # Needed for special SPWs such as ALMA WVR or CHANNEL_AVERAGE data (have no feed info)
66
+ if "polarization_type" not in ant_xds:
67
+ pols_chars = list(partition_polarization.values[0])
68
+ pols_labels = [f"pol_{idx}" for idx in np.arange(0, len(pols_chars))]
69
+ ant_xds = ant_xds.assign_coords(receptor_label=pols_labels)
70
+ pol_type_values = [pols_chars] * len(ant_xds.antenna_name)
71
+ ant_xds = ant_xds.assign_coords(
72
+ polarization_type=(
73
+ ["antenna_name", "receptor_label"],
74
+ pol_type_values,
75
+ )
76
+ )
77
+
78
+ ant_xds.attrs["overall_telescope_name"] = telescope_name
79
+ return ant_xds
80
+
81
+
82
+ def extract_antenna_info(
83
+ ant_xds: xr.Dataset, in_file: str, antenna_id: list, telescope_name: str
84
+ ) -> xr.Dataset:
85
+ """Reformats MSv2 Antenna table content to MSv4 schema.
86
+
87
+ Parameters
88
+ ----------
89
+ ant_xds : xr.Dataset
90
+ The dataset that will be updated with antenna information.
91
+ in_file : str
92
+ Path to the input MSv2.
93
+ antenna_id : list
94
+ A list of antenna IDs to extract information for.
95
+ telescope_name : str
96
+ The name of the telescope.
97
+
98
+ Returns
99
+ -------
100
+ xr.Dataset
101
+ Dataset updated to contain the antenna information.
102
+ """
103
+ to_new_data_variables = {
104
+ "POSITION": ["ANTENNA_POSITION", ["antenna_name", "cartesian_pos_label"]],
105
+ "DISH_DIAMETER": ["ANTENNA_DISH_DIAMETER", ["antenna_name"]],
106
+ }
107
+
108
+ to_new_coords = {
109
+ "NAME": ["antenna_name", ["antenna_name"]],
110
+ "STATION": ["station", ["antenna_name"]],
111
+ "MOUNT": ["mount", ["antenna_name"]],
112
+ # "PHASED_ARRAY_ID": ["phased_array_id", ["antenna_name"]],
113
+ "antenna_id": ["antenna_id", ["antenna_name"]],
114
+ }
115
+
116
+ # Read ANTENNA table into a Xarray Dataset.
117
+ unique_antenna_id = unique_1d(
118
+ antenna_id
119
+ ) # Also ensures that it is sorted otherwise TaQL will give wrong results.
120
+
121
+ generic_ant_xds = load_generic_table(
122
+ in_file,
123
+ "ANTENNA",
124
+ rename_ids=subt_rename_ids["ANTENNA"],
125
+ taql_where=f" where (ROWID() IN [{','.join(map(str,unique_antenna_id))}])", # order is not guaranteed
126
+ )
127
+ generic_ant_xds = generic_ant_xds.assign_coords({"antenna_id": unique_antenna_id})
128
+ generic_ant_xds = generic_ant_xds.sel(
129
+ antenna_id=antenna_id, drop=False
130
+ ) # Make sure the antenna_id order is correct.
131
+
132
+ # ['OFFSET', 'POSITION', 'DISH_DIAMETER', 'FLAG_ROW', 'MOUNT', 'NAME', 'STATION']
133
+ ant_xds = ant_xds.assign_coords({"cartesian_pos_label": ["x", "y", "z"]})
134
+
135
+ ant_xds = convert_generic_xds_to_xradio_schema(
136
+ generic_ant_xds, ant_xds, to_new_data_variables, to_new_coords
137
+ )
138
+
139
+ ant_xds["ANTENNA_DISH_DIAMETER"].attrs.update({"units": ["m"], "type": "quantity"})
140
+
141
+ ant_xds["ANTENNA_POSITION"].attrs["coordinate_system"] = "geocentric"
142
+ ant_xds["ANTENNA_POSITION"].attrs["origin_object_name"] = "earth"
143
+
144
+ if telescope_name in ["ALMA", "VLA", "NOEMA", "EVLA"]:
145
+ # antenna_name = ant_xds["antenna_name"].values + "_" + ant_xds["station"].values
146
+ # works on laptop but fails in github test runner with error:
147
+ # numpy.core._exceptions._UFuncNoLoopError: ufunc 'add' did not contain a loop with signature matching types (dtype('<U4'), dtype('<U4')) -> None
148
+
149
+ # Also doesn't work on github test runner:
150
+ # antenna_name = ant_xds["antenna_name"].values
151
+ # antenna_name = np._core.defchararray.add(antenna_name, "_")
152
+ # antenna_name = np._core.defchararray.add(
153
+ # antenna_name,
154
+ # ant_xds["station"].values,
155
+ # )
156
+
157
+ # None of the native numpy functions work on the github test runner.
158
+ antenna_name = ant_xds["antenna_name"].values
159
+ station = ant_xds["station"].values
160
+ antenna_name = np.array(
161
+ list(map(lambda x, y: x + "_" + y, antenna_name, station))
162
+ )
163
+
164
+ ant_xds["antenna_name"] = xr.DataArray(antenna_name, dims=["antenna_name"])
165
+ ant_xds.attrs["relocatable_antennas"] = True
166
+ else:
167
+ ant_xds.attrs["relocatable_antennas"] = False
168
+
169
+ ant_xds = ant_xds.assign_coords(
170
+ {
171
+ "telescope_name": (
172
+ "antenna_name",
173
+ np.array([telescope_name for ant in ant_xds["antenna_name"]]),
174
+ )
175
+ }
176
+ )
177
+
178
+ return ant_xds
179
+
180
+
181
+ def extract_feed_info(
182
+ ant_xds: xr.Dataset,
183
+ in_file: str,
184
+ antenna_id: list,
185
+ feed_id: int,
186
+ spectral_window_id: int,
187
+ ) -> xr.Dataset:
188
+ """
189
+ Reformats MSv2 Feed table content to MSv4 schema.
190
+
191
+ Parameters
192
+ ----------
193
+ ant_xds : xr.Dataset
194
+ Xarray Dataset containing antenna information.
195
+ in_file : str
196
+ Path to the input MSv2.
197
+ antenna_id : list
198
+ List of antenna IDs.
199
+ feed_id : int
200
+ Feed ID.
201
+ spectral_window_id : int
202
+ Spectral window ID.
203
+
204
+ Returns
205
+ -------
206
+ xr.Dataset
207
+ Dataset updated to contain the feed information.
208
+ """
209
+
210
+ # Extract feed information
211
+ generic_feed_xds = load_generic_table(
212
+ in_file,
213
+ "FEED",
214
+ rename_ids=subt_rename_ids["FEED"],
215
+ taql_where=f" where (ANTENNA_ID IN [{','.join(map(str, ant_xds.antenna_id.values))}]) AND (FEED_ID IN [{','.join(map(str, feed_id))}])",
216
+ ) # Some Lofar and MeerKAT data have the spw column set to -1 so we can't use '(SPECTRAL_WINDOW_ID = {spectral_window_id})'
217
+
218
+ if not generic_feed_xds:
219
+ # Some MSv2 have a FEED table that does not cover all antenna_id (and feed_id)
220
+ return ant_xds
221
+
222
+ feed_spw = np.unique(generic_feed_xds.SPECTRAL_WINDOW_ID)
223
+ if len(feed_spw) == 1 and feed_spw[0] == -1:
224
+ generic_feed_xds = generic_feed_xds.isel(SPECTRAL_WINDOW_ID=0, drop=True)
225
+ else:
226
+ if spectral_window_id not in feed_spw:
227
+ # For some spw the feed table is empty (this is the case with ALMA spw WVR#NOMINAL).
228
+ return ant_xds
229
+ else:
230
+ generic_feed_xds = generic_feed_xds.sel(
231
+ SPECTRAL_WINDOW_ID=spectral_window_id, drop=True
232
+ )
233
+
234
+ assert len(generic_feed_xds.TIME) == len(
235
+ antenna_id
236
+ ), "Can only process feed table with a single time entry for an feed, antenna and spectral_window_id."
237
+ generic_feed_xds = generic_feed_xds.sel(
238
+ ANTENNA_ID=antenna_id, drop=False
239
+ ) # Make sure the antenna_id is in the same order as the xds.
240
+
241
+ num_receptors = np.ravel(generic_feed_xds.NUM_RECEPTORS)
242
+ num_receptors = unique_1d(num_receptors[~np.isnan(num_receptors)])
243
+
244
+ assert (
245
+ len(num_receptors) == 1
246
+ ), "The number of receptors must be constant in feed table."
247
+
248
+ to_new_data_variables = {
249
+ "RECEPTOR_ANGLE": [
250
+ "ANTENNA_RECEPTOR_ANGLE",
251
+ ["antenna_name", "receptor_label"],
252
+ ],
253
+ "FOCUS_LENGTH": [
254
+ "ANTENNA_FOCUS_LENGTH",
255
+ ["antenna_name"],
256
+ ], # optional
257
+ }
258
+
259
+ to_new_coords = {
260
+ "POLARIZATION_TYPE": ["polarization_type", ["antenna_name", "receptor_label"]]
261
+ }
262
+
263
+ ant_xds = convert_generic_xds_to_xradio_schema(
264
+ generic_feed_xds,
265
+ ant_xds,
266
+ to_new_data_variables,
267
+ to_new_coords=to_new_coords,
268
+ )
269
+
270
+ # coords["receptor_label"] = "pol_" + np.arange(ant_xds.sizes["receptor_label"]).astype(str) #Works on laptop but fails in github test runner.
271
+ coords = {
272
+ "receptor_label": np.array(
273
+ list(
274
+ map(
275
+ lambda x, y: x + "_" + y,
276
+ ["pol"] * ant_xds.sizes["receptor_label"],
277
+ np.arange(ant_xds.sizes["receptor_label"]).astype(str),
278
+ )
279
+ ),
280
+ dtype=str,
281
+ )
282
+ }
283
+
284
+ ant_xds = ant_xds.assign_coords(coords)
285
+
286
+ # Correct to expected types. Some ALMA-SD (at least) leave receptor_label, polarization_type columns
287
+ # in the MS empty, causing a type mismatch
288
+ if (
289
+ "polarization_type" in ant_xds.coords
290
+ and ant_xds.coords["polarization_type"].dtype != str
291
+ ):
292
+ ant_xds.coords["polarization_type"] = ant_xds.coords[
293
+ "polarization_type"
294
+ ].astype(str)
295
+ return ant_xds
296
+
297
+
298
+ def create_gain_curve_xds(
299
+ in_file: str, spectral_window_id: int, ant_xds: xr.Dataset
300
+ ) -> xr.Dataset:
301
+ """
302
+ Produces a gain_curve_xds, reformats MSv2 GAIN CURVE table content to MSv4 schema.
303
+
304
+ Parameters
305
+ ----------
306
+ in_file : str
307
+ Path to the input MSv2.
308
+ spectral_window_id : int
309
+ The ID of the spectral window.
310
+ ant_xds : xr.Dataset
311
+ The antenna_xds that has information such as names, stations, etc., for coordinates
312
+
313
+ Returns
314
+ -------
315
+ xr.Dataset
316
+ The updated antenna dataset with gain curve information.
317
+ """
318
+
319
+ gain_curve_xds = None
320
+ if not table_exists(os.path.join(in_file, "GAIN_CURVE")):
321
+ return gain_curve_xds
322
+
323
+ generic_gain_curve_xds = load_generic_table(
324
+ in_file,
325
+ "GAIN_CURVE",
326
+ taql_where=f" where (ANTENNA_ID IN [{','.join(map(str,ant_xds.antenna_id.values))}]) AND (SPECTRAL_WINDOW_ID = {spectral_window_id})",
327
+ )
328
+
329
+ if not generic_gain_curve_xds.data_vars:
330
+ # Some times the gain_curve table is empty (this is the case with ngEHT simulation data we have).
331
+ return gain_curve_xds
332
+
333
+ assert (
334
+ len(generic_gain_curve_xds.SPECTRAL_WINDOW_ID) == 1
335
+ ), "Only one spectral window is supported."
336
+ generic_gain_curve_xds = generic_gain_curve_xds.isel(
337
+ SPECTRAL_WINDOW_ID=0, drop=True
338
+ ) # Drop the spectral window dimension as it is singleton.
339
+
340
+ assert (
341
+ len(generic_gain_curve_xds.TIME) == 1
342
+ ), "Only one gain curve measurement per antenna is supported."
343
+ measured_time = generic_gain_curve_xds.coords["TIME"].values[0]
344
+ generic_gain_curve_xds = generic_gain_curve_xds.isel(TIME=0, drop=True)
345
+
346
+ generic_gain_curve_xds = generic_gain_curve_xds.sel(
347
+ ANTENNA_ID=ant_xds.antenna_id, drop=False
348
+ ) # Make sure the antenna_id is in the same order as the xds .
349
+
350
+ gain_curve_xds = xr.Dataset(attrs={"type": "gain_curve"})
351
+
352
+ to_new_data_variables = {
353
+ "INTERVAL": ["GAIN_CURVE_INTERVAL", ["antenna_name"]],
354
+ "GAIN": [
355
+ "GAIN_CURVE",
356
+ ["antenna_name", "poly_term", "receptor_label"],
357
+ ],
358
+ "SENSITIVITY": [
359
+ "GAIN_CURVE_SENSITIVITY",
360
+ ["antenna_name", "receptor_label"],
361
+ ],
362
+ }
363
+
364
+ to_new_coords = {
365
+ "TYPE": ["gain_curve_type", ["antenna_name"]],
366
+ }
367
+
368
+ gain_curve_xds = convert_generic_xds_to_xradio_schema(
369
+ generic_gain_curve_xds,
370
+ gain_curve_xds,
371
+ to_new_data_variables,
372
+ to_new_coords,
373
+ )
374
+
375
+ ant_borrowed_coords = {
376
+ "antenna_name": ant_xds.coords["antenna_name"],
377
+ "station": ant_xds.coords["station"],
378
+ "mount": ant_xds.coords["mount"],
379
+ "telescope_name": ant_xds.coords["telescope_name"],
380
+ "receptor_label": ant_xds.coords["receptor_label"],
381
+ "polarization_type": ant_xds.coords["polarization_type"],
382
+ }
383
+ gain_curve_xds = gain_curve_xds.assign_coords(ant_borrowed_coords)
384
+
385
+ gain_curve_xds.attrs.update(
386
+ {
387
+ "measured_date": np.datetime_as_string(
388
+ convert_casacore_time([measured_time])[0]
389
+ )
390
+ }
391
+ )
392
+
393
+ # correct expected types (for example "GAIN_CURVE" can be float32)
394
+ for data_var in gain_curve_xds:
395
+ if gain_curve_xds.data_vars[data_var].dtype != np.float64:
396
+ gain_curve_xds[data_var] = gain_curve_xds[data_var].astype(np.float64)
397
+
398
+ return gain_curve_xds
399
+
400
+
401
+ def create_phase_calibration_xds(
402
+ in_file: str,
403
+ spectral_window_id: int,
404
+ ant_xds: xr.Dataset,
405
+ time_min_max: Tuple[np.float64, np.float64],
406
+ phase_cal_interp_time: Union[xr.DataArray, None] = None,
407
+ ) -> xr.Dataset:
408
+ """
409
+ Produces a phase_calibration_xds, reformats MSv2 Phase Cal table content to MSv4 schema.
410
+
411
+ Parameters
412
+ ----------
413
+ in_file : str
414
+ Path to the input MSv2.
415
+ spectral_window_id : int
416
+ The ID of the spectral window.
417
+ ant_xds : xr.Dataset
418
+ The antenna_xds that has information such as names, stations, etc., for coordinates
419
+ time_min_max : Tuple[np.float46, np.float64]
420
+ Min / max times to constrain loading (usually to the time range relevant to an MSv4)
421
+ interp_time : Union[xr.DataArray, None]
422
+ Time axis to interpolate the data vars to (usually main MSv4 time)
423
+
424
+ Returns
425
+ -------
426
+ xr.Dataset
427
+ The updated antenna dataset with phase cal information.
428
+ """
429
+
430
+ phase_cal_xds = None
431
+ if not table_exists(os.path.join(in_file, "PHASE_CAL")):
432
+ return phase_cal_xds
433
+
434
+ # Only read data between the min and max times of the visibility data in the MSv4.
435
+ taql_time_range = make_taql_where_between_min_max(
436
+ time_min_max, in_file, "PHASE_CAL", "TIME"
437
+ )
438
+ generic_phase_cal_xds = load_generic_table(
439
+ in_file,
440
+ "PHASE_CAL",
441
+ timecols=["TIME"],
442
+ taql_where=f" {taql_time_range} AND (ANTENNA_ID IN [{','.join(map(str,ant_xds.antenna_id.values))}]) AND (SPECTRAL_WINDOW_ID = {spectral_window_id})",
443
+ )
444
+
445
+ assert (
446
+ len(generic_phase_cal_xds.SPECTRAL_WINDOW_ID) == 1
447
+ ), "Only one spectral window is supported."
448
+ generic_phase_cal_xds = generic_phase_cal_xds.isel(
449
+ SPECTRAL_WINDOW_ID=0, drop=True
450
+ ) # Drop the spectral window dimension as it is singleton.
451
+
452
+ generic_phase_cal_xds = generic_phase_cal_xds.sel(
453
+ ANTENNA_ID=ant_xds.antenna_id, drop=False
454
+ ) # Make sure the antenna_id is in the same order as the xds.
455
+
456
+ to_new_data_variables = {
457
+ "INTERVAL": ["PHASE_CAL_INTERVAL", ["antenna_name", "time_phase_cal"]],
458
+ "TONE_FREQUENCY": [
459
+ "PHASE_CAL_TONE_FREQUENCY",
460
+ ["antenna_name", "time_phase_cal", "tone_label", "receptor_label"],
461
+ ],
462
+ "PHASE_CAL": [
463
+ "PHASE_CAL",
464
+ ["antenna_name", "time_phase_cal", "tone_label", "receptor_label"],
465
+ ],
466
+ "CABLE_CAL": ["PHASE_CAL_CABLE_CAL", ["antenna_name", "time_phase_cal"]],
467
+ }
468
+
469
+ to_new_coords = {
470
+ "TIME": ["time_phase_cal", ["time_phase_cal"]],
471
+ }
472
+
473
+ phase_cal_xds = xr.Dataset(attrs={"type": "phase_calibration"})
474
+ phase_cal_xds = convert_generic_xds_to_xradio_schema(
475
+ generic_phase_cal_xds, phase_cal_xds, to_new_data_variables, to_new_coords
476
+ )
477
+
478
+ phase_cal_xds["PHASE_CAL"] = phase_cal_xds["PHASE_CAL"].transpose(
479
+ "antenna_name", "time_phase_cal", "receptor_label", "tone_label"
480
+ )
481
+ phase_cal_xds["PHASE_CAL_TONE_FREQUENCY"] = phase_cal_xds[
482
+ "PHASE_CAL_TONE_FREQUENCY"
483
+ ].transpose("antenna_name", "time_phase_cal", "receptor_label", "tone_label")
484
+
485
+ ant_borrowed_coords = {
486
+ "antenna_name": ant_xds.coords["antenna_name"],
487
+ "station": ant_xds.coords["station"],
488
+ "mount": ant_xds.coords["mount"],
489
+ "telescope_name": ant_xds.coords["telescope_name"],
490
+ "receptor_label": ant_xds.coords["receptor_label"],
491
+ "polarization_type": ant_xds.coords["polarization_type"],
492
+ }
493
+ # phase_cal_xds = phase_cal_xds.assign_coords({"tone_label" : "freq_" + np.arange(phase_cal_xds.sizes["tone_label"]).astype(str)}) #Works on laptop but fails in github test runner.
494
+ tone_label_coord = {
495
+ "tone_label": np.array(
496
+ list(
497
+ map(
498
+ lambda x, y: x + "_" + y,
499
+ ["freq"] * phase_cal_xds.sizes["tone_label"],
500
+ np.arange(phase_cal_xds.sizes["tone_label"]).astype(str),
501
+ )
502
+ )
503
+ )
504
+ }
505
+ phase_cal_xds = phase_cal_xds.assign_coords(ant_borrowed_coords | tone_label_coord)
506
+
507
+ # Adjust expected types
508
+ phase_cal_xds["time_phase_cal"] = (
509
+ phase_cal_xds.time_phase_cal.astype("float64").astype("float64") / 10**9
510
+ )
511
+
512
+ phase_cal_xds = interpolate_to_time(
513
+ phase_cal_xds,
514
+ phase_cal_interp_time,
515
+ "antenna_xds",
516
+ time_name="time_phase_cal",
517
+ )
518
+
519
+ time_coord_attrs = {
520
+ "type": "time",
521
+ "units": ["s"],
522
+ "scale": "utc",
523
+ "format": "unix",
524
+ }
525
+
526
+ # If we interpolate rename the time_phase_cal axis to time.
527
+ if phase_cal_interp_time is not None:
528
+ time_coord = {"time": ("time_phase_cal", phase_cal_interp_time.data)}
529
+ phase_cal_xds = phase_cal_xds.assign_coords(time_coord)
530
+ phase_cal_xds.coords["time"].attrs.update(time_coord_attrs)
531
+ phase_cal_xds = phase_cal_xds.swap_dims({"time_phase_cal": "time"}).drop_vars(
532
+ "time_phase_cal"
533
+ )
534
+
535
+ return phase_cal_xds