dls-dodal 1.50.0__py3-none-any.whl → 1.52.0__py3-none-any.whl
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.
- {dls_dodal-1.50.0.dist-info → dls_dodal-1.52.0.dist-info}/METADATA +5 -5
- {dls_dodal-1.50.0.dist-info → dls_dodal-1.52.0.dist-info}/RECORD +76 -68
- dodal/_version.py +2 -2
- dodal/beamlines/adsim.py +5 -3
- dodal/beamlines/b01_1.py +41 -5
- dodal/beamlines/b07.py +13 -2
- dodal/beamlines/b07_1.py +13 -2
- dodal/beamlines/b16.py +8 -4
- dodal/beamlines/b21.py +148 -0
- dodal/beamlines/i03.py +10 -12
- dodal/beamlines/i04.py +7 -7
- dodal/beamlines/i09.py +25 -2
- dodal/beamlines/i09_1.py +13 -2
- dodal/beamlines/i09_2.py +24 -0
- dodal/beamlines/i10.py +5 -6
- dodal/beamlines/i13_1.py +5 -5
- dodal/beamlines/i18.py +5 -6
- dodal/beamlines/i22.py +18 -1
- dodal/beamlines/i24.py +5 -5
- dodal/beamlines/p45.py +4 -3
- dodal/beamlines/p60.py +21 -2
- dodal/beamlines/p99.py +19 -5
- dodal/beamlines/training_rig.py +3 -3
- dodal/common/beamlines/beamline_utils.py +5 -2
- dodal/common/device_utils.py +45 -0
- dodal/devices/aithre_lasershaping/goniometer.py +4 -5
- dodal/devices/aperture.py +4 -7
- dodal/devices/aperturescatterguard.py +2 -2
- dodal/devices/attenuator/attenuator.py +5 -3
- dodal/devices/b07/__init__.py +3 -0
- dodal/devices/b07/enums.py +24 -0
- dodal/devices/b07_1/__init__.py +3 -0
- dodal/devices/b07_1/enums.py +18 -0
- dodal/devices/detector/detector_motion.py +19 -17
- dodal/devices/electron_analyser/abstract/__init__.py +4 -0
- dodal/devices/electron_analyser/abstract/base_driver_io.py +44 -28
- dodal/devices/electron_analyser/abstract/base_region.py +20 -7
- dodal/devices/electron_analyser/detector.py +3 -13
- dodal/devices/electron_analyser/specs/detector.py +24 -4
- dodal/devices/electron_analyser/specs/driver_io.py +20 -5
- dodal/devices/electron_analyser/specs/region.py +9 -5
- dodal/devices/electron_analyser/types.py +21 -5
- dodal/devices/electron_analyser/vgscienta/detector.py +22 -7
- dodal/devices/electron_analyser/vgscienta/driver_io.py +16 -8
- dodal/devices/electron_analyser/vgscienta/region.py +11 -6
- dodal/devices/fast_grid_scan.py +1 -2
- dodal/devices/i04/constants.py +1 -1
- dodal/devices/i09/__init__.py +4 -0
- dodal/devices/i09/dcm.py +26 -0
- dodal/devices/i09/enums.py +15 -0
- dodal/devices/i09_1/__init__.py +3 -0
- dodal/devices/i09_1/enums.py +19 -0
- dodal/devices/i10/mirrors.py +4 -6
- dodal/devices/i10/rasor/rasor_motors.py +0 -14
- dodal/devices/i19/beamstop.py +3 -7
- dodal/devices/i24/aperture.py +4 -6
- dodal/devices/i24/beamstop.py +5 -8
- dodal/devices/i24/pmac.py +4 -8
- dodal/devices/linkam3.py +25 -81
- dodal/devices/motors.py +92 -35
- dodal/devices/oav/pin_image_recognition/__init__.py +11 -14
- dodal/devices/p45.py +0 -12
- dodal/devices/p60/__init__.py +4 -0
- dodal/devices/p60/enums.py +10 -0
- dodal/devices/p60/lab_xray_source.py +21 -0
- dodal/devices/pgm.py +1 -1
- dodal/devices/robot.py +11 -7
- dodal/devices/smargon.py +8 -9
- dodal/devices/tetramm.py +134 -150
- dodal/devices/xbpm_feedback.py +6 -3
- dodal/devices/zocalo/zocalo_results.py +27 -78
- dodal/plans/configure_arm_trigger_and_disarm_detector.py +7 -5
- dodal/devices/adsim.py +0 -13
- dodal/devices/i18/table.py +0 -14
- dodal/devices/i18/thor_labs_stage.py +0 -12
- dodal/devices/i24/i24_detector_motion.py +0 -12
- dodal/devices/scatterguard.py +0 -11
- dodal/devices/training_rig/__init__.py +0 -0
- dodal/devices/training_rig/sample_stage.py +0 -10
- {dls_dodal-1.50.0.dist-info → dls_dodal-1.52.0.dist-info}/WHEEL +0 -0
- {dls_dodal-1.50.0.dist-info → dls_dodal-1.52.0.dist-info}/entry_points.txt +0 -0
- {dls_dodal-1.50.0.dist-info → dls_dodal-1.52.0.dist-info}/licenses/LICENSE +0 -0
- {dls_dodal-1.50.0.dist-info → dls_dodal-1.52.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from asyncio import CancelledError, create_task, sleep
|
|
2
|
+
from contextlib import asynccontextmanager
|
|
3
|
+
|
|
4
|
+
from dodal.log import LOGGER
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@asynccontextmanager
|
|
8
|
+
async def periodic_reminder(
|
|
9
|
+
message: str = "Waiting",
|
|
10
|
+
schedule: tuple[tuple[int | float, int | None], ...] = ( # seconds, frequency
|
|
11
|
+
(1, 3),
|
|
12
|
+
(5, 3),
|
|
13
|
+
(60, 5),
|
|
14
|
+
(300, 5),
|
|
15
|
+
(1800, 5),
|
|
16
|
+
(3600, None),
|
|
17
|
+
),
|
|
18
|
+
):
|
|
19
|
+
"""Periodically logs a message according to a schedule with increasing delays.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
message: The message the user wants to output through logging.
|
|
23
|
+
schedule: A tuple list of tuples consisting of (int|float, int|None).
|
|
24
|
+
A sequence of (delay_seconds, count) pairs defining the logging intervals.
|
|
25
|
+
- delay_seconds is the number of seconds to wait between logs.
|
|
26
|
+
- count is how many times to log at this interval. If count is None, it logs indefinitely at that delay.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
async def _log_loop():
|
|
30
|
+
for delay, count in schedule:
|
|
31
|
+
n = 0
|
|
32
|
+
while count is None or n < count:
|
|
33
|
+
LOGGER.info(message)
|
|
34
|
+
await sleep(delay)
|
|
35
|
+
n += 1
|
|
36
|
+
|
|
37
|
+
task = create_task(_log_loop())
|
|
38
|
+
try:
|
|
39
|
+
yield
|
|
40
|
+
finally:
|
|
41
|
+
task.cancel()
|
|
42
|
+
try:
|
|
43
|
+
await task
|
|
44
|
+
except CancelledError:
|
|
45
|
+
pass
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import math
|
|
3
3
|
|
|
4
|
-
from ophyd_async.core import
|
|
4
|
+
from ophyd_async.core import derived_signal_rw
|
|
5
5
|
from ophyd_async.epics.motor import Motor
|
|
6
6
|
|
|
7
|
+
from dodal.devices.motors import XYZStage
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
|
|
10
|
+
class Goniometer(XYZStage):
|
|
9
11
|
"""The Aithre lab goniometer and the XYZ stage it sits on.
|
|
10
12
|
|
|
11
13
|
`x`, `y` and `z` control the axes of the positioner at the base, while `sampy` and
|
|
@@ -18,9 +20,6 @@ class Goniometer(StandardReadable):
|
|
|
18
20
|
"""
|
|
19
21
|
|
|
20
22
|
def __init__(self, prefix: str, name: str = "") -> None:
|
|
21
|
-
self.x = Motor(prefix + "X")
|
|
22
|
-
self.y = Motor(prefix + "Y")
|
|
23
|
-
self.z = Motor(prefix + "Z")
|
|
24
23
|
self.sampy = Motor(prefix + "SAMPY")
|
|
25
24
|
self.sampz = Motor(prefix + "SAMPZ")
|
|
26
25
|
self.omega = Motor(prefix + "OMEGA")
|
dodal/devices/aperture.py
CHANGED
|
@@ -1,14 +1,11 @@
|
|
|
1
|
-
from ophyd_async.core import StandardReadable
|
|
2
1
|
from ophyd_async.epics.core import epics_signal_r
|
|
3
|
-
from ophyd_async.epics.motor import Motor
|
|
4
2
|
|
|
3
|
+
from dodal.devices.motors import XYZStage
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
|
|
6
|
+
class Aperture(XYZStage):
|
|
7
7
|
def __init__(self, prefix: str, name: str = "") -> None:
|
|
8
|
-
self.x = Motor(prefix + "X")
|
|
9
|
-
self.y = Motor(prefix + "Y")
|
|
10
|
-
self.z = Motor(prefix + "Z")
|
|
11
8
|
self.small = epics_signal_r(float, prefix + "Y:SMALL_CALC")
|
|
12
9
|
self.medium = epics_signal_r(float, prefix + "Y:MEDIUM_CALC")
|
|
13
10
|
self.large = epics_signal_r(float, prefix + "Y:LARGE_CALC")
|
|
14
|
-
super().__init__(name)
|
|
11
|
+
super().__init__(prefix, name)
|
|
@@ -15,7 +15,7 @@ from pydantic import BaseModel, Field
|
|
|
15
15
|
|
|
16
16
|
from dodal.common.beamlines.beamline_parameters import GDABeamlineParameters
|
|
17
17
|
from dodal.devices.aperture import Aperture
|
|
18
|
-
from dodal.devices.
|
|
18
|
+
from dodal.devices.motors import XYStage
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
class InvalidApertureMove(Exception):
|
|
@@ -164,7 +164,7 @@ class ApertureScatterguard(StandardReadable, Preparable):
|
|
|
164
164
|
name: str = "",
|
|
165
165
|
) -> None:
|
|
166
166
|
self.aperture = Aperture(prefix + "-MO-MAPT-01:")
|
|
167
|
-
self.scatterguard =
|
|
167
|
+
self.scatterguard = XYStage(prefix + "-MO-SCAT-01:")
|
|
168
168
|
self._loaded_positions = loaded_positions
|
|
169
169
|
self._tolerances = tolerances
|
|
170
170
|
with self.add_children_as_readables(StandardReadableFormat.HINTED_SIGNAL):
|
|
@@ -39,10 +39,12 @@ class BinaryFilterAttenuator(ReadOnlyAttenuator, Movable[float]):
|
|
|
39
39
|
Where desired_transmission is fraction e.g. 0-1. When the actual_transmission is
|
|
40
40
|
read from the device it is also fractional"""
|
|
41
41
|
|
|
42
|
-
def __init__(self, prefix: str, name: str = ""):
|
|
42
|
+
def __init__(self, prefix: str, num_filters: int, name: str = ""):
|
|
43
43
|
self._calculated_filter_states: DeviceVector[SignalR[int]] = DeviceVector(
|
|
44
44
|
{
|
|
45
|
-
int(digit,
|
|
45
|
+
int(digit, num_filters): epics_signal_r(
|
|
46
|
+
int, f"{prefix}DEC_TO_BIN.B{digit}"
|
|
47
|
+
)
|
|
46
48
|
for digit in string.hexdigits
|
|
47
49
|
if not digit.islower()
|
|
48
50
|
}
|
|
@@ -50,7 +52,7 @@ class BinaryFilterAttenuator(ReadOnlyAttenuator, Movable[float]):
|
|
|
50
52
|
self._filters_in_position: DeviceVector[SignalR[bool]] = DeviceVector(
|
|
51
53
|
{
|
|
52
54
|
i - 1: epics_signal_r(bool, f"{prefix}FILTER{i}:INLIM")
|
|
53
|
-
for i in range(1,
|
|
55
|
+
for i in range(1, num_filters + 1)
|
|
54
56
|
}
|
|
55
57
|
)
|
|
56
58
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from ophyd_async.core import StrictEnum, SupersetEnum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Grating(StrictEnum):
|
|
5
|
+
NI_400 = "400 l/mm Ni"
|
|
6
|
+
NI_1000 = "1000 l/mm Ni"
|
|
7
|
+
PT_600 = "BAD 600 l/mm Pt"
|
|
8
|
+
AU_600 = "600 l/mm Au"
|
|
9
|
+
NO_GRATING = "No Grating"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class LensMode(SupersetEnum):
|
|
13
|
+
LARGE_AREA = "LargeArea"
|
|
14
|
+
HIGH_MAGNIFICATION = "HighMagnification"
|
|
15
|
+
MEDIUM_MAGNIFICATION = "MediumMagnification"
|
|
16
|
+
LOW_MAGNIFICATION = "LowMagnification"
|
|
17
|
+
MEDIUM_ANGULAR_DISPERSION = "MediumAngularDispersion"
|
|
18
|
+
LOW_ANGULAR_DISPERSION = "LowAngularDispersion"
|
|
19
|
+
HIGH_ANGULAR_DISPERSION = "HighAngularDispersion"
|
|
20
|
+
WIDE_ANGLE_MODE = "WideAngleMode"
|
|
21
|
+
MEDIUM_AREA = "MediumArea"
|
|
22
|
+
SMALL_AREA = "SmallArea"
|
|
23
|
+
HIGH_MAGNIFICATION2 = "HighMagnification2"
|
|
24
|
+
NOT_CONNECTED = "Not connected"
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from ophyd_async.core import StrictEnum, SupersetEnum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Grating(StrictEnum):
|
|
5
|
+
AU_400 = "400 l/mm Au"
|
|
6
|
+
AU_600 = "600 l/mm Au"
|
|
7
|
+
PT_600 = "600 l/mm Pt"
|
|
8
|
+
AU_1200 = "1200 l/mm Au"
|
|
9
|
+
ML_1200 = "1200 l/mm ML"
|
|
10
|
+
NO_GRATING = "No Grating"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class LensMode(SupersetEnum):
|
|
14
|
+
SMALL_AREA = "SmallArea"
|
|
15
|
+
ANGLE_RESOLVED_MODE_22 = "AngleResolvedMode22"
|
|
16
|
+
ANGLE_RESOLVED_MODE_30 = "AngleResolvedMode30"
|
|
17
|
+
LARGE_AREA = "LargeArea"
|
|
18
|
+
NOT_CONNECTED = "Not connected"
|
|
@@ -1,42 +1,44 @@
|
|
|
1
|
-
from ophyd_async.core import
|
|
1
|
+
from ophyd_async.core import StrictEnum
|
|
2
2
|
from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
|
|
3
3
|
from ophyd_async.epics.motor import Motor
|
|
4
4
|
|
|
5
|
+
from dodal.devices.motors import XYZStage
|
|
6
|
+
|
|
5
7
|
|
|
6
8
|
class ShutterState(StrictEnum):
|
|
7
9
|
CLOSED = "Closed"
|
|
8
10
|
OPEN = "Open"
|
|
9
11
|
|
|
10
12
|
|
|
11
|
-
class DetectorMotion(
|
|
13
|
+
class DetectorMotion(XYZStage):
|
|
14
|
+
_device_prefix = "-MO-DET-01:"
|
|
15
|
+
_pmac_prefix = "-MO-PMAC-02:"
|
|
16
|
+
|
|
12
17
|
def __init__(self, prefix: str, name: str = ""):
|
|
13
|
-
device_prefix = "
|
|
14
|
-
pmac_prefix = "
|
|
18
|
+
device_prefix = f"{prefix}{self._device_prefix}"
|
|
19
|
+
pmac_prefix = f"{prefix}{self._pmac_prefix}"
|
|
15
20
|
|
|
16
|
-
self.upstream_x = Motor(f"{
|
|
17
|
-
self.downstream_x = Motor(f"{
|
|
18
|
-
self.
|
|
19
|
-
self.y = Motor(f"{prefix}{device_prefix}Y")
|
|
20
|
-
self.z = Motor(f"{prefix}{device_prefix}Z")
|
|
21
|
-
self.yaw = Motor(f"{prefix}{device_prefix}YAW")
|
|
21
|
+
self.upstream_x = Motor(f"{device_prefix}UPSTREAMX")
|
|
22
|
+
self.downstream_x = Motor(f"{device_prefix}DOWNSTREAMX")
|
|
23
|
+
self.yaw = Motor(f"{device_prefix}YAW")
|
|
22
24
|
|
|
23
25
|
self.shutter = epics_signal_rw(
|
|
24
|
-
ShutterState, f"{
|
|
26
|
+
ShutterState, f"{device_prefix}SET_SHUTTER_STATE"
|
|
25
27
|
)
|
|
26
28
|
self.shutter_closed_lim = epics_signal_r(
|
|
27
|
-
float, f"{
|
|
29
|
+
float, f"{device_prefix}CLOSE_LIMIT"
|
|
28
30
|
) # on limit = 1, off = 0
|
|
29
31
|
self.shutter_open_lim = epics_signal_r(
|
|
30
|
-
float, f"{
|
|
32
|
+
float, f"{device_prefix}OPEN_LIMIT"
|
|
31
33
|
) # on limit = 1, off = 0
|
|
32
34
|
self.z_disabled = epics_signal_r(
|
|
33
|
-
float, f"{
|
|
35
|
+
float, f"{device_prefix}Z:DISABLED"
|
|
34
36
|
) # robot interlock, 0=ok to move, 1=blocked
|
|
35
37
|
self.crate_power = epics_signal_r(
|
|
36
|
-
float, f"{
|
|
38
|
+
float, f"{pmac_prefix}CRATE2_HEALTHY"
|
|
37
39
|
) # returns 0 if no power
|
|
38
40
|
self.in_robot_load_safe_position = epics_signal_r(
|
|
39
|
-
int, f"{
|
|
41
|
+
int, f"{pmac_prefix}GPIO_INP_BITS.B2"
|
|
40
42
|
) # returns 1 if safe
|
|
41
43
|
|
|
42
|
-
super().__init__(name)
|
|
44
|
+
super().__init__(device_prefix, name)
|
|
@@ -8,6 +8,8 @@ from .base_region import (
|
|
|
8
8
|
AbstractBaseSequence,
|
|
9
9
|
TAbstractBaseRegion,
|
|
10
10
|
TAbstractBaseSequence,
|
|
11
|
+
TAcquisitionMode,
|
|
12
|
+
TLensMode,
|
|
11
13
|
)
|
|
12
14
|
|
|
13
15
|
__all__ = [
|
|
@@ -15,6 +17,8 @@ __all__ = [
|
|
|
15
17
|
"AbstractBaseSequence",
|
|
16
18
|
"TAbstractBaseRegion",
|
|
17
19
|
"TAbstractBaseSequence",
|
|
20
|
+
"TAcquisitionMode",
|
|
21
|
+
"TLensMode",
|
|
18
22
|
"AbstractAnalyserDriverIO",
|
|
19
23
|
"AbstractElectronAnalyserDetector",
|
|
20
24
|
"AbstractAnalyserDriverIO",
|
|
@@ -1,25 +1,26 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
from abc import ABC, abstractmethod
|
|
3
|
+
from collections.abc import Mapping
|
|
3
4
|
from typing import Generic, TypeVar
|
|
4
5
|
|
|
5
6
|
import numpy as np
|
|
6
|
-
from bluesky.protocols import Movable
|
|
7
|
+
from bluesky.protocols import Movable
|
|
7
8
|
from ophyd_async.core import (
|
|
8
9
|
Array1D,
|
|
9
10
|
AsyncStatus,
|
|
10
11
|
SignalR,
|
|
11
12
|
StandardReadable,
|
|
12
13
|
StandardReadableFormat,
|
|
13
|
-
StrictEnum,
|
|
14
14
|
derived_signal_r,
|
|
15
15
|
soft_signal_rw,
|
|
16
16
|
)
|
|
17
17
|
from ophyd_async.epics.adcore import ADBaseIO
|
|
18
18
|
from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
|
|
19
|
-
from ophyd_async.epics.motor import Motor
|
|
20
19
|
|
|
21
20
|
from dodal.devices.electron_analyser.abstract.base_region import (
|
|
22
21
|
TAbstractBaseRegion,
|
|
22
|
+
TAcquisitionMode,
|
|
23
|
+
TLensMode,
|
|
23
24
|
)
|
|
24
25
|
from dodal.devices.electron_analyser.enums import EnergyMode
|
|
25
26
|
from dodal.devices.electron_analyser.util import to_binding_energy, to_kinetic_energy
|
|
@@ -29,9 +30,8 @@ class AbstractAnalyserDriverIO(
|
|
|
29
30
|
ABC,
|
|
30
31
|
StandardReadable,
|
|
31
32
|
ADBaseIO,
|
|
32
|
-
Preparable,
|
|
33
33
|
Movable[TAbstractBaseRegion],
|
|
34
|
-
Generic[TAbstractBaseRegion],
|
|
34
|
+
Generic[TAbstractBaseRegion, TAcquisitionMode, TLensMode],
|
|
35
35
|
):
|
|
36
36
|
"""
|
|
37
37
|
Generic device to configure electron analyser with new region settings.
|
|
@@ -39,8 +39,30 @@ class AbstractAnalyserDriverIO(
|
|
|
39
39
|
"""
|
|
40
40
|
|
|
41
41
|
def __init__(
|
|
42
|
-
self,
|
|
42
|
+
self,
|
|
43
|
+
prefix: str,
|
|
44
|
+
acquisition_mode_type: type[TAcquisitionMode],
|
|
45
|
+
lens_mode_type: type[TLensMode],
|
|
46
|
+
energy_sources: Mapping[str, SignalR[float]],
|
|
47
|
+
name: str = "",
|
|
43
48
|
) -> None:
|
|
49
|
+
"""
|
|
50
|
+
Constructor method for setting up electron analyser.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
prefix: Base PV to connect to EPICS for this device.
|
|
54
|
+
acquisition_mode_type: Enum that determines the available acquisition modes
|
|
55
|
+
for this device.
|
|
56
|
+
lens_mode_type: Enum that determines the available lens mode for this
|
|
57
|
+
device.
|
|
58
|
+
energy_sources: Map that pairs a source name to an energy value signal
|
|
59
|
+
(in eV).
|
|
60
|
+
name: Name of the device.
|
|
61
|
+
"""
|
|
62
|
+
self.energy_sources = energy_sources
|
|
63
|
+
self.acquisition_mode_type = acquisition_mode_type
|
|
64
|
+
self.lens_mode_type = lens_mode_type
|
|
65
|
+
|
|
44
66
|
with self.add_children_as_readables():
|
|
45
67
|
self.image = epics_signal_r(Array1D[np.float64], prefix + "IMAGE")
|
|
46
68
|
self.spectrum = epics_signal_r(Array1D[np.float64], prefix + "INT_SPECTRUM")
|
|
@@ -58,7 +80,7 @@ class AbstractAnalyserDriverIO(
|
|
|
58
80
|
self.low_energy = epics_signal_rw(float, prefix + "LOW_ENERGY")
|
|
59
81
|
self.high_energy = epics_signal_rw(float, prefix + "HIGH_ENERGY")
|
|
60
82
|
self.slices = epics_signal_rw(int, prefix + "SLICES")
|
|
61
|
-
self.lens_mode = epics_signal_rw(
|
|
83
|
+
self.lens_mode = epics_signal_rw(lens_mode_type, prefix + "LENS_MODE")
|
|
62
84
|
self.pass_energy = epics_signal_rw(
|
|
63
85
|
self.pass_energy_type, prefix + "PASS_ENERGY"
|
|
64
86
|
)
|
|
@@ -92,26 +114,6 @@ class AbstractAnalyserDriverIO(
|
|
|
92
114
|
|
|
93
115
|
super().__init__(prefix=prefix, name=name)
|
|
94
116
|
|
|
95
|
-
@AsyncStatus.wrap
|
|
96
|
-
async def prepare(self, value: Motor):
|
|
97
|
-
"""
|
|
98
|
-
Prepare the driver for a region by passing in the energy source motor selected
|
|
99
|
-
by a region.
|
|
100
|
-
|
|
101
|
-
Args:
|
|
102
|
-
value: The motor that contains the information on the current excitation
|
|
103
|
-
energy. Needed to prepare region for epics to accuratly calculate
|
|
104
|
-
kinetic energy for an energy scan when in binding energy mode.
|
|
105
|
-
"""
|
|
106
|
-
energy_source = value
|
|
107
|
-
excitation_energy_value = await energy_source.user_readback.get_value() # eV
|
|
108
|
-
excitation_energy_source_name = energy_source.name
|
|
109
|
-
|
|
110
|
-
await asyncio.gather(
|
|
111
|
-
self.excitation_energy.set(excitation_energy_value),
|
|
112
|
-
self.excitation_energy_source.set(excitation_energy_source_name),
|
|
113
|
-
)
|
|
114
|
-
|
|
115
117
|
@AsyncStatus.wrap
|
|
116
118
|
async def set(self, region: TAbstractBaseRegion):
|
|
117
119
|
"""
|
|
@@ -121,10 +123,13 @@ class AbstractAnalyserDriverIO(
|
|
|
121
123
|
Args:
|
|
122
124
|
region: Contains the parameters to setup the driver for a scan.
|
|
123
125
|
"""
|
|
126
|
+
|
|
127
|
+
source = self._get_energy_source(region.excitation_energy_source)
|
|
128
|
+
excitation_energy = await source.get_value() # eV
|
|
129
|
+
|
|
124
130
|
pass_energy_type = self.pass_energy_type
|
|
125
131
|
pass_energy = pass_energy_type(region.pass_energy)
|
|
126
132
|
|
|
127
|
-
excitation_energy = await self.excitation_energy.get_value()
|
|
128
133
|
low_energy = to_kinetic_energy(
|
|
129
134
|
region.low_energy, region.energy_mode, excitation_energy
|
|
130
135
|
)
|
|
@@ -141,8 +146,19 @@ class AbstractAnalyserDriverIO(
|
|
|
141
146
|
self.pass_energy.set(pass_energy),
|
|
142
147
|
self.iterations.set(region.iterations),
|
|
143
148
|
self.acquisition_mode.set(region.acquisition_mode),
|
|
149
|
+
self.excitation_energy.set(excitation_energy),
|
|
150
|
+
self.excitation_energy_source.set(source.name),
|
|
144
151
|
)
|
|
145
152
|
|
|
153
|
+
def _get_energy_source(self, alias_name: str) -> SignalR[float]:
|
|
154
|
+
energy_source = self.energy_sources.get(alias_name)
|
|
155
|
+
if energy_source is None:
|
|
156
|
+
raise KeyError(
|
|
157
|
+
f"'{energy_source}' is an invalid energy source. Avaliable energy "
|
|
158
|
+
+ f"sources are '{list(self.energy_sources.keys())}'"
|
|
159
|
+
)
|
|
160
|
+
return energy_source
|
|
161
|
+
|
|
146
162
|
@abstractmethod
|
|
147
163
|
def _create_angle_axis_signal(self, prefix: str) -> SignalR[Array1D[np.float64]]:
|
|
148
164
|
"""
|
|
@@ -3,13 +3,11 @@ from abc import ABC
|
|
|
3
3
|
from collections.abc import Callable
|
|
4
4
|
from typing import Generic, TypeVar
|
|
5
5
|
|
|
6
|
-
from ophyd_async.core import StrictEnum
|
|
6
|
+
from ophyd_async.core import StrictEnum, SupersetEnum
|
|
7
7
|
from pydantic import BaseModel, Field, model_validator
|
|
8
8
|
|
|
9
9
|
from dodal.devices.electron_analyser.enums import EnergyMode
|
|
10
10
|
|
|
11
|
-
TStrictEnum = TypeVar("TStrictEnum", bound=StrictEnum)
|
|
12
|
-
|
|
13
11
|
|
|
14
12
|
def java_to_python_case(java_str: str) -> str:
|
|
15
13
|
"""
|
|
@@ -46,7 +44,18 @@ def energy_mode_validation(data: dict) -> dict:
|
|
|
46
44
|
return data
|
|
47
45
|
|
|
48
46
|
|
|
49
|
-
|
|
47
|
+
TAcquisitionMode = TypeVar("TAcquisitionMode", bound=StrictEnum)
|
|
48
|
+
# Allow SupersetEnum. Specs analysers can connect to Lens mode separately to the
|
|
49
|
+
# analyser which leaves the enum to either be "Not connected" OR the available enums
|
|
50
|
+
# when connected.
|
|
51
|
+
TLensMode = TypeVar("TLensMode", bound=SupersetEnum | StrictEnum)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class AbstractBaseRegion(
|
|
55
|
+
ABC,
|
|
56
|
+
JavaToPythonModel,
|
|
57
|
+
Generic[TAcquisitionMode, TLensMode],
|
|
58
|
+
):
|
|
50
59
|
"""
|
|
51
60
|
Generic region model that holds the data. Specialised region models should inherit
|
|
52
61
|
this to extend functionality. All energy units are assumed to be in eV.
|
|
@@ -58,9 +67,9 @@ class AbstractBaseRegion(ABC, JavaToPythonModel, Generic[TStrictEnum]):
|
|
|
58
67
|
iterations: int = 1
|
|
59
68
|
excitation_energy_source: str = "source1"
|
|
60
69
|
# These ones we need subclasses to provide default values
|
|
61
|
-
lens_mode:
|
|
70
|
+
lens_mode: TLensMode
|
|
62
71
|
pass_energy: int
|
|
63
|
-
acquisition_mode:
|
|
72
|
+
acquisition_mode: TAcquisitionMode
|
|
64
73
|
low_energy: float
|
|
65
74
|
high_energy: float
|
|
66
75
|
step_time: float
|
|
@@ -83,7 +92,11 @@ class AbstractBaseRegion(ABC, JavaToPythonModel, Generic[TStrictEnum]):
|
|
|
83
92
|
TAbstractBaseRegion = TypeVar("TAbstractBaseRegion", bound=AbstractBaseRegion)
|
|
84
93
|
|
|
85
94
|
|
|
86
|
-
class AbstractBaseSequence(
|
|
95
|
+
class AbstractBaseSequence(
|
|
96
|
+
ABC,
|
|
97
|
+
JavaToPythonModel,
|
|
98
|
+
Generic[TAbstractBaseRegion, TLensMode],
|
|
99
|
+
):
|
|
87
100
|
"""
|
|
88
101
|
Generic sequence model that holds the list of region data. Specialised sequence
|
|
89
102
|
models should inherit this to extend functionality and define type of region to
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
from typing import Generic, TypeVar
|
|
2
2
|
|
|
3
|
-
from bluesky.protocols import Preparable
|
|
4
3
|
from ophyd_async.core import (
|
|
5
4
|
AsyncStatus,
|
|
6
5
|
Reference,
|
|
7
6
|
)
|
|
8
|
-
from ophyd_async.epics.motor import Motor
|
|
9
7
|
|
|
10
8
|
from dodal.common.data_util import load_json_file_to_class
|
|
11
9
|
from dodal.devices.electron_analyser.abstract.base_detector import (
|
|
@@ -22,7 +20,6 @@ from dodal.devices.electron_analyser.abstract.base_region import (
|
|
|
22
20
|
|
|
23
21
|
class ElectronAnalyserRegionDetector(
|
|
24
22
|
AbstractElectronAnalyserDetector[TAbstractAnalyserDriverIO],
|
|
25
|
-
Preparable,
|
|
26
23
|
Generic[TAbstractAnalyserDriverIO, TAbstractBaseRegion],
|
|
27
24
|
):
|
|
28
25
|
"""
|
|
@@ -48,16 +45,10 @@ class ElectronAnalyserRegionDetector(
|
|
|
48
45
|
return self._driver_ref()
|
|
49
46
|
|
|
50
47
|
@AsyncStatus.wrap
|
|
51
|
-
async def
|
|
52
|
-
|
|
53
|
-
Prepare driver with the region stored and energy_source motor.
|
|
54
|
-
|
|
55
|
-
Args:
|
|
56
|
-
value: The excitation energy source that the region has selected.
|
|
57
|
-
"""
|
|
58
|
-
excitation_energy_source = value
|
|
59
|
-
await self.driver.prepare(excitation_energy_source)
|
|
48
|
+
async def trigger(self) -> None:
|
|
49
|
+
# Configure region parameters on the driver first before data collection.
|
|
60
50
|
await self.driver.set(self.region)
|
|
51
|
+
await super().trigger()
|
|
61
52
|
|
|
62
53
|
|
|
63
54
|
TElectronAnalyserRegionDetector = TypeVar(
|
|
@@ -82,7 +73,6 @@ class ElectronAnalyserDetector(
|
|
|
82
73
|
|
|
83
74
|
def __init__(
|
|
84
75
|
self,
|
|
85
|
-
prefix: str,
|
|
86
76
|
sequence_class: type[TAbstractBaseSequence],
|
|
87
77
|
driver: TAbstractAnalyserDriverIO,
|
|
88
78
|
name: str = "",
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
from collections.abc import Mapping
|
|
2
|
+
from typing import Generic
|
|
3
|
+
|
|
4
|
+
from ophyd_async.core import SignalR
|
|
5
|
+
|
|
6
|
+
from dodal.devices.electron_analyser.abstract.base_driver_io import TLensMode
|
|
1
7
|
from dodal.devices.electron_analyser.detector import (
|
|
2
8
|
ElectronAnalyserDetector,
|
|
3
9
|
)
|
|
@@ -6,8 +12,22 @@ from dodal.devices.electron_analyser.specs.region import SpecsRegion, SpecsSeque
|
|
|
6
12
|
|
|
7
13
|
|
|
8
14
|
class SpecsDetector(
|
|
9
|
-
ElectronAnalyserDetector[
|
|
15
|
+
ElectronAnalyserDetector[
|
|
16
|
+
SpecsAnalyserDriverIO[TLensMode],
|
|
17
|
+
SpecsSequence[TLensMode],
|
|
18
|
+
SpecsRegion[TLensMode],
|
|
19
|
+
],
|
|
20
|
+
Generic[TLensMode],
|
|
10
21
|
):
|
|
11
|
-
def __init__(
|
|
12
|
-
|
|
13
|
-
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
prefix: str,
|
|
25
|
+
lens_mode_type: type[TLensMode],
|
|
26
|
+
energy_sources: Mapping[str, SignalR[float]],
|
|
27
|
+
name: str = "",
|
|
28
|
+
):
|
|
29
|
+
driver = SpecsAnalyserDriverIO[TLensMode](
|
|
30
|
+
prefix, lens_mode_type, energy_sources
|
|
31
|
+
)
|
|
32
|
+
seq = SpecsSequence[lens_mode_type]
|
|
33
|
+
super().__init__(seq, driver, name)
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
from collections.abc import Mapping
|
|
3
|
+
from typing import Generic
|
|
2
4
|
|
|
3
5
|
import numpy as np
|
|
4
6
|
from ophyd_async.core import (
|
|
@@ -13,12 +15,22 @@ from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
|
|
|
13
15
|
from dodal.devices.electron_analyser.abstract.base_driver_io import (
|
|
14
16
|
AbstractAnalyserDriverIO,
|
|
15
17
|
)
|
|
18
|
+
from dodal.devices.electron_analyser.abstract.base_region import TLensMode
|
|
16
19
|
from dodal.devices.electron_analyser.specs.enums import AcquisitionMode
|
|
17
20
|
from dodal.devices.electron_analyser.specs.region import SpecsRegion
|
|
18
21
|
|
|
19
22
|
|
|
20
|
-
class SpecsAnalyserDriverIO(
|
|
21
|
-
|
|
23
|
+
class SpecsAnalyserDriverIO(
|
|
24
|
+
AbstractAnalyserDriverIO[SpecsRegion, AcquisitionMode, TLensMode],
|
|
25
|
+
Generic[TLensMode],
|
|
26
|
+
):
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
prefix: str,
|
|
30
|
+
lens_mode_type: type[TLensMode],
|
|
31
|
+
energy_sources: Mapping[str, SignalR[float]],
|
|
32
|
+
name: str = "",
|
|
33
|
+
) -> None:
|
|
22
34
|
with self.add_children_as_readables(StandardReadableFormat.CONFIG_SIGNAL):
|
|
23
35
|
# Used for setting up region data acquisition.
|
|
24
36
|
self.psu_mode = epics_signal_rw(str, prefix + "SCAN_RANGE")
|
|
@@ -28,11 +40,14 @@ class SpecsAnalyserDriverIO(AbstractAnalyserDriverIO[SpecsRegion]):
|
|
|
28
40
|
# Used to read detector data after acqusition.
|
|
29
41
|
self.min_angle_axis = epics_signal_r(float, prefix + "Y_MIN_RBV")
|
|
30
42
|
self.max_angle_axis = epics_signal_r(float, prefix + "Y_MAX_RBV")
|
|
43
|
+
self.energy_channels = epics_signal_r(
|
|
44
|
+
int, prefix + "TOTAL_POINTS_ITERATION_RBV"
|
|
45
|
+
)
|
|
31
46
|
|
|
32
|
-
super().__init__(prefix, AcquisitionMode, name)
|
|
47
|
+
super().__init__(prefix, AcquisitionMode, lens_mode_type, energy_sources, name)
|
|
33
48
|
|
|
34
49
|
@AsyncStatus.wrap
|
|
35
|
-
async def set(self, region: SpecsRegion):
|
|
50
|
+
async def set(self, region: SpecsRegion[TLensMode]):
|
|
36
51
|
await super().set(region)
|
|
37
52
|
|
|
38
53
|
await asyncio.gather(
|
|
@@ -70,7 +85,7 @@ class SpecsAnalyserDriverIO(AbstractAnalyserDriverIO[SpecsRegion]):
|
|
|
70
85
|
"eV",
|
|
71
86
|
min_energy=self.low_energy,
|
|
72
87
|
max_energy=self.high_energy,
|
|
73
|
-
total_points_iterations=self.
|
|
88
|
+
total_points_iterations=self.energy_channels,
|
|
74
89
|
)
|
|
75
90
|
return energy_axis
|
|
76
91
|
|
|
@@ -1,15 +1,18 @@
|
|
|
1
|
+
from typing import Generic
|
|
2
|
+
|
|
1
3
|
from pydantic import Field
|
|
2
4
|
|
|
3
5
|
from dodal.devices.electron_analyser.abstract.base_region import (
|
|
4
6
|
AbstractBaseRegion,
|
|
5
7
|
AbstractBaseSequence,
|
|
8
|
+
TLensMode,
|
|
6
9
|
)
|
|
7
10
|
from dodal.devices.electron_analyser.specs.enums import AcquisitionMode
|
|
8
11
|
|
|
9
12
|
|
|
10
|
-
class SpecsRegion(AbstractBaseRegion[AcquisitionMode]):
|
|
13
|
+
class SpecsRegion(AbstractBaseRegion[AcquisitionMode, TLensMode], Generic[TLensMode]):
|
|
11
14
|
# Override base class with defaults
|
|
12
|
-
lens_mode:
|
|
15
|
+
lens_mode: TLensMode
|
|
13
16
|
pass_energy: int = 5
|
|
14
17
|
acquisition_mode: AcquisitionMode = AcquisitionMode.FIXED_TRANSMISSION
|
|
15
18
|
low_energy: float = Field(default=800, alias="start_energy")
|
|
@@ -19,9 +22,10 @@ class SpecsRegion(AbstractBaseRegion[AcquisitionMode]):
|
|
|
19
22
|
# Specific to this class
|
|
20
23
|
values: int = 1
|
|
21
24
|
centre_energy: float = 0
|
|
22
|
-
|
|
25
|
+
# ToDo - Update to an enum https://github.com/DiamondLightSource/dodal/issues/1328
|
|
26
|
+
psu_mode: str = "1.5kV"
|
|
23
27
|
estimated_time_in_ms: float = 0
|
|
24
28
|
|
|
25
29
|
|
|
26
|
-
class SpecsSequence(AbstractBaseSequence[SpecsRegion]):
|
|
27
|
-
regions: list[SpecsRegion] = Field(default_factory=lambda: [])
|
|
30
|
+
class SpecsSequence(AbstractBaseSequence[SpecsRegion, TLensMode], Generic[TLensMode]):
|
|
31
|
+
regions: list[SpecsRegion[TLensMode]] = Field(default_factory=lambda: [])
|