xradio 1.0.2__py3-none-any.whl → 1.1.0__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/_casacore/casacore_from_casatools.py +1 -1
- xradio/_utils/dict_helpers.py +38 -7
- xradio/_utils/list_and_array.py +26 -3
- xradio/_utils/schema.py +44 -0
- xradio/_utils/xarray_helpers.py +63 -0
- xradio/_utils/zarr/common.py +4 -2
- xradio/image/__init__.py +4 -2
- xradio/image/_util/_casacore/common.py +2 -1
- xradio/image/_util/_casacore/xds_from_casacore.py +105 -51
- xradio/image/_util/_casacore/xds_to_casacore.py +117 -52
- xradio/image/_util/_fits/xds_from_fits.py +124 -36
- xradio/image/_util/_zarr/common.py +0 -1
- xradio/image/_util/casacore.py +133 -16
- xradio/image/_util/common.py +6 -5
- xradio/image/_util/image_factory.py +466 -27
- xradio/image/image.py +72 -100
- xradio/image/image_xds.py +262 -0
- xradio/image/schema.py +85 -0
- xradio/measurement_set/__init__.py +5 -4
- xradio/measurement_set/_utils/_msv2/_tables/read.py +7 -3
- xradio/measurement_set/_utils/_msv2/conversion.py +6 -9
- xradio/measurement_set/_utils/_msv2/create_field_and_source_xds.py +1 -0
- xradio/measurement_set/_utils/_msv2/msv4_sub_xdss.py +1 -1
- xradio/measurement_set/_utils/_utils/interpolate.py +5 -0
- xradio/measurement_set/_utils/_utils/partition_attrs.py +0 -1
- xradio/measurement_set/convert_msv2_to_processing_set.py +9 -9
- xradio/measurement_set/load_processing_set.py +2 -2
- xradio/measurement_set/measurement_set_xdt.py +83 -93
- xradio/measurement_set/open_processing_set.py +1 -1
- xradio/measurement_set/processing_set_xdt.py +33 -26
- xradio/schema/check.py +70 -19
- xradio/schema/common.py +0 -1
- xradio/testing/__init__.py +0 -0
- xradio/testing/_utils/__template__.py +58 -0
- xradio/testing/measurement_set/__init__.py +58 -0
- xradio/testing/measurement_set/checker.py +131 -0
- xradio/testing/measurement_set/io.py +22 -0
- xradio/testing/measurement_set/msv2_io.py +1854 -0
- {xradio-1.0.2.dist-info → xradio-1.1.0.dist-info}/METADATA +64 -23
- xradio-1.1.0.dist-info/RECORD +75 -0
- {xradio-1.0.2.dist-info → xradio-1.1.0.dist-info}/WHEEL +1 -1
- xradio-1.0.2.dist-info/RECORD +0 -66
- {xradio-1.0.2.dist-info → xradio-1.1.0.dist-info}/licenses/LICENSE.txt +0 -0
- {xradio-1.0.2.dist-info → xradio-1.1.0.dist-info}/top_level.txt +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import copy
|
|
2
2
|
import os
|
|
3
|
+
import time
|
|
3
4
|
|
|
4
5
|
import dask.array as da
|
|
5
6
|
import numpy as np
|
|
@@ -12,9 +13,19 @@ try:
|
|
|
12
13
|
except ImportError:
|
|
13
14
|
import xradio._utils._casacore.casacore_from_casatools as tables
|
|
14
15
|
|
|
15
|
-
from .common import
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
from xradio.image._util._casacore.common import (
|
|
17
|
+
_image_flag,
|
|
18
|
+
_beam_fit_params,
|
|
19
|
+
_create_new_image,
|
|
20
|
+
_object_name,
|
|
21
|
+
_pointing_center,
|
|
22
|
+
)
|
|
23
|
+
from xradio.image._util.common import (
|
|
24
|
+
_aperture_or_sky,
|
|
25
|
+
_compute_sky_reference_pixel,
|
|
26
|
+
_doppler_types,
|
|
27
|
+
)
|
|
28
|
+
from xradio._utils._casacore.tables import open_table_rw
|
|
18
29
|
|
|
19
30
|
|
|
20
31
|
def _compute_direction_dict(xds: xr.Dataset) -> dict:
|
|
@@ -23,31 +34,35 @@ def _compute_direction_dict(xds: xr.Dataset) -> dict:
|
|
|
23
34
|
for a CASA image coordinate system
|
|
24
35
|
"""
|
|
25
36
|
direction = {}
|
|
26
|
-
|
|
37
|
+
|
|
38
|
+
xds_dir = xds.attrs["coordinate_system_info"]
|
|
27
39
|
direction["_axes_sizes"] = np.array(
|
|
28
40
|
[xds.sizes[dim] for dim in ("l", "m")], dtype=np.int32
|
|
29
41
|
)
|
|
30
42
|
direction["_image_axes"] = np.array([2, 3], dtype=np.int32)
|
|
31
|
-
direction["system"] = xds_dir["
|
|
43
|
+
direction["system"] = xds_dir["reference_direction"]["attrs"]["equinox"].upper()
|
|
32
44
|
if direction["system"] == "J2000.0":
|
|
33
45
|
direction["system"] = "J2000"
|
|
34
46
|
direction["projection"] = xds_dir["projection"]
|
|
35
47
|
direction["projection_parameters"] = xds_dir["projection_parameters"]
|
|
36
48
|
direction["units"] = [
|
|
37
|
-
xds_dir["
|
|
38
|
-
xds_dir["
|
|
49
|
+
xds_dir["reference_direction"]["attrs"]["units"],
|
|
50
|
+
xds_dir["reference_direction"]["attrs"]["units"],
|
|
39
51
|
]
|
|
40
|
-
direction["crval"] = np.array(xds_dir["
|
|
52
|
+
direction["crval"] = np.array(xds_dir["reference_direction"]["data"])
|
|
41
53
|
direction["cdelt"] = np.array((xds.l[1] - xds.l[0], xds.m[1] - xds.m[0]))
|
|
42
54
|
direction["crpix"] = _compute_sky_reference_pixel(xds)
|
|
43
|
-
direction["pc"] = np.array(xds_dir["
|
|
55
|
+
direction["pc"] = np.array(xds_dir["pixel_coordinate_transformation_matrix"])
|
|
44
56
|
direction["axes"] = ["Right Ascension", "Declination"]
|
|
45
57
|
direction["conversionSystem"] = direction["system"]
|
|
46
|
-
for s in ["longpole", "latpole"]:
|
|
58
|
+
for i, s in enumerate(["longpole", "latpole"]):
|
|
47
59
|
m = "lonpole" if s == "longpole" else s
|
|
48
60
|
# lonpole, latpole are numerical values in degrees in casa images
|
|
49
61
|
direction[s] = float(
|
|
50
|
-
Angle(
|
|
62
|
+
Angle(
|
|
63
|
+
str(xds_dir["native_pole_direction"]["data"][i])
|
|
64
|
+
+ xds_dir["native_pole_direction"]["attrs"]["units"]
|
|
65
|
+
).deg
|
|
51
66
|
)
|
|
52
67
|
return direction
|
|
53
68
|
|
|
@@ -205,50 +220,94 @@ def _coord_dict_from_xds(xds: xr.Dataset) -> dict:
|
|
|
205
220
|
|
|
206
221
|
|
|
207
222
|
def _history_from_xds(xds: xr.Dataset, image: str) -> None:
|
|
208
|
-
|
|
223
|
+
"""
|
|
224
|
+
Write history from xds attributes to CASA image logtable.
|
|
225
|
+
|
|
226
|
+
Parameters
|
|
227
|
+
----------
|
|
228
|
+
xds : xr.Dataset
|
|
229
|
+
Dataset with potential history attribute (stored as dict).
|
|
230
|
+
image : str
|
|
231
|
+
Path to the CASA image.
|
|
232
|
+
|
|
233
|
+
Notes
|
|
234
|
+
-----
|
|
235
|
+
History is stored in data variable attributes (e.g., SKY.attrs['history']),
|
|
236
|
+
not in the main dataset attributes.
|
|
237
|
+
"""
|
|
238
|
+
# Check if history exists in data variable attributes (SKY, APERTURE, etc.)
|
|
239
|
+
history_dict = None
|
|
240
|
+
for var_name in xds.data_vars:
|
|
241
|
+
if "history" in xds[var_name].attrs:
|
|
242
|
+
history_dict = xds[var_name].attrs["history"]
|
|
243
|
+
break
|
|
244
|
+
|
|
245
|
+
# Also check dataset-level attributes as a fallback
|
|
246
|
+
if history_dict is None and "history" in xds.attrs:
|
|
247
|
+
history_dict = xds.attrs["history"]
|
|
248
|
+
|
|
249
|
+
if history_dict is None or not isinstance(history_dict, dict):
|
|
250
|
+
return
|
|
251
|
+
|
|
252
|
+
# Convert dict back to xr.Dataset to access data
|
|
253
|
+
try:
|
|
254
|
+
history_xds = xr.Dataset.from_dict(history_dict)
|
|
255
|
+
except Exception:
|
|
256
|
+
# If conversion fails, history dict may be malformed, skip writing
|
|
257
|
+
return
|
|
258
|
+
|
|
259
|
+
# Check for row in both dims and coords (it could be either depending on the xarray version)
|
|
260
|
+
nrows = 0
|
|
261
|
+
if "row" in history_xds.dims:
|
|
262
|
+
nrows = history_xds.sizes["row"]
|
|
263
|
+
elif "row" in history_xds.coords:
|
|
264
|
+
nrows = len(history_xds.row)
|
|
265
|
+
elif "row" in history_xds.data_vars:
|
|
266
|
+
# row might also be a data variable
|
|
267
|
+
nrows = len(history_xds.row)
|
|
268
|
+
|
|
209
269
|
if nrows > 0:
|
|
210
270
|
# TODO need to implement nrows == 0 case
|
|
211
271
|
with open_table_rw(os.sep.join([image, "logtable"])) as tb:
|
|
212
272
|
tb.addrows(nrows + 1)
|
|
213
273
|
for c in ["TIME", "PRIORITY", "MESSAGE", "LOCATION", "OBJECT_ID"]:
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
274
|
+
if c in history_xds.data_vars or c in history_xds.coords:
|
|
275
|
+
vals = history_xds[c].values
|
|
276
|
+
if c == "TIME":
|
|
277
|
+
k = time.time() + 40587 * 86400
|
|
278
|
+
elif c == "PRIORITY":
|
|
279
|
+
k = "INFO"
|
|
280
|
+
elif c == "MESSAGE":
|
|
281
|
+
k = (
|
|
282
|
+
"Wrote xds to "
|
|
283
|
+
+ os.path.basename(image)
|
|
284
|
+
+ " using cngi_io.xds_to_casa_image_2()"
|
|
285
|
+
)
|
|
286
|
+
elif c == "LOCATION":
|
|
287
|
+
k = "cngi_io.xds_to_casa_image_2"
|
|
288
|
+
elif c == "OBJECT_ID":
|
|
289
|
+
k = ""
|
|
290
|
+
vals = np.append(vals, k)
|
|
291
|
+
tb.putcol(c, vals)
|
|
231
292
|
|
|
232
293
|
|
|
233
294
|
def _imageinfo_dict_from_xds(xds: xr.Dataset) -> dict:
|
|
234
295
|
ii = {}
|
|
235
296
|
ap_sky = _aperture_or_sky(xds)
|
|
236
|
-
ii["imagetype"] =
|
|
237
|
-
xds[ap_sky].attrs["image_type"] if "image_type" in xds[ap_sky].attrs else ""
|
|
238
|
-
)
|
|
297
|
+
ii["imagetype"] = xds[ap_sky].attrs["type"] if "type" in xds[ap_sky].attrs else ""
|
|
239
298
|
ii["objectname"] = (
|
|
240
299
|
xds[ap_sky].attrs[_object_name] if _object_name in xds[ap_sky].attrs else ""
|
|
241
300
|
)
|
|
242
|
-
if "
|
|
301
|
+
if "BEAM_FIT_PARAMS" in xds.data_vars:
|
|
243
302
|
# multi beam
|
|
244
303
|
pp = {}
|
|
245
304
|
pp["nChannels"] = xds.sizes["frequency"]
|
|
246
305
|
pp["nStokes"] = xds.sizes["polarization"]
|
|
247
306
|
|
|
248
|
-
bu = xds.
|
|
307
|
+
bu = xds.BEAM_FIT_PARAMS.attrs["units"]
|
|
249
308
|
chan = 0
|
|
250
309
|
polarization = 0
|
|
251
|
-
bv = xds.
|
|
310
|
+
bv = xds.BEAM_FIT_PARAMS.values
|
|
252
311
|
for i in range(pp["nChannels"] * pp["nStokes"]):
|
|
253
312
|
bp = bv[0][chan][polarization][:]
|
|
254
313
|
b = {
|
|
@@ -281,7 +340,9 @@ def _imageinfo_dict_from_xds(xds: xr.Dataset) -> dict:
|
|
|
281
340
|
|
|
282
341
|
|
|
283
342
|
def _write_casa_data(xds: xr.Dataset, image_full_path: str) -> None:
|
|
343
|
+
|
|
284
344
|
sky_ap = _aperture_or_sky(xds)
|
|
345
|
+
|
|
285
346
|
if xds[sky_ap].shape[0] != 1:
|
|
286
347
|
raise RuntimeError("XDS can only be converted if it has exactly one time plane")
|
|
287
348
|
trans_coords = (
|
|
@@ -290,9 +351,8 @@ def _write_casa_data(xds: xr.Dataset, image_full_path: str) -> None:
|
|
|
290
351
|
else ("frequency", "polarization", "v", "u")
|
|
291
352
|
)
|
|
292
353
|
casa_image_shape = xds[sky_ap].isel(time=0).transpose(*trans_coords).shape[::-1]
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
)
|
|
354
|
+
flag = xds[sky_ap].attrs[_image_flag] if _image_flag in xds[sky_ap].attrs else ""
|
|
355
|
+
# TODO this all needs to be refactored to use flag rather than mask
|
|
296
356
|
masks = []
|
|
297
357
|
masks_rec = {}
|
|
298
358
|
mask_rec = {
|
|
@@ -311,10 +371,14 @@ def _write_casa_data(xds: xr.Dataset, image_full_path: str) -> None:
|
|
|
311
371
|
}
|
|
312
372
|
for m in xds.data_vars:
|
|
313
373
|
attrs = xds[m].attrs
|
|
314
|
-
if "
|
|
374
|
+
if "type" in attrs and attrs["type"] in ("mask", "flag"):
|
|
315
375
|
masks_rec[m] = mask_rec
|
|
316
376
|
masks_rec[m]["mask"] = f"Table: {os.sep.join([image_full_path, m])}"
|
|
317
377
|
masks.append(m)
|
|
378
|
+
if flag and flag in xds.data_vars and flag not in masks:
|
|
379
|
+
masks_rec[flag] = mask_rec
|
|
380
|
+
masks_rec[flag]["mask"] = f"Table: {os.sep.join([image_full_path, flag])}"
|
|
381
|
+
masks.append(flag)
|
|
318
382
|
myvars = [sky_ap]
|
|
319
383
|
myvars.extend(masks)
|
|
320
384
|
# xr.apply_ufunc seems to like stripping attributes from xds and its coordinates,
|
|
@@ -327,15 +391,15 @@ def _write_casa_data(xds: xr.Dataset, image_full_path: str) -> None:
|
|
|
327
391
|
do_mask_nans = False
|
|
328
392
|
there_are_nans = nan_mask.any()
|
|
329
393
|
if there_are_nans:
|
|
330
|
-
|
|
331
|
-
if not
|
|
394
|
+
has_flag = bool(flag)
|
|
395
|
+
if not has_flag:
|
|
332
396
|
do_mask_nans = True
|
|
333
397
|
else:
|
|
334
|
-
|
|
335
|
-
da.logical_not, xds[
|
|
398
|
+
notted_flag = xr.apply_ufunc(
|
|
399
|
+
da.logical_not, xds[flag].copy(deep=True), dask="allowed"
|
|
336
400
|
)
|
|
337
401
|
some_nans_are_not_already_masked = xr.apply_ufunc(
|
|
338
|
-
da.logical_and, nan_mask,
|
|
402
|
+
da.logical_and, nan_mask, notted_flag, dask="allowed"
|
|
339
403
|
)
|
|
340
404
|
do_mask_nans = some_nans_are_not_already_masked.any()
|
|
341
405
|
if do_mask_nans:
|
|
@@ -350,8 +414,8 @@ def _write_casa_data(xds: xr.Dataset, image_full_path: str) -> None:
|
|
|
350
414
|
] = f"Table: {os.sep.join([image_full_path, mask_name])}"
|
|
351
415
|
masks.append(mask_name)
|
|
352
416
|
arr_masks[mask_name] = nan_mask
|
|
353
|
-
if
|
|
354
|
-
mask_name = f"mask_xds_nans_or_{
|
|
417
|
+
if flag:
|
|
418
|
+
mask_name = f"mask_xds_nans_or_{flag}"
|
|
355
419
|
i = 0
|
|
356
420
|
while mask_name in masks:
|
|
357
421
|
mask_name = f"mask_xds_nans{i}"
|
|
@@ -365,21 +429,22 @@ def _write_casa_data(xds: xr.Dataset, image_full_path: str) -> None:
|
|
|
365
429
|
# apparent reason, so make a copy of the xds to run it on
|
|
366
430
|
arr_masks[mask_name] = xr.apply_ufunc(
|
|
367
431
|
da.logical_or,
|
|
368
|
-
xds[
|
|
432
|
+
xds[flag].copy(deep=True),
|
|
369
433
|
nan_mask,
|
|
370
434
|
dask="allowed",
|
|
371
435
|
)
|
|
372
|
-
|
|
436
|
+
flag = mask_name
|
|
373
437
|
data_type = "complex" if "u" in xds.coords else "float"
|
|
374
|
-
|
|
438
|
+
|
|
439
|
+
_write_initial_image(xds, image_full_path, flag, casa_image_shape[::-1])
|
|
375
440
|
for v in myvars:
|
|
376
|
-
_write_pixels(v,
|
|
441
|
+
_write_pixels(v, flag, image_full_path, xds)
|
|
377
442
|
for name, v in arr_masks.items():
|
|
378
|
-
_write_pixels(name,
|
|
443
|
+
_write_pixels(name, flag, image_full_path, xds, v)
|
|
379
444
|
if masks:
|
|
380
445
|
with open_table_rw(image_full_path) as tb:
|
|
381
446
|
tb.putkeyword("masks", masks_rec)
|
|
382
|
-
tb.putkeyword("Image_defaultmask",
|
|
447
|
+
tb.putkeyword("Image_defaultmask", flag)
|
|
383
448
|
|
|
384
449
|
|
|
385
450
|
def _write_initial_image(
|
|
@@ -13,12 +13,14 @@ from astropy.time import Time
|
|
|
13
13
|
|
|
14
14
|
from xradio._utils.coord_math import _deg_to_rad
|
|
15
15
|
from xradio._utils.dict_helpers import (
|
|
16
|
+
make_direction_location_dict,
|
|
16
17
|
make_quantity,
|
|
17
18
|
make_spectral_coord_reference_dict,
|
|
18
19
|
make_skycoord_dict,
|
|
19
20
|
make_time_measure_dict,
|
|
20
21
|
)
|
|
21
22
|
|
|
23
|
+
from xradio._utils.list_and_array import to_python_type
|
|
22
24
|
from xradio.measurement_set._utils._utils.stokes_types import stokes_types
|
|
23
25
|
from xradio.image._util.common import (
|
|
24
26
|
_compute_linear_world_values,
|
|
@@ -34,6 +36,11 @@ from xradio.image._util.common import (
|
|
|
34
36
|
_l_m_attr_notes,
|
|
35
37
|
)
|
|
36
38
|
|
|
39
|
+
from xradio.image._util._casacore.common import (
|
|
40
|
+
_image_flag,
|
|
41
|
+
_beam_fit_params,
|
|
42
|
+
)
|
|
43
|
+
|
|
37
44
|
|
|
38
45
|
def _fits_image_to_xds(
|
|
39
46
|
img_full_path: str,
|
|
@@ -41,6 +48,7 @@ def _fits_image_to_xds(
|
|
|
41
48
|
verbose: bool,
|
|
42
49
|
do_sky_coords: bool,
|
|
43
50
|
compute_mask: bool,
|
|
51
|
+
image_type: str = "SKY",
|
|
44
52
|
) -> dict:
|
|
45
53
|
"""
|
|
46
54
|
compute_mask : bool, optional
|
|
@@ -65,13 +73,27 @@ def _fits_image_to_xds(
|
|
|
65
73
|
sphr_dims = helpers["sphr_dims"]
|
|
66
74
|
ary = _read_image_array(img_full_path, chunks, helpers, verbose)
|
|
67
75
|
dim_order = _get_xds_dim_order(sphr_dims)
|
|
68
|
-
xds = _add_sky_or_aperture(
|
|
76
|
+
xds = _add_sky_or_aperture(
|
|
77
|
+
xds, ary, dim_order, header, helpers, sphr_dims, image_type
|
|
78
|
+
)
|
|
69
79
|
xds.attrs = attrs
|
|
70
80
|
xds = _add_coord_attrs(xds, helpers)
|
|
71
81
|
if helpers["has_multibeam"]:
|
|
72
|
-
xds = _do_multibeam(xds, img_full_path)
|
|
82
|
+
xds = _do_multibeam(xds, img_full_path, image_type=image_type)
|
|
83
|
+
xds[image_type.upper()].attrs[_beam_fit_params] = (
|
|
84
|
+
"BEAM_FIT_PARAMS_" + image_type.upper()
|
|
85
|
+
)
|
|
86
|
+
xds["BEAM_FIT_PARAMS_" + image_type.upper()].attrs["type"] = (
|
|
87
|
+
"beam_fit_params_" + image_type.lower()
|
|
88
|
+
)
|
|
73
89
|
elif "beam" in helpers and helpers["beam"] is not None:
|
|
74
|
-
xds = _add_beam(xds, helpers)
|
|
90
|
+
xds = _add_beam(xds, helpers, image_type)
|
|
91
|
+
xds[image_type.upper()].attrs[_beam_fit_params] = (
|
|
92
|
+
"BEAM_FIT_PARAMS_" + image_type.upper()
|
|
93
|
+
)
|
|
94
|
+
xds["BEAM_FIT_PARAMS_" + image_type.upper()].attrs["type"] = (
|
|
95
|
+
"beam_fit_params_" + image_type.lower()
|
|
96
|
+
)
|
|
75
97
|
return xds
|
|
76
98
|
|
|
77
99
|
|
|
@@ -116,7 +138,7 @@ def _add_vel_attrs(xds: xr.Dataset, helpers: dict) -> xr.Dataset:
|
|
|
116
138
|
vel_coord = xds.coords["velocity"]
|
|
117
139
|
meta = {"units": "m/s"}
|
|
118
140
|
if helpers["has_freq"]:
|
|
119
|
-
meta["doppler_type"] = helpers.get("doppler", "
|
|
141
|
+
meta["doppler_type"] = helpers.get("doppler", "radio")
|
|
120
142
|
else:
|
|
121
143
|
meta["doppler_type"] = _doppler_types[0]
|
|
122
144
|
meta["type"] = "doppler"
|
|
@@ -151,7 +173,7 @@ def _is_freq_like(v: str) -> bool:
|
|
|
151
173
|
return v.startswith("FREQ") or v == "VOPT" or v == "VRAD"
|
|
152
174
|
|
|
153
175
|
|
|
154
|
-
def
|
|
176
|
+
def _xds_coordinate_system_info_attrs_from_header(helpers: dict, header) -> dict:
|
|
155
177
|
# helpers is modified in place, headers is not modified
|
|
156
178
|
t_axes = helpers["t_axes"]
|
|
157
179
|
p0 = header[f"CTYPE{t_axes[0]}"][-3:]
|
|
@@ -161,8 +183,8 @@ def _xds_direction_attrs_from_header(helpers: dict, header) -> dict:
|
|
|
161
183
|
f"Projections for direction axes ({p0}, {p1}) differ, but they "
|
|
162
184
|
"must be the same"
|
|
163
185
|
)
|
|
164
|
-
|
|
165
|
-
|
|
186
|
+
coordinate_system_info = {}
|
|
187
|
+
coordinate_system_info["projection"] = p0
|
|
166
188
|
helpers["projection"] = p0
|
|
167
189
|
ref_sys = header["RADESYS"]
|
|
168
190
|
ref_eqx = None if ref_sys.upper() == "ICRS" else header["EQUINOX"]
|
|
@@ -171,7 +193,9 @@ def _xds_direction_attrs_from_header(helpers: dict, header) -> dict:
|
|
|
171
193
|
helpers["ref_sys"] = ref_sys
|
|
172
194
|
helpers["ref_eqx"] = ref_eqx
|
|
173
195
|
# fits does not support conversion frames
|
|
174
|
-
|
|
196
|
+
coordinate_system_info["reference_direction"] = make_skycoord_dict(
|
|
197
|
+
[0.0, 0.0], units="rad", frame=ref_sys
|
|
198
|
+
)
|
|
175
199
|
dir_axes = helpers["dir_axes"]
|
|
176
200
|
ddata = []
|
|
177
201
|
dunits = []
|
|
@@ -182,15 +206,20 @@ def _xds_direction_attrs_from_header(helpers: dict, header) -> dict:
|
|
|
182
206
|
# direction["reference"]["value"][i] = x.value
|
|
183
207
|
x = helpers["cdelt"][i] * u.Unit(_get_unit(helpers["cunit"][i]))
|
|
184
208
|
dunits.append("rad")
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
direction["reference"]["attrs"]["equinox"] = ref_eqx.lower()
|
|
188
|
-
direction["latpole"] = make_quantity(
|
|
189
|
-
header["LATPOLE"] * _deg_to_rad, "rad", dims=["l", "m"]
|
|
209
|
+
coordinate_system_info["reference_direction"] = make_skycoord_dict(
|
|
210
|
+
ddata, units=dunits, frame=ref_sys
|
|
190
211
|
)
|
|
191
|
-
|
|
192
|
-
|
|
212
|
+
if ref_eqx is not None:
|
|
213
|
+
coordinate_system_info["reference_direction"]["attrs"][
|
|
214
|
+
"equinox"
|
|
215
|
+
] = ref_eqx.lower()
|
|
216
|
+
|
|
217
|
+
coordinate_system_info["native_pole_direction"] = make_direction_location_dict(
|
|
218
|
+
[header["LONPOLE"] * _deg_to_rad, header["LATPOLE"] * _deg_to_rad],
|
|
219
|
+
units="rad",
|
|
220
|
+
frame="native_projection",
|
|
193
221
|
)
|
|
222
|
+
|
|
194
223
|
pc = np.zeros([2, 2])
|
|
195
224
|
for i in (0, 1):
|
|
196
225
|
for j in (0, 1):
|
|
@@ -205,10 +234,61 @@ def _xds_direction_attrs_from_header(helpers: dict, header) -> dict:
|
|
|
205
234
|
f"Could not find PC{dir_axes[i]+1}_{dir_axes[j]+1} or "
|
|
206
235
|
f"PC0{dir_axes[i]+1}_0{dir_axes[j]+1} in FITS header"
|
|
207
236
|
)
|
|
208
|
-
|
|
237
|
+
coordinate_system_info["pixel_coordinate_transformation_matrix"] = to_python_type(
|
|
238
|
+
pc
|
|
239
|
+
)
|
|
209
240
|
# Is there really no fits header parameter for projection_parameters?
|
|
210
|
-
|
|
211
|
-
return
|
|
241
|
+
coordinate_system_info["projection_parameters"] = [0.0, 0.0]
|
|
242
|
+
return coordinate_system_info
|
|
243
|
+
|
|
244
|
+
# direction = {}
|
|
245
|
+
# direction["projection"] = p0
|
|
246
|
+
# helpers["projection"] = p0
|
|
247
|
+
# ref_sys = header["RADESYS"]
|
|
248
|
+
# ref_eqx = None if ref_sys.upper() == "ICRS" else header["EQUINOX"]
|
|
249
|
+
# if ref_sys == "FK5" and ref_eqx == 2000:
|
|
250
|
+
# ref_eqx = "J2000.0"
|
|
251
|
+
# helpers["ref_sys"] = ref_sys
|
|
252
|
+
# helpers["ref_eqx"] = ref_eqx
|
|
253
|
+
# # fits does not support conversion frames
|
|
254
|
+
# direction["reference"] = make_skycoord_dict([0.0, 0.0], units="rad", frame=ref_sys)
|
|
255
|
+
# dir_axes = helpers["dir_axes"]
|
|
256
|
+
# ddata = []
|
|
257
|
+
# dunits = []
|
|
258
|
+
# for i in dir_axes:
|
|
259
|
+
# x = helpers["crval"][i] * u.Unit(_get_unit(helpers["cunit"][i]))
|
|
260
|
+
# x = x.to("rad")
|
|
261
|
+
# ddata.append(x.value)
|
|
262
|
+
# # direction["reference"]["value"][i] = x.value
|
|
263
|
+
# x = helpers["cdelt"][i] * u.Unit(_get_unit(helpers["cunit"][i]))
|
|
264
|
+
# dunits.append("rad")
|
|
265
|
+
# direction["reference"] = make_skycoord_dict(ddata, units=dunits, frame=ref_sys)
|
|
266
|
+
# if ref_eqx is not None:
|
|
267
|
+
# direction["reference"]["attrs"]["equinox"] = ref_eqx.lower()
|
|
268
|
+
# direction["latpole"] = make_quantity(
|
|
269
|
+
# header["LATPOLE"] * _deg_to_rad, "rad", dims=["l", "m"]
|
|
270
|
+
# )
|
|
271
|
+
# direction["lonpole"] = make_quantity(
|
|
272
|
+
# header["LONPOLE"] * _deg_to_rad, "rad", dims=["l", "m"]
|
|
273
|
+
# )
|
|
274
|
+
# pc = np.zeros([2, 2])
|
|
275
|
+
# for i in (0, 1):
|
|
276
|
+
# for j in (0, 1):
|
|
277
|
+
# # dir_axes are now 0-based, but fits needs 1-based
|
|
278
|
+
# try:
|
|
279
|
+
# pc[i][j] = header[f"PC{dir_axes[i]+1}_{dir_axes[j]+1}"]
|
|
280
|
+
# except KeyError:
|
|
281
|
+
# try:
|
|
282
|
+
# pc[i][j] = header[f"PC0{dir_axes[i]+1}_0{dir_axes[j]+1}"]
|
|
283
|
+
# except KeyError:
|
|
284
|
+
# raise RuntimeError(
|
|
285
|
+
# f"Could not find PC{dir_axes[i]+1}_{dir_axes[j]+1} or "
|
|
286
|
+
# f"PC0{dir_axes[i]+1}_0{dir_axes[j]+1} in FITS header"
|
|
287
|
+
# )
|
|
288
|
+
# direction["pc"] = pc
|
|
289
|
+
# # Is there really no fits header parameter for projection_parameters?
|
|
290
|
+
# direction["projection_parameters"] = np.array([0.0, 0.0])
|
|
291
|
+
# return direction
|
|
212
292
|
|
|
213
293
|
|
|
214
294
|
def _fits_header_c_values_to_metadata(helpers: dict, header) -> None:
|
|
@@ -258,7 +338,7 @@ def _get_telescope_metadata(helpers: dict, header) -> dict:
|
|
|
258
338
|
"type": "location",
|
|
259
339
|
"units": "rad",
|
|
260
340
|
},
|
|
261
|
-
"data":
|
|
341
|
+
"data": [long, lat],
|
|
262
342
|
"dims": ["ellipsoid_dir_label"],
|
|
263
343
|
"coords": {
|
|
264
344
|
"ellipsoid_dir_label": {
|
|
@@ -276,7 +356,7 @@ def _get_telescope_metadata(helpers: dict, header) -> dict:
|
|
|
276
356
|
"type": "location",
|
|
277
357
|
"units": "m",
|
|
278
358
|
},
|
|
279
|
-
"data":
|
|
359
|
+
"data": [r],
|
|
280
360
|
"dims": ["ellipsoid_dis_label"],
|
|
281
361
|
"coords": {
|
|
282
362
|
"ellipsoid_dis_label": {
|
|
@@ -465,7 +545,9 @@ def _fits_header_to_xds_attrs(
|
|
|
465
545
|
else:
|
|
466
546
|
raise RuntimeError("Could not find both direction axes")
|
|
467
547
|
if dir_axes is not None:
|
|
468
|
-
attrs["
|
|
548
|
+
attrs["coordinate_system_info"] = _xds_coordinate_system_info_attrs_from_header(
|
|
549
|
+
helpers, header
|
|
550
|
+
)
|
|
469
551
|
helpers["has_mask"] = False
|
|
470
552
|
if compute_mask:
|
|
471
553
|
# 🧠 Why the primary.data reference here is Safe (does not cause
|
|
@@ -527,7 +609,7 @@ def _fits_header_to_xds_attrs(
|
|
|
527
609
|
data=Time(header["DATE-OBS"], format="isot").mjd,
|
|
528
610
|
units=["d"],
|
|
529
611
|
scale=header["TIMESYS"],
|
|
530
|
-
time_format="
|
|
612
|
+
time_format="mjd",
|
|
531
613
|
)
|
|
532
614
|
|
|
533
615
|
# TODO complete _make_history_xds when spec has been finalized
|
|
@@ -608,7 +690,8 @@ def _create_coords(
|
|
|
608
690
|
else:
|
|
609
691
|
# Fourier image
|
|
610
692
|
coords["u"], coords["v"] = _get_uv_values(helpers)
|
|
611
|
-
coords["
|
|
693
|
+
coords["beam_params_label"] = ["major", "minor", "pa"]
|
|
694
|
+
|
|
612
695
|
xds = xr.Dataset(coords=coords)
|
|
613
696
|
return xds
|
|
614
697
|
|
|
@@ -698,7 +781,7 @@ def _get_velocity_values(helpers: dict) -> list:
|
|
|
698
781
|
# FIXME change namee, even if there is only a single beam, we make a
|
|
699
782
|
# multi beam array using it. If we have a beam, it will always be
|
|
700
783
|
# "mutltibeam" is name is redundant and confusing
|
|
701
|
-
def _do_multibeam(xds: xr.Dataset, imname: str) -> xr.Dataset:
|
|
784
|
+
def _do_multibeam(xds: xr.Dataset, imname: str, image_type: str = "SKY") -> xr.Dataset:
|
|
702
785
|
"""Only run if we are sure there are multiple beams"""
|
|
703
786
|
hdulist = fits.open(imname)
|
|
704
787
|
for hdu in hdulist:
|
|
@@ -720,31 +803,33 @@ def _do_multibeam(xds: xr.Dataset, imname: str) -> xr.Dataset:
|
|
|
720
803
|
beam_array[:, :, :, i] = (
|
|
721
804
|
(beam_array[:, :, :, i] * units[i]).to("rad").value
|
|
722
805
|
)
|
|
723
|
-
return _create_beam_data_var(xds, beam_array)
|
|
806
|
+
return _create_beam_data_var(xds, beam_array, image_type)
|
|
724
807
|
raise RuntimeError(
|
|
725
808
|
"It looks like there should be a BEAMS table but no "
|
|
726
809
|
"such table found in FITS file"
|
|
727
810
|
)
|
|
728
811
|
|
|
729
812
|
|
|
730
|
-
def _add_beam(xds: xr.Dataset, helpers: dict) -> xr.Dataset:
|
|
813
|
+
def _add_beam(xds: xr.Dataset, helpers: dict, image_type: str = "SKY") -> xr.Dataset:
|
|
731
814
|
nchan = xds.sizes["frequency"]
|
|
732
815
|
npol = xds.sizes["polarization"]
|
|
733
816
|
beam_array = np.zeros([1, nchan, npol, 3])
|
|
734
817
|
beam_array[0, :, :, 0] = helpers["beam"]["bmaj"]["data"]
|
|
735
818
|
beam_array[0, :, :, 1] = helpers["beam"]["bmin"]["data"]
|
|
736
819
|
beam_array[0, :, :, 2] = helpers["beam"]["pa"]["data"]
|
|
737
|
-
return _create_beam_data_var(xds, beam_array)
|
|
820
|
+
return _create_beam_data_var(xds, beam_array, image_type)
|
|
738
821
|
|
|
739
822
|
|
|
740
|
-
def _create_beam_data_var(
|
|
823
|
+
def _create_beam_data_var(
|
|
824
|
+
xds: xr.Dataset, beam_array: np.array, image_type: str = "SKY"
|
|
825
|
+
) -> xr.Dataset:
|
|
741
826
|
xdb = xr.DataArray(
|
|
742
|
-
beam_array, dims=["time", "frequency", "polarization", "
|
|
827
|
+
beam_array, dims=["time", "frequency", "polarization", "beam_params_label"]
|
|
743
828
|
)
|
|
744
|
-
xdb = xdb.rename("
|
|
745
|
-
xdb = xdb.assign_coords(
|
|
829
|
+
xdb = xdb.rename("BEAM_FIT_PARAMS_" + image_type.upper())
|
|
830
|
+
xdb = xdb.assign_coords(beam_params_label=["major", "minor", "pa"])
|
|
746
831
|
xdb.attrs["units"] = "rad"
|
|
747
|
-
xds["
|
|
832
|
+
xds["BEAM_FIT_PARAMS_" + image_type.upper()] = xdb
|
|
748
833
|
return xds
|
|
749
834
|
|
|
750
835
|
|
|
@@ -779,7 +864,9 @@ def _add_sky_or_aperture(
|
|
|
779
864
|
header,
|
|
780
865
|
helpers: dict,
|
|
781
866
|
has_sph_dims: bool,
|
|
867
|
+
image_type: str = "SKY",
|
|
782
868
|
) -> xr.Dataset:
|
|
869
|
+
# TODO add code to recognize aperture images and set image_type accordingly
|
|
783
870
|
xda = xr.DataArray(ary, dims=dim_order)
|
|
784
871
|
for h, a in zip(
|
|
785
872
|
["BUNIT", "BTYPE", "OBJECT", "OBSERVER"],
|
|
@@ -792,16 +879,17 @@ def _add_sky_or_aperture(
|
|
|
792
879
|
xda.attrs["telescope"] = _get_telescope_metadata(helpers, header)
|
|
793
880
|
xda.attrs["description"] = None
|
|
794
881
|
xda.attrs["user"] = _user_attrs_from_header(header)
|
|
795
|
-
name = "SKY" if has_sph_dims else "APERTURE"
|
|
882
|
+
# name = "SKY" if has_sph_dims else "APERTURE"
|
|
883
|
+
name = image_type
|
|
796
884
|
xda = xda.rename(name)
|
|
797
885
|
xds[xda.name] = xda
|
|
798
886
|
if helpers["has_mask"]:
|
|
799
887
|
pp = da if type(xda[0].data) == dask.array.core.Array else np
|
|
800
888
|
mask = pp.isnan(xda)
|
|
801
889
|
mask.attrs = {}
|
|
802
|
-
mask = mask.rename("
|
|
803
|
-
xds["
|
|
804
|
-
|
|
890
|
+
mask = mask.rename("FLAG_" + name.upper())
|
|
891
|
+
xds["FLAG_" + name.upper()] = mask
|
|
892
|
+
xds[name.upper()].attrs[_image_flag] = "FLAG_" + name.upper()
|
|
805
893
|
xda = xda.rename(name)
|
|
806
894
|
xds[xda.name] = xda
|
|
807
895
|
return xds
|