dls-dodal 1.36.3__py3-none-any.whl → 1.38.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.36.3.dist-info → dls_dodal-1.38.0.dist-info}/METADATA +4 -3
- {dls_dodal-1.36.3.dist-info → dls_dodal-1.38.0.dist-info}/RECORD +58 -38
- {dls_dodal-1.36.3.dist-info → dls_dodal-1.38.0.dist-info}/WHEEL +1 -1
- dodal/_version.py +2 -2
- dodal/beamlines/i02_1.py +37 -0
- dodal/beamlines/i03.py +34 -5
- dodal/beamlines/i04.py +16 -5
- dodal/beamlines/i10.py +105 -0
- dodal/beamlines/i13_1.py +20 -2
- dodal/beamlines/i22.py +15 -0
- dodal/beamlines/i24.py +14 -2
- dodal/beamlines/p99.py +6 -2
- dodal/beamlines/training_rig.py +10 -1
- dodal/common/crystal_metadata.py +3 -3
- dodal/common/udc_directory_provider.py +3 -1
- dodal/devices/aperturescatterguard.py +3 -0
- dodal/devices/{attenuator.py → attenuator/attenuator.py} +29 -1
- dodal/devices/attenuator/filter.py +11 -0
- dodal/devices/attenuator/filter_selections.py +72 -0
- dodal/devices/bimorph_mirror.py +151 -0
- dodal/devices/current_amplifiers/__init__.py +34 -0
- dodal/devices/current_amplifiers/current_amplifier.py +103 -0
- dodal/devices/current_amplifiers/current_amplifier_detector.py +109 -0
- dodal/devices/current_amplifiers/femto.py +143 -0
- dodal/devices/current_amplifiers/sr570.py +214 -0
- dodal/devices/current_amplifiers/struck_scaler_counter.py +79 -0
- dodal/devices/detector/det_dim_constants.py +15 -0
- dodal/devices/eiger_odin.py +3 -3
- dodal/devices/fast_grid_scan.py +8 -3
- dodal/devices/flux.py +10 -3
- dodal/devices/i03/beamstop.py +85 -0
- dodal/devices/i04/transfocator.py +67 -53
- dodal/devices/i10/rasor/rasor_current_amp.py +72 -0
- dodal/devices/i10/rasor/rasor_motors.py +62 -0
- dodal/devices/i10/rasor/rasor_scaler_cards.py +12 -0
- dodal/devices/i13_1/__init__.py +0 -0
- dodal/devices/i13_1/merlin.py +33 -0
- dodal/devices/i13_1/merlin_controller.py +52 -0
- dodal/devices/i13_1/merlin_io.py +17 -0
- dodal/devices/i24/beam_center.py +1 -1
- dodal/devices/p45.py +31 -20
- dodal/devices/p99/sample_stage.py +2 -28
- dodal/devices/robot.py +2 -2
- dodal/devices/s4_slit_gaps.py +8 -4
- dodal/devices/undulator_dcm.py +9 -11
- dodal/devices/util/lookup_tables.py +14 -10
- dodal/devices/zebra/__init__.py +0 -0
- dodal/devices/{zebra.py → zebra/zebra.py} +9 -33
- dodal/devices/zebra/zebra_constants_mapping.py +96 -0
- dodal/devices/zocalo/zocalo_interaction.py +2 -1
- dodal/devices/zocalo/zocalo_results.py +22 -2
- dodal/log.py +2 -2
- dodal/plans/wrapped.py +3 -3
- {dls_dodal-1.36.3.dist-info → dls_dodal-1.38.0.dist-info}/LICENSE +0 -0
- {dls_dodal-1.36.3.dist-info → dls_dodal-1.38.0.dist-info}/entry_points.txt +0 -0
- {dls_dodal-1.36.3.dist-info → dls_dodal-1.38.0.dist-info}/top_level.txt +0 -0
- /dodal/devices/{zebra_controlled_shutter.py → zebra/zebra_controlled_shutter.py} +0 -0
- /dodal/{devices/util → plans}/save_panda.py +0 -0
|
@@ -1,14 +1,18 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
import math
|
|
2
|
-
from time import sleep, time
|
|
3
3
|
|
|
4
|
-
from
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
from ophyd_async.core import (
|
|
5
|
+
AsyncStatus,
|
|
6
|
+
StandardReadable,
|
|
7
|
+
observe_value,
|
|
8
|
+
wait_for_value,
|
|
9
|
+
)
|
|
10
|
+
from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
|
|
7
11
|
|
|
8
12
|
from dodal.log import LOGGER
|
|
9
13
|
|
|
10
14
|
|
|
11
|
-
class Transfocator(
|
|
15
|
+
class Transfocator(StandardReadable):
|
|
12
16
|
"""The transfocator is a device that puts a number of lenses in the beam to change
|
|
13
17
|
its shape.
|
|
14
18
|
|
|
@@ -18,34 +22,50 @@ class Transfocator(Device):
|
|
|
18
22
|
my_transfocator.set(vert_beamsize_microns)
|
|
19
23
|
"""
|
|
20
24
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
def __init__(self, prefix: str, name: str = ""):
|
|
26
|
+
with self.add_children_as_readables():
|
|
27
|
+
self.beamsize_set_microns = epics_signal_rw(float, prefix + "VERT_REQ")
|
|
28
|
+
self.predicted_vertical_num_lenses = epics_signal_rw(
|
|
29
|
+
float, prefix + "LENS_PRED"
|
|
30
|
+
)
|
|
31
|
+
self.number_filters_sp = epics_signal_rw(int, prefix + "NUM_FILTERS")
|
|
32
|
+
self.start = epics_signal_rw(int, prefix + "START.PROC")
|
|
33
|
+
self.start_rbv = epics_signal_r(int, prefix + "START_RBV")
|
|
34
|
+
self.vertical_lens_rbv = epics_signal_r(float, prefix + "VER")
|
|
30
35
|
|
|
31
|
-
|
|
32
|
-
_POLLING_WAIT = 0.01
|
|
36
|
+
self.TIMEOUT = 120
|
|
33
37
|
|
|
34
|
-
|
|
35
|
-
# For some reason couldn't get monitors working on START_RBV
|
|
36
|
-
# (See https://github.com/DiamondLightSource/dodal/issues/152)
|
|
37
|
-
start_time = time()
|
|
38
|
-
while time() < start_time + self.TIMEOUT:
|
|
39
|
-
RBV_value = self.start_rbv.get()
|
|
40
|
-
if RBV_value == for_value:
|
|
41
|
-
return
|
|
42
|
-
sleep(self._POLLING_WAIT)
|
|
38
|
+
super().__init__(name=name)
|
|
43
39
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
raise TimeoutError()
|
|
40
|
+
async def _observe_beamsize_microns(self):
|
|
41
|
+
is_set_filters_done = False
|
|
47
42
|
|
|
48
|
-
|
|
43
|
+
async def set_based_on_prediction(value: float):
|
|
44
|
+
if not math.isclose(
|
|
45
|
+
self.latest_pred_vertical_num_lenses, value, abs_tol=1e-8
|
|
46
|
+
):
|
|
47
|
+
# We can only put an integer number of lenses in the beam but the
|
|
48
|
+
# calculation in the IOC returns the theoretical float number of lenses
|
|
49
|
+
nonlocal is_set_filters_done
|
|
50
|
+
value = round(value)
|
|
51
|
+
LOGGER.info(f"Transfocator setting {value} filters")
|
|
52
|
+
await self.number_filters_sp.set(value)
|
|
53
|
+
await self.start.set(1)
|
|
54
|
+
LOGGER.info("Waiting for start_rbv to change to 1")
|
|
55
|
+
await wait_for_value(self.start_rbv, 1, self.TIMEOUT)
|
|
56
|
+
LOGGER.info("Waiting for start_rbv to change to 0")
|
|
57
|
+
await wait_for_value(self.start_rbv, 0, self.TIMEOUT)
|
|
58
|
+
self.latest_pred_vertical_num_lenses = value
|
|
59
|
+
is_set_filters_done = True
|
|
60
|
+
|
|
61
|
+
# The value hasn't changed so assume the device is already set up correctly
|
|
62
|
+
async for value in observe_value(self.predicted_vertical_num_lenses):
|
|
63
|
+
await set_based_on_prediction(value)
|
|
64
|
+
if is_set_filters_done:
|
|
65
|
+
break
|
|
66
|
+
|
|
67
|
+
@AsyncStatus.wrap
|
|
68
|
+
async def set(self, value: float):
|
|
49
69
|
"""To set the beamsize on the transfocator we must:
|
|
50
70
|
1. Set the beamsize in the calculator part of the transfocator
|
|
51
71
|
2. Get the predicted number of lenses needed from this calculator
|
|
@@ -53,30 +73,24 @@ class Transfocator(Device):
|
|
|
53
73
|
4. Start the device moving
|
|
54
74
|
5. Wait for the start_rbv goes high and low again
|
|
55
75
|
"""
|
|
56
|
-
|
|
57
|
-
|
|
76
|
+
self.latest_pred_vertical_num_lenses = (
|
|
77
|
+
await self.predicted_vertical_num_lenses.get_value()
|
|
78
|
+
)
|
|
58
79
|
|
|
59
|
-
|
|
60
|
-
if not math.isclose(old_value, value, abs_tol=1e-8):
|
|
61
|
-
self.predicted_vertical_num_lenses.unsubscribe(subscriber)
|
|
80
|
+
LOGGER.info(f"Transfocator setting {value} beamsize")
|
|
62
81
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
self.
|
|
68
|
-
self.start.set(1).wait()
|
|
69
|
-
self.polling_wait_on_start_rbv(1)
|
|
70
|
-
self.polling_wait_on_start_rbv(0)
|
|
71
|
-
# The value hasn't changed so assume the device is already set up correctly
|
|
72
|
-
status.set_finished()
|
|
73
|
-
|
|
74
|
-
LOGGER.info(f"Transfocator setting {beamsize_microns} beamsize")
|
|
75
|
-
if self.beamsize_set_microns.get() != beamsize_microns:
|
|
76
|
-
subscriber = self.predicted_vertical_num_lenses.subscribe(
|
|
77
|
-
set_based_on_predicition, run=False
|
|
82
|
+
if await self.beamsize_set_microns.get_value() != value:
|
|
83
|
+
# Logic in the IOC calculates predicted_vertical_num_lenses when beam_set_microns changes
|
|
84
|
+
await asyncio.gather(
|
|
85
|
+
self.beamsize_set_microns.set(value),
|
|
86
|
+
self._observe_beamsize_microns(),
|
|
78
87
|
)
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
88
|
+
|
|
89
|
+
number_filters_rbv, vertical_lens_size_rbv = await asyncio.gather(
|
|
90
|
+
self.number_filters_sp.get_value(),
|
|
91
|
+
self.vertical_lens_rbv.get_value(),
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
LOGGER.info(
|
|
95
|
+
f"Transfocator set complete. Number of filters is: {number_filters_rbv} and Vertical beam size is: {vertical_lens_size_rbv}"
|
|
96
|
+
)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from ophyd_async.core import Device
|
|
2
|
+
|
|
3
|
+
from dodal.devices.current_amplifiers import (
|
|
4
|
+
SR570,
|
|
5
|
+
Femto3xxGainTable,
|
|
6
|
+
Femto3xxGainToCurrentTable,
|
|
7
|
+
Femto3xxRaiseTime,
|
|
8
|
+
FemtoDDPCA,
|
|
9
|
+
SR570FineGainTable,
|
|
10
|
+
SR570FullGainTable,
|
|
11
|
+
SR570GainTable,
|
|
12
|
+
SR570GainToCurrentTable,
|
|
13
|
+
SR570RaiseTimeTable,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class RasorFemto(Device):
|
|
18
|
+
def __init__(self, prefix: str, suffix: str = "GAIN", name: str = "") -> None:
|
|
19
|
+
self.ca1 = FemtoDDPCA(
|
|
20
|
+
prefix + "-01:",
|
|
21
|
+
suffix=suffix,
|
|
22
|
+
gain_table=Femto3xxGainTable,
|
|
23
|
+
gain_to_current_table=Femto3xxGainToCurrentTable,
|
|
24
|
+
raise_timetable=Femto3xxRaiseTime,
|
|
25
|
+
)
|
|
26
|
+
self.ca2 = FemtoDDPCA(
|
|
27
|
+
prefix + "-02:",
|
|
28
|
+
suffix=suffix,
|
|
29
|
+
gain_table=Femto3xxGainTable,
|
|
30
|
+
gain_to_current_table=Femto3xxGainToCurrentTable,
|
|
31
|
+
raise_timetable=Femto3xxRaiseTime,
|
|
32
|
+
)
|
|
33
|
+
self.ca3 = FemtoDDPCA(
|
|
34
|
+
prefix + "-03:",
|
|
35
|
+
suffix=suffix,
|
|
36
|
+
gain_table=Femto3xxGainTable,
|
|
37
|
+
gain_to_current_table=Femto3xxGainToCurrentTable,
|
|
38
|
+
raise_timetable=Femto3xxRaiseTime,
|
|
39
|
+
)
|
|
40
|
+
super().__init__(name)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class RasorSR570(Device):
|
|
44
|
+
def __init__(self, prefix: str, suffix: str = "SENS:SEL", name: str = "") -> None:
|
|
45
|
+
self.ca1 = SR570(
|
|
46
|
+
prefix + "-04:",
|
|
47
|
+
suffix=suffix,
|
|
48
|
+
fine_gain_table=SR570FineGainTable,
|
|
49
|
+
coarse_gain_table=SR570GainTable,
|
|
50
|
+
combined_table=SR570FullGainTable,
|
|
51
|
+
gain_to_current_table=SR570GainToCurrentTable,
|
|
52
|
+
raise_timetable=SR570RaiseTimeTable,
|
|
53
|
+
)
|
|
54
|
+
self.ca2 = SR570(
|
|
55
|
+
prefix + "-05:",
|
|
56
|
+
suffix=suffix,
|
|
57
|
+
fine_gain_table=SR570FineGainTable,
|
|
58
|
+
coarse_gain_table=SR570GainTable,
|
|
59
|
+
combined_table=SR570FullGainTable,
|
|
60
|
+
gain_to_current_table=SR570GainToCurrentTable,
|
|
61
|
+
raise_timetable=SR570RaiseTimeTable,
|
|
62
|
+
)
|
|
63
|
+
self.ca3 = SR570(
|
|
64
|
+
prefix + "-06:",
|
|
65
|
+
suffix=suffix,
|
|
66
|
+
fine_gain_table=SR570FineGainTable,
|
|
67
|
+
coarse_gain_table=SR570GainTable,
|
|
68
|
+
combined_table=SR570FullGainTable,
|
|
69
|
+
gain_to_current_table=SR570GainToCurrentTable,
|
|
70
|
+
raise_timetable=SR570RaiseTimeTable,
|
|
71
|
+
)
|
|
72
|
+
super().__init__(name)
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from ophyd_async.core import StandardReadable
|
|
2
|
+
from ophyd_async.epics.motor import Motor
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class PinHole(StandardReadable):
|
|
6
|
+
"Two motors stage for rasor pinhole"
|
|
7
|
+
|
|
8
|
+
def __init__(
|
|
9
|
+
self,
|
|
10
|
+
prefix: str,
|
|
11
|
+
name: str = "",
|
|
12
|
+
):
|
|
13
|
+
with self.add_children_as_readables():
|
|
14
|
+
self.x = Motor(prefix + "X")
|
|
15
|
+
self.y = Motor(prefix + "Y")
|
|
16
|
+
super().__init__(name=name)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Diffractometer(StandardReadable):
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
prefix: str,
|
|
23
|
+
name: str = "",
|
|
24
|
+
):
|
|
25
|
+
with self.add_children_as_readables():
|
|
26
|
+
self.tth = Motor(prefix + "TWOTHETA")
|
|
27
|
+
self.th = Motor(prefix + "THETA")
|
|
28
|
+
self.chi = Motor(prefix + "CHI")
|
|
29
|
+
self.chamber_x = Motor(prefix + "X")
|
|
30
|
+
self.alpha = Motor(prefix + "ALPHA")
|
|
31
|
+
super().__init__(name=name)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class DetSlits(StandardReadable):
|
|
35
|
+
"Detector slits"
|
|
36
|
+
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
prefix: str,
|
|
40
|
+
name: str = "",
|
|
41
|
+
):
|
|
42
|
+
with self.add_children_as_readables():
|
|
43
|
+
self.upstream = Motor(prefix + "1:TRANS")
|
|
44
|
+
self.downstream = Motor(prefix + "2:TRANS")
|
|
45
|
+
super().__init__(name=name)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class PaStage(StandardReadable):
|
|
49
|
+
"Rasor detector stage"
|
|
50
|
+
|
|
51
|
+
def __init__(
|
|
52
|
+
self,
|
|
53
|
+
prefix: str,
|
|
54
|
+
name: str = "",
|
|
55
|
+
):
|
|
56
|
+
with self.add_children_as_readables():
|
|
57
|
+
self.ttp = Motor(prefix + "TWOTHETA")
|
|
58
|
+
self.thp = Motor(prefix + "THETA")
|
|
59
|
+
self.py = Motor(prefix + "Y")
|
|
60
|
+
self.pz = Motor(prefix + "Z")
|
|
61
|
+
self.eta = Motor(prefix + "ETA")
|
|
62
|
+
super().__init__(name=name)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from ophyd_async.core import Device
|
|
2
|
+
|
|
3
|
+
from dodal.devices.current_amplifiers import StruckScaler
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class RasorScalerCard1(Device):
|
|
7
|
+
def __init__(self, prefix, name: str = "") -> None:
|
|
8
|
+
self.mon = StruckScaler(prefix=prefix, suffix=".16")
|
|
9
|
+
self.det = StruckScaler(prefix=prefix, suffix=".17")
|
|
10
|
+
self.fluo = StruckScaler(prefix=prefix, suffix=".18")
|
|
11
|
+
self.drain = StruckScaler(prefix=prefix, suffix=".19")
|
|
12
|
+
super().__init__(name)
|
|
File without changes
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from ophyd_async.core import PathProvider, StandardDetector
|
|
2
|
+
from ophyd_async.epics import adcore
|
|
3
|
+
|
|
4
|
+
from dodal.devices.i13_1.merlin_controller import MerlinController
|
|
5
|
+
from dodal.devices.i13_1.merlin_io import MerlinDriverIO
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Merlin(StandardDetector):
|
|
9
|
+
_controller: MerlinController
|
|
10
|
+
_writer: adcore.ADHDFWriter
|
|
11
|
+
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
prefix: str,
|
|
15
|
+
path_provider: PathProvider,
|
|
16
|
+
drv_suffix="CAM:",
|
|
17
|
+
hdf_suffix="HDF:",
|
|
18
|
+
name: str = "",
|
|
19
|
+
):
|
|
20
|
+
self.drv = MerlinDriverIO(prefix + drv_suffix)
|
|
21
|
+
self.hdf = adcore.NDFileHDFIO(prefix + hdf_suffix)
|
|
22
|
+
|
|
23
|
+
super().__init__(
|
|
24
|
+
MerlinController(self.drv),
|
|
25
|
+
adcore.ADHDFWriter(
|
|
26
|
+
self.hdf,
|
|
27
|
+
path_provider,
|
|
28
|
+
lambda: self.name,
|
|
29
|
+
adcore.ADBaseDatasetDescriber(self.drv),
|
|
30
|
+
),
|
|
31
|
+
config_sigs=(self.drv.acquire_period, self.drv.acquire_time),
|
|
32
|
+
name=name,
|
|
33
|
+
)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
from ophyd_async.core import (
|
|
5
|
+
DEFAULT_TIMEOUT,
|
|
6
|
+
AsyncStatus,
|
|
7
|
+
DetectorController,
|
|
8
|
+
TriggerInfo,
|
|
9
|
+
)
|
|
10
|
+
from ophyd_async.epics import adcore
|
|
11
|
+
|
|
12
|
+
from dodal.devices.i13_1.merlin_io import MerlinDriverIO, MerlinImageMode
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class MerlinController(DetectorController):
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
driver: MerlinDriverIO,
|
|
19
|
+
good_states: frozenset[adcore.DetectorState] = adcore.DEFAULT_GOOD_STATES,
|
|
20
|
+
) -> None:
|
|
21
|
+
self.driver = driver
|
|
22
|
+
self.good_states = good_states
|
|
23
|
+
self.frame_timeout: float = 0
|
|
24
|
+
self._arm_status: AsyncStatus | None = None
|
|
25
|
+
for drv_child in self.driver.children():
|
|
26
|
+
logging.debug(drv_child)
|
|
27
|
+
|
|
28
|
+
def get_deadtime(self, exposure: float | None) -> float:
|
|
29
|
+
return 0.002
|
|
30
|
+
|
|
31
|
+
async def prepare(self, trigger_info: TriggerInfo):
|
|
32
|
+
self.frame_timeout = (
|
|
33
|
+
DEFAULT_TIMEOUT + await self.driver.acquire_time.get_value()
|
|
34
|
+
)
|
|
35
|
+
await asyncio.gather(
|
|
36
|
+
self.driver.num_images.set(trigger_info.total_number_of_triggers),
|
|
37
|
+
self.driver.image_mode.set(MerlinImageMode.MULTIPLE),
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
async def arm(self):
|
|
41
|
+
self._arm_status = await adcore.start_acquiring_driver_and_ensure_status(
|
|
42
|
+
self.driver, good_states=self.good_states, timeout=self.frame_timeout
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
async def wait_for_idle(self):
|
|
46
|
+
if self._arm_status:
|
|
47
|
+
await self._arm_status
|
|
48
|
+
|
|
49
|
+
async def disarm(self):
|
|
50
|
+
# We can't use caput callback as we already used it in arm() and we can't have
|
|
51
|
+
# 2 or they will deadlock
|
|
52
|
+
await adcore.stop_busy_record(self.driver.acquire, False, timeout=1)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from ophyd_async.core import StrictEnum
|
|
2
|
+
from ophyd_async.epics import adcore
|
|
3
|
+
from ophyd_async.epics.core import epics_signal_rw_rbv
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class MerlinImageMode(StrictEnum):
|
|
7
|
+
SINGLE = "Single"
|
|
8
|
+
MULTIPLE = "Multiple"
|
|
9
|
+
CONTINUOUS = "Continuous"
|
|
10
|
+
THRESHOLD = "Threshold"
|
|
11
|
+
BACKGROUND = "Background"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class MerlinDriverIO(adcore.ADBaseIO):
|
|
15
|
+
def __init__(self, prefix: str, name: str = "") -> None:
|
|
16
|
+
super().__init__(prefix, name)
|
|
17
|
+
self.image_mode = epics_signal_rw_rbv(MerlinImageMode, prefix + "ImageMode")
|
dodal/devices/i24/beam_center.py
CHANGED
|
@@ -7,6 +7,6 @@ from ophyd_async.epics.core import epics_signal_rw
|
|
|
7
7
|
|
|
8
8
|
class DetectorBeamCenter(StandardReadable):
|
|
9
9
|
def __init__(self, prefix: str, name: str = "") -> None:
|
|
10
|
-
self.beam_x = epics_signal_rw(float, prefix + "BeamX")
|
|
10
|
+
self.beam_x = epics_signal_rw(float, prefix + "BeamX") # in pixels
|
|
11
11
|
self.beam_y = epics_signal_rw(float, prefix + "BeamY")
|
|
12
12
|
super().__init__(name)
|
dodal/devices/p45.py
CHANGED
|
@@ -1,44 +1,55 @@
|
|
|
1
|
-
from
|
|
2
|
-
from
|
|
3
|
-
from ophyd.areadetector.base import ADComponent as Cpt
|
|
1
|
+
from ophyd_async.core import StandardReadable
|
|
2
|
+
from ophyd_async.epics.motor import Motor
|
|
4
3
|
|
|
5
4
|
|
|
6
|
-
class SampleY(
|
|
5
|
+
class SampleY(StandardReadable):
|
|
7
6
|
"""
|
|
8
7
|
Motors for controlling the sample's y position and stretch in the y axis.
|
|
9
8
|
"""
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
def __init__(self, prefix: str, name="") -> None:
|
|
11
|
+
with self.add_children_as_readables():
|
|
12
|
+
self.base = Motor(prefix + "CS:Y")
|
|
13
|
+
self.stretch = Motor(prefix + "CS:Y:STRETCH")
|
|
14
|
+
self.top = Motor(prefix + "Y:TOP")
|
|
15
|
+
self.bottom = Motor(prefix + "Y:BOT")
|
|
16
|
+
super().__init__(name=name)
|
|
15
17
|
|
|
16
18
|
|
|
17
|
-
class SampleTheta(
|
|
19
|
+
class SampleTheta(StandardReadable):
|
|
18
20
|
"""
|
|
19
21
|
Motors for controlling the sample's theta position and skew
|
|
20
22
|
"""
|
|
21
23
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
def __init__(self, prefix: str, name="") -> None:
|
|
25
|
+
with self.add_children_as_readables():
|
|
26
|
+
self.base = Motor(prefix + "THETA:POS")
|
|
27
|
+
self.skew = Motor(prefix + "THETA:SKEW")
|
|
28
|
+
self.top = Motor(prefix + "THETA:TOP")
|
|
29
|
+
self.bottom = Motor(prefix + "THETA:BOT")
|
|
30
|
+
super().__init__(name=name)
|
|
26
31
|
|
|
27
32
|
|
|
28
|
-
class TomoStageWithStretchAndSkew(
|
|
33
|
+
class TomoStageWithStretchAndSkew(StandardReadable):
|
|
29
34
|
"""
|
|
30
35
|
Grouping of motors for the P45 tomography stage
|
|
31
36
|
"""
|
|
32
37
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
38
|
+
def __init__(self, prefix: str, name="") -> None:
|
|
39
|
+
with self.add_children_as_readables():
|
|
40
|
+
self.x = Motor(prefix + "X")
|
|
41
|
+
self.y = SampleY(prefix)
|
|
42
|
+
self.theta = SampleTheta(prefix)
|
|
43
|
+
super().__init__(name=name)
|
|
36
44
|
|
|
37
45
|
|
|
38
|
-
class Choppers(
|
|
46
|
+
class Choppers(StandardReadable):
|
|
39
47
|
"""
|
|
40
48
|
Grouping for the P45 chopper motors
|
|
41
49
|
"""
|
|
42
50
|
|
|
43
|
-
|
|
44
|
-
|
|
51
|
+
def __init__(self, prefix: str, name="") -> None:
|
|
52
|
+
with self.add_children_as_readables():
|
|
53
|
+
self.x = Motor(prefix + "ENDAT")
|
|
54
|
+
self.y = Motor(prefix + "BISS")
|
|
55
|
+
super().__init__(name=name)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
from ophyd_async.core import StandardReadable
|
|
2
|
-
from ophyd_async.epics.core import
|
|
1
|
+
from ophyd_async.core import StandardReadable
|
|
2
|
+
from ophyd_async.epics.core import epics_signal_rw_rbv
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
class SampleAngleStage(StandardReadable):
|
|
@@ -9,29 +9,3 @@ class SampleAngleStage(StandardReadable):
|
|
|
9
9
|
self.roll = epics_signal_rw_rbv(float, prefix + "WRITEROLL", ":RBV")
|
|
10
10
|
self.pitch = epics_signal_rw_rbv(float, prefix + "WRITEPITCH", ":RBV")
|
|
11
11
|
super().__init__(name=name)
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class p99StageSelections(SubsetEnum):
|
|
15
|
-
EMPTY = "Empty"
|
|
16
|
-
MN5UM = "Mn 5um"
|
|
17
|
-
FE = "Fe (empty)"
|
|
18
|
-
CO5UM = "Co 5um"
|
|
19
|
-
NI5UM = "Ni 5um"
|
|
20
|
-
CU5UM = "Cu 5um"
|
|
21
|
-
ZN5UM = "Zn 5um"
|
|
22
|
-
ZR = "Zr (empty)"
|
|
23
|
-
MO = "Mo (empty)"
|
|
24
|
-
RH = "Rh (empty)"
|
|
25
|
-
PD = "Pd (empty)"
|
|
26
|
-
AG = "Ag (empty)"
|
|
27
|
-
CD25UM = "Cd 25um"
|
|
28
|
-
W = "W (empty)"
|
|
29
|
-
PT = "Pt (empty)"
|
|
30
|
-
USER = "User"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
class FilterMotor(StandardReadable):
|
|
34
|
-
def __init__(self, prefix: str, name: str = ""):
|
|
35
|
-
with self.add_children_as_readables():
|
|
36
|
-
self.user_setpoint = epics_signal_rw(p99StageSelections, prefix)
|
|
37
|
-
super().__init__(name=name)
|
dodal/devices/robot.py
CHANGED
|
@@ -70,8 +70,8 @@ class BartRobot(StandardReadable, Movable):
|
|
|
70
70
|
self.program_running = epics_signal_r(bool, prefix + "PROGRAM_RUNNING")
|
|
71
71
|
self.program_name = epics_signal_r(str, prefix + "PROGRAM_NAME")
|
|
72
72
|
self.error_str = epics_signal_r(str, prefix + "PRG_ERR_MSG")
|
|
73
|
-
|
|
74
|
-
self.
|
|
73
|
+
self.error_code = epics_signal_r(int, prefix + "PRG_ERR_CODE")
|
|
74
|
+
self.reset = epics_signal_x(prefix + "RESET.PROC")
|
|
75
75
|
super().__init__(name=name)
|
|
76
76
|
|
|
77
77
|
async def pin_mounted_or_no_pin_found(self):
|
dodal/devices/s4_slit_gaps.py
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
from
|
|
1
|
+
from ophyd_async.core import StandardReadable
|
|
2
|
+
from ophyd_async.epics.motor import Motor
|
|
2
3
|
|
|
3
4
|
|
|
4
|
-
class S4SlitGaps(
|
|
5
|
+
class S4SlitGaps(StandardReadable):
|
|
5
6
|
"""Note that the S4 slits have a different PV fromat to other beamline slits"""
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
def __init__(self, prefix: str, name="") -> None:
|
|
9
|
+
with self.add_children_as_readables():
|
|
10
|
+
self.xgap = Motor(prefix + "XGAP")
|
|
11
|
+
self.ygap = Motor(prefix + "YGAP")
|
|
12
|
+
super().__init__(name=name)
|
dodal/devices/undulator_dcm.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
|
|
3
3
|
from bluesky.protocols import Movable
|
|
4
|
-
from ophyd_async.core import AsyncStatus, StandardReadable
|
|
4
|
+
from ophyd_async.core import AsyncStatus, Reference, StandardReadable
|
|
5
5
|
|
|
6
6
|
from dodal.common.beamlines.beamline_parameters import get_beamline_parameters
|
|
7
7
|
|
|
@@ -33,12 +33,8 @@ class UndulatorDCM(StandardReadable, Movable):
|
|
|
33
33
|
prefix: str = "",
|
|
34
34
|
name: str = "",
|
|
35
35
|
):
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
# Attributes are set after super call so they are not renamed to
|
|
39
|
-
# <name>-undulator, etc.
|
|
40
|
-
self.undulator = undulator
|
|
41
|
-
self.dcm = dcm
|
|
36
|
+
self.undulator_ref = Reference(undulator)
|
|
37
|
+
self.dcm_ref = Reference(dcm)
|
|
42
38
|
|
|
43
39
|
# These attributes are just used by hyperion for lookup purposes
|
|
44
40
|
self.pitch_energy_table_path = (
|
|
@@ -53,13 +49,15 @@ class UndulatorDCM(StandardReadable, Movable):
|
|
|
53
49
|
daq_configuration_path + "/domain/beamlineParameters"
|
|
54
50
|
)["DCM_Perp_Offset_FIXED"]
|
|
55
51
|
|
|
52
|
+
super().__init__(name)
|
|
53
|
+
|
|
56
54
|
@AsyncStatus.wrap
|
|
57
55
|
async def set(self, value: float):
|
|
58
|
-
await self.
|
|
56
|
+
await self.undulator_ref().raise_if_not_enabled()
|
|
59
57
|
await asyncio.gather(
|
|
60
|
-
self.
|
|
61
|
-
self.
|
|
58
|
+
self.dcm_ref().energy_in_kev.set(value, timeout=ENERGY_TIMEOUT_S),
|
|
59
|
+
self.undulator_ref().set(value),
|
|
62
60
|
)
|
|
63
61
|
# DCM Perp pitch
|
|
64
62
|
LOGGER.info(f"Adjusting DCM offset to {self.dcm_fixed_offset_mm} mm")
|
|
65
|
-
await self.
|
|
63
|
+
await self.dcm_ref().offset_in_mm.set(self.dcm_fixed_offset_mm)
|
|
@@ -32,28 +32,32 @@ async def energy_distance_table(lookup_table_path: str) -> np.ndarray:
|
|
|
32
32
|
return loadtxt(StringIO(raw_table), comments=["#", "Units"])
|
|
33
33
|
|
|
34
34
|
|
|
35
|
-
def
|
|
35
|
+
def parse_lookup_table(filename: str) -> list[Sequence]:
|
|
36
|
+
"""Parse a generic lookup table with a number of columns >= 2 and return a list \
|
|
37
|
+
in column major order of the values in it."""
|
|
38
|
+
LOGGER.info(f"Parsing lookup table file {filename}")
|
|
39
|
+
|
|
40
|
+
lut_vals = zip(*loadtxt(filename, comments=["#", "Units"]), strict=False)
|
|
41
|
+
return list(lut_vals)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def linear_interpolation_lut(
|
|
45
|
+
s_values: Sequence, t_values: Sequence
|
|
46
|
+
) -> Callable[[float], float]:
|
|
36
47
|
"""Returns a callable that converts values by linear interpolation of lookup table
|
|
37
48
|
values.
|
|
38
49
|
|
|
39
50
|
If the value falls outside the lookup table then the closest value will be used."""
|
|
40
|
-
LOGGER.info(f"Using lookup table {filename}")
|
|
41
|
-
s_and_t_vals = zip(*loadtxt(filename, comments=["#", "Units"]), strict=False)
|
|
42
|
-
|
|
43
|
-
s_values: Sequence
|
|
44
|
-
t_values: Sequence
|
|
45
|
-
s_values, t_values = s_and_t_vals
|
|
46
|
-
|
|
47
51
|
# numpy interp expects x-values to be increasing
|
|
48
52
|
if not np.all(np.diff(s_values) > 0):
|
|
49
53
|
LOGGER.info(
|
|
50
|
-
|
|
54
|
+
"Configuration values in the lookup table are not ascending, trying reverse order..."
|
|
51
55
|
)
|
|
52
56
|
s_values = list(reversed(s_values))
|
|
53
57
|
t_values = list(reversed(t_values))
|
|
54
58
|
if not np.all(np.diff(s_values) > 0):
|
|
55
59
|
raise AssertionError(
|
|
56
|
-
|
|
60
|
+
"Configuration lookup table does not monotonically increase or decrease."
|
|
57
61
|
)
|
|
58
62
|
|
|
59
63
|
def s_to_t2(s: float) -> float:
|
|
File without changes
|