xradio 0.0.56__py3-none-any.whl → 0.0.58__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 (62) hide show
  1. xradio/__init__.py +2 -2
  2. xradio/_utils/_casacore/casacore_from_casatools.py +12 -2
  3. xradio/_utils/_casacore/tables.py +1 -0
  4. xradio/_utils/coord_math.py +22 -23
  5. xradio/_utils/dict_helpers.py +76 -11
  6. xradio/_utils/schema.py +5 -2
  7. xradio/_utils/zarr/common.py +1 -73
  8. xradio/image/_util/_casacore/xds_from_casacore.py +49 -33
  9. xradio/image/_util/_casacore/xds_to_casacore.py +41 -14
  10. xradio/image/_util/_fits/xds_from_fits.py +146 -35
  11. xradio/image/_util/casacore.py +4 -3
  12. xradio/image/_util/common.py +4 -4
  13. xradio/image/_util/image_factory.py +8 -8
  14. xradio/image/image.py +45 -5
  15. xradio/measurement_set/__init__.py +19 -9
  16. xradio/measurement_set/_utils/__init__.py +1 -3
  17. xradio/measurement_set/_utils/_msv2/__init__.py +0 -0
  18. xradio/measurement_set/_utils/_msv2/_tables/read.py +17 -76
  19. xradio/measurement_set/_utils/_msv2/_tables/read_main_table.py +2 -685
  20. xradio/measurement_set/_utils/_msv2/conversion.py +123 -145
  21. xradio/measurement_set/_utils/_msv2/create_antenna_xds.py +9 -16
  22. xradio/measurement_set/_utils/_msv2/create_field_and_source_xds.py +125 -221
  23. xradio/measurement_set/_utils/_msv2/msv2_to_msv4_meta.py +1 -2
  24. xradio/measurement_set/_utils/_msv2/msv4_info_dicts.py +8 -7
  25. xradio/measurement_set/_utils/_msv2/msv4_sub_xdss.py +27 -72
  26. xradio/measurement_set/_utils/_msv2/partition_queries.py +1 -261
  27. xradio/measurement_set/_utils/_msv2/subtables.py +0 -107
  28. xradio/measurement_set/_utils/_utils/interpolate.py +60 -0
  29. xradio/measurement_set/_utils/_zarr/encoding.py +2 -7
  30. xradio/measurement_set/convert_msv2_to_processing_set.py +0 -2
  31. xradio/measurement_set/load_processing_set.py +2 -2
  32. xradio/measurement_set/measurement_set_xdt.py +14 -14
  33. xradio/measurement_set/open_processing_set.py +1 -3
  34. xradio/measurement_set/processing_set_xdt.py +41 -835
  35. xradio/measurement_set/schema.py +95 -122
  36. xradio/schema/check.py +91 -97
  37. xradio/schema/dataclass.py +159 -22
  38. xradio/schema/export.py +99 -0
  39. xradio/schema/metamodel.py +51 -16
  40. xradio/schema/typing.py +5 -5
  41. {xradio-0.0.56.dist-info → xradio-0.0.58.dist-info}/METADATA +2 -1
  42. xradio-0.0.58.dist-info/RECORD +65 -0
  43. {xradio-0.0.56.dist-info → xradio-0.0.58.dist-info}/WHEEL +1 -1
  44. xradio/image/_util/fits.py +0 -13
  45. xradio/measurement_set/_utils/_msv2/_tables/load.py +0 -66
  46. xradio/measurement_set/_utils/_msv2/_tables/load_main_table.py +0 -490
  47. xradio/measurement_set/_utils/_msv2/_tables/read_subtables.py +0 -398
  48. xradio/measurement_set/_utils/_msv2/_tables/write.py +0 -323
  49. xradio/measurement_set/_utils/_msv2/_tables/write_exp_api.py +0 -388
  50. xradio/measurement_set/_utils/_msv2/chunks.py +0 -115
  51. xradio/measurement_set/_utils/_msv2/descr.py +0 -165
  52. xradio/measurement_set/_utils/_msv2/msv2_msv3.py +0 -7
  53. xradio/measurement_set/_utils/_msv2/partitions.py +0 -392
  54. xradio/measurement_set/_utils/_utils/cds.py +0 -40
  55. xradio/measurement_set/_utils/_utils/xds_helper.py +0 -404
  56. xradio/measurement_set/_utils/_zarr/read.py +0 -263
  57. xradio/measurement_set/_utils/_zarr/write.py +0 -329
  58. xradio/measurement_set/_utils/msv2.py +0 -106
  59. xradio/measurement_set/_utils/zarr.py +0 -133
  60. xradio-0.0.56.dist-info/RECORD +0 -78
  61. {xradio-0.0.56.dist-info → xradio-0.0.58.dist-info}/licenses/LICENSE.txt +0 -0
  62. {xradio-0.0.56.dist-info → xradio-0.0.58.dist-info}/top_level.txt +0 -0
@@ -1,398 +0,0 @@
1
- import toolviper.utils.logger as logger
2
- from pathlib import Path
3
- from typing import Dict, Tuple, Union
4
-
5
- import dask
6
- import numpy as np
7
- import pandas as pd
8
- import xarray as xr
9
-
10
- try:
11
- from casacore import tables
12
- except ImportError:
13
- import xradio._utils._casacore.casacore_from_casatools as tables
14
-
15
- from .table_query import open_query, open_table_ro
16
- from .read import (
17
- read_col_chunk,
18
- convert_casacore_time,
19
- extract_table_attributes,
20
- add_units_measures,
21
- table_exists,
22
- load_generic_table,
23
- )
24
- from .write import revert_time
25
- from xradio._utils.list_and_array import unique_1d
26
-
27
-
28
- def read_ephemerides(
29
- infile: str,
30
- ) -> Union[xr.Dataset, None]:
31
- """
32
- Read ephemerides info from MSv2 FIELD/EPHEMi_....tab subtables
33
-
34
- Parameters
35
- ----------
36
- infile : str
37
- path to MS
38
-
39
- Returns
40
- -------
41
- Union[xr.Dataset, None]
42
- ephemerides xds with metainfo as in the MSv3/EPHEMERIDES subtable
43
- """
44
- field_subt = Path(infile, "FIELD")
45
- subdirs = [
46
- sdir
47
- for sdir in field_subt.iterdir()
48
- if "EPHEM" in sdir.name and sdir.is_dir() and table_exists(str(sdir))
49
- ]
50
- ephem = []
51
- for sdir in subdirs:
52
- logger.debug(f"Reading ephemerides info from: FIELD / {sdir.name}")
53
- # One "EPHEM_*.tab" (each with a difference ephemeris_id) to concatenate
54
- ephem.append(
55
- load_generic_table(infile, str(Path(*sdir.parts[-2:])), timecols=["MJD"])
56
- )
57
-
58
- if ephem:
59
- ephem = xr.concat(ephem, dim="ephemeris_id")
60
- else:
61
- ephem = None
62
-
63
- return ephem
64
-
65
-
66
- def read_delayed_pointing_table(
67
- infile: str,
68
- rename_ids: Dict[str, str] = None,
69
- chunks: Tuple = (10000, 100, 2, 20),
70
- time_slice=None,
71
- ) -> xr.Dataset:
72
- """
73
- Read MS pointing subtable in delayed arrays into an xr.Dataset
74
-
75
- Parameters
76
- ----------
77
- infile : str
78
- path to pointing table
79
- rename_ids : Dict[str, str] (Default value = None)
80
- dict with dimension renaming mapping
81
- chunks : Tuple (Default value = (10000, 100, 2, 20))
82
- chunks for the arrays. Chunks tuple: time, antenna, data_vars_dim_1, data_vars_dim_2
83
- time_slice: slice
84
- time bounds
85
-
86
- Returns
87
- -------
88
- xr.Dataset
89
- pointing dataset
90
- """
91
-
92
- with open_table_ro(infile) as mtable:
93
- taql_time = ""
94
- if time_slice:
95
- times = normalize_time_slice(mtable, time_slice)
96
- taql_time = f"where TIME BETWEEN {times.start} AND {times.stop}"
97
- else:
98
- times = None
99
- taql_all = f"select * from $mtable {taql_time}"
100
- with open_query(mtable, taql_all) as query_all:
101
- if query_all.nrows() == 0:
102
- mtable.close()
103
- note = ""
104
- if taql_time:
105
- note = (
106
- " within the selected time range: {times.start} - {times.stop}"
107
- )
108
- logger.warning(f"POINTING subtable has no data{note}")
109
- return xr.Dataset()
110
-
111
- # pointing table uses time x antenna_id
112
- antennas = unique_1d(query_all.getcol("ANTENNA_ID", 0, -1))
113
- taql_times = f"select DISTINCT TIME from $mtable {taql_time}"
114
- with open_query(None, taql_times) as query_times:
115
- utimes = unique_1d(query_times.getcol("TIME", 0, -1))
116
-
117
- tvars = read_delayed_pointing_times(
118
- infile, antennas, utimes, chunks, query_all, times
119
- )
120
-
121
- dims = ["time", "antenna_id", "dim_2", "dim_3"]
122
-
123
- # now concat all the dask chunks from each time
124
- mvars = {}
125
- for var in tvars.keys():
126
- mvars[var] = xr.DataArray(
127
- dask.array.concatenate(tvars[var], axis=0),
128
- dims=dims[: len(tvars[var][0].shape)],
129
- )
130
-
131
- mcoords = {}
132
- mcoords["time"] = xr.DataArray(convert_casacore_time(utimes), dims=["time"])
133
- mcoords["antenna_id"] = xr.DataArray(np.arange(len(antennas)), dims=["antenna_id"])
134
-
135
- cc_attrs = extract_table_attributes(infile)
136
- attrs = {"other": {"msv2": {"ctds_attrs": cc_attrs}}}
137
- mvars = add_units_measures(mvars, cc_attrs)
138
- mcoords = add_units_measures(mcoords, cc_attrs)
139
-
140
- xds = xr.Dataset(mvars, coords=mcoords)
141
- if rename_ids:
142
- rename_ids = {k: v for k, v in rename_ids.items() if k in xds.sizes}
143
- xds = xds.rename_dims(rename_ids)
144
- xds = xds.assign_attrs(attrs)
145
-
146
- return xds
147
-
148
-
149
- def normalize_time_slice(mtable: tables.table, time_slice: slice) -> slice:
150
- """
151
- If we get indices, produce the TIME column time value for the
152
- start/top indices. If we get timestamps, convert them to casacore
153
- refeference.
154
-
155
- Parameters
156
- ----------
157
- mtable : tables.table
158
- a casacore table from which we are reading a TIME
159
- column
160
- time_slice : slice
161
- slice giving start/stop time. Can be given as
162
- integer indices or as timestamps (Xarray / pandas reference)
163
-
164
- Returns
165
- -------
166
- slice
167
- a (start, stop) slice with times in casacore ref frame
168
- """
169
- if type(time_slice.start) == pd.Timestamp and type(time_slice.stop) == pd.Timestamp:
170
- # Add tol?
171
- eps = np.finfo(float).eps
172
- times = slice(
173
- revert_time(time_slice.start) - eps, revert_time(time_slice.stop) + eps
174
- )
175
-
176
- elif (
177
- int(time_slice.start) == time_slice.start
178
- and int(time_slice.stop) == time_slice.stop
179
- ):
180
- # instead of int cast could be operator.index(time_slice.start)
181
- taql_utimes = "select DISTINCT TIME from $mtable"
182
- with open_query(mtable, taql_utimes) as query_utimes:
183
- utimes = unique_1d(query_utimes.getcol("TIME", 0, -1))
184
- # add a tol around the time ranges returned by taql
185
- if len(utimes) < 2:
186
- tol = 1e-5
187
- else:
188
- tol = np.diff(utimes).min() / 4
189
-
190
- nutimes = len(utimes)
191
- if nutimes == 0:
192
- times = slice(0, 0)
193
- else:
194
- tidxs = slice(
195
- min(nutimes, int(time_slice.start)),
196
- min(nutimes, int(time_slice.stop)) - 1,
197
- )
198
- times = slice(utimes[tidxs.start] - tol, utimes[tidxs.stop] + tol)
199
-
200
- else:
201
- raise ValueError(
202
- f"Invalid time type. Not a timestamp and cannot use as"
203
- f" index: {time_slice.start} (type: {type(time_slice.start)})"
204
- )
205
-
206
- return times
207
-
208
-
209
- def read_delayed_pointing_times(
210
- infile: str,
211
- antennas: np.ndarray,
212
- utimes: np.array,
213
- chunks: tuple,
214
- query_all: tables.table,
215
- time_slice: slice,
216
- ) -> Dict[str, xr.DataArray]:
217
- """
218
- Read pointing table in delayed time / antenna chunks. Loops over
219
- time chunks
220
-
221
- Parameters
222
- ----------
223
- infile : str
224
- path to pointing table
225
- antennas : np.ndarray
226
- antenna ids
227
- utimes : np.ndarray
228
- unique times from table
229
- chunks : tuple
230
- chunks for the arrays
231
- query_all : tables.table
232
- table to read columns
233
- time_slice: slice :
234
- time bounds
235
-
236
- Returns
237
- -------
238
- Dict[str, xr.DataArray]
239
- dictionary of columns=>variables (read as dask.delayed)
240
- """
241
-
242
- antenna_chunks = range(0, len(antennas), chunks[1])
243
-
244
- # loop over time chunks
245
- if time_slice:
246
- time_chunks = [0]
247
- logger.debug(
248
- f"reading single chunk from pointing, with times {time_slice.start} - {time_slice.stop}"
249
- )
250
- else:
251
- time_chunks = range(0, len(utimes), chunks[0])
252
- logger.debug(
253
- f"reading pointing table into {len(time_chunks)} time x {len(antenna_chunks)} antenna chunks"
254
- )
255
-
256
- tvars = {}
257
- for tc in time_chunks:
258
- bvars = read_delayed_pointing_chunks(
259
- infile, antennas, chunks, utimes, tc, query_all
260
- )
261
-
262
- # now concat all the dask chunks from each antenna
263
- for var in bvars.keys():
264
- if len(bvars[var]) == 0:
265
- continue
266
- if var not in tvars:
267
- tvars[var] = []
268
- tvars[var] += [dask.array.concatenate(bvars[var], axis=1)]
269
-
270
- return tvars
271
-
272
-
273
- def read_delayed_pointing_chunks(
274
- infile: str,
275
- antennas: np.ndarray,
276
- chunks: tuple,
277
- utimes: np.ndarray,
278
- tc: int,
279
- tb_tool: tables.table,
280
- ) -> Dict[str, xr.DataArray]:
281
- """
282
- For one time chunk, read the baseline/antenna chunks. Loops over
283
- antenna_id chunks and reads all columns as dask.delayed calls.
284
-
285
- Parameters
286
- ----------
287
- infile : str
288
- path to pointing table
289
- antennas : np.ndarray
290
- antenna ids
291
- chunks : tuple
292
- chunks for the arrays
293
- utimes : np.ndarray
294
- unique times from table
295
- tc : int
296
- time index
297
- tb_tool : tables.table
298
- table to read columns
299
-
300
- Returns
301
- -------
302
- Dict[str, xr.DataArray]
303
- dictionary of columns=>variables (read as dask.delayed)
304
- """
305
-
306
- # add a tol around the time ranges returned by taql, for the next taql queries
307
- if len(utimes) < 2:
308
- tol = 1e-5
309
- else:
310
- tol = np.diff(utimes).min() / 4
311
-
312
- times = (
313
- utimes[tc] - tol,
314
- utimes[min(len(utimes) - 1, tc + chunks[0] - 1)] + tol,
315
- )
316
- ctlen = min(len(utimes), tc + chunks[0]) - tc # chunk time length
317
-
318
- antenna_chunks = range(0, len(antennas), chunks[1])
319
- cols = tb_tool.colnames()
320
-
321
- bvars = {}
322
- for bc in antenna_chunks:
323
- blines = (
324
- antennas[bc],
325
- antennas[min(len(antennas) - 1, bc + chunks[1] - 1)],
326
- )
327
- cblen = min(len(antennas) - bc, chunks[1])
328
-
329
- # read the specified chunk of data
330
- ttql = "TIME BETWEEN %f and %f" % times
331
- atql = "ANTENNA_ID BETWEEN %i and %i" % blines
332
- ts_taql = f"select * from $mtable where {ttql} AND {atql}"
333
- with open_query(None, ts_taql) as ts_tb:
334
- tidxs = np.searchsorted(utimes, ts_tb.getcol("TIME", 0, -1)) - tc
335
- bidxs = np.searchsorted(antennas, ts_tb.getcol("ANTENNA_ID", 0, -1)) - bc
336
- didxs = np.arange(len(bidxs))
337
-
338
- # loop over each column and create delayed dask arrays
339
- for col in cols:
340
- if (col in ["TIME", "ANTENNA_ID"]) or (
341
- not tb_tool.iscelldefined(col, 0)
342
- ):
343
- continue
344
- if col not in bvars:
345
- bvars[col] = []
346
-
347
- cdata = tb_tool.getcol(col, 0, 1)[0]
348
- if isinstance(cdata, str):
349
- cdata = np.array(cdata)
350
- if len(cdata.shape) == 0:
351
- delayed_array = dask.delayed(read_col_chunk)(
352
- infile,
353
- ts_taql,
354
- col,
355
- (ctlen, cblen),
356
- tidxs,
357
- bidxs,
358
- didxs,
359
- None,
360
- None,
361
- )
362
- bvars[col] += [
363
- dask.array.from_delayed(
364
- delayed_array, (ctlen, cblen), cdata.dtype
365
- )
366
- ]
367
-
368
- elif len(cdata.shape) == 2:
369
- d1_list = []
370
- for cc in range(0, cdata.shape[0], chunks[2]):
371
- d1s = (cc, min(cdata.shape[0], cc + chunks[2]) - 1)
372
- d2_list = []
373
- for pc in range(0, cdata.shape[1], chunks[3]):
374
- d2s = (pc, min(cdata.shape[1], pc + chunks[3]) - 1)
375
- cshape = (
376
- ctlen,
377
- cblen,
378
- ) + (d1s[1] - d1s[0] + 1, d2s[1] - d2s[0] + 1)
379
- delayed_array = dask.delayed(read_col_chunk)(
380
- infile,
381
- ts_taql,
382
- col,
383
- cshape,
384
- tidxs,
385
- bidxs,
386
- didxs,
387
- d1s,
388
- d2s,
389
- )
390
- d2_list += [
391
- dask.array.from_delayed(
392
- delayed_array, cshape, cdata.dtype
393
- )
394
- ]
395
- d1_list += [dask.array.concatenate(d2_list, axis=3)]
396
- bvars[col] += [dask.array.concatenate(d1_list, axis=2)]
397
-
398
- return bvars
@@ -1,323 +0,0 @@
1
- import toolviper.utils.logger as logger, os
2
- from typing import Tuple
3
-
4
- import numpy as np
5
- import xarray as xr
6
-
7
- try:
8
- from casacore import tables
9
- except ImportError:
10
- import xradio._utils._casacore.casacore_from_casatools as tables
11
-
12
-
13
- def revert_time(datetimes: np.ndarray) -> np.ndarray:
14
- """
15
- Convert time back from pandas datetime ref to casacore ref
16
- (reverse of read.convert_casacore_time).
17
-
18
- Parameters
19
- ----------
20
- datetimes : np.ndarray
21
- times in pandas reference
22
-
23
- Returns
24
- -------
25
- np.ndarray
26
- times converted to casacore reference
27
-
28
- """
29
- return (datetimes.astype(float) / 10**9) + 3506716800.0
30
-
31
-
32
- #####################################
33
- # translate numpy dtypes to casacore type strings
34
- def type_converter(npdtype: str) -> str:
35
- cctype = "bad"
36
- if (npdtype == "int64") or (npdtype == "int32"):
37
- cctype = "int"
38
- elif npdtype == "bool":
39
- cctype = "bool"
40
- elif npdtype == "float32":
41
- cctype = "float"
42
- elif (npdtype == "float64") or (npdtype == "datetime64[ns]"):
43
- cctype = "double"
44
- elif npdtype == "complex64":
45
- cctype = "complex"
46
- elif npdtype == "complex128":
47
- cctype = "dcomplex"
48
- elif str(npdtype).startswith("<U"):
49
- cctype = "string"
50
-
51
- return cctype
52
-
53
-
54
- ####################################
55
- # create and initialize new output table
56
- def create_table(
57
- outfile: str,
58
- xds: xr.Dataset,
59
- max_rows: int,
60
- infile=None,
61
- cols=None,
62
- generic=False,
63
- ):
64
- if os.path.isdir(outfile):
65
- os.system("rm -fr %s" % outfile)
66
-
67
- # create column descriptions for table description
68
- ctds_attrs = {}
69
- try:
70
- ctds_attrs = xds.attrs["other"]["msv2"]["ctds_attrs"]
71
- except KeyError as exc:
72
- pass
73
-
74
- if cols is None:
75
- if ctds_attrs and "column_descriptions" in ctds_attrs:
76
- cols = {col: col for col in ctds_attrs["column_descriptions"]}
77
- else:
78
- cols = {var: var for var in xds.data_vars}
79
- # Would add all xds data vars regardless of description availability
80
- # +
81
- # list(xds.data_vars) +
82
-
83
- tabledesc = {}
84
- for col, var_name in cols.items():
85
- if ("column_descriptions" in ctds_attrs) and (
86
- col in ctds_attrs["column_descriptions"]
87
- ):
88
- coldesc = ctds_attrs["column_descriptions"][col]
89
- # col not in ignore_msv2_cols
90
- if (
91
- not generic
92
- and "DATA" in col
93
- and "shape" not in coldesc
94
- and var_name in xds.data_vars
95
- ):
96
- coldesc["shape"] = tuple(np.clip(xds[var_name].shape[1:], 1, None))
97
-
98
- if col == "UVW" or (
99
- (not "shape" in coldesc or type(coldesc["shape"]) == str)
100
- and var_name in xds.data_vars
101
- ):
102
- coldesc["shape"] = tuple(np.clip(xds[var_name].shape[1:], 1, None))
103
- else:
104
- coldesc = {"valueType": type_converter(xds[col].dtype)}
105
- if generic or (
106
- col == "UVW" or col == "DATA"
107
- ): # will be statically shaped even if not originally
108
- coldesc = {"shape": tuple(np.clip(xds[col].shape[1:], 1, None))}
109
- elif xds[col].ndim > 1: # make variably shaped
110
- coldesc = {"ndim": xds[col].ndim - 1}
111
- coldesc["name"] = col
112
- coldesc["desc"] = col
113
- tabledesc[col] = coldesc
114
-
115
- # fix the fun set of edge cases from casatestdata that cause errors
116
- if (
117
- "dataManagerType" in tabledesc[col]
118
- and tabledesc[col]["dataManagerType"] == "TiledShapeStMan"
119
- ) and (tabledesc[col]["ndim"] == 1):
120
- tabledesc[col]["dataManagerType"] = ""
121
-
122
- if generic:
123
- tb_tool = tables.table(
124
- outfile,
125
- tabledesc=tabledesc,
126
- nrow=max_rows,
127
- readonly=False,
128
- lockoptions={"option": "permanentwait"},
129
- ack=False,
130
- )
131
- else:
132
- tb_tool = tables.default_ms(outfile, tabledesc)
133
- tb_tool.addrows(max_rows)
134
- # if 'DATA_DESC_ID' in cols: tb_tool.putcol('DATA_DESC_ID',
135
- # np.zeros((max_rows), dtype='int32') - 1, 0, max_rows)
136
-
137
- # write xds attributes to table keywords, skipping certain reserved attributes
138
- existing_keywords = tb_tool.getkeywords()
139
- for attr in ctds_attrs:
140
- if attr in [
141
- "other",
142
- "history",
143
- "info",
144
- ] + list(existing_keywords.keys()):
145
- continue
146
- tb_tool.putkeyword(attr, ctds_attrs[attr])
147
- if "info" in ctds_attrs:
148
- tb_tool.putinfo(ctds_attrs["info"])
149
-
150
- # copy subtables and add to main table
151
- if infile:
152
- subtables = [
153
- ss.path
154
- for ss in os.scandir(infile)
155
- if ss.is_dir() and ("SORTED_TABLE" not in ss.path)
156
- ]
157
- os.system("cp -r %s %s" % (" ".join(subtables), outfile))
158
- for subtable in subtables:
159
- if not tables.tableexists(
160
- os.path.join(outfile, subtable[subtable.rindex("/") + 1 :])
161
- ):
162
- continue
163
- sub_tbl = tables.table(
164
- os.path.join(outfile, subtable[subtable.rindex("/") + 1 :]),
165
- readonly=False,
166
- lockoptions={"option": "permanentwait"},
167
- ack=False,
168
- )
169
- tb_tool.putkeyword(
170
- subtable[subtable.rindex("/") + 1 :], sub_tbl, makesubrecord=True
171
- )
172
- sub_tbl.close()
173
-
174
- tb_tool.close()
175
-
176
-
177
- ############################################################################
178
- ##
179
- ## write_generic_table() - write any xds to generic casacore table format
180
- ##
181
- ############################################################################
182
- def write_generic_table(xds: xr.Dataset, outfile: str, subtable="", cols=None):
183
- """
184
- Write generic xds contents back to casacore table format on disk
185
-
186
- Parameters
187
- ----------
188
- xds : xr.Dataset
189
-
190
- outfile : str
191
-
192
- subtable : str (Default value = "")
193
-
194
- cols : List[str] (Default value = None)
195
-
196
- Returns
197
- -------
198
-
199
- """
200
- outfile = os.path.expanduser(outfile)
201
- logger.debug("writing {os.path.join(outfile, subtable)}")
202
-
203
- try:
204
- ctds_attrs = {}
205
- ctds_attrs = xds.attrs["other"]["msv2"]["ctds_attrs"]
206
- except KeyError as exc:
207
- pass
208
-
209
- if cols is None:
210
- cols = {var.upper(): var for var in xds.data_vars}
211
- cols.update({coo.upper(): coo for coo in xds.coords if coo not in xds.dims})
212
- # Would add cols with a description regardless of presence in xds
213
- # + (
214
- # list(
215
- # ctds_attrs["column_descriptions"].keys()
216
- # if "column_descriptions" in ctds_attrs
217
- # else []
218
- # )
219
- # )
220
- max_rows = xds.row.shape[0] if "row" in xds.dims else 0
221
- create_table(
222
- os.path.join(outfile, subtable),
223
- xds,
224
- max_rows,
225
- infile=None,
226
- cols=cols,
227
- generic=True,
228
- )
229
-
230
- tb_tool = tables.table(
231
- os.path.join(outfile, subtable),
232
- readonly=False,
233
- lockoptions={"option": "permanentwait"},
234
- ack=False,
235
- )
236
- try:
237
- for dv, col in cols.items():
238
- if (dv not in xds) or (np.prod(xds[dv].shape) == 0):
239
- continue
240
- values = (
241
- xds[dv].values
242
- if xds[dv].dtype != "datetime64[ns]"
243
- else revert_time(xds[dv].values)
244
- )
245
- tb_tool.putcol(col, values, 0, values.shape[0], 1)
246
- except Exception:
247
- print(
248
- "ERROR: exception in write generic table - %s, %s, %s, %s"
249
- % (os.path.join(outfile, subtable), dv, str(values.shape), tb_tool.nrows())
250
- )
251
-
252
- # now we have to add this subtable to the main table keywords (assuming a main table already exists)
253
- if len(subtable) > 0:
254
- main_tbl = tables.table(
255
- outfile, readonly=False, lockoptions={"option": "permanentwait"}, ack=False
256
- )
257
- main_tbl.putkeyword(subtable, tb_tool, makesubrecord=True)
258
- main_tbl.done()
259
- tb_tool.close()
260
-
261
-
262
- ###################################
263
- # local helper
264
- def write_main_table_slice(
265
- xda: xr.DataArray,
266
- outfile: str,
267
- ddi: int,
268
- col: str,
269
- full_shape: Tuple,
270
- starts: Tuple,
271
- ):
272
- """
273
- Write an xds row chunk to the corresponding main table slice
274
-
275
- Parameters
276
- ----------
277
- xda : xr.DataArray
278
-
279
- outfile : str
280
-
281
- ddi : int
282
-
283
- col : str
284
-
285
- full_shape : Tuple
286
-
287
- starts : Tuple
288
-
289
-
290
- Returns
291
- -------
292
-
293
- """
294
- # trigger the DAG for this chunk and return values while the table is unlocked
295
- values = xda.compute().values
296
- if xda.dtype == "datetime64[ns]":
297
- values = revert_time(values)
298
-
299
- tbs = tables.table(
300
- outfile, readonly=False, lockoptions={"option": "permanentwait"}, ack=True
301
- )
302
-
303
- # try:
304
- if (
305
- (values.ndim == 1) or (col == "UVW") or (values.shape[1:] == full_shape)
306
- ): # scalar columns
307
- tbs.putcol(col, values, starts[0], len(values))
308
- else:
309
- if not tbs.iscelldefined(col, starts[0]):
310
- tbs.putcell(col, starts[0] + np.arange(len(values)), np.zeros((full_shape)))
311
- tbs.putcolslice(
312
- col,
313
- values,
314
- starts[1 : values.ndim],
315
- tuple(np.array(starts[1 : values.ndim]) + np.array(values.shape[1:]) - 1),
316
- [],
317
- starts[0],
318
- len(values),
319
- 1,
320
- )
321
- # except:
322
- # print("ERROR: write exception - %s, %s, %s" % (col, str(values.shape), str(starts)))
323
- tbs.close()