dls-dodal 1.53.0__py3-none-any.whl → 1.55.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.53.0.dist-info → dls_dodal-1.55.0.dist-info}/METADATA +4 -6
- {dls_dodal-1.53.0.dist-info → dls_dodal-1.55.0.dist-info}/RECORD +74 -66
- dodal/_version.py +16 -3
- dodal/beamline_specific_utils/i05_shared.py +11 -0
- dodal/beamlines/__init__.py +1 -0
- dodal/beamlines/aithre.py +2 -3
- dodal/beamlines/b01_1.py +2 -2
- dodal/beamlines/b07.py +6 -3
- dodal/beamlines/b07_1.py +17 -6
- dodal/beamlines/b16.py +0 -1
- dodal/beamlines/b21.py +4 -6
- dodal/beamlines/i03.py +10 -31
- dodal/beamlines/i04.py +19 -45
- dodal/beamlines/i05.py +22 -0
- dodal/beamlines/i05_1.py +22 -0
- dodal/beamlines/i09.py +7 -3
- dodal/beamlines/i09_1.py +6 -3
- dodal/beamlines/i13_1.py +0 -1
- dodal/beamlines/i19_1.py +1 -2
- dodal/beamlines/i19_2.py +0 -1
- dodal/beamlines/i19_optics.py +1 -4
- dodal/beamlines/i20_1.py +38 -9
- dodal/beamlines/i23.py +1 -2
- dodal/beamlines/i24.py +0 -12
- dodal/beamlines/p60.py +13 -3
- dodal/common/beamlines/beamline_parameters.py +1 -1
- dodal/common/beamlines/device_helpers.py +0 -33
- dodal/devices/aithre_lasershaping/__init__.py +0 -0
- dodal/devices/aithre_lasershaping/goniometer.py +3 -26
- dodal/devices/aithre_lasershaping/laser_robot.py +2 -2
- dodal/devices/b07/__init__.py +2 -2
- dodal/devices/b07/enums.py +15 -0
- dodal/devices/b07_1/__init__.py +10 -1
- dodal/devices/b07_1/ccmc.py +79 -0
- dodal/devices/b07_1/enums.py +3 -0
- dodal/devices/electron_analyser/abstract/base_driver_io.py +25 -48
- dodal/devices/electron_analyser/abstract/base_region.py +9 -11
- dodal/devices/electron_analyser/abstract/types.py +12 -0
- dodal/devices/electron_analyser/specs/detector.py +9 -9
- dodal/devices/electron_analyser/specs/driver_io.py +54 -21
- dodal/devices/electron_analyser/specs/region.py +13 -8
- dodal/devices/electron_analyser/types.py +15 -6
- dodal/devices/electron_analyser/vgscienta/detector.py +18 -8
- dodal/devices/electron_analyser/vgscienta/driver_io.py +62 -24
- dodal/devices/electron_analyser/vgscienta/region.py +33 -16
- dodal/devices/focusing_mirror.py +1 -1
- dodal/devices/i03/undulator_dcm.py +8 -3
- dodal/devices/i05/__init__.py +3 -0
- dodal/devices/i05/enums.py +8 -0
- dodal/devices/i09/__init__.py +2 -2
- dodal/devices/i09/enums.py +16 -0
- dodal/devices/i09_1/__init__.py +2 -2
- dodal/devices/i09_1/enums.py +13 -0
- dodal/devices/i10/mirrors.py +2 -6
- dodal/devices/i13_1/merlin_controller.py +1 -1
- dodal/devices/i19/beamstop.py +2 -2
- dodal/devices/i24/aperture.py +1 -1
- dodal/devices/motors.py +75 -1
- dodal/devices/oav/oav_to_redis_forwarder.py +1 -1
- dodal/devices/oav/pin_image_recognition/__init__.py +1 -2
- dodal/devices/p60/__init__.py +8 -2
- dodal/devices/p60/enums.py +16 -0
- dodal/devices/robot.py +7 -4
- dodal/devices/tetramm.py +1 -2
- dodal/devices/webcam.py +2 -1
- dodal/devices/zebra/zebra.py +1 -1
- dodal/devices/zebra/zebra_controlled_shutter.py +1 -1
- dodal/devices/zocalo/__init__.py +2 -0
- dodal/devices/zocalo/zocalo_results.py +16 -26
- dodal/utils.py +3 -10
- {dls_dodal-1.53.0.dist-info → dls_dodal-1.55.0.dist-info}/WHEEL +0 -0
- {dls_dodal-1.53.0.dist-info → dls_dodal-1.55.0.dist-info}/entry_points.txt +0 -0
- {dls_dodal-1.53.0.dist-info → dls_dodal-1.55.0.dist-info}/licenses/LICENSE +0 -0
- {dls_dodal-1.53.0.dist-info → dls_dodal-1.55.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from typing import TypeVar
|
|
2
|
+
|
|
3
|
+
from ophyd_async.core import StrictEnum, SupersetEnum
|
|
4
|
+
|
|
5
|
+
TAcquisitionMode = TypeVar("TAcquisitionMode", bound=StrictEnum)
|
|
6
|
+
# Allow SupersetEnum. Specs analysers can connect to Lens and Psu mode separately to the
|
|
7
|
+
# analyser which leaves the enum to either be "Not connected" OR the available enums
|
|
8
|
+
# when connected.
|
|
9
|
+
TLensMode = TypeVar("TLensMode", bound=SupersetEnum | StrictEnum)
|
|
10
|
+
TPsuMode = TypeVar("TPsuMode", bound=SupersetEnum | StrictEnum)
|
|
11
|
+
TPassEnergy = TypeVar("TPassEnergy", bound=StrictEnum | float)
|
|
12
|
+
TPassEnergyEnum = TypeVar("TPassEnergyEnum", bound=StrictEnum)
|
|
@@ -3,7 +3,7 @@ from typing import Generic
|
|
|
3
3
|
|
|
4
4
|
from ophyd_async.core import SignalR
|
|
5
5
|
|
|
6
|
-
from dodal.devices.electron_analyser.abstract.
|
|
6
|
+
from dodal.devices.electron_analyser.abstract.types import TLensMode, TPsuMode
|
|
7
7
|
from dodal.devices.electron_analyser.detector import (
|
|
8
8
|
ElectronAnalyserDetector,
|
|
9
9
|
)
|
|
@@ -13,21 +13,21 @@ from dodal.devices.electron_analyser.specs.region import SpecsRegion, SpecsSeque
|
|
|
13
13
|
|
|
14
14
|
class SpecsDetector(
|
|
15
15
|
ElectronAnalyserDetector[
|
|
16
|
-
SpecsAnalyserDriverIO[TLensMode],
|
|
17
|
-
SpecsSequence[TLensMode],
|
|
18
|
-
SpecsRegion[TLensMode],
|
|
16
|
+
SpecsAnalyserDriverIO[TLensMode, TPsuMode],
|
|
17
|
+
SpecsSequence[TLensMode, TPsuMode],
|
|
18
|
+
SpecsRegion[TLensMode, TPsuMode],
|
|
19
19
|
],
|
|
20
|
-
Generic[TLensMode],
|
|
20
|
+
Generic[TLensMode, TPsuMode],
|
|
21
21
|
):
|
|
22
22
|
def __init__(
|
|
23
23
|
self,
|
|
24
24
|
prefix: str,
|
|
25
25
|
lens_mode_type: type[TLensMode],
|
|
26
|
+
psu_mode_type: type[TPsuMode],
|
|
26
27
|
energy_sources: Mapping[str, SignalR[float]],
|
|
27
28
|
name: str = "",
|
|
28
29
|
):
|
|
29
|
-
driver = SpecsAnalyserDriverIO[TLensMode](
|
|
30
|
-
prefix, lens_mode_type, energy_sources
|
|
30
|
+
driver = SpecsAnalyserDriverIO[TLensMode, TPsuMode](
|
|
31
|
+
prefix, lens_mode_type, psu_mode_type, energy_sources
|
|
31
32
|
)
|
|
32
|
-
|
|
33
|
-
super().__init__(seq, driver, name)
|
|
33
|
+
super().__init__(SpecsSequence[lens_mode_type, psu_mode_type], driver, name)
|
|
@@ -15,51 +15,88 @@ from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
|
|
|
15
15
|
from dodal.devices.electron_analyser.abstract.base_driver_io import (
|
|
16
16
|
AbstractAnalyserDriverIO,
|
|
17
17
|
)
|
|
18
|
-
from dodal.devices.electron_analyser.abstract.
|
|
18
|
+
from dodal.devices.electron_analyser.abstract.types import TLensMode, TPsuMode
|
|
19
19
|
from dodal.devices.electron_analyser.specs.enums import AcquisitionMode
|
|
20
20
|
from dodal.devices.electron_analyser.specs.region import SpecsRegion
|
|
21
|
+
from dodal.devices.electron_analyser.util import to_kinetic_energy
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
class SpecsAnalyserDriverIO(
|
|
24
|
-
AbstractAnalyserDriverIO[
|
|
25
|
-
|
|
25
|
+
AbstractAnalyserDriverIO[
|
|
26
|
+
SpecsRegion[TLensMode, TPsuMode],
|
|
27
|
+
AcquisitionMode,
|
|
28
|
+
TLensMode,
|
|
29
|
+
TPsuMode,
|
|
30
|
+
float,
|
|
31
|
+
],
|
|
32
|
+
Generic[TLensMode, TPsuMode],
|
|
26
33
|
):
|
|
27
34
|
def __init__(
|
|
28
35
|
self,
|
|
29
36
|
prefix: str,
|
|
30
37
|
lens_mode_type: type[TLensMode],
|
|
38
|
+
psu_mode_type: type[TPsuMode],
|
|
31
39
|
energy_sources: Mapping[str, SignalR[float]],
|
|
32
40
|
name: str = "",
|
|
33
41
|
) -> None:
|
|
34
42
|
with self.add_children_as_readables(StandardReadableFormat.CONFIG_SIGNAL):
|
|
35
43
|
# Used for setting up region data acquisition.
|
|
36
|
-
self.psu_mode = epics_signal_rw(str, prefix + "SCAN_RANGE")
|
|
37
44
|
self.snapshot_values = epics_signal_rw(int, prefix + "VALUES")
|
|
38
|
-
self.centre_energy = epics_signal_rw(float, prefix + "KINETIC_ENERGY")
|
|
39
45
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
self.energy_channels = epics_signal_r(
|
|
44
|
-
int, prefix + "TOTAL_POINTS_ITERATION_RBV"
|
|
45
|
-
)
|
|
46
|
+
# Used to calculate the angle axis.
|
|
47
|
+
self.min_angle_axis = epics_signal_r(float, prefix + "Y_MIN_RBV")
|
|
48
|
+
self.max_angle_axis = epics_signal_r(float, prefix + "Y_MAX_RBV")
|
|
46
49
|
|
|
47
|
-
|
|
50
|
+
# Used to calculate the energy axis.
|
|
51
|
+
self.energy_channels = epics_signal_r(
|
|
52
|
+
int, prefix + "TOTAL_POINTS_ITERATION_RBV"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
super().__init__(
|
|
56
|
+
prefix=prefix,
|
|
57
|
+
acquisition_mode_type=AcquisitionMode,
|
|
58
|
+
lens_mode_type=lens_mode_type,
|
|
59
|
+
psu_mode_type=psu_mode_type,
|
|
60
|
+
pass_energy_type=float,
|
|
61
|
+
energy_sources=energy_sources,
|
|
62
|
+
name=name,
|
|
63
|
+
)
|
|
48
64
|
|
|
49
65
|
@AsyncStatus.wrap
|
|
50
|
-
async def set(self, region: SpecsRegion[TLensMode]):
|
|
51
|
-
|
|
66
|
+
async def set(self, region: SpecsRegion[TLensMode, TPsuMode]):
|
|
67
|
+
source = self._get_energy_source(region.excitation_energy_source)
|
|
68
|
+
excitation_energy = await source.get_value() # eV
|
|
52
69
|
|
|
70
|
+
low_energy = to_kinetic_energy(
|
|
71
|
+
region.low_energy, region.energy_mode, excitation_energy
|
|
72
|
+
)
|
|
73
|
+
centre_energy = to_kinetic_energy(
|
|
74
|
+
region.centre_energy, region.energy_mode, excitation_energy
|
|
75
|
+
)
|
|
76
|
+
high_energy = to_kinetic_energy(
|
|
77
|
+
region.high_energy, region.energy_mode, excitation_energy
|
|
78
|
+
)
|
|
53
79
|
await asyncio.gather(
|
|
80
|
+
self.region_name.set(region.name),
|
|
81
|
+
self.energy_mode.set(region.energy_mode),
|
|
82
|
+
self.low_energy.set(low_energy),
|
|
83
|
+
self.high_energy.set(high_energy),
|
|
84
|
+
self.slices.set(region.slices),
|
|
85
|
+
self.lens_mode.set(region.lens_mode),
|
|
86
|
+
self.pass_energy.set(region.pass_energy),
|
|
87
|
+
self.iterations.set(region.iterations),
|
|
88
|
+
self.acquisition_mode.set(region.acquisition_mode),
|
|
89
|
+
self.excitation_energy.set(excitation_energy),
|
|
90
|
+
self.excitation_energy_source.set(source.name),
|
|
54
91
|
self.snapshot_values.set(region.values),
|
|
55
92
|
self.psu_mode.set(region.psu_mode),
|
|
56
93
|
)
|
|
57
94
|
if region.acquisition_mode == AcquisitionMode.FIXED_TRANSMISSION:
|
|
58
|
-
await self.centre_energy.set(region.centre_energy)
|
|
59
|
-
|
|
60
|
-
if self.acquisition_mode == AcquisitionMode.FIXED_ENERGY:
|
|
61
95
|
await self.energy_step.set(region.energy_step)
|
|
62
96
|
|
|
97
|
+
if region.acquisition_mode == AcquisitionMode.FIXED_ENERGY:
|
|
98
|
+
await self.centre_energy.set(centre_energy)
|
|
99
|
+
|
|
63
100
|
def _create_angle_axis_signal(self, prefix: str) -> SignalR[Array1D[np.float64]]:
|
|
64
101
|
angle_axis = derived_signal_r(
|
|
65
102
|
self._calculate_angle_axis,
|
|
@@ -97,7 +134,3 @@ class SpecsAnalyserDriverIO(
|
|
|
97
134
|
step = (max_energy - min_energy) / (total_points_iterations - 1)
|
|
98
135
|
axis = np.array([min_energy + i * step for i in range(total_points_iterations)])
|
|
99
136
|
return axis
|
|
100
|
-
|
|
101
|
-
@property
|
|
102
|
-
def pass_energy_type(self) -> type:
|
|
103
|
-
return float
|
|
@@ -5,27 +5,32 @@ from pydantic import Field
|
|
|
5
5
|
from dodal.devices.electron_analyser.abstract.base_region import (
|
|
6
6
|
AbstractBaseRegion,
|
|
7
7
|
AbstractBaseSequence,
|
|
8
|
-
TLensMode,
|
|
9
8
|
)
|
|
9
|
+
from dodal.devices.electron_analyser.abstract.types import TLensMode, TPsuMode
|
|
10
10
|
from dodal.devices.electron_analyser.specs.enums import AcquisitionMode
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
class SpecsRegion(
|
|
13
|
+
class SpecsRegion(
|
|
14
|
+
AbstractBaseRegion[AcquisitionMode, TLensMode, float],
|
|
15
|
+
Generic[TLensMode, TPsuMode],
|
|
16
|
+
):
|
|
14
17
|
# Override base class with defaults
|
|
15
18
|
lens_mode: TLensMode
|
|
16
|
-
pass_energy:
|
|
19
|
+
pass_energy: float = 5
|
|
17
20
|
acquisition_mode: AcquisitionMode = AcquisitionMode.FIXED_TRANSMISSION
|
|
18
21
|
low_energy: float = Field(default=800, alias="start_energy")
|
|
22
|
+
centre_energy: float = 0
|
|
19
23
|
high_energy: float = Field(default=850, alias="end_energy")
|
|
20
24
|
step_time: float = Field(default=1.0, alias="exposure_time")
|
|
21
25
|
energy_step: float = Field(default=0.1, alias="step_energy")
|
|
26
|
+
|
|
22
27
|
# Specific to this class
|
|
23
28
|
values: int = 1
|
|
24
|
-
|
|
25
|
-
# ToDo - Update to an enum https://github.com/DiamondLightSource/dodal/issues/1328
|
|
26
|
-
psu_mode: str = "1.5kV"
|
|
29
|
+
psu_mode: TPsuMode
|
|
27
30
|
estimated_time_in_ms: float = 0
|
|
28
31
|
|
|
29
32
|
|
|
30
|
-
class SpecsSequence(
|
|
31
|
-
|
|
33
|
+
class SpecsSequence(
|
|
34
|
+
AbstractBaseSequence[SpecsRegion[TLensMode, TPsuMode]], Generic[TLensMode, TPsuMode]
|
|
35
|
+
):
|
|
36
|
+
regions: list[SpecsRegion[TLensMode, TPsuMode]] = Field(default_factory=lambda: [])
|
|
@@ -22,27 +22,36 @@ from dodal.devices.electron_analyser.vgscienta.detector import (
|
|
|
22
22
|
|
|
23
23
|
AnyAcqMode = StrictEnum
|
|
24
24
|
AnyLensMode = SupersetEnum | StrictEnum
|
|
25
|
+
AnyPsuMode = SupersetEnum | StrictEnum
|
|
26
|
+
AnyPassEnergy = StrictEnum | float
|
|
27
|
+
AnyPassEnergyEnum = StrictEnum
|
|
25
28
|
|
|
26
29
|
# Electron analyser types that encompasses all implementations, useful for tests and
|
|
27
30
|
# plans
|
|
28
31
|
ElectronAnalyserDetectorImpl = (
|
|
29
|
-
VGScientaDetector[AnyLensMode
|
|
32
|
+
VGScientaDetector[AnyLensMode, AnyPsuMode, AnyPassEnergyEnum]
|
|
33
|
+
| SpecsDetector[AnyLensMode, AnyPsuMode]
|
|
30
34
|
)
|
|
31
35
|
ElectronAnalyserDriverImpl = (
|
|
32
|
-
VGScientaAnalyserDriverIO[AnyLensMode
|
|
36
|
+
VGScientaAnalyserDriverIO[AnyLensMode, AnyPsuMode, AnyPassEnergyEnum]
|
|
37
|
+
| SpecsAnalyserDriverIO[AnyLensMode, AnyPsuMode]
|
|
33
38
|
)
|
|
34
39
|
|
|
35
40
|
# Short hand the type so less verbose
|
|
36
|
-
AbstractBaseRegion = AbstractBaseRegion[AnyAcqMode, AnyLensMode]
|
|
41
|
+
AbstractBaseRegion = AbstractBaseRegion[AnyAcqMode, AnyLensMode, AnyPassEnergy]
|
|
37
42
|
|
|
38
43
|
# Generic electron analyser types that supports full typing with the abstract classes.
|
|
39
44
|
GenericElectronAnalyserDetector = ElectronAnalyserDetector[
|
|
40
|
-
AbstractAnalyserDriverIO[
|
|
41
|
-
|
|
45
|
+
AbstractAnalyserDriverIO[
|
|
46
|
+
AbstractBaseRegion, AnyAcqMode, AnyLensMode, AnyPsuMode, AnyPassEnergy
|
|
47
|
+
],
|
|
48
|
+
AbstractBaseSequence[AbstractBaseRegion],
|
|
42
49
|
AbstractBaseRegion,
|
|
43
50
|
]
|
|
44
51
|
|
|
45
52
|
GenericElectronAnalyserRegionDetector = ElectronAnalyserRegionDetector[
|
|
46
|
-
AbstractAnalyserDriverIO[
|
|
53
|
+
AbstractAnalyserDriverIO[
|
|
54
|
+
AbstractBaseRegion, AnyAcqMode, AnyLensMode, AnyPsuMode, AnyPassEnergy
|
|
55
|
+
],
|
|
47
56
|
AbstractBaseRegion,
|
|
48
57
|
]
|
|
@@ -3,7 +3,11 @@ from typing import Generic
|
|
|
3
3
|
|
|
4
4
|
from ophyd_async.core import SignalR
|
|
5
5
|
|
|
6
|
-
from dodal.devices.electron_analyser.abstract.
|
|
6
|
+
from dodal.devices.electron_analyser.abstract.types import (
|
|
7
|
+
TLensMode,
|
|
8
|
+
TPassEnergyEnum,
|
|
9
|
+
TPsuMode,
|
|
10
|
+
)
|
|
7
11
|
from dodal.devices.electron_analyser.detector import (
|
|
8
12
|
ElectronAnalyserDetector,
|
|
9
13
|
)
|
|
@@ -18,20 +22,26 @@ from dodal.devices.electron_analyser.vgscienta.region import (
|
|
|
18
22
|
|
|
19
23
|
class VGScientaDetector(
|
|
20
24
|
ElectronAnalyserDetector[
|
|
21
|
-
VGScientaAnalyserDriverIO[TLensMode],
|
|
22
|
-
VGScientaSequence[TLensMode],
|
|
23
|
-
VGScientaRegion[TLensMode],
|
|
25
|
+
VGScientaAnalyserDriverIO[TLensMode, TPsuMode, TPassEnergyEnum],
|
|
26
|
+
VGScientaSequence[TLensMode, TPsuMode, TPassEnergyEnum],
|
|
27
|
+
VGScientaRegion[TLensMode, TPassEnergyEnum],
|
|
24
28
|
],
|
|
25
|
-
Generic[TLensMode],
|
|
29
|
+
Generic[TLensMode, TPsuMode, TPassEnergyEnum],
|
|
26
30
|
):
|
|
27
31
|
def __init__(
|
|
28
32
|
self,
|
|
29
33
|
prefix: str,
|
|
30
34
|
lens_mode_type: type[TLensMode],
|
|
35
|
+
psu_mode_type: type[TPsuMode],
|
|
36
|
+
pass_energy_type: type[TPassEnergyEnum],
|
|
31
37
|
energy_sources: Mapping[str, SignalR[float]],
|
|
32
38
|
name: str = "",
|
|
33
39
|
):
|
|
34
|
-
driver = VGScientaAnalyserDriverIO[TLensMode](
|
|
35
|
-
prefix, lens_mode_type, energy_sources
|
|
40
|
+
driver = VGScientaAnalyserDriverIO[TLensMode, TPsuMode, TPassEnergyEnum](
|
|
41
|
+
prefix, lens_mode_type, psu_mode_type, pass_energy_type, energy_sources
|
|
42
|
+
)
|
|
43
|
+
super().__init__(
|
|
44
|
+
VGScientaSequence[lens_mode_type, psu_mode_type, pass_energy_type],
|
|
45
|
+
driver,
|
|
46
|
+
name,
|
|
36
47
|
)
|
|
37
|
-
super().__init__(VGScientaSequence[lens_mode_type], driver, name)
|
|
@@ -15,54 +15,96 @@ from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
|
|
|
15
15
|
from dodal.devices.electron_analyser.abstract.base_driver_io import (
|
|
16
16
|
AbstractAnalyserDriverIO,
|
|
17
17
|
)
|
|
18
|
-
from dodal.devices.electron_analyser.abstract.
|
|
18
|
+
from dodal.devices.electron_analyser.abstract.types import (
|
|
19
|
+
TLensMode,
|
|
20
|
+
TPassEnergyEnum,
|
|
21
|
+
TPsuMode,
|
|
22
|
+
)
|
|
19
23
|
from dodal.devices.electron_analyser.util import to_kinetic_energy
|
|
20
|
-
from dodal.devices.electron_analyser.vgscienta.enums import
|
|
21
|
-
|
|
24
|
+
from dodal.devices.electron_analyser.vgscienta.enums import (
|
|
25
|
+
AcquisitionMode,
|
|
22
26
|
DetectorMode,
|
|
27
|
+
)
|
|
28
|
+
from dodal.devices.electron_analyser.vgscienta.region import (
|
|
23
29
|
VGScientaRegion,
|
|
24
30
|
)
|
|
25
31
|
|
|
26
32
|
|
|
27
33
|
class VGScientaAnalyserDriverIO(
|
|
28
|
-
AbstractAnalyserDriverIO[
|
|
29
|
-
|
|
34
|
+
AbstractAnalyserDriverIO[
|
|
35
|
+
VGScientaRegion[TLensMode, TPassEnergyEnum],
|
|
36
|
+
AcquisitionMode,
|
|
37
|
+
TLensMode,
|
|
38
|
+
TPsuMode,
|
|
39
|
+
TPassEnergyEnum,
|
|
40
|
+
],
|
|
41
|
+
Generic[TLensMode, TPsuMode, TPassEnergyEnum],
|
|
30
42
|
):
|
|
31
43
|
def __init__(
|
|
32
44
|
self,
|
|
33
45
|
prefix: str,
|
|
34
46
|
lens_mode_type: type[TLensMode],
|
|
47
|
+
psu_mode_type: type[TPsuMode],
|
|
48
|
+
pass_energy_type: type[TPassEnergyEnum],
|
|
35
49
|
energy_sources: Mapping[str, SignalR[float]],
|
|
36
50
|
name: str = "",
|
|
37
51
|
) -> None:
|
|
38
52
|
with self.add_children_as_readables(StandardReadableFormat.CONFIG_SIGNAL):
|
|
39
53
|
# Used for setting up region data acquisition.
|
|
40
|
-
self.centre_energy = epics_signal_rw(float, prefix + "CENTRE_ENERGY")
|
|
41
|
-
self.first_x_channel = epics_signal_rw(int, prefix + "MinX")
|
|
42
|
-
self.first_y_channel = epics_signal_rw(int, prefix + "MinY")
|
|
43
|
-
self.x_channel_size = epics_signal_rw(int, prefix + "SizeX")
|
|
44
|
-
self.y_channel_size = epics_signal_rw(int, prefix + "SizeY")
|
|
45
54
|
self.detector_mode = epics_signal_rw(DetectorMode, prefix + "DETECTOR_MODE")
|
|
46
55
|
|
|
47
|
-
|
|
56
|
+
self.region_min_x = epics_signal_rw(int, prefix + "MinX")
|
|
57
|
+
self.region_size_x = epics_signal_rw(int, prefix + "SizeX")
|
|
58
|
+
self.sensor_max_size_x = epics_signal_rw(int, prefix + "MaxSizeX")
|
|
59
|
+
|
|
60
|
+
self.region_min_y = epics_signal_rw(int, prefix + "MinY")
|
|
61
|
+
self.region_size_y = epics_signal_rw(int, prefix + "SizeY")
|
|
62
|
+
self.sensor_max_size_y = epics_signal_rw(int, prefix + "MaxSizeY")
|
|
63
|
+
|
|
64
|
+
super().__init__(
|
|
65
|
+
prefix,
|
|
66
|
+
AcquisitionMode,
|
|
67
|
+
lens_mode_type,
|
|
68
|
+
psu_mode_type,
|
|
69
|
+
pass_energy_type,
|
|
70
|
+
energy_sources,
|
|
71
|
+
name,
|
|
72
|
+
)
|
|
48
73
|
|
|
49
74
|
@AsyncStatus.wrap
|
|
50
|
-
async def set(self, region: VGScientaRegion[TLensMode]):
|
|
51
|
-
|
|
75
|
+
async def set(self, region: VGScientaRegion[TLensMode, TPassEnergyEnum]):
|
|
76
|
+
source = self._get_energy_source(region.excitation_energy_source)
|
|
77
|
+
excitation_energy = await source.get_value() # eV
|
|
52
78
|
|
|
53
|
-
|
|
79
|
+
low_energy = to_kinetic_energy(
|
|
80
|
+
region.low_energy, region.energy_mode, excitation_energy
|
|
81
|
+
)
|
|
54
82
|
centre_energy = to_kinetic_energy(
|
|
55
|
-
region.
|
|
83
|
+
region.centre_energy, region.energy_mode, excitation_energy
|
|
84
|
+
)
|
|
85
|
+
high_energy = to_kinetic_energy(
|
|
86
|
+
region.high_energy, region.energy_mode, excitation_energy
|
|
56
87
|
)
|
|
57
88
|
await asyncio.gather(
|
|
89
|
+
self.region_name.set(region.name),
|
|
90
|
+
self.energy_mode.set(region.energy_mode),
|
|
91
|
+
self.low_energy.set(low_energy),
|
|
58
92
|
self.centre_energy.set(centre_energy),
|
|
93
|
+
self.high_energy.set(high_energy),
|
|
94
|
+
self.slices.set(region.slices),
|
|
95
|
+
self.lens_mode.set(region.lens_mode),
|
|
96
|
+
self.pass_energy.set(region.pass_energy),
|
|
97
|
+
self.iterations.set(region.iterations),
|
|
98
|
+
self.acquisition_mode.set(region.acquisition_mode),
|
|
99
|
+
self.excitation_energy.set(excitation_energy),
|
|
100
|
+
self.excitation_energy_source.set(source.name),
|
|
59
101
|
self.energy_step.set(region.energy_step),
|
|
60
|
-
self.first_x_channel.set(region.first_x_channel),
|
|
61
|
-
self.first_y_channel.set(region.first_y_channel),
|
|
62
|
-
self.x_channel_size.set(region.x_channel_size()),
|
|
63
|
-
self.y_channel_size.set(region.y_channel_size()),
|
|
64
|
-
self.detector_mode.set(region.detector_mode),
|
|
65
102
|
self.image_mode.set(ADImageMode.SINGLE),
|
|
103
|
+
self.detector_mode.set(region.detector_mode),
|
|
104
|
+
self.region_min_x.set(region.min_x),
|
|
105
|
+
self.region_size_x.set(region.size_x),
|
|
106
|
+
self.region_min_y.set(region.min_y),
|
|
107
|
+
self.region_size_y.set(region.size_y),
|
|
66
108
|
)
|
|
67
109
|
|
|
68
110
|
def _create_energy_axis_signal(self, prefix: str) -> SignalR[Array1D[np.float64]]:
|
|
@@ -70,7 +112,3 @@ class VGScientaAnalyserDriverIO(
|
|
|
70
112
|
|
|
71
113
|
def _create_angle_axis_signal(self, prefix: str) -> SignalR[Array1D[np.float64]]:
|
|
72
114
|
return epics_signal_r(Array1D[np.float64], prefix + "Y_SCALE_RBV")
|
|
73
|
-
|
|
74
|
-
@property
|
|
75
|
-
def pass_energy_type(self) -> type:
|
|
76
|
-
return str
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import uuid
|
|
2
2
|
from typing import Generic
|
|
3
3
|
|
|
4
|
-
from pydantic import Field
|
|
4
|
+
from pydantic import Field, field_validator
|
|
5
5
|
|
|
6
6
|
from dodal.devices.electron_analyser.abstract.base_region import (
|
|
7
7
|
AbstractBaseRegion,
|
|
8
8
|
AbstractBaseSequence,
|
|
9
9
|
JavaToPythonModel,
|
|
10
|
+
)
|
|
11
|
+
from dodal.devices.electron_analyser.abstract.types import (
|
|
10
12
|
TLensMode,
|
|
13
|
+
TPassEnergyEnum,
|
|
14
|
+
TPsuMode,
|
|
11
15
|
)
|
|
12
16
|
from dodal.devices.electron_analyser.vgscienta.enums import (
|
|
13
17
|
AcquisitionMode,
|
|
@@ -17,33 +21,43 @@ from dodal.devices.electron_analyser.vgscienta.enums import (
|
|
|
17
21
|
|
|
18
22
|
|
|
19
23
|
class VGScientaRegion(
|
|
20
|
-
AbstractBaseRegion[AcquisitionMode, TLensMode
|
|
24
|
+
AbstractBaseRegion[AcquisitionMode, TLensMode, TPassEnergyEnum],
|
|
25
|
+
Generic[TLensMode, TPassEnergyEnum],
|
|
21
26
|
):
|
|
22
27
|
# Override defaults of base region class
|
|
23
28
|
lens_mode: TLensMode
|
|
24
|
-
pass_energy:
|
|
29
|
+
pass_energy: TPassEnergyEnum
|
|
25
30
|
acquisition_mode: AcquisitionMode = AcquisitionMode.SWEPT
|
|
26
31
|
low_energy: float = 8.0
|
|
27
32
|
high_energy: float = 10.0
|
|
28
33
|
step_time: float = 1.0
|
|
29
34
|
energy_step: float = Field(default=200.0)
|
|
35
|
+
centre_energy: float = Field(alias="fix_energy", default=9)
|
|
30
36
|
# Specific to this class
|
|
31
37
|
id: str = Field(default=str(uuid.uuid4()), alias="region_id")
|
|
32
|
-
fix_energy: float = 9.0
|
|
33
38
|
total_steps: float = 13.0
|
|
34
39
|
total_time: float = 13.0
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
40
|
+
min_x: int = Field(alias="first_x_channel", default=1)
|
|
41
|
+
sensor_max_size_x: int = Field(alias="last_x_channel", default=1000)
|
|
42
|
+
min_y: int = Field(alias="first_y_channel", default=101)
|
|
43
|
+
sensor_max_size_y: int = Field(alias="last_y_channel", default=800)
|
|
39
44
|
detector_mode: DetectorMode = DetectorMode.ADC
|
|
40
45
|
status: Status = Status.READY
|
|
41
46
|
|
|
42
|
-
|
|
43
|
-
|
|
47
|
+
@property
|
|
48
|
+
def size_x(self) -> int:
|
|
49
|
+
return self.sensor_max_size_x - self.min_x + 1
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def size_y(self) -> int:
|
|
53
|
+
return self.sensor_max_size_y - self.min_y + 1
|
|
44
54
|
|
|
45
|
-
|
|
46
|
-
|
|
55
|
+
@field_validator("pass_energy", mode="before")
|
|
56
|
+
@classmethod
|
|
57
|
+
def validate_pass_energy(cls, val):
|
|
58
|
+
# This is needed because if the value is a number, it can't be casted to the
|
|
59
|
+
# enum correctly.
|
|
60
|
+
return str(val)
|
|
47
61
|
|
|
48
62
|
|
|
49
63
|
class VGScientaExcitationEnergySource(JavaToPythonModel):
|
|
@@ -53,16 +67,19 @@ class VGScientaExcitationEnergySource(JavaToPythonModel):
|
|
|
53
67
|
|
|
54
68
|
|
|
55
69
|
class VGScientaSequence(
|
|
56
|
-
AbstractBaseSequence[VGScientaRegion,
|
|
70
|
+
AbstractBaseSequence[VGScientaRegion[TLensMode, TPassEnergyEnum]],
|
|
71
|
+
Generic[TLensMode, TPsuMode, TPassEnergyEnum],
|
|
57
72
|
):
|
|
58
|
-
|
|
73
|
+
psu_mode: TPsuMode = Field(alias="element_set")
|
|
59
74
|
excitation_energy_sources: list[VGScientaExcitationEnergySource] = Field(
|
|
60
75
|
default_factory=lambda: []
|
|
61
76
|
)
|
|
62
|
-
regions: list[VGScientaRegion[TLensMode]] = Field(
|
|
77
|
+
regions: list[VGScientaRegion[TLensMode, TPassEnergyEnum]] = Field(
|
|
78
|
+
default_factory=lambda: []
|
|
79
|
+
)
|
|
63
80
|
|
|
64
81
|
def get_excitation_energy_source_by_region(
|
|
65
|
-
self, region: VGScientaRegion[TLensMode]
|
|
82
|
+
self, region: VGScientaRegion[TLensMode, TPassEnergyEnum]
|
|
66
83
|
) -> VGScientaExcitationEnergySource:
|
|
67
84
|
value = next(
|
|
68
85
|
(
|
dodal/devices/focusing_mirror.py
CHANGED
|
@@ -113,7 +113,7 @@ class SingleMirrorVoltage(Device):
|
|
|
113
113
|
|
|
114
114
|
class MirrorVoltages(StandardReadable):
|
|
115
115
|
def __init__(
|
|
116
|
-
self,
|
|
116
|
+
self, prefix: str, name: str = "", *args, daq_configuration_path: str, **kwargs
|
|
117
117
|
):
|
|
118
118
|
self.voltage_lookup_table_path = (
|
|
119
119
|
daq_configuration_path + "/json/mirrorFocus.json"
|
|
@@ -27,6 +27,8 @@ class UndulatorDCM(StandardReadable, Movable[float]):
|
|
|
27
27
|
instead. See https://github.com/DiamondLightSource/dodal/issues/1092
|
|
28
28
|
"""
|
|
29
29
|
|
|
30
|
+
DCM_PERP_TOLERANCE = 0.01
|
|
31
|
+
|
|
30
32
|
def __init__(
|
|
31
33
|
self,
|
|
32
34
|
undulator: Undulator,
|
|
@@ -60,6 +62,9 @@ class UndulatorDCM(StandardReadable, Movable[float]):
|
|
|
60
62
|
self.dcm_ref().energy_in_kev.set(value, timeout=ENERGY_TIMEOUT_S),
|
|
61
63
|
self.undulator_ref().set(value),
|
|
62
64
|
)
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
await self.dcm_ref().offset_in_mm.
|
|
65
|
+
|
|
66
|
+
# The DCM perp is under vacuum so for heat management it's best to only change it when required
|
|
67
|
+
current_offset = await self.dcm_ref().offset_in_mm.user_readback.get_value()
|
|
68
|
+
if abs(current_offset - self.dcm_fixed_offset_mm) > self.DCM_PERP_TOLERANCE:
|
|
69
|
+
LOGGER.info(f"Adjusting DCM offset to {self.dcm_fixed_offset_mm} mm")
|
|
70
|
+
await self.dcm_ref().offset_in_mm.set(self.dcm_fixed_offset_mm)
|
dodal/devices/i09/__init__.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
from dodal.devices.i09.dcm import DCM
|
|
2
|
-
from dodal.devices.i09.enums import Grating, LensMode
|
|
2
|
+
from dodal.devices.i09.enums import Grating, LensMode, PassEnergy, PsuMode
|
|
3
3
|
|
|
4
|
-
__all__ = ["DCM", "Grating", "LensMode"]
|
|
4
|
+
__all__ = ["DCM", "Grating", "LensMode", "PsuMode", "PassEnergy"]
|
dodal/devices/i09/enums.py
CHANGED
|
@@ -13,3 +13,19 @@ class LensMode(StrictEnum):
|
|
|
13
13
|
ANGULAR60 = "Angular60"
|
|
14
14
|
ANGULAR56 = "Angular56"
|
|
15
15
|
ANGULAR45VUV = "Angular45VUV"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class PsuMode(StrictEnum):
|
|
19
|
+
HIGH = "High"
|
|
20
|
+
LOW = "Low"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class PassEnergy(StrictEnum):
|
|
24
|
+
E5 = 5
|
|
25
|
+
E10 = 10
|
|
26
|
+
E20 = 20
|
|
27
|
+
E50 = 50
|
|
28
|
+
E70 = 70
|
|
29
|
+
E100 = 100
|
|
30
|
+
E200 = 200
|
|
31
|
+
E500 = 500
|
dodal/devices/i09_1/__init__.py
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
from .enums import LensMode
|
|
1
|
+
from .enums import LensMode, PsuMode
|
|
2
2
|
|
|
3
|
-
__all__ = ["LensMode"]
|
|
3
|
+
__all__ = ["LensMode", "PsuMode"]
|
dodal/devices/i09_1/enums.py
CHANGED
|
@@ -16,4 +16,17 @@ class LensMode(SupersetEnum):
|
|
|
16
16
|
LOW_MAGNIFICATION = "LowMagnification"
|
|
17
17
|
HIGH_MAGNIFICATION2 = "HighMagnification2"
|
|
18
18
|
RAMP_MODE = "RampMode"
|
|
19
|
+
# This is connected to the device separately and will only have "Not connected" as
|
|
20
|
+
# option if disconnected. Once it is connected, "Not connected" is replaced with the
|
|
21
|
+
# options above. This is also why this must be a SupersetEnum.
|
|
22
|
+
NOT_CONNECTED = "Not connected"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class PsuMode(SupersetEnum):
|
|
26
|
+
V3500 = "3.5kV"
|
|
27
|
+
V1500 = "1.5kV"
|
|
28
|
+
V400 = "400V"
|
|
29
|
+
# This is connected to the device separately and will only have "Not connected" as
|
|
30
|
+
# option if disconnected. Once it is connected, "Not connected" is replaced with the
|
|
31
|
+
# options above. This is also why this must be a SupersetEnum.
|
|
19
32
|
NOT_CONNECTED = "Not connected"
|
dodal/devices/i10/mirrors.py
CHANGED
|
@@ -1,19 +1,15 @@
|
|
|
1
1
|
from ophyd_async.epics.core import epics_signal_rw
|
|
2
|
-
from ophyd_async.epics.motor import Motor
|
|
3
2
|
|
|
4
|
-
from dodal.devices.motors import
|
|
3
|
+
from dodal.devices.motors import XYZPitchYawRollStage
|
|
5
4
|
|
|
6
5
|
|
|
7
|
-
class PiezoMirror(
|
|
6
|
+
class PiezoMirror(XYZPitchYawRollStage):
|
|
8
7
|
def __init__(
|
|
9
8
|
self,
|
|
10
9
|
prefix: str,
|
|
11
10
|
name: str = "",
|
|
12
11
|
):
|
|
13
12
|
with self.add_children_as_readables():
|
|
14
|
-
self.yaw = Motor(prefix + "YAW")
|
|
15
|
-
self.pitch = Motor(prefix + "PITCH")
|
|
16
|
-
self.roll = Motor(prefix + "ROLL")
|
|
17
13
|
self.fine_pitch = epics_signal_rw(
|
|
18
14
|
float,
|
|
19
15
|
read_pv=prefix + "FPITCH:RBV:AI",
|