lima2.client 3.0a1__tar.gz → 3.1a0__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 (49) hide show
  1. {lima2_client-3.0a1/src/lima2_client.egg-info → lima2_client-3.1a0}/PKG-INFO +1 -1
  2. {lima2_client-3.0a1 → lima2_client-3.1a0}/src/lima2/client/client.py +41 -1
  3. {lima2_client-3.0a1 → lima2_client-3.1a0}/src/lima2/client/pipeline.py +16 -16
  4. {lima2_client-3.0a1 → lima2_client-3.1a0}/src/lima2/client/topology.py +11 -6
  5. {lima2_client-3.0a1 → lima2_client-3.1a0}/src/lima2/master_file/generate.py +24 -59
  6. lima2_client-3.1a0/src/lima2/master_file/py.typed +0 -0
  7. {lima2_client-3.0a1 → lima2_client-3.1a0/src/lima2_client.egg-info}/PKG-INFO +1 -1
  8. {lima2_client-3.0a1 → lima2_client-3.1a0}/src/lima2_client.egg-info/SOURCES.txt +1 -0
  9. {lima2_client-3.0a1 → lima2_client-3.1a0}/LICENSE +0 -0
  10. {lima2_client-3.0a1 → lima2_client-3.1a0}/README.md +0 -0
  11. {lima2_client-3.0a1 → lima2_client-3.1a0}/pyproject.toml +0 -0
  12. {lima2_client-3.0a1 → lima2_client-3.1a0}/setup.cfg +0 -0
  13. {lima2_client-3.0a1 → lima2_client-3.1a0}/src/finite_state_machine/__init__.py +0 -0
  14. {lima2_client-3.0a1 → lima2_client-3.1a0}/src/finite_state_machine/draw_state_diagram.py +0 -0
  15. {lima2_client-3.0a1 → lima2_client-3.1a0}/src/finite_state_machine/examples/__init__.py +0 -0
  16. {lima2_client-3.0a1 → lima2_client-3.1a0}/src/finite_state_machine/examples/async_github_pull_request.py +0 -0
  17. {lima2_client-3.0a1 → lima2_client-3.1a0}/src/finite_state_machine/examples/async_turnstile.py +0 -0
  18. {lima2_client-3.0a1 → lima2_client-3.1a0}/src/finite_state_machine/examples/boolean_field.py +0 -0
  19. {lima2_client-3.0a1 → lima2_client-3.1a0}/src/finite_state_machine/examples/github_pull_request.py +0 -0
  20. {lima2_client-3.0a1 → lima2_client-3.1a0}/src/finite_state_machine/examples/turnstile.py +0 -0
  21. {lima2_client-3.0a1 → lima2_client-3.1a0}/src/finite_state_machine/exceptions.py +0 -0
  22. {lima2_client-3.0a1 → lima2_client-3.1a0}/src/finite_state_machine/state_machine.py +0 -0
  23. {lima2_client-3.0a1 → lima2_client-3.1a0}/src/lima2/client/__init__.py +0 -0
  24. {lima2_client-3.0a1 → lima2_client-3.1a0}/src/lima2/client/convert.py +0 -0
  25. {lima2_client-3.0a1 → lima2_client-3.1a0}/src/lima2/client/detector.py +0 -0
  26. {lima2_client-3.0a1 → lima2_client-3.1a0}/src/lima2/client/devencoded/__init__.py +0 -0
  27. {lima2_client-3.0a1 → lima2_client-3.1a0}/src/lima2/client/devencoded/dense_frame.py +0 -0
  28. {lima2_client-3.0a1 → lima2_client-3.1a0}/src/lima2/client/devencoded/exception.py +0 -0
  29. {lima2_client-3.0a1 → lima2_client-3.1a0}/src/lima2/client/devencoded/smx_sparse_frame.py +0 -0
  30. {lima2_client-3.0a1 → lima2_client-3.1a0}/src/lima2/client/devencoded/sparse_frame.py +0 -0
  31. {lima2_client-3.0a1 → lima2_client-3.1a0}/src/lima2/client/devencoded/structured_array.py +0 -0
  32. {lima2_client-3.0a1 → lima2_client-3.1a0}/src/lima2/client/pipelines/__init__.py +0 -0
  33. {lima2_client-3.0a1 → lima2_client-3.1a0}/src/lima2/client/pipelines/failing.py +0 -0
  34. {lima2_client-3.0a1 → lima2_client-3.1a0}/src/lima2/client/pipelines/legacy.py +0 -0
  35. {lima2_client-3.0a1 → lima2_client-3.1a0}/src/lima2/client/pipelines/smx.py +0 -0
  36. {lima2_client-3.0a1 → lima2_client-3.1a0}/src/lima2/client/pipelines/xpcs.py +0 -0
  37. {lima2_client-3.0a1 → lima2_client-3.1a0}/src/lima2/client/progress_counter.py +0 -0
  38. {lima2_client-3.0a1 → lima2_client-3.1a0}/src/lima2/client/py.typed +0 -0
  39. {lima2_client-3.0a1 → lima2_client-3.1a0}/src/lima2/client/shell.py +0 -0
  40. {lima2_client-3.0a1 → lima2_client-3.1a0}/src/lima2/client/state_machine.py +0 -0
  41. {lima2_client-3.0a1 → lima2_client-3.1a0}/src/lima2/client/utils.py +0 -0
  42. {lima2_client-3.0a1 → lima2_client-3.1a0}/src/lima2/server/__init__.py +0 -0
  43. {lima2_client-3.0a1 → lima2_client-3.1a0}/src/lima2/server/control.py +0 -0
  44. {lima2_client-3.0a1 → lima2_client-3.1a0}/src/lima2/server/receiver.py +0 -0
  45. {lima2_client-3.0a1 → lima2_client-3.1a0}/src/lima2/server/timer.py +0 -0
  46. {lima2_client-3.0a1 → lima2_client-3.1a0}/src/lima2_client.egg-info/dependency_links.txt +0 -0
  47. {lima2_client-3.0a1 → lima2_client-3.1a0}/src/lima2_client.egg-info/entry_points.txt +0 -0
  48. {lima2_client-3.0a1 → lima2_client-3.1a0}/src/lima2_client.egg-info/requires.txt +0 -0
  49. {lima2_client-3.0a1 → lima2_client-3.1a0}/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.0a1
3
+ Version: 3.1a0
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
@@ -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,16 @@ 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
+
80
92
  @classmethod
81
93
  def from_yaml(
82
94
  cls,
@@ -147,7 +159,11 @@ class Client:
147
159
  ####################################################################################
148
160
 
149
161
  def prepare_acq(
150
- self, uuid: UUID, ctrl_params: dict, acq_params: dict, proc_params: dict
162
+ self,
163
+ uuid: UUID,
164
+ ctrl_params: dict,
165
+ acq_params: dict,
166
+ proc_params: dict,
151
167
  ) -> None:
152
168
  """Prepare acquisition with a given uuid and set of params."""
153
169
 
@@ -175,16 +191,40 @@ class Client:
175
191
 
176
192
  self.current_pipeline.register_on_error(on_error)
177
193
 
194
+ # Remove master file tasks of previous acquisitions if done or not started
195
+ for prev_uuid in list(self.master_file_tasks.keys()):
196
+ tasks = self.master_file_tasks[prev_uuid].values()
197
+ if all([not task.started() or task.ready() for task in tasks]):
198
+ logger.debug(
199
+ "All master file tasks done (or never started) for "
200
+ f"{prev_uuid} -> cleaning up"
201
+ )
202
+ self.master_file_tasks.pop(prev_uuid)
203
+ else:
204
+ logger.debug(f"Not all master file tasks done for {prev_uuid}...")
205
+
206
+ self.master_file_tasks[uuid] = mfg.enable_generation(
207
+ pipeline=self.current_pipeline,
208
+ num_frames=ctrl_params["acq"]["nb_frames"],
209
+ ctrl_params=ctl,
210
+ recv_params=acq[0],
211
+ proc_params=proc[0],
212
+ )
213
+
178
214
  @docstring_from(Detector.start_acq)
179
215
  def start_acq(self) -> None:
180
216
  pipeline = self.current_pipeline
181
217
  pipeline.register_on_finished(pipeline.unregister_events)
218
+
182
219
  try:
183
220
  self.detector.start_acq()
184
221
  except Exception:
185
222
  pipeline.unregister_events()
186
223
  raise
187
224
 
225
+ for task in self.master_file_tasks[self.current_pipeline.uuid].values():
226
+ task.start()
227
+
188
228
  @docstring_from(Detector.trigger)
189
229
  def trigger(self) -> None:
190
230
  self.detector.trigger()
@@ -379,13 +379,13 @@ class Pipeline:
379
379
 
380
380
  # Pop stats for new frames from each receiver and concatenate, mixing in cached data.
381
381
  # Data comes from server as 1D array with size (num_frames * num_rois)
382
- new_data = np.concatenate(
383
- [cache.flatten()]
384
- + [
385
- structured_array.decode(dev.popRoiStatistics(), dtype)
386
- for dev in self._devs
387
- ]
388
- )
382
+ def pop_data(dev) -> np.ndarray:
383
+ return structured_array.decode(dev.popRoiStatistics(), dtype)
384
+
385
+ num_rcvs = len(self._devs)
386
+ with ThreadPoolExecutor(max_workers=num_rcvs) as pool:
387
+ flat_cache = [cache.flatten()]
388
+ new_data = np.concatenate(flat_cache + list(pool.map(pop_data, self._devs)))
389
389
 
390
390
  # Guess number of frames from the data
391
391
  num_frames = np.unique(new_data["frame_idx"]).size
@@ -473,13 +473,13 @@ class Pipeline:
473
473
 
474
474
  # Pop profiles for new frames from each receiver and concatenate, mixing in cached data.
475
475
  # Data comes from server as 1D array with size (num_frames * sum(roi_lengths))
476
- new_data = np.concatenate(
477
- [cache.flatten()]
478
- + [
479
- structured_array.decode(dev.popRoiProfiles(), dtype)
480
- for dev in self._devs
481
- ]
482
- )
476
+ def pop_data(dev) -> np.ndarray:
477
+ return structured_array.decode(dev.popRoiProfiles(), dtype)
478
+
479
+ num_rcvs = len(self._devs)
480
+ with ThreadPoolExecutor(max_workers=num_rcvs) as pool:
481
+ flat_cache = [cache.flatten()]
482
+ new_data = np.concatenate(flat_cache + list(pool.map(pop_data, self._devs)))
483
483
 
484
484
  # Reshape into (num_frames, sum(roi_lengths))
485
485
  new_data = new_data.reshape((-1, sum(roi_lengths)))
@@ -654,9 +654,9 @@ class Pipeline:
654
654
  "no associated label"
655
655
  )
656
656
 
657
- def last_frames(rcv_idx):
657
+ def last_frames(rcv_idx: int) -> int:
658
658
  last_frames_str = self._devs[rcv_idx].last_frames
659
- return json.loads(last_frames_str)[f"{source.label}_idx"]
659
+ return int(json.loads(last_frames_str)[f"{source.label}_idx"])
660
660
 
661
661
  num_rcvs = len(self._devs)
662
662
  with ThreadPoolExecutor(max_workers=num_rcvs) as pool:
@@ -9,6 +9,7 @@ import copy
9
9
  from abc import ABC, abstractmethod
10
10
  from collections.abc import Callable
11
11
  from enum import Enum
12
+ from concurrent.futures import ThreadPoolExecutor
12
13
 
13
14
  import tango as tg
14
15
  from beartype import beartype
@@ -107,18 +108,22 @@ class UnevenTopology(Topology):
107
108
  If the frame is found on multiple devices (e.g. frame_idx=-1),
108
109
  return the one found last (not necessarily the most recent).
109
110
  """
110
- frame: EncodedFrame | None = None
111
111
 
112
- for i in range(self.num_receivers):
112
+ def get_frame(rcv_idx) -> EncodedFrame | None:
113
113
  try:
114
- frame = getters[i](frame_idx)
114
+ frame: EncodedFrame = getters[rcv_idx](frame_idx)
115
+ return frame
115
116
  except tg.DevFailed:
116
- pass
117
+ return None
117
118
 
118
- if frame is None:
119
+ num_rcvs = self.num_receivers
120
+ with ThreadPoolExecutor(max_workers=num_rcvs) as pool:
121
+ frames = [f for f in pool.map(get_frame, range(num_rcvs)) if f is not None]
122
+
123
+ if len(frames) == 0:
119
124
  raise IndexError(f"Frame {frame_idx} not available from any receiver")
120
125
 
121
- return frame
126
+ return frames[0]
122
127
 
123
128
 
124
129
  def distribute_acq(
@@ -13,7 +13,6 @@ processing pipelines finish.
13
13
  import json
14
14
  import logging
15
15
  import os
16
- import sys
17
16
  import time
18
17
  import traceback
19
18
  from copy import deepcopy
@@ -25,6 +24,7 @@ import h5py
25
24
  import numpy as np
26
25
 
27
26
  import gevent
27
+ import gevent.event
28
28
 
29
29
  from lima2.client.pipeline import FrameType, Pipeline
30
30
 
@@ -39,13 +39,19 @@ class LUTSource(Enum):
39
39
 
40
40
 
41
41
  class MasterFileTask:
42
- def __init__(self, task, end_event):
42
+ def __init__(self, task: gevent.Greenlet, end_event):
43
43
  self.task = task
44
44
  self.end_event = end_event
45
45
 
46
46
  def ready(self):
47
47
  return self.task.ready()
48
48
 
49
+ def start(self):
50
+ self.task.start()
51
+
52
+ def started(self) -> bool:
53
+ return self.task.started
54
+
49
55
  def wait(self, timeout):
50
56
  finished = gevent.wait([self.task], timeout=timeout)
51
57
  return len(finished) > 0
@@ -60,45 +66,6 @@ class MasterFileTask:
60
66
  self.task.join()
61
67
 
62
68
 
63
- active_tasks = {}
64
-
65
-
66
- def check_generation(
67
- ctrl_params: dict, recv_params: dict, proc_params: dict
68
- ) -> list[dict]:
69
- """Check the parameters to be sent to prepare to ensure master file info"""
70
-
71
- # group all active saving subsystems
72
- active_saving_params: list[dict] = []
73
-
74
- # first check receiver raw saving or SDK saving
75
- for params in [recv_params, ctrl_params]:
76
- if "saving" in params and params["saving"]["enabled"]:
77
- active_saving_params.append(params["saving"])
78
- break
79
-
80
- # then check the saving streams of the current pipeline
81
- for source, params in proc_params.items():
82
- # Skip other subsystems
83
- if not source.startswith("saving"):
84
- continue
85
- # Skip source if channel is disabled
86
- if params["enabled"]:
87
- active_saving_params.append(params)
88
-
89
- # include detector frame_idx dataset in HDF5 files
90
- for saving_params in active_saving_params:
91
- if "include_frame_idx" in saving_params:
92
- saving_params["include_frame_idx"] = True
93
-
94
- # allow retreiving recv-det-frame-idx mapping
95
- if "frame_idx_enabled" in proc_params:
96
- logger.debug("Enabling frame_idx generation")
97
- proc_params["frame_idx_enabled"] = True
98
-
99
- return ctrl_params, recv_params, proc_params
100
-
101
-
102
69
  def enable_generation(
103
70
  pipeline: Pipeline,
104
71
  num_frames: int,
@@ -107,7 +74,7 @@ def enable_generation(
107
74
  proc_params: dict,
108
75
  rr_offset: int = 0,
109
76
  lut_source: LUTSource = LUTSource.AUTO,
110
- ) -> None:
77
+ ) -> dict[str, MasterFileTask]:
111
78
  """Enable the generation of a master file for a given pipeline.
112
79
 
113
80
  Must be called between Client.prepare_acq() and Client.start_acq(), so that the
@@ -244,31 +211,29 @@ def enable_generation(
244
211
  logger.warning(
245
212
  "Master file generation not enabled: no dense frames saving channel enabled."
246
213
  )
247
- return None
214
+ return {}
248
215
 
249
216
  def master_file_task(src, params) -> None:
250
- try:
251
- p = MasterFileGeneration.Params(**params)
252
- gen = MasterFileGeneration(p)
253
- gen.run()
254
- logger.debug(f"On finished {src} master file generation")
255
- except Exception as e:
256
- logger.error("Error generating %s master file: %s", src, e)
257
- traceback.print_exception(*sys.exc_info())
217
+ p = MasterFileGeneration.Params(**params)
218
+ gen = MasterFileGeneration(p)
219
+ gen.run()
220
+ logger.debug(f"On finished {src} master file generation")
258
221
 
259
- tasks = {}
222
+ tasks: dict[str, MasterFileTask] = {}
260
223
 
261
224
  for src, params in generate_params.items():
262
- task = gevent.spawn(master_file_task, src, params)
225
+ task = gevent.Greenlet(master_file_task, src, params)
263
226
  tasks[src] = MasterFileTask(task, end_event)
264
227
 
265
- def cleanup(task):
266
- if all([t.ready() for t in tasks.values()]):
267
- active_tasks.pop(uuid)
268
-
269
- task.link(cleanup)
228
+ def log_error(greenlet: gevent.Greenlet) -> None:
229
+ msg = (
230
+ f"Exception in master file task {str(uuid)} (channel '{src}'):\n"
231
+ + "".join(traceback.format_exception(greenlet.exception))
232
+ )
233
+ logger.error(msg)
234
+ print(msg)
270
235
 
271
- active_tasks[uuid] = tasks
236
+ task.link_exception(callback=log_error)
272
237
 
273
238
  return tasks
274
239
 
File without changes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lima2-client
3
- Version: 3.0a1
3
+ Version: 3.1a0
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
@@ -34,6 +34,7 @@ src/lima2/client/pipelines/legacy.py
34
34
  src/lima2/client/pipelines/smx.py
35
35
  src/lima2/client/pipelines/xpcs.py
36
36
  src/lima2/master_file/generate.py
37
+ src/lima2/master_file/py.typed
37
38
  src/lima2/server/__init__.py
38
39
  src/lima2/server/control.py
39
40
  src/lima2/server/receiver.py
File without changes
File without changes
File without changes