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
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import sys
|
|
3
|
+
import time
|
|
4
|
+
from collections import Counter
|
|
5
|
+
from numbers import Number
|
|
6
|
+
from typing import Any
|
|
7
|
+
from typing import Dict
|
|
8
|
+
from typing import Generator
|
|
9
|
+
from typing import List
|
|
10
|
+
from typing import Optional
|
|
11
|
+
from typing import Tuple
|
|
12
|
+
|
|
13
|
+
import numpy
|
|
14
|
+
from blissdata.beacon.data import BeaconData
|
|
15
|
+
from blissdata.lima.client import lima_client_factory
|
|
16
|
+
from blissdata.redis_engine.exceptions import EndOfStream
|
|
17
|
+
from blissdata.redis_engine.exceptions import IndexNoMoreThereError
|
|
18
|
+
from blissdata.redis_engine.exceptions import IndexNotYetThereError
|
|
19
|
+
from blissdata.redis_engine.exceptions import IndexWontBeThereError
|
|
20
|
+
from blissdata.redis_engine.scan import Scan
|
|
21
|
+
from blissdata.redis_engine.scan import ScanState
|
|
22
|
+
from blissdata.redis_engine.store import DataStore
|
|
23
|
+
from blissdata.redis_engine.stream import StreamingClient
|
|
24
|
+
from numpy.typing import ArrayLike
|
|
25
|
+
|
|
26
|
+
from ..utils import validate_and_init_names
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
from blissdata.stream import LimaStream
|
|
30
|
+
except ImportError:
|
|
31
|
+
from blissdata.streams.lima_stream import LimaStream
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
logger = logging.getLogger(__name__)
|
|
35
|
+
|
|
36
|
+
INFINITY = sys.maxsize
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _get_data_store() -> None:
|
|
40
|
+
redis_url = BeaconData().get_redis_data_db()
|
|
41
|
+
return DataStore(redis_url)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def iter_bliss_scan_data_from_memory(
|
|
45
|
+
scan_key: str,
|
|
46
|
+
lima_names: Optional[List[str]] = None,
|
|
47
|
+
counter_names: Optional[List[str]] = None,
|
|
48
|
+
retry_timeout: Optional[Number] = None,
|
|
49
|
+
retry_period: Optional[Number] = None,
|
|
50
|
+
buffer_in_memory: bool = True,
|
|
51
|
+
) -> Generator[Dict[str, Any], None, None]:
|
|
52
|
+
if not buffer_in_memory:
|
|
53
|
+
raise NotImplementedError("Requires blissdata>=2.0.0rc1")
|
|
54
|
+
lima_names, counter_names = validate_and_init_names(lima_names, counter_names)
|
|
55
|
+
|
|
56
|
+
data_store = _get_data_store()
|
|
57
|
+
scan = data_store.load_scan(scan_key, scan_cls=Scan)
|
|
58
|
+
buffers = {name: list() for name in lima_names + counter_names}
|
|
59
|
+
if not buffers:
|
|
60
|
+
return
|
|
61
|
+
|
|
62
|
+
while scan.state < ScanState.PREPARED:
|
|
63
|
+
scan.update()
|
|
64
|
+
|
|
65
|
+
lima_streams = dict()
|
|
66
|
+
lima_clients = dict()
|
|
67
|
+
counter_streams = dict()
|
|
68
|
+
for name, stream in scan.streams.items():
|
|
69
|
+
if stream.encoding["type"] == "json" and "lima" in stream.info["format"]:
|
|
70
|
+
if name.split(":")[-2] in lima_names:
|
|
71
|
+
lima_streams[name] = stream
|
|
72
|
+
lima_clients[name] = lima_client_factory(data_store, stream.info)
|
|
73
|
+
elif name.split(":")[-1] in counter_names:
|
|
74
|
+
counter_streams[name] = stream
|
|
75
|
+
|
|
76
|
+
client = StreamingClient({**lima_streams, **counter_streams})
|
|
77
|
+
client_timeout = retry_period or 0
|
|
78
|
+
lima_buffer_count = Counter()
|
|
79
|
+
|
|
80
|
+
while True:
|
|
81
|
+
try:
|
|
82
|
+
output = client.read(timeout=client_timeout)
|
|
83
|
+
except EndOfStream:
|
|
84
|
+
break
|
|
85
|
+
for stream, (_, payload) in output.items():
|
|
86
|
+
name_parts = stream.name.split(":")
|
|
87
|
+
if stream.name in lima_streams:
|
|
88
|
+
# payload is a sequence of JSON statuses
|
|
89
|
+
ctr_name = name_parts[-2]
|
|
90
|
+
last_status = payload[-1]
|
|
91
|
+
lima_client = lima_clients[stream.name]
|
|
92
|
+
lima_client.update(**last_status)
|
|
93
|
+
n_already_read = lima_buffer_count[ctr_name]
|
|
94
|
+
try:
|
|
95
|
+
data = lima_client[n_already_read:]
|
|
96
|
+
except IndexNoMoreThereError:
|
|
97
|
+
continue
|
|
98
|
+
buffers[ctr_name].extend(data)
|
|
99
|
+
lima_buffer_count[ctr_name] += len(data)
|
|
100
|
+
else:
|
|
101
|
+
# payload is a sequence of data points (0D, 1D, 2D)
|
|
102
|
+
ctr_name = name_parts[-1]
|
|
103
|
+
buffers[ctr_name].extend(payload)
|
|
104
|
+
|
|
105
|
+
nyield = min(len(v) for v in buffers.values())
|
|
106
|
+
if nyield:
|
|
107
|
+
for i in range(nyield):
|
|
108
|
+
yield {name: values[i] for name, values in buffers.items()}
|
|
109
|
+
buffers = {name: values[nyield:] for name, values in buffers.items()}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def last_lima_image(channel_info: dict) -> ArrayLike:
|
|
113
|
+
"""Get last lima image from memory"""
|
|
114
|
+
data_store = _get_data_store()
|
|
115
|
+
lima_client = lima_client_factory(data_store, channel_info)
|
|
116
|
+
return lima_client.get_last_live_image().array
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _get_streams(
|
|
120
|
+
scan_key: str,
|
|
121
|
+
lima_names: List[str],
|
|
122
|
+
counter_names: List[str],
|
|
123
|
+
):
|
|
124
|
+
data_store = _get_data_store()
|
|
125
|
+
scan = data_store.load_scan(scan_key, scan_cls=Scan)
|
|
126
|
+
|
|
127
|
+
while scan.state < ScanState.PREPARED:
|
|
128
|
+
scan.update()
|
|
129
|
+
|
|
130
|
+
lima_streams = dict()
|
|
131
|
+
counter_streams = dict()
|
|
132
|
+
|
|
133
|
+
for name, stream in scan.streams.items():
|
|
134
|
+
if stream.encoding["type"] == "json" and "lima" in stream.info["format"]:
|
|
135
|
+
if name.split(":")[-2] in lima_names:
|
|
136
|
+
lima_streams[name.split(":")[-2]] = LimaStream(stream)
|
|
137
|
+
|
|
138
|
+
elif name.split(":")[-1] in counter_names:
|
|
139
|
+
counter_streams[name.split(":")[-1]] = stream
|
|
140
|
+
|
|
141
|
+
return lima_streams, counter_streams
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def iter_bliss_scan_data_from_memory_slice(
|
|
145
|
+
scan_key: str,
|
|
146
|
+
lima_names: List[str],
|
|
147
|
+
counter_names: List[str],
|
|
148
|
+
slice_range: Optional[Tuple[int, int]] = None,
|
|
149
|
+
retry_timeout: Optional[Number] = None,
|
|
150
|
+
retry_period: Optional[Number] = None,
|
|
151
|
+
yield_timeout: Optional[Number] = None,
|
|
152
|
+
max_slicing_size: Optional[Number] = None,
|
|
153
|
+
verbose: Optional[bool] = False,
|
|
154
|
+
):
|
|
155
|
+
"""Iterates over the data from a Bliss scan, slicing the streams associated to a lima detector or a counter between specific indexes of the scan (optional)
|
|
156
|
+
|
|
157
|
+
:param str scan_key: key of the Bliss scan (e.g. "esrf:scan:XXXX")
|
|
158
|
+
:param list lima_names: names of lima detectors
|
|
159
|
+
:param list counter_names: names of non-lima detectors (you need to provide at least one)
|
|
160
|
+
:param tuple slice_range: two elements which define the limits of the iteration along the scan. If None, it iterates along the whole scan
|
|
161
|
+
:param Number retry_timeout: timeout when it cannot access the data for `retry_timeout` seconds
|
|
162
|
+
:param Number retry_period: interval in seconds between data access retries
|
|
163
|
+
:param Number yield_timeout: timeout to stop slicing the stream and yield the buffered data
|
|
164
|
+
:param Number max_slicing_size: maximum size of frames to be sliced out of the stream in one single iteration. If None, it will slice all the available data in the stream
|
|
165
|
+
:yields dict: data
|
|
166
|
+
"""
|
|
167
|
+
lima_streams, counter_streams = _get_streams(scan_key, lima_names, counter_names)
|
|
168
|
+
|
|
169
|
+
all_streams = {**lima_streams, **counter_streams}
|
|
170
|
+
if not all_streams:
|
|
171
|
+
logger.warning("There is no stream to slice")
|
|
172
|
+
return
|
|
173
|
+
|
|
174
|
+
if slice_range is None:
|
|
175
|
+
slice_range = (0, INFINITY)
|
|
176
|
+
|
|
177
|
+
if retry_period is None:
|
|
178
|
+
retry_period = 1
|
|
179
|
+
|
|
180
|
+
if yield_timeout is None:
|
|
181
|
+
yield_timeout = 0.01
|
|
182
|
+
|
|
183
|
+
buffers_count = Counter({counter: slice_range[0] for counter in all_streams.keys()})
|
|
184
|
+
|
|
185
|
+
# Read and yield continuously
|
|
186
|
+
stream_on = True
|
|
187
|
+
|
|
188
|
+
incoming_buffers = {stream_name: [] for stream_name in all_streams.keys()}
|
|
189
|
+
non_yielded_buffers = {stream_name: [] for stream_name in all_streams.keys()}
|
|
190
|
+
|
|
191
|
+
restart_buffer = time.perf_counter()
|
|
192
|
+
while stream_on:
|
|
193
|
+
|
|
194
|
+
# While loop will stop unless one single stream is successfully sliced
|
|
195
|
+
stream_on = False
|
|
196
|
+
|
|
197
|
+
for stream_name, stream in all_streams.items():
|
|
198
|
+
try:
|
|
199
|
+
# Stop condition for limited slices
|
|
200
|
+
if (
|
|
201
|
+
slice_range[1] is not INFINITY
|
|
202
|
+
and buffers_count[stream_name] >= slice_range[1]
|
|
203
|
+
):
|
|
204
|
+
continue
|
|
205
|
+
|
|
206
|
+
# Test first index, (slicing between limits do not fall into Error)
|
|
207
|
+
_ = stream[buffers_count[stream_name]]
|
|
208
|
+
if max_slicing_size is None:
|
|
209
|
+
stream_data = stream[buffers_count[stream_name] : slice_range[1]]
|
|
210
|
+
else:
|
|
211
|
+
stream_data = stream[
|
|
212
|
+
buffers_count[stream_name] : min(
|
|
213
|
+
slice_range[1],
|
|
214
|
+
buffers_count[stream_name] + max_slicing_size,
|
|
215
|
+
)
|
|
216
|
+
]
|
|
217
|
+
incoming_buffers[stream_name] = stream_data
|
|
218
|
+
buffers_count[stream_name] += len(stream_data)
|
|
219
|
+
stream_on = True
|
|
220
|
+
|
|
221
|
+
except IndexNotYetThereError:
|
|
222
|
+
stream_on = True
|
|
223
|
+
except IndexWontBeThereError:
|
|
224
|
+
pass
|
|
225
|
+
except IndexNoMoreThereError:
|
|
226
|
+
pass
|
|
227
|
+
except EndOfStream:
|
|
228
|
+
pass
|
|
229
|
+
except RuntimeError:
|
|
230
|
+
pass
|
|
231
|
+
|
|
232
|
+
for stream_name in incoming_buffers.keys():
|
|
233
|
+
if len(incoming_buffers[stream_name]) > 0:
|
|
234
|
+
if len(non_yielded_buffers[stream_name]) == 0:
|
|
235
|
+
non_yielded_buffers[stream_name] = numpy.array(
|
|
236
|
+
incoming_buffers[stream_name]
|
|
237
|
+
)
|
|
238
|
+
else:
|
|
239
|
+
non_yielded_buffers[stream_name] = numpy.concatenate(
|
|
240
|
+
(
|
|
241
|
+
non_yielded_buffers[stream_name],
|
|
242
|
+
incoming_buffers[stream_name],
|
|
243
|
+
)
|
|
244
|
+
)
|
|
245
|
+
incoming_buffers[stream_name] = []
|
|
246
|
+
|
|
247
|
+
if not stream_on or ((time.perf_counter() - restart_buffer) > yield_timeout):
|
|
248
|
+
|
|
249
|
+
frames_to_yield = min(
|
|
250
|
+
[len(value) for value in non_yielded_buffers.values()]
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
if frames_to_yield > 0:
|
|
254
|
+
if verbose:
|
|
255
|
+
for stream_name, stream_buffer in non_yielded_buffers.items():
|
|
256
|
+
print(
|
|
257
|
+
f"After slicing the stream: {stream_name} buffer contains {len(stream_buffer)} items"
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
# Yield point by point
|
|
261
|
+
for index in range(frames_to_yield):
|
|
262
|
+
yield {
|
|
263
|
+
stream_name: stream_buffer[index]
|
|
264
|
+
for stream_name, stream_buffer in non_yielded_buffers.items()
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
# Save the non-yielded points for the next iteration
|
|
268
|
+
for stream_name in non_yielded_buffers.keys():
|
|
269
|
+
non_yielded_buffers[stream_name] = non_yielded_buffers[stream_name][
|
|
270
|
+
frames_to_yield:
|
|
271
|
+
]
|
|
272
|
+
|
|
273
|
+
if verbose:
|
|
274
|
+
for stream_name, stream_buffer in non_yielded_buffers.items():
|
|
275
|
+
print(
|
|
276
|
+
f"After yielding: {stream_name} buffer contains {len(stream_buffer)} non-yielded items"
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
restart_buffer = time.perf_counter()
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import sys
|
|
3
|
+
import time
|
|
4
|
+
from collections import Counter
|
|
5
|
+
from collections.abc import Iterator
|
|
6
|
+
from importlib.metadata import version
|
|
7
|
+
from typing import Any
|
|
8
|
+
from typing import Dict
|
|
9
|
+
from typing import Generator
|
|
10
|
+
from typing import List
|
|
11
|
+
from typing import Optional
|
|
12
|
+
from typing import Tuple
|
|
13
|
+
|
|
14
|
+
import numpy
|
|
15
|
+
from blissdata.beacon.data import BeaconData
|
|
16
|
+
from blissdata.redis_engine.scan import ScanState
|
|
17
|
+
from blissdata.redis_engine.store import DataStore
|
|
18
|
+
from numpy.typing import ArrayLike
|
|
19
|
+
from packaging.version import Version
|
|
20
|
+
|
|
21
|
+
from ..utils import validate_and_init_names
|
|
22
|
+
|
|
23
|
+
_BLISSDATA_VERSION = Version(version("blissdata"))
|
|
24
|
+
if _BLISSDATA_VERSION >= Version("2.3.0"):
|
|
25
|
+
from blissdata.exceptions import EndOfStream
|
|
26
|
+
from blissdata.exceptions import IndexNoMoreThereError
|
|
27
|
+
from blissdata.exceptions import IndexNotYetThereError
|
|
28
|
+
from blissdata.exceptions import IndexWontBeThereError
|
|
29
|
+
from blissdata.streams import CursorGroup
|
|
30
|
+
else:
|
|
31
|
+
from blissdata.redis_engine.exceptions import EndOfStream
|
|
32
|
+
from blissdata.redis_engine.exceptions import IndexNoMoreThereError
|
|
33
|
+
from blissdata.redis_engine.exceptions import IndexNotYetThereError
|
|
34
|
+
from blissdata.redis_engine.exceptions import IndexWontBeThereError
|
|
35
|
+
from blissdata.streams.base import CursorGroup
|
|
36
|
+
|
|
37
|
+
logger = logging.getLogger(__name__)
|
|
38
|
+
|
|
39
|
+
INFINITY = sys.maxsize
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _get_data_store() -> None:
|
|
43
|
+
redis_url = BeaconData().get_redis_data_db()
|
|
44
|
+
return DataStore(redis_url)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def iter_bliss_scan_data_from_memory(
|
|
48
|
+
scan_key: str,
|
|
49
|
+
lima_names: Optional[List[str]] = None,
|
|
50
|
+
counter_names: Optional[List[str]] = None,
|
|
51
|
+
retry_timeout: Optional[float] = None,
|
|
52
|
+
retry_period: Optional[float] = None,
|
|
53
|
+
buffer_in_memory: bool = False,
|
|
54
|
+
) -> Generator[Dict[str, Any], None, None]:
|
|
55
|
+
lima_names, counter_names = validate_and_init_names(lima_names, counter_names)
|
|
56
|
+
|
|
57
|
+
streams = _get_streams(scan_key, lima_names, counter_names)
|
|
58
|
+
if not streams:
|
|
59
|
+
return
|
|
60
|
+
|
|
61
|
+
cursor = CursorGroup(list(streams.values()))
|
|
62
|
+
cursor_timeout = retry_period or 0
|
|
63
|
+
ctr_names = lima_names + counter_names
|
|
64
|
+
data_buffers = {ctr_name: _DataBuffer() for ctr_name in ctr_names}
|
|
65
|
+
stream_to_ctr_names = {
|
|
66
|
+
stream.name: ctr_name for ctr_name, stream in streams.items()
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
while True:
|
|
70
|
+
try:
|
|
71
|
+
views = cursor.read(timeout=cursor_timeout)
|
|
72
|
+
except EndOfStream:
|
|
73
|
+
# No more data - stop the generator
|
|
74
|
+
progress = [
|
|
75
|
+
f"{ctr_name}: {data_buffer.progress}"
|
|
76
|
+
for ctr_name, data_buffer in data_buffers.items()
|
|
77
|
+
]
|
|
78
|
+
logger.info("Data progress on exit:\n %s", "\n".join(progress))
|
|
79
|
+
for ctr_name, data_buffer in data_buffers.items():
|
|
80
|
+
if data_buffer.length_left:
|
|
81
|
+
logger.warning(
|
|
82
|
+
"Data points for counter %r: %s",
|
|
83
|
+
ctr_name,
|
|
84
|
+
data_buffer.progress,
|
|
85
|
+
)
|
|
86
|
+
break
|
|
87
|
+
|
|
88
|
+
for stream, view in views.items():
|
|
89
|
+
ctr_name = stream_to_ctr_names[stream.name]
|
|
90
|
+
data_buffers[ctr_name].append_view(view)
|
|
91
|
+
|
|
92
|
+
if buffer_in_memory:
|
|
93
|
+
for data_buffer in data_buffers.values():
|
|
94
|
+
data_buffer.buffer_in_memory()
|
|
95
|
+
|
|
96
|
+
while True:
|
|
97
|
+
try:
|
|
98
|
+
ctr_values = [
|
|
99
|
+
next(data_buffer) for data_buffer in data_buffers.values()
|
|
100
|
+
]
|
|
101
|
+
except StopIteration:
|
|
102
|
+
# One of the buffers ran out of data
|
|
103
|
+
break
|
|
104
|
+
|
|
105
|
+
yield dict(zip(ctr_names, ctr_values))
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class _DataBuffer(Iterator):
|
|
109
|
+
"""Data iterator with view and data buffering for a single counter."""
|
|
110
|
+
|
|
111
|
+
def __init__(self):
|
|
112
|
+
self._views = []
|
|
113
|
+
self._view_lengths = []
|
|
114
|
+
self._first_view_index = 0
|
|
115
|
+
self._buffer = []
|
|
116
|
+
self._total_length = 0
|
|
117
|
+
self._total_yielded = 0
|
|
118
|
+
|
|
119
|
+
@property
|
|
120
|
+
def length_left(self) -> int:
|
|
121
|
+
return self._total_length - self._total_yielded
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def progress(self) -> str:
|
|
125
|
+
return f"{self._total_yielded}/{self._total_length}"
|
|
126
|
+
|
|
127
|
+
def buffer_in_memory(self) -> None:
|
|
128
|
+
"""Buffer and clear all view objects."""
|
|
129
|
+
if not self._views:
|
|
130
|
+
return
|
|
131
|
+
starts = [0] * len(self._views)
|
|
132
|
+
starts[0] = self._first_view_index
|
|
133
|
+
for view, start in zip(self._views, starts):
|
|
134
|
+
self._buffer.extend(view.get_data(start=start, stop=None))
|
|
135
|
+
self._views.clear()
|
|
136
|
+
self._view_lengths.clear()
|
|
137
|
+
self._first_view_index = 0
|
|
138
|
+
|
|
139
|
+
def append_view(self, view) -> None:
|
|
140
|
+
"""Append view object when not empty."""
|
|
141
|
+
view_length = len(view)
|
|
142
|
+
if view_length:
|
|
143
|
+
self._views.append(view)
|
|
144
|
+
self._view_lengths.append(view_length)
|
|
145
|
+
self._total_length += view_length
|
|
146
|
+
|
|
147
|
+
def __iter__(self) -> "_DataBuffer":
|
|
148
|
+
return self
|
|
149
|
+
|
|
150
|
+
def __next__(self):
|
|
151
|
+
"""Return next scan data point for this counter."""
|
|
152
|
+
if self._buffer:
|
|
153
|
+
return self._buffer.pop(0)
|
|
154
|
+
|
|
155
|
+
if len(self._views) == 0:
|
|
156
|
+
raise StopIteration
|
|
157
|
+
|
|
158
|
+
view = self._views[0]
|
|
159
|
+
start = self._first_view_index
|
|
160
|
+
|
|
161
|
+
self._total_yielded += 1
|
|
162
|
+
self._first_view_index += 1
|
|
163
|
+
|
|
164
|
+
if self._first_view_index == self._view_lengths[0]:
|
|
165
|
+
self._views.pop(0)
|
|
166
|
+
self._view_lengths.pop(0)
|
|
167
|
+
self._first_view_index = 0
|
|
168
|
+
|
|
169
|
+
data = view.get_data(start=start, stop=start + 1)
|
|
170
|
+
return data[0]
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def last_lima_image(scan_key: str, lima_name: str) -> ArrayLike:
|
|
174
|
+
"""Get last lima image from memory"""
|
|
175
|
+
streams = _get_streams(scan_key, [lima_name], [])
|
|
176
|
+
stream = list(streams.values())[0]
|
|
177
|
+
return stream.get_last_live_image().array
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _get_streams(
|
|
181
|
+
scan_key: str,
|
|
182
|
+
lima_names: List[str],
|
|
183
|
+
counter_names: List[str],
|
|
184
|
+
):
|
|
185
|
+
data_store = _get_data_store()
|
|
186
|
+
scan = data_store.load_scan(scan_key)
|
|
187
|
+
|
|
188
|
+
while scan.state < ScanState.PREPARED:
|
|
189
|
+
scan.update()
|
|
190
|
+
|
|
191
|
+
streams = dict()
|
|
192
|
+
|
|
193
|
+
for name, stream in scan.streams.items():
|
|
194
|
+
if (
|
|
195
|
+
stream.event_stream.encoding["type"] == "json"
|
|
196
|
+
and "lima" in stream.info["format"]
|
|
197
|
+
):
|
|
198
|
+
if name.split(":")[-2] in lima_names:
|
|
199
|
+
streams[name.split(":")[-2]] = stream
|
|
200
|
+
|
|
201
|
+
elif name.split(":")[-1] in counter_names:
|
|
202
|
+
streams[name.split(":")[-1]] = stream
|
|
203
|
+
|
|
204
|
+
nnames = len(lima_names) + len(counter_names)
|
|
205
|
+
nstreams = len(streams)
|
|
206
|
+
if nnames != nstreams:
|
|
207
|
+
logger.warning("asked for %d names but got %s streams", nnames, nstreams)
|
|
208
|
+
return dict()
|
|
209
|
+
|
|
210
|
+
return streams
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def iter_bliss_scan_data_from_memory_slice(
|
|
214
|
+
scan_key: str,
|
|
215
|
+
lima_names: List[str],
|
|
216
|
+
counter_names: List[str],
|
|
217
|
+
slice_range: Optional[Tuple[int, int]] = None,
|
|
218
|
+
retry_timeout: Optional[float] = None,
|
|
219
|
+
retry_period: Optional[float] = None,
|
|
220
|
+
yield_timeout: Optional[float] = None,
|
|
221
|
+
max_slicing_size: Optional[float] = None,
|
|
222
|
+
verbose: Optional[bool] = False,
|
|
223
|
+
):
|
|
224
|
+
"""Iterates over the data from a Bliss scan, slicing the streams associated to a lima detector or a counter between specific indexes of the scan (optional)
|
|
225
|
+
|
|
226
|
+
:param str scan_key: key of the Bliss scan (e.g. "esrf:scan:XXXX")
|
|
227
|
+
:param list lima_names: names of lima detectors
|
|
228
|
+
:param list counter_names: names of non-lima detectors (you need to provide at least one)
|
|
229
|
+
:param tuple slice_range: two elements which define the limits of the iteration along the scan. If None, it iterates along the whole scan
|
|
230
|
+
:param float retry_timeout: timeout when it cannot access the data for `retry_timeout` seconds
|
|
231
|
+
:param float retry_period: interval in seconds between data access retries
|
|
232
|
+
:param float yield_timeout: timeout to stop slicing the stream and yield the buffered data
|
|
233
|
+
:param float max_slicing_size: maximum size of frames to be sliced out of the stream in one single iteration. If None, it will slice all the available data in the stream
|
|
234
|
+
:yields dict: data
|
|
235
|
+
"""
|
|
236
|
+
streams = _get_streams(scan_key, lima_names, counter_names)
|
|
237
|
+
if not streams:
|
|
238
|
+
return
|
|
239
|
+
|
|
240
|
+
if slice_range is None:
|
|
241
|
+
slice_range = (0, INFINITY)
|
|
242
|
+
|
|
243
|
+
if yield_timeout is None:
|
|
244
|
+
yield_timeout = 0.01
|
|
245
|
+
|
|
246
|
+
buffers_count = Counter({counter: slice_range[0] for counter in streams.keys()})
|
|
247
|
+
|
|
248
|
+
# Read and yield continuously
|
|
249
|
+
stream_on = True
|
|
250
|
+
|
|
251
|
+
incoming_buffers = {stream_name: [] for stream_name in streams.keys()}
|
|
252
|
+
non_yielded_buffers = {stream_name: [] for stream_name in streams.keys()}
|
|
253
|
+
|
|
254
|
+
restart_buffer = time.perf_counter()
|
|
255
|
+
while stream_on:
|
|
256
|
+
|
|
257
|
+
# While loop will stop unless one single stream is successfully sliced
|
|
258
|
+
stream_on = False
|
|
259
|
+
|
|
260
|
+
for stream_name, stream in streams.items():
|
|
261
|
+
try:
|
|
262
|
+
# Stop condition for limited slices
|
|
263
|
+
if (
|
|
264
|
+
slice_range[1] is not INFINITY
|
|
265
|
+
and buffers_count[stream_name] >= slice_range[1]
|
|
266
|
+
):
|
|
267
|
+
continue
|
|
268
|
+
|
|
269
|
+
# Test first index, (slicing between limits do not fall into Error)
|
|
270
|
+
_ = stream[buffers_count[stream_name]]
|
|
271
|
+
if max_slicing_size is None:
|
|
272
|
+
stream_data = stream[buffers_count[stream_name] : slice_range[1]]
|
|
273
|
+
else:
|
|
274
|
+
stream_data = stream[
|
|
275
|
+
buffers_count[stream_name] : min(
|
|
276
|
+
slice_range[1],
|
|
277
|
+
buffers_count[stream_name] + max_slicing_size,
|
|
278
|
+
)
|
|
279
|
+
]
|
|
280
|
+
incoming_buffers[stream_name] = stream_data
|
|
281
|
+
buffers_count[stream_name] += len(stream_data)
|
|
282
|
+
stream_on = True
|
|
283
|
+
|
|
284
|
+
except IndexNotYetThereError:
|
|
285
|
+
stream_on = True
|
|
286
|
+
except IndexWontBeThereError:
|
|
287
|
+
pass
|
|
288
|
+
except IndexNoMoreThereError:
|
|
289
|
+
pass
|
|
290
|
+
except EndOfStream:
|
|
291
|
+
pass
|
|
292
|
+
except RuntimeError:
|
|
293
|
+
pass
|
|
294
|
+
|
|
295
|
+
for stream_name in incoming_buffers.keys():
|
|
296
|
+
if len(incoming_buffers[stream_name]) > 0:
|
|
297
|
+
if len(non_yielded_buffers[stream_name]) == 0:
|
|
298
|
+
non_yielded_buffers[stream_name] = numpy.array(
|
|
299
|
+
incoming_buffers[stream_name]
|
|
300
|
+
)
|
|
301
|
+
else:
|
|
302
|
+
non_yielded_buffers[stream_name] = numpy.concatenate(
|
|
303
|
+
(
|
|
304
|
+
non_yielded_buffers[stream_name],
|
|
305
|
+
incoming_buffers[stream_name],
|
|
306
|
+
)
|
|
307
|
+
)
|
|
308
|
+
incoming_buffers[stream_name] = []
|
|
309
|
+
|
|
310
|
+
if not stream_on or ((time.perf_counter() - restart_buffer) > yield_timeout):
|
|
311
|
+
|
|
312
|
+
frames_to_yield = min(
|
|
313
|
+
[len(value) for value in non_yielded_buffers.values()]
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
if frames_to_yield > 0:
|
|
317
|
+
if verbose:
|
|
318
|
+
for stream_name, stream_buffer in non_yielded_buffers.items():
|
|
319
|
+
print(
|
|
320
|
+
f"After slicing the stream: {stream_name} buffer contains {len(stream_buffer)} items"
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
# Yield point by point
|
|
324
|
+
for index in range(frames_to_yield):
|
|
325
|
+
yield {
|
|
326
|
+
stream_name: stream_buffer[index]
|
|
327
|
+
for stream_name, stream_buffer in non_yielded_buffers.items()
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
# Save the non-yielded points for the next iteration
|
|
331
|
+
for stream_name in non_yielded_buffers.keys():
|
|
332
|
+
non_yielded_buffers[stream_name] = non_yielded_buffers[stream_name][
|
|
333
|
+
frames_to_yield:
|
|
334
|
+
]
|
|
335
|
+
|
|
336
|
+
if verbose:
|
|
337
|
+
for stream_name, stream_buffer in non_yielded_buffers.items():
|
|
338
|
+
print(
|
|
339
|
+
f"After yielding: {stream_name} buffer contains {len(stream_buffer)} non-yielded items"
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
restart_buffer = time.perf_counter()
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
from collections.abc import Generator
|
|
3
|
+
from typing import Iterator
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ContextIterator(Generator):
|
|
7
|
+
"""Can wrap an iterator or generator to ensure cleanup when used as a context manager."""
|
|
8
|
+
|
|
9
|
+
def __init__(self, it: Iterator):
|
|
10
|
+
self._it = it
|
|
11
|
+
|
|
12
|
+
def __iter__(self):
|
|
13
|
+
return self
|
|
14
|
+
|
|
15
|
+
def __next__(self):
|
|
16
|
+
return next(self._it)
|
|
17
|
+
|
|
18
|
+
def __enter__(self):
|
|
19
|
+
return self
|
|
20
|
+
|
|
21
|
+
def __exit__(self, *_):
|
|
22
|
+
if hasattr(self._it, "close"):
|
|
23
|
+
return self._it.close()
|
|
24
|
+
|
|
25
|
+
def send(self, value):
|
|
26
|
+
if hasattr(self._it, "send"):
|
|
27
|
+
return self._it.send(value)
|
|
28
|
+
|
|
29
|
+
def throw(self, *args):
|
|
30
|
+
if hasattr(self._it, "throw"):
|
|
31
|
+
return self._it.throw(*args)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def contextiterator(iterator):
|
|
35
|
+
"""Decorator that allows to use an iterator or generator as a context manager.
|
|
36
|
+
This ensures that the iterator is closed when exiting the context manager.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
@functools.wraps(iterator)
|
|
40
|
+
def wrapper(*args, **kw):
|
|
41
|
+
return ContextIterator(iterator(*args, **kw))
|
|
42
|
+
|
|
43
|
+
return wrapper
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from contextlib import contextmanager
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from silx.io import h5py_utils
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@contextmanager
|
|
9
|
+
def h5context(filename: str, h5path: Optional[str] = None, **openargs):
|
|
10
|
+
with h5py_utils.File(filename, **openargs) as f:
|
|
11
|
+
if h5path:
|
|
12
|
+
yield f[h5path]
|
|
13
|
+
else:
|
|
14
|
+
yield f
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def h5exists(filename: str, h5path: Optional[str] = None, **openargs) -> bool:
|
|
18
|
+
if not os.path.isfile(filename):
|
|
19
|
+
return False
|
|
20
|
+
with h5py_utils.open_item(filename, "/", **openargs) as f:
|
|
21
|
+
if h5path and h5path != "/":
|
|
22
|
+
return h5path in f
|
|
23
|
+
return True
|