xradio 1.0.1__py3-none-any.whl → 1.1.0__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.
- xradio/_utils/_casacore/casacore_from_casatools.py +1 -1
- xradio/_utils/dict_helpers.py +38 -7
- xradio/_utils/list_and_array.py +26 -3
- xradio/_utils/schema.py +44 -0
- xradio/_utils/xarray_helpers.py +63 -0
- xradio/_utils/zarr/common.py +4 -2
- xradio/image/__init__.py +4 -2
- xradio/image/_util/_casacore/common.py +2 -1
- xradio/image/_util/_casacore/xds_from_casacore.py +105 -51
- xradio/image/_util/_casacore/xds_to_casacore.py +117 -52
- xradio/image/_util/_fits/xds_from_fits.py +124 -36
- xradio/image/_util/_zarr/common.py +0 -1
- xradio/image/_util/casacore.py +133 -16
- xradio/image/_util/common.py +6 -5
- xradio/image/_util/image_factory.py +466 -27
- xradio/image/image.py +72 -100
- xradio/image/image_xds.py +262 -0
- xradio/image/schema.py +85 -0
- xradio/measurement_set/__init__.py +5 -4
- xradio/measurement_set/_utils/_msv2/_tables/read.py +7 -3
- xradio/measurement_set/_utils/_msv2/conversion.py +6 -9
- xradio/measurement_set/_utils/_msv2/create_field_and_source_xds.py +1 -0
- xradio/measurement_set/_utils/_msv2/msv4_sub_xdss.py +1 -1
- xradio/measurement_set/_utils/_utils/interpolate.py +5 -0
- xradio/measurement_set/_utils/_utils/partition_attrs.py +0 -1
- xradio/measurement_set/convert_msv2_to_processing_set.py +9 -9
- xradio/measurement_set/load_processing_set.py +2 -2
- xradio/measurement_set/measurement_set_xdt.py +83 -93
- xradio/measurement_set/open_processing_set.py +7 -3
- xradio/measurement_set/processing_set_xdt.py +33 -26
- xradio/schema/check.py +70 -19
- xradio/schema/common.py +0 -1
- xradio/testing/__init__.py +0 -0
- xradio/testing/_utils/__template__.py +58 -0
- xradio/testing/measurement_set/__init__.py +58 -0
- xradio/testing/measurement_set/checker.py +131 -0
- xradio/testing/measurement_set/io.py +22 -0
- xradio/testing/measurement_set/msv2_io.py +1854 -0
- {xradio-1.0.1.dist-info → xradio-1.1.0.dist-info}/METADATA +64 -23
- xradio-1.1.0.dist-info/RECORD +75 -0
- {xradio-1.0.1.dist-info → xradio-1.1.0.dist-info}/WHEEL +1 -1
- xradio-1.0.1.dist-info/RECORD +0 -66
- {xradio-1.0.1.dist-info → xradio-1.1.0.dist-info}/licenses/LICENSE.txt +0 -0
- {xradio-1.0.1.dist-info → xradio-1.1.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,1854 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Functions to generate MeasurementSets. The motivation is to generate on-the-fly MSs in
|
|
3
|
+
unit tests that can be used in pytest fixtures or external projects that import
|
|
4
|
+
xradio.testing.
|
|
5
|
+
|
|
6
|
+
All functions that depend on casacore have been moved to this module.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from contextlib import contextmanager
|
|
10
|
+
import datetime
|
|
11
|
+
import itertools
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Generator, Tuple, Dict, Any, Optional, Iterable
|
|
14
|
+
|
|
15
|
+
import numpy as np
|
|
16
|
+
|
|
17
|
+
import casacore.tables as tables
|
|
18
|
+
from casacore.tables import default_ms, default_ms_subtable
|
|
19
|
+
from casacore.tables.tableutil import makedminfo, maketabdesc
|
|
20
|
+
from casacore.tables.msutil import complete_ms_desc, makearrcoldesc, required_ms_desc
|
|
21
|
+
|
|
22
|
+
# 2 observations, 2 fields, 2 states
|
|
23
|
+
# 2 SPWs, 4 polarizations
|
|
24
|
+
default_ms_descr = {
|
|
25
|
+
"nrows_per_ddi": 300,
|
|
26
|
+
"nchans": 16,
|
|
27
|
+
"npols": 2,
|
|
28
|
+
"data_cols": ["DATA"], # ['CORRECTED_DATA'],
|
|
29
|
+
# DATA / CORRECTED, etc.
|
|
30
|
+
# subtables needed to test xds structure
|
|
31
|
+
"SPECTRAL_WINDOW": {"0": 0, "1": 1},
|
|
32
|
+
"POLARIZATION": {"0": 0, "1": 1}, # "2": 2, "3": 3},
|
|
33
|
+
"ANTENNA": {"0": 0, "1": 1, "2": 2, "3": 3, "4": 4},
|
|
34
|
+
# subtables neded to test partitioning
|
|
35
|
+
"FIELD": {"0": 0, "1": 1},
|
|
36
|
+
"EPHEMERIDES": {0: 0},
|
|
37
|
+
"SCAN": {
|
|
38
|
+
"1": {
|
|
39
|
+
"0": {"intent": "CAL_ATMOSPHERE#ON_SOURCE"},
|
|
40
|
+
"1": {"intent": "CAL_ATMOSPHERE#OFF_SOURCE"},
|
|
41
|
+
},
|
|
42
|
+
"2": {
|
|
43
|
+
"0": {"intent": "OBSERVE_TARGET#ON_SOURCE"},
|
|
44
|
+
"1": {"intent": "OBSERVE_TARGET#ON_SOURCE"},
|
|
45
|
+
"2": {"intent": "OBSERVE_TARGET#ON_SOURCE"},
|
|
46
|
+
"3": {"intent": "OBSERVE_TARGET#ON_SOURCE"},
|
|
47
|
+
},
|
|
48
|
+
"3": {
|
|
49
|
+
"0": {"intent": "CALIBRATE_DELAY#ON_SOURCE,CALIBRATE_PHASE#ON_SOURCE"},
|
|
50
|
+
"1": {"intent": "CALIBRATE_DELAY#ON_SOURCE,CALIBRATE_PHASE#ON_SOURCE"},
|
|
51
|
+
"2": {"intent": "CALIBRATE_DELAY#ON_SOURCE,CALIBRATE_PHASE#ON_SOURCE"},
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
# CALIBRATE_ATMOSPHERE#OFF_SOURCE,CALIBRATE_ATMOSPHERE#ON_SOURCE,CALIBRATE_WVR#OFF_SOURCE,CALIBRATE_WVR#ON_SOURCE
|
|
55
|
+
"STATE": {
|
|
56
|
+
"0": {"id": 0, "intent": "CAL_ATMOSPHERE#ON_SOURCE"},
|
|
57
|
+
"1": {"id": 1, "intent": "CAL_ATMOSPHERE#OFF_SOURCE"},
|
|
58
|
+
},
|
|
59
|
+
"OBSERVATION": {"0": 0, "1": 1},
|
|
60
|
+
# Mandatory as per MSv2 but not essential to be able to build xdss
|
|
61
|
+
"FEED": {"0": 0},
|
|
62
|
+
"POINTING": {},
|
|
63
|
+
"PROCESSOR": {"0": 0, "1": 1},
|
|
64
|
+
"SOURCE": {"0": 0, "1": 1},
|
|
65
|
+
# Auto-generated without parameters for now:
|
|
66
|
+
# 'FLAG_CMD': {},
|
|
67
|
+
# 'HISTORY': {},
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _gen_test_ms_impl(
|
|
72
|
+
msname: str,
|
|
73
|
+
descr: dict = None,
|
|
74
|
+
opt_tables: bool = True,
|
|
75
|
+
vlbi_tables: bool = True,
|
|
76
|
+
required_only: bool = True,
|
|
77
|
+
misbehave: bool = False,
|
|
78
|
+
):
|
|
79
|
+
"""
|
|
80
|
+
Generates an MS for testing purposes, including main table and
|
|
81
|
+
subtables, either only the required ones or all. Follows the MSv2
|
|
82
|
+
definitions. The aim is to produce MSs with the minimal structure and
|
|
83
|
+
set of rows to exercise xradio functions.
|
|
84
|
+
|
|
85
|
+
- To be able to effectively start testing some functions, these are
|
|
86
|
+
required: MAIN, DATA_DESCRIPTION, SPECTRAL_WINDOW, POLARIZATION, ANTENNA
|
|
87
|
+
- To be able to start testing partitioning: FIELD, STATE
|
|
88
|
+
- Furhter partitioning testing: observation, processor, feed?
|
|
89
|
+
|
|
90
|
+
- The optional tables SYSCAL, WEATHER are generated with very (too)
|
|
91
|
+
simple contents
|
|
92
|
+
|
|
93
|
+
Parameters
|
|
94
|
+
----------
|
|
95
|
+
msname : str
|
|
96
|
+
name of MS on disk
|
|
97
|
+
descr : dict (Default value = None)
|
|
98
|
+
MS description, including description of scans, fields,
|
|
99
|
+
intents, etc. By default (empty dict) will create an MS with minimal
|
|
100
|
+
structure (one observation, one array, one scan, one field, etc.)
|
|
101
|
+
opt_tables : bool (Default value = True)
|
|
102
|
+
whether to produce optional (sub)tables, such as SOURCE, WEATHER
|
|
103
|
+
vlbi_tables : bool (Default value = True)
|
|
104
|
+
whether to add optional (sub)tables GAIN_CURVE and PHASE_CAL
|
|
105
|
+
required_only : bool (Default value = True)
|
|
106
|
+
whether to use the complete or required columns spec
|
|
107
|
+
misbehave : bool (Default value = False)
|
|
108
|
+
whether to generate a misbehaving MS. For example, usual or more
|
|
109
|
+
corner case conformance issues such as absence of STATE subtable,
|
|
110
|
+
missing FEED subtable, presence of missing SOURCE_IDs in the FIELD
|
|
111
|
+
subtable, no ASDM_EXECBLOCK table, no PROCESSOR subtable, etc.
|
|
112
|
+
Additional, more minor misbehaviors:
|
|
113
|
+
- irregular INTERVAL in main table, empty SPW names.
|
|
114
|
+
- missing metadata for MAIN/INTERVAL, SPECTRAL_WINDOW/CHAN_WIDTH
|
|
115
|
+
- missing posrefsys in EPHEM metadata
|
|
116
|
+
|
|
117
|
+
Returns
|
|
118
|
+
-------
|
|
119
|
+
|
|
120
|
+
"""
|
|
121
|
+
if not descr:
|
|
122
|
+
descr = default_ms_descr
|
|
123
|
+
|
|
124
|
+
# Definitely needed: main table + following subtables: DATA_DESCRIPTPION
|
|
125
|
+
# + SPECTRAL_WINDOW + POLARIZATION
|
|
126
|
+
# + ANTENNNA (just for the names)
|
|
127
|
+
# All these are required to create the xdss (they define data and dims)
|
|
128
|
+
outdescr = gen_main_table(msname, descr, required_only, misbehave)
|
|
129
|
+
gen_subt_ddi(msname, descr["SPECTRAL_WINDOW"], descr["POLARIZATION"])
|
|
130
|
+
gen_subt_spw(msname, descr["SPECTRAL_WINDOW"], descr["nchans"], misbehave=misbehave)
|
|
131
|
+
gen_subt_antenna(msname, descr["ANTENNA"])
|
|
132
|
+
gen_subt_pol_setup(msname, descr["npols"], descr["POLARIZATION"])
|
|
133
|
+
|
|
134
|
+
gen_ephem = not misbehave
|
|
135
|
+
# Also needed for partitioning: FIELD, STATE
|
|
136
|
+
gen_subt_field(msname, descr["FIELD"], gen_ephem=gen_ephem, misbehave=misbehave)
|
|
137
|
+
if not misbehave:
|
|
138
|
+
gen_subt_state(msname, descr["STATE"])
|
|
139
|
+
|
|
140
|
+
# Required by MSv2/v3 but not strictly required to load and partition:
|
|
141
|
+
|
|
142
|
+
# BEAM (only MSv3)
|
|
143
|
+
|
|
144
|
+
# FEED
|
|
145
|
+
gen_subt_feed(
|
|
146
|
+
msname, descr["FEED"], descr["ANTENNA"], descr["SPECTRAL_WINDOW"], misbehave
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
# HISTORY
|
|
150
|
+
gen_subt_history(msname, descr["OBSERVATION"])
|
|
151
|
+
|
|
152
|
+
# OBSERVATION
|
|
153
|
+
gen_subt_observation(msname, descr["OBSERVATION"])
|
|
154
|
+
|
|
155
|
+
# POINTING: TODO
|
|
156
|
+
gen_subt_pointing(msname)
|
|
157
|
+
|
|
158
|
+
# PROCESSOR
|
|
159
|
+
if not misbehave:
|
|
160
|
+
gen_subt_processor(msname, descr["PROCESSOR"])
|
|
161
|
+
|
|
162
|
+
if opt_tables:
|
|
163
|
+
# DOPPLER: no examples seen in test MSs
|
|
164
|
+
|
|
165
|
+
# EPHEMERIDES (only defined in Msv3, never seen in practice)
|
|
166
|
+
|
|
167
|
+
# FLAG_CMD (made optional in MSv3, required in MSv2)
|
|
168
|
+
gen_subt_flag_cmd(msname)
|
|
169
|
+
|
|
170
|
+
# FREQ_OFFSET: no examples seen in test MSs
|
|
171
|
+
|
|
172
|
+
# INTERFEROMETER_MODEL (only MSv3)
|
|
173
|
+
|
|
174
|
+
# PHASED_ARRAY (only MSv3)
|
|
175
|
+
|
|
176
|
+
# QUALITY_FREQUENCY_STATISTIC (only MSv3: listend but never defined)
|
|
177
|
+
|
|
178
|
+
# QUALITY_BASELINE_STATISTIC (only MSv3: listend but never defined)
|
|
179
|
+
|
|
180
|
+
# QUALITY_TIME_STATISTIC (only MSv3: listend but never defined)
|
|
181
|
+
|
|
182
|
+
# SOURCE
|
|
183
|
+
gen_subt_source(msname, descr["SOURCE"], misbehave)
|
|
184
|
+
|
|
185
|
+
# SCAN (Only MSv3)
|
|
186
|
+
|
|
187
|
+
# SYSCAL
|
|
188
|
+
gen_subt_syscal(msname, descr["ANTENNA"])
|
|
189
|
+
|
|
190
|
+
# WEATHER
|
|
191
|
+
gen_subt_weather(msname)
|
|
192
|
+
|
|
193
|
+
# ASDM_* subtables. One simple example
|
|
194
|
+
gen_subt_asdm_receiver(msname)
|
|
195
|
+
|
|
196
|
+
if not misbehave:
|
|
197
|
+
# ASDM_EXECBLOCK is used in create_info_dicts when available
|
|
198
|
+
gen_subt_asdm_execblock(msname)
|
|
199
|
+
# ASDM_STATION is used in create_weather
|
|
200
|
+
gen_subt_asdm_station(msname)
|
|
201
|
+
|
|
202
|
+
if vlbi_tables:
|
|
203
|
+
gen_subt_gain_curve(msname, descr["ANTENNA"])
|
|
204
|
+
gen_subt_phase_cal(msname, descr["ANTENNA"])
|
|
205
|
+
|
|
206
|
+
outdescr["params"] = {
|
|
207
|
+
"opt_tables": opt_tables,
|
|
208
|
+
"vlbi_tables": vlbi_tables,
|
|
209
|
+
"misbehave": misbehave,
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return outdescr
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def gen_test_ms(
|
|
216
|
+
msname: str,
|
|
217
|
+
*,
|
|
218
|
+
descr: Optional[Dict[str, Any]] = None,
|
|
219
|
+
opt_tables: bool = True,
|
|
220
|
+
vlbi_tables: bool = True,
|
|
221
|
+
required_only: bool = True,
|
|
222
|
+
misbehave: bool = False,
|
|
223
|
+
) -> Tuple[str, Dict[str, Any]]:
|
|
224
|
+
"""
|
|
225
|
+
Generate an MS on disk and return both the path and the generated description.
|
|
226
|
+
|
|
227
|
+
This is the high-level helper used by tests and benchmarks. The lower-level
|
|
228
|
+
implementation is `_gen_test_ms_impl`.
|
|
229
|
+
"""
|
|
230
|
+
|
|
231
|
+
ms_descr = _gen_test_ms_impl(
|
|
232
|
+
msname,
|
|
233
|
+
descr,
|
|
234
|
+
opt_tables=opt_tables,
|
|
235
|
+
vlbi_tables=vlbi_tables,
|
|
236
|
+
required_only=required_only,
|
|
237
|
+
misbehave=misbehave,
|
|
238
|
+
)
|
|
239
|
+
return msname, ms_descr
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def gen_minimal_ms(
|
|
243
|
+
msname: str = "test_msv2_minimal_required.ms",
|
|
244
|
+
*,
|
|
245
|
+
misbehave: bool = False,
|
|
246
|
+
include_optional_tables: bool = True,
|
|
247
|
+
include_vlbi_tables: bool = True,
|
|
248
|
+
) -> Tuple[str, Dict[str, Any]]:
|
|
249
|
+
"""
|
|
250
|
+
Generate a minimal MSv2 dataset with sensible defaults used across tests/benchmarks.
|
|
251
|
+
"""
|
|
252
|
+
|
|
253
|
+
return gen_test_ms(
|
|
254
|
+
msname,
|
|
255
|
+
opt_tables=include_optional_tables,
|
|
256
|
+
vlbi_tables=include_vlbi_tables,
|
|
257
|
+
required_only=True,
|
|
258
|
+
misbehave=misbehave,
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def make_ms_empty(name: str, descr: dict = None, complete: bool = False):
|
|
263
|
+
"""
|
|
264
|
+
OLD simple function. makes empty (0 rows) MSs
|
|
265
|
+
|
|
266
|
+
Parameters
|
|
267
|
+
----------
|
|
268
|
+
name : str
|
|
269
|
+
|
|
270
|
+
descr : dict (Default value = None)
|
|
271
|
+
|
|
272
|
+
complete : bool (Default value = False)
|
|
273
|
+
|
|
274
|
+
Returns
|
|
275
|
+
-------
|
|
276
|
+
|
|
277
|
+
"""
|
|
278
|
+
if complete:
|
|
279
|
+
tabdesc = complete_ms_desc("MAIN")
|
|
280
|
+
else:
|
|
281
|
+
tabdesc = required_ms_desc("MAIN")
|
|
282
|
+
|
|
283
|
+
datacoldesc = tables.makearrcoldesc(
|
|
284
|
+
"DATA",
|
|
285
|
+
0.1 + 0.1j,
|
|
286
|
+
valuetype="complex",
|
|
287
|
+
ndim=2,
|
|
288
|
+
datamanagertype="TiledShapeStMan",
|
|
289
|
+
datamanagergroup="TiledData",
|
|
290
|
+
comment="The data column",
|
|
291
|
+
)
|
|
292
|
+
del datacoldesc["desc"]["shape"]
|
|
293
|
+
tabdesc.update(tables.maketabdesc(datacoldesc))
|
|
294
|
+
|
|
295
|
+
weightspeccoldesc = tables.makearrcoldesc(
|
|
296
|
+
"WEIGHT_SPECTRUM",
|
|
297
|
+
1.0,
|
|
298
|
+
valuetype="float",
|
|
299
|
+
ndim=2,
|
|
300
|
+
datamanagertype="TiledShapeStMan",
|
|
301
|
+
datamanagergroup="TiledWgtSpectrum",
|
|
302
|
+
comment="Weight for each data point",
|
|
303
|
+
)
|
|
304
|
+
del weightspeccoldesc["desc"]["shape"]
|
|
305
|
+
tabdesc.update(tables.maketabdesc(weightspeccoldesc))
|
|
306
|
+
|
|
307
|
+
vis = tables.default_ms(name, tabdesc=tabdesc, dminfo=makedminfo(tabdesc))
|
|
308
|
+
assert vis.nrows() == 0
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def gen_main_table(
|
|
312
|
+
mspath: str, descr: dict, required_only: bool = True, misbehave: bool = False
|
|
313
|
+
):
|
|
314
|
+
"""
|
|
315
|
+
Create main MSv2 table.
|
|
316
|
+
Relies on the required/complete_ms_desc descriptions of columns
|
|
317
|
+
|
|
318
|
+
Simplifications/assumptions:
|
|
319
|
+
- All polarization setups have the same number of correlations/
|
|
320
|
+
- All scans have the same SPWs (all SPWs)
|
|
321
|
+
- Multiple observation: just repeat the same structure (same scans,
|
|
322
|
+
fields, etc.)
|
|
323
|
+
- Can generate multiple *_DATA columns but they all have the same data
|
|
324
|
+
pattern
|
|
325
|
+
|
|
326
|
+
:return: outdescr
|
|
327
|
+
|
|
328
|
+
Parameters
|
|
329
|
+
----------
|
|
330
|
+
mspath: str :
|
|
331
|
+
|
|
332
|
+
descr: dict :
|
|
333
|
+
|
|
334
|
+
required_only: bool :
|
|
335
|
+
(Default value = True)
|
|
336
|
+
|
|
337
|
+
misbehave : bool (Default value = False)
|
|
338
|
+
all STATE_ID values are set to -1 (assuming a "misbehaved" MS with empty STATE subtable)
|
|
339
|
+
|
|
340
|
+
Returns
|
|
341
|
+
-------
|
|
342
|
+
|
|
343
|
+
"""
|
|
344
|
+
|
|
345
|
+
outdescr = descr.copy()
|
|
346
|
+
|
|
347
|
+
if descr == {}:
|
|
348
|
+
nchans = 16
|
|
349
|
+
npols = 2
|
|
350
|
+
else:
|
|
351
|
+
nchans = descr["nchans"]
|
|
352
|
+
npols = descr["npols"]
|
|
353
|
+
|
|
354
|
+
if required_only:
|
|
355
|
+
ms_desc = required_ms_desc("MAIN")
|
|
356
|
+
else:
|
|
357
|
+
ms_desc = complete_ms_desc("MAIN")
|
|
358
|
+
|
|
359
|
+
ms_desc["UVW"].update(
|
|
360
|
+
options=0,
|
|
361
|
+
shape=[3],
|
|
362
|
+
ndim=1,
|
|
363
|
+
dataManagerGroup="UVWGroup",
|
|
364
|
+
dataManagerType="TiledColumnStMan",
|
|
365
|
+
)
|
|
366
|
+
dmgroups_spec = {"UVW": {"DEFAULTTILESHAPE": [3, nchans]}}
|
|
367
|
+
|
|
368
|
+
# data columns. TODO: move to function - make_data_cols_descr
|
|
369
|
+
for data_col_name in descr["data_cols"]:
|
|
370
|
+
data_col_desc = makearrcoldesc(
|
|
371
|
+
data_col_name,
|
|
372
|
+
0.0,
|
|
373
|
+
options=4,
|
|
374
|
+
valuetype="complex",
|
|
375
|
+
# Beware of the additional column description entry - not present in casatestdata
|
|
376
|
+
shape=[nchans, npols],
|
|
377
|
+
ndim=2,
|
|
378
|
+
datamanagertype="TiledColumnStMan",
|
|
379
|
+
datamanagergroup="DataGroup",
|
|
380
|
+
comment="added by gen_test_ms",
|
|
381
|
+
)
|
|
382
|
+
ms_desc.update(maketabdesc(data_col_desc))
|
|
383
|
+
|
|
384
|
+
dmgroups_spec.update(
|
|
385
|
+
{"DataColsGroup": {"DEFAULTTILESHAPE": [nchans, npols, nchans]}}
|
|
386
|
+
)
|
|
387
|
+
ms_data_man_info = makedminfo(ms_desc, dmgroups_spec)
|
|
388
|
+
|
|
389
|
+
if "STATE" not in descr:
|
|
390
|
+
vis = tables.default_ms(mspath, tabdesc=ms_desc, dminfo=makedminfo(ms_desc))
|
|
391
|
+
assert vis.nrows() == 0
|
|
392
|
+
return
|
|
393
|
+
# else:
|
|
394
|
+
# populate (TODO)
|
|
395
|
+
|
|
396
|
+
# Key columns (that define data rows):
|
|
397
|
+
# - TIME: given in descr
|
|
398
|
+
# - ANTENNA1, ANTENNA2: given in descr
|
|
399
|
+
# - DATA_DESC_ID, PROCESSOR_ID: given in descr
|
|
400
|
+
# - FEED1, FEED2, assumed 0
|
|
401
|
+
# - Not considered: ANTENNA3, FEED3, PHASE_ID, TIME_EXTRA_PREC
|
|
402
|
+
|
|
403
|
+
with default_ms(mspath, ms_desc, ms_data_man_info) as msv2:
|
|
404
|
+
desc = msv2.getcoldesc("UVW")
|
|
405
|
+
assert desc["dataManagerType"] == "TiledColumnStMan"
|
|
406
|
+
dminfo = msv2.getdminfo("UVW")
|
|
407
|
+
assert dminfo["NAME"] == "UVWGroup"
|
|
408
|
+
|
|
409
|
+
# Figure out amount of rows and related IDs
|
|
410
|
+
nrows = descr["nrows_per_ddi"]
|
|
411
|
+
dd_pairs = list(
|
|
412
|
+
itertools.product(
|
|
413
|
+
list(descr["SPECTRAL_WINDOW"].values()), descr["POLARIZATION"].values()
|
|
414
|
+
)
|
|
415
|
+
)
|
|
416
|
+
nddis = len(dd_pairs)
|
|
417
|
+
outdescr["nddis"] = nddis
|
|
418
|
+
dd_ids = np.arange(nddis)
|
|
419
|
+
nrows *= nddis
|
|
420
|
+
# problem: TIME - SPW
|
|
421
|
+
|
|
422
|
+
msv2.addrows(nrows)
|
|
423
|
+
# trasnposing so that indices increase with channel number, not pol
|
|
424
|
+
vis_val = np.arange(nchans * npols).reshape(
|
|
425
|
+
(npols, nchans)
|
|
426
|
+
).transpose() * np.complex64(1 - 1j)
|
|
427
|
+
# vis_val *= np.arange(
|
|
428
|
+
# msv2.putcell("CORRECTED_DATA", 0, vis_val)
|
|
429
|
+
for data_col in descr["data_cols"]:
|
|
430
|
+
msv2.putcol(data_col, np.broadcast_to(vis_val, (nrows, nchans, npols)))
|
|
431
|
+
|
|
432
|
+
# === Key attributes ===
|
|
433
|
+
# Make Ids
|
|
434
|
+
|
|
435
|
+
# TIME
|
|
436
|
+
CASACORE_TO_DATETIME_CORRECTION = 3_506_716_800.0
|
|
437
|
+
start = (
|
|
438
|
+
datetime.datetime(
|
|
439
|
+
2025, 5, 1, 1, 1, tzinfo=datetime.timezone.utc
|
|
440
|
+
).timestamp()
|
|
441
|
+
+ CASACORE_TO_DATETIME_CORRECTION
|
|
442
|
+
)
|
|
443
|
+
time_col = np.arange(nrows) + start
|
|
444
|
+
msv2.putcol("TIME", time_col)
|
|
445
|
+
|
|
446
|
+
# (TIME_EXTRA_PREC): nothing for now
|
|
447
|
+
|
|
448
|
+
nants = len(descr["ANTENNA"])
|
|
449
|
+
# ANTENNA1, ANTENNA2
|
|
450
|
+
# baselines = list(itertools.product(ants, ants))
|
|
451
|
+
# combinations without repetitions: baselines as list of tuples
|
|
452
|
+
ants = np.arange(nants)
|
|
453
|
+
baselines = list(itertools.combinations(ants, 2))
|
|
454
|
+
ant1_ant2 = list(zip(*baselines))
|
|
455
|
+
|
|
456
|
+
# TODO: needs fixes for rounding
|
|
457
|
+
_reps = np.tile(ant1_ant2[0], int(nrows / len(baselines)))
|
|
458
|
+
msv2.putcol("ANTENNA1", np.tile(ant1_ant2[0], int(nrows / len(baselines))))
|
|
459
|
+
msv2.putcol("ANTENNA2", np.tile(ant1_ant2[1], int(nrows / len(baselines))))
|
|
460
|
+
|
|
461
|
+
# (ANTENNA3): nothing for now
|
|
462
|
+
|
|
463
|
+
msv2.putcol("FEED1", np.broadcast_to(0, (nrows)))
|
|
464
|
+
|
|
465
|
+
msv2.putcol("FEED2", np.broadcast_to(0, (nrows)))
|
|
466
|
+
|
|
467
|
+
# (FEED3): nothing for now
|
|
468
|
+
|
|
469
|
+
# DATA_DESC_ID
|
|
470
|
+
# msv2.putcol("DATA_DESC_ID", np.broadcast_to(0, (nrows)))
|
|
471
|
+
ddi_col = np.repeat(dd_ids, nrows / nddis)
|
|
472
|
+
msv2.putcol("DATA_DESC_ID", np.broadcast_to(ddi_col, (nrows)))
|
|
473
|
+
|
|
474
|
+
msv2.putcol("PROCESSOR_ID", np.broadcast_to(0, (nrows)))
|
|
475
|
+
|
|
476
|
+
msv2.putcol("FIELD_ID", np.broadcast_to(0, (nrows)))
|
|
477
|
+
|
|
478
|
+
# PHASE_ID: nothing for now
|
|
479
|
+
|
|
480
|
+
# === Non-key attributes ===
|
|
481
|
+
|
|
482
|
+
# The ones that are IDs
|
|
483
|
+
msv2.putcol("OBSERVATION_ID", np.broadcast_to(0, (nrows)))
|
|
484
|
+
|
|
485
|
+
msv2.putcol("ARRAY_ID", np.broadcast_to(0, (nrows)))
|
|
486
|
+
|
|
487
|
+
msv2.putcol("SCAN_NUMBER", np.broadcast_to(1, (nrows)))
|
|
488
|
+
|
|
489
|
+
# STATE_ID: if no states/intents => all STATE_ID = -1
|
|
490
|
+
if misbehave:
|
|
491
|
+
msv2.putcol("STATE_ID", np.broadcast_to(-1, (nrows)))
|
|
492
|
+
else:
|
|
493
|
+
msv2.putcol("STATE_ID", np.broadcast_to(0, (nrows)))
|
|
494
|
+
|
|
495
|
+
# Make other scalar / inc columns
|
|
496
|
+
|
|
497
|
+
interval = 1.0
|
|
498
|
+
msv2.putcol("INTERVAL", np.broadcast_to(interval, (nrows)))
|
|
499
|
+
if misbehave:
|
|
500
|
+
msv2.putcell("INTERVAL", 0, interval - 0.1)
|
|
501
|
+
msv2.removecolkeyword("INTERVAL", "QuantumUnits")
|
|
502
|
+
|
|
503
|
+
exposure = 0.9 * interval
|
|
504
|
+
msv2.putcol("EXPOSURE", np.broadcast_to(exposure, (nrows)))
|
|
505
|
+
|
|
506
|
+
# TIME_CENTROID
|
|
507
|
+
msv2.putcol("TIME_CENTROID", time_col)
|
|
508
|
+
|
|
509
|
+
# (PULSAR_BIN)
|
|
510
|
+
|
|
511
|
+
# (PULSAR_GATE_ID)
|
|
512
|
+
|
|
513
|
+
# (BASELINE_REF)
|
|
514
|
+
|
|
515
|
+
# UVW
|
|
516
|
+
msv2.putcol("UVW", np.broadcast_to([1.0, 0.5, 1.5], (nrows, 3)))
|
|
517
|
+
|
|
518
|
+
# (UVW2)
|
|
519
|
+
|
|
520
|
+
# (DATA)
|
|
521
|
+
|
|
522
|
+
# (FLOAT_DATA)
|
|
523
|
+
|
|
524
|
+
# (VIDEO_POINT)
|
|
525
|
+
|
|
526
|
+
# (LAG_DATA)
|
|
527
|
+
|
|
528
|
+
# SIGMA
|
|
529
|
+
msv2.putcol("SIGMA", np.broadcast_to(1.0, (nrows, npols)))
|
|
530
|
+
|
|
531
|
+
# (SIGMA_SPECTRUM)
|
|
532
|
+
|
|
533
|
+
# WEIGHT
|
|
534
|
+
# msv2.putcol("WEIGHT", np.broadcast_to(1.0, (nrows, npols)))
|
|
535
|
+
|
|
536
|
+
# (WEIGHT_SPECTRUM)
|
|
537
|
+
|
|
538
|
+
msv2.putcol("FLAG", np.broadcast_to(False, (nrows, nchans, npols)))
|
|
539
|
+
|
|
540
|
+
# FLAG_CATEGORY: leave empty
|
|
541
|
+
|
|
542
|
+
msv2.putcol("FLAG_ROW", np.broadcast_to(False, (nrows)))
|
|
543
|
+
|
|
544
|
+
return outdescr
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
def gen_subt_ddi(mspath: str, spws_descr: dict, pol_setup_descr: dict):
|
|
548
|
+
"""
|
|
549
|
+
Populates DATA_DESCRIPTION
|
|
550
|
+
Q: spws_descr with IDs in keys? IDs in MSv2 are implicit 0...n-1
|
|
551
|
+
"""
|
|
552
|
+
import itertools
|
|
553
|
+
|
|
554
|
+
# TODO generate spw-pol
|
|
555
|
+
with tables.table(
|
|
556
|
+
mspath + "::DATA_DESCRIPTION", ack=False, readonly=False
|
|
557
|
+
) as ddi_tbl:
|
|
558
|
+
nrows = len(spws_descr) * len(pol_setup_descr)
|
|
559
|
+
ddi_tbl.addrows(nrows)
|
|
560
|
+
|
|
561
|
+
ddis = list(
|
|
562
|
+
itertools.product(list(spws_descr.values()), pol_setup_descr.values())
|
|
563
|
+
)
|
|
564
|
+
spw_ids = list(zip(*ddis))[0]
|
|
565
|
+
pol_ids = list(zip(*ddis))[1]
|
|
566
|
+
ddi_tbl.putcol("SPECTRAL_WINDOW_ID", spw_ids)
|
|
567
|
+
ddi_tbl.putcol("POLARIZATION_ID", pol_ids)
|
|
568
|
+
ddi_tbl.putcol("FLAG_ROW", np.broadcast_to(False, nrows))
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
def gen_subt_spw(mspath: str, spw_descr: dict, nchans: int, misbehave=False):
|
|
572
|
+
"""
|
|
573
|
+
Populates SPECTRAL_WINDOW
|
|
574
|
+
"""
|
|
575
|
+
|
|
576
|
+
nspws = len(spw_descr)
|
|
577
|
+
with tables.table(
|
|
578
|
+
mspath + "::SPECTRAL_WINDOW", ack=False, readonly=False
|
|
579
|
+
) as spw_tbl:
|
|
580
|
+
spw_tbl.addrows(nspws)
|
|
581
|
+
# Not in MSv2
|
|
582
|
+
# spw_tbl.putcol("SPECTRAL_WINDOW_ID", list(spw_descr.keys()))
|
|
583
|
+
if misbehave:
|
|
584
|
+
names = ["" for idx in range(nspws)]
|
|
585
|
+
else:
|
|
586
|
+
names = [f"unspecified_test#{idx}" for idx in range(nspws)]
|
|
587
|
+
spw_tbl.putcol("NAME", names)
|
|
588
|
+
# spw_tbl.putcol("REF_FREQUENCY", nspws*[0.9e9])
|
|
589
|
+
# spw_tbl.putcol("TOTAL_BANDWIDTH", nspws*[0.020])
|
|
590
|
+
|
|
591
|
+
for spw in range(nspws):
|
|
592
|
+
# nchans = spw_descr[spw]['NUM_CHAN']
|
|
593
|
+
# spw_tbl.addrows(1) # 1
|
|
594
|
+
|
|
595
|
+
# spw_tbl.putcell("NAME", spw, "unspecified_test")
|
|
596
|
+
spw_tbl.putcell("REF_FREQUENCY", spw, 0.9e9)
|
|
597
|
+
|
|
598
|
+
spw_tbl.putcol("NUM_CHAN", nchans, startrow=spw, nrow=1)
|
|
599
|
+
widths = np.full(nchans, 20000.0)
|
|
600
|
+
spw_tbl.putcell("CHAN_WIDTH", spw, widths)
|
|
601
|
+
if misbehave:
|
|
602
|
+
kws = spw_tbl.getcolkeywords("CHAN_WIDTH")
|
|
603
|
+
units_kw = "QuantumUnits"
|
|
604
|
+
if units_kw in kws:
|
|
605
|
+
spw_tbl.removecolkeyword("CHAN_WIDTH", "QuantumUnits")
|
|
606
|
+
# or alternatively
|
|
607
|
+
# spw_tbl.putcol("CHAN_WIDTH", np.full((1, nchans), 20000.0), startrow=spw, nrow=1)
|
|
608
|
+
spw_tbl.putcell("CHAN_FREQ", spw, np.full(nchans, 10e0))
|
|
609
|
+
spw_tbl.putcell("EFFECTIVE_BW", spw, widths)
|
|
610
|
+
spw_tbl.putcell("TOTAL_BANDWIDTH", spw, sum(widths))
|
|
611
|
+
spw_tbl.putcell("RESOLUTION", spw, widths)
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
def gen_subt_pol_setup(mspath: str, npols, pol_setup_descr: dict):
|
|
615
|
+
"""
|
|
616
|
+
populates POLARIZATION
|
|
617
|
+
"""
|
|
618
|
+
|
|
619
|
+
nsetups = len(pol_setup_descr)
|
|
620
|
+
|
|
621
|
+
with tables.table(mspath + "::POLARIZATION", ack=False, readonly=False) as pol_tbl:
|
|
622
|
+
pol_tbl.addrows(npols)
|
|
623
|
+
# pol_tbl.putcol("POLARIZATION_ID", list(spw_descr.keys()))
|
|
624
|
+
pol_tbl.putcol("NUM_CORR", nsetups * [npols])
|
|
625
|
+
pol_tbl.putcol("FLAG_ROW", nsetups * [False])
|
|
626
|
+
corr_types = [9, 11, 13, 15]
|
|
627
|
+
pol_tbl.putcol(
|
|
628
|
+
"CORR_TYPE", np.broadcast_to(corr_types[:npols], (nsetups, npols))
|
|
629
|
+
)
|
|
630
|
+
corr_products = [0, 0, 1, 1]
|
|
631
|
+
pol_tbl.putcol(
|
|
632
|
+
"CORR_PRODUCT",
|
|
633
|
+
np.broadcast_to(corr_products[:npols], (nsetups, nsetups, npols)),
|
|
634
|
+
)
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
def gen_subt_antenna(mspath: str, ant_descr: dict):
|
|
638
|
+
"""
|
|
639
|
+
create ANTENNA
|
|
640
|
+
"""
|
|
641
|
+
|
|
642
|
+
with tables.table(mspath + "::ANTENNA", ack=False, readonly=False) as ant_tbl:
|
|
643
|
+
nants = len(ant_descr)
|
|
644
|
+
ant_tbl.addrows(nants)
|
|
645
|
+
ant_tbl.putcol("NAME", [f"test_antenna_{idx}" for idx in range(nants)])
|
|
646
|
+
ant_tbl.putcol("STATION", [f"test_station_{idx}" for idx in range(nants)])
|
|
647
|
+
ant_tbl.putcol("DISH_DIAMETER", nants * [12])
|
|
648
|
+
ant_tbl.putcol("FLAG_ROW", nants * [False])
|
|
649
|
+
ant_tbl.putcol("POSITION", np.broadcast_to([0.01, 0.02, 0.03], (nants, 3)))
|
|
650
|
+
|
|
651
|
+
|
|
652
|
+
# field and state very relevant for partitions/sub-MSv2
|
|
653
|
+
def gen_subt_field(
|
|
654
|
+
mspath: str, fields_descr: dict, gen_ephem: bool = True, misbehave: bool = False
|
|
655
|
+
):
|
|
656
|
+
"""
|
|
657
|
+
creates FIELD
|
|
658
|
+
|
|
659
|
+
only supports polynomials with 1 coefficient
|
|
660
|
+
|
|
661
|
+
Parameters
|
|
662
|
+
----------
|
|
663
|
+
mspath : str
|
|
664
|
+
path of output MS
|
|
665
|
+
fields_descr : dict
|
|
666
|
+
fields description
|
|
667
|
+
gen_ephem : bool
|
|
668
|
+
whether to generate ephemeris fields (with EPHEM pseudo-sub tables
|
|
669
|
+
misbehave : bool
|
|
670
|
+
if True, some missing SOURCE_IDs will be added
|
|
671
|
+
|
|
672
|
+
Returns
|
|
673
|
+
-------
|
|
674
|
+
|
|
675
|
+
"""
|
|
676
|
+
|
|
677
|
+
with tables.table(mspath + "::FIELD", ack=False, readonly=False) as fld_tbl:
|
|
678
|
+
nfields = len(fields_descr)
|
|
679
|
+
fld_tbl.addrows(nfields)
|
|
680
|
+
fld_tbl.putcol("NAME", nfields * ["NGC3031"])
|
|
681
|
+
|
|
682
|
+
npoly = 0
|
|
683
|
+
fld_tbl.putcol("NUM_POLY", nfields * [npoly])
|
|
684
|
+
|
|
685
|
+
adir = np.deg2rad([30, 45])
|
|
686
|
+
for dir_col in ["DELAY_DIR", "PHASE_DIR", "REFERENCE_DIR"]:
|
|
687
|
+
fld_tbl.putcol(dir_col, np.broadcast_to(adir, (nfields, npoly + 1, 2)))
|
|
688
|
+
|
|
689
|
+
fld_tbl.putcol("SOURCE_ID", np.arange(nfields))
|
|
690
|
+
if misbehave:
|
|
691
|
+
fld_tbl.putcell("SOURCE_ID", nfields - 1, nfields + 3)
|
|
692
|
+
|
|
693
|
+
if gen_ephem:
|
|
694
|
+
tabdesc_ephemeris_id = {
|
|
695
|
+
"EPHEMERIS_ID": {
|
|
696
|
+
"valueType": "int",
|
|
697
|
+
"dataManagerType": "StandardStMan",
|
|
698
|
+
"dataManagerGroup": "StandardStMan",
|
|
699
|
+
"option": 0,
|
|
700
|
+
"maxlen": 0,
|
|
701
|
+
"ndim": 0,
|
|
702
|
+
"comment": "comment...",
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
fld_tbl.addcols(tabdesc_ephemeris_id)
|
|
706
|
+
fld_tbl.putcol("EPHEMERIS_ID", np.broadcast_to(0, (nfields, 1)))
|
|
707
|
+
|
|
708
|
+
if gen_ephem:
|
|
709
|
+
gen_subt_ephem(mspath, misbehave)
|
|
710
|
+
|
|
711
|
+
|
|
712
|
+
def gen_subt_ephem(mspath: str, misbehave=False):
|
|
713
|
+
"""
|
|
714
|
+
Creates a phony ephemerides table under the FIELD subtable.
|
|
715
|
+
The usual tables... context or 'default_ms_subtable' not working. Needs
|
|
716
|
+
manual coldesc /not in MSv2/3
|
|
717
|
+
|
|
718
|
+
Parameters
|
|
719
|
+
----------
|
|
720
|
+
mspath : str
|
|
721
|
+
path of output MS
|
|
722
|
+
misbehave : bool
|
|
723
|
+
if True, some metadata (keywords) will be missing: posrefsys, and metadata
|
|
724
|
+
of column 'RA'
|
|
725
|
+
|
|
726
|
+
Returns
|
|
727
|
+
-------
|
|
728
|
+
|
|
729
|
+
"""
|
|
730
|
+
# with open_opt_subtable(mspath, "FIELD/EPHEM0_FIELD.tab") as wtbl:
|
|
731
|
+
ephem0_path = Path(mspath) / "FIELD" / "EPHEM0_FIELDNAME.tab"
|
|
732
|
+
tabdesc = {
|
|
733
|
+
"MJD": {
|
|
734
|
+
"valueType": "double",
|
|
735
|
+
"dataManagerType": "StandardStMan",
|
|
736
|
+
"dataManagerGroup": "StandardStMan",
|
|
737
|
+
"option": 0,
|
|
738
|
+
"maxlen": 0,
|
|
739
|
+
"comment": "comment...",
|
|
740
|
+
"keywords": {
|
|
741
|
+
"QuantumUnits": ["s"],
|
|
742
|
+
"MEASINFO": {"type": "epoch", "Ref": "bogus MJD"},
|
|
743
|
+
},
|
|
744
|
+
},
|
|
745
|
+
"RA": {
|
|
746
|
+
"valueType": "double",
|
|
747
|
+
"dataManagerType": "StandardStMan",
|
|
748
|
+
"dataManagerGroup": "StandardStMan",
|
|
749
|
+
"option": 0,
|
|
750
|
+
"maxlen": 0,
|
|
751
|
+
"comment": "comment...",
|
|
752
|
+
"keywords": {
|
|
753
|
+
"UNIT": "deg",
|
|
754
|
+
},
|
|
755
|
+
},
|
|
756
|
+
"DEC": {
|
|
757
|
+
"valueType": "double",
|
|
758
|
+
"dataManagerType": "StandardStMan",
|
|
759
|
+
"dataManagerGroup": "StandardStMan",
|
|
760
|
+
"option": 0,
|
|
761
|
+
"maxlen": 0,
|
|
762
|
+
"comment": "comment...",
|
|
763
|
+
"keywords": {
|
|
764
|
+
"UNIT": "deg",
|
|
765
|
+
},
|
|
766
|
+
},
|
|
767
|
+
"Rho": {
|
|
768
|
+
"valueType": "double",
|
|
769
|
+
"dataManagerType": "StandardStMan",
|
|
770
|
+
"dataManagerGroup": "StandardStMan",
|
|
771
|
+
"option": 0,
|
|
772
|
+
"maxlen": 0,
|
|
773
|
+
"comment": "comment...",
|
|
774
|
+
"keywords": {
|
|
775
|
+
"UNIT": "AU",
|
|
776
|
+
},
|
|
777
|
+
},
|
|
778
|
+
"RadVel": {
|
|
779
|
+
"valueType": "double",
|
|
780
|
+
"dataManagerType": "StandardStMan",
|
|
781
|
+
"dataManagerGroup": "StandardStMan",
|
|
782
|
+
"option": 0,
|
|
783
|
+
"maxlen": 0,
|
|
784
|
+
"comment": "comment...",
|
|
785
|
+
"keywords": {
|
|
786
|
+
"UNIT": "AU/d",
|
|
787
|
+
},
|
|
788
|
+
},
|
|
789
|
+
"DiskLong": {
|
|
790
|
+
"valueType": "double",
|
|
791
|
+
"dataManagerType": "StandardStMan",
|
|
792
|
+
"dataManagerGroup": "StandardStMan",
|
|
793
|
+
"option": 0,
|
|
794
|
+
"maxlen": 0,
|
|
795
|
+
"comment": "comment...",
|
|
796
|
+
"keywords": {
|
|
797
|
+
"UNIT": "deg",
|
|
798
|
+
},
|
|
799
|
+
},
|
|
800
|
+
"DiskLat": {
|
|
801
|
+
"valueType": "double",
|
|
802
|
+
"dataManagerType": "StandardStMan",
|
|
803
|
+
"dataManagerGroup": "StandardStMan",
|
|
804
|
+
"option": 0,
|
|
805
|
+
"maxlen": 0,
|
|
806
|
+
"comment": "comment...",
|
|
807
|
+
"keywords": {
|
|
808
|
+
"UNIT": "deg",
|
|
809
|
+
},
|
|
810
|
+
},
|
|
811
|
+
"SI_lon": {
|
|
812
|
+
"valueType": "double",
|
|
813
|
+
"dataManagerType": "StandardStMan",
|
|
814
|
+
"dataManagerGroup": "StandardStMan",
|
|
815
|
+
"option": 0,
|
|
816
|
+
"maxlen": 0,
|
|
817
|
+
"comment": "comment...",
|
|
818
|
+
"keywords": {
|
|
819
|
+
"UNIT": "deg",
|
|
820
|
+
},
|
|
821
|
+
},
|
|
822
|
+
"SI_lat": {
|
|
823
|
+
"valueType": "double",
|
|
824
|
+
"dataManagerType": "StandardStMan",
|
|
825
|
+
"dataManagerGroup": "StandardStMan",
|
|
826
|
+
"option": 0,
|
|
827
|
+
"maxlen": 0,
|
|
828
|
+
"comment": "comment...",
|
|
829
|
+
"keywords": {
|
|
830
|
+
"UNIT": "deg",
|
|
831
|
+
},
|
|
832
|
+
},
|
|
833
|
+
"r": {
|
|
834
|
+
"valueType": "double",
|
|
835
|
+
"dataManagerType": "StandardStMan",
|
|
836
|
+
"dataManagerGroup": "StandardStMan",
|
|
837
|
+
"option": 0,
|
|
838
|
+
"maxlen": 0,
|
|
839
|
+
"comment": "comment...",
|
|
840
|
+
"keywords": {
|
|
841
|
+
"UNIT": "AU",
|
|
842
|
+
},
|
|
843
|
+
},
|
|
844
|
+
"rdot": {
|
|
845
|
+
"valueType": "double",
|
|
846
|
+
"dataManagerType": "StandardStMan",
|
|
847
|
+
"dataManagerGroup": "StandardStMan",
|
|
848
|
+
"option": 0,
|
|
849
|
+
"maxlen": 0,
|
|
850
|
+
"comment": "comment...",
|
|
851
|
+
"keywords": {
|
|
852
|
+
"UNIT": "km/s",
|
|
853
|
+
},
|
|
854
|
+
},
|
|
855
|
+
"phang": {
|
|
856
|
+
"valueType": "double",
|
|
857
|
+
"dataManagerType": "StandardStMan",
|
|
858
|
+
"dataManagerGroup": "StandardStMan",
|
|
859
|
+
"option": 0,
|
|
860
|
+
"maxlen": 0,
|
|
861
|
+
"comment": "comment...",
|
|
862
|
+
"keywords": {
|
|
863
|
+
"UNIT": "deg",
|
|
864
|
+
},
|
|
865
|
+
},
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
keywords = {
|
|
869
|
+
"VS_CREATE": "2021/01/02/12:33",
|
|
870
|
+
"VS_DATE": "2021/01/02/12:33",
|
|
871
|
+
"VS_VERSION": "0001.0001",
|
|
872
|
+
"MJD0": 58941.4,
|
|
873
|
+
"dMJD": 0.0138889,
|
|
874
|
+
"NAME": "Test_ephem_object",
|
|
875
|
+
"obsloc": "GEOCENTRIC",
|
|
876
|
+
"GeoLong": 0.0,
|
|
877
|
+
"GeoLat": 0.0,
|
|
878
|
+
"GeoDist": 0.0,
|
|
879
|
+
"posrefsys": "ICRF/ICRS",
|
|
880
|
+
}
|
|
881
|
+
with tables.table(
|
|
882
|
+
str(ephem0_path), tabledesc=tabdesc, nrow=1, readonly=False, ack=False
|
|
883
|
+
) as tbl:
|
|
884
|
+
|
|
885
|
+
if misbehave:
|
|
886
|
+
del tabdesc["RA"]["keywords"]
|
|
887
|
+
del keywords["posrefsys"]
|
|
888
|
+
|
|
889
|
+
tbl.putcol("MJD", 50000)
|
|
890
|
+
tbl.putcol("RA", 230.334)
|
|
891
|
+
tbl.putcol("DEC", -15.678)
|
|
892
|
+
tbl.putcol("Rho", 0.55)
|
|
893
|
+
tbl.putcol("RadVel", 0.004)
|
|
894
|
+
tbl.putcol("DiskLong", 333.01)
|
|
895
|
+
tbl.putcol("DiskLat", 4.09)
|
|
896
|
+
tbl.putcol("SI_lon", 338.81)
|
|
897
|
+
tbl.putcol("SI_lat", 2.44)
|
|
898
|
+
tbl.putcol("r", 9.1234)
|
|
899
|
+
tbl.putcol("rdot", -1.234)
|
|
900
|
+
tbl.putcol("phang", 5.6789)
|
|
901
|
+
tbl.putkeywords(keywords)
|
|
902
|
+
|
|
903
|
+
|
|
904
|
+
def gen_subt_state(mspath: str, states_descr: dict):
|
|
905
|
+
"""
|
|
906
|
+
populates STATE
|
|
907
|
+
"""
|
|
908
|
+
|
|
909
|
+
# intents in OBS_MODE strings
|
|
910
|
+
with tables.table(mspath + "::STATE", ack=False, readonly=False) as st_tbl:
|
|
911
|
+
nstates = len(states_descr)
|
|
912
|
+
st_tbl.addrows(nstates)
|
|
913
|
+
st_tbl.putcol("SIG", nstates * [True])
|
|
914
|
+
st_tbl.putcol("REF", nstates * [False])
|
|
915
|
+
st_tbl.putcol("CAL", nstates * [0.0])
|
|
916
|
+
st_tbl.putcol("LOAD", nstates * [0.0])
|
|
917
|
+
# TODO: generate subscan ids for potentially multiple scans
|
|
918
|
+
st_tbl.putcol("SUB_SCAN", 1 + np.arange(nstates))
|
|
919
|
+
st_tbl.putcol("OBS_MODE", nstates * ["scan_intent#subscan_intent"])
|
|
920
|
+
st_tbl.putcol("FLAG_ROW", nstates * [False])
|
|
921
|
+
|
|
922
|
+
|
|
923
|
+
# Other - optional subtables
|
|
924
|
+
|
|
925
|
+
|
|
926
|
+
@contextmanager
|
|
927
|
+
def open_opt_subtable(
|
|
928
|
+
mspath: str, tbl_name: str
|
|
929
|
+
) -> Generator[tables.table, None, None]:
|
|
930
|
+
"""
|
|
931
|
+
Opens an (optional) subtable of an MS. This can open tables not included
|
|
932
|
+
in the default_ms definition
|
|
933
|
+
|
|
934
|
+
Parameters
|
|
935
|
+
----------
|
|
936
|
+
mspath : str
|
|
937
|
+
path of output MS
|
|
938
|
+
tbl_name : str
|
|
939
|
+
name of the subtable (WEATHER, etc.). Requires a known optional MS
|
|
940
|
+
subtable name that has a known table description.
|
|
941
|
+
|
|
942
|
+
Returns
|
|
943
|
+
-------
|
|
944
|
+
Generator[tables.table, None, None]
|
|
945
|
+
context for an optional subtable created as per MSv2 specs
|
|
946
|
+
"""
|
|
947
|
+
subt_desc = tables.complete_ms_desc(tbl_name)
|
|
948
|
+
# table = tables.table(mspath + "/" + tbl_name, tabledesc=subt_desc,
|
|
949
|
+
# dminfo=makedminfo(subt_desc), ack=False, readonly=False)
|
|
950
|
+
table = default_ms_subtable(tbl_name, mspath + "/" + tbl_name, subt_desc)
|
|
951
|
+
try:
|
|
952
|
+
yield table
|
|
953
|
+
finally:
|
|
954
|
+
table.close()
|
|
955
|
+
|
|
956
|
+
|
|
957
|
+
def gen_subt_source(mspath: str, src_descr: dict, misbehave: bool = False):
|
|
958
|
+
"""
|
|
959
|
+
Populate SOURCE subtable, with time dependent source info
|
|
960
|
+
|
|
961
|
+
Parameters
|
|
962
|
+
----------
|
|
963
|
+
mspath : str
|
|
964
|
+
path of output MS
|
|
965
|
+
src_descr : dict
|
|
966
|
+
sources description
|
|
967
|
+
misbehave : bool
|
|
968
|
+
if misbehave, the NUM_LINES and related columns will be missing
|
|
969
|
+
|
|
970
|
+
Returns
|
|
971
|
+
-------
|
|
972
|
+
|
|
973
|
+
"""
|
|
974
|
+
|
|
975
|
+
# SOURCE is not included in default_ms
|
|
976
|
+
# with tables.table(mspath + "::SOURCE", ack=False, readonly=False) as src_tbl:
|
|
977
|
+
with open_opt_subtable(mspath, "SOURCE") as src_tbl:
|
|
978
|
+
nsrcs = len(src_descr)
|
|
979
|
+
src_tbl.addrows(nsrcs)
|
|
980
|
+
# if misbehave:
|
|
981
|
+
# src_tbl.putcol("SOURCE_ID", np.repeat(-1, len(src_descr)))
|
|
982
|
+
src_tbl.putcol("SOURCE_ID", list(src_descr.values()))
|
|
983
|
+
src_tbl.putcol("TIME", np.broadcast_to(0, (nsrcs)))
|
|
984
|
+
src_tbl.putcol("INTERVAL", np.broadcast_to(0, (nsrcs)))
|
|
985
|
+
src_tbl.putcol("SPECTRAL_WINDOW_ID", np.broadcast_to(0, (nsrcs)))
|
|
986
|
+
|
|
987
|
+
if not misbehave:
|
|
988
|
+
nlines = 3
|
|
989
|
+
src_tbl.putcol("NUM_LINES", np.repeat(nlines, nsrcs))
|
|
990
|
+
|
|
991
|
+
src_tbl.putcol("NAME", np.broadcast_to("test_source_name", (nsrcs)))
|
|
992
|
+
src_tbl.putcol("CALIBRATION_GROUP", np.broadcast_to(0, (nsrcs)))
|
|
993
|
+
src_tbl.putcol("CODE", np.broadcast_to("test_source", (nsrcs)))
|
|
994
|
+
adir = np.deg2rad([29, 34])
|
|
995
|
+
src_tbl.putcol("DIRECTION", np.broadcast_to(adir, (nsrcs, 2)))
|
|
996
|
+
src_tbl.putcol("PROPER_MOTION", np.broadcast_to(adir / 100.0, (nsrcs, 2)))
|
|
997
|
+
# optional cols:
|
|
998
|
+
if not misbehave:
|
|
999
|
+
src_tbl.putcol(
|
|
1000
|
+
"TRANSITION",
|
|
1001
|
+
np.broadcast_to(
|
|
1002
|
+
["test_line0", "test_line1", "test_line2"], (nsrcs, nlines)
|
|
1003
|
+
),
|
|
1004
|
+
)
|
|
1005
|
+
src_tbl.putcol(
|
|
1006
|
+
"REST_FREQUENCY", np.broadcast_to([1e9, 2e9, 3e9], (nsrcs, nlines))
|
|
1007
|
+
)
|
|
1008
|
+
src_tbl.putcol("SYSVEL", np.broadcast_to([1e3, 2e3, 3e3], (nsrcs, nlines)))
|
|
1009
|
+
# SOURCE_MODEL is optional and its type is TableRecord!
|
|
1010
|
+
src_tbl.removecols(["SOURCE_MODEL"])
|
|
1011
|
+
|
|
1012
|
+
with tables.table(mspath, ack=False, readonly=False) as main:
|
|
1013
|
+
main.putkeyword("SOURCE", f"Table: {mspath}/SOURCE")
|
|
1014
|
+
|
|
1015
|
+
|
|
1016
|
+
def gen_subt_pointing(mspath: str):
|
|
1017
|
+
"""
|
|
1018
|
+
Populate POINTING subtable, with antenna-based pointing info
|
|
1019
|
+
|
|
1020
|
+
Very rudimentary.
|
|
1021
|
+
"""
|
|
1022
|
+
# with open_opt_subtable(mspath, "POINTING") as tbl:
|
|
1023
|
+
nrows = 1
|
|
1024
|
+
with tables.table(mspath + "::POINTING", ack=False, readonly=False) as tbl:
|
|
1025
|
+
tbl.addrows(nrows)
|
|
1026
|
+
tbl.putcol("ANTENNA_ID", 0)
|
|
1027
|
+
tbl.putcol("TIME", 2e9)
|
|
1028
|
+
tbl.putcol("INTERVAL", 1e12)
|
|
1029
|
+
tbl.putcol("NAME", "test_pointing_name")
|
|
1030
|
+
tbl.putcol("NUM_POLY", 0)
|
|
1031
|
+
tbl.putcol("TIME_ORIGIN", 1e9)
|
|
1032
|
+
adir = np.deg2rad([28.98, 34.03])
|
|
1033
|
+
tbl.putcol("DIRECTION", np.broadcast_to(adir, (nrows, 1, 2)))
|
|
1034
|
+
tbl.putcol("TARGET", np.broadcast_to(adir + 0.01, (nrows, 1, 2)))
|
|
1035
|
+
tbl.putcol("TRACKING", True)
|
|
1036
|
+
|
|
1037
|
+
|
|
1038
|
+
# Other, more secondary subtables
|
|
1039
|
+
|
|
1040
|
+
|
|
1041
|
+
def gen_subt_feed(
|
|
1042
|
+
mspath: str,
|
|
1043
|
+
feed_descr: dict,
|
|
1044
|
+
ant_descr: str,
|
|
1045
|
+
spw_descr: str,
|
|
1046
|
+
misbehave: bool = False,
|
|
1047
|
+
):
|
|
1048
|
+
"""
|
|
1049
|
+
Populate FEED subtable, with antenna-based pointing info.
|
|
1050
|
+
|
|
1051
|
+
The table is filled for a single FEED_ID (0), for all the available
|
|
1052
|
+
antenna IDs and spectral window IDs. The columns are filled with
|
|
1053
|
+
roughtly similar values as seen in example ALMA MSs imported with
|
|
1054
|
+
importasdm. BEAM_ID is left as -1 (optional BEAM subtable of MSv2 never
|
|
1055
|
+
defined).
|
|
1056
|
+
|
|
1057
|
+
Parameters
|
|
1058
|
+
----------
|
|
1059
|
+
mspath : str
|
|
1060
|
+
path of output MS
|
|
1061
|
+
feed_descr : dict
|
|
1062
|
+
feed description, only one feed supported
|
|
1063
|
+
ant_descr : str
|
|
1064
|
+
|
|
1065
|
+
spw_descr : str
|
|
1066
|
+
|
|
1067
|
+
misbehave : bool
|
|
1068
|
+
if misbehave, the FEED table will be empty.
|
|
1069
|
+
|
|
1070
|
+
Returns
|
|
1071
|
+
-------
|
|
1072
|
+
|
|
1073
|
+
"""
|
|
1074
|
+
if misbehave:
|
|
1075
|
+
return
|
|
1076
|
+
|
|
1077
|
+
with tables.table(mspath + "::FEED", ack=False, readonly=False) as tbl:
|
|
1078
|
+
nfeeds = len(feed_descr)
|
|
1079
|
+
nspws = len(spw_descr)
|
|
1080
|
+
nants = len(ant_descr)
|
|
1081
|
+
nrows = nants * nspws * nfeeds
|
|
1082
|
+
tbl.addrows(nrows)
|
|
1083
|
+
tbl.putcol("ANTENNA_ID", np.tile(np.arange(nants), int(nrows / nants)))
|
|
1084
|
+
tbl.putcol("FEED_ID", np.repeat(0, (nrows)))
|
|
1085
|
+
tbl.putcol(
|
|
1086
|
+
"SPECTRAL_WINDOW_ID", np.repeat(list(spw_descr.values()), (nrows / nspws))
|
|
1087
|
+
)
|
|
1088
|
+
tbl.putcol("TIME", np.broadcast_to(0, (nrows)))
|
|
1089
|
+
tbl.putcol("INTERVAL", np.broadcast_to(5e9, (nrows)))
|
|
1090
|
+
nrecep = 2
|
|
1091
|
+
tbl.putcol("NUM_RECEPTORS", np.broadcast_to(nrecep, (nrows)))
|
|
1092
|
+
tbl.putcol("BEAM_ID", np.broadcast_to(-1, (nrows)))
|
|
1093
|
+
boff = np.deg2rad([0.1, 0.3])
|
|
1094
|
+
tbl.putcol("BEAM_OFFSET", np.broadcast_to(boff, (nrows, 2, nrecep)))
|
|
1095
|
+
pol_types = ["test_X, test_Y"]
|
|
1096
|
+
len_pol_types = 2
|
|
1097
|
+
tbl.putcol(
|
|
1098
|
+
"POLARIZATION_TYPE", np.broadcast_to(pol_types, (nrows, len_pol_types))
|
|
1099
|
+
)
|
|
1100
|
+
tbl.putcol("POL_RESPONSE", np.broadcast_to(0.0, (nrows, 2, nrecep)))
|
|
1101
|
+
tbl.putcol("POSITION", np.broadcast_to([0.0, 0.0, 0.0], (nrows, 3)))
|
|
1102
|
+
tbl.putcol("RECEPTOR_ANGLE", np.broadcast_to([1.51, 0.33], (nrows, nrecep)))
|
|
1103
|
+
|
|
1104
|
+
|
|
1105
|
+
def gen_subt_observation(mspath: str, obs_descr: dict):
|
|
1106
|
+
"""
|
|
1107
|
+
Populate the OBSERVATION table, with one row per observation id/number
|
|
1108
|
+
(MSv2: the id is implicitly the row number).
|
|
1109
|
+
The string columns are filled with values loosely based on example ALMA
|
|
1110
|
+
MSs.
|
|
1111
|
+
|
|
1112
|
+
Parameters
|
|
1113
|
+
----------
|
|
1114
|
+
mspath : str
|
|
1115
|
+
path of output MS
|
|
1116
|
+
obs_descr : dict
|
|
1117
|
+
obs description, with IDs, only the len is considered
|
|
1118
|
+
|
|
1119
|
+
Returns
|
|
1120
|
+
-------
|
|
1121
|
+
|
|
1122
|
+
"""
|
|
1123
|
+
|
|
1124
|
+
nobs = len(obs_descr)
|
|
1125
|
+
with tables.table(mspath + "::OBSERVATION", ack=False, readonly=False) as tbl:
|
|
1126
|
+
tbl.addrows(nobs)
|
|
1127
|
+
# no keys, all data cols:
|
|
1128
|
+
# "test_telescope" would produce errors for example in listobs:
|
|
1129
|
+
# Exception: Telescope test_telescope is not recognized by CASA.
|
|
1130
|
+
# (casa6core::MPosition casa6core::MSMetaData::getObservatoryPosition())
|
|
1131
|
+
tbl.putcol("TELESCOPE_NAME", np.repeat("ALMA", nobs))
|
|
1132
|
+
tbl.putcol("TIME_RANGE", np.broadcast_to([[4.87021e9, 4.87022e9]], (nobs, 2)))
|
|
1133
|
+
tbl.putcol("OBSERVER", np.repeat("Dr. test_observer", nobs))
|
|
1134
|
+
tbl.putcol(
|
|
1135
|
+
"LOG", np.broadcast_to(["test_obs_log_1", "test_obs_log_2"], (nobs, 2))
|
|
1136
|
+
)
|
|
1137
|
+
tbl.putcol("SCHEDULE_TYPE", np.repeat("test_schedule_type", nobs))
|
|
1138
|
+
sched = np.array(
|
|
1139
|
+
[
|
|
1140
|
+
[
|
|
1141
|
+
f"SchedulingBlock uid://A002/X1ftest/Xde{idx}",
|
|
1142
|
+
f"ExecBlock uid://A002/X1abtest/X123{idx}",
|
|
1143
|
+
]
|
|
1144
|
+
for idx in range(nobs)
|
|
1145
|
+
]
|
|
1146
|
+
)
|
|
1147
|
+
tbl.putcol("SCHEDULE", sched)
|
|
1148
|
+
proj = np.array([f"uid://A002/X1ftest/X4ec{idx}" for idx in range(nobs)])
|
|
1149
|
+
tbl.putcol("PROJECT", proj)
|
|
1150
|
+
tbl.putcol("RELEASE_DATE", np.repeat(0, nobs))
|
|
1151
|
+
|
|
1152
|
+
|
|
1153
|
+
def gen_subt_processor(mspath: str, proc_descr: dict):
|
|
1154
|
+
"""
|
|
1155
|
+
Populate the PROCESSOR table, with one row per processor id/number
|
|
1156
|
+
(MSv2: the id is implicitly the row number). The TYPE_ID is left to -1.
|
|
1157
|
+
All values of TYPE are CORRELATOR.
|
|
1158
|
+
MODE_ID are also left to -1 (no ..._MODE subtable).
|
|
1159
|
+
The string columns are filled with values loosely based on example ALMA
|
|
1160
|
+
MSs.
|
|
1161
|
+
|
|
1162
|
+
Parameters
|
|
1163
|
+
----------
|
|
1164
|
+
mspath : str
|
|
1165
|
+
path of output MS
|
|
1166
|
+
proc_descr : dict
|
|
1167
|
+
processors description, with IDs, only the len is considered
|
|
1168
|
+
|
|
1169
|
+
Returns
|
|
1170
|
+
-------
|
|
1171
|
+
|
|
1172
|
+
"""
|
|
1173
|
+
|
|
1174
|
+
nproc = len(proc_descr)
|
|
1175
|
+
with tables.table(mspath + "::PROCESSOR", ack=False, readonly=False) as tbl:
|
|
1176
|
+
tbl.addrows(nproc)
|
|
1177
|
+
# no keys, all data cols:
|
|
1178
|
+
# There could also be RADIOMETER, SPECTROMETER, PULSAR-TIMER, etc. - not nor now
|
|
1179
|
+
tbl.putcol("TYPE", np.repeat("CORRELATOR", nproc))
|
|
1180
|
+
tbl.putcol("SUB_TYPE", np.repeat("test_CORRELATOR_MODE", nproc))
|
|
1181
|
+
tbl.putcol("TYPE_ID", np.repeat(-1, nproc))
|
|
1182
|
+
tbl.putcol("MODE_ID", np.repeat(-1, nproc))
|
|
1183
|
+
tbl.putcol("FLAG_ROW", np.repeat(False, nproc))
|
|
1184
|
+
|
|
1185
|
+
|
|
1186
|
+
def gen_subt_flag_cmd(mspath: str):
|
|
1187
|
+
"""
|
|
1188
|
+
Leaves the FLAG_CMD subtable empty for now, and checks that there are no
|
|
1189
|
+
rows.
|
|
1190
|
+
"""
|
|
1191
|
+
with tables.table(mspath + "::FLAG_CMD", ack=False, readonly=False) as tbl:
|
|
1192
|
+
assert tbl.nrows() == 0
|
|
1193
|
+
|
|
1194
|
+
|
|
1195
|
+
def gen_subt_history(mspath: str, obs_descr: dict):
|
|
1196
|
+
"""
|
|
1197
|
+
Populate the HISTORY table, with only one row per observation
|
|
1198
|
+
The string columns are filled with values loosely based on example ALMA MSs.
|
|
1199
|
+
OBJECT_ID is left all to -1.
|
|
1200
|
+
|
|
1201
|
+
Parameters
|
|
1202
|
+
----------
|
|
1203
|
+
mspath : str
|
|
1204
|
+
path of output MS
|
|
1205
|
+
obs_descr : dict
|
|
1206
|
+
obs description, with IDs, only the len is considered
|
|
1207
|
+
|
|
1208
|
+
Returns
|
|
1209
|
+
-------
|
|
1210
|
+
|
|
1211
|
+
"""
|
|
1212
|
+
|
|
1213
|
+
nobs = len(obs_descr)
|
|
1214
|
+
with tables.table(mspath + "::HISTORY", ack=False, readonly=False) as tbl:
|
|
1215
|
+
tbl.addrows(nobs)
|
|
1216
|
+
# keys
|
|
1217
|
+
tbl.putcol("TIME", np.repeat(0, nobs))
|
|
1218
|
+
tbl.putcol("OBSERVATION_ID", np.arange(nobs))
|
|
1219
|
+
# data
|
|
1220
|
+
tbl.putcol("MESSAGE", np.repeat("made with test ms generator", nobs))
|
|
1221
|
+
tbl.putcol("PRIORITY", np.repeat("INFO", nobs))
|
|
1222
|
+
tbl.putcol("ORIGIN", np.repeat("ms_maker", nobs))
|
|
1223
|
+
tbl.putcol("OBJECT_ID", np.repeat(0, nobs))
|
|
1224
|
+
tbl.putcol("APPLICATION", np.repeat("xradio", nobs))
|
|
1225
|
+
tbl.putcol("CLI_COMMAND", np.broadcast_to("gen_subt_history", (nobs, 1)))
|
|
1226
|
+
tbl.putcol("APP_PARAMS", np.broadcast_to("", (nobs, 1)))
|
|
1227
|
+
|
|
1228
|
+
|
|
1229
|
+
def gen_subt_syscal(mspath: str, ant_descr: dict):
|
|
1230
|
+
"""
|
|
1231
|
+
Creates a SYSCAL subtable and populates it with a (very incomplete) row
|
|
1232
|
+
This is just to enable minimal coverage of some SYSCAL handling code in
|
|
1233
|
+
the casacore tables read/write functions.
|
|
1234
|
+
"""
|
|
1235
|
+
ncal = len(ant_descr)
|
|
1236
|
+
subt_name = "SYSCAL"
|
|
1237
|
+
with open_opt_subtable(mspath, subt_name) as sctbl:
|
|
1238
|
+
sctbl.addrows(ncal)
|
|
1239
|
+
sctbl.putcol("ANTENNA_ID", np.arange(0, ncal))
|
|
1240
|
+
sctbl.putcol("FEED_ID", np.repeat(0, ncal))
|
|
1241
|
+
sctbl.putcol("SPECTRAL_WINDOW_ID", np.repeat(0, ncal))
|
|
1242
|
+
sctbl.putcol("TIME", np.repeat(1e10, ncal))
|
|
1243
|
+
sctbl.putcol("INTERVAL", np.repeat(1e12, ncal))
|
|
1244
|
+
# all data/flags columns in the SYSCAL table are optional!
|
|
1245
|
+
# sctbl.putcol("PHASE_DIFF", np.repeat(0.3, ncal))
|
|
1246
|
+
sctbl.putcol("PHASE_DIFF", np.repeat(0.3, ncal))
|
|
1247
|
+
# sctbl.putcol("TCAL", np.broadcast_to(50.3, (ncal, 2)))
|
|
1248
|
+
sctbl.putcol("TCAL_SPECTRUM", np.broadcast_to(50.3, (ncal, 10, 2)))
|
|
1249
|
+
# TRX, etc.
|
|
1250
|
+
|
|
1251
|
+
with tables.table(mspath, ack=False, readonly=False) as main:
|
|
1252
|
+
main.putkeyword(subt_name, f"Table: {mspath}/{subt_name}")
|
|
1253
|
+
|
|
1254
|
+
|
|
1255
|
+
def gen_subt_weather(mspath: str, misbehave: bool = False):
|
|
1256
|
+
"""
|
|
1257
|
+
Creates a WEATHER subtable and populates it with a (very incomplete) row
|
|
1258
|
+
simply with a very high TIME value, as seen in some corner cases of
|
|
1259
|
+
test MSs.
|
|
1260
|
+
This is just to enable minimal coverage of some WEATHER handling code in
|
|
1261
|
+
the casacore tables read/write functions.
|
|
1262
|
+
"""
|
|
1263
|
+
subt_name = "WEATHER"
|
|
1264
|
+
tabdesc_ns_wx_station = {
|
|
1265
|
+
"NS_WX_STATION_ID": {
|
|
1266
|
+
"valueType": "int",
|
|
1267
|
+
"dataManagerType": "StandardStMan",
|
|
1268
|
+
"dataManagerGroup": "StandardStMan",
|
|
1269
|
+
"option": 0,
|
|
1270
|
+
"maxlen": 0,
|
|
1271
|
+
"comment": "comment...",
|
|
1272
|
+
"keywords": {},
|
|
1273
|
+
},
|
|
1274
|
+
"NS_WX_STATION_POSITION": {
|
|
1275
|
+
"valueType": "double",
|
|
1276
|
+
"dataManagerType": "StandardStMan",
|
|
1277
|
+
"dataManagerGroup": "StandardStMan",
|
|
1278
|
+
"option": 0,
|
|
1279
|
+
"maxlen": 0,
|
|
1280
|
+
"ndim": 1,
|
|
1281
|
+
"comment": "comment...",
|
|
1282
|
+
"keywords": {},
|
|
1283
|
+
},
|
|
1284
|
+
}
|
|
1285
|
+
nrows = 5
|
|
1286
|
+
with open_opt_subtable(mspath, subt_name) as wtbl:
|
|
1287
|
+
wtbl.addrows(nrows)
|
|
1288
|
+
wtbl.putcol("ANTENNA_ID", np.arange(0, nrows))
|
|
1289
|
+
wtbl.putcol("TIME", np.arange(0, nrows) * 100 + 1e12)
|
|
1290
|
+
wtbl.putcol("INTERVAL", np.repeat(1e12, nrows))
|
|
1291
|
+
# all data/flags columns in the WEATHER table are optional!
|
|
1292
|
+
# But note they are always added in the python-casacore defaults
|
|
1293
|
+
wtbl.putcol("H2O", np.repeat(0.03, nrows))
|
|
1294
|
+
if not misbehave:
|
|
1295
|
+
wtbl.addcols(tabdesc_ns_wx_station)
|
|
1296
|
+
wtbl.putcol("ANTENNA_ID", np.repeat(-1, nrows))
|
|
1297
|
+
wtbl.putcol("NS_WX_STATION_ID", np.arange(0, nrows))
|
|
1298
|
+
wtbl.putcol(
|
|
1299
|
+
"NS_WX_STATION_POSITION", np.broadcast_to([0.1, 0.2, 0.3], (nrows, 3))
|
|
1300
|
+
)
|
|
1301
|
+
|
|
1302
|
+
with tables.table(mspath, ack=False, readonly=False) as main:
|
|
1303
|
+
main.putkeyword(subt_name, f"Table: {mspath}/{subt_name}")
|
|
1304
|
+
|
|
1305
|
+
|
|
1306
|
+
def gen_subt_asdm_receiver(mspath: str):
|
|
1307
|
+
"""
|
|
1308
|
+
Produces an over-simple table, with only one row, for basic coverage of ASDM_* subtables handling
|
|
1309
|
+
code.
|
|
1310
|
+
"""
|
|
1311
|
+
|
|
1312
|
+
subt_name = "ASDM_RECEIVER"
|
|
1313
|
+
rec_path = Path(mspath) / subt_name
|
|
1314
|
+
tabdesc = {
|
|
1315
|
+
"receiverId": {
|
|
1316
|
+
"valueType": "int",
|
|
1317
|
+
"dataManagerType": "StandardStMan",
|
|
1318
|
+
"dataManagerGroup": "StandardStMan",
|
|
1319
|
+
"option": 0,
|
|
1320
|
+
"maxlen": 0,
|
|
1321
|
+
"comment": "comment...",
|
|
1322
|
+
"keywords": {},
|
|
1323
|
+
},
|
|
1324
|
+
"spectralWindowId": {
|
|
1325
|
+
"valueType": "int",
|
|
1326
|
+
"dataManagerType": "StandardStMan",
|
|
1327
|
+
"dataManagerGroup": "StandardStMan",
|
|
1328
|
+
"option": 0,
|
|
1329
|
+
"maxlen": 0,
|
|
1330
|
+
"comment": "comment...",
|
|
1331
|
+
"keywords": {},
|
|
1332
|
+
},
|
|
1333
|
+
"timeInterval": {
|
|
1334
|
+
"valueType": "double",
|
|
1335
|
+
"dataManagerType": "StandardStMan",
|
|
1336
|
+
"dataManagerGroup": "StandardStMan",
|
|
1337
|
+
"option": 0,
|
|
1338
|
+
"maxlen": 0,
|
|
1339
|
+
"comment": "comment...",
|
|
1340
|
+
"keywords": {},
|
|
1341
|
+
},
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
with tables.table(
|
|
1345
|
+
str(rec_path), tabledesc=tabdesc, nrow=1, readonly=False, ack=False
|
|
1346
|
+
) as tbl:
|
|
1347
|
+
tbl.putcol("receiverId", 0)
|
|
1348
|
+
tbl.putcol("spectralWindowId", 0)
|
|
1349
|
+
tbl.putcol("timeInterval", 0)
|
|
1350
|
+
|
|
1351
|
+
with tables.table(mspath, ack=False, readonly=False) as main:
|
|
1352
|
+
main.putkeyword(subt_name, f"Table: {mspath}/{subt_name}")
|
|
1353
|
+
|
|
1354
|
+
|
|
1355
|
+
def gen_subt_asdm_station(mspath: str):
|
|
1356
|
+
"""
|
|
1357
|
+
Produces an over-simple table, with only a few rows, for basic coverage of ASDM_STATION subtable
|
|
1358
|
+
handling (relevant when loading WEATHER)
|
|
1359
|
+
"""
|
|
1360
|
+
|
|
1361
|
+
subt_name = "ASDM_STATION"
|
|
1362
|
+
rec_path = Path(mspath) / subt_name
|
|
1363
|
+
tabdesc = {
|
|
1364
|
+
"stationId": {
|
|
1365
|
+
"valueType": "int",
|
|
1366
|
+
"dataManagerType": "StandardStMan",
|
|
1367
|
+
"dataManagerGroup": "StandardStMan",
|
|
1368
|
+
"option": 0,
|
|
1369
|
+
"maxlen": 0,
|
|
1370
|
+
"comment": "comment...",
|
|
1371
|
+
"keywords": {},
|
|
1372
|
+
},
|
|
1373
|
+
"name": {
|
|
1374
|
+
"valueType": "string",
|
|
1375
|
+
"dataManagerType": "StandardStMan",
|
|
1376
|
+
"dataManagerGroup": "StandardStMan",
|
|
1377
|
+
"option": 0,
|
|
1378
|
+
"maxlen": 0,
|
|
1379
|
+
"comment": "comment...",
|
|
1380
|
+
"keywords": {},
|
|
1381
|
+
},
|
|
1382
|
+
"position": {
|
|
1383
|
+
"valueType": "double",
|
|
1384
|
+
"dataManagerType": "StandardStMan",
|
|
1385
|
+
"dataManagerGroup": "StandardStMan",
|
|
1386
|
+
"option": 0,
|
|
1387
|
+
"maxlen": 0,
|
|
1388
|
+
"ndim": 1,
|
|
1389
|
+
"comment": "comment...",
|
|
1390
|
+
"keywords": {},
|
|
1391
|
+
},
|
|
1392
|
+
"type": {
|
|
1393
|
+
"valueType": "string",
|
|
1394
|
+
"dataManagerType": "StandardStMan",
|
|
1395
|
+
"dataManagerGroup": "StandardStMan",
|
|
1396
|
+
"option": 0,
|
|
1397
|
+
"maxlen": 0,
|
|
1398
|
+
"comment": "comment...",
|
|
1399
|
+
"keywords": {},
|
|
1400
|
+
},
|
|
1401
|
+
"time": {
|
|
1402
|
+
"valueType": "double",
|
|
1403
|
+
"dataManagerType": "StandardStMan",
|
|
1404
|
+
"dataManagerGroup": "StandardStMan",
|
|
1405
|
+
"option": 0,
|
|
1406
|
+
"maxlen": 0,
|
|
1407
|
+
"comment": "comment...",
|
|
1408
|
+
"keywords": {},
|
|
1409
|
+
},
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
nrows = 5
|
|
1413
|
+
with tables.table(
|
|
1414
|
+
str(rec_path), tabledesc=tabdesc, nrow=nrows, readonly=False, ack=False
|
|
1415
|
+
) as tbl:
|
|
1416
|
+
tbl.putcol("stationId", np.arange(0, nrows))
|
|
1417
|
+
tbl.putcol("position", np.broadcast_to([10, 20, 30], (nrows, 3)))
|
|
1418
|
+
tbl.putcol("name", [f"test_station{idx}" for idx in np.arange(0, nrows)])
|
|
1419
|
+
tbl.putcol(
|
|
1420
|
+
"type", np.repeat("WEATHER_STATION", nrows)
|
|
1421
|
+
) # ANTENNA_PAD / MAINTENANCE_PAD
|
|
1422
|
+
tbl.putcol("time", np.repeat(1e9, nrows))
|
|
1423
|
+
|
|
1424
|
+
with tables.table(mspath, ack=False, readonly=False) as main:
|
|
1425
|
+
main.putkeyword(subt_name, f"Table: {mspath}/{subt_name}")
|
|
1426
|
+
|
|
1427
|
+
|
|
1428
|
+
def gen_subt_asdm_execblock(mspath: str):
|
|
1429
|
+
"""
|
|
1430
|
+
Produces a basic ASDM/EXECBLOCK table, for basic coverage of code that handles the ASDM_*
|
|
1431
|
+
subtables.
|
|
1432
|
+
For now it simply creates a table with one row
|
|
1433
|
+
"""
|
|
1434
|
+
|
|
1435
|
+
subt_name = "ASDM_EXECBLOCK"
|
|
1436
|
+
rec_path = Path(mspath) / subt_name
|
|
1437
|
+
tabdesc = {
|
|
1438
|
+
"execBlockIDId": {
|
|
1439
|
+
"valueType": "string",
|
|
1440
|
+
"dataManagerType": "StandardStMan",
|
|
1441
|
+
"dataManagerGroup": "StandardStMan",
|
|
1442
|
+
"option": 0,
|
|
1443
|
+
"maxlen": 0,
|
|
1444
|
+
"comment": "comment...",
|
|
1445
|
+
"keywords": {},
|
|
1446
|
+
},
|
|
1447
|
+
"execBlockNumId": {
|
|
1448
|
+
"valueType": "string",
|
|
1449
|
+
"dataManagerType": "StandardStMan",
|
|
1450
|
+
"dataManagerGroup": "StandardStMan",
|
|
1451
|
+
"option": 0,
|
|
1452
|
+
"maxlen": 0,
|
|
1453
|
+
"comment": "comment...",
|
|
1454
|
+
"keywords": {},
|
|
1455
|
+
},
|
|
1456
|
+
"execBlockUID": {
|
|
1457
|
+
"valueType": "string",
|
|
1458
|
+
"dataManagerType": "StandardStMan",
|
|
1459
|
+
"dataManagerGroup": "StandardStMan",
|
|
1460
|
+
"option": 0,
|
|
1461
|
+
"maxlen": 0,
|
|
1462
|
+
"comment": "comment...",
|
|
1463
|
+
"keywords": {},
|
|
1464
|
+
},
|
|
1465
|
+
"sessionReference": {
|
|
1466
|
+
"valueType": "string",
|
|
1467
|
+
"dataManagerType": "StandardStMan",
|
|
1468
|
+
"dataManagerGroup": "StandardStMan",
|
|
1469
|
+
"option": 0,
|
|
1470
|
+
"maxlen": 0,
|
|
1471
|
+
"comment": "comment...",
|
|
1472
|
+
"keywords": {},
|
|
1473
|
+
},
|
|
1474
|
+
"observingScript": {
|
|
1475
|
+
"valueType": "string",
|
|
1476
|
+
"dataManagerType": "StandardStMan",
|
|
1477
|
+
"dataManagerGroup": "StandardStMan",
|
|
1478
|
+
"option": 0,
|
|
1479
|
+
"maxlen": 0,
|
|
1480
|
+
"comment": "comment...",
|
|
1481
|
+
"keywords": {},
|
|
1482
|
+
},
|
|
1483
|
+
"observingScriptUID": {
|
|
1484
|
+
"valueType": "string",
|
|
1485
|
+
"dataManagerType": "StandardStMan",
|
|
1486
|
+
"dataManagerGroup": "StandardStMan",
|
|
1487
|
+
"option": 0,
|
|
1488
|
+
"maxlen": 0,
|
|
1489
|
+
"comment": "comment...",
|
|
1490
|
+
"keywords": {},
|
|
1491
|
+
},
|
|
1492
|
+
"observingLog": {
|
|
1493
|
+
"valueType": "string",
|
|
1494
|
+
"dataManagerType": "StandardStMan",
|
|
1495
|
+
"dataManagerGroup": "StandardStMan",
|
|
1496
|
+
"option": 0,
|
|
1497
|
+
"maxlen": 0,
|
|
1498
|
+
"comment": "comment...",
|
|
1499
|
+
"ndim": 1,
|
|
1500
|
+
"_c_order": True,
|
|
1501
|
+
"keywords": {},
|
|
1502
|
+
},
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
nrows = 1
|
|
1506
|
+
with tables.table(
|
|
1507
|
+
str(rec_path), tabledesc=tabdesc, nrow=nrows, readonly=False, ack=False
|
|
1508
|
+
) as tbl:
|
|
1509
|
+
tbl.putcol("execBlockIDId", "1")
|
|
1510
|
+
tbl.putcol("execBlockNumId", "3")
|
|
1511
|
+
tbl.putcol("execBlockUID", "uid://A001/X1abtest/X123")
|
|
1512
|
+
tbl.putcol("sessionReference", "test_session_ref")
|
|
1513
|
+
tbl.putcol("observingScript", "test script")
|
|
1514
|
+
tbl.putcol("observingScriptUID", "uid://A003/X1abtest/X987")
|
|
1515
|
+
tbl.putcol("observingLog", np.broadcast_to("test log line 0", (nrows, 1)))
|
|
1516
|
+
# tbl.putcol("observingLog", np.broadcast_to(np.array(["test log line 0", "test log line 1"], dtype="str"), (nrows, 2)))
|
|
1517
|
+
|
|
1518
|
+
with tables.table(mspath, ack=False, readonly=False) as main:
|
|
1519
|
+
main.putkeyword(subt_name, f"Table: {mspath}/{subt_name}")
|
|
1520
|
+
|
|
1521
|
+
|
|
1522
|
+
def gen_subt_gain_curve(mspath: str, ant_descr: dict):
|
|
1523
|
+
"""
|
|
1524
|
+
Produces a basic GAIN_CURVE (sub)table, following casacore note #265, for basic coverage of
|
|
1525
|
+
VLBI subtables handling.
|
|
1526
|
+
"""
|
|
1527
|
+
|
|
1528
|
+
subt_name = "GAIN_CURVE"
|
|
1529
|
+
rec_path = Path(mspath) / subt_name
|
|
1530
|
+
tabdesc = {
|
|
1531
|
+
"ANTENNA_ID": {
|
|
1532
|
+
"valueType": "int",
|
|
1533
|
+
"dataManagerType": "StandardStMan",
|
|
1534
|
+
"dataManagerGroup": "StandardStMan",
|
|
1535
|
+
"option": 0,
|
|
1536
|
+
"maxlen": 0,
|
|
1537
|
+
"comment": "comment...",
|
|
1538
|
+
"keywords": {},
|
|
1539
|
+
},
|
|
1540
|
+
"FEED_ID": {
|
|
1541
|
+
"valueType": "int",
|
|
1542
|
+
"dataManagerType": "StandardStMan",
|
|
1543
|
+
"dataManagerGroup": "StandardStMan",
|
|
1544
|
+
"option": 0,
|
|
1545
|
+
"maxlen": 0,
|
|
1546
|
+
"comment": "comment...",
|
|
1547
|
+
"keywords": {},
|
|
1548
|
+
},
|
|
1549
|
+
"SPECTRAL_WINDOW_ID": {
|
|
1550
|
+
"valueType": "int",
|
|
1551
|
+
"dataManagerType": "StandardStMan",
|
|
1552
|
+
"dataManagerGroup": "StandardStMan",
|
|
1553
|
+
"option": 0,
|
|
1554
|
+
"maxlen": 0,
|
|
1555
|
+
"comment": "comment...",
|
|
1556
|
+
"keywords": {},
|
|
1557
|
+
},
|
|
1558
|
+
"TIME": {
|
|
1559
|
+
"valueType": "int",
|
|
1560
|
+
"dataManagerType": "StandardStMan",
|
|
1561
|
+
"dataManagerGroup": "StandardStMan",
|
|
1562
|
+
"option": 0,
|
|
1563
|
+
"maxlen": 0,
|
|
1564
|
+
"comment": "comment...",
|
|
1565
|
+
"keywords": {
|
|
1566
|
+
"UNIT": "s",
|
|
1567
|
+
},
|
|
1568
|
+
},
|
|
1569
|
+
"INTERVAL": {
|
|
1570
|
+
"valueType": "int",
|
|
1571
|
+
"dataManagerType": "StandardStMan",
|
|
1572
|
+
"dataManagerGroup": "StandardStMan",
|
|
1573
|
+
"option": 0,
|
|
1574
|
+
"maxlen": 0,
|
|
1575
|
+
"comment": "comment...",
|
|
1576
|
+
"keywords": {
|
|
1577
|
+
"UNIT": "s",
|
|
1578
|
+
},
|
|
1579
|
+
},
|
|
1580
|
+
"TYPE": {
|
|
1581
|
+
"valueType": "string",
|
|
1582
|
+
"dataManagerType": "StandardStMan",
|
|
1583
|
+
"dataManagerGroup": "StandardStMan",
|
|
1584
|
+
"option": 0,
|
|
1585
|
+
"maxlen": 0,
|
|
1586
|
+
"comment": "comment...",
|
|
1587
|
+
"keywords": {},
|
|
1588
|
+
},
|
|
1589
|
+
"NUM_POLY": {
|
|
1590
|
+
"valueType": "int",
|
|
1591
|
+
"dataManagerType": "StandardStMan",
|
|
1592
|
+
"dataManagerGroup": "StandardStMan",
|
|
1593
|
+
"option": 0,
|
|
1594
|
+
"maxlen": 0,
|
|
1595
|
+
"comment": "comment...",
|
|
1596
|
+
"keywords": {},
|
|
1597
|
+
},
|
|
1598
|
+
"GAIN": {
|
|
1599
|
+
"valueType": "double",
|
|
1600
|
+
"dataManagerType": "StandardStMan",
|
|
1601
|
+
"dataManagerGroup": "StandardStMan",
|
|
1602
|
+
"option": 0,
|
|
1603
|
+
"maxlen": 0,
|
|
1604
|
+
"ndim": 2,
|
|
1605
|
+
# "shape"
|
|
1606
|
+
"comment": "comment...",
|
|
1607
|
+
"keywords": {},
|
|
1608
|
+
},
|
|
1609
|
+
"SENSITIVITY": {
|
|
1610
|
+
"valueType": "double",
|
|
1611
|
+
"dataManagerType": "StandardStMan",
|
|
1612
|
+
"dataManagerGroup": "StandardStMan",
|
|
1613
|
+
"option": 0,
|
|
1614
|
+
"maxlen": 0,
|
|
1615
|
+
"ndim": 1,
|
|
1616
|
+
# "shape"
|
|
1617
|
+
"comment": "comment...",
|
|
1618
|
+
"keywords": {
|
|
1619
|
+
"UNIT": "K/Jy",
|
|
1620
|
+
},
|
|
1621
|
+
},
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
nants = len(ant_descr)
|
|
1625
|
+
nreceptors = 2
|
|
1626
|
+
num_poly = 2
|
|
1627
|
+
with tables.table(
|
|
1628
|
+
str(rec_path), tabledesc=tabdesc, nrow=nants, readonly=False, ack=False
|
|
1629
|
+
) as tbl:
|
|
1630
|
+
tbl.putcol("ANTENNA_ID", np.arange(0, nants))
|
|
1631
|
+
tbl.putcol("FEED_ID", np.repeat(0, nants))
|
|
1632
|
+
tbl.putcol("SPECTRAL_WINDOW_ID", np.repeat(0, nants))
|
|
1633
|
+
tbl.putcol("TIME", np.repeat(1e12, nants))
|
|
1634
|
+
tbl.putcol("INTERVAL", np.repeat(2, nants))
|
|
1635
|
+
tbl.putcol("TYPE", np.repeat("(”POWER(EL)", nants))
|
|
1636
|
+
tbl.putcol("NUM_POLY", np.repeat(num_poly, nants))
|
|
1637
|
+
tbl.putcol("GAIN", np.broadcast_to(0.85, (nants, nreceptors, num_poly)))
|
|
1638
|
+
tbl.putcol("SENSITIVITY", np.broadcast_to(0.95, (nants, nreceptors)))
|
|
1639
|
+
|
|
1640
|
+
with tables.table(mspath, ack=False, readonly=False) as main:
|
|
1641
|
+
main.putkeyword(subt_name, f"Table: {mspath}/{subt_name}")
|
|
1642
|
+
|
|
1643
|
+
|
|
1644
|
+
def gen_subt_phase_cal(mspath: str, ant_descr: dict):
|
|
1645
|
+
"""
|
|
1646
|
+
Produces a basic PHASE_CAL (sub)table, following casacore note #265, for basic coverage of
|
|
1647
|
+
VLBI subtables handling.
|
|
1648
|
+
Note some differences in example test datasets like VLBA_TL016B_split.ms dimensions with respect
|
|
1649
|
+
to the casacore note tables.
|
|
1650
|
+
"""
|
|
1651
|
+
|
|
1652
|
+
subt_name = "PHASE_CAL"
|
|
1653
|
+
rec_path = Path(mspath) / subt_name
|
|
1654
|
+
tabdesc = {
|
|
1655
|
+
"ANTENNA_ID": {
|
|
1656
|
+
"valueType": "int",
|
|
1657
|
+
"dataManagerType": "StandardStMan",
|
|
1658
|
+
"dataManagerGroup": "StandardStMan",
|
|
1659
|
+
"option": 0,
|
|
1660
|
+
"maxlen": 0,
|
|
1661
|
+
"comment": "comment...",
|
|
1662
|
+
"keywords": {},
|
|
1663
|
+
},
|
|
1664
|
+
"FEED_ID": {
|
|
1665
|
+
"valueType": "int",
|
|
1666
|
+
"dataManagerType": "StandardStMan",
|
|
1667
|
+
"dataManagerGroup": "StandardStMan",
|
|
1668
|
+
"option": 0,
|
|
1669
|
+
"maxlen": 0,
|
|
1670
|
+
"comment": "comment...",
|
|
1671
|
+
"keywords": {},
|
|
1672
|
+
},
|
|
1673
|
+
"SPECTRAL_WINDOW_ID": {
|
|
1674
|
+
"valueType": "int",
|
|
1675
|
+
"dataManagerType": "StandardStMan",
|
|
1676
|
+
"dataManagerGroup": "StandardStMan",
|
|
1677
|
+
"option": 0,
|
|
1678
|
+
"maxlen": 0,
|
|
1679
|
+
"comment": "comment...",
|
|
1680
|
+
"keywords": {},
|
|
1681
|
+
},
|
|
1682
|
+
"TIME": {
|
|
1683
|
+
"valueType": "int",
|
|
1684
|
+
"dataManagerType": "StandardStMan",
|
|
1685
|
+
"dataManagerGroup": "StandardStMan",
|
|
1686
|
+
"option": 0,
|
|
1687
|
+
"maxlen": 0,
|
|
1688
|
+
"comment": "comment...",
|
|
1689
|
+
"keywords": {
|
|
1690
|
+
"UNIT": "s",
|
|
1691
|
+
},
|
|
1692
|
+
},
|
|
1693
|
+
"INTERVAL": {
|
|
1694
|
+
"valueType": "int",
|
|
1695
|
+
"dataManagerType": "StandardStMan",
|
|
1696
|
+
"dataManagerGroup": "StandardStMan",
|
|
1697
|
+
"option": 0,
|
|
1698
|
+
"maxlen": 0,
|
|
1699
|
+
"comment": "comment...",
|
|
1700
|
+
"keywords": {
|
|
1701
|
+
"UNIT": "s",
|
|
1702
|
+
},
|
|
1703
|
+
},
|
|
1704
|
+
"NUM_TONES": {
|
|
1705
|
+
"valueType": "int",
|
|
1706
|
+
"dataManagerType": "StandardStMan",
|
|
1707
|
+
"dataManagerGroup": "StandardStMan",
|
|
1708
|
+
"option": 0,
|
|
1709
|
+
"maxlen": 0,
|
|
1710
|
+
"comment": "comment...",
|
|
1711
|
+
"keywords": {},
|
|
1712
|
+
},
|
|
1713
|
+
"TONE_FREQUENCY": {
|
|
1714
|
+
"valueType": "double",
|
|
1715
|
+
"dataManagerType": "StandardStMan",
|
|
1716
|
+
"dataManagerGroup": "StandardStMan",
|
|
1717
|
+
"option": 0,
|
|
1718
|
+
"maxlen": 0,
|
|
1719
|
+
"ndim": 2,
|
|
1720
|
+
# "shape":
|
|
1721
|
+
"comment": "comment...",
|
|
1722
|
+
"keywords": {
|
|
1723
|
+
"QuantumUnits": ["Hz"],
|
|
1724
|
+
"MEASINFO": {"type": "frequency", "Ref": "bogus ref frame"},
|
|
1725
|
+
},
|
|
1726
|
+
},
|
|
1727
|
+
"PHASE_CAL": {
|
|
1728
|
+
"valueType": "double",
|
|
1729
|
+
"dataManagerType": "StandardStMan",
|
|
1730
|
+
"dataManagerGroup": "StandardStMan",
|
|
1731
|
+
"option": 0,
|
|
1732
|
+
"maxlen": 0,
|
|
1733
|
+
"ndim": 2,
|
|
1734
|
+
# "shape"
|
|
1735
|
+
"comment": "comment...",
|
|
1736
|
+
"keywords": {},
|
|
1737
|
+
},
|
|
1738
|
+
"CABLE_CAL": {
|
|
1739
|
+
"valueType": "double",
|
|
1740
|
+
"dataManagerType": "StandardStMan",
|
|
1741
|
+
"dataManagerGroup": "StandardStMan",
|
|
1742
|
+
"option": 0,
|
|
1743
|
+
"maxlen": 0,
|
|
1744
|
+
# "ndim": 1,
|
|
1745
|
+
# "shape"
|
|
1746
|
+
"comment": "comment...",
|
|
1747
|
+
"keywords": {
|
|
1748
|
+
"QuantumUnits": "s",
|
|
1749
|
+
},
|
|
1750
|
+
},
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
nants = len(ant_descr)
|
|
1754
|
+
nreceptors = 2
|
|
1755
|
+
ntones = 3
|
|
1756
|
+
_ntimes = 1
|
|
1757
|
+
with tables.table(
|
|
1758
|
+
str(rec_path), tabledesc=tabdesc, nrow=nants, readonly=False, ack=False
|
|
1759
|
+
) as tbl:
|
|
1760
|
+
tbl.putcol("ANTENNA_ID", np.arange(0, nants))
|
|
1761
|
+
tbl.putcol("FEED_ID", np.repeat(0, nants))
|
|
1762
|
+
tbl.putcol("SPECTRAL_WINDOW_ID", np.repeat(0, nants))
|
|
1763
|
+
tbl.putcol("TIME", np.repeat(1e12, nants))
|
|
1764
|
+
tbl.putcol("INTERVAL", np.repeat(2, nants))
|
|
1765
|
+
tbl.putcol("NUM_TONES", np.repeat(ntones, nants))
|
|
1766
|
+
tbl.putcol(
|
|
1767
|
+
"TONE_FREQUENCY", np.broadcast_to(1.234e9, (nants, ntones, nreceptors))
|
|
1768
|
+
)
|
|
1769
|
+
tbl.putcol("PHASE_CAL", np.broadcast_to(0.92, (nants, ntones, nreceptors)))
|
|
1770
|
+
tbl.putcol("CABLE_CAL", np.repeat(0.93, (nants)))
|
|
1771
|
+
|
|
1772
|
+
with tables.table(mspath, ack=False, readonly=False) as main:
|
|
1773
|
+
main.putkeyword(subt_name, f"Table: {mspath}/{subt_name}")
|
|
1774
|
+
|
|
1775
|
+
|
|
1776
|
+
# Functions from io.py that depend on casacore
|
|
1777
|
+
|
|
1778
|
+
|
|
1779
|
+
def build_processing_set_from_msv2(
|
|
1780
|
+
in_file: str | Path,
|
|
1781
|
+
out_file: str | Path,
|
|
1782
|
+
*,
|
|
1783
|
+
partition_scheme: Optional[Iterable[dict]] = None,
|
|
1784
|
+
persistence_mode: str = "w",
|
|
1785
|
+
parallel_mode: str = "partition",
|
|
1786
|
+
**convert_kwargs: Any,
|
|
1787
|
+
) -> Path:
|
|
1788
|
+
"""
|
|
1789
|
+
Convert an MSv2 dataset into a processing set using the production converter.
|
|
1790
|
+
"""
|
|
1791
|
+
from xradio.measurement_set import convert_msv2_to_processing_set
|
|
1792
|
+
|
|
1793
|
+
convert_msv2_to_processing_set(
|
|
1794
|
+
in_file=str(in_file),
|
|
1795
|
+
out_file=str(out_file),
|
|
1796
|
+
partition_scheme=list(partition_scheme or []),
|
|
1797
|
+
persistence_mode=persistence_mode,
|
|
1798
|
+
parallel_mode=parallel_mode,
|
|
1799
|
+
**convert_kwargs,
|
|
1800
|
+
)
|
|
1801
|
+
return Path(out_file)
|
|
1802
|
+
|
|
1803
|
+
|
|
1804
|
+
def build_msv4_partition(
|
|
1805
|
+
ms_path: str | Path,
|
|
1806
|
+
out_root: str | Path,
|
|
1807
|
+
*,
|
|
1808
|
+
msv4_id: str = "msv4id",
|
|
1809
|
+
partition_kwargs: Optional[Dict[str, Any]] = None,
|
|
1810
|
+
use_table_iter: bool = False,
|
|
1811
|
+
persistence_mode: str = "w",
|
|
1812
|
+
) -> Path:
|
|
1813
|
+
"""
|
|
1814
|
+
Convert a MeasurementSet v2 partition into an MSv4 Zarr tree.
|
|
1815
|
+
"""
|
|
1816
|
+
from xradio.measurement_set._utils._msv2.conversion import (
|
|
1817
|
+
convert_and_write_partition,
|
|
1818
|
+
)
|
|
1819
|
+
|
|
1820
|
+
partition_kwargs = partition_kwargs or {"DATA_DESC_ID": [0]}
|
|
1821
|
+
convert_and_write_partition(
|
|
1822
|
+
str(ms_path),
|
|
1823
|
+
str(out_root),
|
|
1824
|
+
msv4_id,
|
|
1825
|
+
partition_kwargs,
|
|
1826
|
+
use_table_iter=use_table_iter,
|
|
1827
|
+
persistence_mode=persistence_mode,
|
|
1828
|
+
)
|
|
1829
|
+
return Path(out_root) / f"{Path(ms_path).stem}_{msv4_id}"
|
|
1830
|
+
|
|
1831
|
+
|
|
1832
|
+
def build_minimal_msv4_xdt(
|
|
1833
|
+
ms_path: str | Path,
|
|
1834
|
+
*,
|
|
1835
|
+
out_root: str | Path | None = None,
|
|
1836
|
+
msv4_id: str = "msv4id",
|
|
1837
|
+
partition_kwargs: Optional[Dict[str, Any]] = None,
|
|
1838
|
+
use_table_iter: bool = False,
|
|
1839
|
+
persistence_mode: str = "w",
|
|
1840
|
+
) -> Path:
|
|
1841
|
+
"""
|
|
1842
|
+
Convenience wrapper that selects reasonable defaults for the minimal MSv4 conversion.
|
|
1843
|
+
"""
|
|
1844
|
+
|
|
1845
|
+
if out_root is None:
|
|
1846
|
+
out_root = Path(f"{Path(ms_path).stem}_processing_set.zarr")
|
|
1847
|
+
return build_msv4_partition(
|
|
1848
|
+
ms_path,
|
|
1849
|
+
out_root,
|
|
1850
|
+
msv4_id=msv4_id,
|
|
1851
|
+
partition_kwargs=partition_kwargs,
|
|
1852
|
+
use_table_iter=use_table_iter,
|
|
1853
|
+
persistence_mode=persistence_mode,
|
|
1854
|
+
)
|