lima2.client 4.2.1__tar.gz → 4.3.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.2.1 → lima2_client-4.3.0}/.gitlab-ci.yml +1 -0
- {lima2_client-4.2.1/src/lima2_client.egg-info → lima2_client-4.3.0}/PKG-INFO +2 -1
- {lima2_client-4.2.1 → lima2_client-4.3.0}/conda/recipe.yaml +1 -0
- lima2_client-4.3.0/docs/reference/accumulation.md +13 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/mkdocs.yml +1 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/pyproject.toml +1 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/client/_utils.py +16 -0
- lima2_client-4.3.0/src/lima2/client/accumulation.py +150 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/client/services.py +62 -1
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/common/pipelines.py +1 -1
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/conductor/acquisition.py +49 -14
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/conductor/acquisition_system.py +7 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/conductor/processing/master_file.py +327 -58
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/conductor/processing/pipeline.py +157 -36
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/conductor/processing/reduced_data.py +24 -7
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/conductor/webservice/acquisition.py +73 -2
- {lima2_client-4.2.1 → lima2_client-4.3.0/src/lima2_client.egg-info}/PKG-INFO +2 -1
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2_client.egg-info/SOURCES.txt +5 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2_client.egg-info/requires.txt +1 -0
- lima2_client-4.3.0/src/lima2_client.egg-info/scm_file_list.json +146 -0
- lima2_client-4.3.0/src/lima2_client.egg-info/scm_version.json +8 -0
- lima2_client-4.3.0/tests/unit/client/test_accumulation.py +178 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/tests/unit/client/test_acquisition.py +30 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/tests/unit/client/test_utils.py +13 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/tests/unit/conductor/mock_devices.py +1 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/tests/unit/conductor/test_acquisition.py +90 -26
- {lima2_client-4.2.1 → lima2_client-4.3.0}/tests/unit/conductor/test_master_file.py +252 -35
- {lima2_client-4.2.1 → lima2_client-4.3.0}/tests/unit/conductor/test_pipeline.py +147 -40
- {lima2_client-4.2.1 → lima2_client-4.3.0}/tests/unit/conductor/test_reduced_data.py +29 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/tests/unit/conductor/webservice/test_acquisition.py +127 -16
- {lima2_client-4.2.1 → lima2_client-4.3.0}/.gitignore +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/LICENSE +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/README.md +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/docs/assets/lima2-components-and-repos.svg +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/docs/assets/lima2-conductor-communication-model.svg +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/docs/assets/lima2-conductor-modules.svg +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/docs/assets/lima2-conductor-monitor-webpage.png +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/docs/assets/logo-transparent-crop.png +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/docs/concepts.md +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/docs/contact.md +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/docs/dev/conductor_client.md +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/docs/dev/conductor_design.md +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/docs/dev/conductor_requirements.md +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/docs/dev/index.md +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/docs/features.md +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/docs/getting_started.md +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/docs/index.md +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/docs/license.md +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/docs/monitor.md +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/docs/reference/acquisition.md +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/docs/reference/detector.md +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/docs/reference/exceptions.md +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/docs/reference/index.md +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/docs/reference/pipeline.md +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/docs/reference/services.md +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/docs/reference/session.md +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/docs/release_notes.md +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/docs/shell.md +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/flake8.ini +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/scripts/cleanup_processing_ds.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/setup.cfg +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/client/__init__.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/client/exceptions.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/client/py.typed +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/client/session.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/client/shell.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/common/__init__.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/common/devencoded/__init__.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/common/devencoded/common.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/common/devencoded/data_array.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/common/devencoded/exception.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/common/devencoded/smx_sparse_frame.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/common/devencoded/sparse_frame.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/common/devencoded/structured_array.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/common/exceptions.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/common/progress_counter.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/common/py.typed +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/common/state.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/common/types.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/conductor/__init__.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/conductor/detector.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/conductor/processing/__init__.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/conductor/processing/fai.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/conductor/processing/failing.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/conductor/processing/legacy.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/conductor/processing/smx.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/conductor/processing/xpcs.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/conductor/py.typed +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/conductor/tango/__init__.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/conductor/tango/container.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/conductor/tango/control.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/conductor/tango/processing.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/conductor/tango/receiver.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/conductor/tango/utils.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/conductor/topology.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/conductor/utils.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/conductor/webservice/__init__.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/conductor/webservice/benchmark.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/conductor/webservice/benchmark_generators/__init__.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/conductor/webservice/benchmark_generators/rois.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/conductor/webservice/cli.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/conductor/webservice/detector.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/conductor/webservice/main.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/conductor/webservice/monitor/favicon.ico +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/conductor/webservice/monitor/fonts/Roboto-Italic.woff2 +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/conductor/webservice/monitor/fonts/Roboto-Medium.woff2 +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/conductor/webservice/monitor/fonts/Roboto-MediumItalic.woff2 +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/conductor/webservice/monitor/fonts/Roboto-Regular.woff2 +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/conductor/webservice/monitor/fonts.css +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/conductor/webservice/monitor/index.html +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/conductor/webservice/monitor/milligram.css +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/conductor/webservice/monitor/normalize.css +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/conductor/webservice/pipeline.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/conductor/webservice/utils.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2/conductor/webservice/webapp.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2_client.egg-info/dependency_links.txt +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2_client.egg-info/entry_points.txt +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/src/lima2_client.egg-info/top_level.txt +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/tests/__init__.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/tests/integration/__init__.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/tests/integration/conftest.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/tests/integration/tango_db/simulator_ctl.yaml +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/tests/integration/tango_db/simulator_rcv1.yaml +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/tests/integration/tango_db/simulator_rcv2.yaml +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/tests/integration/test_failing.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/tests/integration/test_legacy.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/tests/unit/__init__.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/tests/unit/client/__init__.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/tests/unit/client/conftest.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/tests/unit/client/test_detector.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/tests/unit/client/test_pipeline.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/tests/unit/client/test_session.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/tests/unit/common/__init__.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/tests/unit/common/test_data_array.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/tests/unit/common/test_progress_counter.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/tests/unit/common/test_smx_sparse_frame.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/tests/unit/common/test_sparse_frame.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/tests/unit/common/test_state.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/tests/unit/conductor/__init__.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/tests/unit/conductor/conftest.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/tests/unit/conductor/mock_devencoded.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/tests/unit/conductor/test_acquisition_system.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/tests/unit/conductor/test_processing.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/tests/unit/conductor/test_tango_devices.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/tests/unit/conductor/test_topology.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/tests/unit/conductor/test_utils.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/tests/unit/conductor/webservice/__init__.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/tests/unit/conductor/webservice/test_benchmark.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/tests/unit/conductor/webservice/test_detector.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/tests/unit/conductor/webservice/test_pipelines.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/tests/unit/conductor/webservice/test_utils.py +0 -0
- {lima2_client-4.2.1 → lima2_client-4.3.0}/tests/unit/conductor/webservice/test_webapp.py +0 -0
|
@@ -88,6 +88,7 @@ integration-test:
|
|
|
88
88
|
script:
|
|
89
89
|
- mamba create -n lima2_env -c conda-forge -c esrf-bcu --yes
|
|
90
90
|
"esrf-bcu::lima2-core>=1.0.0,<2"
|
|
91
|
+
conda-forge::pip
|
|
91
92
|
conda-forge::git # for setuptools_scm
|
|
92
93
|
- mamba run -n lima2_env pip install .[dev]
|
|
93
94
|
- export LIMA2_PLUGIN_FOLDER="$(conda info --base)/envs/lima2_env/lib/plugins"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lima2-client
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.3.0
|
|
4
4
|
Summary: The Python client library of the Lima2 system
|
|
5
5
|
Author: BCU (ESRF)
|
|
6
6
|
License-Expression: MIT
|
|
@@ -17,6 +17,7 @@ Requires-Dist: packaging
|
|
|
17
17
|
Requires-Dist: pytango
|
|
18
18
|
Requires-Dist: requests
|
|
19
19
|
Provides-Extra: conductor
|
|
20
|
+
Requires-Dist: hdf5plugin; extra == "conductor"
|
|
20
21
|
Requires-Dist: jsonschema-default; extra == "conductor"
|
|
21
22
|
Requires-Dist: jsonschema-rs; extra == "conductor"
|
|
22
23
|
Requires-Dist: orjson; extra == "conductor"
|
|
@@ -6,8 +6,10 @@
|
|
|
6
6
|
"""Lima2 client private utility functions."""
|
|
7
7
|
|
|
8
8
|
import logging
|
|
9
|
+
from typing import Any
|
|
9
10
|
|
|
10
11
|
import h5py
|
|
12
|
+
import numpy as np
|
|
11
13
|
import tango as tg
|
|
12
14
|
|
|
13
15
|
from lima2.common import types
|
|
@@ -130,3 +132,17 @@ def hdf5_get_frame(
|
|
|
130
132
|
raise NotImplementedError(repr(frame_type))
|
|
131
133
|
|
|
132
134
|
return frm
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def jsonify_numpy_arrays(d: dict[str, Any]) -> dict[str, Any]:
|
|
138
|
+
"""
|
|
139
|
+
Return an identical dictionary with numpy arrays replaced by JSON-ready objects.
|
|
140
|
+
"""
|
|
141
|
+
ret: dict[str, Any] = {}
|
|
142
|
+
for k, v in d.items():
|
|
143
|
+
if isinstance(v, np.ndarray):
|
|
144
|
+
v = dict(type="numpy.ndarray", value=v.tolist(), dtype=str(v.dtype))
|
|
145
|
+
elif isinstance(v, dict):
|
|
146
|
+
v = jsonify_numpy_arrays(v)
|
|
147
|
+
ret[k] = v
|
|
148
|
+
return ret
|
|
@@ -0,0 +1,150 @@
|
|
|
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 accumulation helpers."""
|
|
7
|
+
|
|
8
|
+
import math
|
|
9
|
+
from typing import Literal, NamedTuple
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AccumulationParams(NamedTuple):
|
|
13
|
+
expo_time: float
|
|
14
|
+
nb_frames: int
|
|
15
|
+
nb_frames_per_trigger: int
|
|
16
|
+
acc_nb_frames: int
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def params_livetime(
|
|
20
|
+
expo_time: float,
|
|
21
|
+
max_expo_time: float,
|
|
22
|
+
npoints: int,
|
|
23
|
+
max_nb_frames_per_trigger: int,
|
|
24
|
+
trigger_mode: Literal["internal", "software", "external", "gate"],
|
|
25
|
+
) -> AccumulationParams:
|
|
26
|
+
"""
|
|
27
|
+
Calculate an optimal set of acquisition params given an upper limit constraint on
|
|
28
|
+
the duration of a single exposure, adding in accumulation if necessary, and if the
|
|
29
|
+
detector allows us to acquire a long-enough train of frames in a single trigger
|
|
30
|
+
pulse.
|
|
31
|
+
|
|
32
|
+
Assumes we are working in terms of "livetime", meaning the total exposure does not
|
|
33
|
+
include deadtime between frames. In this mode, accumulating `N` frames corresponds
|
|
34
|
+
to an integrated exposure of `N * subframe_expo`.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
expo_time: Desired exposure of one accumulated frame (seconds).
|
|
38
|
+
max_expo_time: Desired upper limit on the expo time per subframe (seconds).
|
|
39
|
+
npoints: Number of scan iterations.
|
|
40
|
+
max_nb_frames_per_trigger: Maximum number of frames that the detector is able to
|
|
41
|
+
acquire per trigger signal.
|
|
42
|
+
trigger_mode: Whether the detector is triggered by hardware (external/gate),
|
|
43
|
+
software, or internally.
|
|
44
|
+
"""
|
|
45
|
+
if max_expo_time <= 0:
|
|
46
|
+
raise ValueError("max_expo_time must be positive")
|
|
47
|
+
if expo_time <= 0:
|
|
48
|
+
raise ValueError("livetime must be positive")
|
|
49
|
+
if npoints < 0:
|
|
50
|
+
raise ValueError("npoints must not be negative")
|
|
51
|
+
|
|
52
|
+
if expo_time > max_expo_time:
|
|
53
|
+
acc_nb_frames = int(math.ceil(expo_time / max_expo_time))
|
|
54
|
+
det_expo_time = expo_time / acc_nb_frames
|
|
55
|
+
det_nb_frames = npoints * acc_nb_frames
|
|
56
|
+
nb_frames_per_trigger = acc_nb_frames
|
|
57
|
+
|
|
58
|
+
if acc_nb_frames > max_nb_frames_per_trigger:
|
|
59
|
+
if trigger_mode == "software":
|
|
60
|
+
raise ValueError(
|
|
61
|
+
f"Can't resolve acquisition parameters in software triggering with "
|
|
62
|
+
f"accumulation: "
|
|
63
|
+
f"would need to set acc_nb_frames={acc_nb_frames} but the "
|
|
64
|
+
f"upper limit is {max_nb_frames_per_trigger}."
|
|
65
|
+
)
|
|
66
|
+
elif trigger_mode == "external":
|
|
67
|
+
nb_frames_per_trigger = 1
|
|
68
|
+
|
|
69
|
+
return AccumulationParams(
|
|
70
|
+
expo_time=det_expo_time,
|
|
71
|
+
nb_frames=det_nb_frames,
|
|
72
|
+
nb_frames_per_trigger=nb_frames_per_trigger,
|
|
73
|
+
acc_nb_frames=acc_nb_frames,
|
|
74
|
+
)
|
|
75
|
+
else:
|
|
76
|
+
return AccumulationParams(
|
|
77
|
+
expo_time=expo_time,
|
|
78
|
+
nb_frames=npoints,
|
|
79
|
+
nb_frames_per_trigger=1,
|
|
80
|
+
acc_nb_frames=1,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def params_realtime(
|
|
85
|
+
expo_time: float,
|
|
86
|
+
latency_time: float,
|
|
87
|
+
max_expo_time: float,
|
|
88
|
+
npoints: int,
|
|
89
|
+
max_nb_frames_per_trigger: int,
|
|
90
|
+
trigger_mode: Literal["internal", "software", "external", "gate"],
|
|
91
|
+
) -> AccumulationParams:
|
|
92
|
+
"""
|
|
93
|
+
Calculate an optimal set of acquisition params given an upper limit constraint on
|
|
94
|
+
the duration of a single exposure, adding in accumulation if necessary, and if the
|
|
95
|
+
detector allows us to acquire a long-enough train of frames in a single trigger
|
|
96
|
+
pulse.
|
|
97
|
+
|
|
98
|
+
Assumes we are working in terms of "realtime", where the resulting train of frames
|
|
99
|
+
should not exceed the requested frame time. To accomplish this, we must take into
|
|
100
|
+
account the dead time between exposures (latency time).
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
expo_time: Desired exposure of one accumulated frame (seconds).
|
|
104
|
+
latency_time: Delay between two subframe exposures (seconds).
|
|
105
|
+
max_expo_time: Desired upper limit on the expo time per subframe (seconds).
|
|
106
|
+
npoints: Number of scan iterations.
|
|
107
|
+
max_nb_frames_per_trigger: Maximum number of frames that the detector is able to
|
|
108
|
+
acquire per trigger signal.
|
|
109
|
+
trigger_mode: Whether the detector is triggered by hardware (external/gate),
|
|
110
|
+
software, or internally.
|
|
111
|
+
"""
|
|
112
|
+
if max_expo_time <= 0:
|
|
113
|
+
raise ValueError("max_expo_time must be positive")
|
|
114
|
+
if expo_time <= 0:
|
|
115
|
+
raise ValueError("livetime must be positive")
|
|
116
|
+
if npoints < 0:
|
|
117
|
+
raise ValueError("npoints must not be negative")
|
|
118
|
+
|
|
119
|
+
if expo_time > max_expo_time:
|
|
120
|
+
acc_nb_frames = int(
|
|
121
|
+
math.ceil((expo_time + latency_time) / (max_expo_time + latency_time))
|
|
122
|
+
)
|
|
123
|
+
det_expo_time = (expo_time + latency_time) / acc_nb_frames - latency_time
|
|
124
|
+
det_nb_frames = npoints * acc_nb_frames
|
|
125
|
+
nb_frames_per_trigger = acc_nb_frames
|
|
126
|
+
|
|
127
|
+
if acc_nb_frames > max_nb_frames_per_trigger:
|
|
128
|
+
if trigger_mode == "software":
|
|
129
|
+
raise ValueError(
|
|
130
|
+
f"Can't resolve acquisition parameters in software triggering with "
|
|
131
|
+
f"accumulation: "
|
|
132
|
+
f"would need to set acc_nb_frames={acc_nb_frames} but the "
|
|
133
|
+
f"upper limit is {max_nb_frames_per_trigger}."
|
|
134
|
+
)
|
|
135
|
+
elif trigger_mode == "external":
|
|
136
|
+
nb_frames_per_trigger = 1
|
|
137
|
+
|
|
138
|
+
return AccumulationParams(
|
|
139
|
+
expo_time=det_expo_time,
|
|
140
|
+
nb_frames=det_nb_frames,
|
|
141
|
+
nb_frames_per_trigger=nb_frames_per_trigger,
|
|
142
|
+
acc_nb_frames=acc_nb_frames,
|
|
143
|
+
)
|
|
144
|
+
else:
|
|
145
|
+
return AccumulationParams(
|
|
146
|
+
expo_time=expo_time,
|
|
147
|
+
nb_frames=npoints,
|
|
148
|
+
nb_frames_per_trigger=1,
|
|
149
|
+
acc_nb_frames=1,
|
|
150
|
+
)
|
|
@@ -32,6 +32,43 @@ from lima2.common.types import FrameInfo, FrameType
|
|
|
32
32
|
logger = logging.getLogger(__name__)
|
|
33
33
|
|
|
34
34
|
|
|
35
|
+
########################################################################################
|
|
36
|
+
# Acquisition results
|
|
37
|
+
########################################################################################
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass
|
|
41
|
+
class AcquisitionSuccess:
|
|
42
|
+
"""The acquisition completed successfully, despite potential warnings."""
|
|
43
|
+
|
|
44
|
+
warnings: list[str]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class AcquisitionFailure:
|
|
49
|
+
"""The acquisition failed because or acquisition or processing issues."""
|
|
50
|
+
|
|
51
|
+
acquisition_errors: list[str]
|
|
52
|
+
"""Fatal errors during frame acquisition."""
|
|
53
|
+
processing_errors: list[str]
|
|
54
|
+
"""Fatal errors during frame processing."""
|
|
55
|
+
warnings: list[str]
|
|
56
|
+
"""Non-critical issues that occurred during the acquisition."""
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class Unknown:
|
|
60
|
+
"""No acquisition is ongoing, or the current acquisition is still running."""
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
AcquisitionResult = AcquisitionSuccess | AcquisitionFailure | Unknown
|
|
64
|
+
"""Return type of `Acquisition.result()`. Outcome of the latest acquisition."""
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
########################################################################################
|
|
68
|
+
# Services
|
|
69
|
+
########################################################################################
|
|
70
|
+
|
|
71
|
+
|
|
35
72
|
class Acquisition:
|
|
36
73
|
"""Conductor /acquisition service requests."""
|
|
37
74
|
|
|
@@ -56,7 +93,7 @@ class Acquisition:
|
|
|
56
93
|
"control": control,
|
|
57
94
|
"receiver": receiver,
|
|
58
95
|
"processing": processing,
|
|
59
|
-
"masterfile_metadata": masterfile_metadata,
|
|
96
|
+
"masterfile_metadata": _utils.jsonify_numpy_arrays(masterfile_metadata),
|
|
60
97
|
},
|
|
61
98
|
).json()
|
|
62
99
|
return UUID(uuid_str)
|
|
@@ -104,6 +141,30 @@ class Acquisition:
|
|
|
104
141
|
runstate = RunState(self.session.get("/acquisition/state").json()["value"])
|
|
105
142
|
return bool(runstate & RunState.FAULT)
|
|
106
143
|
|
|
144
|
+
def result(self) -> AcquisitionResult:
|
|
145
|
+
"""Get information about the latest acquisition's outcome.
|
|
146
|
+
|
|
147
|
+
Returns an instance of either AcquisitionSuccess, AcquisitionFailure, or
|
|
148
|
+
Unknown.
|
|
149
|
+
"""
|
|
150
|
+
result_json = self.session.get("/acquisition/result").json()
|
|
151
|
+
status = result_json["type"]
|
|
152
|
+
|
|
153
|
+
if status == "unknown":
|
|
154
|
+
return Unknown()
|
|
155
|
+
elif status == "success":
|
|
156
|
+
return AcquisitionSuccess(warnings=result_json["warnings"])
|
|
157
|
+
elif status == "failure":
|
|
158
|
+
acq_errors = result_json["errors"].get("acquisition", [])
|
|
159
|
+
proc_errors = result_json["errors"].get("processing", [])
|
|
160
|
+
return AcquisitionFailure(
|
|
161
|
+
acquisition_errors=acq_errors,
|
|
162
|
+
processing_errors=proc_errors,
|
|
163
|
+
warnings=result_json["warnings"],
|
|
164
|
+
)
|
|
165
|
+
else:
|
|
166
|
+
raise NotImplementedError(f"Unknown result format: {result_json}")
|
|
167
|
+
|
|
107
168
|
def default_params(self) -> tuple[dict[str, Any], dict[str, Any]]:
|
|
108
169
|
"""Get default acquisition parameters.
|
|
109
170
|
|
|
@@ -15,6 +15,7 @@ import traceback
|
|
|
15
15
|
import weakref
|
|
16
16
|
from dataclasses import dataclass
|
|
17
17
|
from enum import Enum, auto
|
|
18
|
+
from pprint import pformat
|
|
18
19
|
from typing import Awaitable, Callable
|
|
19
20
|
|
|
20
21
|
from lima2.common.exceptions import Lima2BackendError
|
|
@@ -24,6 +25,7 @@ from lima2.conductor.processing.master_file import (
|
|
|
24
25
|
FrameSavingChannel,
|
|
25
26
|
MasterFileMetadata,
|
|
26
27
|
)
|
|
28
|
+
from lima2.conductor.processing.pipeline import Pipeline
|
|
27
29
|
from lima2.conductor.processing.reduced_data import RoiParams
|
|
28
30
|
from lima2.conductor.tango.container import TangoProcessingGroup
|
|
29
31
|
from lima2.conductor.tango.processing import ProcessingErrorEvent, TangoProcessing
|
|
@@ -72,6 +74,9 @@ OnEndCallback = Callable[[SuccessFlag], Awaitable[None]]
|
|
|
72
74
|
OnFailureCallback = Callable[[], Awaitable[None]]
|
|
73
75
|
"""Signature of the on_failure() callback."""
|
|
74
76
|
|
|
77
|
+
OnProcessingFinishedCallback = Callable[[], Awaitable[None]]
|
|
78
|
+
"""Signature of the on_processing_finished() callback."""
|
|
79
|
+
|
|
75
80
|
|
|
76
81
|
class AcquisitionState:
|
|
77
82
|
"""Represents the state of a running acquisition.
|
|
@@ -94,13 +99,17 @@ class AcquisitionState:
|
|
|
94
99
|
num_receivers: int,
|
|
95
100
|
on_end: OnEndCallback,
|
|
96
101
|
on_failure: OnFailureCallback,
|
|
102
|
+
on_processing_finished: OnProcessingFinishedCallback,
|
|
97
103
|
) -> None:
|
|
98
104
|
self.num_receivers = num_receivers
|
|
99
105
|
|
|
100
|
-
|
|
106
|
+
# NOTE: use WeakMethods to avoid cyclic Acquisition <-> AcquisitionState refs
|
|
107
|
+
self.on_end_ref = weakref.WeakMethod(on_end)
|
|
101
108
|
"""Called when all devices are done (successfully or not)."""
|
|
102
|
-
self.
|
|
109
|
+
self.on_failure_ref = weakref.WeakMethod(on_failure)
|
|
103
110
|
"""Called when any device fails, but only once."""
|
|
111
|
+
self.on_processing_finished_ref = weakref.WeakMethod(on_processing_finished)
|
|
112
|
+
"""Called when all processing devices are finished (successfully or not)."""
|
|
104
113
|
|
|
105
114
|
# Substate: we need to keep track of how many receivers and processing
|
|
106
115
|
# devices are done, whether some failed, whether failure has already
|
|
@@ -247,6 +256,10 @@ class AcquisitionState:
|
|
|
247
256
|
case AcquisitionEvent.PROC_FINISHED:
|
|
248
257
|
self.proc_finished = True
|
|
249
258
|
|
|
259
|
+
on_processing_finished = self.on_processing_finished_ref()
|
|
260
|
+
if on_processing_finished:
|
|
261
|
+
await on_processing_finished()
|
|
262
|
+
|
|
250
263
|
if self.acq_finished:
|
|
251
264
|
events.append(AcquisitionEvent.FINISHED)
|
|
252
265
|
|
|
@@ -262,9 +275,13 @@ class AcquisitionState:
|
|
|
262
275
|
logger.info("🔥 Pipeline finished with errors: see above 🔥")
|
|
263
276
|
|
|
264
277
|
try:
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
278
|
+
on_end = self.on_end_ref()
|
|
279
|
+
if on_end:
|
|
280
|
+
await on_end(
|
|
281
|
+
SuccessFlag(
|
|
282
|
+
success=not (self.rcv_failed or self.proc_failed)
|
|
283
|
+
)
|
|
284
|
+
)
|
|
268
285
|
except BaseException:
|
|
269
286
|
logger.warning(
|
|
270
287
|
f"Exception in on_end() callback: {traceback.format_exc()}"
|
|
@@ -276,7 +293,9 @@ class AcquisitionState:
|
|
|
276
293
|
if not self.failed_once:
|
|
277
294
|
self.failed_once = True
|
|
278
295
|
try:
|
|
279
|
-
|
|
296
|
+
on_failure = self.on_failure_ref()
|
|
297
|
+
if on_failure:
|
|
298
|
+
await on_failure()
|
|
280
299
|
except BaseException:
|
|
281
300
|
logger.warning(
|
|
282
301
|
f"Exception in on_failure() callback: "
|
|
@@ -336,6 +355,7 @@ class Acquisition:
|
|
|
336
355
|
on_end: OnEndCallback,
|
|
337
356
|
on_failure: OnFailureCallback,
|
|
338
357
|
num_frames: int,
|
|
358
|
+
accumulation_depth: int,
|
|
339
359
|
frame_infos: dict[str, FrameInfo],
|
|
340
360
|
roi_params: RoiParams,
|
|
341
361
|
saving_channels: dict[str, FrameSavingChannel],
|
|
@@ -346,6 +366,7 @@ class Acquisition:
|
|
|
346
366
|
num_receivers=len(processing_devices),
|
|
347
367
|
on_end=on_end,
|
|
348
368
|
on_failure=on_failure,
|
|
369
|
+
on_processing_finished=self.on_processing_finished,
|
|
349
370
|
)
|
|
350
371
|
self.proc_group = processing_devices
|
|
351
372
|
"""Processing device group."""
|
|
@@ -358,6 +379,7 @@ class Acquisition:
|
|
|
358
379
|
self.pipeline = pipeline_class(
|
|
359
380
|
devices=processing_devices,
|
|
360
381
|
num_frames=num_frames,
|
|
382
|
+
accumulation_depth=accumulation_depth,
|
|
361
383
|
frame_infos=frame_infos,
|
|
362
384
|
roi_params=roi_params,
|
|
363
385
|
saving_channels=saving_channels,
|
|
@@ -366,14 +388,30 @@ class Acquisition:
|
|
|
366
388
|
"""Processing pipeline."""
|
|
367
389
|
|
|
368
390
|
self.processing_errors: list[str] = []
|
|
369
|
-
"""Holds processing error messages that occurred during the run, if any.
|
|
391
|
+
"""Holds processing error messages that occurred during the run, if any.
|
|
392
|
+
|
|
393
|
+
This list contains errors from the REMOTE processing devices.
|
|
394
|
+
"""
|
|
370
395
|
|
|
371
|
-
self.
|
|
372
|
-
"""
|
|
396
|
+
self.pipeline_result: Pipeline.PipelineResult | None = None
|
|
397
|
+
"""Holds the return value of pipeline.close(), once processing is finished.
|
|
398
|
+
|
|
399
|
+
This result contains information about the LOCAL pipeline-related tasks, not the
|
|
400
|
+
online processing done by Lima2 backend devices.
|
|
401
|
+
"""
|
|
402
|
+
|
|
403
|
+
async def on_processing_finished(self) -> None:
|
|
404
|
+
"""Registered as the AcquisitionState's on_processing_finished() callback."""
|
|
405
|
+
logger.info("Closing pipeline")
|
|
406
|
+
self.pipeline_result = await self.pipeline.close()
|
|
407
|
+
logger.info(f"Pipeline result: {pformat(self.pipeline_result)}")
|
|
373
408
|
|
|
374
409
|
def __del__(self) -> None:
|
|
375
410
|
logger.debug(f"Acquisition instance {self.uuid} destroyed")
|
|
376
411
|
|
|
412
|
+
def started(self) -> bool:
|
|
413
|
+
return self.state.started
|
|
414
|
+
|
|
377
415
|
def running(self) -> bool:
|
|
378
416
|
return self.state.running()
|
|
379
417
|
|
|
@@ -391,6 +429,7 @@ class Acquisition:
|
|
|
391
429
|
on_end: OnEndCallback,
|
|
392
430
|
on_failure: OnFailureCallback,
|
|
393
431
|
num_frames: int,
|
|
432
|
+
accumulation_depth: int,
|
|
394
433
|
frame_infos: dict[str, FrameInfo],
|
|
395
434
|
roi_params: RoiParams,
|
|
396
435
|
saving_channels: dict[str, FrameSavingChannel],
|
|
@@ -405,6 +444,7 @@ class Acquisition:
|
|
|
405
444
|
on_end=on_end,
|
|
406
445
|
on_failure=on_failure,
|
|
407
446
|
num_frames=num_frames,
|
|
447
|
+
accumulation_depth=accumulation_depth,
|
|
408
448
|
frame_infos=frame_infos,
|
|
409
449
|
roi_params=roi_params,
|
|
410
450
|
saving_channels=saving_channels,
|
|
@@ -450,11 +490,6 @@ class Acquisition:
|
|
|
450
490
|
|
|
451
491
|
await self_ref.notify(event=AcquisitionEvent.PROC_DEVICE_DONE)
|
|
452
492
|
|
|
453
|
-
self_ref.done_devices.add(device)
|
|
454
|
-
if self_ref.done_devices == set(self_ref.proc_group):
|
|
455
|
-
logger.info("Closing pipeline")
|
|
456
|
-
await self_ref.pipeline.close()
|
|
457
|
-
|
|
458
493
|
async def on_device_error(evt: ProcessingErrorEvent) -> None:
|
|
459
494
|
"""Reports a processing error to the registered callback."""
|
|
460
495
|
self_ref = wself()
|
|
@@ -385,6 +385,11 @@ class AcquisitionSystem:
|
|
|
385
385
|
frame_types=frame_types,
|
|
386
386
|
)
|
|
387
387
|
|
|
388
|
+
if "accumulation" in proc_params and proc_params["accumulation"]["enabled"]:
|
|
389
|
+
accumulation_depth = proc_params["accumulation"]["nb_frames"]
|
|
390
|
+
else:
|
|
391
|
+
accumulation_depth = 1
|
|
392
|
+
|
|
388
393
|
logger.info(f"Frame infos:\n{pformat(frame_infos, indent=2)}")
|
|
389
394
|
logger.info(f"Saving channels:\n{pformat(saving_channels, indent=2)}")
|
|
390
395
|
|
|
@@ -393,6 +398,7 @@ class AcquisitionSystem:
|
|
|
393
398
|
on_end=self.on_acquisition_end,
|
|
394
399
|
on_failure=self.on_acquisition_failure,
|
|
395
400
|
num_frames=num_frames,
|
|
401
|
+
accumulation_depth=accumulation_depth,
|
|
396
402
|
frame_infos=frame_infos,
|
|
397
403
|
roi_params=roi_params,
|
|
398
404
|
saving_channels=saving_channels,
|
|
@@ -620,6 +626,7 @@ class AcquisitionSystem:
|
|
|
620
626
|
devices=proc_devices,
|
|
621
627
|
frame_infos=frame_infos,
|
|
622
628
|
num_frames=0,
|
|
629
|
+
accumulation_depth=1,
|
|
623
630
|
roi_params=RoiParams(rois=[], profiles=[]),
|
|
624
631
|
saving_channels={},
|
|
625
632
|
masterfile_metadata=MasterFileMetadata(
|