lima2.client 4.0.7__tar.gz → 4.0.8__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 (62) hide show
  1. {lima2_client-4.0.7/src/lima2_client.egg-info → lima2_client-4.0.8}/PKG-INFO +1 -1
  2. {lima2_client-4.0.7 → lima2_client-4.0.8}/src/lima2/common/devencoded/dense_frame.py +4 -2
  3. {lima2_client-4.0.7 → lima2_client-4.0.8}/src/lima2/conductor/acquisition.py +138 -48
  4. {lima2_client-4.0.7 → lima2_client-4.0.8}/src/lima2/conductor/acquisition_system.py +265 -180
  5. lima2_client-4.0.8/src/lima2/conductor/detector.py +38 -0
  6. {lima2_client-4.0.7 → lima2_client-4.0.8}/src/lima2/conductor/processing/__init__.py +0 -22
  7. {lima2_client-4.0.7 → lima2_client-4.0.8}/src/lima2/conductor/processing/master_file.py +65 -36
  8. {lima2_client-4.0.7 → lima2_client-4.0.8}/src/lima2/conductor/processing/pipeline.py +98 -268
  9. {lima2_client-4.0.7 → lima2_client-4.0.8}/src/lima2/conductor/processing/reduced_data.py +82 -92
  10. lima2_client-4.0.8/src/lima2/conductor/tango/container.py +183 -0
  11. {lima2_client-4.0.7 → lima2_client-4.0.8}/src/lima2/conductor/tango/control.py +33 -9
  12. {lima2_client-4.0.7 → lima2_client-4.0.8}/src/lima2/conductor/tango/processing.py +1 -32
  13. {lima2_client-4.0.7 → lima2_client-4.0.8}/src/lima2/conductor/tango/receiver.py +34 -8
  14. {lima2_client-4.0.7 → lima2_client-4.0.8}/src/lima2/conductor/tango/utils.py +9 -1
  15. {lima2_client-4.0.7 → lima2_client-4.0.8}/src/lima2/conductor/utils.py +8 -4
  16. {lima2_client-4.0.7 → lima2_client-4.0.8}/src/lima2/conductor/webservice/acquisition.py +2 -2
  17. {lima2_client-4.0.7 → lima2_client-4.0.8}/src/lima2/conductor/webservice/pipeline.py +9 -15
  18. {lima2_client-4.0.7 → lima2_client-4.0.8}/src/lima2/conductor/webservice/utils.py +1 -1
  19. {lima2_client-4.0.7 → lima2_client-4.0.8}/src/lima2/conductor/webservice/webapp.py +7 -18
  20. {lima2_client-4.0.7 → lima2_client-4.0.8/src/lima2_client.egg-info}/PKG-INFO +1 -1
  21. {lima2_client-4.0.7 → lima2_client-4.0.8}/src/lima2_client.egg-info/SOURCES.txt +2 -0
  22. {lima2_client-4.0.7 → lima2_client-4.0.8}/LICENSE +0 -0
  23. {lima2_client-4.0.7 → lima2_client-4.0.8}/README.md +0 -0
  24. {lima2_client-4.0.7 → lima2_client-4.0.8}/pyproject.toml +0 -0
  25. {lima2_client-4.0.7 → lima2_client-4.0.8}/setup.cfg +0 -0
  26. {lima2_client-4.0.7 → lima2_client-4.0.8}/src/lima2/client/__init__.py +0 -0
  27. {lima2_client-4.0.7 → lima2_client-4.0.8}/src/lima2/client/exceptions.py +0 -0
  28. {lima2_client-4.0.7 → lima2_client-4.0.8}/src/lima2/client/py.typed +0 -0
  29. {lima2_client-4.0.7 → lima2_client-4.0.8}/src/lima2/client/services.py +0 -0
  30. {lima2_client-4.0.7 → lima2_client-4.0.8}/src/lima2/client/session.py +0 -0
  31. {lima2_client-4.0.7 → lima2_client-4.0.8}/src/lima2/client/shell.py +0 -0
  32. {lima2_client-4.0.7 → lima2_client-4.0.8}/src/lima2/common/__init__.py +0 -0
  33. {lima2_client-4.0.7 → lima2_client-4.0.8}/src/lima2/common/devencoded/__init__.py +0 -0
  34. {lima2_client-4.0.7 → lima2_client-4.0.8}/src/lima2/common/devencoded/exception.py +0 -0
  35. {lima2_client-4.0.7 → lima2_client-4.0.8}/src/lima2/common/devencoded/smx_sparse_frame.py +0 -0
  36. {lima2_client-4.0.7 → lima2_client-4.0.8}/src/lima2/common/devencoded/sparse_frame.py +0 -0
  37. {lima2_client-4.0.7 → lima2_client-4.0.8}/src/lima2/common/devencoded/structured_array.py +0 -0
  38. {lima2_client-4.0.7 → lima2_client-4.0.8}/src/lima2/common/exceptions.py +0 -0
  39. {lima2_client-4.0.7 → lima2_client-4.0.8}/src/lima2/common/pipelines.py +0 -0
  40. {lima2_client-4.0.7 → lima2_client-4.0.8}/src/lima2/common/progress_counter.py +0 -0
  41. {lima2_client-4.0.7 → lima2_client-4.0.8}/src/lima2/common/py.typed +0 -0
  42. {lima2_client-4.0.7 → lima2_client-4.0.8}/src/lima2/common/state.py +0 -0
  43. {lima2_client-4.0.7 → lima2_client-4.0.8}/src/lima2/common/types.py +0 -0
  44. {lima2_client-4.0.7 → lima2_client-4.0.8}/src/lima2/conductor/__init__.py +0 -0
  45. {lima2_client-4.0.7 → lima2_client-4.0.8}/src/lima2/conductor/processing/failing.py +0 -0
  46. {lima2_client-4.0.7 → lima2_client-4.0.8}/src/lima2/conductor/processing/legacy.py +0 -0
  47. {lima2_client-4.0.7 → lima2_client-4.0.8}/src/lima2/conductor/processing/smx.py +0 -0
  48. {lima2_client-4.0.7 → lima2_client-4.0.8}/src/lima2/conductor/processing/xpcs.py +0 -0
  49. {lima2_client-4.0.7 → lima2_client-4.0.8}/src/lima2/conductor/py.typed +0 -0
  50. {lima2_client-4.0.7 → lima2_client-4.0.8}/src/lima2/conductor/tango/__init__.py +0 -0
  51. {lima2_client-4.0.7 → lima2_client-4.0.8}/src/lima2/conductor/topology.py +0 -0
  52. {lima2_client-4.0.7 → lima2_client-4.0.8}/src/lima2/conductor/webservice/__init__.py +0 -0
  53. {lima2_client-4.0.7 → lima2_client-4.0.8}/src/lima2/conductor/webservice/benchmark.py +0 -0
  54. {lima2_client-4.0.7 → lima2_client-4.0.8}/src/lima2/conductor/webservice/benchmark_generators/__init__.py +0 -0
  55. {lima2_client-4.0.7 → lima2_client-4.0.8}/src/lima2/conductor/webservice/benchmark_generators/rois.py +0 -0
  56. {lima2_client-4.0.7 → lima2_client-4.0.8}/src/lima2/conductor/webservice/cli.py +0 -0
  57. {lima2_client-4.0.7 → lima2_client-4.0.8}/src/lima2/conductor/webservice/detector.py +0 -0
  58. {lima2_client-4.0.7 → lima2_client-4.0.8}/src/lima2/conductor/webservice/main.py +0 -0
  59. {lima2_client-4.0.7 → lima2_client-4.0.8}/src/lima2_client.egg-info/dependency_links.txt +0 -0
  60. {lima2_client-4.0.7 → lima2_client-4.0.8}/src/lima2_client.egg-info/entry_points.txt +0 -0
  61. {lima2_client-4.0.7 → lima2_client-4.0.8}/src/lima2_client.egg-info/requires.txt +0 -0
  62. {lima2_client-4.0.7 → lima2_client-4.0.8}/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: 4.0.7
3
+ Version: 4.0.8
4
4
  Summary: Python client library for Lima2 detector
5
5
  Author: BCU (ESRF)
6
6
  License-Expression: MIT
@@ -125,7 +125,7 @@ def decode(raw_data: EncodedFrame) -> Frame:
125
125
  except Exception:
126
126
  raise DevEncodedFormatNotSupported(
127
127
  "Image format from Lima Tango device not supported (found %s)." % data_type
128
- )
128
+ ) from None
129
129
  if endianness != 0:
130
130
  raise DevEncodedFormatNotSupported(
131
131
  "Unsupported endianness (found %s)." % endianness
@@ -139,7 +139,9 @@ def decode(raw_data: EncodedFrame) -> Frame:
139
139
  try:
140
140
  dtype = MODE_TO_NUMPY[mode]
141
141
  except Exception:
142
- raise DevEncodedFormatNotSupported("Data format %s is not supported" % mode)
142
+ raise DevEncodedFormatNotSupported(
143
+ "Data format %s is not supported" % mode
144
+ ) from None
143
145
 
144
146
  data = numpy.frombuffer(raw_data, offset=header_size, dtype=dtype)
145
147
  data.shape = dim3, dim2, dim1
@@ -10,15 +10,25 @@ Contains state logic for running acquisitions.
10
10
 
11
11
  import asyncio
12
12
  import logging
13
+ import time
13
14
  import traceback
15
+ import weakref
14
16
  from dataclasses import dataclass
15
17
  from enum import Enum, auto
16
18
  from typing import Awaitable, Callable
17
- from uuid import UUID
18
- import weakref
19
-
20
- from lima2.conductor.processing.pipeline import Pipeline
21
19
 
20
+ from lima2.common.exceptions import Lima2BackendError
21
+ from lima2.common.types import FrameInfo
22
+ from lima2.conductor.processing import pipeline_classes
23
+ from lima2.conductor.processing.master_file import (
24
+ FrameSavingChannel,
25
+ MasterFileMetadata,
26
+ )
27
+ from lima2.conductor.processing.reduced_data import RoiParams
28
+ from lima2.conductor.tango.container import TangoProcessingGroup
29
+ from lima2.conductor.tango.processing import ProcessingErrorEvent, TangoProcessing
30
+ from lima2.conductor.tango.utils import format_exception
31
+ from lima2.conductor.utils import naturaltime
22
32
 
23
33
  logger = logging.getLogger(__name__)
24
34
 
@@ -171,17 +181,6 @@ class AcquisitionState:
171
181
  await self.event_queue.put((event, fut))
172
182
  return fut
173
183
 
174
- async def process(self, event: AcquisitionEvent) -> None:
175
- """Enqueue an event and wait for it to be processed.
176
-
177
- If other events are already in the queue, they will be processed first.
178
-
179
- The returned future can be awaited to pause until the event has been
180
- processed.
181
- """
182
- fut = await self.enqueue(event=event)
183
- await fut
184
-
185
184
  async def _handle(self, event: AcquisitionEvent) -> list[AcquisitionEvent]:
186
185
  """Handle an event.
187
186
 
@@ -295,32 +294,58 @@ class AcquisitionState:
295
294
  f"Error while handling {evt}: {traceback.format_exc()}"
296
295
  )
297
296
  fut.set_exception(e)
298
- finally:
299
- # NOTE: break condition
300
- if evt is AcquisitionEvent.FINISHED:
301
- break
297
+
298
+ # NOTE: break condition
299
+ if evt is AcquisitionEvent.FINISHED:
300
+ break
302
301
 
303
302
 
304
303
  class Acquisition:
305
- """
306
- A prepared acquisition, listening to events from the control and receiver devices,
307
- and, optionally, from the processing devices.
304
+ """Prepared acquisition, ready to start.
305
+
306
+ Holds an internal state, a set of processing devices, and a Pipeline instance.
307
+
308
+ Listens to events from all devices in order to update its state and properly manage
309
+ local tasks.
308
310
  """
309
311
 
310
312
  def __init__(
311
313
  self,
312
- uuid: UUID,
313
- num_receivers: int,
314
+ processing_devices: TangoProcessingGroup,
314
315
  on_end: OnEndCallback,
315
316
  on_failure: OnFailureCallback,
317
+ num_frames: int,
318
+ frame_infos: dict[str, FrameInfo],
319
+ roi_params: RoiParams,
320
+ saving_channels: dict[str, FrameSavingChannel],
321
+ masterfile_metadata: MasterFileMetadata,
316
322
  ) -> None:
317
- self.uuid = uuid
323
+ self.uuid = processing_devices.uuid
318
324
  self.state = AcquisitionState(
319
- num_receivers=num_receivers,
325
+ num_receivers=len(processing_devices),
320
326
  on_end=on_end,
321
327
  on_failure=on_failure,
322
328
  )
323
- self.pipeline: Pipeline | None = None
329
+ self.proc_group = processing_devices
330
+ """Processing device group."""
331
+
332
+ # Instantiate a Pipeline
333
+ pipeline_class = pipeline_classes[self.proc_group.class_name]
334
+ self.pipeline = pipeline_class(
335
+ devices=processing_devices,
336
+ num_frames=num_frames,
337
+ frame_infos=frame_infos,
338
+ roi_params=roi_params,
339
+ saving_channels=saving_channels,
340
+ masterfile_metadata=masterfile_metadata,
341
+ )
342
+ """Processing pipeline."""
343
+
344
+ self.processing_errors: list[str] = []
345
+ """Holds processing error messages that occurred during the run, if any."""
346
+
347
+ self.done_devices: set[TangoProcessing] = set()
348
+ """Set of processing devices that have finished (successfully or not)."""
324
349
 
325
350
  def __del__(self) -> None:
326
351
  logger.debug(f"Acquisition instance {self.uuid} destroyed")
@@ -333,45 +358,110 @@ class Acquisition:
333
358
 
334
359
  def start(self) -> None:
335
360
  """Start the pipeline subtasks and listen for device events."""
336
- if self.pipeline is None:
337
- raise RuntimeError(f"Pipeline is not attached for acquisition {self.uuid}")
338
-
339
361
  self.pipeline.start()
340
362
  self.state.start()
341
363
 
342
- async def attach(self, pipeline: Pipeline) -> None:
343
- """Register handlers to a pipeline's events."""
344
- self.pipeline = pipeline
364
+ @staticmethod
365
+ async def attach(
366
+ processing_devices: TangoProcessingGroup,
367
+ on_end: OnEndCallback,
368
+ on_failure: OnFailureCallback,
369
+ num_frames: int,
370
+ frame_infos: dict[str, FrameInfo],
371
+ roi_params: RoiParams,
372
+ saving_channels: dict[str, FrameSavingChannel],
373
+ masterfile_metadata: MasterFileMetadata,
374
+ ) -> "Acquisition":
375
+ """
376
+ Instantiate a complete Acquisition instance listening to processing device
377
+ events.
378
+ """
379
+ instance = Acquisition(
380
+ processing_devices=processing_devices,
381
+ on_end=on_end,
382
+ on_failure=on_failure,
383
+ num_frames=num_frames,
384
+ frame_infos=frame_infos,
385
+ roi_params=roi_params,
386
+ saving_channels=saving_channels,
387
+ masterfile_metadata=masterfile_metadata,
388
+ )
389
+
390
+ await instance.listen()
391
+
392
+ return instance
393
+
394
+ def detach(self) -> None:
395
+ """Stop listening to events."""
396
+ self.state.detach()
397
+
398
+ async def notify(self, event: AcquisitionEvent) -> None:
399
+ """Enqueue an event in the AcquisitionState and return."""
400
+ await self.state.enqueue(event=event)
401
+
402
+ async def listen(self) -> None:
403
+ """Listen to processing device events to update the AcquisitionState.
404
+
405
+ Should be called just after instantiating the Acquisition instance.
406
+ """
345
407
 
346
408
  # NOTE: Using weakref in event handlers is necessary to allow the
347
- # garbage collector to destroy the Acquisition object.
409
+ # garbage collector to destroy the Pipeline object.
348
410
  # This is CRUCIAL for proper cleanup of the tango DeviceProxy
349
411
  # instances.
350
412
  # See https://gitlab.esrf.fr/limagroup/lima2-client/-/issues/90.
351
413
  wself = weakref.ref(self)
352
414
 
353
- async def on_pipeline_finished() -> None:
415
+ async def on_device_finished(device: TangoProcessing) -> None:
416
+ """Adds a processing device to the done_devices set.
417
+
418
+ When the set is complete, call the on_finished callback registered to
419
+ this pipeline instance (see constructor).
420
+ """
421
+ logger.info(f"Processing device {device.name} is done")
422
+
354
423
  self_ref = wself()
355
- if not self_ref:
424
+ if self_ref is None:
356
425
  return
426
+
357
427
  await self_ref.notify(event=AcquisitionEvent.PROC_DEVICE_DONE)
358
428
 
359
- async def on_pipeline_error() -> None:
429
+ self_ref.done_devices.add(device)
430
+ if self_ref.done_devices == set(self_ref.proc_group):
431
+ logger.info("Closing pipeline")
432
+ await self_ref.pipeline.close()
433
+
434
+ async def on_device_error(evt: ProcessingErrorEvent) -> None:
435
+ """Reports a processing error to the registered callback."""
360
436
  self_ref = wself()
361
- if not self_ref:
437
+ if self_ref is None:
362
438
  return
439
+
440
+ err_msg = format_exception(
441
+ where=evt.device_name,
442
+ when="while processing frames",
443
+ what=evt.what,
444
+ info=evt.info,
445
+ )
446
+ logger.warning(err_msg)
447
+ self_ref.processing_errors.append(err_msg)
448
+
363
449
  await self_ref.notify(event=AcquisitionEvent.PROC_DEVICE_FAILED)
364
450
 
365
- await self.pipeline.attach(
366
- on_finished=on_pipeline_finished, on_error=on_pipeline_error
451
+ reg_t0 = time.perf_counter()
452
+
453
+ results = await self.proc_group.listen(
454
+ on_finished=on_device_finished, on_error=on_device_error
367
455
  )
368
456
 
369
- def detach(self) -> None:
370
- """Stop listening to events."""
371
- self.state.detach()
372
- if self.pipeline is not None:
373
- self.pipeline.unregister()
457
+ errors = [res for res in results if res is not None]
458
+ if len(errors) > 0:
459
+ raise Lima2BackendError(
460
+ "Couldn't register event handlers on some processing devices:\n- "
461
+ + "\n- ".join([str(err) for err in errors])
462
+ )
374
463
 
375
- async def notify(self, event: AcquisitionEvent) -> None:
376
- """Enqueue an event in the AcquisitionState and return."""
377
- await self.state.enqueue(event=event)
464
+ logger.info(
465
+ f"Registered to processing device events in "
466
+ f"{naturaltime(time.perf_counter() - reg_t0)}"
467
+ )