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.
- xradio/_utils/coord_math.py +100 -0
- xradio/_utils/list_and_array.py +49 -4
- xradio/_utils/schema.py +36 -16
- xradio/image/_util/_casacore/xds_from_casacore.py +5 -5
- xradio/image/_util/_casacore/xds_to_casacore.py +12 -11
- xradio/image/_util/_fits/xds_from_fits.py +18 -17
- xradio/image/_util/_zarr/zarr_low_level.py +29 -12
- xradio/image/_util/common.py +1 -1
- xradio/image/_util/image_factory.py +1 -1
- xradio/measurement_set/__init__.py +18 -0
- xradio/measurement_set/_utils/__init__.py +5 -0
- xradio/{vis/_vis_utils/_ms → measurement_set/_utils/_msv2}/_tables/load_main_table.py +1 -1
- xradio/{vis/_vis_utils/_ms → measurement_set/_utils/_msv2}/_tables/read.py +15 -1
- xradio/{vis/_vis_utils/_ms → measurement_set/_utils/_msv2}/conversion.py +186 -84
- xradio/measurement_set/_utils/_msv2/create_antenna_xds.py +535 -0
- xradio/{vis/_vis_utils/_ms → measurement_set/_utils/_msv2}/create_field_and_source_xds.py +146 -58
- xradio/measurement_set/_utils/_msv2/msv4_info_dicts.py +203 -0
- xradio/measurement_set/_utils/_msv2/msv4_sub_xdss.py +550 -0
- xradio/{vis/_vis_utils/_ms → measurement_set/_utils/_msv2}/subtables.py +1 -1
- xradio/{vis/_vis_utils → measurement_set/_utils}/_utils/xds_helper.py +1 -1
- xradio/{vis/_vis_utils/ms.py → measurement_set/_utils/msv2.py} +4 -4
- xradio/{vis/_vis_utils → measurement_set/_utils}/zarr.py +3 -3
- xradio/{vis → measurement_set}/convert_msv2_to_processing_set.py +9 -2
- xradio/{vis → measurement_set}/load_processing_set.py +16 -20
- xradio/measurement_set/measurement_set_xds.py +83 -0
- xradio/{vis/read_processing_set.py → measurement_set/open_processing_set.py} +25 -34
- xradio/measurement_set/processing_set.py +777 -0
- xradio/measurement_set/schema.py +1979 -0
- xradio/schema/check.py +42 -22
- xradio/schema/dataclass.py +56 -6
- xradio/sphinx/__init__.py +12 -0
- xradio/sphinx/schema_table.py +351 -0
- {xradio-0.0.40.dist-info → xradio-0.0.42.dist-info}/METADATA +17 -15
- xradio-0.0.42.dist-info/RECORD +76 -0
- {xradio-0.0.40.dist-info → xradio-0.0.42.dist-info}/WHEEL +1 -1
- xradio/_utils/common.py +0 -101
- xradio/vis/__init__.py +0 -14
- xradio/vis/_processing_set.py +0 -302
- xradio/vis/_vis_utils/__init__.py +0 -5
- xradio/vis/_vis_utils/_ms/create_antenna_xds.py +0 -482
- xradio/vis/_vis_utils/_ms/msv4_infos.py +0 -0
- xradio/vis/_vis_utils/_ms/msv4_sub_xdss.py +0 -306
- xradio/vis/schema.py +0 -1102
- xradio-0.0.40.dist-info/RECORD +0 -73
- /xradio/{vis/_vis_utils/_ms → measurement_set/_utils/_msv2}/_tables/load.py +0 -0
- /xradio/{vis/_vis_utils/_ms → measurement_set/_utils/_msv2}/_tables/read_main_table.py +0 -0
- /xradio/{vis/_vis_utils/_ms → measurement_set/_utils/_msv2}/_tables/read_subtables.py +0 -0
- /xradio/{vis/_vis_utils/_ms → measurement_set/_utils/_msv2}/_tables/table_query.py +0 -0
- /xradio/{vis/_vis_utils/_ms → measurement_set/_utils/_msv2}/_tables/write.py +0 -0
- /xradio/{vis/_vis_utils/_ms → measurement_set/_utils/_msv2}/_tables/write_exp_api.py +0 -0
- /xradio/{vis/_vis_utils/_ms → measurement_set/_utils/_msv2}/chunks.py +0 -0
- /xradio/{vis/_vis_utils/_ms → measurement_set/_utils/_msv2}/descr.py +0 -0
- /xradio/{vis/_vis_utils/_ms → measurement_set/_utils/_msv2}/msv2_msv3.py +0 -0
- /xradio/{vis/_vis_utils/_ms → measurement_set/_utils/_msv2}/msv2_to_msv4_meta.py +0 -0
- /xradio/{vis/_vis_utils/_ms → measurement_set/_utils/_msv2}/optimised_functions.py +0 -0
- /xradio/{vis/_vis_utils/_ms → measurement_set/_utils/_msv2}/partition_queries.py +0 -0
- /xradio/{vis/_vis_utils/_ms → measurement_set/_utils/_msv2}/partitions.py +0 -0
- /xradio/{vis/_vis_utils → measurement_set/_utils}/_utils/cds.py +0 -0
- /xradio/{vis/_vis_utils → measurement_set/_utils}/_utils/partition_attrs.py +0 -0
- /xradio/{vis/_vis_utils → measurement_set/_utils}/_utils/stokes_types.py +0 -0
- /xradio/{vis/_vis_utils → measurement_set/_utils}/_zarr/encoding.py +0 -0
- /xradio/{vis/_vis_utils → measurement_set/_utils}/_zarr/read.py +0 -0
- /xradio/{vis/_vis_utils → measurement_set/_utils}/_zarr/write.py +0 -0
- {xradio-0.0.40.dist-info → xradio-0.0.42.dist-info}/LICENSE.txt +0 -0
- {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
|