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,390 @@
1
+ import os
2
+ import time
3
+ from numbers import Number
4
+ from datetime import datetime
5
+ from contextlib import contextmanager
6
+ from multiprocessing import Queue, Process
7
+ from typing import Optional, Sequence, NamedTuple, Iterator, Mapping, Tuple
8
+
9
+ import h5py
10
+ import numpy
11
+ from numpy.typing import DTypeLike
12
+ from silx.io.dictdump import nxtodict
13
+
14
+ SEP = "/"
15
+
16
+
17
+ class DataPoint(NamedTuple):
18
+ values: numpy.ndarray
19
+ url_template: str
20
+
21
+
22
+ class ChannelInfo(NamedTuple):
23
+ name: str
24
+ shape: tuple = tuple()
25
+ dtype: DTypeLike = numpy.uint16
26
+ internal_template: str = os.path.join("{{dirname}}", "{{basename}}") + SEP.join(
27
+ ("::", "{scan_number}.1", "instrument", "{name}")
28
+ )
29
+ external_template: str = os.path.join(
30
+ "{{dirname}}", "scan{scan_number:04d}", "{name}_{file_index:04d}.h5"
31
+ ) + SEP.join(("::", "entry_0000", "ESRF-ID00", "{name}"))
32
+ external: bool = False
33
+ points_per_file: int = 1
34
+ positioner: bool = False
35
+ start_metadata: Mapping = dict()
36
+ end_metadata: Mapping = dict()
37
+ server_delay: Number = 0
38
+
39
+ @property
40
+ def ndim(self):
41
+ return len(self.shape)
42
+
43
+ def internal_url(self, scan_number: int) -> str:
44
+ url_template = self.internal_template.format(
45
+ scan_number=scan_number, name=self.name
46
+ )
47
+ if self.positioner:
48
+ url_template += f"{SEP}value"
49
+ else:
50
+ url_template += f"{SEP}data"
51
+ return url_template.split("::")[-1]
52
+
53
+ def _dataset_url_template(self, scan_number: int, file_index: int):
54
+ if self.external:
55
+ template = self.external_template
56
+ else:
57
+ template = self.internal_template
58
+ url_template = template.format(
59
+ scan_number=scan_number, name=self.name, file_index=file_index
60
+ )
61
+ if self.positioner:
62
+ url_template += f"{SEP}value"
63
+ else:
64
+ url_template += f"{SEP}data"
65
+ return url_template
66
+
67
+ def generate(self, scan_number: int) -> Iterator[DataPoint]:
68
+ values = numpy.zeros(self.shape, dtype=self.dtype)
69
+ add = numpy.array(1, dtype=self.dtype)
70
+ if self.points_per_file > 0:
71
+ i = 0
72
+ while True:
73
+ file_index = i // self.points_per_file
74
+ scan_index = i % self.points_per_file
75
+ values = values + add # copy
76
+ url_template = self._dataset_url_template(scan_number, file_index)
77
+ yield DataPoint(values=values, url_template=url_template)
78
+ i += 1
79
+ else:
80
+ scan_index = 0
81
+ url_template = self._dataset_url_template(scan_number, 0)
82
+ while True:
83
+ values = values + add # copy
84
+ yield DataPoint(values=values, url_template=url_template)
85
+ scan_index += 1
86
+
87
+ def generate_virtual_sources(
88
+ self, npoints: int, scan_number: int
89
+ ) -> Iterator[Tuple[str, dict]]:
90
+ if not self.external:
91
+ return
92
+ nfiles = npoints // self.points_per_file
93
+ nlast = npoints % self.points_per_file
94
+ if nlast:
95
+ nfiles += 1
96
+ else:
97
+ nlast = self.points_per_file
98
+ for file_index in range(nfiles):
99
+ url_template = self._dataset_url_template(scan_number, file_index)
100
+ if file_index == (nfiles - 1):
101
+ shape = (nlast,) + self.shape
102
+ else:
103
+ shape = (self.points_per_file,) + self.shape
104
+ yield url_template, {"shape": shape, "dtype": self.dtype}
105
+
106
+
107
+ def timestamp() -> str:
108
+ return datetime.now().astimezone().isoformat()
109
+
110
+
111
+ def ensure_parents(root, dataset: str):
112
+ parts = [s for s in dataset.split(SEP) if s]
113
+ root.attrs.setdefault("NX_class", "NXroot")
114
+ entry = root.require_group(parts[0])
115
+ entry.attrs.setdefault("NX_class", "NXentry")
116
+ parent = entry
117
+ for group_name in parts[1:-1]:
118
+ parent = parent.require_group(group_name)
119
+ parent.attrs.setdefault("NX_class", "NXcollection")
120
+ dset_name = parts[-1]
121
+ return parent, dset_name
122
+
123
+
124
+ def append_data(root, dataset: str, data: numpy.ndarray):
125
+ parent, dset_name = ensure_parents(root, dataset)
126
+ if dset_name in parent:
127
+ dset = parent[dset_name]
128
+ dset.resize(dset.shape[0] + 1, axis=0)
129
+ else:
130
+ dset = parent.create_dataset(
131
+ dset_name,
132
+ shape=(1,) + data.shape,
133
+ dtype=data.dtype,
134
+ maxshape=(None,) + data.shape,
135
+ )
136
+ dset[-1] = data
137
+
138
+
139
+ def save_vds(root, dataset: str, layout: h5py.VirtualLayout):
140
+ parent, dset_name = ensure_parents(root, dataset)
141
+ parent.create_virtual_dataset(dset_name, layout, fillvalue=numpy.nan)
142
+
143
+
144
+ def scan(
145
+ filename: str,
146
+ scan_number: int,
147
+ counters: Sequence[ChannelInfo],
148
+ start_positioners: Optional[Mapping] = None,
149
+ end_positioners: Optional[Mapping] = None,
150
+ start_metadata: Optional[Mapping] = None,
151
+ end_metadata: Optional[Mapping] = None,
152
+ exposure_time: int = 0.1,
153
+ flush_period: int = 0.1,
154
+ npoints: int = 10,
155
+ start_delay: Number = 0,
156
+ queue: Optional[Queue] = None,
157
+ ):
158
+ time.sleep(start_delay)
159
+ print("Start writing scan", scan_number)
160
+ dirname = os.path.dirname(filename)
161
+ basename = os.path.basename(filename)
162
+ os.makedirs(dirname, exist_ok=True)
163
+
164
+ with lima_servers(counters) as lima_queues:
165
+ with writer_context(filename, scan_number) as entry:
166
+ instrument = entry.create_group("instrument")
167
+ instrument.attrs["NX_class"] = "NXinstrument"
168
+ measurement = entry.create_group("measurement")
169
+ measurement.attrs["NX_class"] = "NXcollection"
170
+ positioners = instrument.create_group("positioners")
171
+ positioners.attrs["NX_class"] = "NXcollection"
172
+
173
+ # Save start metadata
174
+ if start_metadata:
175
+ nxtodict(entry, start_metadata, update_mode="add")
176
+ for ctr in counters:
177
+ if ctr.start_metadata:
178
+ nxtodict(entry, ctr.start_metadata, update_mode="add")
179
+
180
+ if start_positioners:
181
+ g = instrument.create_group("start_positioner")
182
+ g.attrs["NX_class"] = "NXcollection"
183
+ for k, v in start_positioners.items():
184
+ g[k] = v
185
+ if not any(ctr.name == k for ctr in counters):
186
+ positioners[k] = v
187
+
188
+ entry[f"writer{SEP}status"][()] = "RUNNING"
189
+
190
+ # Save data
191
+ generators = [ctr.generate(scan_number) for ctr in counters]
192
+ t0 = time.time()
193
+ for scan_index, *detectors in zip(range(npoints), *generators):
194
+ time.sleep(exposure_time)
195
+
196
+ for ctr, data in zip(counters, detectors):
197
+ url = data.url_template.format(dirname=dirname, basename=basename)
198
+ destfilename, path_in_file = url.split("::")
199
+ if destfilename == filename:
200
+ append_data(entry.parent, path_in_file, data.values)
201
+ if scan_index == 0:
202
+ measurement[ctr.name] = h5py.SoftLink(path_in_file)
203
+ else:
204
+ lima_queues[ctr.name].put((destfilename, path_in_file, data))
205
+
206
+ t1 = time.time()
207
+ if (t1 - t0) >= flush_period:
208
+ entry.file.flush()
209
+ t0 = t1
210
+
211
+ msg = f"Written point {scan_index} of scan {scan_number}"
212
+ print(msg)
213
+ if queue is not None:
214
+ queue.put(
215
+ (
216
+ scan_number,
217
+ scan_index,
218
+ "w",
219
+ )
220
+ )
221
+
222
+ # Create Lima virtual datasets and soft links
223
+ for ctr in counters:
224
+ if not ctr.external:
225
+ continue
226
+
227
+ layout = h5py.VirtualLayout(
228
+ shape=(npoints,) + ctr.shape, dtype=ctr.dtype
229
+ )
230
+ off = 0
231
+ for url_template, kwargs in ctr.generate_virtual_sources(
232
+ npoints, scan_number
233
+ ):
234
+ url = url_template.format(dirname=dirname, basename=basename)
235
+ destfilename, path_in_file = url.split("::")
236
+ n = kwargs["shape"][0]
237
+ layout[off : off + n] = h5py.VirtualSource(
238
+ destfilename, path_in_file, **kwargs
239
+ )
240
+ off += n
241
+
242
+ url_template = ctr.internal_url(scan_number)
243
+ path_in_file = url_template.format(dirname=dirname, basename=basename)
244
+ save_vds(entry.parent, path_in_file, layout)
245
+ measurement[ctr.name] = h5py.SoftLink(path_in_file)
246
+
247
+ # Save final metadata
248
+ if end_positioners:
249
+ g = instrument.create_group("end_positioner")
250
+ g.attrs["NX_class"] = "NXcollection"
251
+ for k, v in end_positioners.items():
252
+ g[k] = v
253
+
254
+ if end_metadata:
255
+ nxtodict(entry, end_metadata, update_mode="modify")
256
+ for ctr in counters:
257
+ if ctr.end_metadata:
258
+ nxtodict(entry, ctr.end_metadata, update_mode="modify")
259
+ print("Finishing writing scan", scan_number)
260
+ print("Finished writing scan", scan_number)
261
+
262
+
263
+ @contextmanager
264
+ def writer_context(filename: str, scan_number: int) -> Iterator[h5py.Group]:
265
+ with h5py.File(filename, mode="a") as root:
266
+ root.attrs["NX_class"] = "NXroot"
267
+ entry = root.create_group(f"{scan_number}.1")
268
+
269
+ try:
270
+ entry.attrs["NX_class"] = "NXentry"
271
+ entry["start_time"] = timestamp()
272
+ writer = entry.create_group("writer")
273
+ writer.attrs["NX_class"] = "NXnote"
274
+ writer["status"] = "STARTING"
275
+ yield entry
276
+ except Exception:
277
+ writer["status"][()] = "FAILED"
278
+ raise
279
+ else:
280
+ writer["status"][()] = "SUCCEEDED"
281
+ finally:
282
+ entry["end_time"] = timestamp()
283
+
284
+
285
+ @contextmanager
286
+ def lima_servers(counters: Sequence[ChannelInfo]):
287
+ queues = dict()
288
+ processed = list()
289
+ for ctr in counters:
290
+ if not ctr.external:
291
+ continue
292
+ queue = Queue()
293
+ process = Process(
294
+ target=lima_main, args=(ctr.name, queue), kwargs={"delay": ctr.server_delay}
295
+ )
296
+ process.start()
297
+ processed.append(process)
298
+ queues[ctr.name] = queue
299
+
300
+ try:
301
+ yield queues
302
+ finally:
303
+ for queue in queues.values():
304
+ queue.put(None)
305
+ for process in processed:
306
+ process.join()
307
+
308
+
309
+ def lima_main(name: str, queue: Queue, delay: Number = 0):
310
+ aroot = None
311
+ afilename = None
312
+ print("Start lima server", name)
313
+ try:
314
+ while True:
315
+ job = queue.get()
316
+ if job is None:
317
+ return
318
+
319
+ time.sleep(delay)
320
+
321
+ filename, path_in_file, data = job
322
+ if afilename != filename:
323
+ if aroot is not None:
324
+ aroot.close()
325
+ os.makedirs(os.path.dirname(filename), exist_ok=True)
326
+ aroot = h5py.File(filename, "a")
327
+
328
+ append_data(aroot, path_in_file, data.values)
329
+ except BaseException as e:
330
+ print("Lima server", name, "failed:", str(e))
331
+ raise
332
+ finally:
333
+ if aroot is not None:
334
+ aroot.close()
335
+ print("Stop lima server", name)
336
+
337
+
338
+ def scans(
339
+ filename: str, scan_numbers: Sequence[int], counters: Sequence[ChannelInfo], **kw
340
+ ):
341
+ for scan_number in scan_numbers:
342
+ scan(filename, scan_number, counters, **kw)
343
+
344
+
345
+ def log_point(
346
+ scan_number: int, scan_index: int, queue: Optional[Queue] = None, write: bool = True
347
+ ):
348
+ if write:
349
+ action = "Written"
350
+ else:
351
+ action = "Read"
352
+ msg = f"{action} point {scan_index} of scan {scan_number}"
353
+ print(msg)
354
+ if queue is not None:
355
+ queue.put((scan_number, scan_index, action[0].lower()))
356
+
357
+
358
+ @contextmanager
359
+ def start_scan(
360
+ filename: str, scan_number: int, counters: Sequence[ChannelInfo], **kwargs
361
+ ) -> Queue:
362
+ queue = Queue()
363
+ kwargs["queue"] = queue
364
+ process = Process(
365
+ target=scan, args=(filename, scan_number, counters), kwargs=kwargs
366
+ )
367
+ process.start()
368
+ try:
369
+ yield queue
370
+ finally:
371
+ process.join()
372
+
373
+
374
+ @contextmanager
375
+ def start_scans(
376
+ filename: str,
377
+ scan_numbers: Sequence[int],
378
+ counters: Sequence[ChannelInfo],
379
+ **kwargs,
380
+ ) -> Queue:
381
+ queue = Queue()
382
+ kwargs["queue"] = queue
383
+ process = Process(
384
+ target=scans, args=(filename, scan_numbers, counters), kwargs=kwargs
385
+ )
386
+ process.start()
387
+ try:
388
+ yield queue
389
+ finally:
390
+ process.join()
@@ -0,0 +1,309 @@
1
+ import os
2
+ import numpy
3
+ import pytest
4
+ from blissdata.h5api import dynamic_hdf5
5
+ from . import scanner
6
+
7
+
8
+ @pytest.mark.parametrize("server_delay", [0, 0.2])
9
+ def test_single_scan_iteration(server_delay, tmpdir):
10
+ scan_number = 10
11
+ exposure_time = 0.1
12
+ npoints = 21
13
+ retry_period = exposure_time
14
+ flush_period = 2 * exposure_time
15
+
16
+ retry_timeout = 10
17
+
18
+ basename = "sample_0001.h5"
19
+ filename = str(tmpdir / basename)
20
+ counters = [
21
+ scanner.ChannelInfo("samy", positioner=True),
22
+ scanner.ChannelInfo("diode1"),
23
+ scanner.ChannelInfo("mca1", shape=(10,)),
24
+ scanner.ChannelInfo(
25
+ "lima1",
26
+ shape=(4, 7),
27
+ external=True,
28
+ points_per_file=2,
29
+ server_delay=server_delay,
30
+ ),
31
+ ]
32
+
33
+ with scanner.start_scan(
34
+ filename,
35
+ scan_number,
36
+ counters,
37
+ exposure_time=exposure_time,
38
+ flush_period=flush_period,
39
+ npoints=npoints,
40
+ start_delay=0.5,
41
+ ) as queue:
42
+ with dynamic_hdf5.File(
43
+ filename,
44
+ lima_names=["lima1"],
45
+ retry_timeout=retry_timeout,
46
+ retry_period=retry_period,
47
+ instrument_name="ESRF-ID00",
48
+ ) as nxroot:
49
+ datasets = [nxroot[ctr.internal_url(scan_number)] for ctr in counters]
50
+ for scan_index, points in enumerate(zip(*datasets)):
51
+ assert len(points) == len(counters)
52
+ for ctr, data in zip(counters, points):
53
+ expected = numpy.full(ctr.shape, scan_index + 1, dtype=ctr.dtype)
54
+ numpy.testing.assert_array_equal(data, expected)
55
+ scanner.log_point(scan_number, scan_index, queue, write=False)
56
+ assert_read_write_order(queue, npoints, 1)
57
+
58
+
59
+ def test_multiple_scan_iteration(tmpdir):
60
+ scan_numbers = [10, 11, 12]
61
+ exposure_time = 0.1
62
+ npoints = 21
63
+ retry_period = exposure_time
64
+ flush_period = 2 * exposure_time
65
+
66
+ retry_timeout = 10
67
+
68
+ basename = "sample_0001.h5"
69
+ filename = str(tmpdir / basename)
70
+ counters = [
71
+ scanner.ChannelInfo("samy", positioner=True),
72
+ scanner.ChannelInfo("diode1"),
73
+ scanner.ChannelInfo("mca1", shape=(10,)),
74
+ scanner.ChannelInfo(
75
+ "lima1", shape=(4, 7), external=True, points_per_file=2, server_delay=0.1
76
+ ),
77
+ ]
78
+
79
+ scan_number = 0
80
+ with scanner.start_scans(
81
+ filename,
82
+ scan_numbers,
83
+ counters,
84
+ exposure_time=exposure_time,
85
+ flush_period=flush_period,
86
+ npoints=npoints,
87
+ ) as queue:
88
+ with dynamic_hdf5.File(
89
+ filename,
90
+ lima_names=["lima1"],
91
+ retry_timeout=retry_timeout,
92
+ retry_period=retry_period,
93
+ instrument_name="ESRF-ID00",
94
+ ) as nxroot:
95
+ for scan in nxroot: # loops forever
96
+ scan_number = int(scan.split(".")[0])
97
+ datasets = [nxroot[ctr.internal_url(scan_number)] for ctr in counters]
98
+ for scan_index, points in enumerate(zip(*datasets)):
99
+ assert len(points) == len(counters)
100
+ for ctr, data in zip(counters, points):
101
+ expected = numpy.full(
102
+ ctr.shape, scan_index + 1, dtype=ctr.dtype
103
+ )
104
+ numpy.testing.assert_array_equal(data, expected)
105
+ scanner.log_point(scan_number, scan_index, queue, write=False)
106
+ if scan_number == 12:
107
+ break
108
+ assert scan_number == 12
109
+ assert_read_write_order(queue, npoints, 3)
110
+
111
+
112
+ def test_multiple_scan_instrument_list(tmpdir):
113
+ scan_numbers = [10, 11, 12]
114
+ exposure_time = 0.02
115
+ npoints = 11
116
+ retry_period = exposure_time
117
+ flush_period = 2 * exposure_time
118
+
119
+ retry_timeout = 10
120
+
121
+ basename = "sample_0001.h5"
122
+ filename = str(tmpdir / basename)
123
+ counters = [
124
+ scanner.ChannelInfo("samy", positioner=True),
125
+ scanner.ChannelInfo("diode1"),
126
+ scanner.ChannelInfo("mca1", shape=(10,)),
127
+ scanner.ChannelInfo("lima1", shape=(4, 7), external=True, points_per_file=2),
128
+ ]
129
+ expected = {ctr.name for ctr in counters if not ctr.external}
130
+ expected.add("positioners")
131
+
132
+ if os.name == "nt":
133
+ # Parallel reading does not work on windows
134
+ # This means when the reader has access, the lima VDS is already created
135
+ expected.add("lima1")
136
+
137
+ scan_number = 0
138
+ with scanner.start_scans(
139
+ filename,
140
+ scan_numbers,
141
+ counters,
142
+ exposure_time=exposure_time,
143
+ flush_period=flush_period,
144
+ npoints=npoints,
145
+ ):
146
+ with dynamic_hdf5.File(
147
+ filename,
148
+ lima_names=["lima1"],
149
+ retry_timeout=retry_timeout,
150
+ retry_period=retry_period,
151
+ instrument_name="ESRF-ID00",
152
+ ) as nxroot:
153
+ for scan in nxroot: # loops forever
154
+ scan_number = int(scan.split(".")[0])
155
+ nxentry = nxroot[scan]
156
+ keys = set(nxentry["instrument"])
157
+ assert keys == expected
158
+ if scan_number == 12:
159
+ break
160
+ assert scan_number == 12
161
+
162
+
163
+ def test_single_scan_vds_delay(tmpdir):
164
+ scan_number = 10
165
+ exposure_time = 0.1
166
+ npoints = 1
167
+ retry_period = exposure_time
168
+ flush_period = 2 * exposure_time
169
+
170
+ retry_timeout = 10
171
+ server_delay = 1
172
+
173
+ basename = "sample_0001.h5"
174
+ filename = str(tmpdir / basename)
175
+ counters = [
176
+ scanner.ChannelInfo("samy", positioner=True),
177
+ scanner.ChannelInfo("diode1"),
178
+ scanner.ChannelInfo("mca1", shape=(10,)),
179
+ scanner.ChannelInfo(
180
+ "lima1",
181
+ shape=(4, 7),
182
+ external=True,
183
+ points_per_file=1,
184
+ server_delay=server_delay,
185
+ ),
186
+ ]
187
+
188
+ with scanner.start_scan(
189
+ filename,
190
+ scan_number,
191
+ counters,
192
+ exposure_time=exposure_time,
193
+ flush_period=flush_period,
194
+ npoints=npoints,
195
+ start_delay=0.5,
196
+ ) as queue:
197
+ with dynamic_hdf5.File(
198
+ filename,
199
+ lima_names=["lima1"],
200
+ retry_timeout=retry_timeout,
201
+ retry_period=retry_period,
202
+ instrument_name="ESRF-ID00",
203
+ ) as nxroot:
204
+ keys = set(nxroot["10.1/instrument"])
205
+ assert "lima1" in keys
206
+
207
+ datasets = [nxroot[ctr.internal_url(scan_number)] for ctr in counters]
208
+ for scan_index, points in enumerate(zip(*datasets)):
209
+ assert len(points) == len(counters)
210
+ for ctr, data in zip(counters, points):
211
+ expected = numpy.full(ctr.shape, scan_index + 1, dtype=ctr.dtype)
212
+ numpy.testing.assert_array_equal(data, expected)
213
+ scanner.log_point(scan_number, scan_index, queue, write=False)
214
+
215
+
216
+ def test_single_scan_slice(tmpdir):
217
+ npoints = 21
218
+ retry_timeout = 10
219
+
220
+ basename = "sample_0001.h5"
221
+ filename = str(tmpdir / basename)
222
+ counters = [
223
+ scanner.ChannelInfo("samy", positioner=True),
224
+ scanner.ChannelInfo("diode1"),
225
+ scanner.ChannelInfo("mca1", shape=(10,)),
226
+ scanner.ChannelInfo("lima1", shape=(4, 7), external=True, points_per_file=2),
227
+ ]
228
+
229
+ # Test slicing along the scan dimension
230
+ scan_number = 1
231
+ with scanner.start_scan(filename, scan_number, counters, npoints=npoints) as queue:
232
+ with dynamic_hdf5.File(
233
+ filename,
234
+ lima_names=["lima1"],
235
+ retry_timeout=retry_timeout,
236
+ instrument_name="ESRF-ID00",
237
+ ) as nxroot:
238
+ for i, ctr in enumerate(counters):
239
+ dataset = nxroot[ctr.internal_url(scan_number)]
240
+ for scan_index, data in enumerate(dataset[5:8], 5):
241
+ expected = numpy.full(ctr.shape, scan_index + 1, dtype=ctr.dtype)
242
+ numpy.testing.assert_array_equal(data, expected)
243
+ if i == 0 and os.name != "nt":
244
+ # Parallel reading does not work on windows
245
+ assert (
246
+ queue.qsize() < npoints
247
+ ), "slicing should return before the writing finished"
248
+
249
+ scan_number += 1
250
+ with scanner.start_scan(filename, scan_number, counters, npoints=npoints) as queue:
251
+ with dynamic_hdf5.File(
252
+ filename,
253
+ lima_names=["lima1"],
254
+ retry_timeout=retry_timeout,
255
+ instrument_name="ESRF-ID00",
256
+ ) as nxroot:
257
+ for i, ctr in enumerate(counters):
258
+ dataset = nxroot[ctr.internal_url(scan_number)]
259
+ for scan_index, data in enumerate(dataset[[5, 6, 7]], 5):
260
+ expected = numpy.full(ctr.shape, scan_index + 1, dtype=ctr.dtype)
261
+ numpy.testing.assert_array_equal(data, expected)
262
+ if i == 0 and os.name != "nt":
263
+ # Parallel reading does not work on windows
264
+ assert (
265
+ queue.qsize() < npoints
266
+ ), "slicing should return before the writing finished"
267
+
268
+ # Test slicing along the scan and detector dimensions
269
+ scan_number += 1
270
+ with scanner.start_scan(filename, scan_number, counters, npoints=npoints) as queue:
271
+ with dynamic_hdf5.File(
272
+ filename,
273
+ lima_names=["lima1"],
274
+ retry_timeout=retry_timeout,
275
+ instrument_name="ESRF-ID00",
276
+ ) as nxroot:
277
+ for i, ctr in enumerate(counters):
278
+ dataset = nxroot[ctr.internal_url(scan_number)]
279
+ idx = (slice(5, 8),) + ctr.ndim * (0,)
280
+ for scan_index, data in enumerate(dataset[idx], 5):
281
+ assert data == scan_index + 1
282
+ if i == 0 and os.name != "nt":
283
+ # Parallel reading does not work on windows
284
+ assert (
285
+ queue.qsize() < npoints
286
+ ), "slicing should return before the writing finished"
287
+
288
+
289
+ def assert_read_write_order(queue, npoints, nscans):
290
+ # Check whether all points are written and read
291
+ lst = list()
292
+ while not queue.empty():
293
+ lst.append(queue.get())
294
+ nexpected = 2 * npoints * nscans
295
+ assert len(lst) == nexpected
296
+
297
+ # Check whether reading and writing happened in parallel
298
+ if os.name == "nt":
299
+ # Parallel reading does not work on windows
300
+ return
301
+
302
+ actions = dict()
303
+ for scan_number, _, action in lst:
304
+ scan_actions = actions.setdefault(scan_number, list())
305
+ scan_actions.append(action)
306
+
307
+ for scan_actions in actions.values():
308
+ gaps = numpy.diff([i for i, action in enumerate(scan_actions) if action == "r"])
309
+ assert (gaps > 1).any(), str(lst)