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