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,6 +9,7 @@ import toolviper.utils.logger as logger
9
9
  import numpy as np
10
10
  import xarray as xr
11
11
  from astropy import units as u
12
+ from xradio._utils.list_and_array import to_python_type
12
13
 
13
14
  try:
14
15
  from casacore import tables
@@ -19,14 +20,15 @@ except ImportError:
19
20
  from xradio._utils._casacore.casacore_from_casatools import image as casa_image
20
21
 
21
22
 
22
- from .common import (
23
- _active_mask,
23
+ from xradio.image._util._casacore.common import (
24
+ _image_flag,
25
+ _beam_fit_params,
24
26
  _object_name,
25
27
  _open_image_ro,
26
28
  _pointing_center,
27
29
  )
28
30
 
29
- from ..common import (
31
+ from xradio.image._util.common import (
30
32
  _compute_linear_world_values,
31
33
  _compute_velocity_values,
32
34
  _compute_world_sph_dims,
@@ -37,10 +39,11 @@ from ..common import (
37
39
  _image_type,
38
40
  _l_m_attr_notes,
39
41
  )
40
- from ...._utils._casacore.tables import extract_table_attributes, open_table_ro
42
+ from xradio._utils._casacore.tables import extract_table_attributes, open_table_ro
41
43
  from xradio._utils.coord_math import _deg_to_rad
42
44
  from xradio._utils.dict_helpers import (
43
45
  _casacore_q_to_xradio_q,
46
+ make_direction_location_dict,
44
47
  make_spectral_coord_reference_dict,
45
48
  make_quantity,
46
49
  make_skycoord_dict,
@@ -68,13 +71,15 @@ def _add_mask(
68
71
  xda = xr.DataArray(ary, dims=dimorder)
69
72
  # True pixels are good in numpy masked arrays
70
73
  xda = da.logical_not(xda)
71
- xda.attrs["image_type"] = "Mask"
74
+ xda.attrs["type"] = "flag"
72
75
  xda = xda.rename(name)
73
76
  xds[xda.name] = xda
74
77
  return xds
75
78
 
76
79
 
77
- def _casa_image_to_xds_image_attrs(image: casa_image, history: bool = True) -> dict:
80
+ def _casa_image_to_xds_image_attrs(
81
+ image: casa_image, history: bool = False, image_type: str = "SKY"
82
+ ) -> dict:
78
83
  """
79
84
  get the image attributes from the casacoreimage object
80
85
  """
@@ -177,20 +182,27 @@ def _casa_image_to_xds_image_attrs(image: casa_image, history: bool = True) -> d
177
182
  obj = "objectname"
178
183
  attrs[_object_name] = imageinfo[obj] if obj in imageinfo else ""
179
184
  attrs["user"] = meta_dict["miscinfo"]
185
+ """
180
186
  defmask = "Image_defaultmask"
181
187
  with open_table_ro(image.name()) as casa_table:
182
188
  # the actual mask is a data var and data var names are all caps by convention
183
- attrs[_active_mask] = (
184
- casa_table.getkeyword(defmask).upper()
185
- if defmask in casa_table.keywordnames()
186
- else None
187
- )
189
+ import re
190
+
191
+ if defmask in casa_table.keywordnames():
192
+ am = casa_table.getkeyword(defmask).upper()
193
+ am = re.sub(r"\bMASK(\d+)\b", r"MASK_\1", am)
194
+ else:
195
+ am = None
196
+ attrs[_image_flag] = "FLAG_" + image_type.upper()
197
+ """
188
198
  attrs["description"] = None
189
- # if also loading history, put it as another xds in the image attrs
199
+ # Store history as a dict (not xr.Dataset) for Xarray compatibility
190
200
  if history:
191
201
  htable = os.sep.join([os.path.abspath(image.name()), "logtable"])
192
202
  if os.path.isdir(htable):
193
- attrs["history"] = read_generic_table(htable)
203
+ history_xds = read_generic_table(htable)
204
+ # Convert xr.Dataset to dict for serialization compatibility
205
+ attrs["history"] = history_xds.to_dict()
194
206
  else:
195
207
  logger.warning(
196
208
  f"Unable to find history table {htable}. History will not be included"
@@ -205,13 +217,14 @@ def _add_sky_or_aperture(
205
217
  img_full_path: str,
206
218
  has_sph_dims: bool,
207
219
  history: bool,
220
+ image_type: str,
208
221
  ) -> xr.Dataset:
209
222
  xda = xr.DataArray(ary, dims=dimorder).astype(ary.dtype)
210
223
  with _open_image_ro(img_full_path) as casa_image:
211
- xda.attrs = _casa_image_to_xds_image_attrs(casa_image, history)
224
+ xda.attrs = _casa_image_to_xds_image_attrs(casa_image, history, image_type)
212
225
  # xds.attrs = attrs
213
- name = "SKY" if has_sph_dims else "APERTURE"
214
- xda = xda.rename(name)
226
+ # name = "SKY" if has_sph_dims else "APERTURE"
227
+ xda = xda.rename(image_type)
215
228
  xds[xda.name] = xda
216
229
  return xds
217
230
 
@@ -272,41 +285,76 @@ def _casa_image_to_xds_attrs(img_full_path: str) -> dict:
272
285
  raise RuntimeError("No direction reference frame found")
273
286
  casa_system = coord_dir_dict[system]
274
287
  ap_system, ap_equinox = _convert_direction_system(casa_system, "native")
275
- dir_dict = {}
276
288
 
277
- dir_dict["reference"] = make_skycoord_dict(
278
- data=[0.0, 0.0], units="rad", frame=ap_system
289
+ coordinate_system_info = {}
290
+
291
+ unit0 = u.Unit(_get_unit(coord_dir_dict["units"][0]))
292
+ unit1 = u.Unit(_get_unit(coord_dir_dict["units"][1]))
293
+ ra = float((coord_dir_dict["crval"][0] * unit0).to("rad").value)
294
+ dec = float((coord_dir_dict["crval"][1] * unit1).to("rad").value)
295
+ coordinate_system_info["reference_direction"] = make_skycoord_dict(
296
+ data=[ra, dec], units="rad", frame=ap_system
279
297
  )
280
298
  if ap_equinox:
281
- dir_dict["reference"]["attrs"]["equinox"] = ap_equinox
282
- for i in range(2):
283
- unit = u.Unit(_get_unit(coord_dir_dict["units"][i]))
284
- q = coord_dir_dict["crval"][i] * unit
285
- x = q.to("rad")
286
- dir_dict["reference"]["data"][i] = x.value
287
- k = "latpole"
288
- if k in coord_dir_dict:
289
- for j in (k, "lonpole"):
290
- m = "longpole" if j == "lonpole" else j
291
- dir_dict[j] = make_quantity(
292
- value=coord_dir_dict[m] * _deg_to_rad, units="rad", dims=["l", "m"]
293
- )
294
- for j in ("pc", "projection_parameters", "projection"):
295
- if j in coord_dir_dict:
296
- dir_dict[j] = coord_dir_dict[j]
297
- attrs["direction"] = dir_dict
299
+ coordinate_system_info["reference_direction"]["attrs"][
300
+ "equinox"
301
+ ] = ap_equinox
302
+
303
+ pol_dir = [-1, coord_dir_dict["latpole"] * _deg_to_rad]
304
+ if "lonpole" in coord_dir_dict:
305
+ pol_dir[0] = coord_dir_dict["lonpole"] * _deg_to_rad
306
+ else:
307
+ pol_dir[0] = coord_dir_dict["longpole"] * _deg_to_rad
308
+
309
+ coordinate_system_info["native_pole_direction"] = make_direction_location_dict(
310
+ pol_dir, "rad", "native_projection"
311
+ )
312
+
313
+ coordinate_system_info["projection"] = coord_dir_dict["projection"]
314
+ coordinate_system_info["projection_parameters"] = to_python_type(
315
+ coord_dir_dict["projection_parameters"]
316
+ )
317
+ coordinate_system_info["pixel_coordinate_transformation_matrix"] = (
318
+ to_python_type(coord_dir_dict["pc"])
319
+ )
320
+
321
+ attrs["coordinate_system_info"] = coordinate_system_info
298
322
  return copy.deepcopy(attrs)
299
323
 
324
+ # dir_dict = {}
325
+
326
+ # dir_dict["reference"] = make_skycoord_dict(
327
+ # data=[0.0, 0.0], units="rad", frame=ap_system
328
+ # )
329
+ # if ap_equinox:
330
+ # dir_dict["reference"]["attrs"]["equinox"] = ap_equinox
331
+ # for i in range(2):
332
+ # unit = u.Unit(_get_unit(coord_dir_dict["units"][i]))
333
+ # q = coord_dir_dict["crval"][i] * unit
334
+ # x = q.to("rad")
335
+ # dir_dict["reference"]["data"][i] = float(x.value)
336
+ # k = "latpole"
337
+ # if k in coord_dir_dict:
338
+ # for j in (k, "lonpole"):
339
+ # m = "longpole" if j == "lonpole" else j
340
+ # dir_dict[j] = make_quantity(
341
+ # value=coord_dir_dict[m] * _deg_to_rad, units="rad", dims=["l", "m"]
342
+ # )
343
+ # for j in ("pc", "projection_parameters", "projection"):
344
+ # if j in coord_dir_dict:
345
+ # dir_dict[j] = coord_dir_dict[j]
346
+ # attrs["direction"] = dir_dict
347
+ # return copy.deepcopy(attrs)
348
+
300
349
 
301
350
  def _casa_image_to_xds_coords(
302
- img_full_path: str, verbose: bool, do_sky_coords: bool
351
+ img_full_path: str, verbose: bool, do_sky_coords: bool, image_type: str
303
352
  ) -> dict:
304
353
  """
305
354
  TODO: complete documentation
306
355
  Create an xds without any pixel data from metadata from the specified CASA image
307
356
  """
308
357
  attrs = {}
309
- # casa_image = images.image(img_full_path)
310
358
  with _open_image_ro(img_full_path) as casa_image:
311
359
  # shape list is the reverse of the actual image shape
312
360
  shape = casa_image.shape()[::-1]
@@ -330,57 +378,56 @@ def _casa_image_to_xds_coords(
330
378
  attrs["sphr_dims"] = sphr_dims
331
379
  coords = {}
332
380
  coord_attrs = {}
333
- (coords["time"], coord_attrs["time"]) = _get_time_values_attrs(coord_dict)
334
- (coords["frequency"], coord_attrs["frequency"]) = _get_freq_values_attrs(
335
- csys, shape
336
- )
337
- (velocity_vals, coord_attrs["velocity"]) = _get_velocity_values_attrs(
381
+ coords["time"], coord_attrs["time"] = _get_time_values_attrs(coord_dict)
382
+ coords["frequency"], coord_attrs["frequency"] = _get_freq_values_attrs(csys, shape)
383
+ velocity_vals, coord_attrs["velocity"] = _get_velocity_values_attrs(
338
384
  coord_dict, coords["frequency"]
339
385
  )
340
- (coords["polarization"], coord_attrs["polarization"]) = _get_pol_values_attrs(
386
+ coords["polarization"], coord_attrs["polarization"] = _get_pol_values_attrs(
341
387
  coord_dict
342
388
  )
343
389
  coords["velocity"] = (["frequency"], velocity_vals)
344
- if len(sphr_dims) > 0:
345
- crpix = _flatten_list(csys.get_referencepixel())[::-1]
346
- inc = _flatten_list(csys.get_increment())[::-1]
347
- unit = _flatten_list(csys.get_unit())[::-1]
348
- attr_note = _l_m_attr_notes()
349
- for c in ["l", "m"]:
350
- idx = dimmap[c]
351
- delta = ((inc[idx]) * u.Unit(_get_unit(unit[idx]))).to("rad").value
352
- coords[c] = _compute_linear_world_values(
353
- naxis=shape[idx], crval=0.0, crpix=crpix[idx], cdelt=delta
354
- )
355
- coord_attrs[c] = {
356
- "note": attr_note[c],
357
- }
358
- if do_sky_coords:
359
- for k in coord_dict.keys():
360
- if k.startswith("direction"):
361
- dc = coordinates.directioncoordinate(coord_dict[k])
362
- break
363
- crval = _flatten_list(csys.get_referencevalue())[::-1]
364
- pick = lambda my_list: [my_list[i] for i in sphr_dims]
365
- my_ret = _compute_world_sph_dims(
366
- projection=dc.get_projection(),
367
- shape=pick(shape),
368
- ctype=diraxes,
369
- crval=pick(crval),
370
- crpix=pick(crpix),
371
- cdelt=pick(inc),
372
- cunit=pick(unit),
373
- )
374
- for i in [0, 1]:
375
- axis_name = my_ret["axis_name"][i]
376
- coords[axis_name] = (["l", "m"], my_ret["value"][i])
377
- coord_attrs[axis_name] = {}
378
- else:
379
- # Fourier image
380
- ret = _get_uv_values_attrs(coord_dict, axis_names, shape)
381
- for z in ["u", "v"]:
382
- coords[z], coord_attrs[z] = ret[z]
383
- coords["beam_param"] = ["major", "minor", "pa"]
390
+ if image_type.upper() != "VISIBILITY_NORMALIZATION":
391
+ if len(sphr_dims) > 0:
392
+ crpix = _flatten_list(csys.get_referencepixel())[::-1]
393
+ inc = _flatten_list(csys.get_increment())[::-1]
394
+ unit = _flatten_list(csys.get_unit())[::-1]
395
+ attr_note = _l_m_attr_notes()
396
+ for c in ["l", "m"]:
397
+ idx = dimmap[c]
398
+ delta = ((inc[idx]) * u.Unit(_get_unit(unit[idx]))).to("rad").value
399
+ coords[c] = _compute_linear_world_values(
400
+ naxis=shape[idx], crval=0.0, crpix=crpix[idx], cdelt=delta
401
+ )
402
+ coord_attrs[c] = {
403
+ "note": attr_note[c],
404
+ }
405
+ if do_sky_coords:
406
+ for k in coord_dict.keys():
407
+ if k.startswith("direction"):
408
+ dc = coordinates.directioncoordinate(coord_dict[k])
409
+ break
410
+ crval = _flatten_list(csys.get_referencevalue())[::-1]
411
+ pick = lambda my_list: [my_list[i] for i in sphr_dims]
412
+ my_ret = _compute_world_sph_dims(
413
+ projection=dc.get_projection(),
414
+ shape=pick(shape),
415
+ ctype=diraxes,
416
+ crval=pick(crval),
417
+ crpix=pick(crpix),
418
+ cdelt=pick(inc),
419
+ cunit=pick(unit),
420
+ )
421
+ for i in [0, 1]:
422
+ axis_name = my_ret["axis_name"][i]
423
+ coords[axis_name] = (["l", "m"], my_ret["value"][i])
424
+ coord_attrs[axis_name] = {}
425
+ else:
426
+ # Fourier image
427
+ ret = _get_uv_values_attrs(coord_dict, axis_names, shape)
428
+ for z in ["u", "v"]:
429
+ coords[z], coord_attrs[z] = ret[z]
430
+ coords["beam_params_label"] = ["major", "minor", "pa"]
384
431
  attrs["shape"] = shape
385
432
  xds = xr.Dataset(coords=coords)
386
433
  for c in coord_attrs.keys():
@@ -390,7 +437,7 @@ def _casa_image_to_xds_coords(
390
437
 
391
438
 
392
439
  def _convert_direction_system(
393
- casa_system: str, which: str, verbose: bool = True
440
+ casa_system: str, which: str, verbose: bool = False
394
441
  ) -> tuple:
395
442
  if casa_system == "J2000":
396
443
  if verbose:
@@ -634,6 +681,7 @@ def _get_persistent_block(
634
681
  block = _read_image_chunk(infile, shapes, starts)
635
682
  block = np.expand_dims(block, new_axes)
636
683
  block = block.transpose(transpose_list)
684
+ block = da.from_array(block, chunks=block.shape)
637
685
  block = xr.DataArray(block, dims=dimorder)
638
686
  return block
639
687
 
@@ -686,11 +734,11 @@ def _get_starts_shapes_slices(
686
734
  def _get_time_values_attrs(cimage_coord_dict: dict) -> Tuple[List[float], dict]:
687
735
  attrs = {}
688
736
  attrs["type"] = "time"
689
- attrs["scale"] = cimage_coord_dict["obsdate"]["refer"]
737
+ attrs["scale"] = cimage_coord_dict["obsdate"]["refer"].lower()
690
738
  unit = cimage_coord_dict["obsdate"]["m0"]["unit"]
691
739
  attrs["units"] = unit
692
740
  time_val = cimage_coord_dict["obsdate"]["m0"]["value"]
693
- attrs["format"] = _get_time_format(time_val, unit)
741
+ attrs["format"] = _get_time_format(time_val, unit).lower()
694
742
  return ([time_val], copy.deepcopy(attrs))
695
743
 
696
744
 
@@ -829,7 +877,11 @@ def _get_velocity_values_attrs(
829
877
 
830
878
 
831
879
  def _get_beam(
832
- img_full_path: str, nchan: int, npol: int, as_dask_array: bool
880
+ img_full_path: str,
881
+ nchan: int,
882
+ npol: int,
883
+ as_dask_array: bool,
884
+ image_type: str = "SKY",
833
885
  ) -> Union[xr.DataArray, None]:
834
886
  # the image may have multiple beams
835
887
  with _open_image_ro(img_full_path) as casa_image:
@@ -862,10 +914,10 @@ def _get_beam(
862
914
  if as_dask_array:
863
915
  beam_array = da.array(beam_array)
864
916
  xdb = xr.DataArray(
865
- beam_array, dims=["time", "frequency", "polarization", "beam_param"]
917
+ beam_array, dims=["time", "frequency", "polarization", "beam_params_label"]
866
918
  )
867
- xdb = xdb.rename("BEAM")
868
- xdb = xdb.assign_coords(beam_param=["major", "minor", "pa"])
919
+ xdb = xdb.rename("BEAM_FIT_PARAMS_" + image_type.upper())
920
+ xdb = xdb.assign_coords(beam_params_label=["major", "minor", "pa"])
869
921
  xdb.attrs["units"] = "rad"
870
922
  return xdb
871
923
 
@@ -1054,7 +1106,7 @@ def _read_image_array(
1054
1106
  indicating the length of a chunk on that particular axis. If
1055
1107
  a key is missing, the associated chunk length along that axis
1056
1108
  is 1. 'l' represents the longitude like dimension, and 'm'
1057
- represents the latitude like dimension. For apeature images,
1109
+ represents the latitude like dimension. For aperture images,
1058
1110
  'u' may be used in place of 'l', and 'v' in place of 'm'.
1059
1111
  :type chunks: list | dict, required
1060
1112
  :param mask: If specified, this is the associated image mask to read, rather than the actual