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