blissdata 0.3.4__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.
- blissdata/__init__.py +17 -0
- blissdata/beacon/__init__.py +1 -0
- blissdata/beacon/_base.py +106 -0
- blissdata/beacon/config.py +24 -0
- blissdata/beacon/data.py +45 -0
- blissdata/beacon/files.py +141 -0
- blissdata/client.py +62 -0
- blissdata/common/__init__.py +15 -0
- blissdata/common/utils.py +159 -0
- blissdata/data/__init__.py +15 -0
- blissdata/data/events/__init__.py +22 -0
- blissdata/data/events/channel.py +149 -0
- blissdata/data/events/lima.py +478 -0
- blissdata/data/events/node.py +28 -0
- blissdata/data/events/scan.py +49 -0
- blissdata/data/events/walk.py +42 -0
- blissdata/data/expiration.py +42 -0
- blissdata/data/lima_image.py +465 -0
- blissdata/data/node.py +1619 -0
- blissdata/data/nodes/__init__.py +11 -0
- blissdata/data/nodes/channel.py +394 -0
- blissdata/data/nodes/dataset.py +82 -0
- blissdata/data/nodes/dataset_collection.py +12 -0
- blissdata/data/nodes/lima.py +422 -0
- blissdata/data/nodes/node_ref_channel.py +34 -0
- blissdata/data/nodes/proposal.py +12 -0
- blissdata/data/nodes/scan.py +192 -0
- blissdata/data/nodes/scan_group.py +12 -0
- blissdata/data/remote_node.py +204 -0
- blissdata/data/scan.py +666 -0
- blissdata/h5api/__init__.py +1 -0
- blissdata/h5api/abstract.py +97 -0
- blissdata/h5api/dynamic_hdf5.py +153 -0
- blissdata/h5api/file_arguments.py +28 -0
- blissdata/h5api/static_hdf5.py +139 -0
- blissdata/h5api/utils/__init__.py +0 -0
- blissdata/h5api/utils/bliss.py +138 -0
- blissdata/h5api/utils/hdf5.py +280 -0
- blissdata/h5api/utils/hdf5_retry.py +98 -0
- blissdata/h5api/utils/lima.py +286 -0
- blissdata/h5api/utils/types.py +13 -0
- blissdata/redis/__init__.py +12 -0
- blissdata/redis/caching.py +390 -0
- blissdata/redis/connection.py +169 -0
- blissdata/redis/manager.py +164 -0
- blissdata/redis/proxy.py +971 -0
- blissdata/redis/scripting.py +24 -0
- blissdata/settings.py +1174 -0
- blissdata/streaming.py +819 -0
- blissdata/streaming_events.py +355 -0
- blissdata/tests/__init__.py +0 -0
- blissdata/tests/beacon/__init__.py +0 -0
- blissdata/tests/beacon/test_config.py +26 -0
- blissdata/tests/beacon/test_data.py +50 -0
- blissdata/tests/beacon/test_files.py +80 -0
- blissdata/tests/conftest.py +0 -0
- blissdata/tests/h5api/__init__.py +0 -0
- blissdata/tests/h5api/scanner.py +390 -0
- blissdata/tests/h5api/test_dynamic_files.py +309 -0
- blissdata/tests/h5api/test_static_files.py +169 -0
- blissdata/tests/redis/__init__.py +0 -0
- blissdata/tests/redis/manager.py +23 -0
- blissdata-0.3.4.dist-info/LICENSE +165 -0
- blissdata-0.3.4.dist-info/METADATA +79 -0
- blissdata-0.3.4.dist-info/RECORD +67 -0
- blissdata-0.3.4.dist-info/WHEEL +5 -0
- blissdata-0.3.4.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# This file is part of the bliss project
|
|
4
|
+
#
|
|
5
|
+
# Copyright (c) 2015-2023 Beamline Control Unit, ESRF
|
|
6
|
+
# Distributed under the GNU LGPLv3. See LICENSE for more info.
|
|
7
|
+
|
|
8
|
+
"""
|
|
9
|
+
Events returned received when walking a `DataNode`,
|
|
10
|
+
derived from raw Redis stream events.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from typing import NamedTuple, Union, Sequence
|
|
14
|
+
from enum import Enum
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
__all__ = ["EventType", "EventData", "Event"]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class EventType(Enum):
|
|
21
|
+
NEW_NODE = 1
|
|
22
|
+
NEW_DATA = 2
|
|
23
|
+
END_SCAN = 3
|
|
24
|
+
PREPARED_SCAN = 4
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class EventData(NamedTuple):
|
|
28
|
+
first_index: int = -1
|
|
29
|
+
"""Index of the first element of `data`"""
|
|
30
|
+
data: Union[None, Sequence, str] = None
|
|
31
|
+
"""Sequence of data points"""
|
|
32
|
+
description: any = None
|
|
33
|
+
"""Data description from the raw DataStream events"""
|
|
34
|
+
block_size: int = 0
|
|
35
|
+
"""Number of data points in this event"""
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Event(NamedTuple):
|
|
39
|
+
type: EventType
|
|
40
|
+
node: any
|
|
41
|
+
"""Node object that provides an API to the corresponding Redis node"""
|
|
42
|
+
data: EventData = None
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
from blissdata.client import get_redis_proxy
|
|
3
|
+
|
|
4
|
+
_DATA_EXPIRATION_TIME = 600 # 10 minutes
|
|
5
|
+
_PARENT_EXPIRATION_TIME = 24 * 3600 # 1 day
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def set_default_expiration_time(seconds: int, data: Optional[bool] = False):
|
|
9
|
+
if data:
|
|
10
|
+
global _DATA_EXPIRATION_TIME
|
|
11
|
+
_DATA_EXPIRATION_TIME = seconds
|
|
12
|
+
else:
|
|
13
|
+
global _PARENT_EXPIRATION_TIME
|
|
14
|
+
_PARENT_EXPIRATION_TIME = seconds
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_default_expiration_time(data: Optional[bool] = False) -> int:
|
|
18
|
+
if data:
|
|
19
|
+
return _DATA_EXPIRATION_TIME
|
|
20
|
+
else:
|
|
21
|
+
return _PARENT_EXPIRATION_TIME
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def set_expiration_time(keys, data: Optional[bool] = False):
|
|
25
|
+
"""Set the expiration time of all Redis keys"""
|
|
26
|
+
seconds = get_default_expiration_time(data)
|
|
27
|
+
async_proxy = get_redis_proxy(db=1).pipeline()
|
|
28
|
+
try:
|
|
29
|
+
for name in keys:
|
|
30
|
+
async_proxy.expire(name, seconds)
|
|
31
|
+
finally:
|
|
32
|
+
async_proxy.execute()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def remove_expiration_time(keys):
|
|
36
|
+
"""Remove the expiration time of all Redis keys"""
|
|
37
|
+
async_proxy = get_redis_proxy(db=1).pipeline()
|
|
38
|
+
try:
|
|
39
|
+
for name in keys:
|
|
40
|
+
async_proxy.persist(name)
|
|
41
|
+
finally:
|
|
42
|
+
async_proxy.execute()
|
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# This file is part of the bliss project
|
|
4
|
+
#
|
|
5
|
+
# Copyright (c) 2015-2023 Beamline Control Unit, ESRF
|
|
6
|
+
# Distributed under the GNU LGPLv3. See LICENSE for more info.
|
|
7
|
+
|
|
8
|
+
"""Utility functions to read Lima images from file or device server.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import struct
|
|
12
|
+
import numpy
|
|
13
|
+
import typing
|
|
14
|
+
import enum
|
|
15
|
+
from silx.third_party.EdfFile import EdfFile
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
import cv2
|
|
19
|
+
except ImportError:
|
|
20
|
+
cv2 = None
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
import h5py
|
|
24
|
+
except ImportError:
|
|
25
|
+
h5py = None
|
|
26
|
+
|
|
27
|
+
DATA_HEADER_FORMAT = "<IHHIIHHHHHHHHIIIIIIQ"
|
|
28
|
+
DATA_MAGIC = struct.unpack(">I", b"DTAY")[0]
|
|
29
|
+
DATA_HEADER_SIZE = struct.calcsize(DATA_HEADER_FORMAT)
|
|
30
|
+
|
|
31
|
+
VIDEO_HEADER_FORMAT = "!IHHqiiHHHH"
|
|
32
|
+
VIDEO_MAGIC = struct.unpack(">I", b"VDEO")[0]
|
|
33
|
+
VIDEO_HEADER_SIZE = struct.calcsize(VIDEO_HEADER_FORMAT)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ImageData(typing.NamedTuple):
|
|
37
|
+
array: numpy.ndarray
|
|
38
|
+
frame_id: typing.Optional[int]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class VIDEO_MODES(enum.Enum):
|
|
42
|
+
# From https://github.com/esrf-bliss/Lima/blob/master/common/include/lima/Constants.h#L118
|
|
43
|
+
Y8 = 0
|
|
44
|
+
Y16 = 1
|
|
45
|
+
Y32 = 2
|
|
46
|
+
Y64 = 3
|
|
47
|
+
RGB555 = 4
|
|
48
|
+
RGB565 = 5
|
|
49
|
+
RGB24 = 6
|
|
50
|
+
RGB32 = 7
|
|
51
|
+
BGR24 = 8
|
|
52
|
+
BGR32 = 9
|
|
53
|
+
BAYER_RG8 = 10
|
|
54
|
+
BAYER_RG16 = 11
|
|
55
|
+
BAYER_BG8 = 12
|
|
56
|
+
BAYER_BG16 = 13
|
|
57
|
+
I420 = 14
|
|
58
|
+
YUV411 = 15
|
|
59
|
+
YUV422 = 16
|
|
60
|
+
YUV444 = 17
|
|
61
|
+
YUV411PACKED = 18
|
|
62
|
+
YUV422PACKED = 19
|
|
63
|
+
YUV444PACKED = 20
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class IMAGE_MODES(enum.Enum):
|
|
67
|
+
DARRAY_UINT8 = 0
|
|
68
|
+
DARRAY_UINT16 = 1
|
|
69
|
+
DARRAY_UINT32 = 2
|
|
70
|
+
DARRAY_UINT64 = 3
|
|
71
|
+
DARRAY_INT8 = 4
|
|
72
|
+
DARRAY_INT16 = 5
|
|
73
|
+
DARRAY_INT32 = 6
|
|
74
|
+
DARRAY_INT64 = 7
|
|
75
|
+
DARRAY_FLOAT32 = 8
|
|
76
|
+
DARRAY_FLOAT64 = 9
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# Mapping used for direct conversion from raw data to numpy array
|
|
80
|
+
MODE_TO_NUMPY = {
|
|
81
|
+
IMAGE_MODES.DARRAY_UINT8: numpy.uint8,
|
|
82
|
+
IMAGE_MODES.DARRAY_UINT16: numpy.uint16,
|
|
83
|
+
IMAGE_MODES.DARRAY_UINT32: numpy.uint32,
|
|
84
|
+
IMAGE_MODES.DARRAY_UINT64: numpy.uint64,
|
|
85
|
+
IMAGE_MODES.DARRAY_INT8: numpy.int8,
|
|
86
|
+
IMAGE_MODES.DARRAY_INT16: numpy.int16,
|
|
87
|
+
IMAGE_MODES.DARRAY_INT32: numpy.int32,
|
|
88
|
+
IMAGE_MODES.DARRAY_INT64: numpy.int64,
|
|
89
|
+
IMAGE_MODES.DARRAY_FLOAT32: numpy.float32,
|
|
90
|
+
IMAGE_MODES.DARRAY_FLOAT64: numpy.float64,
|
|
91
|
+
VIDEO_MODES.Y8: numpy.uint8,
|
|
92
|
+
VIDEO_MODES.Y16: numpy.uint16,
|
|
93
|
+
VIDEO_MODES.Y32: numpy.int32,
|
|
94
|
+
VIDEO_MODES.Y64: numpy.int64,
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class RgbCodec(typing.NamedTuple):
|
|
99
|
+
opencv_code: int
|
|
100
|
+
"""OpenCV identifier for the transformation"""
|
|
101
|
+
compute_input_shape: typing.Callable
|
|
102
|
+
"""Callable to convert from output image shape to input raw shape"""
|
|
103
|
+
input_dtype: numpy.dtype
|
|
104
|
+
"""dtype for the input data"""
|
|
105
|
+
post_scale: int
|
|
106
|
+
"""bit scale to normalize the output to 8 bits"""
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
_RGB_CODECS = {}
|
|
110
|
+
_RGB_CODECS[VIDEO_MODES.RGB24] = RgbCodec(None, lambda w, h: (h, w, 3), numpy.uint8, 0)
|
|
111
|
+
_RGB_CODECS[VIDEO_MODES.RGB32] = RgbCodec(None, lambda w, h: (h, w, 4), numpy.uint8, 0)
|
|
112
|
+
|
|
113
|
+
if cv2:
|
|
114
|
+
_RGB_CODECS[VIDEO_MODES.YUV422PACKED] = RgbCodec(
|
|
115
|
+
cv2.COLOR_YUV2RGB_Y422, lambda w, h: (h, w, 2), numpy.uint8, 0
|
|
116
|
+
)
|
|
117
|
+
_RGB_CODECS[VIDEO_MODES.I420] = RgbCodec(
|
|
118
|
+
cv2.COLOR_YUV2RGB_I420, lambda w, h: (h + h // 2, w), numpy.uint8, 0
|
|
119
|
+
)
|
|
120
|
+
_RGB_CODECS[VIDEO_MODES.BGR24] = RgbCodec(
|
|
121
|
+
cv2.COLOR_BGR2RGB, lambda w, h: (h, w, 3), numpy.uint8, 0
|
|
122
|
+
)
|
|
123
|
+
_RGB_CODECS[VIDEO_MODES.BAYER_BG16] = RgbCodec(
|
|
124
|
+
cv2.COLOR_BayerRG2RGB, lambda w, h: (h, w), numpy.uint16, 12
|
|
125
|
+
)
|
|
126
|
+
_RGB_CODECS[VIDEO_MODES.BAYER_BG8] = RgbCodec(
|
|
127
|
+
cv2.COLOR_BayerRG2RGB, lambda w, h: (h, w), numpy.uint8, 0
|
|
128
|
+
)
|
|
129
|
+
_RGB_CODECS[VIDEO_MODES.BAYER_RG16] = RgbCodec(
|
|
130
|
+
cv2.COLOR_BayerRG2BGR, lambda w, h: (h, w), numpy.uint16, 12
|
|
131
|
+
)
|
|
132
|
+
_RGB_CODECS[VIDEO_MODES.BAYER_RG8] = RgbCodec(
|
|
133
|
+
cv2.COLOR_BayerRG2BGR, lambda w, h: (h, w), numpy.uint8, 0
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class ImageFormatNotSupported(Exception):
|
|
138
|
+
"""Raised when the RAW data from a Lima device can't be decoded as a grey
|
|
139
|
+
scale or RGB numpy array."""
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class Frame(typing.NamedTuple):
|
|
143
|
+
"""
|
|
144
|
+
Provide data frame from Lima including few metadata
|
|
145
|
+
"""
|
|
146
|
+
|
|
147
|
+
data: numpy.array
|
|
148
|
+
"""Data of the frame"""
|
|
149
|
+
|
|
150
|
+
frame_number: typing.Optional[int]
|
|
151
|
+
"""Number of the frame. Can be None. 0 is the first frame"""
|
|
152
|
+
|
|
153
|
+
source: str
|
|
154
|
+
"""Source of the data. Can be "video", "file", or "memory"
|
|
155
|
+
"""
|
|
156
|
+
|
|
157
|
+
def __bool__(self) -> bool:
|
|
158
|
+
"""Return true is this frame is not None
|
|
159
|
+
|
|
160
|
+
Helper for compatibility. This have to be removed. The API should return
|
|
161
|
+
`None` when there is nothing, and not return an empty tuple.
|
|
162
|
+
|
|
163
|
+
..note:: 2020-02-27: This have to be removed at one point
|
|
164
|
+
"""
|
|
165
|
+
return self.data is not None
|
|
166
|
+
|
|
167
|
+
def __iter__(self):
|
|
168
|
+
"""Mimick a 2-tuple, for compatibility with the previous version.
|
|
169
|
+
|
|
170
|
+
..note:: 2020-02-27: This have to be removed at one point
|
|
171
|
+
"""
|
|
172
|
+
yield self[0]
|
|
173
|
+
yield self[1]
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def decode_devencoded_video(
|
|
177
|
+
raw_data: bytes,
|
|
178
|
+
) -> typing.Optional[typing.Tuple[numpy.ndarray, int]]:
|
|
179
|
+
"""Decode data provided by Lima device video attribute.
|
|
180
|
+
|
|
181
|
+
See https://lima1.readthedocs.io/en/latest/applications/tango/python/doc/#devencoded-video-image
|
|
182
|
+
|
|
183
|
+
Argument:
|
|
184
|
+
raw_data: Data returns by Lima video attribute
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
A tuple with the frame data (as a numpy array), and the frame number
|
|
188
|
+
if an image is available. None if there is not yet acquired image.
|
|
189
|
+
|
|
190
|
+
Raises:
|
|
191
|
+
ImageFormatNotSupported: when the retrieved data is not supported
|
|
192
|
+
"""
|
|
193
|
+
if isinstance(raw_data, tuple):
|
|
194
|
+
# Support the direct output from proxy.video_last_image
|
|
195
|
+
if raw_data[0] != "VIDEO_IMAGE":
|
|
196
|
+
raise ImageFormatNotSupported(
|
|
197
|
+
"Data type VIDEO_IMAGE expected (found %s)." % raw_data[0]
|
|
198
|
+
)
|
|
199
|
+
raw_data = raw_data[1]
|
|
200
|
+
|
|
201
|
+
if len(raw_data) < VIDEO_HEADER_SIZE:
|
|
202
|
+
raise ImageFormatNotSupported("Image header smaller than the expected size")
|
|
203
|
+
|
|
204
|
+
(
|
|
205
|
+
magic,
|
|
206
|
+
header_version,
|
|
207
|
+
image_mode,
|
|
208
|
+
image_frame_number,
|
|
209
|
+
image_width,
|
|
210
|
+
image_height,
|
|
211
|
+
endian,
|
|
212
|
+
header_size,
|
|
213
|
+
_pad0,
|
|
214
|
+
_pad1,
|
|
215
|
+
) = struct.unpack_from(VIDEO_HEADER_FORMAT, raw_data)
|
|
216
|
+
|
|
217
|
+
if magic != VIDEO_MAGIC:
|
|
218
|
+
raise ImageFormatNotSupported(
|
|
219
|
+
"Magic header not supported (found 0x%x)." % magic
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
if header_version != 1:
|
|
223
|
+
raise ImageFormatNotSupported(
|
|
224
|
+
"Image header version not supported (found %s)." % header_version
|
|
225
|
+
)
|
|
226
|
+
if image_frame_number < 0:
|
|
227
|
+
return None
|
|
228
|
+
|
|
229
|
+
if endian != 0:
|
|
230
|
+
raise ImageFormatNotSupported(
|
|
231
|
+
"Decoding video frame from this Lima device is "
|
|
232
|
+
"not supported by bliss cause of the endianness (found %s)." % endian
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
try:
|
|
236
|
+
mode = VIDEO_MODES(image_mode)
|
|
237
|
+
except Exception:
|
|
238
|
+
raise ImageFormatNotSupported(
|
|
239
|
+
"Video format unsupported (found %s)." % image_mode
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
if mode in MODE_TO_NUMPY:
|
|
243
|
+
dtype = MODE_TO_NUMPY[mode]
|
|
244
|
+
data = numpy.frombuffer(raw_data, offset=header_size, dtype=dtype).copy()
|
|
245
|
+
data.shape = image_height, image_width
|
|
246
|
+
elif mode in _RGB_CODECS:
|
|
247
|
+
data = decode_rgb_data(
|
|
248
|
+
raw_data, image_width, image_height, mode, offset=header_size
|
|
249
|
+
)
|
|
250
|
+
else:
|
|
251
|
+
raise ImageFormatNotSupported(f"Video format {mode} is not supported")
|
|
252
|
+
|
|
253
|
+
return data, image_frame_number
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def decode_devencoded_image(raw_data: bytes) -> ImageData:
|
|
257
|
+
"""Decode data provided by Lima device image attribute
|
|
258
|
+
|
|
259
|
+
See https://lima1.readthedocs.io/en/latest/applications/tango/python/doc/#devencoded-data-array
|
|
260
|
+
|
|
261
|
+
Argument:
|
|
262
|
+
raw_data: Data returns by Lima image attribute
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
An ImageData
|
|
266
|
+
|
|
267
|
+
Raises:
|
|
268
|
+
ImageFormatNotSupported: when the retrieved data is not supported
|
|
269
|
+
"""
|
|
270
|
+
if isinstance(raw_data, tuple):
|
|
271
|
+
# Support the direct output from proxy.readImage
|
|
272
|
+
if raw_data[0] != "DATA_ARRAY":
|
|
273
|
+
raise ImageFormatNotSupported(
|
|
274
|
+
"Data type DATA_ARRAY expected (found %s)." % raw_data[0]
|
|
275
|
+
)
|
|
276
|
+
raw_data = raw_data[1]
|
|
277
|
+
|
|
278
|
+
(
|
|
279
|
+
magic,
|
|
280
|
+
version,
|
|
281
|
+
header_size,
|
|
282
|
+
_category,
|
|
283
|
+
data_type,
|
|
284
|
+
endianness,
|
|
285
|
+
nb_dim,
|
|
286
|
+
dim1,
|
|
287
|
+
dim2,
|
|
288
|
+
_dim3,
|
|
289
|
+
_dim4,
|
|
290
|
+
_dim5,
|
|
291
|
+
_dim6,
|
|
292
|
+
_dim_step1,
|
|
293
|
+
_dim_step2,
|
|
294
|
+
_dim_step3,
|
|
295
|
+
_dim_step4,
|
|
296
|
+
_dim_step5,
|
|
297
|
+
_dim_step6,
|
|
298
|
+
frame_id,
|
|
299
|
+
) = struct.unpack_from(DATA_HEADER_FORMAT, raw_data)
|
|
300
|
+
|
|
301
|
+
if magic != DATA_MAGIC:
|
|
302
|
+
raise ImageFormatNotSupported(
|
|
303
|
+
"Magic header not supported (found 0x%x)." % magic
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
# Assume backward-compatible incremental versioning
|
|
307
|
+
if version < 1:
|
|
308
|
+
raise ImageFormatNotSupported(
|
|
309
|
+
"Image header version not supported (found %s)." % version
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
if version < 3:
|
|
313
|
+
frame_id = None
|
|
314
|
+
|
|
315
|
+
try:
|
|
316
|
+
mode = IMAGE_MODES(data_type)
|
|
317
|
+
except Exception:
|
|
318
|
+
raise ImageFormatNotSupported(
|
|
319
|
+
"Image format from Lima Tango device not supported (found %s)." % data_type
|
|
320
|
+
)
|
|
321
|
+
if endianness != 0:
|
|
322
|
+
raise ImageFormatNotSupported("Unsupported endianness (found %s)." % endianness)
|
|
323
|
+
|
|
324
|
+
if nb_dim != 2:
|
|
325
|
+
raise ImageFormatNotSupported(
|
|
326
|
+
"Image header nb_dim==2 expected (found %s)." % nb_dim
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
try:
|
|
330
|
+
dtype = MODE_TO_NUMPY[mode]
|
|
331
|
+
except Exception:
|
|
332
|
+
raise ImageFormatNotSupported("Data format %s is not supported" % mode)
|
|
333
|
+
|
|
334
|
+
data = numpy.frombuffer(raw_data, offset=header_size, dtype=dtype)
|
|
335
|
+
data.shape = dim2, dim1
|
|
336
|
+
|
|
337
|
+
# Create a memory copy only if it is needed
|
|
338
|
+
if not data.flags.writeable:
|
|
339
|
+
data = numpy.array(data)
|
|
340
|
+
|
|
341
|
+
return ImageData(data, frame_id=frame_id)
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def decode_rgb_data(
|
|
345
|
+
raw_data: bytes, width: int, height: int, mode: int, offset: int = 0
|
|
346
|
+
) -> numpy.ndarray:
|
|
347
|
+
"""
|
|
348
|
+
Decode an encoded raw data into numpy array.
|
|
349
|
+
|
|
350
|
+
Arguments:
|
|
351
|
+
raw_data: Encoded raw data
|
|
352
|
+
offset: Location of the data in the buffer
|
|
353
|
+
width: width of the output image
|
|
354
|
+
height: height of the output image
|
|
355
|
+
mode: LimaCDD video mode
|
|
356
|
+
"""
|
|
357
|
+
codec = _RGB_CODECS.get(mode, None)
|
|
358
|
+
if codec is None:
|
|
359
|
+
raise ValueError(f"Video mode {mode} not supported yet.")
|
|
360
|
+
|
|
361
|
+
if codec.compute_input_shape is not None:
|
|
362
|
+
shape = codec.compute_input_shape(width, height)
|
|
363
|
+
else:
|
|
364
|
+
shape = height, width
|
|
365
|
+
|
|
366
|
+
if len(raw_data) - offset <= 0:
|
|
367
|
+
return None
|
|
368
|
+
|
|
369
|
+
# Create a view, without memory copy if possible
|
|
370
|
+
npbuf = numpy.frombuffer(raw_data, offset=offset, dtype=codec.input_dtype)
|
|
371
|
+
npbuf.shape = shape
|
|
372
|
+
|
|
373
|
+
if codec.opencv_code is not None:
|
|
374
|
+
npbuf = cv2.cvtColor(npbuf, codec.opencv_code)
|
|
375
|
+
if npbuf.ndim == 3 and npbuf.itemsize > 1 and codec.post_scale != 0:
|
|
376
|
+
in_bits = codec.post_scale
|
|
377
|
+
if in_bits is None:
|
|
378
|
+
in_bits = 8 * npbuf.dtype.itemsize
|
|
379
|
+
shift = in_bits - 8
|
|
380
|
+
if shift > 0:
|
|
381
|
+
npbuf = numpy.right_shift(npbuf, shift).astype(numpy.uint8)
|
|
382
|
+
|
|
383
|
+
# Create a memory copy only if it is needed
|
|
384
|
+
if not npbuf.flags.writeable:
|
|
385
|
+
npbuf = numpy.array(npbuf)
|
|
386
|
+
|
|
387
|
+
return npbuf
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def read_video_last_image(proxy) -> typing.Optional[typing.Tuple[numpy.ndarray, int]]:
|
|
391
|
+
"""Read and decode video last image from a Lima detector
|
|
392
|
+
|
|
393
|
+
Argument:
|
|
394
|
+
proxy: A Tango Lima proxy
|
|
395
|
+
|
|
396
|
+
Returns:
|
|
397
|
+
A tuple with the frame data (as a numpy array), and the frame number
|
|
398
|
+
if an image is available. None if there is not yet acquired image.
|
|
399
|
+
|
|
400
|
+
Raises:
|
|
401
|
+
ImageFormatNotSupported: when the retrieved data is not supported
|
|
402
|
+
"""
|
|
403
|
+
# get last video image
|
|
404
|
+
_, raw_data = proxy.video_last_image
|
|
405
|
+
decoded = decode_devencoded_video(raw_data)
|
|
406
|
+
return decoded
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def image_from_server(proxy, image_index_in_scan: int) -> ImageData:
|
|
410
|
+
"""Read and decode image (or last image ready) from a Lima detector.
|
|
411
|
+
|
|
412
|
+
Argument:
|
|
413
|
+
proxy: A Tango Lima proxy
|
|
414
|
+
image_index_in_scan: The image index to decode, else -1 to use the last index
|
|
415
|
+
(last_image_ready).
|
|
416
|
+
|
|
417
|
+
Returns:
|
|
418
|
+
The frame data (as an ImageData)
|
|
419
|
+
|
|
420
|
+
Raises:
|
|
421
|
+
IndexError: when no images are yet taken
|
|
422
|
+
ImageFormatNotSupported: when the retrieved data is not supported
|
|
423
|
+
"""
|
|
424
|
+
if image_index_in_scan == -1:
|
|
425
|
+
image_index_in_scan = proxy.last_image_ready
|
|
426
|
+
if image_index_in_scan == -1:
|
|
427
|
+
raise IndexError("No image has been taken yet")
|
|
428
|
+
|
|
429
|
+
try:
|
|
430
|
+
raw_msg = proxy.readImage(image_index_in_scan)
|
|
431
|
+
except Exception:
|
|
432
|
+
raise RuntimeError("Error while reading image")
|
|
433
|
+
else:
|
|
434
|
+
raw_msg = raw_msg[-1]
|
|
435
|
+
|
|
436
|
+
data = decode_devencoded_image(raw_msg)
|
|
437
|
+
return data
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
def image_from_file(filename, path_in_file, image_index, file_format):
|
|
441
|
+
"""
|
|
442
|
+
:param str filename:
|
|
443
|
+
:param str path_in_file:
|
|
444
|
+
:param int image_index: for multi-frame formats
|
|
445
|
+
:param str file_format: HDF5, HDF5BS, EDFLZ4, ...
|
|
446
|
+
This is not the file extension!
|
|
447
|
+
"""
|
|
448
|
+
file_format = file_format.lower()
|
|
449
|
+
if file_format.startswith("edf"):
|
|
450
|
+
if file_format == "edfconcat":
|
|
451
|
+
image_index = 0
|
|
452
|
+
if EdfFile is not None:
|
|
453
|
+
f = EdfFile(filename)
|
|
454
|
+
return f.GetData(image_index)
|
|
455
|
+
else:
|
|
456
|
+
raise RuntimeError(
|
|
457
|
+
"EdfFile module is not available, " "cannot return image data."
|
|
458
|
+
)
|
|
459
|
+
elif file_format.startswith("hdf5"):
|
|
460
|
+
if h5py is not None:
|
|
461
|
+
with h5py.File(filename, mode="r") as f:
|
|
462
|
+
dataset = f[path_in_file]
|
|
463
|
+
return dataset[image_index]
|
|
464
|
+
else:
|
|
465
|
+
raise RuntimeError("Format not managed yet")
|