xradio 0.0.51__py3-none-any.whl → 0.0.53__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.
@@ -17,9 +17,10 @@ from ..common import (
17
17
  )
18
18
  from xradio._utils.coord_math import _deg_to_rad
19
19
  from xradio._utils.dict_helpers import (
20
- make_quantity,
21
20
  make_frequency_reference_dict,
21
+ make_quantity,
22
22
  make_skycoord_dict,
23
+ make_time_measure_dict,
23
24
  )
24
25
  import copy
25
26
  import dask
@@ -49,7 +50,7 @@ def _fits_image_to_xds(
49
50
  sphr_dims = helpers["sphr_dims"]
50
51
  ary = _read_image_array(img_full_path, chunks, helpers, verbose)
51
52
  dim_order = _get_xds_dim_order(sphr_dims)
52
- xds = _add_sky_or_aperture(xds, ary, dim_order, helpers, sphr_dims)
53
+ xds = _add_sky_or_aperture(xds, ary, dim_order, header, helpers, sphr_dims)
53
54
  xds.attrs = attrs
54
55
  xds = _add_coord_attrs(xds, helpers)
55
56
  if helpers["has_multibeam"]:
@@ -70,13 +71,7 @@ def _add_coord_attrs(xds: xr.Dataset, helpers: dict) -> xr.Dataset:
70
71
 
71
72
  def _add_time_attrs(xds: xr.Dataset, helpers: dict) -> xr.Dataset:
72
73
  time_coord = xds.coords["time"]
73
- meta = copy.deepcopy(helpers["obsdate"])
74
- del meta["value"]
75
- # meta["units"] = [ meta["units"] ]
76
- # meta['format'] = 'MJD'
77
- # meta['time_scale'] = meta['refer']
78
- # del meta['refer']
79
- time_coord.attrs = meta
74
+ time_coord.attrs = copy.deepcopy(helpers["obsdate"]["attrs"])
80
75
  xds.assign_coords(time=time_coord)
81
76
  return xds
82
77
 
@@ -87,8 +82,6 @@ def _add_freq_attrs(xds: xr.Dataset, helpers: dict) -> xr.Dataset:
87
82
  if helpers["has_freq"]:
88
83
  meta["rest_frequency"] = make_quantity(helpers["restfreq"], "Hz")
89
84
  meta["rest_frequencies"] = [meta["rest_frequency"]]
90
- # meta["frame"] = helpers["specsys"]
91
- # meta["units"] = "Hz"
92
85
  meta["type"] = "frequency"
93
86
  meta["wave_unit"] = "mm"
94
87
  freq_axis = helpers["freq_axis"]
@@ -128,16 +121,6 @@ def _add_l_m_attrs(xds: xr.Dataset, helpers: dict) -> xr.Dataset:
128
121
 
129
122
 
130
123
  def _add_lin_attrs(xds: xr.Dataset, helpers: dict) -> xr.Dataset:
131
- """
132
- if helpers["sphr_dims"]:
133
- for i, name in zip(helpers["dir_axes"], helpers["sphr_axis_names"]):
134
- meta = {
135
- "units": "rad",
136
- "crval": helpers["crval"][i],
137
- "cdelt": helpers["cdelt"][i],
138
- }
139
- xds.coords[name].attrs = meta
140
- """
141
124
  if not helpers["sphr_dims"]:
142
125
  for i, j in zip(helpers["dir_axes"], ("u", "v")):
143
126
  meta = {
@@ -167,7 +150,7 @@ def _xds_direction_attrs_from_header(helpers: dict, header) -> dict:
167
150
  direction["projection"] = p0
168
151
  helpers["projection"] = p0
169
152
  ref_sys = header["RADESYS"]
170
- ref_eqx = header["EQUINOX"]
153
+ ref_eqx = None if ref_sys.upper() == "ICRS" else header["EQUINOX"]
171
154
  if ref_sys == "FK5" and ref_eqx == 2000:
172
155
  ref_eqx = "J2000.0"
173
156
  helpers["ref_sys"] = ref_sys
@@ -176,15 +159,6 @@ def _xds_direction_attrs_from_header(helpers: dict, header) -> dict:
176
159
  direction["reference"] = make_skycoord_dict(
177
160
  [0.0, 0.0], units=["rad", "rad"], frame=ref_sys
178
161
  )
179
- """
180
- direction["reference"] = {
181
- "type": "sky_coord",
182
- "frame": ref_sys,
183
- "equinox": ref_eqx,
184
- "units": ["rad", "rad"],
185
- "value": [0.0, 0.0],
186
- }
187
- """
188
162
  dir_axes = helpers["dir_axes"]
189
163
  ddata = []
190
164
  dunits = []
@@ -194,9 +168,10 @@ def _xds_direction_attrs_from_header(helpers: dict, header) -> dict:
194
168
  ddata.append(x.value)
195
169
  # direction["reference"]["value"][i] = x.value
196
170
  x = helpers["cdelt"][i] * u.Unit(_get_unit(helpers["cunit"][i]))
197
- dunits.append(x.to("rad"))
171
+ dunits.append("rad")
198
172
  direction["reference"] = make_skycoord_dict(ddata, units=dunits, frame=ref_sys)
199
- direction["reference"]["attrs"]["equinox"] = ref_eqx.lower()
173
+ if ref_eqx is not None:
174
+ direction["reference"]["attrs"]["equinox"] = ref_eqx.lower()
200
175
  direction["latpole"] = make_quantity(
201
176
  header["LATPOLE"] * _deg_to_rad, "rad", dims=["l", "m"]
202
177
  )
@@ -261,18 +236,21 @@ def _get_telescope_metadata(helpers: dict, header) -> dict:
261
236
  r = np.sqrt(np.sum(xyz * xyz))
262
237
  lat = np.arcsin(z / r)
263
238
  long = np.arctan2(y, x)
264
- tel["position"] = {
265
- "type": "position",
266
- # I haven't seen a FITS keyword for reference frame of telescope posiiton
267
- "ellipsoid": "GRS80",
268
- "units": ["rad", "rad", "m"],
269
- "value": np.array([long, lat, r]),
239
+ tel["location"] = {
240
+ "attrs": {
241
+ "coordinate_system": "geocentric",
242
+ # I haven't seen a FITS keyword for reference frame of telescope posiiton
243
+ "frame": "ITRF",
244
+ "origin_object_name": "earth",
245
+ "type": "location",
246
+ "units": ["rad", "rad", "m"],
247
+ },
248
+ "data": np.array([long, lat, r]),
270
249
  }
271
- helpers["tel_pos"] = tel["position"]
272
250
  return tel
273
251
 
274
252
 
275
- def _pointing_center_to_metadata(helpers: dict, header) -> dict:
253
+ def _compute_pointing_center(helpers: dict, header) -> dict:
276
254
  # Neither helpers or header is modified
277
255
  t_axes = helpers["t_axes"]
278
256
  long_unit = header[f"CUNIT{t_axes[0]}"]
@@ -285,7 +263,9 @@ def _pointing_center_to_metadata(helpers: dict, header) -> dict:
285
263
  pc_lat = float(header[f"CRVAL{t_axes[1]}"]) * unit[1]
286
264
  pc_long = pc_long.to(u.rad).value
287
265
  pc_lat = pc_lat.to(u.rad).value
288
- return {"value": np.array([pc_long, pc_lat]), "initial": True}
266
+ return make_skycoord_dict(
267
+ [pc_long, pc_lat], units=["rad", "rad"], frame=helpers["ref_sys"]
268
+ )
289
269
 
290
270
 
291
271
  def _user_attrs_from_header(header) -> dict:
@@ -294,6 +274,9 @@ def _user_attrs_from_header(header) -> dict:
294
274
  "ALTRPIX",
295
275
  "ALTRVAL",
296
276
  "BITPIX",
277
+ "BMAJ",
278
+ "BMIN",
279
+ "BPA",
297
280
  "BSCALE",
298
281
  "BTYPE",
299
282
  "BUNIT",
@@ -319,21 +302,20 @@ def _user_attrs_from_header(header) -> dict:
319
302
  ]
320
303
  regex = r"|".join(
321
304
  [
322
- "^NAXIS\\d?$",
323
- "^CRVAL\\d$",
324
- "^CRPIX\\d$",
325
- "^CTYPE\\d$",
326
- "^CDELT\\d$",
327
- "^CUNIT\\d$",
328
- "^OBSGEO-(X|Y|Z)$",
329
- "^P(C|V)\\d_\\d$",
305
+ r"^NAXIS\d?$",
306
+ r"^CRVAL\d$",
307
+ r"^CRPIX\d$",
308
+ r"^CTYPE\d$",
309
+ r"^CDELT\d$",
310
+ r"^CUNIT\d$",
311
+ r"^OBSGEO-(X|Y|Z)$",
312
+ r"^P(C|V)0?\d_0?\d",
330
313
  ]
331
314
  )
332
315
  user = {}
333
316
  for k, v in header.items():
334
- if re.search(regex, k) or k in exclude:
335
- continue
336
- user[k.lower()] = v
317
+ if not (re.search(regex, k) or k in exclude):
318
+ user[k.lower()] = v
337
319
  return user
338
320
 
339
321
 
@@ -382,7 +364,7 @@ def _create_dim_map(helpers: dict, header) -> dict:
382
364
  return dim_map
383
365
 
384
366
 
385
- def _fits_header_to_xds_attrs(hdulist: fits.hdu.hdulist.HDUList) -> dict:
367
+ def _fits_header_to_xds_attrs(hdulist: fits.hdu.hdulist.HDUList) -> tuple:
386
368
  primary = None
387
369
  beams = None
388
370
  for hdu in hdulist:
@@ -418,9 +400,7 @@ def _fits_header_to_xds_attrs(hdulist: fits.hdu.hdulist.HDUList) -> dict:
418
400
  if dir_axes is not None:
419
401
  attrs["direction"] = _xds_direction_attrs_from_header(helpers, header)
420
402
  # FIXME read fits data in chunks in case all data too large to hold in memory
421
- has_mask = da.any(da.isnan(primary.data)).compute()
422
- attrs["active_mask"] = "MASK0" if has_mask else None
423
- helpers["has_mask"] = has_mask
403
+ helpers["has_mask"] = da.any(da.isnan(primary.data)).compute()
424
404
  beam = _beam_attr_from_header(helpers, header)
425
405
  if beam != "mb":
426
406
  helpers["beam"] = beam
@@ -432,24 +412,15 @@ def _fits_header_to_xds_attrs(hdulist: fits.hdu.hdulist.HDUList) -> dict:
432
412
  helpers["dtype"] = "float64"
433
413
  else:
434
414
  raise RuntimeError(f'Unhandled data type {header["BITPIX"]}')
435
- helpers["btype"] = header["BTYPE"] if "BTYPE" in header else None
436
- helpers["bunit"] = header["BUNIT"] if "BUNIT" in header else None
437
- attrs["object_name"] = header["OBJECT"] if "OBJECT" in header else None
438
- obsdate = {}
439
- obsdate["type"] = "time"
440
- obsdate["value"] = Time(header["DATE-OBS"], format="isot").mjd
441
- obsdate["units"] = ["d"]
442
- obsdate["scale"] = header["TIMESYS"]
443
- obsdate["format"] = "MJD"
444
- attrs["obsdate"] = obsdate
445
- helpers["obsdate"] = obsdate
446
- attrs["observer"] = header["OBSERVER"]
447
- attrs["pointing_center"] = _pointing_center_to_metadata(helpers, header)
448
- attrs["description"] = None
449
- attrs["telescope"] = _get_telescope_metadata(helpers, header)
415
+ helpers["obsdate"] = make_time_measure_dict(
416
+ data=Time(header["DATE-OBS"], format="isot").mjd,
417
+ units=["d"],
418
+ scale=header["TIMESYS"],
419
+ time_format="MJD",
420
+ )
421
+
450
422
  # TODO complete _make_history_xds when spec has been finalized
451
423
  # attrs['history'] = _make_history_xds(header)
452
- attrs["user"] = _user_attrs_from_header(header)
453
424
  return attrs, helpers, header
454
425
 
455
426
 
@@ -532,7 +503,7 @@ def _create_coords(
532
503
 
533
504
 
534
505
  def _get_time_values(helpers):
535
- return [helpers["obsdate"]["value"]]
506
+ return [helpers["obsdate"]["data"]]
536
507
 
537
508
 
538
509
  def _get_pol_values(helpers):
@@ -710,14 +681,22 @@ def _add_sky_or_aperture(
710
681
  xds: xr.Dataset,
711
682
  ary: Union[np.ndarray, da.array],
712
683
  dim_order: list,
684
+ header,
713
685
  helpers: dict,
714
686
  has_sph_dims: bool,
715
687
  ) -> xr.Dataset:
716
688
  xda = xr.DataArray(ary, dims=dim_order)
717
- image_type = helpers["btype"]
718
- unit = helpers["bunit"]
719
- xda.attrs[_image_type] = image_type
720
- xda.attrs["units"] = unit
689
+ for h, a in zip(
690
+ ["BUNIT", "BTYPE", "OBJECT", "OBSERVER"],
691
+ ["units", _image_type, "object_name", "observer"],
692
+ ):
693
+ if h in header:
694
+ xda.attrs[a] = header[h]
695
+ xda.attrs["obsdate"] = helpers["obsdate"].copy()
696
+ xda.attrs["pointing_center"] = _compute_pointing_center(helpers, header)
697
+ xda.attrs["telescope"] = _get_telescope_metadata(helpers, header)
698
+ xda.attrs["description"] = None
699
+ xda.attrs["user"] = _user_attrs_from_header(header)
721
700
  name = "SKY" if has_sph_dims else "APERTURE"
722
701
  xda = xda.rename(name)
723
702
  xds[xda.name] = xda
@@ -727,6 +706,9 @@ def _add_sky_or_aperture(
727
706
  mask.attrs = {}
728
707
  mask = mask.rename("MASK0")
729
708
  xds["MASK0"] = mask
709
+ xda.attrs["active_mask"] = "MASK0"
710
+ xda = xda.rename(name)
711
+ xds[xda.name] = xda
730
712
  return xds
731
713
 
732
714
 
@@ -13,4 +13,4 @@ _np_types = {
13
13
  "int64": np.int64,
14
14
  }
15
15
 
16
- _top_level_sub_xds = "_attrs_xds"
16
+ _top_level_sub_xds = "_attrs_xds_"
@@ -12,9 +12,9 @@ from xradio._utils.zarr.common import _get_file_system_and_items
12
12
 
13
13
 
14
14
  def _read_zarr(
15
- zarr_store: str, output: dict, selection: dict = {}
15
+ zarr_store: str, id_dict: dict, selection: dict = {}
16
16
  ) -> (xr.Dataset, bool):
17
- # supported key/values in output are:
17
+ # supported key/values in id_dict are:
18
18
  # "dv"
19
19
  # what data variables should be returned as.
20
20
  # "numpy": numpy arrays
@@ -29,27 +29,27 @@ def _read_zarr(
29
29
  do_dask = False
30
30
  do_numpy = False
31
31
  do_np_coords = False
32
- if "dv" in output:
33
- dv = output["dv"]
32
+ if "dv" in id_dict:
33
+ dv = id_dict["dv"]
34
34
  if dv in ["dask", "numpy"]:
35
35
  do_dask = dv == "dask"
36
36
  do_numpy = not do_dask
37
37
  else:
38
38
  raise ValueError(
39
- f"Unsupported value {output[dv]} for output[dv]. "
39
+ f"Unsupported value {id_dict[dv]} for id_dict[dv]. "
40
40
  "Supported values are 'dask' and 'numpy'"
41
41
  )
42
- if "coords" in output:
43
- c = output["coords"]
42
+ if "coords" in id_dict:
43
+ c = id_dict["coords"]
44
44
  if c == "numpy":
45
45
  do_np_coords = True
46
46
  else:
47
47
  raise ValueError(
48
- f"Unexpected value {c} for output[coords]. "
48
+ f"Unexpected value {c} for id_dict[coords]. "
49
49
  "The supported value is 'numpy'"
50
50
  )
51
51
  # do not pass selection, because that is only for the top level data vars
52
- xds = _decode(xds, zarr_store, output)
52
+ xds = _decode(xds, zarr_store, id_dict)
53
53
  if do_np_coords:
54
54
  xds = _coords_to_numpy(xds)
55
55
  if do_dask:
@@ -59,11 +59,9 @@ def _read_zarr(
59
59
  return xds
60
60
 
61
61
 
62
- def _decode(xds: xr.Dataset, zarr_store: str, output: dict) -> (xr.Dataset, bool):
62
+ def _decode(xds: xr.Dataset, zarr_store: str, id_dict: dict) -> xr.Dataset:
63
63
  xds.attrs = _decode_dict(xds.attrs, "")
64
- sub_xdses = _decode_sub_xdses(zarr_store, output)
65
- for k, v in sub_xdses.items():
66
- xds.attrs[k] = v
64
+ _decode_sub_xdses(xds, zarr_store, id_dict)
67
65
  return xds
68
66
 
69
67
 
@@ -85,26 +83,45 @@ def _decode_dict(my_dict: dict, top_key: str) -> dict:
85
83
  return my_dict
86
84
 
87
85
 
88
- def _decode_sub_xdses(zarr_store: str, output: dict) -> dict:
89
- sub_xdses = {}
86
+ def _decode_sub_xdses(xarrayObj, top_dir: str, id_dict: dict) -> None:
87
+ # FIXME this also needs to support S3
88
+ # determine immediate subdirs of zarr_store
89
+ entries = os.scandir(top_dir)
90
+ for d in entries:
91
+ path = os.path.join(top_dir, d.name)
92
+ if os.path.isdir(path):
93
+ if d.name.startswith(_top_level_sub_xds):
94
+ ky = d.name[len(_top_level_sub_xds) :]
95
+ xarrayObj.attrs[ky] = _read_zarr(path, id_dict)
96
+ # TODO if attrs that are xdses have attrs that are xdses ...
97
+ else:
98
+ # descend into the directory
99
+ _decode_sub_xdses(xarrayObj[d.name], path, id_dict)
90
100
 
91
- fs, store_contents = _get_file_system_and_items(zarr_store)
92
101
 
102
+ """
103
+ def _decode_sub_xdses(zarr_store: str, id_dict: dict) -> dict:
104
+ sub_xdses = {}
105
+ fs, store_contents = _get_file_system_and_items(zarr_store)
93
106
  if isinstance(fs, s3fs.core.S3FileSystem):
94
107
  # could we just use the items as returned from the helper function..?
95
108
  store_tree = fs.walk(zarr_store, topdown=True)
109
+ # Q: what is prepend_s3 used for? In this version it is defined but not used.
96
110
  prepend_s3 = "s3://"
97
111
  else:
98
112
  store_tree = os.walk(zarr_store, topdown=True)
99
113
  prepend_s3 = ""
100
-
101
114
  for root, dirs, files in store_tree:
115
+ relpath = os.path.relpath(root, zarr_store)
116
+ print("rpath", relpath)
102
117
  for d in dirs:
103
118
  if d.startswith(_top_level_sub_xds):
104
- xds = _read_zarr(os.sep.join([root, d]), output)
119
+ xds = _read_zarr(os.sep.join([root, d]), id_dict)
105
120
  # for k, v in xds.data_vars.items():
106
121
  # xds = xds.drop_vars([k]).assign({k: v.compute()})
107
- ky = d[len(_top_level_sub_xds) + 1 :]
122
+ ky = d[len(_top_level_sub_xds) :]
108
123
  sub_xdses[ky] = xds
109
-
124
+ print(f"Sub xdses: {sub_xdses.keys()}")
125
+ print("return")
110
126
  return sub_xdses
127
+ """
@@ -1,3 +1,5 @@
1
+ import dask.array as da
2
+ import logging
1
3
  import numpy as np
2
4
  import xarray as xr
3
5
  import os
@@ -5,45 +7,59 @@ from .common import _np_types, _top_level_sub_xds
5
7
 
6
8
 
7
9
  def _write_zarr(xds: xr.Dataset, zarr_store: str):
10
+ max_chunk_size = 0.95 * 2**30
11
+ for dv in xds.data_vars:
12
+ obj = xds[dv]
13
+ if isinstance(obj, xr.core.dataarray.DataArray) and isinstance(
14
+ obj.data, da.Array
15
+ ):
16
+ # get chunk size to make sure it is small enough to be compressed
17
+ ary = obj.data
18
+ chunk_size_bytes = np.prod(ary.chunksize) * np.dtype(ary.dtype).itemsize
19
+ if chunk_size_bytes > max_chunk_size:
20
+ raise ValueError(
21
+ f"Chunk size of {chunk_size_bytes/1e9} GB for data variable {dv} "
22
+ "bytes is too large for compression. To fix this, "
23
+ "reduce the chunk size of the dask array in the data variable "
24
+ f"by at least a factor of {chunk_size_bytes/max_chunk_size}."
25
+ )
8
26
  xds_copy = xds.copy(deep=True)
9
- xds_copy, xds_dict = _encode(xds_copy)
27
+ sub_xds_dict = _encode(xds_copy, zarr_store)
10
28
  z_obj = xds_copy.to_zarr(store=zarr_store, compute=True)
11
- _write_sub_xdses(zarr_store, xds_dict, _top_level_sub_xds)
29
+ if sub_xds_dict:
30
+ _write_sub_xdses(sub_xds_dict)
12
31
 
13
32
 
14
- def _encode(xds: xr.Dataset):
33
+ def _encode(xds: xr.Dataset, top_path: str) -> dict:
15
34
  # encode attrs
16
- xds.attrs, xds_dict = _encode_dict(xds.attrs)
17
- return xds, xds_dict
35
+ sub_xds_dict = {}
36
+ _encode_dict(xds.attrs, top_path, sub_xds_dict)
37
+ for dv in xds.data_vars:
38
+ _encode_dict(xds[dv].attrs, os.sep.join([top_path, dv]), sub_xds_dict)
39
+ logging.debug(f"Encoded sub_xds_dict: {sub_xds_dict}")
40
+ return sub_xds_dict
18
41
 
19
42
 
20
- def _encode_dict(my_dict: dict, top_key="") -> tuple:
21
- xds_dict = {}
43
+ def _encode_dict(my_dict: dict, top_path: str, sub_xds_dict) -> tuple:
22
44
  del_keys = []
23
45
  for k, v in my_dict.items():
24
46
  if isinstance(v, dict):
25
- z = os.sep.join([top_key, k]) if top_key else k
26
- my_dict[k], ret_xds_dict = _encode_dict(v, z)
27
- if ret_xds_dict:
28
- xds_dict[k] = ret_xds_dict
47
+ z = os.sep.join([top_path, k])
48
+ _encode_dict(v, z, sub_xds_dict)
29
49
  elif isinstance(v, np.ndarray):
30
50
  my_dict[k] = {}
31
51
  my_dict[k]["_type"] = "numpy.ndarray"
32
52
  my_dict[k]["_value"] = v.tolist()
33
53
  my_dict[k]["_dtype"] = str(v.dtype)
34
54
  elif isinstance(v, xr.Dataset):
35
- xds_dict[k] = v.copy(deep=True)
55
+ sub_xds_dict[os.sep.join([top_path, f"{_top_level_sub_xds}{k}"])] = v.copy(
56
+ deep=True
57
+ )
36
58
  del_keys.append(k)
37
59
  for k in del_keys:
38
60
  del my_dict[k]
39
- return my_dict, xds_dict
40
61
 
41
62
 
42
- def _write_sub_xdses(zarr_store: str, xds_dict: dict, path: str):
43
- for k, v in xds_dict.items():
44
- my_path = f"{path}_{k}" if path else f"{k}"
45
- if isinstance(v, dict):
46
- _write_sub_xdses(zarr_store, xds_dict[k], my_path)
47
- elif isinstance(v, xr.Dataset):
48
- zs = os.sep.join([zarr_store, my_path])
49
- z_obj = v.to_zarr(store=zs, compute=True)
63
+ def _write_sub_xdses(sub_xds: dict):
64
+ for k, v in sub_xds.items():
65
+ z_obj = v.to_zarr(store=k, compute=True)
@@ -48,7 +48,9 @@ def _load_casa_image_block(infile: str, block_des: dict, do_sky_coords) -> xr.Da
48
48
  block = _get_persistent_block(
49
49
  image_full_path, shapes, starts, dimorder, transpose_list, new_axes
50
50
  )
51
- xds = _add_sky_or_aperture(xds, block, dimorder, image_full_path, ret["sphr_dims"])
51
+ xds = _add_sky_or_aperture(
52
+ xds, block, dimorder, image_full_path, ret["sphr_dims"], True
53
+ )
52
54
  mymasks = _get_mask_names(image_full_path)
53
55
  for m in mymasks:
54
56
  full_path = os.sep.join([image_full_path, m])
@@ -57,7 +59,7 @@ def _load_casa_image_block(infile: str, block_des: dict, do_sky_coords) -> xr.Da
57
59
  )
58
60
  # data vars are all caps by convention
59
61
  xds = _add_mask(xds, m.upper(), block, dimorder)
60
- xds.attrs = _casa_image_to_xds_attrs(image_full_path, True)
62
+ xds.attrs = _casa_image_to_xds_attrs(image_full_path)
61
63
  mb = _multibeam_array(xds, image_full_path, False)
62
64
  if mb is not None:
63
65
  selectors = {}
@@ -86,6 +88,7 @@ def _read_casa_image(
86
88
  dimorder,
87
89
  img_full_path,
88
90
  ret["sphr_dims"],
91
+ history,
89
92
  )
90
93
  if masks:
91
94
  mymasks = _get_mask_names(img_full_path)
@@ -93,7 +96,7 @@ def _read_casa_image(
93
96
  ary = _read_image_array(img_full_path, chunks, mask=m, verbose=verbose)
94
97
  # data var names are all caps by convention
95
98
  xds = _add_mask(xds, m.upper(), ary, dimorder)
96
- xds.attrs = _casa_image_to_xds_attrs(img_full_path, history)
99
+ xds.attrs = _casa_image_to_xds_attrs(img_full_path)
97
100
  mb = _multibeam_array(xds, img_full_path, True)
98
101
  if mb is not None:
99
102
  xds["BEAM"] = mb
xradio/image/image.py CHANGED
@@ -7,6 +7,9 @@ import warnings
7
7
  from typing import List, Union
8
8
  import copy
9
9
  import numpy as np
10
+ import os
11
+ import shutil
12
+ import toolviper.utils.logger as logger
10
13
  import xarray as xr
11
14
 
12
15
  # from .._utils.zarr.common import _load_no_dask_zarr
@@ -182,27 +185,36 @@ def load_image(infile: str, block_des: dict = {}, do_sky_coords=True) -> xr.Data
182
185
 
183
186
 
184
187
  def write_image(
185
- xds: xr.Dataset, imagename: str, out_format: str = "casa", overwrite=False
188
+ xds: xr.Dataset, imagename: str, out_format: str = "casa", overwrite: bool = False
186
189
  ) -> None:
187
190
  """
188
191
  Convert an xds image to CASA or zarr image.
189
192
  xds : xarray.Dataset
190
193
  XDS to convert
191
194
  imagename : str
192
- Path to output CASA image
195
+ Path to output image
193
196
  out_format : str
194
197
  Format of output image, currently "casa" and "zarr" are supported
198
+ overwrite : bool
199
+ If True, overwrite existing image. Default is False.
195
200
  Returns
196
201
  -------
197
202
  None
198
203
  """
204
+ if os.path.exists(imagename):
205
+ if overwrite:
206
+ logger.warning(
207
+ f"Because overwrite=True, removing existing path {imagename}"
208
+ )
209
+ if os.path.isdir(imagename):
210
+ shutil.rmtree(imagename)
211
+ else:
212
+ os.remove(imagename)
213
+ else:
214
+ raise FileExistsError(
215
+ f"Path {imagename} already exists. Set overwrite=True to remove it."
216
+ )
199
217
  my_format = out_format.lower()
200
-
201
- if overwrite:
202
- import os
203
-
204
- os.system("rm -rf " + imagename)
205
-
206
218
  if my_format == "casa":
207
219
  _xds_to_casa_image(xds, imagename)
208
220
  elif my_format == "zarr":
@@ -16,6 +16,7 @@ from xradio.measurement_set._utils._msv2.msv4_sub_xdss import (
16
16
  create_pointing_xds,
17
17
  create_system_calibration_xds,
18
18
  create_weather_xds,
19
+ create_phased_array_xds,
19
20
  )
20
21
  from .msv4_info_dicts import create_info_dicts
21
22
  from xradio.measurement_set.schema import MSV4_SCHEMA_VERSION
@@ -1243,6 +1244,14 @@ def convert_and_write_partition(
1243
1244
  + str(time.time() - start)
1244
1245
  )
1245
1246
 
1247
+ # Create phased array xds
1248
+ phased_array_xds = create_phased_array_xds(
1249
+ in_file,
1250
+ ant_xds.antenna_name,
1251
+ ant_xds.receptor_label,
1252
+ ant_xds.polarization_type,
1253
+ )
1254
+
1246
1255
  start = time.time()
1247
1256
 
1248
1257
  # Time and frequency should always be increasing
@@ -1358,6 +1367,9 @@ def convert_and_write_partition(
1358
1367
  if weather_xds:
1359
1368
  ms_xdt["/weather_xds"] = weather_xds
1360
1369
 
1370
+ if phased_array_xds:
1371
+ ms_xdt["/phased_array_xds"] = phased_array_xds
1372
+
1361
1373
  if storage_backend == "zarr":
1362
1374
  ms_xdt.to_zarr(store=os.path.join(out_file, ms_v4_name))
1363
1375
  elif storage_backend == "netcdf":
@@ -17,7 +17,8 @@ from xradio.measurement_set._utils._msv2._tables.read import (
17
17
  make_taql_where_between_min_max,
18
18
  load_generic_table,
19
19
  )
20
- from xradio._utils.list_and_array import cast_to_str
20
+ from xradio._utils.list_and_array import cast_to_str, get_pad_value
21
+
21
22
  from xradio._utils.coord_math import (
22
23
  convert_to_si_units,
23
24
  add_position_offsets,
@@ -547,10 +548,16 @@ def pad_missing_sources(
547
548
  for source_id in unique_source_ids
548
549
  if source_id not in source_xds.coords["SOURCE_ID"]
549
550
  ]
551
+ if len(missing_source_ids) < 1:
552
+ return source_xds
550
553
 
551
554
  # would like to use the new-ish xr.pad, but it creates issues with indices/coords and is
552
555
  # also not free of overheads, as it for example changes all numeric types to float64
553
- missing_source_xds = xr.full_like(source_xds.isel(SOURCE_ID=0), fill_value=np.nan)
556
+ fill_value = {
557
+ var_name: get_pad_value(var.dtype)
558
+ for var_name, var in source_xds.data_vars.items()
559
+ }
560
+ missing_source_xds = xr.full_like(source_xds.isel(SOURCE_ID=0), fill_value)
554
561
  pad_str = "Unknown"
555
562
  pad_str_type = "<U9"
556
563
  for var in missing_source_xds.data_vars: