xarray-ms 0.4.0a2__tar.gz → 0.4.0a4__tar.gz
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.
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/.github/workflows/ci.yml +2 -23
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/PKG-INFO +1 -1
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/doc/source/changelog.rst +8 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/doc/source/conf.py +1 -1
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/pyproject.toml +2 -2
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/tests/test_antenna.py +59 -0
- xarray_ms-0.4.0a4/tests/test_table_utils.py +89 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/tests/test_write.py +10 -10
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/xarray_ms/backend/msv2/factories/antenna.py +5 -1
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/xarray_ms/backend/msv2/factories/correlated.py +2 -1
- xarray_ms-0.4.0a4/xarray_ms/backend/msv2/table_utils.py +53 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/xarray_ms/backend/msv2/writes.py +124 -126
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/xarray_ms/errors.py +5 -0
- xarray_ms-0.4.0a2/xarray_ms/backend/msv2/table_utils.py +0 -19
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/.github/ISSUE_TEMPLATE.md +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/.github/dependabot.yml +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/.github/workflows/pre-commit.yml +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/.github/workflows/readthedocs.yml +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/.gitignore +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/.pre-commit-config.yaml +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/.readthedocs.yaml +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/LICENSE +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/README.rst +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/doc/Makefile +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/doc/make.bat +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/doc/source/api.rst +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/doc/source/index.rst +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/doc/source/install.rst +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/doc/source/introduction.rst +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/doc/source/links.rst +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/doc/source/partitioning.rst +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/doc/source/roadmap.rst +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/doc/source/tutorial.rst +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/hello.txt +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/tests/__init__.py +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/tests/conftest.py +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/tests/msv4_test_corpus/__init__.py +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/tests/msv4_test_corpus/conftest.py +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/tests/msv4_test_corpus/test_msv_corpus.py +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/tests/test_backend.py +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/tests/test_basic.py +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/tests/test_field_and_source.py +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/tests/test_github.py +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/tests/test_imputation.py +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/tests/test_measures.py +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/tests/test_multiton.py +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/tests/test_read.py +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/tests/test_structure.py +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/tests/test_utils.py +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/tests/test_zarr_roundtrip.py +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/xarray_ms/__init__.py +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/xarray_ms/backend/msv2/array.py +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/xarray_ms/backend/msv2/entrypoint.py +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/xarray_ms/backend/msv2/entrypoint_utils.py +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/xarray_ms/backend/msv2/factories/__init__.py +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/xarray_ms/backend/msv2/factories/core.py +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/xarray_ms/backend/msv2/factories/field_and_source.py +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/xarray_ms/backend/msv2/imputation.py +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/xarray_ms/backend/msv2/measures_adapters.py +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/xarray_ms/backend/msv2/measures_encoders.py +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/xarray_ms/backend/msv2/partition.py +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/xarray_ms/backend/msv2/structure.py +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/xarray_ms/casa_types.py +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/xarray_ms/msv4_types.py +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/xarray_ms/multiton.py +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/xarray_ms/query.py +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/xarray_ms/testing/__init__.py +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/xarray_ms/testing/simulator.py +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/xarray_ms/testing/utils.py +0 -0
- {xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/xarray_ms/utils.py +0 -0
|
@@ -29,29 +29,8 @@ jobs:
|
|
|
29
29
|
strategy:
|
|
30
30
|
fail-fast: false
|
|
31
31
|
matrix:
|
|
32
|
-
os: ["ubuntu-22.04", "macos-14", "macos-15"]
|
|
33
|
-
python-version: ["3.11", "3.12", "3.13"]
|
|
34
|
-
is_main_or_release:
|
|
35
|
-
- ${{ contains(github.ref, 'main') || startsWith(github.ref, 'refs/tags')}}
|
|
36
|
-
exclude:
|
|
37
|
-
# Don't test on macos-{13,14} in PR's
|
|
38
|
-
- is_main_or_release: false
|
|
39
|
-
os: "macos-14"
|
|
40
|
-
# Don't test on 3.11 - 3.12 in PR's
|
|
41
|
-
- is_main_or_release: false
|
|
42
|
-
python-version: "3.11"
|
|
43
|
-
- is_main_or_release: false
|
|
44
|
-
python-version: "3.12"
|
|
45
|
-
# Don't test on macos on 3.11 - 3.12 in PRs
|
|
46
|
-
- os: "macos-14"
|
|
47
|
-
python-version: "3.11"
|
|
48
|
-
- os: "macos-14"
|
|
49
|
-
python-version: "3.12"
|
|
50
|
-
- os: "macos-15"
|
|
51
|
-
python-version: "3.11"
|
|
52
|
-
- os: "macos-15"
|
|
53
|
-
python-version: "3.12"
|
|
54
|
-
|
|
32
|
+
os: ["ubuntu-22.04", "macos-13", "macos-14", "macos-15"]
|
|
33
|
+
python-version: ["3.11", "3.12", "3.13", "3.14"]
|
|
55
34
|
|
|
56
35
|
steps:
|
|
57
36
|
- name: Set up Python ${{ matrix.python-version }}
|
|
@@ -3,6 +3,14 @@
|
|
|
3
3
|
Changelog
|
|
4
4
|
=========
|
|
5
5
|
|
|
6
|
+
X.Y.Z (DD-MM-YYYY)
|
|
7
|
+
------------------
|
|
8
|
+
* Handle duplicate antenna name (:pr:`151`)
|
|
9
|
+
* Deprecate python 3.10 support (:pr:`150`)
|
|
10
|
+
* Add python 3.14 to CI testing matrix (:pr:`150`)
|
|
11
|
+
* Test more comprehensively in pull requests (:pr:`150`)
|
|
12
|
+
* Depend on arcae ``>= 0.5.1, < 0.6.0`` (:pr:`149`)
|
|
13
|
+
|
|
6
14
|
0.5.1 (06-03-2026)
|
|
7
15
|
------------------
|
|
8
16
|
* Correct ``BroadcastMSv2Array`` initialisation of ``MSv2Array`` base class
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
project = "xarray-ms"
|
|
12
12
|
copyright = "2024 - 2025 NRF (SARAO) and Rhodes University (RATT) Centre"
|
|
13
13
|
author = "Simon Perkins"
|
|
14
|
-
release = "0.4.0-alpha.
|
|
14
|
+
release = "0.4.0-alpha.4"
|
|
15
15
|
|
|
16
16
|
# -- General configuration ---------------------------------------------------
|
|
17
17
|
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "xarray-ms"
|
|
3
|
-
version = "0.4.0-alpha.
|
|
3
|
+
version = "0.4.0-alpha.4"
|
|
4
4
|
description = "xarray MSv4 views over MSv2 Measurement Sets"
|
|
5
5
|
authors = [{name = "Simon Perkins", email = "simon.perkins@gmail.com"}]
|
|
6
6
|
readme = "README.rst"
|
|
@@ -55,7 +55,7 @@ extend-select = ["I"]
|
|
|
55
55
|
# github_url = "https://github.com/<user or organization>/<project>/"
|
|
56
56
|
|
|
57
57
|
[tool.tbump.version]
|
|
58
|
-
current = "0.4.0-alpha.
|
|
58
|
+
current = "0.4.0-alpha.4"
|
|
59
59
|
|
|
60
60
|
# https://semver.org/#spec-item-9
|
|
61
61
|
regex = '''
|
|
@@ -4,6 +4,8 @@ import pytest
|
|
|
4
4
|
import xarray
|
|
5
5
|
from arcae.lib.arrow_tables import Table, ms_descriptor
|
|
6
6
|
|
|
7
|
+
from xarray_ms.errors import DuplicateAntennaNameWarning
|
|
8
|
+
|
|
7
9
|
NANTENNA = 6
|
|
8
10
|
|
|
9
11
|
|
|
@@ -138,3 +140,60 @@ def test_antenna_feed_join(simmed_ms, auto_corrs):
|
|
|
138
140
|
npt.assert_array_equal(
|
|
139
141
|
p1["antenna_xds"].antenna_name, np.array(["ANTENNA-0", "ANTENNA-2"], dtype=object)
|
|
140
142
|
)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@pytest.mark.parametrize(
|
|
146
|
+
"simmed_ms",
|
|
147
|
+
[{"name": "dup.ms", "nantenna": 3, "auto_corrs": True}],
|
|
148
|
+
indirect=True,
|
|
149
|
+
)
|
|
150
|
+
def test_duplicate_antenna_names(simmed_ms):
|
|
151
|
+
"""When ANTENNA::NAME contains duplicates, names are made unique and a
|
|
152
|
+
warning is emitted. The names in antenna_xds and the baseline coordinate
|
|
153
|
+
must be consistent with each other."""
|
|
154
|
+
|
|
155
|
+
# Overwrite ANTENNA::NAME so that antenna 0 and antenna 1 share a name.
|
|
156
|
+
with Table.from_filename(f"{simmed_ms}::ANTENNA") as T:
|
|
157
|
+
T.putcol("NAME", np.asarray(["SAME", "SAME", "OTHER"]))
|
|
158
|
+
|
|
159
|
+
with pytest.warns(DuplicateAntennaNameWarning, match="SAME"):
|
|
160
|
+
dt = xarray.open_datatree(simmed_ms, auto_corrs=True).load()
|
|
161
|
+
|
|
162
|
+
partition = dt[list(dt.children)[0]]
|
|
163
|
+
antenna_xds = partition["antenna_xds"]
|
|
164
|
+
|
|
165
|
+
# All antenna_name coordinate values must be unique
|
|
166
|
+
ant_names = antenna_xds.antenna_name.values
|
|
167
|
+
assert len(ant_names) == len(set(ant_names)), "antenna_name coordinate has duplicates"
|
|
168
|
+
|
|
169
|
+
# Duplicated base names get -N suffixes; unique names are unchanged
|
|
170
|
+
assert "SAME-1" in ant_names
|
|
171
|
+
assert "SAME-2" in ant_names
|
|
172
|
+
assert "OTHER" in ant_names
|
|
173
|
+
|
|
174
|
+
# correlated dataset baseline_antenna{i}_name maps
|
|
175
|
+
# appropriately to the antenna dataset antenna names.
|
|
176
|
+
concat_ant_names = np.concatenate(
|
|
177
|
+
[partition.baseline_antenna1_name, partition.baseline_antenna2_name]
|
|
178
|
+
)
|
|
179
|
+
unique_ant_names, inv = np.unique(concat_ant_names, return_inverse=True)
|
|
180
|
+
_, lookup = np.where(unique_ant_names[:, None] == ant_names[None, :])
|
|
181
|
+
ant1 = lookup[inv[: len(inv) // 2]]
|
|
182
|
+
ant2 = lookup[inv[len(inv) // 2 :]]
|
|
183
|
+
|
|
184
|
+
np.testing.assert_array_equal(partition.baseline_antenna1_name, ant_names[ant1])
|
|
185
|
+
np.testing.assert_array_equal(partition.baseline_antenna2_name, ant_names[ant2])
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
@pytest.mark.parametrize(
|
|
189
|
+
"simmed_ms",
|
|
190
|
+
[{"name": "nodup.ms", "nantenna": 3, "auto_corrs": True}],
|
|
191
|
+
indirect=True,
|
|
192
|
+
)
|
|
193
|
+
def test_unique_antenna_names_no_warning(simmed_ms):
|
|
194
|
+
"""When all antenna names are already unique no warning should be emitted."""
|
|
195
|
+
import warnings
|
|
196
|
+
|
|
197
|
+
with warnings.catch_warnings():
|
|
198
|
+
warnings.simplefilter("error", DuplicateAntennaNameWarning)
|
|
199
|
+
xarray.open_datatree(simmed_ms, auto_corrs=True).load()
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import warnings
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import numpy.testing as npt
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from xarray_ms.backend.msv2.table_utils import unique_antenna_names
|
|
8
|
+
from xarray_ms.errors import DuplicateAntennaNameWarning
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_all_unique_no_change():
|
|
12
|
+
names = np.array(["A", "B", "C"])
|
|
13
|
+
result = unique_antenna_names(names)
|
|
14
|
+
npt.assert_array_equal(result, names)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_all_unique_no_warning():
|
|
18
|
+
names = np.array(["A", "B", "C"])
|
|
19
|
+
with warnings.catch_warnings():
|
|
20
|
+
warnings.simplefilter("error", DuplicateAntennaNameWarning)
|
|
21
|
+
unique_antenna_names(names) # should not raise
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_two_duplicates():
|
|
25
|
+
names = np.array(["A", "A", "B"])
|
|
26
|
+
with pytest.warns(DuplicateAntennaNameWarning):
|
|
27
|
+
result = unique_antenna_names(names)
|
|
28
|
+
npt.assert_array_equal(result, ["A-1", "A-2", "B"])
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_triple_duplicate():
|
|
32
|
+
names = np.array(["A", "A", "A"])
|
|
33
|
+
with pytest.warns(DuplicateAntennaNameWarning):
|
|
34
|
+
result = unique_antenna_names(names)
|
|
35
|
+
npt.assert_array_equal(result, ["A-1", "A-2", "A-3"])
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_non_contiguous_duplicates():
|
|
39
|
+
names = np.array(["A", "B", "A", "C", "A"])
|
|
40
|
+
with pytest.warns(DuplicateAntennaNameWarning):
|
|
41
|
+
result = unique_antenna_names(names)
|
|
42
|
+
npt.assert_array_equal(result, ["A-1", "B", "A-2", "C", "A-3"])
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_unique_name_unchanged_when_mixed():
|
|
46
|
+
names = np.array(["X", "Y", "X"])
|
|
47
|
+
with pytest.warns(DuplicateAntennaNameWarning):
|
|
48
|
+
result = unique_antenna_names(names)
|
|
49
|
+
npt.assert_array_equal(result, ["X-1", "Y", "X-2"])
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_underscore_in_base_name():
|
|
53
|
+
"""Dash separator avoids collision with names already containing underscores."""
|
|
54
|
+
names = np.array(["ANTENNA_1", "ANTENNA_1"])
|
|
55
|
+
with pytest.warns(DuplicateAntennaNameWarning):
|
|
56
|
+
result = unique_antenna_names(names)
|
|
57
|
+
npt.assert_array_equal(result, ["ANTENNA_1-1", "ANTENNA_1-2"])
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test_empty_array():
|
|
61
|
+
names = np.array([], dtype=str)
|
|
62
|
+
result = unique_antenna_names(names)
|
|
63
|
+
npt.assert_array_equal(result, [])
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_single_element():
|
|
67
|
+
names = np.array(["A"])
|
|
68
|
+
result = unique_antenna_names(names)
|
|
69
|
+
npt.assert_array_equal(result, ["A"])
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def test_warning_raised_on_duplicates():
|
|
73
|
+
names = np.array(["A", "A", "B"])
|
|
74
|
+
with pytest.warns(DuplicateAntennaNameWarning, match="Duplicate antenna names"):
|
|
75
|
+
unique_antenna_names(names)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_warning_lists_duplicate_names():
|
|
79
|
+
names = np.array(["FOO", "BAR", "FOO"])
|
|
80
|
+
with pytest.warns(DuplicateAntennaNameWarning, match="FOO"):
|
|
81
|
+
unique_antenna_names(names)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def test_result_is_always_unique():
|
|
85
|
+
"""Output names must all be distinct regardless of input."""
|
|
86
|
+
names = np.array(["A", "A", "B", "B", "C"])
|
|
87
|
+
with pytest.warns(DuplicateAntennaNameWarning):
|
|
88
|
+
result = unique_antenna_names(names)
|
|
89
|
+
assert len(set(result.tolist())) == len(result)
|
|
@@ -31,8 +31,8 @@ def test_store(simmed_ms):
|
|
|
31
31
|
xdt[node.path] = DataTree(ds)
|
|
32
32
|
assert len(node.encoding) > 0
|
|
33
33
|
|
|
34
|
-
xdt.sync_msv2(
|
|
35
|
-
xdt.to_msv2(
|
|
34
|
+
xdt.sync_msv2()
|
|
35
|
+
xdt.to_msv2()
|
|
36
36
|
written = written or True
|
|
37
37
|
|
|
38
38
|
with xarray.open_datatree(simmed_ms, auto_corrs=True) as xdt:
|
|
@@ -64,7 +64,7 @@ def test_store_region(simmed_ms):
|
|
|
64
64
|
assert len(node.encoding) > 0
|
|
65
65
|
|
|
66
66
|
# Create the new MS columns
|
|
67
|
-
xdt.sync_msv2(
|
|
67
|
+
xdt.sync_msv2()
|
|
68
68
|
|
|
69
69
|
for node in xdt.subtree:
|
|
70
70
|
if node.attrs.get("type") in CORRELATED_DATASET_TYPES:
|
|
@@ -72,7 +72,7 @@ def test_store_region(simmed_ms):
|
|
|
72
72
|
ds = ds.isel(**region)
|
|
73
73
|
ds = ds.assign(CORRECTED=xarray.full_like(ds.CORRECTED, 1 + 2j))
|
|
74
74
|
# Now write it out
|
|
75
|
-
ds.to_msv2(
|
|
75
|
+
ds.to_msv2(compute=False, region=region)
|
|
76
76
|
|
|
77
77
|
# We can check that CORRECTED has been written correctly
|
|
78
78
|
with arcae.table(simmed_ms) as T:
|
|
@@ -115,12 +115,12 @@ def test_distributed_write(simmed_ms, processes, nworkers, chunks):
|
|
|
115
115
|
assert len(node.encoding) > 0
|
|
116
116
|
|
|
117
117
|
# Create the new MS columns
|
|
118
|
-
dt.sync_msv2(
|
|
119
|
-
dt.to_msv2(
|
|
118
|
+
dt.sync_msv2()
|
|
119
|
+
dt.to_msv2(compute=False)
|
|
120
120
|
|
|
121
121
|
for node in dt.subtree:
|
|
122
122
|
if node.attrs.get("type") in CORRELATED_DATASET_TYPES:
|
|
123
|
-
node.ds.to_msv2(
|
|
123
|
+
node.ds.to_msv2(compute=True)
|
|
124
124
|
|
|
125
125
|
with arcae.table(simmed_ms) as T:
|
|
126
126
|
corrected = T.getcol("CORRECTED")
|
|
@@ -144,11 +144,11 @@ def test_indexed_write(simmed_ms):
|
|
|
144
144
|
ds = node.ds.assign(CORRECTED=xarray.full_like(node.VISIBILITY, 1 + 2j))
|
|
145
145
|
dt[node.path] = DataTree(ds)
|
|
146
146
|
|
|
147
|
-
dt.sync_msv2(
|
|
148
|
-
dt.to_msv2(
|
|
147
|
+
dt.sync_msv2()
|
|
148
|
+
dt.to_msv2(compute=False)
|
|
149
149
|
|
|
150
150
|
for node in dt.subtree:
|
|
151
151
|
if node.attrs.get("type") in CORRELATED_DATASET_TYPES:
|
|
152
152
|
ds = node.ds.isel(time=slice(0, 2), baseline_id=slice(0, 2), frequency=1)
|
|
153
153
|
with pytest.raises(MismatchedWriteRegion):
|
|
154
|
-
ds.to_msv2(
|
|
154
|
+
ds.to_msv2(compute=True)
|
|
@@ -4,6 +4,7 @@ from xarray import Dataset, Variable
|
|
|
4
4
|
from xarray_ms.backend.msv2.factories.core import DatasetFactory
|
|
5
5
|
from xarray_ms.backend.msv2.imputation import maybe_impute_observation_table
|
|
6
6
|
from xarray_ms.backend.msv2.measures_encoders import MSv2CoderFactory
|
|
7
|
+
from xarray_ms.backend.msv2.table_utils import unique_antenna_names
|
|
7
8
|
from xarray_ms.errors import InvalidMeasurementSet
|
|
8
9
|
|
|
9
10
|
RELOCATABLE_ARRAY = {"ALMA", "VLA", "NOEMA", "EVLA"}
|
|
@@ -53,7 +54,10 @@ class AntennaFactory(DatasetFactory):
|
|
|
53
54
|
)
|
|
54
55
|
|
|
55
56
|
ant_coder_factory = MSv2CoderFactory.from_arrow_table(filtered_ants)
|
|
56
|
-
|
|
57
|
+
# Deduplicate against the full ANTENNA table so suffix assignments are
|
|
58
|
+
# consistent with those produced in the correlated dataset factory.
|
|
59
|
+
all_ant_names = unique_antenna_names(ants["NAME"].to_numpy().astype(str))
|
|
60
|
+
antenna_names = all_ant_names[feed_ant_id[mask]]
|
|
57
61
|
telescope_names = np.asarray([telescope_name] * len(antenna_names), dtype=str)
|
|
58
62
|
position = pac.list_flatten(filtered_ants["POSITION"]).to_numpy().reshape(-1, 3)
|
|
59
63
|
diameter = filtered_ants["DISH_DIAMETER"].to_numpy()
|
|
@@ -26,6 +26,7 @@ from xarray_ms.backend.msv2.measures_encoders import (
|
|
|
26
26
|
VisibilityCoder,
|
|
27
27
|
)
|
|
28
28
|
from xarray_ms.backend.msv2.structure import MSv2StructureFactory, PartitionKeyT
|
|
29
|
+
from xarray_ms.backend.msv2.table_utils import unique_antenna_names
|
|
29
30
|
from xarray_ms.casa_types import ColumnDesc, Polarisations
|
|
30
31
|
from xarray_ms.errors import (
|
|
31
32
|
ColumnShapeImputationWarning,
|
|
@@ -266,7 +267,7 @@ class CorrelatedFactory(DatasetFactory):
|
|
|
266
267
|
assert (partition.nbl,) == ant1.shape
|
|
267
268
|
|
|
268
269
|
antenna = self._subtable_factories["ANTENNA"].instance
|
|
269
|
-
ant_names = antenna["NAME"].to_numpy().astype(str)
|
|
270
|
+
ant_names = unique_antenna_names(antenna["NAME"].to_numpy().astype(str))
|
|
270
271
|
ant1_names = ant_names[ant1]
|
|
271
272
|
ant2_names = ant_names[ant2]
|
|
272
273
|
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import warnings
|
|
3
|
+
from typing import Any, Dict
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
import pyarrow as pa
|
|
7
|
+
|
|
8
|
+
from xarray_ms.errors import DuplicateAntennaNameWarning
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def unique_antenna_names(names: np.ndarray) -> np.ndarray:
|
|
12
|
+
"""Return antenna names with duplicates made unique by appending -N suffixes.
|
|
13
|
+
|
|
14
|
+
All occurrences of a duplicated name are renamed: the first gets ``-1``,
|
|
15
|
+
the second ``-2``, and so on. Names that are already unique are left
|
|
16
|
+
unchanged. A :class:`~xarray_ms.errors.DuplicateAntennaNameWarning` is
|
|
17
|
+
emitted when any renaming is performed.
|
|
18
|
+
"""
|
|
19
|
+
unique, counts = np.unique(names, return_counts=True)
|
|
20
|
+
if not (duplicates := set(unique[counts > 1].tolist())):
|
|
21
|
+
return names
|
|
22
|
+
|
|
23
|
+
warnings.warn(
|
|
24
|
+
f"Duplicate antenna names detected in the ANTENNA table: "
|
|
25
|
+
f"{sorted(duplicates)}. "
|
|
26
|
+
f"A numeric suffix will be appended to make names unique.",
|
|
27
|
+
DuplicateAntennaNameWarning,
|
|
28
|
+
stacklevel=2,
|
|
29
|
+
)
|
|
30
|
+
# Build as a Python list to avoid numpy fixed-width string truncation,
|
|
31
|
+
# then convert back to an array with a wide enough dtype.
|
|
32
|
+
result = names.tolist()
|
|
33
|
+
counters: Dict[str, int] = {}
|
|
34
|
+
for i, name in enumerate(result):
|
|
35
|
+
if name in duplicates:
|
|
36
|
+
counters[name] = counters.get(name, 0) + 1
|
|
37
|
+
result[i] = f"{name}-{counters[name]}"
|
|
38
|
+
return np.array(result, dtype=str)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def extract_table_desc(table: pa.Table) -> Dict[str, Any]:
|
|
42
|
+
"""Extract the CASA table descriptor stored in an Arrow table constructed by arcae"""
|
|
43
|
+
try:
|
|
44
|
+
arcae_metadata = table.schema.metadata[b"__arcae_metadata__"]
|
|
45
|
+
except KeyError:
|
|
46
|
+
raise KeyError("__arcae_metadata__ was not present in the table metadata")
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
return json.loads(arcae_metadata)["__casa_descriptor__"]
|
|
50
|
+
except KeyError:
|
|
51
|
+
raise KeyError(
|
|
52
|
+
f"arcae metadata {arcae_metadata} does not contain a __casa_descriptor__"
|
|
53
|
+
)
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import warnings
|
|
2
2
|
from collections import defaultdict
|
|
3
|
+
from dataclasses import dataclass
|
|
3
4
|
from importlib.metadata import version as package_version
|
|
4
|
-
from typing import Any, Dict,
|
|
5
|
+
from typing import Any, Dict, List, Literal, Mapping, Set, Tuple
|
|
5
6
|
|
|
6
7
|
import numpy as np
|
|
7
8
|
import numpy.typing as npt
|
|
@@ -25,7 +26,40 @@ else:
|
|
|
25
26
|
|
|
26
27
|
ShapeSetType = Set[Tuple[int, ...]]
|
|
27
28
|
DTypeSetType = Set[npt.DTypeLike]
|
|
28
|
-
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class DataVariableInfo:
|
|
33
|
+
counts: int
|
|
34
|
+
shapes: ShapeSetType
|
|
35
|
+
dtypes: DTypeSetType
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
DataVariableInfoMap = Dict[Tuple[str, str], DataVariableInfo]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
MSV4_WRITE_MAP = {
|
|
42
|
+
"UVW": "UVW",
|
|
43
|
+
"VISIBILITY": "DATA",
|
|
44
|
+
"WEIGHT": "WEIGHT_SPECTRUM",
|
|
45
|
+
"FLAG": "FLAG",
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# MSv4 variables that will not be written back to
|
|
50
|
+
# the MSv2 as they are effectively metadata
|
|
51
|
+
IGNORED_VARIABLES = [
|
|
52
|
+
# Standard MSv4 variables
|
|
53
|
+
"EFFECTIVE_INTEGRATION_TIME",
|
|
54
|
+
"TIME_CENTROID",
|
|
55
|
+
"TIME_CENTROID_EXTRA_PRECISION",
|
|
56
|
+
"EFFECTIVE_CHANNEL_WIDTH",
|
|
57
|
+
"FREQUENCY_CENTROID",
|
|
58
|
+
# Added by Correlated Factory if grids are irregular
|
|
59
|
+
"TIME",
|
|
60
|
+
"INTEGRATION_TIME",
|
|
61
|
+
"CHANNEL_WIDTH",
|
|
62
|
+
]
|
|
29
63
|
|
|
30
64
|
|
|
31
65
|
def validate_column_desc(
|
|
@@ -100,7 +134,7 @@ def fit_tile_shape(shape: Tuple[int, ...], dtype: npt.DTypeLike) -> Dict[str, np
|
|
|
100
134
|
|
|
101
135
|
def generate_column_descriptor(
|
|
102
136
|
table_desc: Dict[str, Any],
|
|
103
|
-
|
|
137
|
+
data_var_map: DataVariableInfoMap,
|
|
104
138
|
) -> Tuple[Dict[str, Any], Dict[str, Any]]:
|
|
105
139
|
"""
|
|
106
140
|
Synthesises a column descriptor from:
|
|
@@ -113,9 +147,8 @@ def generate_column_descriptor(
|
|
|
113
147
|
Args:
|
|
114
148
|
table_desc: Table descriptor containing existing
|
|
115
149
|
column definitions.
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
Variable.
|
|
150
|
+
data_var_map: A mapping of shapes and data types
|
|
151
|
+
associated with each xarray Variable.
|
|
119
152
|
|
|
120
153
|
Returns:
|
|
121
154
|
A (descriptor, dminfo) tuple containing any columns
|
|
@@ -125,23 +158,22 @@ def generate_column_descriptor(
|
|
|
125
158
|
actual_desc = {}
|
|
126
159
|
dm_groups = []
|
|
127
160
|
|
|
128
|
-
for (var_name,
|
|
161
|
+
for (var_name, msv2_column), data_var_info in data_var_map.items():
|
|
129
162
|
# If there are existing descriptors, either for
|
|
130
163
|
# columns present on the table, or in the canonical definition
|
|
131
164
|
# validate that the variable shape matches the column
|
|
132
|
-
if column_desc := table_desc.get(
|
|
133
|
-
validate_column_desc(var_name,
|
|
134
|
-
elif column_desc := canonical_table_desc.get(
|
|
135
|
-
validate_column_desc(var_name,
|
|
165
|
+
if column_desc := table_desc.get(msv2_column):
|
|
166
|
+
validate_column_desc(var_name, msv2_column, data_var_info.shapes, column_desc)
|
|
167
|
+
elif column_desc := canonical_table_desc.get(msv2_column):
|
|
168
|
+
validate_column_desc(var_name, msv2_column, data_var_info.shapes, column_desc)
|
|
136
169
|
else:
|
|
137
|
-
# Construct a column descriptor and possibly an
|
|
138
|
-
# associated data manager
|
|
170
|
+
# Construct a column descriptor and possibly an associated data manager
|
|
139
171
|
# Unify variable numpy types
|
|
140
|
-
dtype = np.result_type(*dtypes)
|
|
172
|
+
dtype = np.result_type(*data_var_info.dtypes)
|
|
141
173
|
|
|
142
174
|
if dtype is object:
|
|
143
175
|
raise NotImplementedError(
|
|
144
|
-
f"Types of variable {var_name} ({list(dtypes)}) "
|
|
176
|
+
f"Types of variable {var_name} ({list(data_var_info.dtypes)}) "
|
|
145
177
|
f"resolves to an object. "
|
|
146
178
|
f"Writing of objects is not supported"
|
|
147
179
|
)
|
|
@@ -155,21 +187,21 @@ def generate_column_descriptor(
|
|
|
155
187
|
|
|
156
188
|
column_desc = {"valueType": casa_type, "option": 0}
|
|
157
189
|
|
|
158
|
-
if len(shapes) == 1:
|
|
190
|
+
if len(data_var_info.shapes) == 1:
|
|
159
191
|
# If the shape is fixed, Tile the column
|
|
160
192
|
# column descriptor shapes are fortran ordered
|
|
161
|
-
fixed_shape = tuple(reversed(next(iter(shapes))))
|
|
193
|
+
fixed_shape = tuple(reversed(next(iter(data_var_info.shapes))))
|
|
162
194
|
row_only = len(fixed_shape) == 0
|
|
163
195
|
if not row_only:
|
|
164
196
|
column_desc["option"] |= 4
|
|
165
197
|
column_desc["shape"] = list(fixed_shape)
|
|
166
198
|
column_desc["ndim"] = len(fixed_shape)
|
|
167
|
-
column_desc["dataManagerGroup"] = dm_group = f"{
|
|
199
|
+
column_desc["dataManagerGroup"] = dm_group = f"{msv2_column}_GROUP"
|
|
168
200
|
column_desc["dataManagerType"] = dm_type = "TiledColumnStMan"
|
|
169
201
|
|
|
170
202
|
dm_groups.append(
|
|
171
203
|
{
|
|
172
|
-
"COLUMNS": [
|
|
204
|
+
"COLUMNS": [msv2_column],
|
|
173
205
|
"NAME": dm_group,
|
|
174
206
|
"TYPE": dm_type,
|
|
175
207
|
"SPEC": fit_tile_shape(fixed_shape, dtype),
|
|
@@ -182,7 +214,7 @@ def generate_column_descriptor(
|
|
|
182
214
|
column_desc["dataManagerGroup"] = "StandardStMan"
|
|
183
215
|
column_desc["dataManagerType"] = "StandardStMan"
|
|
184
216
|
|
|
185
|
-
actual_desc[
|
|
217
|
+
actual_desc[msv2_column] = column_desc
|
|
186
218
|
|
|
187
219
|
dminfo = {f"*{i + 1}": g for i, g in enumerate(dm_groups)}
|
|
188
220
|
return actual_desc, dminfo
|
|
@@ -216,68 +248,39 @@ def msv2_store_from_dataset(ds: Dataset, region="auto") -> MSv2Store:
|
|
|
216
248
|
)
|
|
217
249
|
|
|
218
250
|
|
|
219
|
-
|
|
251
|
+
WriteMapT = Tuple[str, str] | List[Tuple[str, str]] | Dict[str, str] | None
|
|
220
252
|
|
|
221
253
|
|
|
222
|
-
def
|
|
254
|
+
def promote_write_map(variables: WriteMapT) -> Dict[str, str]:
|
|
255
|
+
"""Promotes the supplied write variable mapping into a Dict[str, str]"""
|
|
223
256
|
type_except = TypeError(
|
|
224
257
|
f"{variables} should be one of:\n"
|
|
225
|
-
f"1. a str
|
|
226
|
-
f"2.
|
|
227
|
-
f"3. A
|
|
228
|
-
f"4. A Dict[str, str] variable -> column mapping"
|
|
258
|
+
f"1. a Tuple[str, str] variable -> column mapping\n"
|
|
259
|
+
f"2. An Iterable[Tuple[str, str]].\n"
|
|
260
|
+
f"3. A Dict[str, str] variable -> column mapping"
|
|
229
261
|
)
|
|
230
262
|
|
|
231
|
-
def
|
|
232
|
-
if
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
return
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
return {src: dest for src, dest in map(check_var, variables)}
|
|
250
|
-
|
|
251
|
-
if isinstance(variables, dict):
|
|
252
|
-
for k, v in variables.items():
|
|
253
|
-
if not isinstance(k, str) or not isinstance(v, str):
|
|
254
|
-
raise type_except
|
|
255
|
-
|
|
256
|
-
raise type_except
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
IGNORED_VARIABLES = [
|
|
260
|
-
# Standard MSv4 variables
|
|
261
|
-
# spectrum_xds
|
|
262
|
-
"SPECTRUM",
|
|
263
|
-
# visibility_xds
|
|
264
|
-
"VISIBILITY",
|
|
265
|
-
"FLAG",
|
|
266
|
-
"WEIGHT",
|
|
267
|
-
"UVW",
|
|
268
|
-
"EFFECTIVE_INTEGRATION_TIME",
|
|
269
|
-
"TIME_CENTROID",
|
|
270
|
-
"TIME_CENTROID_EXTRA_PRECISION",
|
|
271
|
-
"EFFECTIVE_CHANNEL_WIDTH",
|
|
272
|
-
"FREQUENCY_CENTROID",
|
|
273
|
-
# Added by Correlated Factory if grids are irregular
|
|
274
|
-
"TIME",
|
|
275
|
-
"INTEGRATION_TIME",
|
|
276
|
-
"CHANNEL_WIDTH",
|
|
277
|
-
]
|
|
263
|
+
def check_tuple(tup: Tuple[str, str]) -> Tuple[str, str]:
|
|
264
|
+
if (
|
|
265
|
+
isinstance(tup, tuple) and len(tup) == 2 and all(isinstance(t, str) for t in tup)
|
|
266
|
+
):
|
|
267
|
+
return tup
|
|
268
|
+
|
|
269
|
+
raise type_except
|
|
270
|
+
|
|
271
|
+
if variables is None:
|
|
272
|
+
return {}
|
|
273
|
+
elif isinstance(variables, dict):
|
|
274
|
+
return variables
|
|
275
|
+
elif isinstance(variables, tuple):
|
|
276
|
+
return dict([check_tuple(variables)])
|
|
277
|
+
elif isinstance(variables, (list, set)):
|
|
278
|
+
return dict([check_tuple(v) for v in variables])
|
|
279
|
+
else:
|
|
280
|
+
raise type_except
|
|
278
281
|
|
|
279
282
|
|
|
280
|
-
def sync_msv2(dt: DataTree,
|
|
283
|
+
def sync_msv2(dt: DataTree, write_map: WriteMapT = None):
|
|
281
284
|
assert isinstance(dt, DataTree)
|
|
282
285
|
vis_datasets = [
|
|
283
286
|
n for n in dt.subtree if n.attrs.get("type") in CORRELATED_DATASET_TYPES
|
|
@@ -291,61 +294,56 @@ def sync_msv2(dt: DataTree, variables: WriteVariablesT):
|
|
|
291
294
|
table_factory = msv2_store_from_dataset(next(iter(vis_datasets)).ds).table_factory
|
|
292
295
|
table_desc = table_factory.instance.tabledesc()
|
|
293
296
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
# columns = table_factory.instance.columns()
|
|
298
|
-
|
|
299
|
-
list_var_names: List[Any] = (
|
|
300
|
-
[variables] if isinstance(variables, str) else list(variables)
|
|
297
|
+
write_map = {**MSV4_WRITE_MAP, **promote_write_map(write_map)}
|
|
298
|
+
var_info_map: DataVariableInfoMap = defaultdict(
|
|
299
|
+
lambda: DataVariableInfo(0, set(), set())
|
|
301
300
|
)
|
|
302
|
-
set_var_names = set(list_var_names)
|
|
303
|
-
|
|
304
|
-
if len(set_var_names) == 0:
|
|
305
|
-
warnings.warn("Empty 'variables'")
|
|
306
|
-
return
|
|
307
|
-
|
|
308
|
-
shapes_and_dtypes: ShapeAndDTypeType = defaultdict(lambda: (set(), set()))
|
|
309
301
|
|
|
310
|
-
for node in vis_datasets:
|
|
302
|
+
for nodes_visited, node in enumerate(vis_datasets, 1):
|
|
311
303
|
assert isinstance(node, DataTree)
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
)
|
|
316
|
-
|
|
317
|
-
for n in set_var_names:
|
|
318
|
-
var = node.data_vars[n]
|
|
304
|
+
for name, var in node.data_vars.items():
|
|
305
|
+
if name in IGNORED_VARIABLES:
|
|
306
|
+
continue
|
|
319
307
|
|
|
308
|
+
# Don't try to create anything that doesn't translate
|
|
309
|
+
# to a MAIN MSv2 row dimension
|
|
320
310
|
if var.dims[: len(MAIN_PREFIX_DIMS)] != MAIN_PREFIX_DIMS:
|
|
321
|
-
|
|
322
|
-
f"{
|
|
311
|
+
warnings.warn(
|
|
312
|
+
f"Ignoring {name} in {node.path} "
|
|
313
|
+
f"dimensions {var.dims} do not begin with {MAIN_PREFIX_DIMS}",
|
|
314
|
+
UserWarning,
|
|
323
315
|
)
|
|
316
|
+
continue
|
|
317
|
+
|
|
318
|
+
entry = var_info_map[(name, write_map.get(name, name))]
|
|
319
|
+
entry.counts += 1
|
|
320
|
+
entry.shapes.add(var.shape[len(MAIN_PREFIX_DIMS) :])
|
|
321
|
+
entry.dtypes.add(var.dtype)
|
|
322
|
+
|
|
323
|
+
# Ensure that the identified variables are universally
|
|
324
|
+
# defined across all DataTree.
|
|
325
|
+
for key, entry in list(var_info_map.items()):
|
|
326
|
+
if entry.counts != nodes_visited:
|
|
327
|
+
warnings.warn(
|
|
328
|
+
f"Ignoring {key[0]} which does not appear in all "
|
|
329
|
+
f"visibility datasets {list(dt.children)}",
|
|
330
|
+
UserWarning,
|
|
331
|
+
)
|
|
332
|
+
var_info_map.pop(key)
|
|
324
333
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
dtypes.add(var.dtype)
|
|
328
|
-
|
|
329
|
-
table_factory = msv2_store_from_dataset(next(iter(vis_datasets)).ds)._table_factory
|
|
330
|
-
table_desc = table_factory.instance.tabledesc()
|
|
331
|
-
column_descs, dminfo = generate_column_descriptor(table_desc, shapes_and_dtypes)
|
|
334
|
+
# Generate column descriptors and add the columns
|
|
335
|
+
column_descs, dminfo = generate_column_descriptor(table_desc, var_info_map)
|
|
332
336
|
table_factory.instance.addcols(column_descs, dminfo)
|
|
333
337
|
assert set(column_descs.keys()).issubset(table_factory.instance.columns())
|
|
334
338
|
|
|
335
339
|
|
|
336
340
|
def datatree_to_msv2(
|
|
337
341
|
dt: DataTree,
|
|
338
|
-
|
|
342
|
+
write_map: WriteMapT = None,
|
|
339
343
|
compute: Literal[True] = True,
|
|
340
344
|
write_inherited_coords: bool = False,
|
|
341
|
-
):
|
|
345
|
+
) -> None:
|
|
342
346
|
assert isinstance(dt, DataTree)
|
|
343
|
-
list_var_names = [variables] if isinstance(variables, str) else list(variables)
|
|
344
|
-
set_var_names = set(list_var_names)
|
|
345
|
-
|
|
346
|
-
if len(set_var_names) == 0:
|
|
347
|
-
warnings.warn("Empty 'variables'")
|
|
348
|
-
return
|
|
349
347
|
|
|
350
348
|
if (
|
|
351
349
|
len(
|
|
@@ -363,32 +361,32 @@ def datatree_to_msv2(
|
|
|
363
361
|
for node in vis_datasets:
|
|
364
362
|
at_root = node is dt.root
|
|
365
363
|
ds = node.to_dataset(inherit=write_inherited_coords or at_root)
|
|
366
|
-
dataset_to_msv2(ds,
|
|
364
|
+
dataset_to_msv2(ds, write_map=write_map, compute=compute)
|
|
367
365
|
|
|
368
366
|
|
|
369
367
|
def dataset_to_msv2(
|
|
370
368
|
ds: Dataset,
|
|
371
|
-
|
|
369
|
+
write_map: WriteMapT = None,
|
|
372
370
|
compute: Literal[True] = True,
|
|
373
371
|
region: Mapping[str, slice | Literal["auto"]] | Literal["auto"] = "auto",
|
|
374
|
-
):
|
|
372
|
+
) -> None:
|
|
375
373
|
assert isinstance(ds, Dataset)
|
|
376
|
-
list_vars = [variables] if isinstance(variables, str) else list(variables)
|
|
377
|
-
|
|
378
|
-
if len(list_vars) == 0:
|
|
379
|
-
return
|
|
380
374
|
|
|
381
375
|
# Strip out
|
|
382
|
-
# 1. variables
|
|
376
|
+
# 1. ancilliary variables
|
|
383
377
|
# 2. coordinates
|
|
384
378
|
# 3. attributes
|
|
385
|
-
|
|
386
|
-
|
|
379
|
+
write_map = {**MSV4_WRITE_MAP, **promote_write_map(write_map)}
|
|
380
|
+
ignored = set(IGNORED_VARIABLES) & set(ds.data_vars)
|
|
381
|
+
write_ds = ds.drop_vars(ignored | set(ds.coords)).drop_attrs()
|
|
382
|
+
write_ds = write_ds.rename_vars(
|
|
383
|
+
{k: v for k, v in write_map.items() if k in write_ds.data_vars}
|
|
384
|
+
)
|
|
387
385
|
|
|
388
|
-
msv2_store = msv2_store_from_dataset(
|
|
389
|
-
msv2_store.set_write_region(
|
|
386
|
+
msv2_store = msv2_store_from_dataset(write_ds, region)
|
|
387
|
+
msv2_store.set_write_region(write_ds)
|
|
390
388
|
writer = ArrayWriter()
|
|
391
|
-
dump_to_store(
|
|
389
|
+
dump_to_store(write_ds, msv2_store, writer)
|
|
392
390
|
writes = writer.sync(compute=compute)
|
|
393
391
|
|
|
394
392
|
if compute:
|
|
@@ -30,6 +30,11 @@ class ImputedMetadataWarning(MissingMetadataWarning):
|
|
|
30
30
|
if the original metadata is missing"""
|
|
31
31
|
|
|
32
32
|
|
|
33
|
+
class DuplicateAntennaNameWarning(UserWarning):
|
|
34
|
+
"""Warning raised when duplicate antenna names are found in the ANTENNA table
|
|
35
|
+
and are made unique by appending a numeric suffix"""
|
|
36
|
+
|
|
37
|
+
|
|
33
38
|
class FrameConversionWarning(UserWarning):
|
|
34
39
|
"""Warning raised if there's no defined conversion from a CASA
|
|
35
40
|
to astropy reference frame"""
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
from typing import Any, Dict
|
|
3
|
-
|
|
4
|
-
import pyarrow as pa
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def extract_table_desc(table: pa.Table) -> Dict[str, Any]:
|
|
8
|
-
"""Extract the CASA table descriptor stored in an Arrow table constructed by arcae"""
|
|
9
|
-
try:
|
|
10
|
-
arcae_metadata = table.schema.metadata[b"__arcae_metadata__"]
|
|
11
|
-
except KeyError:
|
|
12
|
-
raise KeyError("__arcae_metadata__ was not present in the table metadata")
|
|
13
|
-
|
|
14
|
-
try:
|
|
15
|
-
return json.loads(arcae_metadata)["__casa_descriptor__"]
|
|
16
|
-
except KeyError:
|
|
17
|
-
raise KeyError(
|
|
18
|
-
f"arcae metadata {arcae_metadata} does not contain a __casa_descriptor__"
|
|
19
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{xarray_ms-0.4.0a2 → xarray_ms-0.4.0a4}/xarray_ms/backend/msv2/factories/field_and_source.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|