uda-xarray 0.1.0__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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Samuel Jackson
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,133 @@
1
+ Metadata-Version: 2.4
2
+ Name: uda-xarray
3
+ Version: 0.1.0
4
+ Summary: UDA backend for xarray
5
+ Author-email: Samuel Jackson <samuel.jackson@ukaea.uk>
6
+ Maintainer-email: Samuel Jackson <samuel.jackson@ukaea.uk>
7
+ Project-URL: repository, https://github.com/samueljackson92/uda-xarray
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Requires-Python: <3.13,>=3.11
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+ Requires-Dist: uda>=2.9.2
17
+ Requires-Dist: uda-mast
18
+ Requires-Dist: xarray>=2025.12.0
19
+ Dynamic: license-file
20
+
21
+ # uda-xarray
22
+
23
+ An xarray backend for UDA (Universal Data Access) that enables seamless integration of UDA data sources with the xarray ecosystem.
24
+
25
+ ## Overview
26
+
27
+ uda-xarray provides a backend plugin for xarray that allows you to access UDA data sources using xarray's familiar API. It automatically handles the conversion of UDA signals to xarray DataArrays and Datasets, making it easy to work with UDA data in scientific Python workflows.
28
+
29
+ ## Features
30
+
31
+ - **xarray Integration**: Access UDA data using xarray's `open_dataset` function
32
+ - **Automatic Conversion**: Converts UDA signals to xarray DataArrays with proper coordinates and metadata
33
+ - **Error Handling**: Includes error data alongside signal data
34
+ - **URI-based Access**: Simple URI scheme (`uda://signal_name:shot`) for accessing data
35
+
36
+ ## Installation
37
+
38
+ ```bash
39
+ pip install uda-xarray
40
+ ```
41
+
42
+ Or using `uv`:
43
+
44
+ ```bash
45
+ uv pip install uda-xarray
46
+ ```
47
+
48
+ ## Usage
49
+
50
+ Open a UDA dataset using xarray:
51
+
52
+ ```python
53
+ import xarray as xr
54
+
55
+ # Open a UDA signal by name and shot number
56
+ ds = xr.open_dataset("uda://ip:30421", engine="uda")
57
+
58
+ # Access the data
59
+ data = ds["data"]
60
+ errors = ds["error"]
61
+
62
+ # The dataset includes time coordinates
63
+ time = ds["time"]
64
+
65
+ # Access metadata
66
+ units = ds["data"].attrs["units"]
67
+ signal_name = ds["data"].attrs["uda_name"]
68
+ ```
69
+
70
+ The URI format is: `uda://<signal_name>:<shot_number>`
71
+
72
+ ## Data Structure
73
+
74
+ When you open a UDA dataset, uda-xarray creates an xarray Dataset with:
75
+
76
+ - **data**: The signal data as a DataArray
77
+ - **error**: The error data as a DataArray
78
+ - **time**: Time coordinates (dimension)
79
+ - **attrs**: Metadata including units and UDA signal name
80
+
81
+ ## Limitations
82
+
83
+ - Currently only supports 1D signals (2D signals will raise `NotImplementedError`)
84
+ - Requires a working UDA client connection
85
+
86
+ ## Requirements
87
+
88
+ - Python >= 3.11, < 3.13
89
+ - uda >= 2.9.2
90
+ - xarray >= 2025.12.0
91
+
92
+ ## Development
93
+
94
+ ### Setup
95
+
96
+ Clone the repository and install development dependencies:
97
+
98
+ ```bash
99
+ git clone https://github.com/samueljackson92/uda-xarray.git
100
+ cd uda-xarray
101
+ uv sync
102
+ ```
103
+
104
+ ### Running Tests
105
+
106
+ ```bash
107
+ pytest tests/
108
+ ```
109
+
110
+ ### Code Quality
111
+
112
+ The project uses ruff for linting and formatting, and pylint for additional checks:
113
+
114
+ ```bash
115
+ # Run ruff
116
+ uv run ruff check uda_xarray tests
117
+ uv run ruff format uda_xarray tests
118
+
119
+ # Run pylint
120
+ uv run pylint uda_xarray
121
+ ```
122
+
123
+ ## Contributing
124
+
125
+ Contributions are welcome! Please ensure:
126
+
127
+ 1. Tests pass for any new functionality
128
+ 2. Code follows the project's style guidelines (ruff and pylint)
129
+ 3. Documentation is updated as needed
130
+
131
+ ## License
132
+
133
+ MIT License
@@ -0,0 +1,113 @@
1
+ # uda-xarray
2
+
3
+ An xarray backend for UDA (Universal Data Access) that enables seamless integration of UDA data sources with the xarray ecosystem.
4
+
5
+ ## Overview
6
+
7
+ uda-xarray provides a backend plugin for xarray that allows you to access UDA data sources using xarray's familiar API. It automatically handles the conversion of UDA signals to xarray DataArrays and Datasets, making it easy to work with UDA data in scientific Python workflows.
8
+
9
+ ## Features
10
+
11
+ - **xarray Integration**: Access UDA data using xarray's `open_dataset` function
12
+ - **Automatic Conversion**: Converts UDA signals to xarray DataArrays with proper coordinates and metadata
13
+ - **Error Handling**: Includes error data alongside signal data
14
+ - **URI-based Access**: Simple URI scheme (`uda://signal_name:shot`) for accessing data
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ pip install uda-xarray
20
+ ```
21
+
22
+ Or using `uv`:
23
+
24
+ ```bash
25
+ uv pip install uda-xarray
26
+ ```
27
+
28
+ ## Usage
29
+
30
+ Open a UDA dataset using xarray:
31
+
32
+ ```python
33
+ import xarray as xr
34
+
35
+ # Open a UDA signal by name and shot number
36
+ ds = xr.open_dataset("uda://ip:30421", engine="uda")
37
+
38
+ # Access the data
39
+ data = ds["data"]
40
+ errors = ds["error"]
41
+
42
+ # The dataset includes time coordinates
43
+ time = ds["time"]
44
+
45
+ # Access metadata
46
+ units = ds["data"].attrs["units"]
47
+ signal_name = ds["data"].attrs["uda_name"]
48
+ ```
49
+
50
+ The URI format is: `uda://<signal_name>:<shot_number>`
51
+
52
+ ## Data Structure
53
+
54
+ When you open a UDA dataset, uda-xarray creates an xarray Dataset with:
55
+
56
+ - **data**: The signal data as a DataArray
57
+ - **error**: The error data as a DataArray
58
+ - **time**: Time coordinates (dimension)
59
+ - **attrs**: Metadata including units and UDA signal name
60
+
61
+ ## Limitations
62
+
63
+ - Currently only supports 1D signals (2D signals will raise `NotImplementedError`)
64
+ - Requires a working UDA client connection
65
+
66
+ ## Requirements
67
+
68
+ - Python >= 3.11, < 3.13
69
+ - uda >= 2.9.2
70
+ - xarray >= 2025.12.0
71
+
72
+ ## Development
73
+
74
+ ### Setup
75
+
76
+ Clone the repository and install development dependencies:
77
+
78
+ ```bash
79
+ git clone https://github.com/samueljackson92/uda-xarray.git
80
+ cd uda-xarray
81
+ uv sync
82
+ ```
83
+
84
+ ### Running Tests
85
+
86
+ ```bash
87
+ pytest tests/
88
+ ```
89
+
90
+ ### Code Quality
91
+
92
+ The project uses ruff for linting and formatting, and pylint for additional checks:
93
+
94
+ ```bash
95
+ # Run ruff
96
+ uv run ruff check uda_xarray tests
97
+ uv run ruff format uda_xarray tests
98
+
99
+ # Run pylint
100
+ uv run pylint uda_xarray
101
+ ```
102
+
103
+ ## Contributing
104
+
105
+ Contributions are welcome! Please ensure:
106
+
107
+ 1. Tests pass for any new functionality
108
+ 2. Code follows the project's style guidelines (ruff and pylint)
109
+ 3. Documentation is updated as needed
110
+
111
+ ## License
112
+
113
+ MIT License
@@ -0,0 +1,70 @@
1
+ [project]
2
+ name = "uda-xarray"
3
+ version = "0.1.0"
4
+ description = "UDA backend for xarray"
5
+ readme = "README.md"
6
+ requires-python = ">=3.11, <3.13"
7
+ authors = [
8
+ {name = "Samuel Jackson", email = "samuel.jackson@ukaea.uk"},
9
+ ]
10
+ maintainers = [
11
+ {name = "Samuel Jackson", email = "samuel.jackson@ukaea.uk"},
12
+ ]
13
+
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Programming Language :: Python :: 3",
17
+ "Programming Language :: Python :: 3.11",
18
+ "Programming Language :: Python :: 3.12",
19
+ "License :: OSI Approved :: MIT License",
20
+ ]
21
+
22
+ dependencies = [
23
+ "uda>=2.9.2",
24
+ "uda-mast",
25
+ "xarray>=2025.12.0",
26
+ ]
27
+
28
+ [tool.setuptools]
29
+ packages = ["uda_xarray"]
30
+
31
+ [project.urls]
32
+ repository = "https://github.com/samueljackson92/uda-xarray"
33
+
34
+ [tool.ruff]
35
+ exclude = []
36
+
37
+ [dependency-groups]
38
+ dev = [
39
+ "bump-my-version>=1.2.5",
40
+ "pylint>=4.0.4",
41
+ "pytest>=8.4.1",
42
+ "pytest-mock>=3.15.1",
43
+ "ruff>=0.14.10",
44
+ ]
45
+
46
+ [project.entry-points."xarray.backends"]
47
+ uda = "uda_xarray.main:UDABackendEntrypoint"
48
+
49
+ [tool.bumpversion]
50
+ current_version = "0.1.0"
51
+ parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
52
+ serialize = ["{major}.{minor}.{patch}"]
53
+ search = "{current_version}"
54
+ replace = "{new_version}"
55
+ regex = false
56
+ ignore_missing_version = false
57
+ ignore_missing_files = false
58
+ tag = true
59
+ sign_tags = false
60
+ tag_name = "v{new_version}"
61
+ tag_message = "Bump version: {current_version} → {new_version}"
62
+ allow_dirty = false
63
+ commit = true
64
+ message = "Bump version: {current_version} → {new_version}"
65
+ commit_args = ""
66
+
67
+ [[tool.bumpversion.files]]
68
+ filename = "pyproject.toml"
69
+ search = 'version = "{current_version}"'
70
+ replace = 'version = "{new_version}"'
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,150 @@
1
+ import xarray as xr
2
+ import numpy as np
3
+ import pyuda
4
+ from unittest.mock import Mock
5
+
6
+
7
+ def test_open_uda_dataset(mocker):
8
+ # Create mock signal object
9
+ mock_signal = Mock()
10
+ mock_signal.data = np.array([1.0, 2.0, 3.0])
11
+ mock_signal.shape = (3,)
12
+ dim1 = Mock(label="time")
13
+ dim1.data = np.array([0.0, 1.0, 2.0])
14
+ mock_signal.dims = [dim1]
15
+
16
+ mock_signal.units = "A"
17
+ mock_signal.time = Mock()
18
+ mock_signal.time.data = np.array([0.0, 1.0, 2.0])
19
+ mock_signal.time.label = "time"
20
+ mock_signal.errors = Mock()
21
+ mock_signal.errors.data = np.array([0.1, 0.1, 0.1])
22
+
23
+ # Mock the pyuda Client
24
+ mock_client = Mock()
25
+ mock_client.get.return_value = mock_signal
26
+ mocker.patch("pyuda.Client", return_value=mock_client)
27
+ mocker.patch(
28
+ "uda_xarray.main.UDABackendEntrypoint._get_signal_type",
29
+ return_value="Signal",
30
+ )
31
+
32
+ ds = xr.open_dataset("uda://ip:30421", engine="uda")
33
+
34
+ # Verify the client was called correctly
35
+ mock_client.get.assert_called_once_with("ip", 30421)
36
+
37
+ assert ds["data"].name == "data"
38
+ assert ds["data"].dims == ("time",)
39
+ assert "time" in ds.coords
40
+
41
+ assert ds["data"].shape == ds["time"].shape
42
+ assert "units" in ds["data"].attrs
43
+ assert ds["data"].attrs["uda_name"] == "ip"
44
+
45
+
46
+ def test_open_uda_dataset_2d(mocker):
47
+ # Create mock 2D signal object
48
+ mock_signal = Mock()
49
+ mock_signal.data = np.array([[1.0, 2.0], [3.0, 4.0]])
50
+ mock_signal.shape = (2, 2)
51
+ dim1 = Mock(label="time")
52
+ dim1.data = np.array([0.0, 1.0])
53
+ dim2 = Mock(label="channel")
54
+ dim2.data = np.array([0, 1])
55
+ mock_signal.dims = [dim1, dim2]
56
+ mock_signal.units = "eV"
57
+ mock_signal.errors = Mock()
58
+ mock_signal.errors.data = np.array([[0.1, 0.1], [0.1, 0.1]])
59
+ mock_signal.time = Mock()
60
+ mock_signal.time.data = np.array([0.0, 1.0])
61
+ mock_signal.time.label = "time"
62
+
63
+ # Mock the pyuda Client
64
+ mock_client = Mock()
65
+ mock_client.get.return_value = mock_signal
66
+ mocker.patch("pyuda.Client", return_value=mock_client)
67
+ mocker.patch(
68
+ "uda_xarray.main.UDABackendEntrypoint._get_signal_type",
69
+ return_value="Signal",
70
+ )
71
+
72
+ ds = xr.open_dataset("uda://AYE_TE:30421", engine="uda")
73
+
74
+ mock_client.get.assert_called_once_with("AYE_TE", 30421)
75
+
76
+ assert ds["data"].name == "data"
77
+ assert ds["data"].dims == ("channel", "time")
78
+ assert "time" in ds.coords
79
+ assert "channel" in ds.coords
80
+
81
+
82
+ def test_open_uda_dataset_video(mocker):
83
+ mock_signal = Mock()
84
+ mock_signal.is_color = False
85
+ mock_signal.frame_times = np.array([0.0, 0.033, 0.066])
86
+ frame1 = Mock()
87
+ frame1.k = np.array([[10, 20], [30, 40]])
88
+ frame2 = Mock()
89
+ frame2.k = np.array([[15, 25], [35, 45]])
90
+ frame3 = Mock()
91
+ frame3.k = np.array([[20, 30], [40, 50]])
92
+ mock_signal.frames = [frame1, frame2, frame3]
93
+ mock_signal.height = 2
94
+ mock_signal.width = 2
95
+ mock_signal.duration = 0.066
96
+ mock_signal.num_frames = 3
97
+ mock_signal.name = "rba"
98
+ mock_signal.description = "Mock video signal"
99
+ mock_signal.units = "counts"
100
+
101
+ mock_client = Mock()
102
+ mock_client.get_images.return_value = mock_signal
103
+ mocker.patch("pyuda.Client", return_value=mock_client)
104
+ mocker.patch(
105
+ "uda_xarray.main.UDABackendEntrypoint._get_signal_type",
106
+ return_value="Image",
107
+ )
108
+
109
+ ds = xr.open_dataset("uda://rba:30421", engine="uda")
110
+ assert ds["data"].name == "data"
111
+ assert ds["data"].dims == ("time", "height", "width")
112
+ assert "time" in ds.coords
113
+ assert ds.sizes["time"] == 3
114
+
115
+
116
+ def test_open_uda_dataset_invalid_signal(mocker):
117
+ # Mock the pyuda Client to raise an exception
118
+ mock_client = Mock()
119
+ mock_client.get.side_effect = pyuda.ServerException("Signal not found")
120
+ mocker.patch("pyuda.Client", return_value=mock_client)
121
+ mocker.patch(
122
+ "uda_xarray.main.UDABackendEntrypoint._get_signal_type",
123
+ return_value="Signal",
124
+ )
125
+
126
+ try:
127
+ xr.open_dataset("uda://invalid_signal:99999", engine="uda")
128
+ except RuntimeError as e:
129
+ assert "Could not open UDA dataset" in str(e)
130
+ else:
131
+ assert False, "Expected RuntimeError was not raised"
132
+
133
+
134
+ def test_open_uda_dataset_invalid_format():
135
+ try:
136
+ xr.open_dataset("invalid_format", engine="uda")
137
+ except ValueError as e:
138
+ assert (
139
+ "UDA dataset must be specified as uda://<signal_name>:<shot_number>"
140
+ in str(e)
141
+ )
142
+ else:
143
+ assert False, "Expected ValueError was not raised"
144
+
145
+ try:
146
+ xr.open_dataset("http://invalid_scheme:12345", engine="uda")
147
+ except ValueError as e:
148
+ assert "UDA dataset must start with the uda:// scheme" in str(e)
149
+ else:
150
+ assert False, "Expected ValueError was not raised"
@@ -0,0 +1,164 @@
1
+ """Xarray UDA backend entrypoint."""
2
+
3
+ from enum import Enum
4
+ from typing import Optional
5
+
6
+ import numpy as np
7
+ import pyuda
8
+ import xarray as xr
9
+ from mast.mast_client import ListType
10
+ from xarray.backends import BackendEntrypoint
11
+
12
+
13
+ class SignalType(str, Enum):
14
+ """Enum for UDA signal types."""
15
+
16
+ SIGNAL = "Signal"
17
+ IMAGE = "Image"
18
+
19
+
20
+ Signal = pyuda.Signal
21
+ Video = pyuda.Video
22
+
23
+
24
+ class UDABackendEntrypoint(BackendEntrypoint):
25
+ """Xarray UDA backend entrypoint."""
26
+
27
+ def open_dataset(
28
+ self,
29
+ filename_or_obj,
30
+ *,
31
+ drop_variables=None,
32
+ frame_number: Optional[int] = None, # noqa: F821
33
+ ) -> xr.Dataset:
34
+ """Open a UDA dataset given a signal name and shot number.
35
+
36
+ Parameters
37
+ ----------
38
+ filename_or_obj : str
39
+ UDA dataset specified as uda://<signal_name>:<shot_number>
40
+ drop_variables : list, optional
41
+ Variables to drop from the dataset (not used).
42
+ frame_number : int, optional
43
+ Frame number to extract from an image signal (if applicable).
44
+ """
45
+
46
+ if ":" not in filename_or_obj:
47
+ raise ValueError(
48
+ "UDA dataset must be specified as uda://<signal_name>:<shot_number>"
49
+ )
50
+
51
+ if "uda://" not in filename_or_obj:
52
+ raise ValueError("UDA dataset must start with the uda:// scheme")
53
+
54
+ name, shot = filename_or_obj.rsplit(":", maxsplit=1)
55
+ name = name.replace("uda://", "")
56
+ shot = int(shot)
57
+
58
+ client = pyuda.Client()
59
+ signal_type = self._get_signal_type(client, name, shot)
60
+
61
+ try:
62
+ if signal_type == SignalType.SIGNAL:
63
+ signal = client.get(name, shot)
64
+ dataset = self._handle_signal(name, signal)
65
+ elif signal_type == SignalType.IMAGE:
66
+ kwargs = {}
67
+ if frame_number is not None:
68
+ kwargs["frame_number"] = frame_number
69
+ signal = client.get_images(name, shot, **kwargs)
70
+ dataset = self._handle_images(name, signal, frame_number=frame_number)
71
+ else:
72
+ raise NotImplementedError(f"Signal type {signal_type} not supported")
73
+ # pylint: disable=c-extension-no-member
74
+ except (pyuda.ServerException, pyuda.cpyuda.ClientException) as e:
75
+ raise RuntimeError(f"Could not open UDA dataset {filename_or_obj}") from e
76
+
77
+ return dataset
78
+
79
+ def _handle_signal(self, name: str, signal: Signal) -> xr.Dataset:
80
+ dim_data = {dim.label: dim.data for dim in signal.dims}
81
+
82
+ # Rename time dimension to just "time" if we can.
83
+ if signal.time.label in dim_data:
84
+ dim_data["time"] = dim_data.pop(signal.time.label)
85
+
86
+ item = xr.DataArray(
87
+ signal.data,
88
+ coords=dim_data,
89
+ attrs={"units": signal.units, "uda_name": name},
90
+ )
91
+
92
+ error = xr.DataArray(
93
+ signal.errors.data,
94
+ coords=dim_data,
95
+ )
96
+
97
+ dataset = xr.Dataset(data_vars={"data": item, "error": error})
98
+ return dataset
99
+
100
+ def _handle_images(
101
+ self, name: str, video: Video, frame_number: Optional[int] = None
102
+ ) -> xr.Dataset:
103
+ attrs = {
104
+ name: getattr(video, name)
105
+ for name in dir(video)
106
+ if not name.startswith("_") and not callable(getattr(video, name))
107
+ }
108
+
109
+ attrs.pop("frame_times")
110
+ attrs.pop("frames")
111
+
112
+ attrs["CLASS"] = "IMAGE"
113
+ attrs["IMAGE_VERSION"] = "1.2"
114
+
115
+ time = np.atleast_1d(video.frame_times)
116
+ if frame_number is not None:
117
+ time = time[frame_number : frame_number + 1]
118
+ coords = {"time": xr.DataArray(time, dims=["time"], attrs={"units": "s"})}
119
+
120
+ if video.is_color:
121
+ frames = [np.dstack((frame.r, frame.g, frame.b)) for frame in video.frames]
122
+ frames = np.stack(frames)
123
+ if frames.shape[1] != video.height:
124
+ frames = np.swapaxes(frames, 1, 2)
125
+ dim_names = ["time", "height", "width", "channel"]
126
+
127
+ attrs["IMAGE_SUBCLASS"] = "IMAGE_TRUECOLOR"
128
+ attrs["INTERLACE_MODE"] = "INTERLACE_PIXEL"
129
+ else:
130
+ frames = [frame.k for frame in video.frames]
131
+ frames = np.stack(frames)
132
+ frames = np.atleast_3d(frames)
133
+ if frames.shape[1] != video.height:
134
+ frames = np.swapaxes(frames, 1, 2)
135
+ dim_names = ["time", "height", "width"]
136
+
137
+ attrs["IMAGE_SUBCLASS"] = "IMAGE_INDEXED"
138
+
139
+ dataset = xr.DataArray(frames, dims=dim_names, coords=coords, attrs=attrs)
140
+ dataset = dataset.to_dataset(name="data")
141
+ dataset.attrs["uda_name"] = name
142
+ return dataset
143
+
144
+ def _get_signal_type(
145
+ self, client: pyuda.Client, name: str, shot: int
146
+ ) -> SignalType:
147
+ sources = client.list(ListType.SOURCES, shot=shot)
148
+ source_types = {s.source_alias: s.type for s in sources}
149
+
150
+ if name in source_types and source_types[name] == "Image":
151
+ return SignalType.IMAGE
152
+ return SignalType.SIGNAL
153
+
154
+ def open_datatree(self, filename_or_obj, *, drop_variables=None):
155
+ raise NotImplementedError("UDA backend does not support open_datatree")
156
+
157
+ open_dataset_parameters = ["filename_or_obj", "drop_variables"]
158
+
159
+ def guess_can_open(self, filename_or_obj):
160
+ return filename_or_obj.startswith("uda://")
161
+
162
+ description = "Use UDA data in Xarray"
163
+
164
+ url = "https://github.com/samueljackson92/uda-xarray"
@@ -0,0 +1,133 @@
1
+ Metadata-Version: 2.4
2
+ Name: uda-xarray
3
+ Version: 0.1.0
4
+ Summary: UDA backend for xarray
5
+ Author-email: Samuel Jackson <samuel.jackson@ukaea.uk>
6
+ Maintainer-email: Samuel Jackson <samuel.jackson@ukaea.uk>
7
+ Project-URL: repository, https://github.com/samueljackson92/uda-xarray
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Requires-Python: <3.13,>=3.11
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+ Requires-Dist: uda>=2.9.2
17
+ Requires-Dist: uda-mast
18
+ Requires-Dist: xarray>=2025.12.0
19
+ Dynamic: license-file
20
+
21
+ # uda-xarray
22
+
23
+ An xarray backend for UDA (Universal Data Access) that enables seamless integration of UDA data sources with the xarray ecosystem.
24
+
25
+ ## Overview
26
+
27
+ uda-xarray provides a backend plugin for xarray that allows you to access UDA data sources using xarray's familiar API. It automatically handles the conversion of UDA signals to xarray DataArrays and Datasets, making it easy to work with UDA data in scientific Python workflows.
28
+
29
+ ## Features
30
+
31
+ - **xarray Integration**: Access UDA data using xarray's `open_dataset` function
32
+ - **Automatic Conversion**: Converts UDA signals to xarray DataArrays with proper coordinates and metadata
33
+ - **Error Handling**: Includes error data alongside signal data
34
+ - **URI-based Access**: Simple URI scheme (`uda://signal_name:shot`) for accessing data
35
+
36
+ ## Installation
37
+
38
+ ```bash
39
+ pip install uda-xarray
40
+ ```
41
+
42
+ Or using `uv`:
43
+
44
+ ```bash
45
+ uv pip install uda-xarray
46
+ ```
47
+
48
+ ## Usage
49
+
50
+ Open a UDA dataset using xarray:
51
+
52
+ ```python
53
+ import xarray as xr
54
+
55
+ # Open a UDA signal by name and shot number
56
+ ds = xr.open_dataset("uda://ip:30421", engine="uda")
57
+
58
+ # Access the data
59
+ data = ds["data"]
60
+ errors = ds["error"]
61
+
62
+ # The dataset includes time coordinates
63
+ time = ds["time"]
64
+
65
+ # Access metadata
66
+ units = ds["data"].attrs["units"]
67
+ signal_name = ds["data"].attrs["uda_name"]
68
+ ```
69
+
70
+ The URI format is: `uda://<signal_name>:<shot_number>`
71
+
72
+ ## Data Structure
73
+
74
+ When you open a UDA dataset, uda-xarray creates an xarray Dataset with:
75
+
76
+ - **data**: The signal data as a DataArray
77
+ - **error**: The error data as a DataArray
78
+ - **time**: Time coordinates (dimension)
79
+ - **attrs**: Metadata including units and UDA signal name
80
+
81
+ ## Limitations
82
+
83
+ - Currently only supports 1D signals (2D signals will raise `NotImplementedError`)
84
+ - Requires a working UDA client connection
85
+
86
+ ## Requirements
87
+
88
+ - Python >= 3.11, < 3.13
89
+ - uda >= 2.9.2
90
+ - xarray >= 2025.12.0
91
+
92
+ ## Development
93
+
94
+ ### Setup
95
+
96
+ Clone the repository and install development dependencies:
97
+
98
+ ```bash
99
+ git clone https://github.com/samueljackson92/uda-xarray.git
100
+ cd uda-xarray
101
+ uv sync
102
+ ```
103
+
104
+ ### Running Tests
105
+
106
+ ```bash
107
+ pytest tests/
108
+ ```
109
+
110
+ ### Code Quality
111
+
112
+ The project uses ruff for linting and formatting, and pylint for additional checks:
113
+
114
+ ```bash
115
+ # Run ruff
116
+ uv run ruff check uda_xarray tests
117
+ uv run ruff format uda_xarray tests
118
+
119
+ # Run pylint
120
+ uv run pylint uda_xarray
121
+ ```
122
+
123
+ ## Contributing
124
+
125
+ Contributions are welcome! Please ensure:
126
+
127
+ 1. Tests pass for any new functionality
128
+ 2. Code follows the project's style guidelines (ruff and pylint)
129
+ 3. Documentation is updated as needed
130
+
131
+ ## License
132
+
133
+ MIT License
@@ -0,0 +1,11 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ tests/test_backend.py
5
+ uda_xarray/main.py
6
+ uda_xarray.egg-info/PKG-INFO
7
+ uda_xarray.egg-info/SOURCES.txt
8
+ uda_xarray.egg-info/dependency_links.txt
9
+ uda_xarray.egg-info/entry_points.txt
10
+ uda_xarray.egg-info/requires.txt
11
+ uda_xarray.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [xarray.backends]
2
+ uda = uda_xarray.main:UDABackendEntrypoint
@@ -0,0 +1,3 @@
1
+ uda>=2.9.2
2
+ uda-mast
3
+ xarray>=2025.12.0
@@ -0,0 +1 @@
1
+ uda_xarray