lima2.client 3.0a0__tar.gz → 3.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {lima2_client-3.0a0/src/lima2_client.egg-info → lima2_client-3.1.0}/PKG-INFO +2 -2
- {lima2_client-3.0a0 → lima2_client-3.1.0}/pyproject.toml +1 -1
- {lima2_client-3.0a0 → lima2_client-3.1.0}/src/lima2/client/client.py +81 -3
- {lima2_client-3.0a0 → lima2_client-3.1.0}/src/lima2/client/detector.py +38 -10
- {lima2_client-3.0a0 → lima2_client-3.1.0}/src/lima2/client/pipeline.py +276 -154
- {lima2_client-3.0a0 → lima2_client-3.1.0}/src/lima2/client/pipelines/legacy.py +4 -2
- {lima2_client-3.0a0 → lima2_client-3.1.0}/src/lima2/client/pipelines/smx.py +53 -10
- {lima2_client-3.0a0 → lima2_client-3.1.0}/src/lima2/client/pipelines/xpcs.py +20 -9
- {lima2_client-3.0a0 → lima2_client-3.1.0}/src/lima2/client/state_machine.py +3 -0
- {lima2_client-3.0a0 → lima2_client-3.1.0}/src/lima2/client/topology.py +20 -15
- {lima2_client-3.0a0 → lima2_client-3.1.0}/src/lima2/client/utils.py +23 -3
- lima2_client-3.1.0/src/lima2/master_file/generate.py +815 -0
- lima2_client-3.1.0/src/lima2/master_file/py.typed +0 -0
- {lima2_client-3.0a0 → lima2_client-3.1.0/src/lima2_client.egg-info}/PKG-INFO +2 -2
- {lima2_client-3.0a0 → lima2_client-3.1.0}/src/lima2_client.egg-info/SOURCES.txt +1 -0
- {lima2_client-3.0a0 → lima2_client-3.1.0}/src/lima2_client.egg-info/requires.txt +1 -1
- lima2_client-3.0a0/src/lima2/master_file/generate.py +0 -247
- {lima2_client-3.0a0 → lima2_client-3.1.0}/LICENSE +0 -0
- {lima2_client-3.0a0 → lima2_client-3.1.0}/README.md +0 -0
- {lima2_client-3.0a0 → lima2_client-3.1.0}/setup.cfg +0 -0
- {lima2_client-3.0a0 → lima2_client-3.1.0}/src/finite_state_machine/__init__.py +0 -0
- {lima2_client-3.0a0 → lima2_client-3.1.0}/src/finite_state_machine/draw_state_diagram.py +0 -0
- {lima2_client-3.0a0 → lima2_client-3.1.0}/src/finite_state_machine/examples/__init__.py +0 -0
- {lima2_client-3.0a0 → lima2_client-3.1.0}/src/finite_state_machine/examples/async_github_pull_request.py +0 -0
- {lima2_client-3.0a0 → lima2_client-3.1.0}/src/finite_state_machine/examples/async_turnstile.py +0 -0
- {lima2_client-3.0a0 → lima2_client-3.1.0}/src/finite_state_machine/examples/boolean_field.py +0 -0
- {lima2_client-3.0a0 → lima2_client-3.1.0}/src/finite_state_machine/examples/github_pull_request.py +0 -0
- {lima2_client-3.0a0 → lima2_client-3.1.0}/src/finite_state_machine/examples/turnstile.py +0 -0
- {lima2_client-3.0a0 → lima2_client-3.1.0}/src/finite_state_machine/exceptions.py +0 -0
- {lima2_client-3.0a0 → lima2_client-3.1.0}/src/finite_state_machine/state_machine.py +0 -0
- {lima2_client-3.0a0 → lima2_client-3.1.0}/src/lima2/client/__init__.py +0 -0
- {lima2_client-3.0a0 → lima2_client-3.1.0}/src/lima2/client/convert.py +0 -0
- {lima2_client-3.0a0 → lima2_client-3.1.0}/src/lima2/client/devencoded/__init__.py +0 -0
- {lima2_client-3.0a0 → lima2_client-3.1.0}/src/lima2/client/devencoded/dense_frame.py +0 -0
- {lima2_client-3.0a0 → lima2_client-3.1.0}/src/lima2/client/devencoded/exception.py +0 -0
- {lima2_client-3.0a0 → lima2_client-3.1.0}/src/lima2/client/devencoded/smx_sparse_frame.py +0 -0
- {lima2_client-3.0a0 → lima2_client-3.1.0}/src/lima2/client/devencoded/sparse_frame.py +0 -0
- {lima2_client-3.0a0 → lima2_client-3.1.0}/src/lima2/client/devencoded/structured_array.py +0 -0
- {lima2_client-3.0a0 → lima2_client-3.1.0}/src/lima2/client/pipelines/__init__.py +0 -0
- {lima2_client-3.0a0 → lima2_client-3.1.0}/src/lima2/client/pipelines/failing.py +0 -0
- {lima2_client-3.0a0 → lima2_client-3.1.0}/src/lima2/client/progress_counter.py +0 -0
- {lima2_client-3.0a0 → lima2_client-3.1.0}/src/lima2/client/py.typed +0 -0
- {lima2_client-3.0a0 → lima2_client-3.1.0}/src/lima2/client/shell.py +0 -0
- {lima2_client-3.0a0 → lima2_client-3.1.0}/src/lima2/server/__init__.py +0 -0
- {lima2_client-3.0a0 → lima2_client-3.1.0}/src/lima2/server/control.py +0 -0
- {lima2_client-3.0a0 → lima2_client-3.1.0}/src/lima2/server/receiver.py +0 -0
- {lima2_client-3.0a0 → lima2_client-3.1.0}/src/lima2/server/timer.py +0 -0
- {lima2_client-3.0a0 → lima2_client-3.1.0}/src/lima2_client.egg-info/dependency_links.txt +0 -0
- {lima2_client-3.0a0 → lima2_client-3.1.0}/src/lima2_client.egg-info/entry_points.txt +0 -0
- {lima2_client-3.0a0 → lima2_client-3.1.0}/src/lima2_client.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lima2-client
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.1.0
|
|
4
4
|
Summary: Python client library for Lima2 detector
|
|
5
5
|
Author: BCU (ESRF)
|
|
6
6
|
License: Copyright (c) 2020-2024 Beamline Control Unit, ESRF
|
|
@@ -24,7 +24,7 @@ Requires-Dist: gevent
|
|
|
24
24
|
Requires-Dist: jsonschema
|
|
25
25
|
Requires-Dist: jsonschema-default
|
|
26
26
|
Requires-Dist: beartype
|
|
27
|
-
Requires-Dist: numpy
|
|
27
|
+
Requires-Dist: numpy<2.3
|
|
28
28
|
Requires-Dist: pyyaml
|
|
29
29
|
Requires-Dist: typing-extensions
|
|
30
30
|
Requires-Dist: h5py
|
|
@@ -34,6 +34,8 @@ from lima2.client.progress_counter import ProgressCounter, SingleCounter
|
|
|
34
34
|
from lima2.client.topology import TopologyKind
|
|
35
35
|
from lima2.client.utils import docstring_from
|
|
36
36
|
|
|
37
|
+
from lima2.master_file import generate as mfg
|
|
38
|
+
|
|
37
39
|
logger = logging.getLogger(__name__)
|
|
38
40
|
|
|
39
41
|
|
|
@@ -77,6 +79,32 @@ class Client:
|
|
|
77
79
|
|
|
78
80
|
self._tango_db = tg.Database()
|
|
79
81
|
|
|
82
|
+
# Show master file generation info messages
|
|
83
|
+
mfg.logger.setLevel(logging.INFO)
|
|
84
|
+
|
|
85
|
+
self.master_file_tasks: dict[UUID, dict[str, mfg.MasterFileTask]] = {}
|
|
86
|
+
"""Master file generation tasks for each pipeline UUID.
|
|
87
|
+
|
|
88
|
+
Each entry corresponds to a pipeline, and holds a dict structured like
|
|
89
|
+
{saving channel name: list of tasks}.
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
def cleanup(self):
|
|
93
|
+
"""Unregister all state loggers and tango event handlers.
|
|
94
|
+
|
|
95
|
+
This method serves only in specific cases where event handlers must for
|
|
96
|
+
some reason be unregistered. In a normal use case, a Client is
|
|
97
|
+
instantiated once per Lima2 instance, and no cleanup is necessary.
|
|
98
|
+
|
|
99
|
+
NOTE: After this call, the Client instance should no longer be used.
|
|
100
|
+
"""
|
|
101
|
+
self._detector.unregister_events()
|
|
102
|
+
self._detector = None
|
|
103
|
+
|
|
104
|
+
for pipeline in self.__pipelines.values():
|
|
105
|
+
pipeline.unregister_events()
|
|
106
|
+
self.__pipelines.clear()
|
|
107
|
+
|
|
80
108
|
@classmethod
|
|
81
109
|
def from_yaml(
|
|
82
110
|
cls,
|
|
@@ -147,16 +175,30 @@ class Client:
|
|
|
147
175
|
####################################################################################
|
|
148
176
|
|
|
149
177
|
def prepare_acq(
|
|
150
|
-
self,
|
|
178
|
+
self,
|
|
179
|
+
uuid: UUID,
|
|
180
|
+
ctrl_params: dict,
|
|
181
|
+
acq_params: dict,
|
|
182
|
+
proc_params: dict,
|
|
151
183
|
) -> None:
|
|
152
184
|
"""Prepare acquisition with a given uuid and set of params."""
|
|
153
185
|
|
|
186
|
+
# Check parameters required for master file generation
|
|
187
|
+
checked_params = mfg.check_generation(
|
|
188
|
+
ctrl_params=ctrl_params,
|
|
189
|
+
recv_params=acq_params,
|
|
190
|
+
proc_params=proc_params,
|
|
191
|
+
topology_kind=self.topology_kind,
|
|
192
|
+
)
|
|
193
|
+
ctrl_params, acq_params, proc_params = checked_params
|
|
194
|
+
|
|
154
195
|
# Adjust params for distributed acquisition
|
|
155
196
|
ctl, acq, proc = topology.distribute_acq(
|
|
156
197
|
ctl_params=ctrl_params,
|
|
157
198
|
acq_params=acq_params,
|
|
158
199
|
proc_params=proc_params,
|
|
159
200
|
num_receivers=len(self.receivers),
|
|
201
|
+
topology_kind=self.topology_kind,
|
|
160
202
|
)
|
|
161
203
|
|
|
162
204
|
self.detector.prepare_acq(uuid, ctl, acq, proc)
|
|
@@ -174,9 +216,40 @@ class Client:
|
|
|
174
216
|
|
|
175
217
|
self.current_pipeline.register_on_error(on_error)
|
|
176
218
|
|
|
219
|
+
# Remove master file tasks of previous acquisitions if done or not started
|
|
220
|
+
for prev_uuid in list(self.master_file_tasks.keys()):
|
|
221
|
+
tasks = self.master_file_tasks[prev_uuid].values()
|
|
222
|
+
if all([not task.started() or task.ready() for task in tasks]):
|
|
223
|
+
logger.debug(
|
|
224
|
+
"All master file tasks done (or never started) for "
|
|
225
|
+
f"{prev_uuid} -> cleaning up"
|
|
226
|
+
)
|
|
227
|
+
self.master_file_tasks.pop(prev_uuid)
|
|
228
|
+
else:
|
|
229
|
+
logger.debug(f"Not all master file tasks done for {prev_uuid}...")
|
|
230
|
+
|
|
231
|
+
self.master_file_tasks[uuid] = mfg.enable_generation(
|
|
232
|
+
pipeline=self.current_pipeline,
|
|
233
|
+
num_frames=ctrl_params["acq"]["nb_frames"],
|
|
234
|
+
ctrl_params=ctl,
|
|
235
|
+
recv_params=acq[0],
|
|
236
|
+
proc_params=proc[0],
|
|
237
|
+
topology_kind=self.topology_kind,
|
|
238
|
+
)
|
|
239
|
+
|
|
177
240
|
@docstring_from(Detector.start_acq)
|
|
178
241
|
def start_acq(self) -> None:
|
|
179
|
-
self.
|
|
242
|
+
pipeline = self.current_pipeline
|
|
243
|
+
pipeline.register_on_finished(pipeline.unregister_events)
|
|
244
|
+
|
|
245
|
+
try:
|
|
246
|
+
self.detector.start_acq()
|
|
247
|
+
except Exception:
|
|
248
|
+
pipeline.unregister_events()
|
|
249
|
+
raise
|
|
250
|
+
|
|
251
|
+
for task in self.master_file_tasks[self.current_pipeline.uuid].values():
|
|
252
|
+
task.start()
|
|
180
253
|
|
|
181
254
|
@docstring_from(Detector.trigger)
|
|
182
255
|
def trigger(self) -> None:
|
|
@@ -293,9 +366,14 @@ class Client:
|
|
|
293
366
|
|
|
294
367
|
Raises if the pipeline is absent from a receiver.
|
|
295
368
|
"""
|
|
369
|
+
|
|
370
|
+
# First unregister Tango events
|
|
371
|
+
pipeline = self.__pipelines.pop(uuid, None)
|
|
372
|
+
if pipeline is not None:
|
|
373
|
+
pipeline.unregister_events()
|
|
374
|
+
|
|
296
375
|
for recv in self.receivers:
|
|
297
376
|
recv.erasePipeline(str(uuid))
|
|
298
|
-
self.__pipelines.pop(uuid, None)
|
|
299
377
|
|
|
300
378
|
def clear_previous_pipelines(self) -> None:
|
|
301
379
|
"""Erase all pipelines except the current one."""
|
|
@@ -24,6 +24,8 @@ import logging
|
|
|
24
24
|
import uuid
|
|
25
25
|
from collections.abc import Iterator
|
|
26
26
|
|
|
27
|
+
import gevent
|
|
28
|
+
|
|
27
29
|
import tango
|
|
28
30
|
from beartype import beartype
|
|
29
31
|
from gevent.lock import BoundedSemaphore
|
|
@@ -71,6 +73,7 @@ class Detector:
|
|
|
71
73
|
|
|
72
74
|
self.__ctrl = ctrl_dev
|
|
73
75
|
self.__recvs = list(recv_devs)
|
|
76
|
+
self._state_event_ids: dict[tango.DeviceProxy, int] = {}
|
|
74
77
|
|
|
75
78
|
self.ping()
|
|
76
79
|
|
|
@@ -98,6 +101,17 @@ class Detector:
|
|
|
98
101
|
d.set_green_mode(tango.GreenMode.Gevent)
|
|
99
102
|
d.set_timeout_millis(timeout * 1000)
|
|
100
103
|
|
|
104
|
+
def unregister_events(self):
|
|
105
|
+
"""Unregister all state loggers and tango event handlers.
|
|
106
|
+
|
|
107
|
+
This method serves only in specific cases where event handlers must for
|
|
108
|
+
some reason be unregistered. In a normal use case, a Client is
|
|
109
|
+
instantiated once per Lima2 instance, and no cleanup is necessary.
|
|
110
|
+
"""
|
|
111
|
+
for device, event_id in self._state_event_ids.items():
|
|
112
|
+
device.unsubscribe_event(event_id)
|
|
113
|
+
self._state_event_ids.clear()
|
|
114
|
+
|
|
101
115
|
@contextlib.contextmanager
|
|
102
116
|
def _locked_fsm(self) -> Iterator[fsm.StateMachine]:
|
|
103
117
|
self.__fsm_lock.acquire()
|
|
@@ -406,12 +420,18 @@ class Detector:
|
|
|
406
420
|
f"prepare_acq({uuid}, {ctrl_params}, {acq_params}, {proc_params})"
|
|
407
421
|
)
|
|
408
422
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
423
|
+
def parallel_exec(ctrl_fn, recv_fn):
|
|
424
|
+
tasks = [gevent.spawn(ctrl_fn, self.__ctrl)]
|
|
425
|
+
tasks += [gevent.spawn(recv_fn, i, r) for i, r in enumerate(self.__recvs)]
|
|
426
|
+
gevent.joinall(tasks, raise_error=True)
|
|
427
|
+
|
|
428
|
+
def validate_ctrl(ctrl):
|
|
429
|
+
validate(
|
|
430
|
+
instance=ctrl_params,
|
|
431
|
+
schema=self.params_schema[ctrl.name()]["acq_params"],
|
|
432
|
+
)
|
|
413
433
|
|
|
414
|
-
|
|
434
|
+
def validate_recv(i, recv):
|
|
415
435
|
validate(
|
|
416
436
|
instance=acq_params[i],
|
|
417
437
|
schema=self.params_schema[recv.name()]["acq_params"],
|
|
@@ -420,13 +440,19 @@ class Detector:
|
|
|
420
440
|
pipeline_class = pipelines.get_class(proc_params[i]["class_name"])
|
|
421
441
|
validate(instance=proc_params[i], schema=pipeline_class.params_schema)
|
|
422
442
|
|
|
443
|
+
parallel_exec(validate_ctrl, validate_recv)
|
|
444
|
+
|
|
423
445
|
# Update parameters on the device servers
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
446
|
+
def update_ctrl(ctrl):
|
|
447
|
+
# TODO: Rename acq_params to ctrl_params
|
|
448
|
+
ctrl.acq_params = json.dumps(ctrl_params)
|
|
449
|
+
|
|
450
|
+
def update_recv(i, recv):
|
|
427
451
|
recv.acq_params = json.dumps(acq_params[i])
|
|
428
452
|
recv.proc_params = json.dumps(proc_params[i])
|
|
429
453
|
|
|
454
|
+
parallel_exec(update_ctrl, update_recv)
|
|
455
|
+
|
|
430
456
|
# Raises and reverts to IDLE on exception
|
|
431
457
|
with self._locked_fsm() as fsm:
|
|
432
458
|
fsm.prepare(str(uuid))
|
|
@@ -619,12 +645,14 @@ class Detector:
|
|
|
619
645
|
finally:
|
|
620
646
|
_logger.info(f"Current state: {fsm.state}")
|
|
621
647
|
|
|
622
|
-
|
|
623
|
-
dev.subscribe_event(
|
|
648
|
+
self._state_event_ids = {
|
|
649
|
+
dev: dev.subscribe_event(
|
|
624
650
|
"acq_state",
|
|
625
651
|
tango.EventType.CHANGE_EVENT,
|
|
626
652
|
on_acq_state_change,
|
|
627
653
|
)
|
|
654
|
+
for dev in self.devs
|
|
655
|
+
}
|
|
628
656
|
|
|
629
657
|
def _register_on_end_acq_handlers(self):
|
|
630
658
|
"""Register an "end of acquisition" callback on each receiver.
|