lima2.client 4.1.8__tar.gz → 4.2.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-4.1.8/src/lima2_client.egg-info → lima2_client-4.2.0}/PKG-INFO +1 -1
- {lima2_client-4.1.8 → lima2_client-4.2.0}/conda/recipe.yaml +3 -0
- lima2_client-4.2.0/docs/assets/lima2-conductor-monitor-webpage.png +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/docs/features.md +12 -3
- lima2_client-4.2.0/docs/monitor.md +12 -0
- lima2_client-4.2.0/src/lima2/client/_utils.py +132 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/src/lima2/client/services.py +78 -40
- lima2_client-4.2.0/src/lima2/common/devencoded/common.py +26 -0
- lima2_client-4.2.0/src/lima2/common/devencoded/data_array.py +208 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/src/lima2/common/devencoded/smx_sparse_frame.py +3 -4
- {lima2_client-4.1.8 → lima2_client-4.2.0}/src/lima2/common/devencoded/sparse_frame.py +6 -4
- {lima2_client-4.1.8 → lima2_client-4.2.0}/src/lima2/common/devencoded/structured_array.py +1 -1
- {lima2_client-4.1.8 → lima2_client-4.2.0}/src/lima2/common/pipelines.py +24 -1
- {lima2_client-4.1.8 → lima2_client-4.2.0}/src/lima2/common/types.py +22 -12
- {lima2_client-4.1.8 → lima2_client-4.2.0}/src/lima2/conductor/acquisition.py +14 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/src/lima2/conductor/acquisition_system.py +77 -20
- {lima2_client-4.1.8 → lima2_client-4.2.0}/src/lima2/conductor/detector.py +0 -2
- {lima2_client-4.1.8 → lima2_client-4.2.0}/src/lima2/conductor/processing/__init__.py +2 -0
- lima2_client-4.2.0/src/lima2/conductor/processing/fai.py +75 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/src/lima2/conductor/processing/master_file.py +64 -32
- {lima2_client-4.1.8 → lima2_client-4.2.0}/src/lima2/conductor/processing/pipeline.py +142 -10
- {lima2_client-4.1.8 → lima2_client-4.2.0}/src/lima2/conductor/processing/reduced_data.py +2 -5
- {lima2_client-4.1.8 → lima2_client-4.2.0}/src/lima2/conductor/processing/xpcs.py +0 -1
- {lima2_client-4.1.8 → lima2_client-4.2.0}/src/lima2/conductor/tango/container.py +3 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/src/lima2/conductor/tango/control.py +1 -1
- {lima2_client-4.1.8 → lima2_client-4.2.0}/src/lima2/conductor/tango/processing.py +13 -1
- {lima2_client-4.1.8 → lima2_client-4.2.0}/src/lima2/conductor/tango/receiver.py +1 -1
- {lima2_client-4.1.8 → lima2_client-4.2.0}/src/lima2/conductor/tango/utils.py +1 -2
- {lima2_client-4.1.8 → lima2_client-4.2.0}/src/lima2/conductor/topology.py +2 -2
- {lima2_client-4.1.8 → lima2_client-4.2.0}/src/lima2/conductor/webservice/acquisition.py +28 -21
- {lima2_client-4.1.8 → lima2_client-4.2.0}/src/lima2/conductor/webservice/benchmark.py +3 -3
- {lima2_client-4.1.8 → lima2_client-4.2.0}/src/lima2/conductor/webservice/detector.py +17 -6
- lima2_client-4.2.0/src/lima2/conductor/webservice/monitor/favicon.ico +0 -0
- lima2_client-4.2.0/src/lima2/conductor/webservice/monitor/fonts/Roboto-Italic.woff2 +0 -0
- lima2_client-4.2.0/src/lima2/conductor/webservice/monitor/fonts/Roboto-Medium.woff2 +0 -0
- lima2_client-4.2.0/src/lima2/conductor/webservice/monitor/fonts/Roboto-MediumItalic.woff2 +0 -0
- lima2_client-4.2.0/src/lima2/conductor/webservice/monitor/fonts/Roboto-Regular.woff2 +0 -0
- lima2_client-4.2.0/src/lima2/conductor/webservice/monitor/fonts.css +43 -0
- lima2_client-4.2.0/src/lima2/conductor/webservice/monitor/index.html +288 -0
- lima2_client-4.2.0/src/lima2/conductor/webservice/monitor/milligram.css +633 -0
- lima2_client-4.2.0/src/lima2/conductor/webservice/monitor/normalize.css +350 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/src/lima2/conductor/webservice/pipeline.py +108 -28
- {lima2_client-4.1.8 → lima2_client-4.2.0}/src/lima2/conductor/webservice/webapp.py +105 -30
- {lima2_client-4.1.8 → lima2_client-4.2.0/src/lima2_client.egg-info}/PKG-INFO +1 -1
- {lima2_client-4.1.8 → lima2_client-4.2.0}/src/lima2_client.egg-info/SOURCES.txt +16 -2
- {lima2_client-4.1.8 → lima2_client-4.2.0}/tests/integration/conftest.py +20 -1
- {lima2_client-4.1.8 → lima2_client-4.2.0}/tests/integration/test_failing.py +4 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/tests/integration/test_legacy.py +170 -69
- {lima2_client-4.1.8 → lima2_client-4.2.0}/tests/unit/client/test_pipeline.py +94 -45
- lima2_client-4.2.0/tests/unit/client/test_utils.py +160 -0
- lima2_client-4.1.8/tests/unit/common/test_dense_frame.py → lima2_client-4.2.0/tests/unit/common/test_data_array.py +69 -34
- {lima2_client-4.1.8 → lima2_client-4.2.0}/tests/unit/conductor/mock_devices.py +18 -13
- {lima2_client-4.1.8 → lima2_client-4.2.0}/tests/unit/conductor/test_acquisition.py +4 -3
- {lima2_client-4.1.8 → lima2_client-4.2.0}/tests/unit/conductor/test_acquisition_system.py +74 -16
- {lima2_client-4.1.8 → lima2_client-4.2.0}/tests/unit/conductor/test_master_file.py +30 -1
- {lima2_client-4.1.8 → lima2_client-4.2.0}/tests/unit/conductor/test_pipeline.py +159 -27
- {lima2_client-4.1.8 → lima2_client-4.2.0}/tests/unit/conductor/test_processing.py +1 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/tests/unit/conductor/test_reduced_data.py +1 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/tests/unit/conductor/test_tango_devices.py +60 -3
- {lima2_client-4.1.8 → lima2_client-4.2.0}/tests/unit/conductor/test_topology.py +2 -1
- {lima2_client-4.1.8 → lima2_client-4.2.0}/tests/unit/conductor/webservice/test_acquisition.py +14 -1
- {lima2_client-4.1.8 → lima2_client-4.2.0}/tests/unit/conductor/webservice/test_pipelines.py +86 -10
- {lima2_client-4.1.8 → lima2_client-4.2.0}/tests/unit/conductor/webservice/test_webapp.py +24 -2
- lima2_client-4.1.8/docs/monitor.md +0 -9
- lima2_client-4.1.8/src/lima2/common/devencoded/dense_frame.py +0 -153
- {lima2_client-4.1.8 → lima2_client-4.2.0}/.gitignore +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/.gitlab-ci.yml +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/LICENSE +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/README.md +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/docs/assets/lima2-components-and-repos.svg +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/docs/assets/lima2-conductor-communication-model.svg +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/docs/assets/lima2-conductor-modules.svg +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/docs/assets/logo-transparent-crop.png +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/docs/concepts.md +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/docs/contact.md +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/docs/dev/conductor_client.md +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/docs/dev/conductor_design.md +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/docs/dev/conductor_requirements.md +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/docs/dev/index.md +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/docs/getting_started.md +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/docs/index.md +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/docs/license.md +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/docs/reference/acquisition.md +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/docs/reference/detector.md +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/docs/reference/exceptions.md +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/docs/reference/index.md +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/docs/reference/pipeline.md +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/docs/reference/services.md +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/docs/reference/session.md +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/docs/release_notes.md +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/docs/shell.md +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/flake8.ini +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/mkdocs.yml +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/pyproject.toml +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/scripts/cleanup_processing_ds.py +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/setup.cfg +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/src/lima2/client/__init__.py +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/src/lima2/client/exceptions.py +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/src/lima2/client/py.typed +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/src/lima2/client/session.py +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/src/lima2/client/shell.py +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/src/lima2/common/__init__.py +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/src/lima2/common/devencoded/__init__.py +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/src/lima2/common/devencoded/exception.py +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/src/lima2/common/exceptions.py +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/src/lima2/common/progress_counter.py +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/src/lima2/common/py.typed +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/src/lima2/common/state.py +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/src/lima2/conductor/__init__.py +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/src/lima2/conductor/processing/failing.py +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/src/lima2/conductor/processing/legacy.py +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/src/lima2/conductor/processing/smx.py +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/src/lima2/conductor/py.typed +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/src/lima2/conductor/tango/__init__.py +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/src/lima2/conductor/utils.py +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/src/lima2/conductor/webservice/__init__.py +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/src/lima2/conductor/webservice/benchmark_generators/__init__.py +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/src/lima2/conductor/webservice/benchmark_generators/rois.py +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/src/lima2/conductor/webservice/cli.py +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/src/lima2/conductor/webservice/main.py +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/src/lima2/conductor/webservice/utils.py +1 -1
- {lima2_client-4.1.8 → lima2_client-4.2.0}/src/lima2_client.egg-info/dependency_links.txt +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/src/lima2_client.egg-info/entry_points.txt +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/src/lima2_client.egg-info/requires.txt +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/src/lima2_client.egg-info/top_level.txt +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/tests/__init__.py +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/tests/integration/__init__.py +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/tests/integration/tango_db/simulator_ctl.yaml +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/tests/integration/tango_db/simulator_rcv1.yaml +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/tests/integration/tango_db/simulator_rcv2.yaml +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/tests/unit/__init__.py +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/tests/unit/client/__init__.py +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/tests/unit/client/conftest.py +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/tests/unit/client/test_acquisition.py +1 -1
- {lima2_client-4.1.8 → lima2_client-4.2.0}/tests/unit/client/test_detector.py +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/tests/unit/client/test_session.py +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/tests/unit/common/__init__.py +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/tests/unit/common/test_progress_counter.py +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/tests/unit/common/test_smx_sparse_frame.py +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/tests/unit/common/test_sparse_frame.py +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/tests/unit/common/test_state.py +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/tests/unit/conductor/__init__.py +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/tests/unit/conductor/conftest.py +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/tests/unit/conductor/mock_devencoded.py +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/tests/unit/conductor/test_utils.py +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/tests/unit/conductor/webservice/__init__.py +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/tests/unit/conductor/webservice/test_benchmark.py +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/tests/unit/conductor/webservice/test_detector.py +0 -0
- {lima2_client-4.1.8 → lima2_client-4.2.0}/tests/unit/conductor/webservice/test_utils.py +0 -0
|
Binary file
|
|
@@ -32,9 +32,18 @@ Both modes have a prefetched variant, where the frames are prefetched in memory
|
|
|
32
32
|
|
|
33
33
|
Lima2 implements the following processing pipelines:
|
|
34
34
|
|
|
35
|
-
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
- **Classic (aka Legacy)**: a general-purpose, CPU-based pipeline that can perform
|
|
36
|
+
standard image operations (cropping, rotation, background subtraction, etc.) as well
|
|
37
|
+
as [ROI statistics and profiles](./concepts.md#region-of-interest-roi).
|
|
38
|
+
- **FAI (Fast Azimuthal Integration)**: specialized to process diffraction patterns, FAI
|
|
39
|
+
runs an online azimuthal integration step on GPU to efficiently find rings in
|
|
40
|
+
circularly-symmetric images.
|
|
41
|
+
- [**Smx (Serial Macromolecular Crystallography)**](https://www.esrf.fr/id29): an
|
|
42
|
+
extended FAI pipeline, which additionally performs peak finding and sparsification after the
|
|
43
|
+
azimuthal integration step.
|
|
44
|
+
- [**XPCS (X-ray Photon Correlation
|
|
45
|
+
Spectroscopy)**](https://www.esrf.fr/UsersAndScience/Experiments/CBS/ID10): a pipeline
|
|
46
|
+
dedicated to extracting temporal correlations from the images.
|
|
38
47
|
|
|
39
48
|
Naturally, depending on the chosen processing type, the kinds of frames and
|
|
40
49
|
[reduced data](./concepts.md#reduced-data) available to consumers changes.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Status monitor
|
|
2
|
+
|
|
3
|
+
The conductor serves a status monitoring webpage at its root endpoint ("/"). This page
|
|
4
|
+
can be accessed in any browser, using the conductor's hostname and port.
|
|
5
|
+
|
|
6
|
+
As long as the conductor is running, the page will periodically poll to update the
|
|
7
|
+
displayed information.
|
|
8
|
+
|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
This page is mainly aimed at Lima2 administrators, and can be used to get an overview of
|
|
12
|
+
the system's state (detector, receivers, ...).
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# This file is part of the Lima2 project
|
|
2
|
+
#
|
|
3
|
+
# Copyright (c) 2020-2024 Beamline Control Unit, ESRF
|
|
4
|
+
# Distributed under the MIT licence. See LICENSE for more info.
|
|
5
|
+
|
|
6
|
+
"""Lima2 client private utility functions."""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
|
|
10
|
+
import h5py
|
|
11
|
+
import tango as tg
|
|
12
|
+
|
|
13
|
+
from lima2.common import types
|
|
14
|
+
from lima2.common.devencoded.common import Frame
|
|
15
|
+
from lima2.common.devencoded.data_array import Vector
|
|
16
|
+
from lima2.common.devencoded.smx_sparse_frame import SmxSparseFrame
|
|
17
|
+
from lima2.common.devencoded.sparse_frame import SparseFrame
|
|
18
|
+
from lima2.common.types import FrameType
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def tango_get_frame(
|
|
24
|
+
url: str,
|
|
25
|
+
command_name: str,
|
|
26
|
+
lookup_idx: int,
|
|
27
|
+
) -> Frame | Vector | SparseFrame | SmxSparseFrame:
|
|
28
|
+
"""Fetch a frame from a Lima2 receiver over tango, and decode it."""
|
|
29
|
+
|
|
30
|
+
device = tg.DeviceProxy(url)
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
raw_data: tuple[str, bytes] = device.command_inout(
|
|
34
|
+
name=command_name, cmd_param=lookup_idx
|
|
35
|
+
)
|
|
36
|
+
except tg.DevFailed as e:
|
|
37
|
+
logger.error(
|
|
38
|
+
f"Call to {command_name}({lookup_idx}) on {device.dev_name()} failed"
|
|
39
|
+
)
|
|
40
|
+
raise RuntimeError(
|
|
41
|
+
f"Unable to get frame {lookup_idx} from {device.dev_name()}:\n{e}"
|
|
42
|
+
) from e
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
type_id, payload = raw_data
|
|
46
|
+
except ValueError:
|
|
47
|
+
raise RuntimeError(
|
|
48
|
+
f"Unexpected data received from Lima2 device: {raw_data!r}"
|
|
49
|
+
) from None
|
|
50
|
+
|
|
51
|
+
if type_id not in types.decoder_by_id:
|
|
52
|
+
raise NotImplementedError(f"No decoder for '{type_id}'")
|
|
53
|
+
|
|
54
|
+
decoder = types.decoder_by_id[type_id]
|
|
55
|
+
|
|
56
|
+
return decoder(payload)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def hdf5_get_frame(
|
|
60
|
+
url: str,
|
|
61
|
+
frame_idx: int,
|
|
62
|
+
lookup_idx: int,
|
|
63
|
+
frame_type: FrameType,
|
|
64
|
+
shape: tuple[int, ...],
|
|
65
|
+
) -> Frame | Vector | SparseFrame | SmxSparseFrame:
|
|
66
|
+
"""Fetch a frame from an HDF5 file."""
|
|
67
|
+
try:
|
|
68
|
+
filepath, datapath = url.removeprefix("file://").split(":")
|
|
69
|
+
except ValueError as e:
|
|
70
|
+
raise RuntimeError(
|
|
71
|
+
f"Invalid hdf5 url format: '{url}'. "
|
|
72
|
+
f"Expected 'file://file/path.h5:data/path"
|
|
73
|
+
) from e
|
|
74
|
+
|
|
75
|
+
frm: Frame | Vector | SparseFrame | SmxSparseFrame
|
|
76
|
+
|
|
77
|
+
with h5py.File(filepath, "r") as f:
|
|
78
|
+
if frame_type == FrameType.DENSE:
|
|
79
|
+
try:
|
|
80
|
+
frm_data = f[f"{datapath}/data"][lookup_idx]
|
|
81
|
+
except IndexError as e:
|
|
82
|
+
raise RuntimeError(
|
|
83
|
+
f"Can't access element {lookup_idx} at {url}."
|
|
84
|
+
) from e
|
|
85
|
+
frm = Frame(data=frm_data, idx=frame_idx)
|
|
86
|
+
|
|
87
|
+
elif frame_type == FrameType.SPARSE:
|
|
88
|
+
try:
|
|
89
|
+
index = f[f"{datapath}/index"][lookup_idx]
|
|
90
|
+
intensity = f[f"{datapath}/intensity"][lookup_idx]
|
|
91
|
+
except IndexError as e:
|
|
92
|
+
raise RuntimeError(
|
|
93
|
+
f"Can't access element {lookup_idx} at {url}."
|
|
94
|
+
) from e
|
|
95
|
+
frm = SparseFrame(
|
|
96
|
+
index=index, intensity=intensity, idx=frame_idx, shape=shape
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
elif frame_type == FrameType.SMX_SPARSE:
|
|
100
|
+
try:
|
|
101
|
+
index = f[f"{datapath}/index"][lookup_idx]
|
|
102
|
+
intensity = f[f"{datapath}/intensity"][lookup_idx]
|
|
103
|
+
background_avg = f[f"{datapath}/background_avg"][lookup_idx]
|
|
104
|
+
background_std = f[f"{datapath}/background_std"][lookup_idx]
|
|
105
|
+
except IndexError as e:
|
|
106
|
+
raise RuntimeError(
|
|
107
|
+
f"Can't access element {lookup_idx} at {url}."
|
|
108
|
+
) from e
|
|
109
|
+
|
|
110
|
+
frm = SmxSparseFrame(
|
|
111
|
+
index=index,
|
|
112
|
+
intensity=intensity,
|
|
113
|
+
background_avg=background_avg,
|
|
114
|
+
background_std=background_std,
|
|
115
|
+
idx=frame_idx,
|
|
116
|
+
shape=shape,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
elif frame_type == FrameType.VECTOR:
|
|
120
|
+
try:
|
|
121
|
+
vector_data = f[f"{datapath}/data"][lookup_idx]
|
|
122
|
+
except IndexError as e:
|
|
123
|
+
raise RuntimeError(
|
|
124
|
+
f"Can't access element {lookup_idx} at {url}."
|
|
125
|
+
) from e
|
|
126
|
+
|
|
127
|
+
frm = Vector(data=vector_data, idx=frame_idx)
|
|
128
|
+
|
|
129
|
+
else:
|
|
130
|
+
raise NotImplementedError(repr(frame_type))
|
|
131
|
+
|
|
132
|
+
return frm
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
"""Conductor HTTP services wrapped into functions."""
|
|
7
7
|
|
|
8
8
|
import logging
|
|
9
|
+
import warnings
|
|
9
10
|
from dataclasses import dataclass
|
|
10
11
|
from enum import Enum, auto
|
|
11
12
|
from importlib.metadata import version
|
|
@@ -14,19 +15,19 @@ from uuid import UUID
|
|
|
14
15
|
|
|
15
16
|
import numpy as np
|
|
16
17
|
import numpy.typing as npt
|
|
17
|
-
import tango as tg
|
|
18
18
|
from packaging.version import Version
|
|
19
19
|
|
|
20
|
+
from lima2.client import _utils
|
|
20
21
|
from lima2.client.exceptions import ConductorConnectionError, ConductorEndpointNotFound
|
|
21
22
|
from lima2.client.session import ConductorSession
|
|
22
|
-
from lima2.common import
|
|
23
|
-
from lima2.common.devencoded.
|
|
23
|
+
from lima2.common.devencoded.common import Frame
|
|
24
|
+
from lima2.common.devencoded.data_array import Vector
|
|
24
25
|
from lima2.common.devencoded.smx_sparse_frame import SmxSparseFrame
|
|
25
26
|
from lima2.common.devencoded.sparse_frame import SparseFrame
|
|
26
|
-
from lima2.common.exceptions import Lima2LookupError, Lima2NotFound
|
|
27
|
+
from lima2.common.exceptions import Lima2LookupError, Lima2NotFound, Lima2ValueError
|
|
27
28
|
from lima2.common.progress_counter import ProgressCounter, SingleCounter
|
|
28
29
|
from lima2.common.state import RunState, State
|
|
29
|
-
from lima2.common.types import FrameInfo
|
|
30
|
+
from lima2.common.types import FrameInfo, FrameType
|
|
30
31
|
|
|
31
32
|
logger = logging.getLogger(__name__)
|
|
32
33
|
|
|
@@ -43,14 +44,19 @@ class Acquisition:
|
|
|
43
44
|
control: dict[str, Any],
|
|
44
45
|
receiver: dict[str, Any],
|
|
45
46
|
processing: dict[str, Any],
|
|
47
|
+
masterfile_metadata: dict[str, Any] | None = None,
|
|
46
48
|
) -> UUID:
|
|
47
49
|
"""Prepare for a new acquisition given control, receiver and processing params."""
|
|
50
|
+
if masterfile_metadata is None:
|
|
51
|
+
masterfile_metadata = {}
|
|
52
|
+
|
|
48
53
|
uuid_str = self.session.post(
|
|
49
54
|
"/acquisition/prepare",
|
|
50
55
|
json={
|
|
51
56
|
"control": control,
|
|
52
57
|
"receiver": receiver,
|
|
53
58
|
"processing": processing,
|
|
59
|
+
"masterfile_metadata": masterfile_metadata,
|
|
54
60
|
},
|
|
55
61
|
).json()
|
|
56
62
|
return UUID(uuid_str)
|
|
@@ -334,58 +340,90 @@ class Pipeline:
|
|
|
334
340
|
def lookup(self, frame_idx: int, uuid: str | UUID = "current") -> tuple[int, str]:
|
|
335
341
|
"""Look up a frame by index.
|
|
336
342
|
|
|
343
|
+
Warning: Deprecated
|
|
344
|
+
This function will be removed in a future release.
|
|
345
|
+
|
|
337
346
|
Returns a tuple (frame index, receiver url).
|
|
338
347
|
"""
|
|
348
|
+
warnings.warn(
|
|
349
|
+
"This function is deprecated and will be removed in a future release.",
|
|
350
|
+
stacklevel=2,
|
|
351
|
+
)
|
|
339
352
|
json = self.session.get(f"/pipeline/{str(uuid)}/lookup/{frame_idx}").json()
|
|
340
353
|
|
|
341
354
|
return (json["frame_idx"], json["receiver_url"])
|
|
342
355
|
|
|
343
356
|
def get_frame(
|
|
344
357
|
self, frame_idx: int, source: str, uuid: str | UUID = "current"
|
|
345
|
-
) -> Frame | SparseFrame | SmxSparseFrame:
|
|
346
|
-
"""Fetch a frame
|
|
358
|
+
) -> Frame | SparseFrame | SmxSparseFrame | Vector:
|
|
359
|
+
"""Fetch a frame by absolute index.
|
|
347
360
|
|
|
348
|
-
|
|
349
|
-
ValueError: Invalid frame source.
|
|
350
|
-
RuntimeError: The frame isn't available.
|
|
351
|
-
"""
|
|
352
|
-
try:
|
|
353
|
-
fidx, rcv_url = self.lookup(frame_idx=frame_idx, uuid=uuid)
|
|
354
|
-
except Lima2LookupError as e:
|
|
355
|
-
raise RuntimeError(f"Can't find {source} {frame_idx}: {e}") from None
|
|
361
|
+
If frame_idx == -1, returns the most recent frame acquired.
|
|
356
362
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
except KeyError as e:
|
|
361
|
-
raise NotImplementedError(class_name) from e
|
|
363
|
+
Requires a source name, which depends on the type of processing pipeline.
|
|
364
|
+
Valid source names can be queried with
|
|
365
|
+
[`frame_channels()`][lima2.client.services.Pipeline.frame_channels].
|
|
362
366
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
except KeyError as e:
|
|
366
|
-
raise ValueError(
|
|
367
|
-
f"Invalid frame source '{source}' for pipeline {class_name}."
|
|
368
|
-
) from e
|
|
367
|
+
This function will first make a lookup request to determine the frame's
|
|
368
|
+
location, which could be either on disk or inside a receiver's memory.
|
|
369
369
|
|
|
370
|
-
|
|
370
|
+
NOTE: some types of frames cannot be accessed online since they aren't buffered
|
|
371
|
+
in memory, e.g. raw frames. Such a frame will become available once the file it
|
|
372
|
+
belongs to has been closed.
|
|
371
373
|
|
|
372
|
-
|
|
374
|
+
On the other hand, some types of frames are only available from memory buffers,
|
|
375
|
+
and are never saved, e.g. "input frames". Such a frame will no longer be
|
|
376
|
+
available when the (circular) buffer that contains it has wrapped around.
|
|
373
377
|
|
|
378
|
+
Raises:
|
|
379
|
+
ValueError: Invalid frame source.
|
|
380
|
+
RuntimeError: The frame can't be retrieved.
|
|
381
|
+
"""
|
|
374
382
|
try:
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
383
|
+
lookup_result = self.session.get(
|
|
384
|
+
f"/pipeline/{str(uuid)}/lookup/{source}/{frame_idx}"
|
|
385
|
+
).json()
|
|
386
|
+
except Lima2ValueError as e:
|
|
387
|
+
raise ValueError(f"{e}") from None
|
|
388
|
+
except Lima2LookupError as e:
|
|
389
|
+
raise RuntimeError(f"{e}") from None
|
|
390
|
+
|
|
391
|
+
h5_loc = lookup_result["hdf5"]
|
|
392
|
+
buffer_loc = lookup_result["tango"]
|
|
393
|
+
|
|
394
|
+
if h5_loc is not None:
|
|
395
|
+
try:
|
|
396
|
+
return _utils.hdf5_get_frame(
|
|
397
|
+
url=h5_loc["url"],
|
|
398
|
+
frame_idx=frame_idx,
|
|
399
|
+
lookup_idx=h5_loc["idx"],
|
|
400
|
+
frame_type=FrameType[h5_loc["frame_type"]],
|
|
401
|
+
shape=tuple(h5_loc["shape"]),
|
|
402
|
+
)
|
|
403
|
+
except OSError as e:
|
|
404
|
+
# We cannot be too specific with the exception type here,
|
|
405
|
+
# see https://docs.h5py.org/en/stable/faq.html#exceptions.
|
|
406
|
+
|
|
407
|
+
# The file is likely still open for writing.
|
|
408
|
+
# Absorb the IO error and fall through to try online access.
|
|
409
|
+
logger.debug(
|
|
410
|
+
f"Can't read frame from HDF5 file {h5_loc} (error: {e}) "
|
|
411
|
+
f"-> falling back to tango access"
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
if buffer_loc is not None:
|
|
415
|
+
return _utils.tango_get_frame(
|
|
416
|
+
url=buffer_loc["url"],
|
|
417
|
+
command_name=buffer_loc["command_name"],
|
|
418
|
+
lookup_idx=buffer_loc["idx"],
|
|
379
419
|
)
|
|
380
|
-
raise RuntimeError(
|
|
381
|
-
f"Unable to get frame {fidx} from {device.dev_name()}:\n{e}"
|
|
382
|
-
) from e
|
|
383
|
-
|
|
384
|
-
decoder = types.decoder_by_type[frame_source.frame_type]
|
|
385
420
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
421
|
+
# NOTE: if the frame source isn't buffered AND it hasn't been saved to disk,
|
|
422
|
+
# we end up with no way to fetch it.
|
|
423
|
+
raise RuntimeError(
|
|
424
|
+
f"Requested {source} {frame_idx} currently has no known location "
|
|
425
|
+
f"in either memory or disk."
|
|
426
|
+
)
|
|
389
427
|
|
|
390
428
|
|
|
391
429
|
class ConnectionState(Enum):
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# This file is part of the Lima2 project
|
|
2
|
+
#
|
|
3
|
+
# Copyright (c) 2020-2024 Beamline Control Unit, ESRF
|
|
4
|
+
# Distributed under the MIT license. See LICENSE for more info.
|
|
5
|
+
|
|
6
|
+
"""Definitions used by multiple DevEncoded deserialization modules."""
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
import numpy.typing as npt
|
|
12
|
+
|
|
13
|
+
DevEncoded = bytes | tuple[str, bytes]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class Frame:
|
|
18
|
+
"""
|
|
19
|
+
Provide data frame from Lima including few metadata
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
data: npt.NDArray[Any]
|
|
23
|
+
"""Data of the frame"""
|
|
24
|
+
|
|
25
|
+
idx: int
|
|
26
|
+
"""Index of the frame where 0 is the first frame"""
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# This file is part of the Lima2 project
|
|
2
|
+
#
|
|
3
|
+
# Copyright (c) 2020-2024 Beamline Control Unit, ESRF
|
|
4
|
+
# Distributed under the MIT license. See LICENSE for more info.
|
|
5
|
+
|
|
6
|
+
"""Decoding of DevEncoded data in the DATA_ARRAY format.
|
|
7
|
+
|
|
8
|
+
This includes multiple categories of payloads, for instance dense frames (2D) and
|
|
9
|
+
vectors (1D).
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import struct
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from enum import IntEnum
|
|
15
|
+
from typing import Any, TypeVar
|
|
16
|
+
|
|
17
|
+
import numpy as np
|
|
18
|
+
import numpy.typing as npt
|
|
19
|
+
|
|
20
|
+
from lima2.common.devencoded.common import DevEncoded, Frame
|
|
21
|
+
from lima2.common.devencoded.exception import DevEncodedFormatNotSupported
|
|
22
|
+
|
|
23
|
+
T = TypeVar("T", bound=np.generic)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class DataArrayCategory(IntEnum):
|
|
27
|
+
"""Analogue to lima::tango::category_enum. Values must match."""
|
|
28
|
+
|
|
29
|
+
SCALAR_STACK = 0
|
|
30
|
+
SPECTRUM = 1
|
|
31
|
+
IMAGE = 2
|
|
32
|
+
SPECTRUM_STACK = 3
|
|
33
|
+
IMAGE_STACK = 4
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class DataArrayDType(IntEnum):
|
|
37
|
+
"""Analogue to lima::tango::data_type_enum. Values must match."""
|
|
38
|
+
|
|
39
|
+
UINT8 = 0
|
|
40
|
+
UINT16 = 1
|
|
41
|
+
UINT32 = 2
|
|
42
|
+
UINT64 = 3
|
|
43
|
+
INT8 = 4
|
|
44
|
+
INT16 = 5
|
|
45
|
+
INT32 = 6
|
|
46
|
+
INT64 = 7
|
|
47
|
+
FLOAT32 = 8
|
|
48
|
+
FLOAT64 = 9
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# Mapping used for direct conversion from raw data to numpy array
|
|
52
|
+
DTYPE_TO_NUMPY: dict[DataArrayDType, type] = {
|
|
53
|
+
DataArrayDType.UINT8: np.uint8,
|
|
54
|
+
DataArrayDType.UINT16: np.uint16,
|
|
55
|
+
DataArrayDType.UINT32: np.uint32,
|
|
56
|
+
DataArrayDType.UINT64: np.uint64,
|
|
57
|
+
DataArrayDType.INT8: np.int8,
|
|
58
|
+
DataArrayDType.INT16: np.int16,
|
|
59
|
+
DataArrayDType.INT32: np.int32,
|
|
60
|
+
DataArrayDType.INT64: np.int64,
|
|
61
|
+
DataArrayDType.FLOAT32: np.float32,
|
|
62
|
+
DataArrayDType.FLOAT64: np.float64,
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
HEADER_FORMAT = "<IHHIIHHHHHHHHIIIIIIQ"
|
|
66
|
+
MAGIC = struct.unpack(">I", b"DTAY")[0]
|
|
67
|
+
HEADER_SIZE = struct.calcsize(HEADER_FORMAT)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@dataclass
|
|
71
|
+
class DataArrayHeader:
|
|
72
|
+
magic: int
|
|
73
|
+
version: int
|
|
74
|
+
header_size: int
|
|
75
|
+
category: int
|
|
76
|
+
data_type: int
|
|
77
|
+
endianness: int
|
|
78
|
+
nb_dim: int
|
|
79
|
+
dim1: int
|
|
80
|
+
dim2: int
|
|
81
|
+
dim3: int
|
|
82
|
+
dim4: int
|
|
83
|
+
dim5: int
|
|
84
|
+
dim6: int
|
|
85
|
+
dim_step1: int
|
|
86
|
+
dim_step2: int
|
|
87
|
+
dim_step3: int
|
|
88
|
+
dim_step4: int
|
|
89
|
+
dim_step5: int
|
|
90
|
+
dim_step6: int
|
|
91
|
+
frame_idx: int
|
|
92
|
+
|
|
93
|
+
@staticmethod
|
|
94
|
+
def from_bytes(raw: bytes) -> "DataArrayHeader":
|
|
95
|
+
return DataArrayHeader(*struct.unpack_from(HEADER_FORMAT, raw))
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def unpack_frame(
|
|
99
|
+
payload: bytes, shape: tuple[int, int, int], dtype: np.dtype[T], frame_idx: int
|
|
100
|
+
) -> Frame:
|
|
101
|
+
"""Instantiate a Frame from a data array header and payload."""
|
|
102
|
+
|
|
103
|
+
data = np.frombuffer(payload, dtype=dtype).reshape(shape)
|
|
104
|
+
|
|
105
|
+
# Create a memory copy only if it is needed
|
|
106
|
+
if not data.flags.writeable:
|
|
107
|
+
data = np.array(data)
|
|
108
|
+
|
|
109
|
+
return Frame(data=data, idx=frame_idx)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@dataclass
|
|
113
|
+
class Vector:
|
|
114
|
+
"""1D data, aka spectrum."""
|
|
115
|
+
|
|
116
|
+
data: npt.NDArray[Any]
|
|
117
|
+
idx: int
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def unpack_vector(payload: bytes, dtype: np.dtype[T], frame_idx: int) -> Vector:
|
|
121
|
+
"""Instantiate a Vector from a data array header and payload."""
|
|
122
|
+
|
|
123
|
+
data = np.frombuffer(payload, dtype=dtype)
|
|
124
|
+
|
|
125
|
+
# Create a memory copy only if it is needed
|
|
126
|
+
if not data.flags.writeable:
|
|
127
|
+
data = np.array(data)
|
|
128
|
+
|
|
129
|
+
return Vector(data=data, idx=frame_idx)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def decode(raw_data: DevEncoded) -> Frame | Vector:
|
|
133
|
+
"""Decode a DevEncoded DATA_ARRAY and unpack the payload.
|
|
134
|
+
|
|
135
|
+
The resulting data depends on the header's category field.
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
if isinstance(raw_data, tuple):
|
|
139
|
+
# Support the direct output from proxy.readImage
|
|
140
|
+
if raw_data[0] not in ["DATA_ARRAY"]:
|
|
141
|
+
raise DevEncodedFormatNotSupported(
|
|
142
|
+
"Data type DATA_ARRAY expected (found %s)." % raw_data[0]
|
|
143
|
+
)
|
|
144
|
+
raw_data = raw_data[1]
|
|
145
|
+
|
|
146
|
+
header = DataArrayHeader.from_bytes(raw=raw_data)
|
|
147
|
+
|
|
148
|
+
if header.magic != MAGIC:
|
|
149
|
+
raise DevEncodedFormatNotSupported(
|
|
150
|
+
"Magic header not supported (found 0x%x)." % header.magic
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
# Assume backward-compatible incremental versioning
|
|
154
|
+
if header.version < 1:
|
|
155
|
+
raise DevEncodedFormatNotSupported(
|
|
156
|
+
f"Data array header version {header.version} not supported (expected >= 1)."
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
if header.header_size != HEADER_SIZE:
|
|
160
|
+
raise DevEncodedFormatNotSupported(
|
|
161
|
+
f"Invalid header size {header.header_size} " f"(expected {HEADER_SIZE})."
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
if header.endianness != 0:
|
|
165
|
+
raise DevEncodedFormatNotSupported(
|
|
166
|
+
"Unsupported endianness (found %s)." % header.endianness
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
try:
|
|
170
|
+
data_type = DataArrayDType(header.data_type)
|
|
171
|
+
except ValueError:
|
|
172
|
+
raise DevEncodedFormatNotSupported(
|
|
173
|
+
f"Data array data_type {header.data_type} not supported"
|
|
174
|
+
) from None
|
|
175
|
+
|
|
176
|
+
if data_type not in DTYPE_TO_NUMPY:
|
|
177
|
+
raise NotImplementedError(
|
|
178
|
+
f"Missing dtype association for {data_type}"
|
|
179
|
+
) from None
|
|
180
|
+
|
|
181
|
+
dtype = DTYPE_TO_NUMPY[data_type]
|
|
182
|
+
|
|
183
|
+
match header.category:
|
|
184
|
+
case DataArrayCategory.IMAGE:
|
|
185
|
+
if header.nb_dim != 3:
|
|
186
|
+
raise DevEncodedFormatNotSupported(
|
|
187
|
+
f"Expected frame nb_dim == 3, got {header.nb_dim}"
|
|
188
|
+
)
|
|
189
|
+
return unpack_frame(
|
|
190
|
+
payload=raw_data[header.header_size :],
|
|
191
|
+
shape=(header.dim3, header.dim2, header.dim1),
|
|
192
|
+
dtype=np.dtype(dtype),
|
|
193
|
+
frame_idx=header.frame_idx,
|
|
194
|
+
)
|
|
195
|
+
case DataArrayCategory.SPECTRUM:
|
|
196
|
+
if header.nb_dim != 1:
|
|
197
|
+
raise DevEncodedFormatNotSupported(
|
|
198
|
+
f"Expected vector nb_dim == 1, got {header.nb_dim}"
|
|
199
|
+
)
|
|
200
|
+
return unpack_vector(
|
|
201
|
+
payload=raw_data[header.header_size :],
|
|
202
|
+
dtype=np.dtype(dtype),
|
|
203
|
+
frame_idx=header.frame_idx,
|
|
204
|
+
)
|
|
205
|
+
case _:
|
|
206
|
+
raise NotImplementedError(
|
|
207
|
+
f"No unpack function for data array category {header.category}"
|
|
208
|
+
)
|
|
@@ -12,9 +12,8 @@ from typing import Any
|
|
|
12
12
|
import numpy as np
|
|
13
13
|
import numpy.typing as npt
|
|
14
14
|
|
|
15
|
-
from lima2.common.devencoded.
|
|
16
|
-
|
|
17
|
-
from .exception import DevEncodedFormatNotSupported
|
|
15
|
+
from lima2.common.devencoded.common import DevEncoded, Frame
|
|
16
|
+
from lima2.common.devencoded.exception import DevEncodedFormatNotSupported
|
|
18
17
|
|
|
19
18
|
DATA_HEADER_FORMAT = "<HHIII"
|
|
20
19
|
DATA_HEADER_SIZE = struct.calcsize(DATA_HEADER_FORMAT)
|
|
@@ -91,7 +90,7 @@ class SmxSparseFrame:
|
|
|
91
90
|
return self.densify_peaks()
|
|
92
91
|
|
|
93
92
|
|
|
94
|
-
def decode(raw_data:
|
|
93
|
+
def decode(raw_data: DevEncoded) -> SmxSparseFrame:
|
|
95
94
|
"""Decode data provided by Lima2
|
|
96
95
|
|
|
97
96
|
Argument:
|
|
@@ -12,9 +12,8 @@ from typing import Any
|
|
|
12
12
|
import numpy as np
|
|
13
13
|
import numpy.typing as npt
|
|
14
14
|
|
|
15
|
-
from lima2.common.devencoded.
|
|
16
|
-
|
|
17
|
-
from .exception import DevEncodedFormatNotSupported
|
|
15
|
+
from lima2.common.devencoded.common import Frame
|
|
16
|
+
from lima2.common.devencoded.exception import DevEncodedFormatNotSupported
|
|
18
17
|
|
|
19
18
|
DATA_HEADER_FORMAT = "<HHII"
|
|
20
19
|
DATA_HEADER_SIZE = struct.calcsize(DATA_HEADER_FORMAT)
|
|
@@ -53,7 +52,10 @@ class SparseFrame:
|
|
|
53
52
|
return Frame(dense, self.idx)
|
|
54
53
|
|
|
55
54
|
|
|
56
|
-
|
|
55
|
+
EncodedSparseFrame = bytes | tuple[str, bytes]
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def decode(raw_data: EncodedSparseFrame) -> SparseFrame:
|
|
57
59
|
"""Decode data provided by Lima2
|
|
58
60
|
|
|
59
61
|
Argument:
|
|
@@ -11,7 +11,7 @@ from typing import Any
|
|
|
11
11
|
import numpy
|
|
12
12
|
import numpy.typing as npt
|
|
13
13
|
|
|
14
|
-
from .exception import DevEncodedFormatNotSupported
|
|
14
|
+
from lima2.common.devencoded.exception import DevEncodedFormatNotSupported
|
|
15
15
|
|
|
16
16
|
DATA_HEADER_FORMAT = "<IHHIIHHHHHHHHIIIIIIQ"
|
|
17
17
|
DATA_MAGIC = struct.unpack(">I", b"DTAY")[0]
|