xradio 0.0.30__py3-none-any.whl → 0.0.31__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/__init__.py +10 -10
- xradio/_utils/common.py +57 -0
- xradio/_utils/{array.py → list_and_array.py} +23 -0
- xradio/vis/_processing_set.py +19 -7
- xradio/vis/_vis_utils/_ms/_tables/create_field_and_source_xds.py +205 -203
- xradio/vis/_vis_utils/_ms/_tables/load_main_table.py +1 -1
- xradio/vis/_vis_utils/_ms/_tables/read.py +20 -19
- xradio/vis/_vis_utils/_ms/_tables/read_main_table.py +1 -1
- xradio/vis/_vis_utils/_ms/_tables/read_subtables.py +1 -1
- xradio/vis/_vis_utils/_ms/chunks.py +1 -1
- xradio/vis/_vis_utils/_ms/conversion.py +16 -20
- xradio/vis/_vis_utils/_ms/descr.py +1 -1
- xradio/vis/_vis_utils/_ms/msv2_to_msv4_meta.py +2 -2
- xradio/vis/_vis_utils/_ms/msv4_sub_xdss.py +7 -4
- xradio/vis/_vis_utils/_ms/partition_queries.py +18 -16
- xradio/vis/convert_msv2_to_processing_set.py +11 -10
- xradio/vis/read_processing_set.py +7 -7
- {xradio-0.0.30.dist-info → xradio-0.0.31.dist-info}/METADATA +1 -1
- {xradio-0.0.30.dist-info → xradio-0.0.31.dist-info}/RECORD +22 -22
- {xradio-0.0.30.dist-info → xradio-0.0.31.dist-info}/WHEEL +1 -1
- {xradio-0.0.30.dist-info → xradio-0.0.31.dist-info}/LICENSE.txt +0 -0
- {xradio-0.0.30.dist-info → xradio-0.0.31.dist-info}/top_level.txt +0 -0
|
@@ -17,33 +17,35 @@ from xradio.vis._vis_utils._ms._tables.read import (
|
|
|
17
17
|
)
|
|
18
18
|
from xradio.vis._vis_utils._ms._tables.table_query import open_table_ro
|
|
19
19
|
import graphviper.utils.logger as logger
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
from xradio._utils.list_and_array import (
|
|
21
|
+
check_if_consistent,
|
|
22
|
+
unique_1d,
|
|
23
|
+
to_list,
|
|
24
|
+
to_np_array,
|
|
25
|
+
)
|
|
26
|
+
from xradio._utils.common import cast_to_str, convert_to_si_units, add_position_offsets
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
def create_field_and_source_xds(
|
|
30
|
-
in_file,
|
|
31
|
-
field_id,
|
|
32
|
-
spectral_window_id,
|
|
33
|
-
field_times,
|
|
34
|
-
is_single_dish,
|
|
30
|
+
in_file: str,
|
|
31
|
+
field_id: list,
|
|
32
|
+
spectral_window_id: int,
|
|
33
|
+
field_times: list,
|
|
34
|
+
is_single_dish: bool,
|
|
35
35
|
time_min_max: Tuple[np.float64, np.float64],
|
|
36
36
|
ephemeris_interp_time: Union[xr.DataArray, None] = None,
|
|
37
37
|
):
|
|
38
38
|
"""
|
|
39
39
|
Create a field and source xarray dataset (xds) from the given input file, field ID, and spectral window ID.
|
|
40
|
+
Data is extracted from the FIELD and SOURCE tables and if there is ephemeris data, it is also extracted.
|
|
41
|
+
field_id will only be guaranteed to be a list of length 1 when partition_scheme does not include "FIELD_ID".
|
|
40
42
|
|
|
41
43
|
Parameters:
|
|
42
44
|
----------
|
|
43
45
|
in_file : str
|
|
44
46
|
The path to the input file.
|
|
45
|
-
field_id : int
|
|
46
|
-
The
|
|
47
|
+
field_id : list of int
|
|
48
|
+
The field ids to select.
|
|
47
49
|
spectral_window_id : int
|
|
48
50
|
The ID of the spectral window.
|
|
49
51
|
time_min_max : Tuple[np.float64, np.float46]
|
|
@@ -61,14 +63,13 @@ def create_field_and_source_xds(
|
|
|
61
63
|
|
|
62
64
|
field_and_source_xds = xr.Dataset()
|
|
63
65
|
|
|
64
|
-
field_and_source_xds, ephemeris_path, ephemeris_table_name = (
|
|
66
|
+
field_and_source_xds, ephemeris_path, ephemeris_table_name, source_id = (
|
|
65
67
|
create_field_info_and_check_ephemeris(
|
|
66
68
|
field_and_source_xds, in_file, field_id, field_times, is_single_dish
|
|
67
69
|
)
|
|
68
70
|
)
|
|
69
|
-
source_id = field_and_source_xds.attrs["source_id"]
|
|
70
71
|
|
|
71
|
-
if
|
|
72
|
+
if field_and_source_xds.attrs["is_ephemeris"]:
|
|
72
73
|
field_and_source_xds = extract_ephemeris_info(
|
|
73
74
|
field_and_source_xds,
|
|
74
75
|
ephemeris_path,
|
|
@@ -77,26 +78,16 @@ def create_field_and_source_xds(
|
|
|
77
78
|
time_min_max,
|
|
78
79
|
ephemeris_interp_time,
|
|
79
80
|
)
|
|
80
|
-
field_and_source_xds.attrs["is_ephemeris"] = True
|
|
81
|
-
field_and_source_xds = extract_source_info(
|
|
82
|
-
field_and_source_xds,
|
|
83
|
-
in_file,
|
|
84
|
-
True,
|
|
85
|
-
source_id,
|
|
86
|
-
spectral_window_id,
|
|
87
|
-
)
|
|
88
81
|
|
|
89
|
-
|
|
90
|
-
field_and_source_xds
|
|
91
|
-
|
|
92
|
-
)
|
|
93
|
-
field_and_source_xds.attrs["is_ephemeris"] = False
|
|
82
|
+
field_and_source_xds = extract_source_info(
|
|
83
|
+
field_and_source_xds, in_file, source_id, spectral_window_id
|
|
84
|
+
)
|
|
94
85
|
|
|
95
86
|
logger.debug(
|
|
96
87
|
f"create_field_and_source_xds() execution time {time.time() - start_time:0.2f} s"
|
|
97
88
|
)
|
|
98
89
|
|
|
99
|
-
return field_and_source_xds
|
|
90
|
+
return field_and_source_xds, source_id
|
|
100
91
|
|
|
101
92
|
|
|
102
93
|
def extract_ephemeris_info(
|
|
@@ -128,10 +119,11 @@ def extract_ephemeris_info(
|
|
|
128
119
|
xds : xr.Dataset
|
|
129
120
|
The xarray dataset with the added ephemeris information.
|
|
130
121
|
"""
|
|
131
|
-
# The JPL-Horizons
|
|
122
|
+
# The JPL-Horizons ephemeris table implementation in CASA does not follow the standard way of defining measures.
|
|
132
123
|
# Consequently a lot of hardcoding is needed to extract the information.
|
|
133
124
|
# https://casadocs.readthedocs.io/en/latest/notebooks/external-data.html
|
|
134
125
|
|
|
126
|
+
# Only read data between the min and max times of the visibility data in the MSv4.
|
|
135
127
|
min_max_mjd = (
|
|
136
128
|
convert_casacore_time_to_mjd(time_min_max[0]),
|
|
137
129
|
convert_casacore_time_to_mjd(time_min_max[1]),
|
|
@@ -143,11 +135,14 @@ def extract_ephemeris_info(
|
|
|
143
135
|
path, table_name, timecols=["MJD"], taql_where=taql_time_range
|
|
144
136
|
)
|
|
145
137
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
ephemeris_xds = ephemeris_xds.isel(
|
|
138
|
+
assert (
|
|
139
|
+
len(ephemeris_xds.ephemeris_id) == 1
|
|
140
|
+
), "Non standard ephemeris table. Only a single ephemeris is allowed per MSv4."
|
|
141
|
+
ephemeris_xds = ephemeris_xds.isel(
|
|
142
|
+
ephemeris_id=0
|
|
143
|
+
) # Collapse the ephemeris_id dimension.
|
|
150
144
|
|
|
145
|
+
# Get meta data.
|
|
151
146
|
ephemeris_meta = ephemeris_xds.attrs["other"]["msv2"]["ctds_attrs"]
|
|
152
147
|
ephemris_column_description = ephemeris_xds.attrs["other"]["msv2"]["ctds_attrs"][
|
|
153
148
|
"column_descriptions"
|
|
@@ -170,11 +165,16 @@ def extract_ephemeris_info(
|
|
|
170
165
|
|
|
171
166
|
coords = {
|
|
172
167
|
"ellipsoid_pos_label": ["lon", "lat", "dist"],
|
|
173
|
-
"
|
|
168
|
+
"ephem_time": ephemeris_xds[
|
|
169
|
+
"time"
|
|
170
|
+
].data, # We are using the "ephem_time" label because it might not match the optional time axis of the source and field info. If ephemeris_interpolate=True then rename it to time.
|
|
174
171
|
"sky_pos_label": ["ra", "dec", "dist"],
|
|
175
172
|
}
|
|
176
173
|
|
|
177
|
-
|
|
174
|
+
temp_xds = xr.Dataset()
|
|
175
|
+
|
|
176
|
+
# Add mandatory data: SOURCE_POSITION
|
|
177
|
+
temp_xds["SOURCE_POSITION"] = xr.DataArray(
|
|
178
178
|
np.column_stack(
|
|
179
179
|
(
|
|
180
180
|
ephemeris_xds["ra"].data,
|
|
@@ -182,22 +182,23 @@ def extract_ephemeris_info(
|
|
|
182
182
|
ephemeris_xds["rho"].data,
|
|
183
183
|
)
|
|
184
184
|
),
|
|
185
|
-
dims=["
|
|
185
|
+
dims=["ephem_time", "sky_pos_label"],
|
|
186
186
|
)
|
|
187
|
-
# Have to use cast_to_str because the
|
|
187
|
+
# Have to use cast_to_str because the ephemeris table units are not consistently in a list or a string.
|
|
188
188
|
sky_coord_units = [
|
|
189
189
|
cast_to_str(ephemris_column_description["RA"]["keywords"][unit_keyword]),
|
|
190
190
|
cast_to_str(ephemris_column_description["DEC"]["keywords"][unit_keyword]),
|
|
191
191
|
cast_to_str(ephemris_column_description["Rho"]["keywords"][unit_keyword]),
|
|
192
192
|
]
|
|
193
|
-
|
|
193
|
+
temp_xds["SOURCE_POSITION"].attrs.update(
|
|
194
194
|
{"type": "sky_coord", "frame": sky_coord_frame, "units": sky_coord_units}
|
|
195
195
|
)
|
|
196
196
|
|
|
197
|
-
|
|
198
|
-
|
|
197
|
+
# Add mandatory data: SOURCE_RADIAL_VELOCITY
|
|
198
|
+
temp_xds["SOURCE_RADIAL_VELOCITY"] = xr.DataArray(
|
|
199
|
+
ephemeris_xds["radvel"].data, dims=["ephem_time"]
|
|
199
200
|
)
|
|
200
|
-
|
|
201
|
+
temp_xds["SOURCE_RADIAL_VELOCITY"].attrs.update(
|
|
201
202
|
{
|
|
202
203
|
"type": "quantity",
|
|
203
204
|
"units": [
|
|
@@ -208,15 +209,16 @@ def extract_ephemeris_info(
|
|
|
208
209
|
}
|
|
209
210
|
)
|
|
210
211
|
|
|
212
|
+
# Add mandatory data: OBSERVATION_POSITION
|
|
211
213
|
observation_position = [
|
|
212
214
|
ephemeris_meta["GeoLong"],
|
|
213
215
|
ephemeris_meta["GeoLat"],
|
|
214
216
|
ephemeris_meta["GeoDist"],
|
|
215
217
|
]
|
|
216
|
-
|
|
218
|
+
temp_xds["OBSERVATION_POSITION"] = xr.DataArray(
|
|
217
219
|
observation_position, dims=["ellipsoid_pos_label"]
|
|
218
220
|
)
|
|
219
|
-
|
|
221
|
+
temp_xds["OBSERVATION_POSITION"].attrs.update(
|
|
220
222
|
{
|
|
221
223
|
"type": "location",
|
|
222
224
|
"units": ["deg", "deg", "m"],
|
|
@@ -227,14 +229,12 @@ def extract_ephemeris_info(
|
|
|
227
229
|
}
|
|
228
230
|
) # I think the units are ['deg','deg','m'] and 'WGS84'.
|
|
229
231
|
|
|
230
|
-
# Add optional data
|
|
231
|
-
# NORTH_POLE_POSITION_ANGLE
|
|
232
|
-
|
|
232
|
+
# Add optional data NORTH_POLE_POSITION_ANGLE and NORTH_POLE_ANGULAR_DISTANCE
|
|
233
233
|
if "np_ang" in ephemeris_xds.data_vars:
|
|
234
|
-
|
|
235
|
-
ephemeris_xds["np_ang"].data, dims=["
|
|
234
|
+
temp_xds["NORTH_POLE_POSITION_ANGLE"] = xr.DataArray(
|
|
235
|
+
ephemeris_xds["np_ang"].data, dims=["ephem_time"]
|
|
236
236
|
)
|
|
237
|
-
|
|
237
|
+
temp_xds["NORTH_POLE_POSITION_ANGLE"].attrs.update(
|
|
238
238
|
{
|
|
239
239
|
"type": "quantity",
|
|
240
240
|
"units": [
|
|
@@ -246,10 +246,10 @@ def extract_ephemeris_info(
|
|
|
246
246
|
)
|
|
247
247
|
|
|
248
248
|
if "np_dist" in ephemeris_xds.data_vars:
|
|
249
|
-
|
|
250
|
-
ephemeris_xds["np_dist"].data, dims=["
|
|
249
|
+
temp_xds["NORTH_POLE_ANGULAR_DISTANCE"] = xr.DataArray(
|
|
250
|
+
ephemeris_xds["np_dist"].data, dims=["ephem_time"]
|
|
251
251
|
)
|
|
252
|
-
|
|
252
|
+
temp_xds["NORTH_POLE_ANGULAR_DISTANCE"].attrs.update(
|
|
253
253
|
{
|
|
254
254
|
"type": "quantity",
|
|
255
255
|
"units": [
|
|
@@ -260,8 +260,9 @@ def extract_ephemeris_info(
|
|
|
260
260
|
}
|
|
261
261
|
)
|
|
262
262
|
|
|
263
|
+
# Add optional data: SUB_OBSERVER_POSITION and SUB_SOLAR_POSITION
|
|
263
264
|
if "disklong" in ephemeris_xds.data_vars:
|
|
264
|
-
|
|
265
|
+
temp_xds["SUB_OBSERVER_POSITION"] = xr.DataArray(
|
|
265
266
|
np.column_stack(
|
|
266
267
|
(
|
|
267
268
|
ephemeris_xds["disklong"].data,
|
|
@@ -269,7 +270,7 @@ def extract_ephemeris_info(
|
|
|
269
270
|
np.zeros(ephemeris_xds["disklong"].shape),
|
|
270
271
|
)
|
|
271
272
|
),
|
|
272
|
-
dims=["
|
|
273
|
+
dims=["ephem_time", "ellipsoid_pos_label"],
|
|
273
274
|
)
|
|
274
275
|
|
|
275
276
|
if "DiskLong" in ephemris_column_description:
|
|
@@ -279,7 +280,7 @@ def extract_ephemeris_info(
|
|
|
279
280
|
units_key_lon = "diskLong"
|
|
280
281
|
units_key_lat = "diskLat"
|
|
281
282
|
|
|
282
|
-
|
|
283
|
+
temp_xds["SUB_OBSERVER_POSITION"].attrs.update(
|
|
283
284
|
{
|
|
284
285
|
"type": "location",
|
|
285
286
|
"ellipsoid": "NA",
|
|
@@ -302,7 +303,7 @@ def extract_ephemeris_info(
|
|
|
302
303
|
)
|
|
303
304
|
|
|
304
305
|
if "si_lon" in ephemeris_xds.data_vars:
|
|
305
|
-
|
|
306
|
+
temp_xds["SUB_SOLAR_POSITION"] = xr.DataArray(
|
|
306
307
|
np.column_stack(
|
|
307
308
|
(
|
|
308
309
|
ephemeris_xds["si_lon"].data,
|
|
@@ -310,9 +311,9 @@ def extract_ephemeris_info(
|
|
|
310
311
|
ephemeris_xds["r"].data,
|
|
311
312
|
)
|
|
312
313
|
),
|
|
313
|
-
dims=["
|
|
314
|
+
dims=["ephem_time", "ellipsoid_pos_label"],
|
|
314
315
|
)
|
|
315
|
-
|
|
316
|
+
temp_xds["SUB_SOLAR_POSITION"].attrs.update(
|
|
316
317
|
{
|
|
317
318
|
"type": "location",
|
|
318
319
|
"ellipsoid": "NA",
|
|
@@ -332,11 +333,12 @@ def extract_ephemeris_info(
|
|
|
332
333
|
}
|
|
333
334
|
)
|
|
334
335
|
|
|
336
|
+
# Add optional data: HELIOCENTRIC_RADIAL_VELOCITY
|
|
335
337
|
if "rdot" in ephemeris_xds.data_vars:
|
|
336
|
-
|
|
337
|
-
ephemeris_xds["rdot"].data, dims=["
|
|
338
|
+
temp_xds["HELIOCENTRIC_RADIAL_VELOCITY"] = xr.DataArray(
|
|
339
|
+
ephemeris_xds["rdot"].data, dims=["ephem_time"]
|
|
338
340
|
)
|
|
339
|
-
|
|
341
|
+
temp_xds["HELIOCENTRIC_RADIAL_VELOCITY"].attrs.update(
|
|
340
342
|
{
|
|
341
343
|
"type": "quantity",
|
|
342
344
|
"units": [
|
|
@@ -347,11 +349,12 @@ def extract_ephemeris_info(
|
|
|
347
349
|
}
|
|
348
350
|
)
|
|
349
351
|
|
|
352
|
+
# Add optional data: OBSERVER_PHASE_ANGLE
|
|
350
353
|
if "phang" in ephemeris_xds.data_vars:
|
|
351
|
-
|
|
352
|
-
ephemeris_xds["phang"].data, dims=["
|
|
354
|
+
temp_xds["OBSERVER_PHASE_ANGLE"] = xr.DataArray(
|
|
355
|
+
ephemeris_xds["phang"].data, dims=["ephem_time"]
|
|
353
356
|
)
|
|
354
|
-
|
|
357
|
+
temp_xds["OBSERVER_PHASE_ANGLE"].attrs.update(
|
|
355
358
|
{
|
|
356
359
|
"type": "quantity",
|
|
357
360
|
"units": [
|
|
@@ -362,29 +365,52 @@ def extract_ephemeris_info(
|
|
|
362
365
|
}
|
|
363
366
|
)
|
|
364
367
|
|
|
365
|
-
|
|
366
|
-
|
|
368
|
+
temp_xds = temp_xds.assign_coords(coords)
|
|
369
|
+
temp_xds["ephem_time"].attrs.update(
|
|
367
370
|
{"type": "time", "units": ["s"], "scale": "UTC", "format": "UNIX"}
|
|
368
371
|
)
|
|
369
372
|
|
|
370
|
-
|
|
371
|
-
|
|
373
|
+
# Convert to si units and interpolate if ephemeris_interpolate=True:
|
|
374
|
+
temp_xds = convert_to_si_units(temp_xds)
|
|
375
|
+
temp_xds = interpolate_to_time(
|
|
376
|
+
temp_xds, interp_time, "field_and_source_xds", time_name="ephem_time"
|
|
377
|
+
)
|
|
372
378
|
|
|
379
|
+
# If we interpolate rename the ephem_time axis to time.
|
|
380
|
+
if interp_time is not None:
|
|
381
|
+
temp_xds = temp_xds.swap_dims({"ephem_time": "time"}).drop_vars("ephem_time")
|
|
382
|
+
|
|
383
|
+
xds = xr.merge([xds, temp_xds])
|
|
384
|
+
|
|
385
|
+
# Add the SOURCE_POSITION to the FIELD_PHASE_CENTER or FIELD_REFERENCE_CENTER. Ephemeris obs: When loaded from the MSv2 field table the FIELD_REFERENCE_CENTER or FIELD_PHASE_CENTER only contain an offset from the SOURCE_POSITION.
|
|
386
|
+
# We also need to add a distance dimension to the FIELD_PHASE_CENTER or FIELD_REFERENCE_CENTER to match the SOURCE_POSITION.
|
|
387
|
+
# FIELD_PHASE_CENTER is used for interferometer data and FIELD_REFERENCE_CENTER is used for single dish data.
|
|
373
388
|
if is_single_dish:
|
|
374
|
-
|
|
389
|
+
center_dv = "FIELD_REFERENCE_CENTER"
|
|
390
|
+
else:
|
|
391
|
+
center_dv = "FIELD_PHASE_CENTER"
|
|
392
|
+
|
|
393
|
+
if "time" in xds[center_dv].coords:
|
|
394
|
+
assert (
|
|
395
|
+
interp_time is not None
|
|
396
|
+
), 'ephemeris_interpolate must be True if there is ephemeris data and multiple fields (this will occur if "FIELD_ID" is not in partition_scheme).'
|
|
397
|
+
|
|
398
|
+
xds[center_dv] = xr.DataArray(
|
|
375
399
|
add_position_offsets(
|
|
376
|
-
np.
|
|
377
|
-
|
|
400
|
+
np.column_stack(
|
|
401
|
+
(xds[center_dv].values, np.zeros(xds[center_dv].values.shape[0]))
|
|
402
|
+
),
|
|
403
|
+
xds["SOURCE_POSITION"].values,
|
|
378
404
|
),
|
|
379
|
-
dims=["
|
|
405
|
+
dims=[xds["SOURCE_POSITION"].dims[0], "sky_pos_label"],
|
|
380
406
|
)
|
|
381
407
|
else:
|
|
382
|
-
xds[
|
|
408
|
+
xds[center_dv] = xr.DataArray(
|
|
383
409
|
add_position_offsets(
|
|
384
|
-
np.append(xds[
|
|
385
|
-
xds["SOURCE_POSITION"].
|
|
410
|
+
np.append(xds[center_dv].values, 0),
|
|
411
|
+
xds["SOURCE_POSITION"].values,
|
|
386
412
|
),
|
|
387
|
-
dims=["
|
|
413
|
+
dims=[xds["SOURCE_POSITION"].dims[0], "sky_pos_label"],
|
|
388
414
|
)
|
|
389
415
|
|
|
390
416
|
xds["FIELD_PHASE_CENTER"].attrs.update(xds["SOURCE_POSITION"].attrs)
|
|
@@ -392,56 +418,7 @@ def extract_ephemeris_info(
|
|
|
392
418
|
return xds
|
|
393
419
|
|
|
394
420
|
|
|
395
|
-
def
|
|
396
|
-
new_pos = dv_1 + dv_2
|
|
397
|
-
|
|
398
|
-
while np.any(new_pos[:, 0] > np.pi) or np.any(new_pos[:, 0] < -np.pi):
|
|
399
|
-
new_pos[:, 0] = np.where(
|
|
400
|
-
new_pos[:, 0] > np.pi, new_pos[:, 0] - 2 * np.pi, new_pos[:, 0]
|
|
401
|
-
)
|
|
402
|
-
new_pos[:, 0] = np.where(
|
|
403
|
-
new_pos[:, 0] < -np.pi, new_pos[:, 0] + 2 * np.pi, new_pos[:, 0]
|
|
404
|
-
)
|
|
405
|
-
|
|
406
|
-
while np.any(new_pos[:, 1] > np.pi / 2) or np.any(new_pos[:, 1] < -np.pi / 2):
|
|
407
|
-
new_pos[:, 1] = np.where(
|
|
408
|
-
new_pos[:, 1] > np.pi / 2, new_pos[:, 1] - np.pi, new_pos[:, 1]
|
|
409
|
-
)
|
|
410
|
-
new_pos[:, 1] = np.where(
|
|
411
|
-
new_pos[:, 1] < -np.pi / 2, new_pos[:, 1] + np.pi, new_pos[:, 1]
|
|
412
|
-
)
|
|
413
|
-
|
|
414
|
-
return new_pos
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
def convert_to_si_units(xds):
|
|
418
|
-
for data_var in xds.data_vars:
|
|
419
|
-
if "units" in xds[data_var].attrs:
|
|
420
|
-
for u_i, u in enumerate(xds[data_var].attrs["units"]):
|
|
421
|
-
if u == "km":
|
|
422
|
-
xds[data_var][..., u_i] = xds[data_var][..., u_i] * 1e3
|
|
423
|
-
xds[data_var].attrs["units"][u_i] = "m"
|
|
424
|
-
if u == "km/s":
|
|
425
|
-
xds[data_var][..., u_i] = xds[data_var][..., u_i] * 1e3
|
|
426
|
-
xds[data_var].attrs["units"][u_i] = "m/s"
|
|
427
|
-
if u == "deg":
|
|
428
|
-
xds[data_var][..., u_i] = xds[data_var][..., u_i] * np.pi / 180
|
|
429
|
-
xds[data_var].attrs["units"][u_i] = "rad"
|
|
430
|
-
if u == "Au" or u == "AU":
|
|
431
|
-
xds[data_var][..., u_i] = xds[data_var][..., u_i] * 149597870700
|
|
432
|
-
xds[data_var].attrs["units"][u_i] = "m"
|
|
433
|
-
if u == "Au/d" or u == "AU/d":
|
|
434
|
-
xds[data_var][..., u_i] = (
|
|
435
|
-
xds[data_var][..., u_i] * 149597870700 / 86400
|
|
436
|
-
)
|
|
437
|
-
xds[data_var].attrs["units"][u_i] = "m/s"
|
|
438
|
-
if u == "arcsec":
|
|
439
|
-
xds[data_var][..., u_i] = xds[data_var][..., u_i] * np.pi / 648000
|
|
440
|
-
xds[data_var].attrs["units"][u_i] = "rad"
|
|
441
|
-
return xds
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
def extract_source_info(xds, path, is_ephemeris, source_id, spectral_window_id):
|
|
421
|
+
def extract_source_info(xds, path, source_id, spectral_window_id):
|
|
445
422
|
"""
|
|
446
423
|
Extracts source information from the given path and adds it to the xarray dataset.
|
|
447
424
|
|
|
@@ -451,8 +428,6 @@ def extract_source_info(xds, path, is_ephemeris, source_id, spectral_window_id):
|
|
|
451
428
|
The xarray dataset to which the source information will be added.
|
|
452
429
|
path : str
|
|
453
430
|
The path to the input file.
|
|
454
|
-
is_ephemeris : bool
|
|
455
|
-
Flag indicating if the source is an ephemeris.
|
|
456
431
|
source_id : int
|
|
457
432
|
The ID of the source.
|
|
458
433
|
spectral_window_id : int
|
|
@@ -464,65 +439,104 @@ def extract_source_info(xds, path, is_ephemeris, source_id, spectral_window_id):
|
|
|
464
439
|
The xarray dataset with the added source information.
|
|
465
440
|
"""
|
|
466
441
|
|
|
467
|
-
|
|
442
|
+
is_ephemeris = xds.attrs[
|
|
443
|
+
"is_ephemeris"
|
|
444
|
+
] # If ephemeris data is present we ignore the SOURCE_DIRECTION in the source table.
|
|
445
|
+
|
|
446
|
+
if all(source_id == -1):
|
|
468
447
|
logger.warning(
|
|
469
448
|
f"Source_id is -1. No source information will be included in the field_and_source_xds."
|
|
470
449
|
)
|
|
471
|
-
xds
|
|
450
|
+
xds = xds.assign_coords(
|
|
451
|
+
{"source_name": "Unknown"}
|
|
452
|
+
) # Need to add this for ps.summary() to work.
|
|
472
453
|
return xds
|
|
473
454
|
|
|
455
|
+
from xradio._utils.list_and_array import check_if_consistent, unique_1d
|
|
456
|
+
|
|
457
|
+
unique_source_id = unique_1d(source_id)
|
|
458
|
+
taql_where = f"where (SOURCE_ID IN [{','.join(map(str, unique_source_id))}]) AND (SPECTRAL_WINDOW_ID = {spectral_window_id})"
|
|
459
|
+
|
|
474
460
|
source_xds = read_generic_table(
|
|
475
461
|
path,
|
|
476
462
|
"SOURCE",
|
|
477
463
|
ignore=["SOURCE_MODEL"], # Trying to read SOURCE_MODEL causes an error.
|
|
478
|
-
taql_where=
|
|
464
|
+
taql_where=taql_where,
|
|
479
465
|
)
|
|
480
466
|
|
|
481
467
|
if len(source_xds.data_vars) == 0: # The source xds is empty.
|
|
482
468
|
logger.warning(
|
|
483
469
|
f"SOURCE table empty for source_id {source_id} and spectral_window_id {spectral_window_id}."
|
|
484
470
|
)
|
|
485
|
-
xds
|
|
471
|
+
xds = xds.assign_coords(
|
|
472
|
+
{"source_name": "Unknown"}
|
|
473
|
+
) # Need to add this for ps.summary() to work.
|
|
486
474
|
return xds
|
|
487
475
|
|
|
488
|
-
assert (
|
|
489
|
-
len(source_xds.source_id) == 1
|
|
490
|
-
), "Can only process source table with a single source_id and spectral_window_id for a given MSv4 partition."
|
|
491
476
|
assert (
|
|
492
477
|
len(source_xds.spectral_window_id) == 1
|
|
493
|
-
), "Can only process source table with a single
|
|
478
|
+
), "Can only process source table with a single spectral_window_id for a given MSv4 partition."
|
|
479
|
+
|
|
480
|
+
# This source table time is not the same as the time in the field_and_source_xds that is derived from the main MSv4 time axis.
|
|
481
|
+
# The source_id maps to the time axis in the field_and_source_xds. That is why "if len(source_id) == 1" is used to check if there should be a time axis.
|
|
494
482
|
assert (
|
|
495
483
|
len(source_xds.time) == 1
|
|
496
484
|
), "Can only process source table with a single time entry for a source_id and spectral_window_id."
|
|
497
|
-
source_xds = source_xds.isel(time=0, source_id=0, spectral_window_id=0)
|
|
498
485
|
|
|
499
|
-
|
|
500
|
-
xds.attrs["code"] = str(source_xds["code"].data)
|
|
486
|
+
source_xds = source_xds.isel(time=0, spectral_window_id=0, drop=True)
|
|
501
487
|
source_column_description = source_xds.attrs["other"]["msv2"]["ctds_attrs"][
|
|
502
488
|
"column_descriptions"
|
|
503
489
|
]
|
|
504
490
|
|
|
491
|
+
# Get source name (the time axis is optional and will probably be required if the partition scheme does not include 'FIELD_ID' or 'SOURCE_ID'.).
|
|
492
|
+
# Note again that this optional time axis has nothing to do with the original time axis in the source table that we drop.
|
|
493
|
+
coords = {}
|
|
494
|
+
if len(source_id) == 1:
|
|
495
|
+
source_xds = source_xds.sel(source_id=source_id[0])
|
|
496
|
+
coords["source_name"] = (
|
|
497
|
+
source_xds["name"].values.item() + "_" + str(source_id[0])
|
|
498
|
+
)
|
|
499
|
+
direction_dims = ["sky_dir_label"]
|
|
500
|
+
# coords["source_id"] = source_id[0]
|
|
501
|
+
else:
|
|
502
|
+
source_xds = source_xds.sel(source_id=source_id)
|
|
503
|
+
coords["source_name"] = (
|
|
504
|
+
"time",
|
|
505
|
+
np.char.add(
|
|
506
|
+
source_xds["name"].data, np.char.add("_", source_id.astype(str))
|
|
507
|
+
),
|
|
508
|
+
)
|
|
509
|
+
direction_dims = ["time", "sky_dir_label"]
|
|
510
|
+
# coords["source_id"] = ("time", source_id)
|
|
511
|
+
|
|
512
|
+
# If ephemeris data is present we ignore the SOURCE_DIRECTION.
|
|
505
513
|
if not is_ephemeris:
|
|
506
514
|
msv4_measure = column_description_casacore_to_msv4_measure(
|
|
507
515
|
source_column_description["DIRECTION"]
|
|
508
516
|
)
|
|
509
517
|
xds["SOURCE_DIRECTION"] = xr.DataArray(
|
|
510
|
-
source_xds["direction"].data, dims=
|
|
518
|
+
source_xds["direction"].data, dims=direction_dims
|
|
511
519
|
)
|
|
512
520
|
xds["SOURCE_DIRECTION"].attrs.update(msv4_measure)
|
|
513
521
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
522
|
+
# Do we have line data:
|
|
523
|
+
if source_xds["num_lines"].data.ndim == 0:
|
|
524
|
+
num_lines = np.array([source_xds["num_lines"].data.item()])
|
|
525
|
+
else:
|
|
526
|
+
num_lines = source_xds["num_lines"].data
|
|
527
|
+
|
|
528
|
+
if any(num_lines > 0):
|
|
521
529
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
530
|
+
if len(source_id) == 1:
|
|
531
|
+
coords_lines = {"line_name": source_xds["transition"].data}
|
|
532
|
+
xds = xds.assign_coords(coords_lines)
|
|
533
|
+
line_dims = ["line_label"]
|
|
534
|
+
else:
|
|
535
|
+
coords_lines = {
|
|
536
|
+
"line_name": (("time", "line_label"), source_xds["transition"].data)
|
|
537
|
+
}
|
|
538
|
+
xds = xds.assign_coords(coords_lines)
|
|
539
|
+
line_dims = ["time", "line_label"]
|
|
526
540
|
|
|
527
541
|
optional_data_variables = {
|
|
528
542
|
"rest_frequency": "LINE_REST_FREQUENCY",
|
|
@@ -533,8 +547,9 @@ def extract_source_info(xds, path, is_ephemeris, source_id, spectral_window_id):
|
|
|
533
547
|
msv4_measure = column_description_casacore_to_msv4_measure(
|
|
534
548
|
source_column_description[generic_name.upper()]
|
|
535
549
|
)
|
|
550
|
+
|
|
536
551
|
xds[msv4_name] = xr.DataArray(
|
|
537
|
-
source_xds[generic_name].data, dims=
|
|
552
|
+
source_xds[generic_name].data, dims=line_dims
|
|
538
553
|
)
|
|
539
554
|
xds[msv4_name].attrs.update(msv4_measure)
|
|
540
555
|
|
|
@@ -546,10 +561,11 @@ def extract_source_info(xds, path, is_ephemeris, source_id, spectral_window_id):
|
|
|
546
561
|
)
|
|
547
562
|
assert (
|
|
548
563
|
False
|
|
549
|
-
), "Doppler table present. Please open an issue on https://github.com/casangi/xradio/issues so that we can
|
|
564
|
+
), "Doppler table present. Please open an issue on https://github.com/casangi/xradio/issues so that we can add support for this."
|
|
550
565
|
except:
|
|
551
566
|
pass
|
|
552
567
|
|
|
568
|
+
xds = xds.assign_coords(coords)
|
|
553
569
|
return xds
|
|
554
570
|
|
|
555
571
|
|
|
@@ -577,47 +593,41 @@ def create_field_info_and_check_ephemeris(
|
|
|
577
593
|
ephemeris_table_name : str
|
|
578
594
|
The name of the ephemeris table.
|
|
579
595
|
"""
|
|
596
|
+
|
|
597
|
+
# Federico do know how to do this taql query?
|
|
598
|
+
unqiue_field_id = unique_1d(
|
|
599
|
+
field_id
|
|
600
|
+
) # field_ids can be repeated so that the time mapping is correct if there are multiple fields. The read_generic_table required unique field_ids.
|
|
601
|
+
taql_where = f"where (ROWID() IN [{','.join(map(str, unqiue_field_id))}])"
|
|
580
602
|
field_xds = read_generic_table(
|
|
581
603
|
in_file,
|
|
582
604
|
"FIELD",
|
|
583
605
|
rename_ids=subt_rename_ids["FIELD"],
|
|
584
|
-
|
|
585
|
-
# print('1****',field_xds)
|
|
586
|
-
# field_xds['field_id'] = np.arange(len(field_xds.field_id))
|
|
587
|
-
|
|
588
|
-
assert len(field_xds.poly_id) == 1, "Polynomial field positions not supported."
|
|
589
|
-
field_xds = field_xds.isel(poly_id=0)
|
|
590
|
-
field_xds = field_xds.sel(field_id=field_id)
|
|
591
|
-
|
|
592
|
-
from xradio._utils.array import check_if_consistent
|
|
593
|
-
|
|
594
|
-
source_id = check_if_consistent(field_xds.source_id, "source_id")
|
|
595
|
-
|
|
596
|
-
# print('source_id', source_id)
|
|
597
|
-
# print(field_xds)
|
|
598
|
-
# print('***')
|
|
599
|
-
# print(field_xds.field_id)
|
|
600
|
-
# print('***')
|
|
601
|
-
# print(field_id)
|
|
602
|
-
|
|
603
|
-
field_and_source_xds.attrs.update(
|
|
604
|
-
{
|
|
605
|
-
"field_name": str(field_xds["name"].data),
|
|
606
|
-
"field_code": str(field_xds["code"].data),
|
|
607
|
-
# "field_id": field_id,
|
|
608
|
-
"source_id": source_id,
|
|
609
|
-
}
|
|
606
|
+
taql_where=taql_where,
|
|
610
607
|
)
|
|
611
608
|
|
|
609
|
+
assert (
|
|
610
|
+
len(field_xds.poly_id) == 1
|
|
611
|
+
), "Polynomial field positions not supported. Please open an issue on https://github.com/casangi/xradio/issues so that we can add support for this."
|
|
612
|
+
field_xds = field_xds.isel(poly_id=0, drop=True)
|
|
613
|
+
# field_xds = field_xds.assign_coords({'field_id':field_xds['field_id'].data})
|
|
614
|
+
field_xds = field_xds.assign_coords({"field_id": unqiue_field_id})
|
|
615
|
+
field_xds = field_xds.sel(field_id=field_id, drop=False)
|
|
616
|
+
source_id = to_np_array(field_xds.source_id.values)
|
|
617
|
+
|
|
612
618
|
ephemeris_table_name = None
|
|
613
619
|
ephemeris_path = None
|
|
614
620
|
is_ephemeris = False
|
|
621
|
+
field_and_source_xds.attrs["is_ephemeris"] = (
|
|
622
|
+
False # If we find a path to the ephemeris table we will set this to True.
|
|
623
|
+
)
|
|
615
624
|
|
|
616
|
-
# Need to check if ephemeris_id is present and if
|
|
625
|
+
# Need to check if ephemeris_id is present and if ephemeris table is present.
|
|
617
626
|
if "ephemeris_id" in field_xds:
|
|
618
627
|
ephemeris_id = check_if_consistent(
|
|
619
628
|
field_xds.ephemeris_id, "ephemeris_id"
|
|
620
629
|
) # int(field_xds["ephemeris_id"].data)
|
|
630
|
+
|
|
621
631
|
if ephemeris_id > -1:
|
|
622
632
|
files = os.listdir(os.path.join(in_file, "FIELD"))
|
|
623
633
|
ephemeris_table_name_start = "EPHEM" + str(ephemeris_id)
|
|
@@ -625,6 +635,7 @@ def create_field_info_and_check_ephemeris(
|
|
|
625
635
|
ephemeris_name_table_index = [
|
|
626
636
|
i for i in range(len(files)) if ephemeris_table_name_start in files[i]
|
|
627
637
|
]
|
|
638
|
+
|
|
628
639
|
assert len(ephemeris_name_table_index) == 1, (
|
|
629
640
|
"More than one ephemeris table which starts with "
|
|
630
641
|
+ ephemeris_table_name_start
|
|
@@ -635,28 +646,12 @@ def create_field_info_and_check_ephemeris(
|
|
|
635
646
|
e_index = ephemeris_name_table_index[0]
|
|
636
647
|
ephemeris_path = os.path.join(in_file, "FIELD")
|
|
637
648
|
ephemeris_table_name = files[e_index]
|
|
649
|
+
field_and_source_xds.attrs["is_ephemeris"] = True
|
|
638
650
|
else:
|
|
639
651
|
logger.warning(
|
|
640
652
|
f"Could not find ephemeris table for field_id {field_id}. Ephemeris information will not be included in the field_and_source_xds."
|
|
641
653
|
)
|
|
642
654
|
|
|
643
|
-
# if is_ephemeris:
|
|
644
|
-
# field_data_variables = {
|
|
645
|
-
# "delay_dir": "FIELD_DELAY_CENTER_OFFSET",
|
|
646
|
-
# "phase_dir": "FIELD_PHASE_CENTER_OFFSET",
|
|
647
|
-
# "reference_dir": "FIELD_REFERENCE_CENTER_OFFSET",
|
|
648
|
-
# }
|
|
649
|
-
# field_measures_type = "sky_coord_offset"
|
|
650
|
-
# field_and_source_xds.attrs["field_and_source_xds_type"] = "ephemeris"
|
|
651
|
-
# else:
|
|
652
|
-
# field_data_variables = {
|
|
653
|
-
# "delay_dir": "FIELD_DELAY_CENTER",
|
|
654
|
-
# "phase_dir": "FIELD_PHASE_CENTER",
|
|
655
|
-
# "reference_dir": "FIELD_REFERENCE_CENTER",
|
|
656
|
-
# }
|
|
657
|
-
# field_measures_type = "sky_coord"
|
|
658
|
-
# field_and_source_xds.attrs["field_and_source_xds_type"] = "standard"
|
|
659
|
-
|
|
660
655
|
if is_single_dish:
|
|
661
656
|
field_data_variables = {
|
|
662
657
|
"reference_dir": "FIELD_REFERENCE_CENTER",
|
|
@@ -674,14 +669,21 @@ def create_field_info_and_check_ephemeris(
|
|
|
674
669
|
coords["sky_dir_label"] = ["ra", "dec"]
|
|
675
670
|
field_column_description = field_xds.attrs["other"]["msv2"]["ctds_attrs"][
|
|
676
671
|
"column_descriptions"
|
|
677
|
-
]
|
|
672
|
+
]
|
|
678
673
|
|
|
679
674
|
coords = {}
|
|
680
|
-
|
|
675
|
+
# field_times is the same as the time axis in the main MSv4 dataset and is used if more than one field is present.
|
|
681
676
|
if field_times is not None:
|
|
682
677
|
coords["time"] = field_times
|
|
683
678
|
dims = ["time", "sky_dir_label"]
|
|
679
|
+
coords["field_name"] = (
|
|
680
|
+
"time",
|
|
681
|
+
np.char.add(field_xds["name"].data, np.char.add("_", field_id.astype(str))),
|
|
682
|
+
)
|
|
683
|
+
# coords["field_id"] = ("time", field_id)
|
|
684
684
|
else:
|
|
685
|
+
coords["field_name"] = field_xds["name"].values.item() + "_" + str(field_id)
|
|
686
|
+
# coords["field_id"] = field_id
|
|
685
687
|
dims = ["sky_dir_label"]
|
|
686
688
|
|
|
687
689
|
for generic_name, msv4_name in field_data_variables.items():
|
|
@@ -707,4 +709,4 @@ def create_field_info_and_check_ephemeris(
|
|
|
707
709
|
field_and_source_xds[msv4_name].attrs["type"] = field_measures_type
|
|
708
710
|
|
|
709
711
|
field_and_source_xds = field_and_source_xds.assign_coords(coords)
|
|
710
|
-
return field_and_source_xds, ephemeris_path, ephemeris_table_name
|
|
712
|
+
return field_and_source_xds, ephemeris_path, ephemeris_table_name, source_id
|