xradio 1.0.2__py3-none-any.whl → 1.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. xradio/_utils/_casacore/casacore_from_casatools.py +75 -9
  2. xradio/_utils/dict_helpers.py +38 -7
  3. xradio/_utils/list_and_array.py +26 -3
  4. xradio/_utils/schema.py +44 -0
  5. xradio/_utils/xarray_helpers.py +63 -0
  6. xradio/_utils/zarr/common.py +4 -2
  7. xradio/image/__init__.py +4 -2
  8. xradio/image/_util/_casacore/common.py +2 -1
  9. xradio/image/_util/_casacore/xds_from_casacore.py +144 -92
  10. xradio/image/_util/_casacore/xds_to_casacore.py +118 -53
  11. xradio/image/_util/_fits/xds_from_fits.py +125 -37
  12. xradio/image/_util/_zarr/common.py +0 -1
  13. xradio/image/_util/casacore.py +183 -25
  14. xradio/image/_util/common.py +10 -8
  15. xradio/image/_util/image_factory.py +469 -27
  16. xradio/image/image.py +72 -100
  17. xradio/image/image_xds.py +262 -0
  18. xradio/image/schema.py +85 -0
  19. xradio/measurement_set/__init__.py +5 -4
  20. xradio/measurement_set/_utils/_msv2/_tables/read.py +4 -3
  21. xradio/measurement_set/_utils/_msv2/conversion.py +6 -9
  22. xradio/measurement_set/_utils/_msv2/create_field_and_source_xds.py +1 -0
  23. xradio/measurement_set/_utils/_msv2/msv4_sub_xdss.py +1 -1
  24. xradio/measurement_set/_utils/_utils/interpolate.py +5 -0
  25. xradio/measurement_set/_utils/_utils/partition_attrs.py +0 -1
  26. xradio/measurement_set/convert_msv2_to_processing_set.py +9 -9
  27. xradio/measurement_set/load_processing_set.py +2 -2
  28. xradio/measurement_set/measurement_set_xdt.py +83 -93
  29. xradio/measurement_set/open_processing_set.py +1 -1
  30. xradio/measurement_set/processing_set_xdt.py +33 -26
  31. xradio/schema/check.py +70 -19
  32. xradio/schema/common.py +0 -1
  33. xradio/testing/__init__.py +0 -0
  34. xradio/testing/_utils/__template__.py +58 -0
  35. xradio/testing/measurement_set/__init__.py +58 -0
  36. xradio/testing/measurement_set/checker.py +131 -0
  37. xradio/testing/measurement_set/io.py +22 -0
  38. xradio/testing/measurement_set/msv2_io.py +1854 -0
  39. {xradio-1.0.2.dist-info → xradio-1.1.1.dist-info}/METADATA +65 -23
  40. xradio-1.1.1.dist-info/RECORD +75 -0
  41. {xradio-1.0.2.dist-info → xradio-1.1.1.dist-info}/WHEEL +1 -1
  42. xradio-1.0.2.dist-info/RECORD +0 -66
  43. {xradio-1.0.2.dist-info → xradio-1.1.1.dist-info}/licenses/LICENSE.txt +0 -0
  44. {xradio-1.0.2.dist-info → xradio-1.1.1.dist-info}/top_level.txt +0 -0
@@ -9,14 +9,17 @@ import warnings
9
9
  from typing import Union
10
10
 
11
11
  import xarray as xr
12
+ import re
13
+ import dask.array as da
14
+ from xradio._utils.schema import get_data_group_keys
12
15
 
13
16
  try:
14
17
  from casacore import tables
15
18
  except ImportError:
16
19
  import xradio._utils._casacore.casacore_from_casatools as tables
17
20
 
18
- from ._casacore.common import _open_image_ro
19
- from ._casacore.xds_from_casacore import (
21
+
22
+ from xradio.image._util._casacore.xds_from_casacore import (
20
23
  _add_mask,
21
24
  _add_sky_or_aperture,
22
25
  _casa_image_to_xds_attrs,
@@ -28,34 +31,79 @@ from ._casacore.xds_from_casacore import (
28
31
  _get_beam,
29
32
  _read_image_array,
30
33
  )
31
- from ._casacore.xds_to_casacore import (
34
+ from xradio.image._util._casacore.xds_to_casacore import (
32
35
  _coord_dict_from_xds,
33
36
  _history_from_xds,
34
37
  _imageinfo_dict_from_xds,
35
38
  _write_casa_data,
36
39
  )
37
- from .common import _aperture_or_sky, _get_xds_dim_order, _dask_arrayize_dv
40
+ from xradio.image._util.common import (
41
+ _aperture_or_sky,
42
+ _get_xds_dim_order,
43
+ _dask_arrayize_dv,
44
+ )
45
+ from xradio.image._util._casacore.common import _beam_fit_params, _open_image_ro
38
46
 
39
47
  warnings.filterwarnings("ignore", category=FutureWarning)
40
48
 
41
49
 
42
- def _load_casa_image_block(infile: str, block_des: dict, do_sky_coords) -> xr.Dataset:
50
+ def _squeeze_if_needed(ary: da, image_type: str) -> da:
51
+ if image_type.upper() == "VISIBILITY_NORMALIZATION":
52
+ shape = ary.shape
53
+ if shape[3] != 1 or shape[4] != 1:
54
+ raise ValueError(
55
+ "VISIBILITY_NORMALIZATION casa image must have l and m of length 1. Found "
56
+ + [shape[3], shape[4]]
57
+ )
58
+ ary = ary.squeeze(axis=(3, 4))
59
+ return ary
60
+
61
+
62
+ def _get_casa_image_metadata(infile: str, do_sky_coords: bool, image_type: str) -> dict:
43
63
  image_full_path = os.path.expanduser(infile)
44
64
  with _open_image_ro(image_full_path) as casa_image:
45
65
  coords = casa_image.coordinates()
46
66
  cshape = casa_image.shape()
47
- ret = _casa_image_to_xds_coords(image_full_path, False, do_sky_coords)
48
- xds = ret["xds"].isel(block_des)
67
+ ret = _casa_image_to_xds_coords(image_full_path, False, do_sky_coords, image_type)
68
+ xds = ret["xds"]
69
+ sphr_dims = ret["sphr_dims"]
49
70
  nchan = ret["xds"].sizes["frequency"]
50
71
  npol = ret["xds"].sizes["polarization"]
72
+ dimorder = _get_xds_dim_order(ret["sphr_dims"], image_type)
73
+ metadata = {
74
+ "coords": coords,
75
+ "cshape": cshape,
76
+ "image_full_path": image_full_path,
77
+ "nchan": nchan,
78
+ "npol": npol,
79
+ "dimorder": dimorder,
80
+ # "xds_attrs": _casa_image_to_xds_attrs(image_full_path),
81
+ "sphr_dims": sphr_dims,
82
+ "xds": xds,
83
+ }
84
+ return metadata
85
+
86
+
87
+ def _load_casa_image_block(
88
+ infile: str, block_des: dict, do_sky_coords: bool, image_type: str
89
+ ) -> xr.Dataset:
90
+ md = _get_casa_image_metadata(infile, do_sky_coords, image_type)
91
+ coords = md["coords"]
92
+ cshape = md["cshape"]
93
+ dimorder = md["dimorder"]
94
+ sphr_dims = md["sphr_dims"]
95
+ nchan = md["nchan"]
96
+ npol = md["npol"]
97
+ xds = md["xds"].isel(block_des)
98
+ image_full_path = md["image_full_path"]
51
99
  starts, shapes, slices = _get_starts_shapes_slices(block_des, coords, cshape)
52
- dimorder = _get_xds_dim_order(ret["sphr_dims"])
53
100
  transpose_list, new_axes = _get_transpose_list(coords)
54
101
  block = _get_persistent_block(
55
102
  image_full_path, shapes, starts, dimorder, transpose_list, new_axes
56
103
  )
104
+ block = _squeeze_if_needed(block, image_type)
57
105
  xds = _add_sky_or_aperture(
58
- xds, block, dimorder, image_full_path, ret["sphr_dims"], True
106
+ xds, block, dimorder, image_full_path, sphr_dims, False, image_type
59
107
  )
60
108
  mymasks = _get_mask_names(image_full_path)
61
109
  for m in mymasks:
@@ -64,58 +112,168 @@ def _load_casa_image_block(infile: str, block_des: dict, do_sky_coords) -> xr.Da
64
112
  full_path, shapes, starts, dimorder, transpose_list, new_axes
65
113
  )
66
114
  # data vars are all caps by convention
67
- xds = _add_mask(xds, m.upper(), block, dimorder)
115
+ mask_name = re.sub(r"\bMASK(\d+)\b", r"MASK_\1", m.upper())
116
+ xds = _add_mask(xds, mask_name, block, dimorder)
68
117
  xds.attrs = _casa_image_to_xds_attrs(image_full_path)
69
- beam = _get_beam(image_full_path, nchan, npol, False)
118
+ beam = _get_beam(image_full_path, nchan, npol, False, image_type)
119
+
70
120
  if beam is not None:
71
121
  selectors = {
72
122
  k: block_des[k]
73
123
  for k in ("time", "frequency", "polarization")
74
124
  if k in block_des
75
125
  }
76
- xds["BEAM"] = beam.isel(selectors)
126
+ xds["BEAM_FIT_PARAMS_" + image_type.upper()] = beam.isel(selectors)
127
+ xds["BEAM_FIT_PARAMS_" + image_type.upper()].attrs["type"] = (
128
+ "beam_fit_params_" + image_type.lower()
129
+ )
130
+ xds[image_type.upper()].attrs[_beam_fit_params] = (
131
+ "BEAM_FIT_PARAMS_" + image_type.upper()
132
+ )
77
133
  return xds
78
134
 
79
135
 
80
- def _read_casa_image(
136
+ def _open_casa_image(
81
137
  infile: str,
82
138
  chunks: Union[list, dict],
83
139
  verbose: bool,
84
140
  do_sky_coords: bool,
85
141
  masks: bool = True,
86
- history: bool = True,
142
+ history: bool = False,
143
+ image_type: str = "SKY",
87
144
  ) -> xr.Dataset:
88
- img_full_path = os.path.expanduser(infile)
89
- ret = _casa_image_to_xds_coords(img_full_path, verbose, do_sky_coords)
90
- xds = ret["xds"]
91
- dimorder = _get_xds_dim_order(ret["sphr_dims"])
145
+ md = _get_casa_image_metadata(infile, do_sky_coords, image_type)
146
+ xds = md["xds"]
147
+ dimorder = md["dimorder"]
148
+ sphr_dims = md["sphr_dims"]
149
+ img_full_path = md["image_full_path"]
150
+ ary = _read_image_array(img_full_path, chunks, verbose=verbose)
151
+ ary = _squeeze_if_needed(ary, image_type)
92
152
  xds = _add_sky_or_aperture(
93
153
  xds,
94
- _read_image_array(img_full_path, chunks, verbose=verbose),
154
+ ary,
95
155
  dimorder,
96
156
  img_full_path,
97
- ret["sphr_dims"],
157
+ sphr_dims,
98
158
  history,
159
+ image_type,
99
160
  )
100
161
  if masks:
101
162
  mymasks = _get_mask_names(img_full_path)
102
163
  for m in mymasks:
103
164
  ary = _read_image_array(img_full_path, chunks, mask=m, verbose=verbose)
104
165
  # data var names are all caps by convention
105
- xds = _add_mask(xds, m.upper(), ary, dimorder)
166
+ mask_name = re.sub(r"\bMASK(\d+)\b", r"MASK_\1", m.upper())
167
+ xds = _add_mask(xds, mask_name, ary, dimorder)
106
168
  xds.attrs = _casa_image_to_xds_attrs(img_full_path)
107
169
  beam = _get_beam(
108
- img_full_path, xds.sizes["frequency"], xds.sizes["polarization"], True
170
+ img_full_path,
171
+ xds.sizes["frequency"],
172
+ xds.sizes["polarization"],
173
+ True,
174
+ image_type,
109
175
  )
110
176
  if beam is not None:
111
- xds["BEAM"] = beam
177
+ xds["BEAM_FIT_PARAMS_" + image_type.upper()] = beam
178
+ xds["BEAM_FIT_PARAMS_" + image_type.upper()].attrs["type"] = (
179
+ "beam_fit_params_" + image_type.lower()
180
+ )
181
+ xds[image_type.upper()].attrs[_beam_fit_params] = (
182
+ "BEAM_FIT_PARAMS_" + image_type.upper()
183
+ )
184
+
112
185
  # xds = _add_coord_attrs(xds, ret["icoords"], ret["dir_axes"])
113
186
  xds = _dask_arrayize_dv(xds)
187
+
114
188
  return xds
115
189
 
116
190
 
117
- def _xds_to_casa_image(xds: xr.Dataset, imagename: str) -> None:
118
- image_full_path = os.path.expanduser(imagename)
191
+ def _xds_to_multiple_casa_images(xds: xr.Dataset, image_store_name: str) -> None:
192
+ """Function disentagles xradio xr.Dataset into multiple casa images based on data_groups attribute.
193
+ An xr.Dataset may contain multiple images (sky, residual, psf, etc) stored under different data variables sharing common coordinates.
194
+ An addtional complication is that CASA images allow for internal masks and beam fit parameters to be stored alongside the main image data so these also need to be handled.
195
+ This function creates separate casa images for each image type found in the data_groups attribute of the xr.Dataset.
196
+
197
+ Parameters
198
+ ----------
199
+ xds : xr.Dataset
200
+ The xradio xr.Dataset containing multiple images and associated data.
201
+ image_store_name : str
202
+ The base name or path for storing the output CASA images.
203
+ If only one image is written, it will be named image_store_name, esle the images
204
+ will be named image_store_name.<image_type> where <image_type> is sky, residual, point_spread_function, etc.
205
+ """
206
+
207
+ data_vars_name_set = set(xds.data_vars.keys())
208
+
209
+ data_group_keys = list(get_data_group_keys(schema_name="image").keys())
210
+ internal_image_types_to_exclude = [
211
+ "flag",
212
+ "beam_fit_params_sky",
213
+ "beam_fit_params_point_spread_function",
214
+ ]
215
+ n_image_written = 0
216
+ last_image_written = ""
217
+ for data_group in xds.attrs["data_groups"].keys():
218
+ for image_type in data_group_keys:
219
+ if (image_type in xds.attrs["data_groups"][data_group]) and (
220
+ image_type not in internal_image_types_to_exclude
221
+ ):
222
+ image_name = xds.attrs["data_groups"][data_group][image_type]
223
+ if image_name in data_vars_name_set:
224
+ image_to_write_xds = xr.Dataset()
225
+ image_to_write_xds.attrs = xds.attrs.copy()
226
+
227
+ if image_type == "aperture":
228
+ image_to_write_xds["APERTURE"] = xds[image_name]
229
+ else:
230
+ image_to_write_xds["SKY"] = xds[image_name]
231
+
232
+ # This code handles adding internal masks and beam fit params if they exist.
233
+ if image_type == "sky":
234
+ if (
235
+ "beam_fit_params_sky"
236
+ in xds.attrs["data_groups"][data_group]
237
+ ):
238
+ beam_fit_params_name = xds.attrs["data_groups"][data_group][
239
+ "beam_fit_params_sky"
240
+ ]
241
+ image_to_write_xds["BEAM_FIT_PARAMS"] = xds[
242
+ beam_fit_params_name
243
+ ]
244
+
245
+ if "flag" in xds.attrs["data_groups"][data_group]:
246
+ mask_sky_name = xds.attrs["data_groups"][data_group]["flag"]
247
+ image_to_write_xds["MASK_0"] = xds[mask_sky_name]
248
+ image_to_write_xds["SKY"].attrs["flag"] = "MASK_0"
249
+
250
+ if image_type == "point_spread_function":
251
+ if (
252
+ "beam_fit_params_point_spread_function"
253
+ in xds.attrs["data_groups"][data_group]
254
+ ):
255
+ beam_fit_params_name = xds.attrs["data_groups"][data_group][
256
+ "beam_fit_params_point_spread_function"
257
+ ]
258
+ image_to_write_xds["BEAM_FIT_PARAMS"] = xds[
259
+ beam_fit_params_name
260
+ ]
261
+ outname = image_store_name + "." + image_type
262
+ _xds_to_casa_image(image_to_write_xds, outname)
263
+ if not os.path.exists(outname):
264
+ raise IOError(f"Failed to write CASA image {outname}")
265
+ n_image_written += 1
266
+ last_image_written = outname
267
+ data_vars_name_set.remove(image_name)
268
+ if n_image_written == 0:
269
+ raise ValueError("No valid image types found in xds to write to CASA images.")
270
+ if n_image_written == 1:
271
+ # rename the single written image to what the user requested
272
+ os.rename(last_image_written, image_store_name)
273
+
274
+
275
+ def _xds_to_casa_image(xds: xr.Dataset, image_store_name: str) -> None:
276
+ image_full_path = os.path.expanduser(image_store_name)
119
277
  _write_casa_data(xds, image_full_path)
120
278
  # create coordinates
121
279
  ap_sky = _aperture_or_sky(xds)
@@ -7,6 +7,7 @@ from typing import Dict, List
7
7
  import xarray as xr
8
8
  from xradio._utils.coord_math import _deg_to_rad
9
9
  from xradio._utils.dict_helpers import make_quantity
10
+ import toolviper.utils.logger as logger
10
11
 
11
12
  _c = 2.99792458e08 * u.m / u.s
12
13
  # OPTICAL = Z
@@ -17,17 +18,18 @@ _doppler_types = [
17
18
  "beta",
18
19
  "gamma",
19
20
  ]
20
- _image_type = "image_type"
21
+ _image_type = "type"
21
22
 
22
23
 
23
24
  def _aperture_or_sky(xds: xr.Dataset) -> str:
24
25
  return "SKY" if "SKY" in xds.data_vars or "l" in xds.coords else "APERTURE"
25
26
 
26
27
 
27
- def _get_xds_dim_order(has_sph: bool) -> list:
28
+ def _get_xds_dim_order(has_sph: bool, image_type: str) -> list:
28
29
  dimorder = ["time", "frequency", "polarization"]
29
- dir_lin = ["l", "m"] if has_sph else ["u", "v"]
30
- dimorder.extend(dir_lin)
30
+ if image_type.upper() != "VISIBILITY_NORMALIZATION":
31
+ dir_lin = ["l", "m"] if has_sph else ["u", "v"]
32
+ dimorder.extend(dir_lin)
31
33
  return dimorder
32
34
 
33
35
 
@@ -252,10 +254,10 @@ def _compute_sky_reference_pixel(xds: xr.Dataset) -> np.ndarray:
252
254
 
253
255
  def _l_m_attr_notes() -> Dict[str, str]:
254
256
  return {
255
- "l": "l is the angle measured from the phase center to the east. "
256
- "So l = x*cdelt, where x is the number of pixels from the phase center. "
257
+ "l": "l is the angle measured from the reference direction to the east. "
258
+ "So l = x*cdelt, where x is the number of pixels from the reference direction. "
257
259
  "See AIPS Memo #27, Section III.",
258
- "m": "m is the angle measured from the phase center to the north. "
259
- "So m = y*cdelt, where y is the number of pixels from the phase center. "
260
+ "m": "m is the angle measured from the reference direction to the north. "
261
+ "So m = y*cdelt, where y is the number of pixels from the reference direction. "
260
262
  "See AIPS Memo #27, Section III.",
261
263
  }