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,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)
|