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.
Files changed (67) hide show
  1. blissdata/__init__.py +17 -0
  2. blissdata/beacon/__init__.py +1 -0
  3. blissdata/beacon/_base.py +106 -0
  4. blissdata/beacon/config.py +24 -0
  5. blissdata/beacon/data.py +45 -0
  6. blissdata/beacon/files.py +141 -0
  7. blissdata/client.py +62 -0
  8. blissdata/common/__init__.py +15 -0
  9. blissdata/common/utils.py +159 -0
  10. blissdata/data/__init__.py +15 -0
  11. blissdata/data/events/__init__.py +22 -0
  12. blissdata/data/events/channel.py +149 -0
  13. blissdata/data/events/lima.py +478 -0
  14. blissdata/data/events/node.py +28 -0
  15. blissdata/data/events/scan.py +49 -0
  16. blissdata/data/events/walk.py +42 -0
  17. blissdata/data/expiration.py +42 -0
  18. blissdata/data/lima_image.py +465 -0
  19. blissdata/data/node.py +1619 -0
  20. blissdata/data/nodes/__init__.py +11 -0
  21. blissdata/data/nodes/channel.py +394 -0
  22. blissdata/data/nodes/dataset.py +82 -0
  23. blissdata/data/nodes/dataset_collection.py +12 -0
  24. blissdata/data/nodes/lima.py +422 -0
  25. blissdata/data/nodes/node_ref_channel.py +34 -0
  26. blissdata/data/nodes/proposal.py +12 -0
  27. blissdata/data/nodes/scan.py +192 -0
  28. blissdata/data/nodes/scan_group.py +12 -0
  29. blissdata/data/remote_node.py +204 -0
  30. blissdata/data/scan.py +666 -0
  31. blissdata/h5api/__init__.py +1 -0
  32. blissdata/h5api/abstract.py +97 -0
  33. blissdata/h5api/dynamic_hdf5.py +153 -0
  34. blissdata/h5api/file_arguments.py +28 -0
  35. blissdata/h5api/static_hdf5.py +139 -0
  36. blissdata/h5api/utils/__init__.py +0 -0
  37. blissdata/h5api/utils/bliss.py +138 -0
  38. blissdata/h5api/utils/hdf5.py +280 -0
  39. blissdata/h5api/utils/hdf5_retry.py +98 -0
  40. blissdata/h5api/utils/lima.py +286 -0
  41. blissdata/h5api/utils/types.py +13 -0
  42. blissdata/redis/__init__.py +12 -0
  43. blissdata/redis/caching.py +390 -0
  44. blissdata/redis/connection.py +169 -0
  45. blissdata/redis/manager.py +164 -0
  46. blissdata/redis/proxy.py +971 -0
  47. blissdata/redis/scripting.py +24 -0
  48. blissdata/settings.py +1174 -0
  49. blissdata/streaming.py +819 -0
  50. blissdata/streaming_events.py +355 -0
  51. blissdata/tests/__init__.py +0 -0
  52. blissdata/tests/beacon/__init__.py +0 -0
  53. blissdata/tests/beacon/test_config.py +26 -0
  54. blissdata/tests/beacon/test_data.py +50 -0
  55. blissdata/tests/beacon/test_files.py +80 -0
  56. blissdata/tests/conftest.py +0 -0
  57. blissdata/tests/h5api/__init__.py +0 -0
  58. blissdata/tests/h5api/scanner.py +390 -0
  59. blissdata/tests/h5api/test_dynamic_files.py +309 -0
  60. blissdata/tests/h5api/test_static_files.py +169 -0
  61. blissdata/tests/redis/__init__.py +0 -0
  62. blissdata/tests/redis/manager.py +23 -0
  63. blissdata-0.3.4.dist-info/LICENSE +165 -0
  64. blissdata-0.3.4.dist-info/METADATA +79 -0
  65. blissdata-0.3.4.dist-info/RECORD +67 -0
  66. blissdata-0.3.4.dist-info/WHEEL +5 -0
  67. 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")