dls-dodal 1.57.0__py3-none-any.whl → 1.59.1__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.57.0.dist-info → dls_dodal-1.59.1.dist-info}/METADATA +2 -1
- {dls_dodal-1.57.0.dist-info → dls_dodal-1.59.1.dist-info}/RECORD +63 -46
- dodal/_version.py +2 -2
- dodal/beamlines/b07.py +10 -5
- dodal/beamlines/b07_1.py +10 -5
- dodal/beamlines/b21.py +22 -0
- dodal/beamlines/i02_1.py +80 -0
- dodal/beamlines/i03.py +7 -4
- dodal/beamlines/i04.py +20 -3
- dodal/beamlines/i09.py +10 -9
- dodal/beamlines/i09_1.py +10 -5
- dodal/beamlines/i10-1.py +25 -0
- dodal/beamlines/i10.py +17 -1
- dodal/beamlines/i11.py +0 -17
- dodal/beamlines/i19_2.py +20 -0
- dodal/beamlines/i21.py +27 -0
- dodal/beamlines/i22.py +12 -2
- dodal/beamlines/i24.py +32 -3
- dodal/beamlines/k07.py +31 -0
- dodal/beamlines/p60.py +10 -9
- dodal/common/beamlines/commissioning_mode.py +33 -0
- dodal/common/watcher_utils.py +1 -1
- dodal/devices/apple2_undulator.py +18 -142
- dodal/devices/attenuator/attenuator.py +48 -2
- dodal/devices/attenuator/filter.py +3 -0
- dodal/devices/attenuator/filter_selections.py +26 -0
- dodal/devices/baton.py +4 -0
- dodal/devices/eiger.py +2 -1
- dodal/devices/electron_analyser/__init__.py +4 -0
- dodal/devices/electron_analyser/abstract/base_driver_io.py +30 -18
- dodal/devices/electron_analyser/energy_sources.py +101 -0
- dodal/devices/electron_analyser/specs/detector.py +6 -6
- dodal/devices/electron_analyser/specs/driver_io.py +7 -15
- dodal/devices/electron_analyser/vgscienta/detector.py +6 -6
- dodal/devices/electron_analyser/vgscienta/driver_io.py +7 -14
- dodal/devices/fast_grid_scan.py +130 -64
- dodal/devices/focusing_mirror.py +30 -0
- dodal/devices/i02_1/__init__.py +0 -0
- dodal/devices/i02_1/fast_grid_scan.py +61 -0
- dodal/devices/i02_1/sample_motors.py +19 -0
- dodal/devices/i04/murko_results.py +69 -23
- dodal/devices/i10/i10_apple2.py +282 -140
- dodal/devices/i19/backlight.py +17 -0
- dodal/devices/i21/__init__.py +3 -0
- dodal/devices/i21/enums.py +8 -0
- dodal/devices/i22/nxsas.py +2 -0
- dodal/devices/i24/commissioning_jungfrau.py +114 -0
- dodal/devices/smargon.py +0 -56
- dodal/devices/temperture_controller/__init__.py +3 -0
- dodal/devices/temperture_controller/lakeshore/__init__.py +0 -0
- dodal/devices/temperture_controller/lakeshore/lakeshore.py +204 -0
- dodal/devices/temperture_controller/lakeshore/lakeshore_io.py +112 -0
- dodal/devices/tetramm.py +38 -16
- dodal/devices/undulator.py +13 -9
- dodal/devices/v2f.py +39 -0
- dodal/devices/xbpm_feedback.py +12 -6
- dodal/devices/zebra/zebra.py +1 -0
- dodal/devices/zebra/zebra_constants_mapping.py +1 -1
- dodal/parameters/experiment_parameter_base.py +1 -5
- {dls_dodal-1.57.0.dist-info → dls_dodal-1.59.1.dist-info}/WHEEL +0 -0
- {dls_dodal-1.57.0.dist-info → dls_dodal-1.59.1.dist-info}/entry_points.txt +0 -0
- {dls_dodal-1.57.0.dist-info → dls_dodal-1.59.1.dist-info}/licenses/LICENSE +0 -0
- {dls_dodal-1.57.0.dist-info → dls_dodal-1.59.1.dist-info}/top_level.txt +0 -0
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
from collections.abc import Mapping
|
|
3
2
|
from typing import Generic
|
|
4
3
|
|
|
5
4
|
import numpy as np
|
|
6
5
|
from ophyd_async.core import (
|
|
7
6
|
Array1D,
|
|
8
|
-
AsyncStatus,
|
|
9
7
|
SignalR,
|
|
10
8
|
StandardReadableFormat,
|
|
11
9
|
derived_signal_r,
|
|
@@ -16,7 +14,10 @@ from dodal.devices.electron_analyser.abstract.base_driver_io import (
|
|
|
16
14
|
AbstractAnalyserDriverIO,
|
|
17
15
|
)
|
|
18
16
|
from dodal.devices.electron_analyser.abstract.types import TLensMode, TPsuMode
|
|
19
|
-
from dodal.devices.electron_analyser.
|
|
17
|
+
from dodal.devices.electron_analyser.energy_sources import (
|
|
18
|
+
DualEnergySource,
|
|
19
|
+
EnergySource,
|
|
20
|
+
)
|
|
20
21
|
from dodal.devices.electron_analyser.specs.enums import AcquisitionMode
|
|
21
22
|
from dodal.devices.electron_analyser.specs.region import SpecsRegion
|
|
22
23
|
|
|
@@ -36,7 +37,7 @@ class SpecsAnalyserDriverIO(
|
|
|
36
37
|
prefix: str,
|
|
37
38
|
lens_mode_type: type[TLensMode],
|
|
38
39
|
psu_mode_type: type[TPsuMode],
|
|
39
|
-
|
|
40
|
+
energy_source: EnergySource | DualEnergySource,
|
|
40
41
|
name: str = "",
|
|
41
42
|
) -> None:
|
|
42
43
|
with self.add_children_as_readables(StandardReadableFormat.CONFIG_SIGNAL):
|
|
@@ -58,18 +59,11 @@ class SpecsAnalyserDriverIO(
|
|
|
58
59
|
lens_mode_type=lens_mode_type,
|
|
59
60
|
psu_mode_type=psu_mode_type,
|
|
60
61
|
pass_energy_type=float,
|
|
61
|
-
|
|
62
|
+
energy_source=energy_source,
|
|
62
63
|
name=name,
|
|
63
64
|
)
|
|
64
65
|
|
|
65
|
-
|
|
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
|
|
69
|
-
# Copy region so doesn't alter the actual region and switch to kinetic energy
|
|
70
|
-
ke_region = region.model_copy()
|
|
71
|
-
ke_region.switch_energy_mode(EnergyMode.KINETIC, excitation_energy)
|
|
72
|
-
|
|
66
|
+
async def _set_region(self, ke_region: SpecsRegion[TLensMode, TPsuMode]):
|
|
73
67
|
await asyncio.gather(
|
|
74
68
|
self.region_name.set(ke_region.name),
|
|
75
69
|
self.energy_mode.set(ke_region.energy_mode),
|
|
@@ -81,8 +75,6 @@ class SpecsAnalyserDriverIO(
|
|
|
81
75
|
self.pass_energy.set(ke_region.pass_energy),
|
|
82
76
|
self.iterations.set(ke_region.iterations),
|
|
83
77
|
self.acquisition_mode.set(ke_region.acquisition_mode),
|
|
84
|
-
self.excitation_energy.set(excitation_energy),
|
|
85
|
-
self.excitation_energy_source.set(source.name),
|
|
86
78
|
self.snapshot_values.set(ke_region.values),
|
|
87
79
|
self.psu_mode.set(ke_region.psu_mode),
|
|
88
80
|
)
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
from collections.abc import Mapping
|
|
2
1
|
from typing import Generic
|
|
3
2
|
|
|
4
|
-
from ophyd_async.core import SignalR
|
|
5
|
-
|
|
6
3
|
from dodal.devices.electron_analyser.abstract.types import (
|
|
7
4
|
TLensMode,
|
|
8
5
|
TPassEnergyEnum,
|
|
@@ -11,7 +8,10 @@ from dodal.devices.electron_analyser.abstract.types import (
|
|
|
11
8
|
from dodal.devices.electron_analyser.detector import (
|
|
12
9
|
ElectronAnalyserDetector,
|
|
13
10
|
)
|
|
14
|
-
from dodal.devices.electron_analyser.
|
|
11
|
+
from dodal.devices.electron_analyser.energy_sources import (
|
|
12
|
+
DualEnergySource,
|
|
13
|
+
EnergySource,
|
|
14
|
+
)
|
|
15
15
|
from dodal.devices.electron_analyser.vgscienta.driver_io import (
|
|
16
16
|
VGScientaAnalyserDriverIO,
|
|
17
17
|
)
|
|
@@ -35,11 +35,11 @@ class VGScientaDetector(
|
|
|
35
35
|
lens_mode_type: type[TLensMode],
|
|
36
36
|
psu_mode_type: type[TPsuMode],
|
|
37
37
|
pass_energy_type: type[TPassEnergyEnum],
|
|
38
|
-
|
|
38
|
+
energy_source: DualEnergySource | EnergySource,
|
|
39
39
|
name: str = "",
|
|
40
40
|
):
|
|
41
41
|
driver = VGScientaAnalyserDriverIO[TLensMode, TPsuMode, TPassEnergyEnum](
|
|
42
|
-
prefix, lens_mode_type, psu_mode_type, pass_energy_type,
|
|
42
|
+
prefix, lens_mode_type, psu_mode_type, pass_energy_type, energy_source
|
|
43
43
|
)
|
|
44
44
|
super().__init__(
|
|
45
45
|
VGScientaSequence[lens_mode_type, psu_mode_type, pass_energy_type],
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
from collections.abc import Mapping
|
|
3
2
|
from typing import Generic
|
|
4
3
|
|
|
5
4
|
import numpy as np
|
|
6
5
|
from ophyd_async.core import (
|
|
7
6
|
Array1D,
|
|
8
|
-
AsyncStatus,
|
|
9
7
|
SignalR,
|
|
10
8
|
StandardReadableFormat,
|
|
11
9
|
)
|
|
@@ -19,7 +17,10 @@ from dodal.devices.electron_analyser.abstract.types import (
|
|
|
19
17
|
TPassEnergyEnum,
|
|
20
18
|
TPsuMode,
|
|
21
19
|
)
|
|
22
|
-
from dodal.devices.electron_analyser.
|
|
20
|
+
from dodal.devices.electron_analyser.energy_sources import (
|
|
21
|
+
DualEnergySource,
|
|
22
|
+
EnergySource,
|
|
23
|
+
)
|
|
23
24
|
from dodal.devices.electron_analyser.vgscienta.enums import (
|
|
24
25
|
AcquisitionMode,
|
|
25
26
|
DetectorMode,
|
|
@@ -45,7 +46,7 @@ class VGScientaAnalyserDriverIO(
|
|
|
45
46
|
lens_mode_type: type[TLensMode],
|
|
46
47
|
psu_mode_type: type[TPsuMode],
|
|
47
48
|
pass_energy_type: type[TPassEnergyEnum],
|
|
48
|
-
|
|
49
|
+
energy_source: EnergySource | DualEnergySource,
|
|
49
50
|
name: str = "",
|
|
50
51
|
) -> None:
|
|
51
52
|
with self.add_children_as_readables(StandardReadableFormat.CONFIG_SIGNAL):
|
|
@@ -66,17 +67,11 @@ class VGScientaAnalyserDriverIO(
|
|
|
66
67
|
lens_mode_type,
|
|
67
68
|
psu_mode_type,
|
|
68
69
|
pass_energy_type,
|
|
69
|
-
|
|
70
|
+
energy_source,
|
|
70
71
|
name,
|
|
71
72
|
)
|
|
72
73
|
|
|
73
|
-
|
|
74
|
-
async def set(self, region: VGScientaRegion[TLensMode, TPassEnergyEnum]):
|
|
75
|
-
source = self._get_energy_source(region.excitation_energy_source)
|
|
76
|
-
excitation_energy = await source.get_value() # eV
|
|
77
|
-
# Copy region so doesn't alter the actual region and switch to kinetic energy
|
|
78
|
-
ke_region = region.model_copy()
|
|
79
|
-
ke_region.switch_energy_mode(EnergyMode.KINETIC, excitation_energy)
|
|
74
|
+
async def _set_region(self, ke_region: VGScientaRegion[TLensMode, TPassEnergyEnum]):
|
|
80
75
|
await asyncio.gather(
|
|
81
76
|
self.region_name.set(ke_region.name),
|
|
82
77
|
self.energy_mode.set(ke_region.energy_mode),
|
|
@@ -89,8 +84,6 @@ class VGScientaAnalyserDriverIO(
|
|
|
89
84
|
self.iterations.set(ke_region.iterations),
|
|
90
85
|
self.acquire_time.set(ke_region.acquire_time),
|
|
91
86
|
self.acquisition_mode.set(ke_region.acquisition_mode),
|
|
92
|
-
self.excitation_energy.set(excitation_energy),
|
|
93
|
-
self.excitation_energy_source.set(source.name),
|
|
94
87
|
self.energy_step.set(ke_region.energy_step),
|
|
95
88
|
self.detector_mode.set(ke_region.detector_mode),
|
|
96
89
|
self.region_min_x.set(ke_region.min_x),
|
dodal/devices/fast_grid_scan.py
CHANGED
|
@@ -9,9 +9,11 @@ from ophyd_async.core import (
|
|
|
9
9
|
AsyncStatus,
|
|
10
10
|
Device,
|
|
11
11
|
Signal,
|
|
12
|
+
SignalR,
|
|
12
13
|
SignalRW,
|
|
13
14
|
StandardReadable,
|
|
14
15
|
derived_signal_r,
|
|
16
|
+
soft_signal_r_and_setter,
|
|
15
17
|
wait_for_value,
|
|
16
18
|
)
|
|
17
19
|
from ophyd_async.epics.core import (
|
|
@@ -20,7 +22,7 @@ from ophyd_async.epics.core import (
|
|
|
20
22
|
epics_signal_rw_rbv,
|
|
21
23
|
epics_signal_x,
|
|
22
24
|
)
|
|
23
|
-
from pydantic import field_validator
|
|
25
|
+
from pydantic import BaseModel, field_validator
|
|
24
26
|
from pydantic.dataclasses import dataclass
|
|
25
27
|
|
|
26
28
|
from dodal.log import LOGGER
|
|
@@ -63,7 +65,7 @@ class GridScanParamsCommon(AbstractExperimentWithBeamParams):
|
|
|
63
65
|
"""
|
|
64
66
|
Common holder class for the parameters of a grid scan in a similar
|
|
65
67
|
layout to EPICS. The parameters and functions of this class are common
|
|
66
|
-
to both the zebra and panda triggered fast grid scans.
|
|
68
|
+
to both the zebra and panda triggered fast grid scans in 2d or 3d.
|
|
67
69
|
|
|
68
70
|
The grid specified is where data is taken e.g. it can be assumed the first frame is
|
|
69
71
|
at x_start, y1_start, z1_start and subsequent frames are N*step_size away.
|
|
@@ -71,15 +73,11 @@ class GridScanParamsCommon(AbstractExperimentWithBeamParams):
|
|
|
71
73
|
|
|
72
74
|
x_steps: int = 1
|
|
73
75
|
y_steps: int = 1
|
|
74
|
-
z_steps: int = 0
|
|
75
76
|
x_step_size_mm: float = 0.1
|
|
76
77
|
y_step_size_mm: float = 0.1
|
|
77
|
-
z_step_size_mm: float = 0.1
|
|
78
78
|
x_start_mm: float = 0.1
|
|
79
79
|
y1_start_mm: float = 0.1
|
|
80
|
-
y2_start_mm: float = 0.1
|
|
81
80
|
z1_start_mm: float = 0.1
|
|
82
|
-
z2_start_mm: float = 0.1
|
|
83
81
|
|
|
84
82
|
# Whether to set the stub offsets after centering
|
|
85
83
|
set_stub_offsets: bool = False
|
|
@@ -92,16 +90,10 @@ class GridScanParamsCommon(AbstractExperimentWithBeamParams):
|
|
|
92
90
|
def y_axis(self) -> GridAxis:
|
|
93
91
|
return GridAxis(self.y1_start_mm, self.y_step_size_mm, self.y_steps)
|
|
94
92
|
|
|
93
|
+
# In 2D grid scans, z axis is just the start position
|
|
95
94
|
@property
|
|
96
95
|
def z_axis(self) -> GridAxis:
|
|
97
|
-
return GridAxis(self.
|
|
98
|
-
|
|
99
|
-
def get_num_images(self):
|
|
100
|
-
return self.x_steps * (self.y_steps + self.z_steps)
|
|
101
|
-
|
|
102
|
-
@property
|
|
103
|
-
def is_3d_grid_scan(self):
|
|
104
|
-
return self.z_steps > 0
|
|
96
|
+
return GridAxis(self.z1_start_mm, 0, 1)
|
|
105
97
|
|
|
106
98
|
def grid_position_to_motor_position(self, grid_position: ndarray) -> ndarray:
|
|
107
99
|
"""Converts a grid position, given as steps in the x, y, z grid,
|
|
@@ -130,15 +122,33 @@ class GridScanParamsCommon(AbstractExperimentWithBeamParams):
|
|
|
130
122
|
)
|
|
131
123
|
|
|
132
124
|
|
|
133
|
-
|
|
134
|
-
|
|
125
|
+
class GridScanParamsThreeD(GridScanParamsCommon):
|
|
126
|
+
"""Additional parameters required to do a 3 dimensional gridscan.
|
|
135
127
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
128
|
+
A 3D gridscan works by doing two 2D gridscans. The first of these grids is x_steps by
|
|
129
|
+
y_steps. The sample is then rotated by 90 degrees, and then the second grid is
|
|
130
|
+
x_steps by z_steps.
|
|
139
131
|
"""
|
|
140
132
|
|
|
141
|
-
|
|
133
|
+
# Start position for z and y during the second gridscan
|
|
134
|
+
z2_start_mm: float = 0.1
|
|
135
|
+
y2_start_mm: float = 0.1
|
|
136
|
+
|
|
137
|
+
z_step_size_mm: float = 0.1
|
|
138
|
+
|
|
139
|
+
# Number of vertical steps during the second grid scan, after the rotation in omega
|
|
140
|
+
z_steps: int = 1
|
|
141
|
+
|
|
142
|
+
@property
|
|
143
|
+
def z_axis(self) -> GridAxis:
|
|
144
|
+
return GridAxis(self.z2_start_mm, self.z_step_size_mm, self.z_steps)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
ParamType = TypeVar("ParamType", bound=GridScanParamsCommon, covariant=True)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class WithDwellTime(BaseModel):
|
|
151
|
+
dwell_time_ms: float = 213
|
|
142
152
|
|
|
143
153
|
@field_validator("dwell_time_ms")
|
|
144
154
|
@classmethod
|
|
@@ -154,7 +164,14 @@ class ZebraGridScanParams(GridScanParamsCommon):
|
|
|
154
164
|
return dwell_time_ms
|
|
155
165
|
|
|
156
166
|
|
|
157
|
-
class
|
|
167
|
+
class ZebraGridScanParamsThreeD(GridScanParamsThreeD, WithDwellTime):
|
|
168
|
+
"""
|
|
169
|
+
Params for standard Zebra FGS. Adds on the dwell time, which is really the time
|
|
170
|
+
between trigger positions.
|
|
171
|
+
"""
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class PandAGridScanParams(GridScanParamsThreeD):
|
|
158
175
|
"""
|
|
159
176
|
Params for panda constant-motion scan. Adds on the goniometer run-up distance
|
|
160
177
|
"""
|
|
@@ -163,53 +180,50 @@ class PandAGridScanParams(GridScanParamsCommon):
|
|
|
163
180
|
|
|
164
181
|
|
|
165
182
|
class MotionProgram(Device):
|
|
166
|
-
def __init__(self, prefix: str, name: str = "") -> None:
|
|
183
|
+
def __init__(self, prefix: str, name: str = "", has_prog_num=True) -> None:
|
|
167
184
|
super().__init__(name)
|
|
168
185
|
self.running = epics_signal_r(int, prefix + "PROGBITS")
|
|
169
|
-
|
|
186
|
+
if has_prog_num:
|
|
187
|
+
self.program_number = epics_signal_r(float, prefix + "CS1:PROG_NUM")
|
|
188
|
+
else:
|
|
189
|
+
# Prog number PV doesn't currently exist for i02-1
|
|
190
|
+
self.program_number = soft_signal_r_and_setter(float, -1)[0]
|
|
170
191
|
|
|
171
192
|
|
|
172
193
|
class FastGridScanCommon(StandardReadable, Flyable, ABC, Generic[ParamType]):
|
|
173
|
-
"""Device for a general fast grid scan
|
|
194
|
+
"""Device containing the minimal signals for a general fast grid scan.
|
|
174
195
|
|
|
175
196
|
When the motion program is started, the goniometer will move in a snake-like grid trajectory,
|
|
176
|
-
with X as the fast axis and Y as the slow axis.
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
See https://github.com/DiamondLightSource/hyperion/wiki/Coordinate-Systems for more
|
|
197
|
+
with X as the fast axis and Y as the slow axis.
|
|
198
|
+
|
|
199
|
+
See ZebraFastGridScanThreeD as an example of how to implement.
|
|
180
200
|
"""
|
|
181
201
|
|
|
182
|
-
def __init__(
|
|
202
|
+
def __init__(
|
|
203
|
+
self, prefix: str, motion_controller_prefix: str, name: str = ""
|
|
204
|
+
) -> None:
|
|
205
|
+
super().__init__(name)
|
|
183
206
|
self.x_steps = epics_signal_rw_rbv(int, f"{prefix}X_NUM_STEPS")
|
|
184
207
|
self.y_steps = epics_signal_rw_rbv(
|
|
185
208
|
int, f"{prefix}Y_NUM_STEPS"
|
|
186
209
|
) # Number of vertical steps during the first grid scan
|
|
187
|
-
self.z_steps = epics_signal_rw_rbv(
|
|
188
|
-
int, f"{prefix}Z_NUM_STEPS"
|
|
189
|
-
) # Number of vertical steps during the second grid scan, after the rotation in omega
|
|
190
210
|
self.x_step_size = epics_signal_rw_rbv(float, f"{prefix}X_STEP_SIZE")
|
|
191
211
|
self.y_step_size = epics_signal_rw_rbv(float, f"{prefix}Y_STEP_SIZE")
|
|
192
|
-
self.z_step_size = epics_signal_rw_rbv(float, f"{prefix}Z_STEP_SIZE")
|
|
193
212
|
self.x_start = epics_signal_rw_rbv(float, f"{prefix}X_START")
|
|
194
213
|
self.y1_start = epics_signal_rw_rbv(float, f"{prefix}Y_START")
|
|
195
|
-
self.y2_start = epics_signal_rw_rbv(float, f"{prefix}Y2_START")
|
|
196
214
|
self.z1_start = epics_signal_rw_rbv(float, f"{prefix}Z_START")
|
|
197
|
-
self.z2_start = epics_signal_rw_rbv(float, f"{prefix}Z2_START")
|
|
198
215
|
|
|
199
|
-
|
|
216
|
+
# This can be created like a regular signal instead of an abstract method
|
|
217
|
+
# once https://github.com/DiamondLightSource/mx-bluesky/issues/1203 is done
|
|
218
|
+
self.scan_invalid = self._create_scan_invalid_signal(prefix)
|
|
200
219
|
|
|
201
220
|
self.run_cmd = epics_signal_x(f"{prefix}RUN.PROC")
|
|
202
221
|
self.stop_cmd = epics_signal_x(f"{prefix}STOP.PROC")
|
|
203
222
|
self.status = epics_signal_r(int, f"{prefix}SCAN_STATUS")
|
|
204
223
|
|
|
205
|
-
self.expected_images =
|
|
206
|
-
self._calculate_expected_images,
|
|
207
|
-
x=self.x_steps,
|
|
208
|
-
y=self.y_steps,
|
|
209
|
-
z=self.z_steps,
|
|
210
|
-
)
|
|
224
|
+
self.expected_images = self._create_expected_images_signal()
|
|
211
225
|
|
|
212
|
-
self.motion_program =
|
|
226
|
+
self.motion_program = self._create_motion_program(motion_controller_prefix)
|
|
213
227
|
|
|
214
228
|
self.position_counter = self._create_position_counter(prefix)
|
|
215
229
|
|
|
@@ -221,23 +235,12 @@ class FastGridScanCommon(StandardReadable, Flyable, ABC, Generic[ParamType]):
|
|
|
221
235
|
self.movable_params: dict[str, Signal] = {
|
|
222
236
|
"x_steps": self.x_steps,
|
|
223
237
|
"y_steps": self.y_steps,
|
|
224
|
-
"z_steps": self.z_steps,
|
|
225
238
|
"x_step_size_mm": self.x_step_size,
|
|
226
239
|
"y_step_size_mm": self.y_step_size,
|
|
227
|
-
"z_step_size_mm": self.z_step_size,
|
|
228
240
|
"x_start_mm": self.x_start,
|
|
229
241
|
"y1_start_mm": self.y1_start,
|
|
230
|
-
"y2_start_mm": self.y2_start,
|
|
231
242
|
"z1_start_mm": self.z1_start,
|
|
232
|
-
"z2_start_mm": self.z2_start,
|
|
233
243
|
}
|
|
234
|
-
super().__init__(name)
|
|
235
|
-
|
|
236
|
-
def _calculate_expected_images(self, x: int, y: int, z: int) -> int:
|
|
237
|
-
LOGGER.info(f"Reading num of images found {x, y, z} images in each axis")
|
|
238
|
-
first_grid = x * y
|
|
239
|
-
second_grid = x * z
|
|
240
|
-
return first_grid + second_grid
|
|
241
244
|
|
|
242
245
|
@AsyncStatus.wrap
|
|
243
246
|
async def kickoff(self):
|
|
@@ -266,25 +269,84 @@ class FastGridScanCommon(StandardReadable, Flyable, ABC, Generic[ParamType]):
|
|
|
266
269
|
raise
|
|
267
270
|
|
|
268
271
|
@abstractmethod
|
|
269
|
-
def
|
|
270
|
-
pass
|
|
272
|
+
def _create_expected_images_signal(self) -> SignalR[int]: ...
|
|
271
273
|
|
|
274
|
+
@abstractmethod
|
|
275
|
+
def _create_position_counter(self, prefix: str) -> SignalRW[int]: ...
|
|
272
276
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
277
|
+
# This can be created within init rather than as a separate method after https://github.com/DiamondLightSource/mx-bluesky/issues/1203
|
|
278
|
+
@abstractmethod
|
|
279
|
+
def _create_scan_invalid_signal(self, prefix: str) -> SignalR[float]: ...
|
|
280
|
+
|
|
281
|
+
# This can be created within init rather than as a separate method after https://github.com/DiamondLightSource/mx-bluesky/issues/1203
|
|
282
|
+
@abstractmethod
|
|
283
|
+
def _create_motion_program(
|
|
284
|
+
self, motion_controller_prefix: str
|
|
285
|
+
) -> MotionProgram: ...
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
class FastGridScanThreeD(FastGridScanCommon[ParamType]):
|
|
289
|
+
"""Device for standard 3D FGS.
|
|
290
|
+
|
|
291
|
+
After completeing the first grid, if Z steps isn't 0, the goniometer will
|
|
292
|
+
rotate in the omega direction such that it moves from the X-Y, to the X-Z plane then
|
|
293
|
+
do a second grid scan. The detector is triggered after every x step.
|
|
294
|
+
See https://github.com/DiamondLightSource/hyperion/wiki/Coordinate-Systems for more.
|
|
295
|
+
|
|
296
|
+
Subclasses must implement _create_position_counter.
|
|
276
297
|
"""
|
|
277
298
|
|
|
278
299
|
def __init__(self, prefix: str, name: str = "") -> None:
|
|
279
300
|
full_prefix = prefix + "FGS:"
|
|
280
|
-
# Time taken to travel between X steps
|
|
281
|
-
self.dwell_time_ms = epics_signal_rw_rbv(float, f"{full_prefix}DWELL_TIME")
|
|
282
301
|
|
|
302
|
+
# Number of vertical steps during the second grid scan, after the rotation in omega
|
|
303
|
+
self.z_steps = epics_signal_rw_rbv(int, f"{prefix}Z_NUM_STEPS")
|
|
304
|
+
self.z_step_size = epics_signal_rw_rbv(float, f"{prefix}Z_STEP_SIZE")
|
|
305
|
+
self.z2_start = epics_signal_rw_rbv(float, f"{prefix}Z2_START")
|
|
306
|
+
self.y2_start = epics_signal_rw_rbv(float, f"{prefix}Y2_START")
|
|
283
307
|
self.x_counter = epics_signal_r(int, f"{full_prefix}X_COUNTER")
|
|
284
308
|
self.y_counter = epics_signal_r(int, f"{full_prefix}Y_COUNTER")
|
|
285
309
|
|
|
286
310
|
super().__init__(full_prefix, prefix, name)
|
|
287
311
|
|
|
312
|
+
self.movable_params["z_step_size_mm"] = self.z_step_size
|
|
313
|
+
self.movable_params["z2_start_mm"] = self.z2_start
|
|
314
|
+
self.movable_params["y2_start_mm"] = self.y2_start
|
|
315
|
+
self.movable_params["z_steps"] = self.z_steps
|
|
316
|
+
|
|
317
|
+
def _create_expected_images_signal(self):
|
|
318
|
+
return derived_signal_r(
|
|
319
|
+
self._calculate_expected_images,
|
|
320
|
+
x=self.x_steps,
|
|
321
|
+
y=self.y_steps,
|
|
322
|
+
z=self.z_steps,
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
def _calculate_expected_images(self, x: int, y: int, z: int) -> int:
|
|
326
|
+
LOGGER.info(f"Reading num of images found {x, y, z} images in each axis")
|
|
327
|
+
first_grid = x * y
|
|
328
|
+
second_grid = x * z
|
|
329
|
+
return first_grid + second_grid
|
|
330
|
+
|
|
331
|
+
def _create_scan_invalid_signal(self, prefix: str) -> SignalR[float]:
|
|
332
|
+
return epics_signal_r(float, f"{prefix}SCAN_INVALID")
|
|
333
|
+
|
|
334
|
+
def _create_motion_program(self, motion_controller_prefix: str):
|
|
335
|
+
return MotionProgram(motion_controller_prefix)
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
class ZebraFastGridScanThreeD(FastGridScanThreeD[ZebraGridScanParamsThreeD]):
|
|
339
|
+
"""Device for standard Zebra 3D FGS.
|
|
340
|
+
|
|
341
|
+
In this scan, the goniometer's velocity profile follows a parabolic shape between X steps,
|
|
342
|
+
with the slowest points occuring at each X step.
|
|
343
|
+
"""
|
|
344
|
+
|
|
345
|
+
def __init__(self, prefix: str, name: str = "") -> None:
|
|
346
|
+
full_prefix = prefix + "FGS:"
|
|
347
|
+
# Time taken to travel between X steps
|
|
348
|
+
self.dwell_time_ms = epics_signal_rw_rbv(float, f"{full_prefix}DWELL_TIME")
|
|
349
|
+
super().__init__(prefix, name)
|
|
288
350
|
self.movable_params["dwell_time_ms"] = self.dwell_time_ms
|
|
289
351
|
|
|
290
352
|
def _create_position_counter(self, prefix: str):
|
|
@@ -293,8 +355,12 @@ class ZebraFastGridScan(FastGridScanCommon[ZebraGridScanParams]):
|
|
|
293
355
|
)
|
|
294
356
|
|
|
295
357
|
|
|
296
|
-
class PandAFastGridScan(
|
|
297
|
-
"""Device for panda constant-motion scan
|
|
358
|
+
class PandAFastGridScan(FastGridScanThreeD[PandAGridScanParams]):
|
|
359
|
+
"""Device for panda constant-motion scan.
|
|
360
|
+
|
|
361
|
+
In this scan, the goniometer's velocity
|
|
362
|
+
is constant through each row. It doesn't slow down when going through trigger points.
|
|
363
|
+
"""
|
|
298
364
|
|
|
299
365
|
def __init__(self, prefix: str, name: str = "") -> None:
|
|
300
366
|
full_prefix = prefix + "PGS:"
|
|
@@ -309,7 +375,7 @@ class PandAFastGridScan(FastGridScanCommon[PandAGridScanParams]):
|
|
|
309
375
|
self.run_up_distance_mm = epics_signal_rw_rbv(
|
|
310
376
|
float, f"{full_prefix}RUNUP_DISTANCE"
|
|
311
377
|
)
|
|
312
|
-
super().__init__(
|
|
378
|
+
super().__init__(prefix, name)
|
|
313
379
|
|
|
314
380
|
self.movable_params["run_up_distance_mm"] = self.run_up_distance_mm
|
|
315
381
|
|
dodal/devices/focusing_mirror.py
CHANGED
|
@@ -17,6 +17,7 @@ from ophyd_async.epics.core import (
|
|
|
17
17
|
)
|
|
18
18
|
from ophyd_async.epics.motor import Motor
|
|
19
19
|
|
|
20
|
+
from dodal.devices.motors import XYPitchStage
|
|
20
21
|
from dodal.log import LOGGER
|
|
21
22
|
|
|
22
23
|
VOLTAGE_POLLING_DELAY_S = 0.5
|
|
@@ -134,6 +135,35 @@ class MirrorVoltages(StandardReadable):
|
|
|
134
135
|
)
|
|
135
136
|
|
|
136
137
|
|
|
138
|
+
class SimpleMirror(XYPitchStage):
|
|
139
|
+
"""Simple Focusing Mirror"""
|
|
140
|
+
|
|
141
|
+
def __init__(
|
|
142
|
+
self,
|
|
143
|
+
prefix: str,
|
|
144
|
+
name: str = "",
|
|
145
|
+
x_infix: str = "X",
|
|
146
|
+
y_infix: str = "Y",
|
|
147
|
+
pitch_infix: str = "PITCH",
|
|
148
|
+
):
|
|
149
|
+
super().__init__(
|
|
150
|
+
name=name,
|
|
151
|
+
prefix=prefix,
|
|
152
|
+
x_infix=x_infix,
|
|
153
|
+
y_infix=y_infix,
|
|
154
|
+
pitch_infix=pitch_infix,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
with self.add_children_as_readables(StandardReadableFormat.CONFIG_SIGNAL):
|
|
158
|
+
self.type, _ = soft_signal_r_and_setter(MirrorType, MirrorType.SINGLE)
|
|
159
|
+
|
|
160
|
+
with self.add_children_as_readables():
|
|
161
|
+
self.yaw = Motor(prefix + "YAW")
|
|
162
|
+
self.bend = Motor(prefix + "BEND")
|
|
163
|
+
self.jack1 = Motor(prefix + "J1")
|
|
164
|
+
self.jack2 = Motor(prefix + "J2")
|
|
165
|
+
|
|
166
|
+
|
|
137
167
|
class FocusingMirror(StandardReadable):
|
|
138
168
|
"""Focusing Mirror"""
|
|
139
169
|
|
|
File without changes
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from ophyd_async.core import SignalR, derived_signal_r, soft_signal_r_and_setter
|
|
2
|
+
from ophyd_async.epics.core import epics_signal_rw_rbv
|
|
3
|
+
|
|
4
|
+
from dodal.devices.fast_grid_scan import (
|
|
5
|
+
FastGridScanCommon,
|
|
6
|
+
GridScanParamsCommon,
|
|
7
|
+
MotionProgram,
|
|
8
|
+
WithDwellTime,
|
|
9
|
+
)
|
|
10
|
+
from dodal.log import LOGGER
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ZebraGridScanParamsTwoD(GridScanParamsCommon, WithDwellTime):
|
|
14
|
+
"""
|
|
15
|
+
Params for 2D Zebra FGS. Adds on the dwell time, which is really the time
|
|
16
|
+
between trigger positions.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ZebraFastGridScanTwoD(FastGridScanCommon[ZebraGridScanParamsTwoD]):
|
|
21
|
+
"""i02-1's EPICS interface for the 2D FGS differs slightly from the standard 3D
|
|
22
|
+
version:
|
|
23
|
+
- No Z steps, Z step sizes, or Y2 start positions, or Z2 start
|
|
24
|
+
- No scan valid PV - see https://github.com/DiamondLightSource/mx-bluesky/issues/1203
|
|
25
|
+
- No program_number - see https://github.com/DiamondLightSource/mx-bluesky/issues/1203
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self, prefix: str, motion_controller_prefix: str, name: str = ""
|
|
30
|
+
) -> None:
|
|
31
|
+
full_prefix = prefix + "FGS:"
|
|
32
|
+
super().__init__(full_prefix, motion_controller_prefix, name)
|
|
33
|
+
|
|
34
|
+
# This signal could be put in the common device if the prefix gets standardised.
|
|
35
|
+
# See https://github.com/DiamondLightSource/mx-bluesky/issues/1203
|
|
36
|
+
self.dwell_time_ms = epics_signal_rw_rbv(float, f"{full_prefix}EXPOSURE_TIME")
|
|
37
|
+
|
|
38
|
+
self.movable_params["dwell_time_ms"] = self.dwell_time_ms
|
|
39
|
+
|
|
40
|
+
def _create_expected_images_signal(self):
|
|
41
|
+
return derived_signal_r(
|
|
42
|
+
self._calculate_expected_images,
|
|
43
|
+
x=self.x_steps,
|
|
44
|
+
y=self.y_steps,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
def _calculate_expected_images(self, x: int, y: int) -> int:
|
|
48
|
+
LOGGER.info(f"Reading num of images found {x, y} images in each axis")
|
|
49
|
+
return x * y
|
|
50
|
+
|
|
51
|
+
# VMXm triggers the grid scan through GDA, which has its own validity check
|
|
52
|
+
# so whilst this PV is being added, it isn't essential
|
|
53
|
+
def _create_scan_invalid_signal(self, prefix: str) -> SignalR[float]:
|
|
54
|
+
return soft_signal_r_and_setter(float, 0)[0]
|
|
55
|
+
|
|
56
|
+
def _create_motion_program(self, motion_controller_prefix):
|
|
57
|
+
return MotionProgram(motion_controller_prefix, has_prog_num=False)
|
|
58
|
+
|
|
59
|
+
# To be standardised in https://github.com/DiamondLightSource/mx-bluesky/issues/1203
|
|
60
|
+
def _create_position_counter(self, prefix: str):
|
|
61
|
+
return epics_signal_rw_rbv(int, f"{prefix}POS_COUNTER")
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from ophyd_async.core import StandardReadable
|
|
2
|
+
from ophyd_async.epics.motor import Motor
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class SampleMotors(StandardReadable):
|
|
6
|
+
"""Virtual Smaract motors on i02-1 (VMXm)"""
|
|
7
|
+
|
|
8
|
+
def __init__(
|
|
9
|
+
self,
|
|
10
|
+
prefix: str,
|
|
11
|
+
name: str = "",
|
|
12
|
+
):
|
|
13
|
+
# See https://github.com/DiamondLightSource/mx-bluesky/issues/1212
|
|
14
|
+
# regarding a potential motion issue with omega
|
|
15
|
+
with self.add_children_as_readables():
|
|
16
|
+
self.x = Motor(f"{prefix}X")
|
|
17
|
+
self.z = Motor(f"{prefix}Z")
|
|
18
|
+
self.omega = Motor(f"{prefix}OMEGA")
|
|
19
|
+
super().__init__(name=name)
|