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.
Files changed (56) hide show
  1. {lima2_client-2.0.0/src/lima2.client.egg-info → lima2_client-2.0rc2}/PKG-INFO +7 -6
  2. {lima2_client-2.0.0 → lima2_client-2.0rc2}/setup.cfg +8 -3
  3. {lima2_client-2.0.0 → lima2_client-2.0rc2}/src/lima2/client/__init__.py +3 -3
  4. {lima2_client-2.0.0 → lima2_client-2.0rc2}/src/lima2/client/channel.py +1 -1
  5. {lima2_client-2.0.0 → lima2_client-2.0rc2}/src/lima2/client/client.py +57 -38
  6. {lima2_client-2.0.0 → lima2_client-2.0rc2}/src/lima2/client/convert.py +1 -1
  7. {lima2_client-2.0.0 → lima2_client-2.0rc2}/src/lima2/client/detector.py +20 -16
  8. {lima2_client-2.0.0 → lima2_client-2.0rc2}/src/lima2/client/devencoded/__init__.py +1 -1
  9. {lima2_client-2.0.0 → lima2_client-2.0rc2}/src/lima2/client/devencoded/dense_frame.py +9 -4
  10. {lima2_client-2.0.0 → lima2_client-2.0rc2}/src/lima2/client/devencoded/exception.py +1 -1
  11. {lima2_client-2.0.0 → lima2_client-2.0rc2}/src/lima2/client/devencoded/sparse_frame.py +8 -7
  12. {lima2_client-2.0.0 → lima2_client-2.0rc2}/src/lima2/client/devencoded/sparse_frame_with_background.py +8 -6
  13. {lima2_client-2.0.0 → lima2_client-2.0rc2}/src/lima2/client/devencoded/structured_array.py +2 -2
  14. lima2_client-2.0rc2/src/lima2/client/pipeline.py +555 -0
  15. lima2_client-2.0rc2/src/lima2/client/pipelines/__init__.py +46 -0
  16. lima2_client-2.0rc2/src/lima2/client/pipelines/legacy.py +56 -0
  17. lima2_client-2.0rc2/src/lima2/client/pipelines/smx.py +120 -0
  18. lima2_client-2.0rc2/src/lima2/client/pipelines/xpcs.py +127 -0
  19. lima2_client-2.0rc2/src/lima2/client/progress_counter.py +69 -0
  20. lima2_client-2.0rc2/src/lima2/client/py.typed +0 -0
  21. {lima2_client-2.0.0 → lima2_client-2.0rc2}/src/lima2/client/shell.py +21 -29
  22. {lima2_client-2.0.0 → lima2_client-2.0rc2}/src/lima2/client/state_machine.py +27 -22
  23. lima2_client-2.0rc2/src/lima2/client/topology.py +162 -0
  24. lima2_client-2.0rc2/src/lima2/client/utils.py +49 -0
  25. {lima2_client-2.0.0 → lima2_client-2.0rc2}/src/lima2/server/__init__.py +1 -1
  26. {lima2_client-2.0.0 → lima2_client-2.0rc2}/src/lima2/server/control.py +1 -1
  27. {lima2_client-2.0.0 → lima2_client-2.0rc2}/src/lima2/server/receiver.py +12 -1
  28. {lima2_client-2.0.0 → lima2_client-2.0rc2}/src/lima2/server/timer.py +1 -1
  29. {lima2_client-2.0.0 → lima2_client-2.0rc2/src/lima2.client.egg-info}/PKG-INFO +7 -6
  30. {lima2_client-2.0.0 → lima2_client-2.0rc2}/src/lima2.client.egg-info/SOURCES.txt +8 -4
  31. {lima2_client-2.0.0 → lima2_client-2.0rc2}/src/lima2.client.egg-info/requires.txt +1 -0
  32. {lima2_client-2.0.0 → lima2_client-2.0rc2}/tests/test_client.py +7 -2
  33. {lima2_client-2.0.0 → lima2_client-2.0rc2}/tests/test_detector.py +1 -29
  34. {lima2_client-2.0.0 → lima2_client-2.0rc2}/tests/test_import.py +1 -1
  35. lima2_client-2.0rc2/tests/test_pipeline.py +486 -0
  36. {lima2_client-2.0.0 → lima2_client-2.0rc2}/tests/test_pipelines.py +6 -1
  37. lima2_client-2.0rc2/tests/test_progress_counter.py +35 -0
  38. {lima2_client-2.0.0 → lima2_client-2.0rc2}/tests/test_state_machine.py +2 -2
  39. {lima2_client-2.0.0 → lima2_client-2.0rc2}/tests/test_timer.py +1 -1
  40. lima2_client-2.0rc2/tests/test_topology.py +145 -0
  41. lima2_client-2.0rc2/tests/test_utils.py +30 -0
  42. lima2_client-2.0.0/src/lima2/client/aggregator.py +0 -126
  43. lima2_client-2.0.0/src/lima2/client/pipelines/__init__.py +0 -52
  44. lima2_client-2.0.0/src/lima2/client/pipelines/legacy.py +0 -122
  45. lima2_client-2.0.0/src/lima2/client/pipelines/smx.py +0 -111
  46. lima2_client-2.0.0/src/lima2/client/pipelines/xpcs.py +0 -145
  47. lima2_client-2.0.0/src/lima2/client/processing.py +0 -212
  48. lima2_client-2.0.0/src/lima2/client/utils.py +0 -18
  49. lima2_client-2.0.0/tests/test_aggregator.py +0 -78
  50. lima2_client-2.0.0/tests/test_utils.py +0 -16
  51. /lima2_client-2.0.0/LICENCE → /lima2_client-2.0rc2/LICENSE +0 -0
  52. {lima2_client-2.0.0 → lima2_client-2.0rc2}/README.md +0 -0
  53. {lima2_client-2.0.0 → lima2_client-2.0rc2}/pyproject.toml +0 -0
  54. {lima2_client-2.0.0 → lima2_client-2.0rc2}/src/lima2.client.egg-info/dependency_links.txt +0 -0
  55. {lima2_client-2.0.0 → lima2_client-2.0rc2}/src/lima2.client.egg-info/entry_points.txt +0 -0
  56. {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
1
+ Metadata-Version: 2.2
2
2
  Name: lima2.client
3
- Version: 2.0.0
3
+ Version: 2.0rc2
4
4
  Summary: Python client library for Lima2 detector
5
- Home-page: https://gitlab.esrf.fr/limagroup/Lima2-client
5
+ Home-page: https://gitlab.esrf.fr/limagroup/lima2-client
6
6
  Author: BCU (ESRF)
7
- License: LGPLv3
7
+ License: MIT
8
8
  Classifier: Intended Audience :: Science/Research
9
- Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)
9
+ Classifier: License :: OSI Approved :: MIT License
10
10
  Classifier: Programming Language :: Python :: 3
11
11
  Description-Content-Type: text/markdown
12
- License-File: LICENCE
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 = LGPLv3
8
- url = https://gitlab.esrf.fr/limagroup/Lima2-client
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 :: GNU Lesser General Public License v3 (LGPLv3)
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 licence. See LICENSE for more info.
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.processing import Processing
19
+ from lima2.client.pipeline import Pipeline
20
20
 
21
- __all__ = [Client, CommError, State, Processing]
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 licence. See LICENSE for more info.
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 licence. See LICENSE for more info.
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 Optional, Self
27
+ from typing_extensions import Any, Optional
27
28
 
28
- from . import pipelines, state_machine
29
- from .aggregator import Aggregator, ProgressCounter, SingleCounter
30
- from .detector import Detector
31
- from .processing import Processing, Topology
32
- from .utils import docstring_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.detector = Detector(ctl_dev, *rcv_devs)
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.topology = Topology(topology_name)
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, Processing] = {}
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
- ) -> Self:
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 nb_recvs(self):
98
- return len(self.recv_devs)
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
- @docstring_from(Detector.prepare_acq)
134
- def prepare_acq(self, *args, **kwargs):
135
- self.detector.prepare_acq(*args, **kwargs)
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 self.aggregator(
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) -> Optional[Processing]:
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 processing according to the Tango class (Homogeneous processing for now)
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, list(instances), self.topology)
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) -> Optional[Processing]:
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.topology}):",
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 licence. See LICENSE for more info.
6
+ # Distributed under the MIT license. See LICENSE for more info.
7
7
 
8
8
  """Convertion routines.
9
9
 
@@ -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 licence. See LICENSE for more info.
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, schema=get_acq_params_schema(self.ctrl.name())
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], schema=get_acq_params_schema(recv.name())
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,4 +3,4 @@
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 licence. See LICENSE for more info.
6
+ # Distributed under the MIT license. See LICENSE for more info.
@@ -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 licence. See LICENSE for more info.
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
- class Frame(typing.NamedTuple):
55
+ @dataclass
56
+ class Frame:
52
57
  """
53
58
  Provide data frame from Lima including few metadata
54
59
  """
55
60
 
56
- data: numpy.array
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: bytes) -> Frame:
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 licence. See LICENSE for more info.
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 licence. See LICENSE for more info.
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
- class SparseFrame(typing.NamedTuple):
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.array
31
- intensity: np.array
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: bytes) -> SparseFrame:
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 licence. See LICENSE for more info.
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
- class SparseFrameWithBackground(typing.NamedTuple):
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.array
27
- intensity: np.array
28
- background_avg: np.array
29
- background_std: np.array
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 licence. See LICENSE for more info.
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: