lima2.client 2.0.0__tar.gz → 2.0rc2__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-2.0.0/src/lima2.client.egg-info → lima2_client-2.0rc2}/PKG-INFO +7 -6
- {lima2_client-2.0.0 → lima2_client-2.0rc2}/setup.cfg +8 -3
- {lima2_client-2.0.0 → lima2_client-2.0rc2}/src/lima2/client/__init__.py +3 -3
- {lima2_client-2.0.0 → lima2_client-2.0rc2}/src/lima2/client/channel.py +1 -1
- {lima2_client-2.0.0 → lima2_client-2.0rc2}/src/lima2/client/client.py +57 -38
- {lima2_client-2.0.0 → lima2_client-2.0rc2}/src/lima2/client/convert.py +1 -1
- {lima2_client-2.0.0 → lima2_client-2.0rc2}/src/lima2/client/detector.py +20 -16
- {lima2_client-2.0.0 → lima2_client-2.0rc2}/src/lima2/client/devencoded/__init__.py +1 -1
- {lima2_client-2.0.0 → lima2_client-2.0rc2}/src/lima2/client/devencoded/dense_frame.py +9 -4
- {lima2_client-2.0.0 → lima2_client-2.0rc2}/src/lima2/client/devencoded/exception.py +1 -1
- {lima2_client-2.0.0 → lima2_client-2.0rc2}/src/lima2/client/devencoded/sparse_frame.py +8 -7
- {lima2_client-2.0.0 → lima2_client-2.0rc2}/src/lima2/client/devencoded/sparse_frame_with_background.py +8 -6
- {lima2_client-2.0.0 → lima2_client-2.0rc2}/src/lima2/client/devencoded/structured_array.py +2 -2
- lima2_client-2.0rc2/src/lima2/client/pipeline.py +555 -0
- lima2_client-2.0rc2/src/lima2/client/pipelines/__init__.py +46 -0
- lima2_client-2.0rc2/src/lima2/client/pipelines/legacy.py +56 -0
- lima2_client-2.0rc2/src/lima2/client/pipelines/smx.py +120 -0
- lima2_client-2.0rc2/src/lima2/client/pipelines/xpcs.py +127 -0
- lima2_client-2.0rc2/src/lima2/client/progress_counter.py +69 -0
- lima2_client-2.0rc2/src/lima2/client/py.typed +0 -0
- {lima2_client-2.0.0 → lima2_client-2.0rc2}/src/lima2/client/shell.py +21 -29
- {lima2_client-2.0.0 → lima2_client-2.0rc2}/src/lima2/client/state_machine.py +27 -22
- lima2_client-2.0rc2/src/lima2/client/topology.py +162 -0
- lima2_client-2.0rc2/src/lima2/client/utils.py +49 -0
- {lima2_client-2.0.0 → lima2_client-2.0rc2}/src/lima2/server/__init__.py +1 -1
- {lima2_client-2.0.0 → lima2_client-2.0rc2}/src/lima2/server/control.py +1 -1
- {lima2_client-2.0.0 → lima2_client-2.0rc2}/src/lima2/server/receiver.py +12 -1
- {lima2_client-2.0.0 → lima2_client-2.0rc2}/src/lima2/server/timer.py +1 -1
- {lima2_client-2.0.0 → lima2_client-2.0rc2/src/lima2.client.egg-info}/PKG-INFO +7 -6
- {lima2_client-2.0.0 → lima2_client-2.0rc2}/src/lima2.client.egg-info/SOURCES.txt +8 -4
- {lima2_client-2.0.0 → lima2_client-2.0rc2}/src/lima2.client.egg-info/requires.txt +1 -0
- {lima2_client-2.0.0 → lima2_client-2.0rc2}/tests/test_client.py +7 -2
- {lima2_client-2.0.0 → lima2_client-2.0rc2}/tests/test_detector.py +1 -29
- {lima2_client-2.0.0 → lima2_client-2.0rc2}/tests/test_import.py +1 -1
- lima2_client-2.0rc2/tests/test_pipeline.py +486 -0
- {lima2_client-2.0.0 → lima2_client-2.0rc2}/tests/test_pipelines.py +6 -1
- lima2_client-2.0rc2/tests/test_progress_counter.py +35 -0
- {lima2_client-2.0.0 → lima2_client-2.0rc2}/tests/test_state_machine.py +2 -2
- {lima2_client-2.0.0 → lima2_client-2.0rc2}/tests/test_timer.py +1 -1
- lima2_client-2.0rc2/tests/test_topology.py +145 -0
- lima2_client-2.0rc2/tests/test_utils.py +30 -0
- lima2_client-2.0.0/src/lima2/client/aggregator.py +0 -126
- lima2_client-2.0.0/src/lima2/client/pipelines/__init__.py +0 -52
- lima2_client-2.0.0/src/lima2/client/pipelines/legacy.py +0 -122
- lima2_client-2.0.0/src/lima2/client/pipelines/smx.py +0 -111
- lima2_client-2.0.0/src/lima2/client/pipelines/xpcs.py +0 -145
- lima2_client-2.0.0/src/lima2/client/processing.py +0 -212
- lima2_client-2.0.0/src/lima2/client/utils.py +0 -18
- lima2_client-2.0.0/tests/test_aggregator.py +0 -78
- lima2_client-2.0.0/tests/test_utils.py +0 -16
- /lima2_client-2.0.0/LICENCE → /lima2_client-2.0rc2/LICENSE +0 -0
- {lima2_client-2.0.0 → lima2_client-2.0rc2}/README.md +0 -0
- {lima2_client-2.0.0 → lima2_client-2.0rc2}/pyproject.toml +0 -0
- {lima2_client-2.0.0 → lima2_client-2.0rc2}/src/lima2.client.egg-info/dependency_links.txt +0 -0
- {lima2_client-2.0.0 → lima2_client-2.0rc2}/src/lima2.client.egg-info/entry_points.txt +0 -0
- {lima2_client-2.0.0 → lima2_client-2.0rc2}/src/lima2.client.egg-info/top_level.txt +0 -0
|
@@ -1,21 +1,22 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: lima2.client
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.0rc2
|
|
4
4
|
Summary: Python client library for Lima2 detector
|
|
5
|
-
Home-page: https://gitlab.esrf.fr/limagroup/
|
|
5
|
+
Home-page: https://gitlab.esrf.fr/limagroup/lima2-client
|
|
6
6
|
Author: BCU (ESRF)
|
|
7
|
-
License:
|
|
7
|
+
License: MIT
|
|
8
8
|
Classifier: Intended Audience :: Science/Research
|
|
9
|
-
Classifier: License :: OSI Approved ::
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
10
|
Classifier: Programming Language :: Python :: 3
|
|
11
11
|
Description-Content-Type: text/markdown
|
|
12
|
-
License-File:
|
|
12
|
+
License-File: LICENSE
|
|
13
13
|
Requires-Dist: pytango
|
|
14
14
|
Requires-Dist: gevent
|
|
15
15
|
Requires-Dist: jsonschema
|
|
16
16
|
Requires-Dist: jsonschema-default
|
|
17
17
|
Requires-Dist: finite-state-machine
|
|
18
18
|
Requires-Dist: beartype
|
|
19
|
+
Requires-Dist: numpy
|
|
19
20
|
Requires-Dist: pyyaml
|
|
20
21
|
Requires-Dist: typing-extensions
|
|
21
22
|
Provides-Extra: shell
|
|
@@ -4,11 +4,11 @@ author = BCU (ESRF)
|
|
|
4
4
|
description = Python client library for Lima2 detector
|
|
5
5
|
long_description = file: README.md
|
|
6
6
|
long_description_content_type = text/markdown
|
|
7
|
-
license =
|
|
8
|
-
url = https://gitlab.esrf.fr/limagroup/
|
|
7
|
+
license = MIT
|
|
8
|
+
url = https://gitlab.esrf.fr/limagroup/lima2-client
|
|
9
9
|
classifiers =
|
|
10
10
|
Intended Audience :: Science/Research
|
|
11
|
-
License :: OSI Approved ::
|
|
11
|
+
License :: OSI Approved :: MIT License
|
|
12
12
|
Programming Language :: Python :: 3
|
|
13
13
|
|
|
14
14
|
[options]
|
|
@@ -19,6 +19,7 @@ install_requires =
|
|
|
19
19
|
jsonschema-default
|
|
20
20
|
finite-state-machine
|
|
21
21
|
beartype
|
|
22
|
+
numpy
|
|
22
23
|
pyyaml
|
|
23
24
|
typing-extensions
|
|
24
25
|
|
|
@@ -42,6 +43,10 @@ required_plugins =
|
|
|
42
43
|
addopts = -v --forked
|
|
43
44
|
pythonpath = src
|
|
44
45
|
|
|
46
|
+
[coverage:run]
|
|
47
|
+
omit =
|
|
48
|
+
src/lima2/client/shell.py
|
|
49
|
+
|
|
45
50
|
[egg_info]
|
|
46
51
|
tag_build =
|
|
47
52
|
tag_date = 0
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# This file is part of the Lima2 project
|
|
4
4
|
#
|
|
5
5
|
# Copyright (c) 2020-2024 Beamline Control Unit, ESRF
|
|
6
|
-
# Distributed under the MIT
|
|
6
|
+
# Distributed under the MIT license. See LICENSE for more info.
|
|
7
7
|
|
|
8
8
|
"""Lima2.Client package
|
|
9
9
|
|
|
@@ -16,6 +16,6 @@
|
|
|
16
16
|
from lima2.client.client import Client
|
|
17
17
|
from lima2.client.detector import CommError
|
|
18
18
|
from lima2.client.state_machine import State
|
|
19
|
-
from lima2.client.
|
|
19
|
+
from lima2.client.pipeline import Pipeline
|
|
20
20
|
|
|
21
|
-
__all__ = [Client, CommError, State,
|
|
21
|
+
__all__ = ["Client", "CommError", "State", "Pipeline"]
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# This file is part of the Lima2 project
|
|
4
4
|
#
|
|
5
5
|
# Copyright (c) 2020-2024 Beamline Control Unit, ESRF
|
|
6
|
-
# Distributed under the MIT
|
|
6
|
+
# Distributed under the MIT license. See LICENSE for more info.
|
|
7
7
|
|
|
8
8
|
import numpy as np
|
|
9
9
|
from abc import ABC, abstractmethod
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# This file is part of the Lima2 project
|
|
4
4
|
#
|
|
5
5
|
# Copyright (c) 2020-2024 Beamline Control Unit, ESRF
|
|
6
|
-
# Distributed under the MIT
|
|
6
|
+
# Distributed under the MIT license. See LICENSE for more info.
|
|
7
7
|
|
|
8
8
|
"""User-facing interface exposing Lima2's control and acquisition API.
|
|
9
9
|
|
|
@@ -18,18 +18,20 @@ individual receivers, for instance if one of them is stuck on a frame while the
|
|
|
18
18
|
processing data.
|
|
19
19
|
"""
|
|
20
20
|
|
|
21
|
+
import os
|
|
21
22
|
from uuid import UUID
|
|
22
23
|
|
|
23
24
|
import tango as tg
|
|
24
25
|
import yaml
|
|
25
26
|
from beartype import beartype
|
|
26
|
-
from typing_extensions import
|
|
27
|
+
from typing_extensions import Any, Optional
|
|
27
28
|
|
|
28
|
-
from . import pipelines, state_machine
|
|
29
|
-
from .
|
|
30
|
-
from .
|
|
31
|
-
from .
|
|
32
|
-
from .
|
|
29
|
+
from lima2.client import pipelines, progress_counter, state_machine, topology
|
|
30
|
+
from lima2.client.detector import Detector
|
|
31
|
+
from lima2.client.pipeline import Pipeline
|
|
32
|
+
from lima2.client.progress_counter import ProgressCounter, SingleCounter
|
|
33
|
+
from lima2.client.topology import TopologyKind
|
|
34
|
+
from lima2.client.utils import docstring_from
|
|
33
35
|
|
|
34
36
|
|
|
35
37
|
@beartype
|
|
@@ -47,17 +49,18 @@ class Client:
|
|
|
47
49
|
The topology is queried from the control device properties.
|
|
48
50
|
"""
|
|
49
51
|
|
|
50
|
-
self.
|
|
52
|
+
self._detector = Detector(ctl_dev, *rcv_devs)
|
|
51
53
|
|
|
52
54
|
try:
|
|
55
|
+
# Get the topology property from control
|
|
53
56
|
topology_prop = ctl_dev.get_property("receiver_topology")
|
|
54
57
|
topology_name = topology_prop["receiver_topology"][0]
|
|
55
|
-
self.
|
|
58
|
+
self.topology_kind = TopologyKind(topology_name)
|
|
56
59
|
except IndexError:
|
|
57
60
|
# Property does not exist
|
|
58
61
|
raise RuntimeError(
|
|
59
62
|
"Could not find 'receiver_topology' property in control device"
|
|
60
|
-
)
|
|
63
|
+
) from None
|
|
61
64
|
except ValueError as e:
|
|
62
65
|
# Invalid topology (no matching name in Topology enum)
|
|
63
66
|
raise ValueError(
|
|
@@ -65,10 +68,9 @@ class Client:
|
|
|
65
68
|
) from e
|
|
66
69
|
|
|
67
70
|
self.recv_devs = rcv_devs
|
|
68
|
-
self.aggregator = Aggregator.from_topology(self.topology)
|
|
69
71
|
|
|
70
72
|
# Cache of processing pipelines by uuid (see method `pipeline`)
|
|
71
|
-
self.__pipelines: dict[UUID,
|
|
73
|
+
self.__pipelines: dict[UUID, Pipeline] = {}
|
|
72
74
|
|
|
73
75
|
self.tango_db = tg.Database()
|
|
74
76
|
|
|
@@ -76,8 +78,8 @@ class Client:
|
|
|
76
78
|
def from_yaml(
|
|
77
79
|
cls,
|
|
78
80
|
config_filename: str = "l2c_config.yaml",
|
|
79
|
-
) ->
|
|
80
|
-
with open(config_filename) as config_f:
|
|
81
|
+
) -> "Client":
|
|
82
|
+
with open(os.path.abspath(config_filename)) as config_f:
|
|
81
83
|
# Raises on io/parsing error
|
|
82
84
|
config = yaml.safe_load(config_f)
|
|
83
85
|
|
|
@@ -89,13 +91,9 @@ class Client:
|
|
|
89
91
|
|
|
90
92
|
return Client(ctl_dev=ctl_dev, rcv_devs=rcv_devs)
|
|
91
93
|
|
|
92
|
-
####################################################################################
|
|
93
|
-
# Client properties
|
|
94
|
-
####################################################################################
|
|
95
|
-
|
|
96
94
|
@property
|
|
97
|
-
def
|
|
98
|
-
return
|
|
95
|
+
def detector(self) -> Detector:
|
|
96
|
+
return self._detector
|
|
99
97
|
|
|
100
98
|
####################################################################################
|
|
101
99
|
# Detector properties
|
|
@@ -103,17 +101,17 @@ class Client:
|
|
|
103
101
|
|
|
104
102
|
@property
|
|
105
103
|
@docstring_from(Detector.det_info)
|
|
106
|
-
def det_info(self):
|
|
104
|
+
def det_info(self) -> dict[str, Any]:
|
|
107
105
|
return self.detector.det_info
|
|
108
106
|
|
|
109
107
|
@property
|
|
110
108
|
@docstring_from(Detector.det_status)
|
|
111
|
-
def det_status(self):
|
|
109
|
+
def det_status(self) -> dict[str, Any]:
|
|
112
110
|
return self.detector.det_status
|
|
113
111
|
|
|
114
112
|
@property
|
|
115
113
|
@docstring_from(Detector.det_capabilities)
|
|
116
|
-
def det_capabilities(self):
|
|
114
|
+
def det_capabilities(self) -> dict[str, Any]:
|
|
117
115
|
return self.detector.det_capabilities
|
|
118
116
|
|
|
119
117
|
@property
|
|
@@ -123,31 +121,42 @@ class Client:
|
|
|
123
121
|
|
|
124
122
|
@property
|
|
125
123
|
@docstring_from(Detector.params_default)
|
|
126
|
-
def params_default(self) -> dict:
|
|
124
|
+
def params_default(self) -> dict[str, Any]:
|
|
127
125
|
return self.detector.params_default
|
|
128
126
|
|
|
129
127
|
####################################################################################
|
|
130
128
|
# Control
|
|
131
129
|
####################################################################################
|
|
132
130
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
131
|
+
def prepare_acq(
|
|
132
|
+
self, uuid: UUID, ctrl_params: dict, acq_params: dict, proc_params: dict
|
|
133
|
+
) -> None:
|
|
134
|
+
"""Prepare acquisition with a given uuid and set of params."""
|
|
135
|
+
|
|
136
|
+
# Adjust params for distributed acquisition
|
|
137
|
+
ctl, acq, proc = topology.distribute_acq(
|
|
138
|
+
ctl_params=ctrl_params,
|
|
139
|
+
acq_params=acq_params,
|
|
140
|
+
proc_params=proc_params,
|
|
141
|
+
num_receivers=len(self.recv_devs),
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
self.detector.prepare_acq(uuid, ctl, acq, proc)
|
|
136
145
|
|
|
137
146
|
@docstring_from(Detector.start_acq)
|
|
138
|
-
def start_acq(self):
|
|
147
|
+
def start_acq(self) -> None:
|
|
139
148
|
self.detector.start_acq()
|
|
140
149
|
|
|
141
150
|
@docstring_from(Detector.trigger)
|
|
142
|
-
def trigger(self):
|
|
151
|
+
def trigger(self) -> None:
|
|
143
152
|
self.detector.trigger()
|
|
144
153
|
|
|
145
154
|
@docstring_from(Detector.stop_acq)
|
|
146
|
-
def stop_acq(self):
|
|
155
|
+
def stop_acq(self) -> None:
|
|
147
156
|
self.detector.stop_acq()
|
|
148
157
|
|
|
149
158
|
@docstring_from(Detector.reset_acq)
|
|
150
|
-
def reset_acq(self):
|
|
159
|
+
def reset_acq(self) -> None:
|
|
151
160
|
self.detector.reset_acq()
|
|
152
161
|
|
|
153
162
|
####################################################################################
|
|
@@ -168,7 +177,7 @@ class Client:
|
|
|
168
177
|
@property
|
|
169
178
|
def nb_frames_xferred(self) -> ProgressCounter:
|
|
170
179
|
"""Aggregated number of frames transferred"""
|
|
171
|
-
return
|
|
180
|
+
return progress_counter.aggregate(
|
|
172
181
|
[
|
|
173
182
|
SingleCounter(
|
|
174
183
|
name="nb_frames_xferred",
|
|
@@ -191,7 +200,7 @@ class Client:
|
|
|
191
200
|
return []
|
|
192
201
|
return [UUID(u) for u in {y for x in uuids for y in x}]
|
|
193
202
|
|
|
194
|
-
def pipeline(self, uuid: Optional[UUID] = None) ->
|
|
203
|
+
def pipeline(self, uuid: Optional[UUID] = None) -> Pipeline:
|
|
195
204
|
"""Get a specific pipeline by uuid. Return the current one by default (uuid=None)"""
|
|
196
205
|
if uuid is None:
|
|
197
206
|
return self.current_pipeline
|
|
@@ -208,16 +217,26 @@ class Client:
|
|
|
208
217
|
self.tango_db.get_device_info(instance).class_name for instance in instances
|
|
209
218
|
]
|
|
210
219
|
|
|
211
|
-
# Select the
|
|
220
|
+
# Select the pipeline according to the Tango class (Homogeneous processing for now)
|
|
212
221
|
pipeline_class = pipelines.get_class(tango_class_name=class_names[0])
|
|
213
222
|
|
|
223
|
+
# Note: sort list of pipeline instances by name such that pipelines are ordered
|
|
224
|
+
# by receiver index. This is necessary e.g. in a strict round-robin topology to
|
|
225
|
+
# be able to find a frame by index.
|
|
226
|
+
proc_devs = [tg.DeviceProxy(url) for url in sorted(list(instances))]
|
|
227
|
+
|
|
214
228
|
# Instantiate pipeline
|
|
215
|
-
self.__pipelines[uuid] = pipeline_class(uuid,
|
|
229
|
+
self.__pipelines[uuid] = pipeline_class(uuid, proc_devs, self.topology_kind)
|
|
216
230
|
|
|
217
231
|
return self.__pipelines[uuid]
|
|
218
232
|
|
|
219
233
|
@property
|
|
220
|
-
def current_pipeline(self) ->
|
|
234
|
+
def current_pipeline(self) -> Pipeline:
|
|
235
|
+
"""Return the current pipeline as a Pipeline object.
|
|
236
|
+
|
|
237
|
+
Raises if no pipeline has been created, or if receivers disagree on the
|
|
238
|
+
current pipeline's uuid.
|
|
239
|
+
"""
|
|
221
240
|
uuids: list[str] = [recv.current_pipeline for recv in self.recv_devs]
|
|
222
241
|
|
|
223
242
|
if all([uuid == "" for uuid in uuids]):
|
|
@@ -230,7 +249,7 @@ class Client:
|
|
|
230
249
|
|
|
231
250
|
return self.pipeline(uuids[0])
|
|
232
251
|
|
|
233
|
-
def erase_pipeline(self, uuid: UUID):
|
|
252
|
+
def erase_pipeline(self, uuid: UUID) -> None:
|
|
234
253
|
"""Erase a pipeline instance"""
|
|
235
254
|
self.__pipelines.pop(uuid, None)
|
|
236
255
|
for recv in self.recv_devs:
|
|
@@ -247,7 +266,7 @@ class Client:
|
|
|
247
266
|
f"State: {self.detector.state}",
|
|
248
267
|
"Controller:",
|
|
249
268
|
f" {self.detector.ctrl}",
|
|
250
|
-
f"Receivers ({self.
|
|
269
|
+
f"Receivers ({self.topology_kind}):",
|
|
251
270
|
*[f" - {recv}" for recv in self.detector.recvs],
|
|
252
271
|
"Progress counters:",
|
|
253
272
|
f" - {self.nb_frames_acquired}",
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# This file is part of the Lima2 project
|
|
4
4
|
#
|
|
5
5
|
# Copyright (c) 2020-2024 Beamline Control Unit, ESRF
|
|
6
|
-
# Distributed under the MIT
|
|
6
|
+
# Distributed under the MIT license. See LICENSE for more info.
|
|
7
7
|
|
|
8
8
|
"""Low level interface to the detector.
|
|
9
9
|
|
|
@@ -23,12 +23,11 @@ from __future__ import annotations
|
|
|
23
23
|
import json
|
|
24
24
|
import logging
|
|
25
25
|
import uuid
|
|
26
|
-
import jsonschema
|
|
27
|
-
from jsonschema_default import create_from
|
|
28
|
-
|
|
29
|
-
from beartype import beartype
|
|
30
26
|
|
|
27
|
+
import jsonschema
|
|
31
28
|
import tango
|
|
29
|
+
from beartype import beartype
|
|
30
|
+
from jsonschema_default import create_from
|
|
32
31
|
|
|
33
32
|
from lima2.client import state_machine as fsm
|
|
34
33
|
from lima2.client.state_machine import State
|
|
@@ -69,6 +68,12 @@ class Detector:
|
|
|
69
68
|
# Preconditions
|
|
70
69
|
if not recv_devs:
|
|
71
70
|
raise ValueError("Must provide at least one receiver")
|
|
71
|
+
elif len(recv_devs) > 1:
|
|
72
|
+
rank_order = [d.recv_rank for d in recv_devs]
|
|
73
|
+
if not all([r == i for i, r in enumerate(rank_order)]):
|
|
74
|
+
raise ValueError(
|
|
75
|
+
f"Receiver MPI rank order does not match client order: {rank_order}"
|
|
76
|
+
)
|
|
72
77
|
|
|
73
78
|
self.__ctrl = ctrl_dev
|
|
74
79
|
self.__recvs = list(recv_devs)
|
|
@@ -81,7 +86,7 @@ class Detector:
|
|
|
81
86
|
) from e
|
|
82
87
|
|
|
83
88
|
self.__params_schema = None
|
|
84
|
-
self.__fsm = fsm.StateMachine(self)
|
|
89
|
+
self.__fsm = fsm.StateMachine(self.__ctrl, self.__recvs)
|
|
85
90
|
self.__try_sync_hard()
|
|
86
91
|
|
|
87
92
|
if _logger.getEffectiveLevel() == logging.DEBUG:
|
|
@@ -120,7 +125,7 @@ class Detector:
|
|
|
120
125
|
Group:
|
|
121
126
|
Info
|
|
122
127
|
"""
|
|
123
|
-
return json.loads(self.__ctrl.det_info)
|
|
128
|
+
return json.loads(self.__ctrl.det_info) # type: ignore
|
|
124
129
|
|
|
125
130
|
@property
|
|
126
131
|
def det_status(self) -> dict:
|
|
@@ -142,7 +147,7 @@ class Detector:
|
|
|
142
147
|
Group:
|
|
143
148
|
Status
|
|
144
149
|
"""
|
|
145
|
-
return json.loads(self.__ctrl.det_status)
|
|
150
|
+
return json.loads(self.__ctrl.det_status) # type: ignore
|
|
146
151
|
|
|
147
152
|
@property
|
|
148
153
|
def det_capabilities(self) -> dict:
|
|
@@ -159,7 +164,7 @@ class Detector:
|
|
|
159
164
|
Group:
|
|
160
165
|
Info
|
|
161
166
|
"""
|
|
162
|
-
return json.loads(self.__ctrl.det_capabilities)
|
|
167
|
+
return json.loads(self.__ctrl.det_capabilities) # type: ignore
|
|
163
168
|
|
|
164
169
|
def __try_params_schema(self):
|
|
165
170
|
"""
|
|
@@ -416,16 +421,15 @@ class Detector:
|
|
|
416
421
|
f"prepare_acq({uuid}, {ctrl_params}, {acq_params}, {proc_params})"
|
|
417
422
|
)
|
|
418
423
|
|
|
419
|
-
# Validate ctrl_params and recv_params against schema
|
|
420
|
-
def get_acq_params_schema(name) -> str:
|
|
421
|
-
return self.params_schema[name]["acq_params"]
|
|
422
|
-
|
|
423
424
|
jsonschema.validate(
|
|
424
|
-
instance=ctrl_params,
|
|
425
|
+
instance=ctrl_params,
|
|
426
|
+
schema=self.params_schema[self.ctrl.name()]["acq_params"],
|
|
425
427
|
)
|
|
428
|
+
|
|
426
429
|
for i, recv in enumerate(self.__recvs):
|
|
427
430
|
jsonschema.validate(
|
|
428
|
-
instance=acq_params[i],
|
|
431
|
+
instance=acq_params[i],
|
|
432
|
+
schema=self.params_schema[recv.name()]["acq_params"],
|
|
429
433
|
)
|
|
430
434
|
|
|
431
435
|
# TODO: Validate proc_params against the schema
|
|
@@ -516,7 +520,7 @@ class Detector:
|
|
|
516
520
|
Group:
|
|
517
521
|
Status
|
|
518
522
|
"""
|
|
519
|
-
return self.__ctrl.nb_frames_acquired
|
|
523
|
+
return self.__ctrl.nb_frames_acquired # type: ignore
|
|
520
524
|
|
|
521
525
|
@property
|
|
522
526
|
def nb_frames_xferred(self) -> list[int]:
|
|
@@ -3,14 +3,16 @@
|
|
|
3
3
|
# This file is part of the Lima2 project
|
|
4
4
|
#
|
|
5
5
|
# Copyright (c) 2020-2024 Beamline Control Unit, ESRF
|
|
6
|
-
# Distributed under the MIT
|
|
6
|
+
# Distributed under the MIT license. See LICENSE for more info.
|
|
7
7
|
|
|
8
8
|
"""Utility functions to decode Lima data from device server.
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
|
+
from dataclasses import dataclass
|
|
11
12
|
import struct
|
|
12
13
|
import numpy
|
|
13
14
|
import typing
|
|
15
|
+
from typing_extensions import Union
|
|
14
16
|
import enum
|
|
15
17
|
|
|
16
18
|
from .exception import DevEncodedFormatNotSupported
|
|
@@ -19,6 +21,8 @@ DATA_HEADER_FORMAT = "<IHHIIHHHHHHHHIIIIIIQ"
|
|
|
19
21
|
DATA_MAGIC = struct.unpack(">I", b"DTAY")[0]
|
|
20
22
|
DATA_HEADER_SIZE = struct.calcsize(DATA_HEADER_FORMAT)
|
|
21
23
|
|
|
24
|
+
EncodedFrame = Union[bytes, tuple[str, bytes]]
|
|
25
|
+
|
|
22
26
|
|
|
23
27
|
class IMAGE_MODES(enum.Enum):
|
|
24
28
|
DARRAY_UINT8 = 0
|
|
@@ -48,12 +52,13 @@ MODE_TO_NUMPY = {
|
|
|
48
52
|
}
|
|
49
53
|
|
|
50
54
|
|
|
51
|
-
|
|
55
|
+
@dataclass
|
|
56
|
+
class Frame:
|
|
52
57
|
"""
|
|
53
58
|
Provide data frame from Lima including few metadata
|
|
54
59
|
"""
|
|
55
60
|
|
|
56
|
-
data: numpy.
|
|
61
|
+
data: numpy.ndarray
|
|
57
62
|
"""Data of the frame"""
|
|
58
63
|
|
|
59
64
|
idx: typing.Optional[int]
|
|
@@ -66,7 +71,7 @@ class Frame(typing.NamedTuple):
|
|
|
66
71
|
return self.data is None
|
|
67
72
|
|
|
68
73
|
|
|
69
|
-
def decode(raw_data:
|
|
74
|
+
def decode(raw_data: EncodedFrame) -> Frame:
|
|
70
75
|
"""Decode data provided by Lima device image attribute
|
|
71
76
|
|
|
72
77
|
See https://lima1.readthedocs.io/en/latest/applications/tango/python/doc/#devencoded-data-array
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# This file is part of the Lima2 project
|
|
4
4
|
#
|
|
5
5
|
# Copyright (c) 2020-2024 Beamline Control Unit, ESRF
|
|
6
|
-
# Distributed under the MIT
|
|
6
|
+
# Distributed under the MIT license. See LICENSE for more info.
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class DevEncodedFormatNotSupported(Exception):
|
|
@@ -3,32 +3,33 @@
|
|
|
3
3
|
# This file is part of the Lima2 project
|
|
4
4
|
#
|
|
5
5
|
# Copyright (c) 2020-2024 Beamline Control Unit, ESRF
|
|
6
|
-
# Distributed under the MIT
|
|
6
|
+
# Distributed under the MIT license. See LICENSE for more info.
|
|
7
7
|
|
|
8
8
|
"""Utility functions to decode Lima data from device server.
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
11
|
from __future__ import annotations
|
|
12
12
|
|
|
13
|
+
from dataclasses import dataclass
|
|
13
14
|
import struct
|
|
14
15
|
import numpy as np
|
|
15
|
-
import typing
|
|
16
16
|
|
|
17
17
|
from .exception import DevEncodedFormatNotSupported
|
|
18
18
|
|
|
19
|
-
from lima2.client.devencoded.dense_frame import Frame
|
|
19
|
+
from lima2.client.devencoded.dense_frame import Frame, EncodedFrame
|
|
20
20
|
|
|
21
21
|
DATA_HEADER_FORMAT = "<HHII"
|
|
22
22
|
DATA_HEADER_SIZE = struct.calcsize(DATA_HEADER_FORMAT)
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
@dataclass
|
|
26
|
+
class SparseFrame:
|
|
26
27
|
"""
|
|
27
28
|
Provide sparse data frame from Lima including few metadata
|
|
28
29
|
"""
|
|
29
30
|
|
|
30
|
-
index: np.
|
|
31
|
-
intensity: np.
|
|
31
|
+
index: np.ndarray
|
|
32
|
+
intensity: np.ndarray
|
|
32
33
|
|
|
33
34
|
idx: int | None
|
|
34
35
|
"""Index of the frame where 0 is the first frame"""
|
|
@@ -54,7 +55,7 @@ class SparseFrame(typing.NamedTuple):
|
|
|
54
55
|
return Frame(dense, self.idx)
|
|
55
56
|
|
|
56
57
|
|
|
57
|
-
def decode(raw_data:
|
|
58
|
+
def decode(raw_data: EncodedFrame) -> SparseFrame:
|
|
58
59
|
"""Decode data provided by Lima2
|
|
59
60
|
|
|
60
61
|
Argument:
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# This file is part of the Lima2 project
|
|
4
4
|
#
|
|
5
5
|
# Copyright (c) 2020-2024 Beamline Control Unit, ESRF
|
|
6
|
-
# Distributed under the MIT
|
|
6
|
+
# Distributed under the MIT license. See LICENSE for more info.
|
|
7
7
|
|
|
8
8
|
"""Utility functions to decode Lima data from device server.
|
|
9
9
|
"""
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import struct
|
|
12
12
|
import numpy as np
|
|
13
13
|
import typing
|
|
14
|
+
from dataclasses import dataclass
|
|
14
15
|
|
|
15
16
|
from .exception import DevEncodedFormatNotSupported
|
|
16
17
|
|
|
@@ -18,15 +19,16 @@ DATA_HEADER_FORMAT = "<HHIII"
|
|
|
18
19
|
DATA_HEADER_SIZE = struct.calcsize(DATA_HEADER_FORMAT)
|
|
19
20
|
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
@dataclass
|
|
23
|
+
class SparseFrameWithBackground:
|
|
22
24
|
"""
|
|
23
25
|
Provide sparse data frame from Lima including few metadata
|
|
24
26
|
"""
|
|
25
27
|
|
|
26
|
-
index: np.
|
|
27
|
-
intensity: np.
|
|
28
|
-
background_avg: np.
|
|
29
|
-
background_std: np.
|
|
28
|
+
index: np.ndarray
|
|
29
|
+
intensity: np.ndarray
|
|
30
|
+
background_avg: np.ndarray
|
|
31
|
+
background_std: np.ndarray
|
|
30
32
|
|
|
31
33
|
idx: typing.Optional[int]
|
|
32
34
|
"""Index of the frame where 0 is the first frame"""
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# This file is part of the Lima2 project
|
|
4
4
|
#
|
|
5
5
|
# Copyright (c) 2020-2024 Beamline Control Unit, ESRF
|
|
6
|
-
# Distributed under the MIT
|
|
6
|
+
# Distributed under the MIT license. See LICENSE for more info.
|
|
7
7
|
|
|
8
8
|
"""Utility functions to decode Lima data from device server.
|
|
9
9
|
"""
|
|
@@ -72,7 +72,7 @@ def decode(raw_data: bytes, dtype) -> numpy.ndarray:
|
|
|
72
72
|
"Image header version not supported (found %s)." % version
|
|
73
73
|
)
|
|
74
74
|
|
|
75
|
-
data = numpy.frombuffer(raw_data, offset=header_size, dtype=dtype)
|
|
75
|
+
data: numpy.ndarray = numpy.frombuffer(raw_data, offset=header_size, dtype=dtype)
|
|
76
76
|
|
|
77
77
|
# Create a memory copy only if it is needed
|
|
78
78
|
if not data.flags.writeable:
|