ewoksdata 2.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.
- ewoksdata/__init__.py +0 -0
- ewoksdata/data/__init__.py +0 -0
- ewoksdata/data/bliss.py +341 -0
- ewoksdata/data/blissdata/__init__.py +31 -0
- ewoksdata/data/blissdata/blissdatav0.py +119 -0
- ewoksdata/data/blissdata/blissdatav1.py +279 -0
- ewoksdata/data/blissdata/blissdatav2.py +342 -0
- ewoksdata/data/contextiterator.py +43 -0
- ewoksdata/data/hdf5/__init__.py +23 -0
- ewoksdata/data/hdf5/config.py +186 -0
- ewoksdata/data/hdf5/dataset_writer/__init__.py +4 -0
- ewoksdata/data/hdf5/dataset_writer/_base.py +55 -0
- ewoksdata/data/hdf5/dataset_writer/_non_sequential.py +139 -0
- ewoksdata/data/hdf5/dataset_writer/_non_sequential_stack.py +158 -0
- ewoksdata/data/hdf5/dataset_writer/_sequential.py +143 -0
- ewoksdata/data/hdf5/dataset_writer/_sequential_stack.py +195 -0
- ewoksdata/data/hdf5/types.py +14 -0
- ewoksdata/data/nexus.py +187 -0
- ewoksdata/data/url.py +30 -0
- ewoksdata/data/utils.py +53 -0
- ewoksdata/tests/__init__.py +0 -0
- ewoksdata/tests/conftest.py +30 -0
- ewoksdata/tests/data/__init__.py +1 -0
- ewoksdata/tests/data/bliss_scans.py +137 -0
- ewoksdata/tests/test_contextiterator.py +61 -0
- ewoksdata/tests/test_data_bliss.py +98 -0
- ewoksdata/tests/test_data_hdf5.py +14 -0
- ewoksdata/tests/test_data_nexus.py +80 -0
- ewoksdata/tests/test_dataset_writer.py +275 -0
- ewoksdata-2.1.0.dist-info/METADATA +70 -0
- ewoksdata-2.1.0.dist-info/RECORD +34 -0
- ewoksdata-2.1.0.dist-info/WHEEL +5 -0
- ewoksdata-2.1.0.dist-info/licenses/LICENSE.md +20 -0
- ewoksdata-2.1.0.dist-info/top_level.txt +1 -0
ewoksdata/__init__.py
ADDED
|
File without changes
|
|
File without changes
|
ewoksdata/data/bliss.py
ADDED
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
"""Utilities for reading Bliss scan data from HDF5/Nexus files
|
|
2
|
+
or from memory (Redis or device servers).
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
import time
|
|
9
|
+
from numbers import Integral
|
|
10
|
+
from numbers import Number
|
|
11
|
+
from typing import Any
|
|
12
|
+
from typing import Dict
|
|
13
|
+
from typing import Iterator
|
|
14
|
+
from typing import List
|
|
15
|
+
from typing import Optional
|
|
16
|
+
from typing import Sequence
|
|
17
|
+
from typing import Tuple
|
|
18
|
+
from typing import Union
|
|
19
|
+
|
|
20
|
+
import h5py
|
|
21
|
+
import hdf5plugin # noqa: F401 - needed for transparent compression handling
|
|
22
|
+
import numpy
|
|
23
|
+
from numpy.typing import ArrayLike
|
|
24
|
+
from silx.io import h5py_utils
|
|
25
|
+
from silx.io.url import DataUrl
|
|
26
|
+
from silx.io.utils import get_data as silx_get_data
|
|
27
|
+
from silx.utils import retry as retrymod
|
|
28
|
+
|
|
29
|
+
from . import hdf5
|
|
30
|
+
from . import nexus
|
|
31
|
+
from . import url
|
|
32
|
+
from .blissdata import BUFFER_IN_MEMORY_DEFAULT
|
|
33
|
+
from .blissdata import dynamic_hdf5
|
|
34
|
+
from .blissdata import iter_bliss_scan_data_from_memory
|
|
35
|
+
from .blissdata import iter_bliss_scan_data_from_memory_slice # noqa: F401
|
|
36
|
+
from .blissdata import last_lima_image # noqa: F401
|
|
37
|
+
from .contextiterator import contextiterator
|
|
38
|
+
from .utils import validate_and_init_names
|
|
39
|
+
|
|
40
|
+
logger = logging.getLogger(__name__)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def get_data(
|
|
44
|
+
data: Union[str, DataUrl, ArrayLike, Number],
|
|
45
|
+
retry_timeout: Optional[float] = None,
|
|
46
|
+
retry_period: Optional[float] = None,
|
|
47
|
+
**hdf5_options: Any,
|
|
48
|
+
) -> Union[numpy.ndarray, Number, Sequence]:
|
|
49
|
+
"""
|
|
50
|
+
Retrieve numerical data from a data source and return it as a NumPy array or scalar.
|
|
51
|
+
|
|
52
|
+
This function accepts various data representations, including data URLs,
|
|
53
|
+
array-like objects, and scalar numbers. The data source may not be
|
|
54
|
+
immediately available so a retry mechanism is used.
|
|
55
|
+
|
|
56
|
+
:param data: The input data source. May be:
|
|
57
|
+
- A string or :class:`DataUrl` referencing a dataset (e.g., HDF5),
|
|
58
|
+
- An array-like object convertible to a NumPy array,
|
|
59
|
+
- A scalar numerical value.
|
|
60
|
+
:param retry_timeout: Maximum time in seconds to wait for the data to become
|
|
61
|
+
available. If ``None`` (default), no timeout is applied.
|
|
62
|
+
:param retry_period: Interval in seconds between availability checks when
|
|
63
|
+
retrying. If ``None`` (default), a period of 0.1 seconds is used.
|
|
64
|
+
:param hdf5_options: Additional keyword arguments forwarded to
|
|
65
|
+
:class:`silx.io.h5py_utils.File` when opening HDF5 resources.
|
|
66
|
+
|
|
67
|
+
:returns: The resolved data. Array-like inputs are returned as NumPy arrays,
|
|
68
|
+
while scalar inputs are returned as numbers.
|
|
69
|
+
|
|
70
|
+
:raises TimeoutError: If the data does not become available within ``retry_timeout``.
|
|
71
|
+
:raises ValueError: If the input type is unsupported or the data cannot be resolved.
|
|
72
|
+
"""
|
|
73
|
+
if isinstance(data, (str, DataUrl)):
|
|
74
|
+
data_url = url.as_dataurl(data)
|
|
75
|
+
filename, h5path, idx = url.h5dataset_url_parse(data_url)
|
|
76
|
+
|
|
77
|
+
_wait_for_file(filename, retry_timeout=retry_timeout, retry_period=retry_period)
|
|
78
|
+
|
|
79
|
+
if _is_hdf5_filename(filename):
|
|
80
|
+
return _get_hdf5_data(
|
|
81
|
+
filename,
|
|
82
|
+
h5path,
|
|
83
|
+
idx=idx,
|
|
84
|
+
retry_timeout=retry_timeout,
|
|
85
|
+
retry_period=retry_period,
|
|
86
|
+
**hdf5_options,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
if not data_url.scheme():
|
|
90
|
+
for scheme in ("fabio", "silx"):
|
|
91
|
+
if sys.platform == "win32":
|
|
92
|
+
new_data_url = url.as_dataurl(f"{scheme}:///{data}")
|
|
93
|
+
else:
|
|
94
|
+
new_data_url = url.as_dataurl(f"{scheme}://{data}")
|
|
95
|
+
if new_data_url.is_valid():
|
|
96
|
+
data_url = new_data_url
|
|
97
|
+
break
|
|
98
|
+
|
|
99
|
+
return silx_get_data(data_url)
|
|
100
|
+
|
|
101
|
+
if isinstance(data, (Sequence, Number, numpy.ndarray)):
|
|
102
|
+
return data
|
|
103
|
+
|
|
104
|
+
raise TypeError(f"Unsupported data type: {type(data)!r}")
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _wait_for_file(
|
|
108
|
+
filename: str,
|
|
109
|
+
retry_timeout: Optional[float] = None,
|
|
110
|
+
retry_period: Optional[float] = None,
|
|
111
|
+
**_,
|
|
112
|
+
) -> None:
|
|
113
|
+
if retry_period is None:
|
|
114
|
+
retry_period = 0.1
|
|
115
|
+
t0 = time.time()
|
|
116
|
+
while True:
|
|
117
|
+
if os.path.exists(filename):
|
|
118
|
+
return
|
|
119
|
+
if retry_timeout is not None:
|
|
120
|
+
if (time.time() - t0) > retry_timeout:
|
|
121
|
+
raise TimeoutError(
|
|
122
|
+
f"File {filename} is not created within {retry_timeout} sec"
|
|
123
|
+
)
|
|
124
|
+
time.sleep(retry_period)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _is_hdf5_filename(filename: str) -> bool:
|
|
128
|
+
_, ext = os.path.splitext(filename)
|
|
129
|
+
return ext in (".h5", ".hdf5", ".nx", ".nxs", ".nexus")
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def get_image(
|
|
133
|
+
data: Union[str, DataUrl, ArrayLike, Number], **hdf5_options: Any
|
|
134
|
+
) -> numpy.ndarray:
|
|
135
|
+
"""
|
|
136
|
+
Return a NumPy array or a scalar from *data*.
|
|
137
|
+
|
|
138
|
+
Retry options, such as `retry_timeout` and `retry_period` are forwarded to the internal HDF5 data reader.
|
|
139
|
+
|
|
140
|
+
:param data: Either a data-URL, an array-like object or a scalar number.
|
|
141
|
+
:param hdf5_options: Additional options forwarded to :class:`silx.io.h5py_utils.File`.
|
|
142
|
+
:return: A 2-D image.
|
|
143
|
+
"""
|
|
144
|
+
data = get_data(data, **hdf5_options)
|
|
145
|
+
return numpy.atleast_2d(numpy.squeeze(data))
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@h5py_utils.retry()
|
|
149
|
+
def _get_hdf5_data(
|
|
150
|
+
filename: str,
|
|
151
|
+
h5path: str,
|
|
152
|
+
idx: Optional[Tuple[int, ...]] = None,
|
|
153
|
+
**hdf5_options: Any,
|
|
154
|
+
) -> numpy.ndarray:
|
|
155
|
+
"""
|
|
156
|
+
Read a slice of a dataset inside an HDF5 / Nexus file.
|
|
157
|
+
|
|
158
|
+
:param filename: Path to the HDF5 / Nexus file.
|
|
159
|
+
:param h5path: Internal path to the dataset (e.g. ``/entry/data``).
|
|
160
|
+
:param idx: Optional index or slice tuple used to read a subset of the dataset.
|
|
161
|
+
:param **hdf5_options: Additional options forwarded to :class:`silx.io.h5py_utils.File`.
|
|
162
|
+
:return: The requested slice.
|
|
163
|
+
"""
|
|
164
|
+
with hdf5.h5context(filename, h5path, **hdf5_options) as dset:
|
|
165
|
+
if _is_bliss_file(dset):
|
|
166
|
+
# ``end_time`` is added by the Bliss writer when the scan is
|
|
167
|
+
# finished. If it is missing we wait for the file to be
|
|
168
|
+
# completed.
|
|
169
|
+
if "end_time" not in nexus.get_nxentry(dset):
|
|
170
|
+
raise retrymod.RetryError("Bliss file not yet complete")
|
|
171
|
+
if idx is None:
|
|
172
|
+
idx = tuple()
|
|
173
|
+
return dset[idx]
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@contextiterator
|
|
177
|
+
def iter_bliss_scan_data_from_hdf5(
|
|
178
|
+
filename: str,
|
|
179
|
+
scan_nr: Integral,
|
|
180
|
+
subscan: Optional[Integral] = None,
|
|
181
|
+
lima_names: Optional[List[str]] = None,
|
|
182
|
+
counter_names: Optional[List[str]] = None,
|
|
183
|
+
retry_timeout: Optional[float] = None,
|
|
184
|
+
retry_period: Optional[float] = None,
|
|
185
|
+
**hdf5_options: Any,
|
|
186
|
+
) -> Iterator[Dict[str, Any]]:
|
|
187
|
+
"""
|
|
188
|
+
Iterate over the data of a single Bliss scan stored in an HDF5 file.
|
|
189
|
+
|
|
190
|
+
:param filename: Bliss dataset filename (HDF5 / Nexus).
|
|
191
|
+
:param scan_nr: Scan number inside the file.
|
|
192
|
+
:param lima_names: List of LIMA detector names.
|
|
193
|
+
:param counter_names: List of non-LIMA counter names.
|
|
194
|
+
:param subscan: Sub-scan number (e.g. ``10.2`` -> ``scan_nr=10`` and ``subscan=2``).
|
|
195
|
+
:param retry_timeout: Timeout for the retry mechanism.
|
|
196
|
+
:param retry_period: Interval between retries.
|
|
197
|
+
:param **hdf5_options: Additional options forwarded to :class:`blissdata.h5api.dynamic_hdf5.File`.
|
|
198
|
+
:yields: Mapping ``detector_name -> value`` for the each scan point.
|
|
199
|
+
"""
|
|
200
|
+
if not subscan:
|
|
201
|
+
subscan = 1
|
|
202
|
+
lima_names, counter_names = validate_and_init_names(lima_names, counter_names)
|
|
203
|
+
|
|
204
|
+
with dynamic_hdf5.File(
|
|
205
|
+
filename,
|
|
206
|
+
lima_names=lima_names,
|
|
207
|
+
retry_timeout=retry_timeout,
|
|
208
|
+
retry_period=retry_period,
|
|
209
|
+
**hdf5_options,
|
|
210
|
+
) as root:
|
|
211
|
+
scan = root[f"{scan_nr}.{subscan}"]
|
|
212
|
+
# assert _is_bliss_file(scan), "Not a Bliss dataset file"
|
|
213
|
+
measurement = scan["measurement"]
|
|
214
|
+
instrument = scan["instrument"]
|
|
215
|
+
|
|
216
|
+
datasets: Dict[str, Any] = {name: measurement[name] for name in counter_names}
|
|
217
|
+
for name in lima_names:
|
|
218
|
+
datasets[name] = instrument[f"{name}/data"]
|
|
219
|
+
|
|
220
|
+
names = list(datasets.keys())
|
|
221
|
+
for values in zip(*datasets.values()):
|
|
222
|
+
yield dict(zip(names, values))
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
@contextiterator
|
|
226
|
+
def iter_bliss_data(
|
|
227
|
+
filename: str,
|
|
228
|
+
scan_nr: Integral,
|
|
229
|
+
subscan: Optional[Integral] = None,
|
|
230
|
+
scan_key: Optional[str] = None,
|
|
231
|
+
lima_names: Optional[List[str]] = None,
|
|
232
|
+
counter_names: Optional[List[str]] = None,
|
|
233
|
+
start_index: Optional[int] = None,
|
|
234
|
+
retry_timeout: Optional[float] = None,
|
|
235
|
+
retry_period: Optional[float] = None,
|
|
236
|
+
buffer_in_memory: bool = BUFFER_IN_MEMORY_DEFAULT,
|
|
237
|
+
**options: Any,
|
|
238
|
+
) -> Iterator[Tuple[int, Dict[str, Any]]]:
|
|
239
|
+
"""
|
|
240
|
+
Iterate over a Bliss scan and also return the scan-point index.
|
|
241
|
+
|
|
242
|
+
:param filename: Bliss dataset filename.
|
|
243
|
+
:param scan_nr: Scan number inside the dataset.
|
|
244
|
+
:param subscan: Sub-scan number (e.g. ``10.2`` -> ``scan_nr=10`` and ``subscan=2``).
|
|
245
|
+
:param scan_key: Identifier of a scan for blissdata. Iterate scan data from blissdata-cache.
|
|
246
|
+
:param lima_names: List of LIMA detector names.
|
|
247
|
+
:param counter_names: List of non-LIMA counter names.
|
|
248
|
+
:param start_index: First point to yield (default ``0`` -> all points).
|
|
249
|
+
:param retry_timeout: Timeout for the retry mechanism.
|
|
250
|
+
:param retry_period: Interval between retries.
|
|
251
|
+
:param buffer_in_memory: Blissdata buffering in memory. Only applies when `scan_key` is provided.
|
|
252
|
+
Use only when you have enough memory or the data processing can keep up with the scan.
|
|
253
|
+
:param **hdf5_options: Additional options forwarded to :class:`blissdata.h5api.dynamic_hdf5.File`.
|
|
254
|
+
:yields: ``(point_index, data_dict)`` where ``point_index`` is the zero-based
|
|
255
|
+
scan-point number and ``data_dict`` maps detector names to values.
|
|
256
|
+
"""
|
|
257
|
+
start_index = 0 if start_index is None else start_index
|
|
258
|
+
|
|
259
|
+
for idx, data in enumerate(
|
|
260
|
+
iter_bliss_scan_data(
|
|
261
|
+
filename,
|
|
262
|
+
scan_nr,
|
|
263
|
+
subscan=subscan,
|
|
264
|
+
scan_key=scan_key,
|
|
265
|
+
lima_names=lima_names,
|
|
266
|
+
counter_names=counter_names,
|
|
267
|
+
retry_timeout=retry_timeout,
|
|
268
|
+
retry_period=retry_period,
|
|
269
|
+
buffer_in_memory=buffer_in_memory,
|
|
270
|
+
**options,
|
|
271
|
+
)
|
|
272
|
+
):
|
|
273
|
+
if idx >= start_index:
|
|
274
|
+
yield idx, data
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
@contextiterator
|
|
278
|
+
def iter_bliss_scan_data(
|
|
279
|
+
filename: str,
|
|
280
|
+
scan_nr: Integral,
|
|
281
|
+
subscan: Optional[Integral] = None,
|
|
282
|
+
scan_key: Optional[str] = None,
|
|
283
|
+
lima_names: Optional[List[str]] = None,
|
|
284
|
+
counter_names: Optional[List[str]] = None,
|
|
285
|
+
retry_timeout: Optional[float] = None,
|
|
286
|
+
retry_period: Optional[float] = None,
|
|
287
|
+
buffer_in_memory: bool = BUFFER_IN_MEMORY_DEFAULT,
|
|
288
|
+
**hdf5_options: Any,
|
|
289
|
+
) -> Iterator[Dict[str, Any]]:
|
|
290
|
+
"""
|
|
291
|
+
Yield the data of a Bliss scan either from an HDF5 file or from memory (Redis or device servers).
|
|
292
|
+
|
|
293
|
+
:param filename: Path to the HDF5 / Nexus file (ignored when *scan_key* is supplied).
|
|
294
|
+
:param scan_nr: Scan number inside the file.
|
|
295
|
+
:param subscan: Sub-scan number (e.g. ``10.2`` -> ``scan_nr=10`` and ``subscan=2``).
|
|
296
|
+
:param scan_key: Identifier of a scan for blissdata. Iterate scan data from blissdata-cache.
|
|
297
|
+
:param lima_names: List of LIMA detector names.
|
|
298
|
+
:param counter_names: List of non-LIMA counter names.
|
|
299
|
+
:param retry_timeout: Timeout for the retry mechanism.
|
|
300
|
+
:param retry_period: Interval between retries.
|
|
301
|
+
:param buffer_in_memory: Blissdata buffering in memory. Only applies when `scan_key` is provided.
|
|
302
|
+
Use only when you have enough memory or the data processing can keep up with the scan.
|
|
303
|
+
:param **hdf5_options: Additional options forwarded to :class:`blissdata.h5api.dynamic_hdf5.File`.
|
|
304
|
+
:yields: Mapping ``detector_name -> value`` for each scan point.
|
|
305
|
+
"""
|
|
306
|
+
if scan_key:
|
|
307
|
+
yield from iter_bliss_scan_data_from_memory(
|
|
308
|
+
scan_key,
|
|
309
|
+
lima_names=lima_names,
|
|
310
|
+
counter_names=counter_names,
|
|
311
|
+
retry_timeout=retry_timeout,
|
|
312
|
+
retry_period=retry_period,
|
|
313
|
+
buffer_in_memory=buffer_in_memory,
|
|
314
|
+
)
|
|
315
|
+
else:
|
|
316
|
+
yield from iter_bliss_scan_data_from_hdf5(
|
|
317
|
+
filename,
|
|
318
|
+
scan_nr,
|
|
319
|
+
lima_names=lima_names,
|
|
320
|
+
counter_names=counter_names,
|
|
321
|
+
subscan=subscan,
|
|
322
|
+
retry_timeout=retry_timeout,
|
|
323
|
+
retry_period=retry_period,
|
|
324
|
+
**hdf5_options,
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def _is_bliss_file(h5item: Union[h5py.Dataset, h5py.Group]) -> bool:
|
|
329
|
+
"""
|
|
330
|
+
Return ``True`` if *h5item* belongs to a Bliss-generated file.
|
|
331
|
+
|
|
332
|
+
:param h5item: HDF5 dataset or group.
|
|
333
|
+
:return: ``True`` when the file attributes indicate a Bliss creator/publisher.
|
|
334
|
+
"""
|
|
335
|
+
attrs = h5item.file.attrs
|
|
336
|
+
creator = attrs.get("creator", "").lower()
|
|
337
|
+
publisher = attrs.get("publisher", "").lower()
|
|
338
|
+
return creator in _BLISS_PUBLISHERS or publisher in _BLISS_PUBLISHERS
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
_BLISS_PUBLISHERS = ("bliss", "blissdata")
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from importlib.metadata import version
|
|
2
|
+
|
|
3
|
+
from packaging.version import Version
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _not_supported_by_blissdata_version(*_, **kw):
|
|
7
|
+
raise RuntimeError(f"Not supported by blissdata {_BLISSDATA_VERSION}")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
from blissdata.h5api import dynamic_hdf5 # noqa F401
|
|
11
|
+
|
|
12
|
+
_BLISSDATA_VERSION = Version(version("blissdata"))
|
|
13
|
+
|
|
14
|
+
if _BLISSDATA_VERSION >= Version("2.0.0rc1"):
|
|
15
|
+
from .blissdatav2 import iter_bliss_scan_data_from_memory # noqa F401
|
|
16
|
+
from .blissdatav2 import iter_bliss_scan_data_from_memory_slice # noqa F401
|
|
17
|
+
from .blissdatav2 import last_lima_image # noqa F401
|
|
18
|
+
|
|
19
|
+
BUFFER_IN_MEMORY_DEFAULT = False
|
|
20
|
+
elif _BLISSDATA_VERSION >= Version("1"):
|
|
21
|
+
from .blissdatav1 import iter_bliss_scan_data_from_memory # noqa F401
|
|
22
|
+
from .blissdatav1 import iter_bliss_scan_data_from_memory_slice # noqa F401
|
|
23
|
+
from .blissdatav1 import last_lima_image # noqa F401
|
|
24
|
+
|
|
25
|
+
BUFFER_IN_MEMORY_DEFAULT = True
|
|
26
|
+
else:
|
|
27
|
+
from .blissdatav0 import iter_bliss_scan_data_from_memory # noqa F401
|
|
28
|
+
from .blissdatav0 import last_lima_image # noqa F401
|
|
29
|
+
|
|
30
|
+
iter_bliss_scan_data_from_memory_slice = _not_supported_by_blissdata_version
|
|
31
|
+
BUFFER_IN_MEMORY_DEFAULT = True
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from numbers import Number
|
|
3
|
+
from typing import Any
|
|
4
|
+
from typing import Dict
|
|
5
|
+
from typing import Generator
|
|
6
|
+
from typing import List
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
from blissdata.data.events.lima import ImageNotSaved
|
|
10
|
+
from blissdata.data.node import get_node
|
|
11
|
+
from numpy.typing import ArrayLike
|
|
12
|
+
from silx.utils import retry as retrymod
|
|
13
|
+
|
|
14
|
+
from ..utils import validate_and_init_names
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def iter_bliss_scan_data_from_memory(
|
|
20
|
+
db_name: str,
|
|
21
|
+
lima_names: Optional[List[str]] = None,
|
|
22
|
+
counter_names: Optional[List[str]] = None,
|
|
23
|
+
retry_timeout: Optional[Number] = None,
|
|
24
|
+
retry_period: Optional[Number] = None,
|
|
25
|
+
buffer_in_memory: bool = True,
|
|
26
|
+
) -> Generator[Dict[str, Any], None, None]:
|
|
27
|
+
if not buffer_in_memory:
|
|
28
|
+
raise NotImplementedError("Requires blissdata>=2.0.0rc1")
|
|
29
|
+
lima_names, counter_names = validate_and_init_names(lima_names, counter_names)
|
|
30
|
+
|
|
31
|
+
scan_node = _get_node(
|
|
32
|
+
db_name, "scan", retry_timeout=retry_timeout, retry_period=retry_period
|
|
33
|
+
)
|
|
34
|
+
indices = {name: 0 for name in lima_names + counter_names}
|
|
35
|
+
buffers = {name: list() for name in lima_names + counter_names}
|
|
36
|
+
if not buffers:
|
|
37
|
+
return
|
|
38
|
+
|
|
39
|
+
lima_acq_nb = dict()
|
|
40
|
+
for event_type, node, event_data in scan_node.walk_events():
|
|
41
|
+
if node.type == "lima":
|
|
42
|
+
name = node.db_name.split(":")[-2]
|
|
43
|
+
if name not in lima_names:
|
|
44
|
+
continue
|
|
45
|
+
dataview = _get_lima_dataview(
|
|
46
|
+
node,
|
|
47
|
+
indices[name],
|
|
48
|
+
retry_timeout=retry_timeout,
|
|
49
|
+
retry_period=retry_period,
|
|
50
|
+
)
|
|
51
|
+
current_lima_acq_nb = dataview.status_event.status["lima_acq_nb"]
|
|
52
|
+
first_lima_acq_nb = lima_acq_nb.setdefault(name, current_lima_acq_nb)
|
|
53
|
+
if first_lima_acq_nb != current_lima_acq_nb:
|
|
54
|
+
logger.warning("lima is already acquiring the next scan")
|
|
55
|
+
continue
|
|
56
|
+
try:
|
|
57
|
+
data = list(dataview)
|
|
58
|
+
except ImageNotSaved:
|
|
59
|
+
logger.warning(
|
|
60
|
+
"cannot read lima data from file because images are not being saved"
|
|
61
|
+
)
|
|
62
|
+
continue
|
|
63
|
+
except Exception as e:
|
|
64
|
+
logger.warning("cannot read lima data (%s)", str(e))
|
|
65
|
+
continue
|
|
66
|
+
indices[name] += len(data)
|
|
67
|
+
buffers[name].extend(data)
|
|
68
|
+
elif node.type == "channel":
|
|
69
|
+
name = node.db_name.split(":")[-1]
|
|
70
|
+
if name not in counter_names:
|
|
71
|
+
continue
|
|
72
|
+
if event_data:
|
|
73
|
+
data = event_data.data
|
|
74
|
+
else:
|
|
75
|
+
data = node.get_as_array(indices[name], -1)
|
|
76
|
+
indices[name] += len(data)
|
|
77
|
+
buffers[name].extend(data)
|
|
78
|
+
nyield = min(len(v) for v in buffers.values())
|
|
79
|
+
if nyield:
|
|
80
|
+
for i in range(nyield):
|
|
81
|
+
yield {name: values[i] for name, values in buffers.items()}
|
|
82
|
+
buffers = {name: values[nyield:] for name, values in buffers.items()}
|
|
83
|
+
if event_type == event_type.END_SCAN:
|
|
84
|
+
break
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def last_lima_image(db_name: str) -> ArrayLike:
|
|
88
|
+
"""Get last lima image from memory"""
|
|
89
|
+
node = _get_node(db_name, "lima")
|
|
90
|
+
node.from_stream = True
|
|
91
|
+
dataview = node.get(-1)
|
|
92
|
+
try:
|
|
93
|
+
image = dataview.get_last_live_image()
|
|
94
|
+
except AttributeError:
|
|
95
|
+
image = None
|
|
96
|
+
if image is None or image.data is None:
|
|
97
|
+
raise RuntimeError("Cannot get last image from lima")
|
|
98
|
+
return image.data
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@retrymod.retry()
|
|
102
|
+
def _get_node(db_name: str, node_type: str):
|
|
103
|
+
node = get_node(db_name)
|
|
104
|
+
if node is None:
|
|
105
|
+
raise retrymod.RetryError(f"Redis node {db_name} does not exist")
|
|
106
|
+
if node.type != node_type:
|
|
107
|
+
raise RuntimeError(f"Not a Redis {node_type} node")
|
|
108
|
+
return node
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@retrymod.retry()
|
|
112
|
+
def _get_lima_dataview(node, start_index: int):
|
|
113
|
+
dataview = node.get(start_index, -1)
|
|
114
|
+
try:
|
|
115
|
+
if dataview.status_event.proxy is None:
|
|
116
|
+
raise retrymod.RetryError("Lima proxy not known (yet)")
|
|
117
|
+
except Exception:
|
|
118
|
+
raise retrymod.RetryError("Lima proxy not known (yet)")
|
|
119
|
+
return dataview
|