blissdata 2.4.0__tar.gz → 2.4.2__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.
- {blissdata-2.4.0/src/blissdata.egg-info → blissdata-2.4.2}/PKG-INFO +1 -1
- {blissdata-2.4.0 → blissdata-2.4.2}/pyproject.toml +1 -1
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/lima/client.py +46 -26
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/streams/lima/stream.py +40 -48
- blissdata-2.4.2/src/blissdata/tests/streams/plugins/test_lima_stream.py +173 -0
- {blissdata-2.4.0 → blissdata-2.4.2/src/blissdata.egg-info}/PKG-INFO +1 -1
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata.egg-info/SOURCES.txt +1 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/LICENSE +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/README.md +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/setup.cfg +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/__init__.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/beacon/__init__.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/beacon/_base.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/beacon/_utils.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/beacon/config.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/beacon/data.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/beacon/files.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/deprecation.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/exceptions.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/h5api/__init__.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/h5api/abstract.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/h5api/dynamic_hdf5.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/h5api/file_arguments.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/h5api/h5_scan_map.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/h5api/redis_hdf5.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/h5api/static_hdf5.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/h5api/utils/__init__.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/h5api/utils/bliss.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/h5api/utils/hdf5.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/h5api/utils/hdf5_retry.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/h5api/utils/lima.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/h5api/utils/types.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/h5map/__init__.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/h5map/data_items.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/h5map/hdf5_items.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/lima/__init__.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/lima/image_utils.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/py.typed +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/redis_engine/__init__.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/redis_engine/encoding/__init__.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/redis_engine/encoding/json.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/redis_engine/encoding/numeric.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/redis_engine/event_stream.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/redis_engine/exceptions.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/redis_engine/identities.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/redis_engine/memory_tracker.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/redis_engine/scan.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/redis_engine/store.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/scan.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/schemas/__init__.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/schemas/scan_info.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/schemas/scan_info_display.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/streams/__init__.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/streams/base.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/streams/default.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/streams/encoding/__init__.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/streams/encoding/json.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/streams/encoding/numeric.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/streams/event_stream.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/streams/hdf5_fallback/__init__.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/streams/hdf5_fallback/stream.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/streams/lima/__init__.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/streams/scan_sequence/__init__.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/streams/scan_sequence/stream.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/streams/sink.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/tests/__init__.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/tests/beacon/__init__.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/tests/beacon/test_config.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/tests/beacon/test_data.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/tests/beacon/test_files.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/tests/conftest.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/tests/h5api/__init__.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/tests/h5api/scanner.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/tests/h5api/test_dynamic_files.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/tests/h5api/test_h5api_lima_utils.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/tests/h5api/test_hdf5_handlers.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/tests/h5api/test_redis_files.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/tests/h5api/test_static_files.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/tests/h5map/__init__.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/tests/h5map/test_data_items.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/tests/h5map/test_hdf5_items.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/tests/lima/__init__.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/tests/lima/test_image_utils.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/tests/redis_engine/__init__.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/tests/redis_engine/conftest.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/tests/redis_engine/test_cursor.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/tests/redis_engine/test_memory_tracker.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/tests/redis_engine/test_scan.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/tests/redis_engine/test_search.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/tests/redis_engine/utils.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/tests/streams/encoding/__init__.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/tests/streams/encoding/test_json.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/tests/streams/encoding/test_numeric.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/tests/streams/plugins/__init__.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/tests/streams/plugins/test_file_backed_stream.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/tests/streams/plugins/test_plugin_base.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/tests/streams/test_event_stream.py +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata.egg-info/dependency_links.txt +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata.egg-info/entry_points.txt +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata.egg-info/requires.txt +0 -0
- {blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata.egg-info/top_level.txt +0 -0
|
@@ -5,13 +5,13 @@
|
|
|
5
5
|
# Copyright (c) Beamline Control Unit, ESRF
|
|
6
6
|
# Distributed under the GNU LGPLv3. See LICENSE for more info.
|
|
7
7
|
import math
|
|
8
|
+
import time
|
|
8
9
|
import logging
|
|
9
10
|
import numpy as np
|
|
10
11
|
from packaging.version import Version
|
|
11
12
|
from functools import cached_property
|
|
12
13
|
from dataclasses import dataclass
|
|
13
14
|
|
|
14
|
-
from blissdata.exceptions import IndexNoMoreThereError, IndexNotYetThereError
|
|
15
15
|
from . import image_utils
|
|
16
16
|
|
|
17
17
|
try:
|
|
@@ -58,7 +58,7 @@ class ImageReference:
|
|
|
58
58
|
class LimaClient:
|
|
59
59
|
PROTOCOL_VERSION = 1
|
|
60
60
|
|
|
61
|
-
def __init__(self, data_store, **lima_info):
|
|
61
|
+
def __init__(self, data_store, request_update_cb, **lima_info):
|
|
62
62
|
protocol = lima_info["protocol_version"]
|
|
63
63
|
if self.PROTOCOL_VERSION != protocol:
|
|
64
64
|
raise Exception(
|
|
@@ -66,6 +66,8 @@ class LimaClient:
|
|
|
66
66
|
)
|
|
67
67
|
|
|
68
68
|
self._data_store = data_store
|
|
69
|
+
self._request_update = request_update_cb
|
|
70
|
+
self._stream_sealed = False
|
|
69
71
|
self._proxy = None
|
|
70
72
|
self._server_url = lima_info["server_url"]
|
|
71
73
|
self._buffer_max_number = lima_info["buffer_max_number"]
|
|
@@ -123,9 +125,10 @@ class LimaClient:
|
|
|
123
125
|
def __len__(self):
|
|
124
126
|
return self._last_index + 1
|
|
125
127
|
|
|
126
|
-
def update(self, last_index, last_index_saved):
|
|
128
|
+
def update(self, last_index=-1, last_index_saved=-1, sealed=False):
|
|
127
129
|
self._last_index = max(self._last_index, last_index)
|
|
128
130
|
self._last_index_saved = max(self._last_index_saved, last_index_saved)
|
|
131
|
+
self._stream_sealed = self._stream_sealed or sealed
|
|
129
132
|
if self._saved:
|
|
130
133
|
self._last_index_saved_and_closed = self._last_readable_index()
|
|
131
134
|
|
|
@@ -155,10 +158,14 @@ class LimaClient:
|
|
|
155
158
|
|
|
156
159
|
def _last_readable_index(self):
|
|
157
160
|
"""Return the last frame index that can be read from a closed file, assuming a
|
|
158
|
-
file is closed when at least one frame is saved
|
|
161
|
+
file is closed either when stream is sealed or when at least one frame is saved
|
|
162
|
+
into the next file.
|
|
159
163
|
NOTE: This is trickier than a modulo as an acquisition may not be an exact
|
|
160
164
|
multiple of the file length (see self._decompose_frame_id).
|
|
161
165
|
"""
|
|
166
|
+
if self._stream_sealed:
|
|
167
|
+
return self._last_index_saved
|
|
168
|
+
|
|
162
169
|
acq_in_scan, file_in_acq, _ = self._decompose_frame_id(self._last_index_saved)
|
|
163
170
|
return (
|
|
164
171
|
acq_in_scan * self._frames_per_acquisition
|
|
@@ -215,7 +222,7 @@ class LimaClient:
|
|
|
215
222
|
frame_id = normalize_frame_id(data.frame_id)
|
|
216
223
|
return image_utils.ImageData(data.array, frame_id, None)
|
|
217
224
|
|
|
218
|
-
def __getitem__(self, key):
|
|
225
|
+
def __getitem__(self, key: int | slice):
|
|
219
226
|
if isinstance(key, int):
|
|
220
227
|
return self._get_index(key)
|
|
221
228
|
elif isinstance(key, slice):
|
|
@@ -232,21 +239,28 @@ class LimaClient:
|
|
|
232
239
|
raise NotImplementedError("Negative index not supported")
|
|
233
240
|
|
|
234
241
|
if self._saved and frame_id <= self._last_index_saved_and_closed:
|
|
235
|
-
#
|
|
242
|
+
# 1. priority to file if available
|
|
236
243
|
return self._get_from_file(frame_id)
|
|
237
244
|
elif frame_id <= self._last_index:
|
|
238
|
-
# try to access on lima server
|
|
239
245
|
try:
|
|
246
|
+
# 2. try from server then
|
|
240
247
|
return self._get_from_server(frame_id)
|
|
241
|
-
except
|
|
242
|
-
# wait for image to become available from file
|
|
243
|
-
# TODO wait_for_file(timeout)
|
|
248
|
+
except IndexError:
|
|
244
249
|
if self._saved:
|
|
250
|
+
# 3. finally wait for file
|
|
251
|
+
while (
|
|
252
|
+
frame_id > self._last_index_saved_and_closed
|
|
253
|
+
and not self._stream_sealed
|
|
254
|
+
):
|
|
255
|
+
self._request_update()
|
|
256
|
+
time.sleep(0.1)
|
|
257
|
+
if frame_id > self._last_index_saved_and_closed:
|
|
258
|
+
raise
|
|
245
259
|
return self._get_from_file(frame_id)
|
|
246
260
|
else:
|
|
247
261
|
raise
|
|
248
262
|
else:
|
|
249
|
-
raise
|
|
263
|
+
raise IndexError
|
|
250
264
|
|
|
251
265
|
def _get_slice(self, key):
|
|
252
266
|
key_range = range(*key.indices(len(self)))
|
|
@@ -267,7 +281,10 @@ class LimaClient:
|
|
|
267
281
|
|
|
268
282
|
def _get_from_server(self, frame_id):
|
|
269
283
|
if self._buffer_max_number <= (self._last_index - frame_id):
|
|
270
|
-
|
|
284
|
+
# if frame_id is in the current acquisition, then it's not in the
|
|
285
|
+
# buffer any longer. If another acquisition started, it's not here
|
|
286
|
+
# neither.
|
|
287
|
+
raise IndexError
|
|
271
288
|
|
|
272
289
|
acq_in_scan, file_in_acq, frame_in_file = self._decompose_frame_id(frame_id)
|
|
273
290
|
frame_in_acq = file_in_acq * self._frames_per_file + frame_in_file
|
|
@@ -278,15 +295,24 @@ class LimaClient:
|
|
|
278
295
|
if not self._is_acquisition_still_on_server(
|
|
279
296
|
acq_in_scan + self._acquisition_offset
|
|
280
297
|
):
|
|
281
|
-
raise
|
|
298
|
+
raise IndexError
|
|
282
299
|
|
|
283
|
-
|
|
284
|
-
|
|
300
|
+
try:
|
|
301
|
+
frame = image_utils.image_from_server(self.proxy, frame_in_acq)
|
|
302
|
+
except (RuntimeError, image_utils.NoImageAvailable) as e:
|
|
303
|
+
raise IndexError from e
|
|
304
|
+
|
|
305
|
+
if frame_in_acq != frame.frame_id:
|
|
306
|
+
_logger.warning(
|
|
307
|
+
f"Requested frame {frame_in_acq} from lima device, "
|
|
308
|
+
f"got frame {frame.frame_id}. Discarding it."
|
|
309
|
+
)
|
|
310
|
+
raise IndexError
|
|
285
311
|
|
|
286
312
|
if not self._is_acquisition_still_on_server(
|
|
287
313
|
acq_in_scan + self._acquisition_offset
|
|
288
314
|
):
|
|
289
|
-
raise
|
|
315
|
+
raise IndexError
|
|
290
316
|
|
|
291
317
|
return frame.array
|
|
292
318
|
|
|
@@ -296,19 +322,13 @@ class LimaClient:
|
|
|
296
322
|
ref.file_path, ref.data_path, ref.index, ref.format
|
|
297
323
|
)
|
|
298
324
|
|
|
299
|
-
def get_references(self, key) -> ImageReference | list[ImageReference]:
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
# would not return the same amount of images and references.
|
|
303
|
-
saved_len = self._last_index + 1
|
|
325
|
+
def get_references(self, key: int | slice) -> ImageReference | list[ImageReference]:
|
|
326
|
+
"""Create references no matter the images are available or not. It is
|
|
327
|
+
caller responsibility to query indices that make sense."""
|
|
304
328
|
if isinstance(key, int):
|
|
305
|
-
if key < 0:
|
|
306
|
-
key += saved_len
|
|
307
|
-
elif key >= saved_len:
|
|
308
|
-
raise IndexNotYetThereError
|
|
309
329
|
return self._file_ref(key)
|
|
310
330
|
elif isinstance(key, slice):
|
|
311
|
-
key_range = range(*key.indices(
|
|
331
|
+
key_range = range(*key.indices(len(self)))
|
|
312
332
|
return [self._file_ref(i) for i in key_range]
|
|
313
333
|
else:
|
|
314
334
|
raise TypeError(
|
|
@@ -28,8 +28,8 @@ from blissdata.lima.image_utils import ImageData
|
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
class LimaView(BaseView):
|
|
31
|
-
def __init__(self,
|
|
32
|
-
self.
|
|
31
|
+
def __init__(self, stream: "LimaStream", start: int, stop: int) -> None:
|
|
32
|
+
self._stream = stream
|
|
33
33
|
self._view_range = range(start, stop)
|
|
34
34
|
|
|
35
35
|
@property
|
|
@@ -41,13 +41,13 @@ class LimaView(BaseView):
|
|
|
41
41
|
|
|
42
42
|
def get_data(self, start=None, stop=None):
|
|
43
43
|
trimmed_range = self._view_range[start:stop]
|
|
44
|
-
return self.
|
|
44
|
+
return self._stream[trimmed_range.start : trimmed_range.stop]
|
|
45
45
|
|
|
46
46
|
def get_references(
|
|
47
47
|
self, start=None, stop=None
|
|
48
48
|
) -> ImageReference | list[ImageReference]:
|
|
49
49
|
trimmed_range = self._view_range[start:stop]
|
|
50
|
-
return self.
|
|
50
|
+
return self._stream.get_references(
|
|
51
51
|
slice(trimmed_range.start, trimmed_range.stop)
|
|
52
52
|
)
|
|
53
53
|
|
|
@@ -67,10 +67,16 @@ class LimaStream(BaseStream, LimaDirectAccess):
|
|
|
67
67
|
def __init__(self, event_stream):
|
|
68
68
|
super().__init__(event_stream)
|
|
69
69
|
self._client = LimaClient(
|
|
70
|
-
event_stream._data_store,
|
|
70
|
+
data_store=event_stream._data_store,
|
|
71
|
+
request_update_cb=self._force_update_client,
|
|
72
|
+
**event_stream.info["lima_info"],
|
|
71
73
|
)
|
|
72
74
|
self._cursor = Stream(event_stream).cursor()
|
|
73
75
|
|
|
76
|
+
def _force_update_client(self):
|
|
77
|
+
_, status_list, sealed = self._event_stream._read_last(0)
|
|
78
|
+
self._client.update(**status_list[0], sealed=sealed)
|
|
79
|
+
|
|
74
80
|
@staticmethod
|
|
75
81
|
def make_definition(
|
|
76
82
|
name: str,
|
|
@@ -145,31 +151,44 @@ class LimaStream(BaseStream, LimaDirectAccess):
|
|
|
145
151
|
return tuple(self.info["shape"])
|
|
146
152
|
|
|
147
153
|
def __len__(self):
|
|
148
|
-
self.
|
|
154
|
+
self._update_client_if_needed(-1)
|
|
149
155
|
return len(self._client)
|
|
150
156
|
|
|
151
|
-
def
|
|
157
|
+
def _update_client_if_needed(self, key: int | slice):
|
|
158
|
+
if isinstance(key, slice):
|
|
159
|
+
need_update = key.stop is None or not (0 <= key.stop <= len(self._client))
|
|
160
|
+
else:
|
|
161
|
+
need_update = not (0 <= key < len(self._client))
|
|
162
|
+
if not need_update:
|
|
163
|
+
return
|
|
164
|
+
|
|
152
165
|
try:
|
|
153
166
|
view = self._cursor.read(block=False, last_only=True)
|
|
154
167
|
except EndOfStream:
|
|
155
|
-
|
|
156
|
-
|
|
168
|
+
view = None
|
|
169
|
+
# use ._seal instead of .is_sealed() to skip redis query and because
|
|
170
|
+
# we just updated it by reading cursor.
|
|
171
|
+
sealed = self._event_stream._seal is not None
|
|
172
|
+
if view is None:
|
|
173
|
+
self._client.update(sealed=sealed)
|
|
174
|
+
else:
|
|
157
175
|
last_status = view.get_data(-1, None)[0]
|
|
158
|
-
self._client.update(**last_status)
|
|
176
|
+
self._client.update(**last_status, sealed=sealed)
|
|
159
177
|
|
|
160
178
|
def __getitem__(self, key):
|
|
161
|
-
|
|
162
|
-
need_update = key.stop is None or not (0 <= key.stop < len(self._client))
|
|
163
|
-
else:
|
|
164
|
-
need_update = not (0 <= key < len(self._client))
|
|
179
|
+
return self._get_from_client(key, reference_mode=False)
|
|
165
180
|
|
|
166
|
-
|
|
167
|
-
|
|
181
|
+
def get_references(self, key) -> ImageReference | list[ImageReference]:
|
|
182
|
+
return self._get_from_client(key, reference_mode=True)
|
|
168
183
|
|
|
184
|
+
def _get_from_client(self, key, reference_mode):
|
|
185
|
+
self._update_client_if_needed(key)
|
|
169
186
|
try:
|
|
170
|
-
|
|
187
|
+
if reference_mode:
|
|
188
|
+
return self._client.get_references(key)
|
|
189
|
+
else:
|
|
190
|
+
return self._client[key]
|
|
171
191
|
except IndexError:
|
|
172
|
-
# TODO could be verified before asking the client
|
|
173
192
|
index = key.start if isinstance(key, slice) else key
|
|
174
193
|
if index is None:
|
|
175
194
|
index = 0
|
|
@@ -177,7 +196,7 @@ class LimaStream(BaseStream, LimaDirectAccess):
|
|
|
177
196
|
index += len(self._client)
|
|
178
197
|
|
|
179
198
|
if index >= len(self._client):
|
|
180
|
-
if self.
|
|
199
|
+
if self._client._stream_sealed:
|
|
181
200
|
raise IndexWontBeThereError
|
|
182
201
|
else:
|
|
183
202
|
raise IndexNotYetThereError
|
|
@@ -189,7 +208,7 @@ class LimaStream(BaseStream, LimaDirectAccess):
|
|
|
189
208
|
return True
|
|
190
209
|
|
|
191
210
|
def _build_view_from_events(self, index: int, events: EventRange, last_only: bool):
|
|
192
|
-
self._client.update(**events.data[-1])
|
|
211
|
+
self._client.update(**events.data[-1], sealed=events.end_of_stream)
|
|
193
212
|
|
|
194
213
|
if len(self._client) <= index:
|
|
195
214
|
# no new images despite client update
|
|
@@ -199,34 +218,7 @@ class LimaStream(BaseStream, LimaDirectAccess):
|
|
|
199
218
|
start = len(self._client) - 1
|
|
200
219
|
else:
|
|
201
220
|
start = index
|
|
202
|
-
return LimaView(self
|
|
203
|
-
|
|
204
|
-
def get_references(self, key) -> ImageReference | list[ImageReference]:
|
|
205
|
-
if isinstance(key, slice):
|
|
206
|
-
need_update = key.stop is None or not (0 <= key.stop < len(self._client))
|
|
207
|
-
else:
|
|
208
|
-
need_update = not (0 <= key < len(self._client))
|
|
209
|
-
|
|
210
|
-
if need_update:
|
|
211
|
-
self._update_client()
|
|
212
|
-
|
|
213
|
-
try:
|
|
214
|
-
return self._client.get_references(key)
|
|
215
|
-
except IndexError:
|
|
216
|
-
# TODO could be verified before asking the client
|
|
217
|
-
index = key.start if isinstance(key, slice) else key
|
|
218
|
-
if index is None:
|
|
219
|
-
index = 0
|
|
220
|
-
elif index < 0:
|
|
221
|
-
index += len(self._client)
|
|
222
|
-
|
|
223
|
-
if index >= len(self._client):
|
|
224
|
-
if self._event_stream.is_sealed():
|
|
225
|
-
raise IndexWontBeThereError
|
|
226
|
-
else:
|
|
227
|
-
raise IndexNotYetThereError
|
|
228
|
-
else:
|
|
229
|
-
raise IndexNoMoreThereError
|
|
221
|
+
return LimaView(self, start, len(self._client))
|
|
230
222
|
|
|
231
223
|
def get_last_live_image(self) -> ImageData:
|
|
232
224
|
return self._client.get_last_live_image()
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import time
|
|
3
|
+
from threading import Thread
|
|
4
|
+
from contextlib import contextmanager
|
|
5
|
+
|
|
6
|
+
from blissdata.lima.client import LimaClient
|
|
7
|
+
from blissdata.streams.encoding.json import JsonStreamEncoder
|
|
8
|
+
from blissdata.streams.event_stream import EventStream
|
|
9
|
+
from blissdata.streams.lima import LimaStream
|
|
10
|
+
from blissdata.exceptions import (
|
|
11
|
+
IndexNoMoreThereError,
|
|
12
|
+
IndexNotYetThereError,
|
|
13
|
+
IndexWontBeThereError,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class LimaDataSimulator:
|
|
18
|
+
def __init__(self, rw_stream, buffer_size, acq_size, file_size=None):
|
|
19
|
+
self.rw_stream = rw_stream
|
|
20
|
+
self.last_index = -1
|
|
21
|
+
self.last_index_saved = -1
|
|
22
|
+
|
|
23
|
+
self.buffer_size = buffer_size
|
|
24
|
+
self.acq_size = acq_size
|
|
25
|
+
self.file_size = file_size
|
|
26
|
+
|
|
27
|
+
def publish_images(self, last_index=-1, last_index_saved=-1):
|
|
28
|
+
self.last_index = max(self.last_index, last_index)
|
|
29
|
+
self.last_index_saved = max(self.last_index_saved, last_index_saved)
|
|
30
|
+
self.rw_stream.send(
|
|
31
|
+
{"last_index": self.last_index, "last_index_saved": self.last_index_saved}
|
|
32
|
+
)
|
|
33
|
+
self.rw_stream.join()
|
|
34
|
+
|
|
35
|
+
def get_from_server_mock(self, frame_id: int):
|
|
36
|
+
if frame_id > self.last_index:
|
|
37
|
+
raise IndexError("Not acquired yet")
|
|
38
|
+
if frame_id <= self.last_index - self.buffer_size:
|
|
39
|
+
raise IndexError("Buffer expired")
|
|
40
|
+
print(f"read {frame_id} from memory")
|
|
41
|
+
return f"memo-{frame_id}"
|
|
42
|
+
|
|
43
|
+
def get_from_file_mock(self, frame_id):
|
|
44
|
+
if self.file_size is None:
|
|
45
|
+
raise Exception("Stream is not being saved")
|
|
46
|
+
if frame_id > (self.last_index_saved // self.file_size * self.file_size):
|
|
47
|
+
raise Exception("File not complete")
|
|
48
|
+
print(f"read {frame_id} from file")
|
|
49
|
+
return f"file-{frame_id}"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@contextmanager
|
|
53
|
+
def lima_stream_pair(data_store, buffer_size, acq_size, file_size=None):
|
|
54
|
+
info = {
|
|
55
|
+
"protocol_version": LimaClient.PROTOCOL_VERSION,
|
|
56
|
+
"server_url": "cafe",
|
|
57
|
+
"buffer_max_number": buffer_size,
|
|
58
|
+
"frame_per_acquisition": acq_size,
|
|
59
|
+
"acquisition_offset": 0,
|
|
60
|
+
}
|
|
61
|
+
if file_size is not None:
|
|
62
|
+
info.update(
|
|
63
|
+
{
|
|
64
|
+
"file_offset": 0,
|
|
65
|
+
"frame_per_file": file_size,
|
|
66
|
+
"file_path": "foo",
|
|
67
|
+
"data_path": "bar",
|
|
68
|
+
"file_format": "hdf5",
|
|
69
|
+
}
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
encoder = JsonStreamEncoder()
|
|
73
|
+
model = data_store._stream_model(encoding=encoder.info(), info={"lima_info": info})
|
|
74
|
+
event_rw_stream = EventStream.create(data_store, "test_stream", model)
|
|
75
|
+
event_ro_stream = EventStream.open(data_store, "test_stream", model)
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
yield LimaStream(event_rw_stream), LimaStream(event_ro_stream)
|
|
79
|
+
finally:
|
|
80
|
+
event_rw_stream._sink.stop()
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def test_get_image_no_file(monkeypatch, data_store):
|
|
84
|
+
params = dict(buffer_size=10, acq_size=1000, file_size=None)
|
|
85
|
+
|
|
86
|
+
with lima_stream_pair(data_store, **params) as (rw_stream, ro_stream):
|
|
87
|
+
data_simu = LimaDataSimulator(rw_stream, **params)
|
|
88
|
+
monkeypatch.setattr(
|
|
89
|
+
LimaClient, "_get_from_server", data_simu.get_from_server_mock
|
|
90
|
+
)
|
|
91
|
+
monkeypatch.setattr(LimaClient, "_get_from_file", data_simu.get_from_file_mock)
|
|
92
|
+
|
|
93
|
+
# not image at all
|
|
94
|
+
assert len(ro_stream) == 0
|
|
95
|
+
with pytest.raises(IndexNotYetThereError):
|
|
96
|
+
ro_stream[0]
|
|
97
|
+
|
|
98
|
+
# 0 to 4
|
|
99
|
+
data_simu.publish_images(last_index=4)
|
|
100
|
+
assert len(ro_stream) == 5
|
|
101
|
+
assert ro_stream[0] == "memo-0"
|
|
102
|
+
assert ro_stream[4] == "memo-4"
|
|
103
|
+
with pytest.raises(IndexNotYetThereError):
|
|
104
|
+
ro_stream[5]
|
|
105
|
+
|
|
106
|
+
# 5 to 14 (buffer wrapped around)
|
|
107
|
+
data_simu.publish_images(last_index=14)
|
|
108
|
+
assert len(ro_stream) == 15
|
|
109
|
+
assert ro_stream[5] == "memo-5"
|
|
110
|
+
assert ro_stream[14] == "memo-14"
|
|
111
|
+
with pytest.raises(IndexNoMoreThereError):
|
|
112
|
+
ro_stream[4]
|
|
113
|
+
with pytest.raises(IndexNotYetThereError):
|
|
114
|
+
ro_stream[15]
|
|
115
|
+
|
|
116
|
+
rw_stream.seal()
|
|
117
|
+
with pytest.raises(IndexWontBeThereError):
|
|
118
|
+
ro_stream[15]
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@pytest.mark.timeout(60)
|
|
122
|
+
def test_get_image_file(monkeypatch, data_store):
|
|
123
|
+
params = dict(buffer_size=10, acq_size=1000, file_size=20)
|
|
124
|
+
|
|
125
|
+
with lima_stream_pair(data_store, **params) as (rw_stream, ro_stream):
|
|
126
|
+
data_simu = LimaDataSimulator(rw_stream, **params)
|
|
127
|
+
monkeypatch.setattr(
|
|
128
|
+
LimaClient, "_get_from_server", data_simu.get_from_server_mock
|
|
129
|
+
)
|
|
130
|
+
monkeypatch.setattr(LimaClient, "_get_from_file", data_simu.get_from_file_mock)
|
|
131
|
+
|
|
132
|
+
# buffer [0, 9], file [None]
|
|
133
|
+
data_simu.publish_images(last_index=9, last_index_saved=9)
|
|
134
|
+
assert len(ro_stream) == 10
|
|
135
|
+
assert ro_stream[0] == "memo-0"
|
|
136
|
+
assert ro_stream[9] == "memo-9"
|
|
137
|
+
with pytest.raises(IndexNotYetThereError):
|
|
138
|
+
ro_stream[10]
|
|
139
|
+
|
|
140
|
+
# buffer [15, 24], file [0-19]
|
|
141
|
+
data_simu.publish_images(last_index=24, last_index_saved=20)
|
|
142
|
+
assert len(ro_stream) == 25
|
|
143
|
+
assert ro_stream[0] == "file-0"
|
|
144
|
+
assert ro_stream[9] == "file-9"
|
|
145
|
+
assert ro_stream[19] == "file-19"
|
|
146
|
+
assert ro_stream[20] == "memo-20"
|
|
147
|
+
assert ro_stream[24] == "memo-24"
|
|
148
|
+
with pytest.raises(IndexNotYetThereError):
|
|
149
|
+
ro_stream[25]
|
|
150
|
+
|
|
151
|
+
# buffer [25, 34], file [0-19] ----> dead zone [20, 24]
|
|
152
|
+
data_simu.publish_images(last_index=34, last_index_saved=20)
|
|
153
|
+
|
|
154
|
+
def complete_file_soon():
|
|
155
|
+
time.sleep(0.5)
|
|
156
|
+
# buffer [31, 40], file [0-39] ----> file caught up the dead zone
|
|
157
|
+
data_simu.publish_images(last_index=40, last_index_saved=40)
|
|
158
|
+
|
|
159
|
+
t = Thread(target=complete_file_soon)
|
|
160
|
+
t.start()
|
|
161
|
+
# reading in the dead zone will wait for file index to catch up
|
|
162
|
+
assert ro_stream[24] == "file-24"
|
|
163
|
+
t.join()
|
|
164
|
+
|
|
165
|
+
# read from memory if file is not complete
|
|
166
|
+
assert ro_stream[40] == "memo-40"
|
|
167
|
+
|
|
168
|
+
rw_stream.seal()
|
|
169
|
+
with pytest.raises(IndexWontBeThereError):
|
|
170
|
+
ro_stream[41]
|
|
171
|
+
|
|
172
|
+
# read from file after stream sealing
|
|
173
|
+
assert ro_stream[40] == "file-40"
|
|
@@ -95,4 +95,5 @@ src/blissdata/tests/streams/encoding/test_json.py
|
|
|
95
95
|
src/blissdata/tests/streams/encoding/test_numeric.py
|
|
96
96
|
src/blissdata/tests/streams/plugins/__init__.py
|
|
97
97
|
src/blissdata/tests/streams/plugins/test_file_backed_stream.py
|
|
98
|
+
src/blissdata/tests/streams/plugins/test_lima_stream.py
|
|
98
99
|
src/blissdata/tests/streams/plugins/test_plugin_base.py
|
|
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
|
|
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
|
|
File without changes
|
{blissdata-2.4.0 → blissdata-2.4.2}/src/blissdata/tests/streams/plugins/test_file_backed_stream.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|