dls-dodal 1.36.1a0__py3-none-any.whl → 1.36.2__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.1a0.dist-info → dls_dodal-1.36.2.dist-info}/METADATA +1 -1
- {dls_dodal-1.36.1a0.dist-info → dls_dodal-1.36.2.dist-info}/RECORD +17 -21
- dodal/_version.py +2 -2
- dodal/beamlines/__init__.py +1 -0
- dodal/beamlines/adsim.py +75 -0
- dodal/beamlines/i24.py +16 -0
- dodal/devices/adsim.py +10 -10
- dodal/devices/aperture.py +0 -7
- dodal/devices/aperturescatterguard.py +79 -195
- dodal/devices/attenuator.py +15 -5
- dodal/devices/focusing_mirror.py +12 -3
- dodal/devices/oav/pin_image_recognition/__init__.py +2 -4
- dodal/devices/undulator_dcm.py +4 -0
- dodal/adsim.py +0 -17
- dodal/devices/areadetector/__init__.py +0 -10
- dodal/devices/areadetector/adaravis.py +0 -101
- dodal/devices/areadetector/adsim.py +0 -47
- dodal/devices/areadetector/adutils.py +0 -81
- {dls_dodal-1.36.1a0.dist-info → dls_dodal-1.36.2.dist-info}/LICENSE +0 -0
- {dls_dodal-1.36.1a0.dist-info → dls_dodal-1.36.2.dist-info}/WHEEL +0 -0
- {dls_dodal-1.36.1a0.dist-info → dls_dodal-1.36.2.dist-info}/entry_points.txt +0 -0
- {dls_dodal-1.36.1a0.dist-info → dls_dodal-1.36.2.dist-info}/top_level.txt +0 -0
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
dodal/__init__.py,sha256=Ksms_WJF8LTkbm38gEpm1jBpGqcQ8NGvmb2ZJlOE1j8,198
|
|
2
2
|
dodal/__main__.py,sha256=kP2S2RPitnOWpNGokjZ1Yq-1umOtp5sNOZk2B3tBPLM,111
|
|
3
|
-
dodal/_version.py,sha256=
|
|
4
|
-
dodal/adsim.py,sha256=OW2dcS7ciD4Yq9WFw4PN_c5Bwccrmu7R-zr-u6ZCbQM,497
|
|
3
|
+
dodal/_version.py,sha256=eqat9FvXyrftwP2dajRS3LWZiDD9Pay4_MD5eY1_jxU,413
|
|
5
4
|
dodal/cli.py,sha256=NieWNUgLUxyck1rHoFAPJjX1xXLzHNdQ-s4wvxYFfps,3757
|
|
6
5
|
dodal/log.py,sha256=0to7CRsbzbgVfAAfKRAMhsaUuKqF2-7CGdQc-z8Uhno,9499
|
|
7
6
|
dodal/utils.py,sha256=h2sNmTlsaznfxusV1Xj_mXtNjzsWjWAgmps6I0YNA3U,18097
|
|
8
7
|
dodal/beamline_specific_utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
8
|
dodal/beamline_specific_utils/i03.py,sha256=P6Ls4FoVtcacH0RJM3v6ZwwGx27oMppcBdW0la-ohTY,377
|
|
10
9
|
dodal/beamlines/README.md,sha256=K9MkL_GomxlsoTB7Mz-_dJA5NNSbmCfMiutchGg3C8o,404
|
|
11
|
-
dodal/beamlines/__init__.py,sha256=
|
|
10
|
+
dodal/beamlines/__init__.py,sha256=FsS1hMz9nqwTP12UtCJsfVn712mFElcBq2kKKpscp9k,3074
|
|
11
|
+
dodal/beamlines/adsim.py,sha256=DUAFS1ueoZ6DK2cmZkiEm3NElnaro1mUvyodv14pSmU,1839
|
|
12
12
|
dodal/beamlines/b01_1.py,sha256=0gLjg0O9ttMjHzszSyJ_GT3fnoAB6u4aJ4MdAfjJbHA,1788
|
|
13
13
|
dodal/beamlines/i03.py,sha256=WOMkGTcrrKqoBNeRObumMA8Nlo404uIr2ccfn8gK-zs,18063
|
|
14
14
|
dodal/beamlines/i04.py,sha256=z8LUbhyfUDU08oSp85hg7hFE8FJkAyLsqPUQScs5SUA,14567
|
|
@@ -17,7 +17,7 @@ dodal/beamlines/i13_1.py,sha256=csXHrdwUh4sXTmb4X6ZiiSS_XxRkNShsVoBMxYI6rG0,1833
|
|
|
17
17
|
dodal/beamlines/i20_1.py,sha256=MaPgONHqpoZuBtkiKEzYtViJnKBM2_ekeP4OdbmuXHE,1158
|
|
18
18
|
dodal/beamlines/i22.py,sha256=tzx8w86uvJHo5TuwPoIUErB7C5mGhqTzlv45qrppld0,7228
|
|
19
19
|
dodal/beamlines/i23.py,sha256=2j5qLoqE_hg9ETHqNkOVu7LLkVB8qalgXeORnVYKN_I,1075
|
|
20
|
-
dodal/beamlines/i24.py,sha256=
|
|
20
|
+
dodal/beamlines/i24.py,sha256=w9xkufc7CluVj5nvQww1B7k8VN5RzNQ6AsX8bfyAi9k,8544
|
|
21
21
|
dodal/beamlines/p38.py,sha256=JJbclLYoRdIxcpzpW4oTj77YJ001CdEAM0bKRk7seYI,8735
|
|
22
22
|
dodal/beamlines/p45.py,sha256=N4SDTIFok3uMqb37higZHMr3xRjxItsT4ib_KacKKAE,2935
|
|
23
23
|
dodal/beamlines/p99.py,sha256=I6c_3NbvEPOpy2z1uwLRqwCkd83Sf15-OXEykLkn0-c,910
|
|
@@ -36,11 +36,11 @@ dodal/common/beamlines/beamline_utils.py,sha256=LCUt58Y2wRd15PdMIYSeHiKhWVYgt6UC
|
|
|
36
36
|
dodal/common/beamlines/device_helpers.py,sha256=aFzYa4YOPcbrvwoW1k3OrN6NVLYYmMPR8YkCpHHe-98,962
|
|
37
37
|
dodal/devices/CTAB.py,sha256=5_261Ox6NG2cJIzzwnjWz289BG0nZoE0wKOaI5V5jqM,1998
|
|
38
38
|
dodal/devices/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
39
|
-
dodal/devices/adsim.py,sha256=
|
|
40
|
-
dodal/devices/aperture.py,sha256=
|
|
41
|
-
dodal/devices/aperturescatterguard.py,sha256=
|
|
39
|
+
dodal/devices/adsim.py,sha256=vCexraF4zLssHdjfPod-XuQGJE_sWoCttFdx__HDS8w,488
|
|
40
|
+
dodal/devices/aperture.py,sha256=yyw2ei3gM_lmZWDQ6VTbydB58RCDTen_nqBZyoTP2IM,583
|
|
41
|
+
dodal/devices/aperturescatterguard.py,sha256=PRNnGbxYKFWoa1m70Mz-6Ta4NtFG0ShUprBvv2viUeI,8993
|
|
42
42
|
dodal/devices/apple2_undulator.py,sha256=HtjHiDX80n_nzabuFcUioH-gWH1a6VjZzw_xStq7S4w,22470
|
|
43
|
-
dodal/devices/attenuator.py,sha256=
|
|
43
|
+
dodal/devices/attenuator.py,sha256=SEDnnNeUpwBArJbPcwHyr5BwkHO1Jvgsrw1bpFz5u-o,2894
|
|
44
44
|
dodal/devices/backlight.py,sha256=nQIr3J-I-OXnOUoWmr3ruy3nhq_q2US1KXC4NrGG_2U,1634
|
|
45
45
|
dodal/devices/cryostream.py,sha256=K-ldpredpeDTzNt4qtQMg99nKJNjBYoXBbK0WJGexzw,656
|
|
46
46
|
dodal/devices/dcm.py,sha256=JbyxLnrS68nnnv39l9XEWgJgXUBqxX6aFo19MZnL36E,2574
|
|
@@ -50,7 +50,7 @@ dodal/devices/eiger_odin.py,sha256=oZl16K-Qb2yL6tK1fyDQvqbbhhvYMSVcf_e2CjlqMa4,7
|
|
|
50
50
|
dodal/devices/fast_grid_scan.py,sha256=dKF2-8CNmm-LaMXBkqkO31pTcAAwiAXznEeB6tg0f6E,11149
|
|
51
51
|
dodal/devices/fluorescence_detector_motion.py,sha256=-1qCSvW0PdT0m6BcoLxrtc0OJ5UDIBsEe11EOLr-gFw,501
|
|
52
52
|
dodal/devices/flux.py,sha256=RtPStHw7Mad0igVKntKWVZfuZn2clokVJqH14HLix6M,198
|
|
53
|
-
dodal/devices/focusing_mirror.py,sha256=
|
|
53
|
+
dodal/devices/focusing_mirror.py,sha256=vdUPkwyCAZBSR3LQ-EojDOoxVy1ZmOaD_nevETbj7BA,6592
|
|
54
54
|
dodal/devices/hutch_shutter.py,sha256=WXY9JwqAa5prbf72IP7_MTKndPDtAltCpPJlNbq-F_0,3313
|
|
55
55
|
dodal/devices/ipin.py,sha256=eq5jlKw7WGQi8VLrAWpaAIsZmfiVf-5Q0td_B22H6A4,473
|
|
56
56
|
dodal/devices/linkam3.py,sha256=O1ufCb5ce6nfGXVwiO3I1WEnZ_Jp0azWxBcrZ7PWQm4,3869
|
|
@@ -72,16 +72,12 @@ dodal/devices/tetramm.py,sha256=460luDYab-u1QB0CPe7lPihtm9nZxdCDGtLPnXI-XGo,8447
|
|
|
72
72
|
dodal/devices/thawer.py,sha256=4t4yF4VDIrT_tQ8RwjmXe_hDMwVjR8A-4rDkPx19b28,1672
|
|
73
73
|
dodal/devices/turbo_slit.py,sha256=B6SPXqviMnG-U4PnUF1BdTO0LBKmTuwAUKRbxMiNJXo,1125
|
|
74
74
|
dodal/devices/undulator.py,sha256=rQjDhrvgf4uXUEO17CiLopNDEagWOgkmpa02BarozDE,5295
|
|
75
|
-
dodal/devices/undulator_dcm.py,sha256=
|
|
75
|
+
dodal/devices/undulator_dcm.py,sha256=4Y1ZgKatBenQgg4DuZnMtKwLDOH2YvUMo1QkFE0aaXs,2432
|
|
76
76
|
dodal/devices/watsonmarlow323_pump.py,sha256=rwU94YE6esgGLYdh-pe8nBo_3tvgp6brrrbPDrqp5_M,1406
|
|
77
77
|
dodal/devices/webcam.py,sha256=mef075ynDbzZ4pNAjfxR_9tdTTqF_rM7hAOVEEOV-Do,2408
|
|
78
78
|
dodal/devices/xbpm_feedback.py,sha256=j8MHhhE0feoe6R54zPKqS5EbQ0bEDR-nOpLHzHhnHHQ,1156
|
|
79
79
|
dodal/devices/zebra.py,sha256=p191eIGfmwM2EE1YJn6rRgChim2kqXl2KIYbMPUSOvg,9474
|
|
80
80
|
dodal/devices/zebra_controlled_shutter.py,sha256=5-SH5HoXp_6P-xAtfDFJKQq6mBDwreubuCULSz78fgw,1852
|
|
81
|
-
dodal/devices/areadetector/__init__.py,sha256=8IwLxuZMW0MOJpJp_ZDdlaE20hrtsH_PXWGaKgMiYs4,240
|
|
82
|
-
dodal/devices/areadetector/adaravis.py,sha256=Cqw_Mzrp_zODFxQ2LZBJzHp_DsZ6_dAITkZz8gYz_0w,3797
|
|
83
|
-
dodal/devices/areadetector/adsim.py,sha256=cIc9PRbKnftBk7Ut8d8CU_TVrin8EwcKHObP2n9VxWM,1876
|
|
84
|
-
dodal/devices/areadetector/adutils.py,sha256=4axFR3wtn-K-sjMVJyfTcu-8g35odf2cY8mTKv1gS-o,3093
|
|
85
81
|
dodal/devices/areadetector/plugins/CAM.py,sha256=sZzJm5Ez3eWfXZi_EB67wluhZmMQm1UyOc2bJFfzd1U,964
|
|
86
82
|
dodal/devices/areadetector/plugins/MJPG.py,sha256=QTsxCoWbofNpLMGPoOR2hWoM33KyntuLepbF0YmX0KE,3031
|
|
87
83
|
dodal/devices/detector/__init__.py,sha256=-RdACL3tzc3lLArWOoGNje48UUlv2fElOmGOz9yOuO0,317
|
|
@@ -116,7 +112,7 @@ dodal/devices/oav/oav_detector.py,sha256=lUzdkAgviBtiZkN92fA_kleQRmmpfx_wTA06rq-
|
|
|
116
112
|
dodal/devices/oav/oav_parameters.py,sha256=gGN73TQGUiRzlIO5YKiqCRkjpTKsV6LFGo7Eu4Vs82g,6074
|
|
117
113
|
dodal/devices/oav/oav_to_redis_forwarder.py,sha256=pfhaW6Uo_1wDNfywyPkS5UTrY8yhkerhjgJfRMqrJRA,6259
|
|
118
114
|
dodal/devices/oav/utils.py,sha256=3IvSTw6Ygkaz4Hzoz0eU2l6mljpq0NO57M15e-K4jOE,3182
|
|
119
|
-
dodal/devices/oav/pin_image_recognition/__init__.py,sha256=
|
|
115
|
+
dodal/devices/oav/pin_image_recognition/__init__.py,sha256=_eCq-rEtGNXVfrpahfKMLu53lEr49q5rtVqg0GhiODQ,6534
|
|
120
116
|
dodal/devices/oav/pin_image_recognition/manual_test.py,sha256=h1Rto6ZDCB3jWhjSy9N8ECxRN583iYDJr9LxrTJ8kfE,903
|
|
121
117
|
dodal/devices/oav/pin_image_recognition/utils.py,sha256=L9ypluYqeOFoS7gQuws-vTNc8LqaKl2ZIDNeQ2JaNpg,8592
|
|
122
118
|
dodal/devices/oav/snapshots/grid_overlay.py,sha256=CdvCdTKMCiwMwxm2lV28KpcIUSXlscZmWxb73_KKmiI,3694
|
|
@@ -148,9 +144,9 @@ dodal/plan_stubs/wrapped.py,sha256=nriHKX4BF010CmrhdoUhY3-txClW5W8TPLz64kE_AXU,4
|
|
|
148
144
|
dodal/plans/__init__.py,sha256=nH1jNxw3DzDMg9O8Uda0kqKIalRVEWBrq07OLY6Ey38,93
|
|
149
145
|
dodal/plans/scanspec.py,sha256=Q0AcvTKRT401iGMRDSqK-D523UX5_ofiVMZ_rNXKOx8,2074
|
|
150
146
|
dodal/plans/wrapped.py,sha256=Cr2iOpQCuk2ORKo5CZOh-zbQXAjoTfaLrfm7r1--GhU,2098
|
|
151
|
-
dls_dodal-1.36.
|
|
152
|
-
dls_dodal-1.36.
|
|
153
|
-
dls_dodal-1.36.
|
|
154
|
-
dls_dodal-1.36.
|
|
155
|
-
dls_dodal-1.36.
|
|
156
|
-
dls_dodal-1.36.
|
|
147
|
+
dls_dodal-1.36.2.dist-info/LICENSE,sha256=tAkwu8-AdEyGxGoSvJ2gVmQdcicWw3j1ZZueVV74M-E,11357
|
|
148
|
+
dls_dodal-1.36.2.dist-info/METADATA,sha256=CWBsjskEIuoboOXY_f3RJ4xuoDWdR5c9RhDjsuqNL3w,16655
|
|
149
|
+
dls_dodal-1.36.2.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
|
150
|
+
dls_dodal-1.36.2.dist-info/entry_points.txt,sha256=bycw_EKUzup_rxfCetOwcauXV4kLln_OPpPT8jEnr-I,94
|
|
151
|
+
dls_dodal-1.36.2.dist-info/top_level.txt,sha256=xIozdmZk_wmMV4wugpq9-6eZs0vgADNUKz3j2UAwlhc,6
|
|
152
|
+
dls_dodal-1.36.2.dist-info/RECORD,,
|
dodal/_version.py
CHANGED
dodal/beamlines/__init__.py
CHANGED
dodal/beamlines/adsim.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from ophyd_async.epics.adsimdetector import SimDetector
|
|
4
|
+
|
|
5
|
+
from dodal.common.beamlines.beamline_utils import (
|
|
6
|
+
device_factory,
|
|
7
|
+
get_path_provider,
|
|
8
|
+
set_path_provider,
|
|
9
|
+
)
|
|
10
|
+
from dodal.common.beamlines.beamline_utils import set_beamline as set_utils_beamline
|
|
11
|
+
from dodal.common.visit import LocalDirectoryServiceClient, StaticVisitPathProvider
|
|
12
|
+
from dodal.devices.adsim import SimStage
|
|
13
|
+
from dodal.log import set_beamline as set_log_beamline
|
|
14
|
+
from dodal.utils import BeamlinePrefix
|
|
15
|
+
|
|
16
|
+
BL = "adsim"
|
|
17
|
+
PREFIX = BeamlinePrefix("t01")
|
|
18
|
+
set_log_beamline(BL)
|
|
19
|
+
set_utils_beamline(BL)
|
|
20
|
+
|
|
21
|
+
set_path_provider(
|
|
22
|
+
StaticVisitPathProvider(
|
|
23
|
+
BL,
|
|
24
|
+
Path("/tmp"),
|
|
25
|
+
client=LocalDirectoryServiceClient(),
|
|
26
|
+
)
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
"""
|
|
30
|
+
Beamline module for use with the simulated AreaDetector and motors.
|
|
31
|
+
These devices are simulated at the EPICS level, enabling testing of
|
|
32
|
+
dodal and ophyd-async against what appear to be "real" signals.
|
|
33
|
+
|
|
34
|
+
Usage Example
|
|
35
|
+
-------------
|
|
36
|
+
|
|
37
|
+
Start the simulated beamline by following the epics-containers tutorial at
|
|
38
|
+
https://epics-containers.github.io/main/tutorials/launch_example.html
|
|
39
|
+
And ensure that the signals are visible:
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
export EPICS_CA_ADDR_LIST=127.0.0.1
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
How to use the devices in a plan:
|
|
46
|
+
In an ipython terminal run:
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from bluesky.run_engine import RunEngine
|
|
50
|
+
|
|
51
|
+
from dodal.beamlines.adsim import det, stage
|
|
52
|
+
from dodal.plans import count
|
|
53
|
+
|
|
54
|
+
RE = RunEngine()
|
|
55
|
+
d = det(connect_immediately=True)
|
|
56
|
+
s = stage(connect_immediately=True)
|
|
57
|
+
RE(count([d], num=10))
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@device_factory()
|
|
64
|
+
def stage() -> SimStage:
|
|
65
|
+
return SimStage(f"{PREFIX.beamline_prefix}-MO-SIMC-01:")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@device_factory()
|
|
69
|
+
def det() -> SimDetector:
|
|
70
|
+
return SimDetector(
|
|
71
|
+
f"{PREFIX.beamline_prefix}-DI-CAM-01:",
|
|
72
|
+
path_provider=get_path_provider(),
|
|
73
|
+
drv_suffix="DET:",
|
|
74
|
+
hdf_suffix="HDF:",
|
|
75
|
+
)
|
dodal/beamlines/i24.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from dodal.common.beamlines.beamline_utils import BL, device_instantiation
|
|
2
2
|
from dodal.common.beamlines.beamline_utils import set_beamline as set_utils_beamline
|
|
3
|
+
from dodal.devices.attenuator import ReadOnlyAttenuator
|
|
3
4
|
from dodal.devices.detector import DetectorParams
|
|
4
5
|
from dodal.devices.eiger import EigerDetector
|
|
5
6
|
from dodal.devices.hutch_shutter import HutchShutter
|
|
@@ -29,6 +30,21 @@ set_log_beamline(BL)
|
|
|
29
30
|
set_utils_beamline(BL)
|
|
30
31
|
|
|
31
32
|
|
|
33
|
+
def attenuator(
|
|
34
|
+
wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False
|
|
35
|
+
) -> ReadOnlyAttenuator:
|
|
36
|
+
"""Get a read-only attenuator device for i24, instantiate it if it hasn't already
|
|
37
|
+
been. If this is called when already instantiated in i24, it will return the
|
|
38
|
+
existing object."""
|
|
39
|
+
return device_instantiation(
|
|
40
|
+
ReadOnlyAttenuator,
|
|
41
|
+
"attenuator",
|
|
42
|
+
"-OP-ATTN-01:",
|
|
43
|
+
wait_for_connection,
|
|
44
|
+
fake_with_ophyd_sim,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
32
48
|
def aperture(
|
|
33
49
|
wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False
|
|
34
50
|
) -> Aperture:
|
dodal/devices/adsim.py
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
from
|
|
1
|
+
from ophyd_async.core import StandardReadable
|
|
2
|
+
from ophyd_async.epics.motor import Motor
|
|
2
3
|
|
|
3
4
|
|
|
4
|
-
class SimStage(
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
"""
|
|
5
|
+
class SimStage(StandardReadable):
|
|
6
|
+
"""Simulated Sample Stage for use with the containerised simulated beamline
|
|
7
|
+
https://github.com/epics-containers/example-services"""
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
def __init__(self, prefix: str, name: str = "sim"):
|
|
10
|
+
with self.add_children_as_readables():
|
|
11
|
+
self.x = Motor(prefix + "M1")
|
|
12
|
+
self.y = Motor(prefix + "M2")
|
|
13
|
+
super().__init__(name=name)
|
dodal/devices/aperture.py
CHANGED
|
@@ -12,10 +12,3 @@ class Aperture(StandardReadable):
|
|
|
12
12
|
self.medium = epics_signal_r(float, prefix + "Y:MEDIUM_CALC")
|
|
13
13
|
self.large = epics_signal_r(float, prefix + "Y:LARGE_CALC")
|
|
14
14
|
super().__init__(name)
|
|
15
|
-
|
|
16
|
-
async def in_position(self):
|
|
17
|
-
return (
|
|
18
|
-
await self.small.get_value()
|
|
19
|
-
or await self.medium.get_value()
|
|
20
|
-
or await self.large.get_value()
|
|
21
|
-
)
|
|
@@ -1,18 +1,14 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
-
from collections.abc import Callable, Coroutine
|
|
5
|
-
from typing import Any
|
|
6
4
|
|
|
7
|
-
from bluesky.protocols import Movable
|
|
5
|
+
from bluesky.protocols import Movable
|
|
8
6
|
from ophyd_async.core import (
|
|
9
7
|
AsyncStatus,
|
|
10
|
-
Reference,
|
|
11
8
|
StandardReadable,
|
|
12
9
|
StandardReadableFormat,
|
|
13
10
|
StrictEnum,
|
|
14
11
|
)
|
|
15
|
-
from ophyd_async.epics.motor import Motor
|
|
16
12
|
from pydantic import BaseModel, Field
|
|
17
13
|
|
|
18
14
|
from dodal.common.beamlines.beamline_parameters import GDABeamlineParameters
|
|
@@ -111,157 +107,7 @@ def load_positions_from_beamline_parameters(
|
|
|
111
107
|
}
|
|
112
108
|
|
|
113
109
|
|
|
114
|
-
async def _safe_move_whilst_in_beam(
|
|
115
|
-
aperture: Aperture,
|
|
116
|
-
scatterguard: Scatterguard,
|
|
117
|
-
position: AperturePosition,
|
|
118
|
-
aperture_z_tolerance: float,
|
|
119
|
-
):
|
|
120
|
-
"""
|
|
121
|
-
Move the aperture and scatterguard combo safely to a new position.
|
|
122
|
-
See https://github.com/DiamondLightSource/hyperion/wiki/Aperture-Scatterguard-Collisions
|
|
123
|
-
for why this is required. TLDR is that we have a collision at the top of y so we need
|
|
124
|
-
to make sure we move the assembly down before we move the scatterguard up.
|
|
125
|
-
|
|
126
|
-
We also check that the assembly has been moved into the correct z position
|
|
127
|
-
previously. If we try and move whilst in the incorrect Z position we will collide
|
|
128
|
-
with the table.
|
|
129
|
-
"""
|
|
130
|
-
ap_z_in_position = await aperture.z.motor_done_move.get_value()
|
|
131
|
-
if not ap_z_in_position:
|
|
132
|
-
raise InvalidApertureMove(
|
|
133
|
-
"ApertureScatterguard z is still moving. Wait for it to finish "
|
|
134
|
-
"before triggering another move."
|
|
135
|
-
)
|
|
136
|
-
|
|
137
|
-
current_ap_z = await aperture.z.user_readback.get_value()
|
|
138
|
-
diff_on_z = abs(current_ap_z - position.aperture_z)
|
|
139
|
-
if diff_on_z > aperture_z_tolerance:
|
|
140
|
-
raise InvalidApertureMove(
|
|
141
|
-
f"Current aperture z ({current_ap_z}), outside of tolerance ({aperture_z_tolerance}) from target ({position.aperture_z})."
|
|
142
|
-
)
|
|
143
|
-
|
|
144
|
-
current_ap_y = await aperture.y.user_readback.get_value()
|
|
145
|
-
|
|
146
|
-
aperture_x, aperture_y, aperture_z, scatterguard_x, scatterguard_y = position.values
|
|
147
|
-
|
|
148
|
-
if aperture_y > current_ap_y:
|
|
149
|
-
# Assembly needs to move up so move the scatterguard down first
|
|
150
|
-
await asyncio.gather(
|
|
151
|
-
scatterguard.x.set(scatterguard_x),
|
|
152
|
-
scatterguard.y.set(scatterguard_y),
|
|
153
|
-
)
|
|
154
|
-
await asyncio.gather(
|
|
155
|
-
aperture.x.set(aperture_x),
|
|
156
|
-
aperture.y.set(aperture_y),
|
|
157
|
-
aperture.z.set(aperture_z),
|
|
158
|
-
)
|
|
159
|
-
else:
|
|
160
|
-
await asyncio.gather(
|
|
161
|
-
aperture.x.set(aperture_x),
|
|
162
|
-
aperture.y.set(aperture_y),
|
|
163
|
-
aperture.z.set(aperture_z),
|
|
164
|
-
)
|
|
165
|
-
|
|
166
|
-
await asyncio.gather(
|
|
167
|
-
scatterguard.x.set(scatterguard_x),
|
|
168
|
-
scatterguard.y.set(scatterguard_y),
|
|
169
|
-
)
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
class ApertureSelector(StandardReadable, Movable):
|
|
173
|
-
"""Allows for moving all axes other than Y into the correct position, this means
|
|
174
|
-
that we can set up the aperture while it is out of the beam then move it in later."""
|
|
175
|
-
|
|
176
|
-
def __init__(
|
|
177
|
-
self,
|
|
178
|
-
aperture: Aperture,
|
|
179
|
-
scatterguard: Scatterguard,
|
|
180
|
-
out_of_beam: Callable[[], Coroutine[Any, Any, bool]],
|
|
181
|
-
loaded_positions: dict[ApertureValue, AperturePosition],
|
|
182
|
-
aperture_z_tolerance: float,
|
|
183
|
-
):
|
|
184
|
-
self.aperture = Reference(aperture)
|
|
185
|
-
self.scatterguard = Reference(scatterguard)
|
|
186
|
-
self.loaded_positions = loaded_positions
|
|
187
|
-
self.get_is_out_of_beam = out_of_beam
|
|
188
|
-
self.aperture_z_tolerance = aperture_z_tolerance
|
|
189
|
-
super().__init__()
|
|
190
|
-
|
|
191
|
-
@AsyncStatus.wrap
|
|
192
|
-
async def set(self, value: ApertureValue):
|
|
193
|
-
"""Moves the assembly to the position for the specified aperture, whilst keeping
|
|
194
|
-
it out of the beam if it already is so.
|
|
195
|
-
|
|
196
|
-
Moving the assembly whilst out of the beam has no collision risk so we can just
|
|
197
|
-
move all the motors together.
|
|
198
|
-
"""
|
|
199
|
-
if await self.get_is_out_of_beam():
|
|
200
|
-
aperture_x, _, aperture_z, scatterguard_x, scatterguard_y = (
|
|
201
|
-
self.loaded_positions[value].values
|
|
202
|
-
)
|
|
203
|
-
|
|
204
|
-
await asyncio.gather(
|
|
205
|
-
self.aperture().x.set(aperture_x),
|
|
206
|
-
self.aperture().z.set(aperture_z),
|
|
207
|
-
self.scatterguard().x.set(scatterguard_x),
|
|
208
|
-
self.scatterguard().y.set(scatterguard_y),
|
|
209
|
-
)
|
|
210
|
-
else:
|
|
211
|
-
await _safe_move_whilst_in_beam(
|
|
212
|
-
self.aperture(),
|
|
213
|
-
self.scatterguard(),
|
|
214
|
-
self.loaded_positions[value],
|
|
215
|
-
self.aperture_z_tolerance,
|
|
216
|
-
)
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
class OutTrigger(StandardReadable, Triggerable):
|
|
220
|
-
"""Allows for moving just the Y stage of the assembly out of the beam."""
|
|
221
|
-
|
|
222
|
-
def __init__(
|
|
223
|
-
self,
|
|
224
|
-
aperture_y: Motor,
|
|
225
|
-
out_y: float,
|
|
226
|
-
):
|
|
227
|
-
self.aperture_y = Reference(aperture_y)
|
|
228
|
-
self.out_y = out_y
|
|
229
|
-
super().__init__()
|
|
230
|
-
|
|
231
|
-
@AsyncStatus.wrap
|
|
232
|
-
async def trigger(self):
|
|
233
|
-
"""Moves the assembly out of the beam."""
|
|
234
|
-
await self.aperture_y().set(self.out_y)
|
|
235
|
-
|
|
236
|
-
|
|
237
110
|
class ApertureScatterguard(StandardReadable, Movable):
|
|
238
|
-
"""Move the aperture and scatterguard assembly in a safe way. There are two ways to
|
|
239
|
-
interact with the device depending on if you want simplicity or move flexibility.
|
|
240
|
-
|
|
241
|
-
The simple interface is using:
|
|
242
|
-
|
|
243
|
-
await aperture_scatterguard.set(ApertureValue.LARGE)
|
|
244
|
-
|
|
245
|
-
This will move the assembly so that the large aperture is in the beam, regardless
|
|
246
|
-
of where the assembly currently is.
|
|
247
|
-
|
|
248
|
-
However, the aperture Y axis is faster than the others. In some cases we may want to
|
|
249
|
-
move the assembly out of the beam with this axis without moving others:
|
|
250
|
-
|
|
251
|
-
await aperture_scatterguard.move_out.trigger()
|
|
252
|
-
|
|
253
|
-
We may then want to keep the assembly out of the beam whilst asynchronously preparing
|
|
254
|
-
the other axes for the aperture that's to follow:
|
|
255
|
-
|
|
256
|
-
await aperture_scatterguard.aperture_outside_beam.set(ApertureValue.LARGE)
|
|
257
|
-
|
|
258
|
-
Then, at a later time, move back into the beam:
|
|
259
|
-
|
|
260
|
-
await aperture_scatterguard.set(ApertureValue.LARGE)
|
|
261
|
-
|
|
262
|
-
This move will now be faster as only the y is left to move.
|
|
263
|
-
"""
|
|
264
|
-
|
|
265
111
|
def __init__(
|
|
266
112
|
self,
|
|
267
113
|
loaded_positions: dict[ApertureValue, AperturePosition],
|
|
@@ -269,8 +115,8 @@ class ApertureScatterguard(StandardReadable, Movable):
|
|
|
269
115
|
prefix: str = "",
|
|
270
116
|
name: str = "",
|
|
271
117
|
) -> None:
|
|
272
|
-
self.
|
|
273
|
-
self.
|
|
118
|
+
self.aperture = Aperture(prefix + "-MO-MAPT-01:")
|
|
119
|
+
self.scatterguard = Scatterguard(prefix + "-MO-SCAT-01:")
|
|
274
120
|
self.radius = create_hardware_backed_soft_signal(
|
|
275
121
|
float, self._get_current_radius, units="µm"
|
|
276
122
|
)
|
|
@@ -278,43 +124,30 @@ class ApertureScatterguard(StandardReadable, Movable):
|
|
|
278
124
|
self._tolerances = tolerances
|
|
279
125
|
self.add_readables(
|
|
280
126
|
[
|
|
281
|
-
self.
|
|
282
|
-
self.
|
|
283
|
-
self.
|
|
284
|
-
self.
|
|
285
|
-
self.
|
|
127
|
+
self.aperture.x.user_readback,
|
|
128
|
+
self.aperture.y.user_readback,
|
|
129
|
+
self.aperture.z.user_readback,
|
|
130
|
+
self.scatterguard.x.user_readback,
|
|
131
|
+
self.scatterguard.y.user_readback,
|
|
286
132
|
self.radius,
|
|
287
133
|
],
|
|
288
134
|
)
|
|
289
|
-
|
|
290
135
|
with self.add_children_as_readables(StandardReadableFormat.HINTED_SIGNAL):
|
|
291
136
|
self.selected_aperture = create_hardware_backed_soft_signal(
|
|
292
137
|
ApertureValue, self._get_current_aperture_position
|
|
293
138
|
)
|
|
294
139
|
|
|
295
|
-
# Setting this will select the aperture but not move it into beam
|
|
296
|
-
self.aperture_outside_beam = ApertureSelector(
|
|
297
|
-
self._aperture,
|
|
298
|
-
self._scatterguard,
|
|
299
|
-
self._is_out_of_beam,
|
|
300
|
-
self._loaded_positions,
|
|
301
|
-
self._tolerances.aperture_z,
|
|
302
|
-
)
|
|
303
|
-
|
|
304
|
-
# Setting this will just move the assembly out of the beam
|
|
305
|
-
self.move_out = OutTrigger(
|
|
306
|
-
self._aperture.y, loaded_positions[ApertureValue.ROBOT_LOAD].aperture_y
|
|
307
|
-
)
|
|
308
|
-
|
|
309
140
|
super().__init__(name)
|
|
310
141
|
|
|
142
|
+
def get_position_from_gda_aperture_name(
|
|
143
|
+
self, gda_aperture_name: str
|
|
144
|
+
) -> ApertureValue:
|
|
145
|
+
return ApertureValue(gda_aperture_name)
|
|
146
|
+
|
|
311
147
|
@AsyncStatus.wrap
|
|
312
148
|
async def set(self, value: ApertureValue):
|
|
313
|
-
"""This set will move the aperture into the beam or move to robot load"""
|
|
314
149
|
position = self._loaded_positions[value]
|
|
315
|
-
await
|
|
316
|
-
self._aperture, self._scatterguard, position, self._tolerances.aperture_z
|
|
317
|
-
)
|
|
150
|
+
await self._safe_move_within_datacollection_range(position, value)
|
|
318
151
|
|
|
319
152
|
@AsyncStatus.wrap
|
|
320
153
|
async def _set_raw_unsafe(self, position: AperturePosition):
|
|
@@ -324,18 +157,13 @@ class ApertureScatterguard(StandardReadable, Movable):
|
|
|
324
157
|
)
|
|
325
158
|
|
|
326
159
|
await asyncio.gather(
|
|
327
|
-
self.
|
|
328
|
-
self.
|
|
329
|
-
self.
|
|
330
|
-
self.
|
|
331
|
-
self.
|
|
160
|
+
self.aperture.x.set(aperture_x),
|
|
161
|
+
self.aperture.y.set(aperture_y),
|
|
162
|
+
self.aperture.z.set(aperture_z),
|
|
163
|
+
self.scatterguard.x.set(scatterguard_x),
|
|
164
|
+
self.scatterguard.y.set(scatterguard_y),
|
|
332
165
|
)
|
|
333
166
|
|
|
334
|
-
async def _is_out_of_beam(self) -> bool:
|
|
335
|
-
current_ap_y = await self._aperture.y.user_readback.get_value()
|
|
336
|
-
robot_load_ap_y = self._loaded_positions[ApertureValue.ROBOT_LOAD].aperture_y
|
|
337
|
-
return current_ap_y <= robot_load_ap_y + self._tolerances.aperture_y
|
|
338
|
-
|
|
339
167
|
async def _get_current_aperture_position(self) -> ApertureValue:
|
|
340
168
|
"""
|
|
341
169
|
Returns the current aperture position using readback values
|
|
@@ -343,13 +171,15 @@ class ApertureScatterguard(StandardReadable, Movable):
|
|
|
343
171
|
mini aperture y <= ROBOT_LOAD.location.aperture_y + tolerance.
|
|
344
172
|
If no position is found then raises InvalidApertureMove.
|
|
345
173
|
"""
|
|
346
|
-
|
|
174
|
+
current_ap_y = await self.aperture.y.user_readback.get_value(cached=False)
|
|
175
|
+
robot_load_ap_y = self._loaded_positions[ApertureValue.ROBOT_LOAD].aperture_y
|
|
176
|
+
if await self.aperture.large.get_value(cached=False) == 1:
|
|
347
177
|
return ApertureValue.LARGE
|
|
348
|
-
elif await self.
|
|
178
|
+
elif await self.aperture.medium.get_value(cached=False) == 1:
|
|
349
179
|
return ApertureValue.MEDIUM
|
|
350
|
-
elif await self.
|
|
180
|
+
elif await self.aperture.small.get_value(cached=False) == 1:
|
|
351
181
|
return ApertureValue.SMALL
|
|
352
|
-
elif
|
|
182
|
+
elif current_ap_y <= robot_load_ap_y + self._tolerances.aperture_y:
|
|
353
183
|
return ApertureValue.ROBOT_LOAD
|
|
354
184
|
|
|
355
185
|
raise InvalidApertureMove("Current aperture/scatterguard state unrecognised")
|
|
@@ -357,3 +187,57 @@ class ApertureScatterguard(StandardReadable, Movable):
|
|
|
357
187
|
async def _get_current_radius(self) -> float:
|
|
358
188
|
current_value = await self._get_current_aperture_position()
|
|
359
189
|
return self._loaded_positions[current_value].radius
|
|
190
|
+
|
|
191
|
+
async def _safe_move_within_datacollection_range(
|
|
192
|
+
self, position: AperturePosition, value: ApertureValue
|
|
193
|
+
):
|
|
194
|
+
"""
|
|
195
|
+
Move the aperture and scatterguard combo safely to a new position.
|
|
196
|
+
See https://github.com/DiamondLightSource/hyperion/wiki/Aperture-Scatterguard-Collisions
|
|
197
|
+
for why this is required.
|
|
198
|
+
"""
|
|
199
|
+
assert self._loaded_positions is not None
|
|
200
|
+
|
|
201
|
+
ap_z_in_position = await self.aperture.z.motor_done_move.get_value()
|
|
202
|
+
if not ap_z_in_position:
|
|
203
|
+
raise InvalidApertureMove(
|
|
204
|
+
"ApertureScatterguard z is still moving. Wait for it to finish "
|
|
205
|
+
"before triggering another move."
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
current_ap_z = await self.aperture.z.user_readback.get_value()
|
|
209
|
+
diff_on_z = abs(current_ap_z - position.aperture_z)
|
|
210
|
+
if diff_on_z > self._tolerances.aperture_z:
|
|
211
|
+
raise InvalidApertureMove(
|
|
212
|
+
"ApertureScatterguard safe move is not yet defined for positions "
|
|
213
|
+
"outside of LARGE, MEDIUM, SMALL, ROBOT_LOAD. "
|
|
214
|
+
f"Current aperture z ({current_ap_z}), outside of tolerance ({self._tolerances.aperture_z}) from target ({position.aperture_z})."
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
current_ap_y = await self.aperture.y.user_readback.get_value()
|
|
218
|
+
|
|
219
|
+
aperture_x, aperture_y, aperture_z, scatterguard_x, scatterguard_y = (
|
|
220
|
+
position.values
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
if position.aperture_y > current_ap_y:
|
|
224
|
+
await asyncio.gather(
|
|
225
|
+
self.scatterguard.x.set(scatterguard_x),
|
|
226
|
+
self.scatterguard.y.set(scatterguard_y),
|
|
227
|
+
)
|
|
228
|
+
await asyncio.gather(
|
|
229
|
+
self.aperture.x.set(aperture_x),
|
|
230
|
+
self.aperture.y.set(aperture_y),
|
|
231
|
+
self.aperture.z.set(aperture_z),
|
|
232
|
+
)
|
|
233
|
+
else:
|
|
234
|
+
await asyncio.gather(
|
|
235
|
+
self.aperture.x.set(aperture_x),
|
|
236
|
+
self.aperture.y.set(aperture_y),
|
|
237
|
+
self.aperture.z.set(aperture_z),
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
await asyncio.gather(
|
|
241
|
+
self.scatterguard.x.set(scatterguard_x),
|
|
242
|
+
self.scatterguard.y.set(scatterguard_y),
|
|
243
|
+
)
|
dodal/devices/attenuator.py
CHANGED
|
@@ -14,7 +14,20 @@ from ophyd_async.epics.core import epics_signal_r, epics_signal_rw, epics_signal
|
|
|
14
14
|
from dodal.log import LOGGER
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
class
|
|
17
|
+
class ReadOnlyAttenuator(StandardReadable):
|
|
18
|
+
"""A read-only attenuator class with a minimum set of PVs for reading.
|
|
19
|
+
|
|
20
|
+
The actual_transmission will return a fractional transmission between 0-1.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, prefix: str, name: str = "") -> None:
|
|
24
|
+
with self.add_children_as_readables():
|
|
25
|
+
self.actual_transmission = epics_signal_r(float, prefix + "MATCH")
|
|
26
|
+
|
|
27
|
+
super().__init__(name)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Attenuator(ReadOnlyAttenuator, Movable):
|
|
18
31
|
"""The attenuator will insert filters into the beam to reduce its transmission.
|
|
19
32
|
|
|
20
33
|
This device should be set with:
|
|
@@ -42,10 +55,7 @@ class Attenuator(StandardReadable, Movable):
|
|
|
42
55
|
self._use_current_energy = epics_signal_x(prefix + "E2WL:USECURRENTENERGY.PROC")
|
|
43
56
|
self._change = epics_signal_x(prefix + "FANOUT")
|
|
44
57
|
|
|
45
|
-
|
|
46
|
-
self.actual_transmission = epics_signal_r(float, prefix + "MATCH")
|
|
47
|
-
|
|
48
|
-
super().__init__(name)
|
|
58
|
+
super().__init__(prefix, name)
|
|
49
59
|
|
|
50
60
|
@AsyncStatus.wrap
|
|
51
61
|
async def set(self, value: float):
|
dodal/devices/focusing_mirror.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from typing import TypedDict
|
|
2
|
+
|
|
1
3
|
from ophyd_async.core import (
|
|
2
4
|
AsyncStatus,
|
|
3
5
|
Device,
|
|
@@ -36,6 +38,12 @@ class MirrorStripe(StrictEnum):
|
|
|
36
38
|
PLATINUM = "Platinum"
|
|
37
39
|
|
|
38
40
|
|
|
41
|
+
class MirrorStripeConfiguration(TypedDict):
|
|
42
|
+
stripe: MirrorStripe
|
|
43
|
+
yaw_mrad: float
|
|
44
|
+
lat_mm: float
|
|
45
|
+
|
|
46
|
+
|
|
39
47
|
class MirrorVoltageDemand(StrictEnum):
|
|
40
48
|
N_A = "N/A"
|
|
41
49
|
OK = "OK"
|
|
@@ -173,9 +181,10 @@ class FocusingMirrorWithStripes(FocusingMirror):
|
|
|
173
181
|
|
|
174
182
|
super().__init__(prefix, name, *args, **kwargs)
|
|
175
183
|
|
|
176
|
-
def energy_to_stripe(self, energy_kev) ->
|
|
184
|
+
def energy_to_stripe(self, energy_kev) -> MirrorStripeConfiguration:
|
|
185
|
+
"""Return the stripe, yaw angle and lateral position for the specified energy"""
|
|
177
186
|
# In future, this should be configurable per-mirror
|
|
178
187
|
if energy_kev < 7:
|
|
179
|
-
return MirrorStripe.BARE
|
|
188
|
+
return {"stripe": MirrorStripe.BARE, "yaw_mrad": 6.2, "lat_mm": 0.0}
|
|
180
189
|
else:
|
|
181
|
-
return MirrorStripe.RHODIUM
|
|
190
|
+
return {"stripe": MirrorStripe.RHODIUM, "yaw_mrad": 0.0, "lat_mm": 10.0}
|
|
@@ -61,7 +61,7 @@ class PinTipDetection(StandardReadable):
|
|
|
61
61
|
self.triggered_bottom_edge, self._bottom_edge_setter = soft_signal_r_and_setter(
|
|
62
62
|
Array1D[np.int32], name="triggered_bottom_edge"
|
|
63
63
|
)
|
|
64
|
-
self.array_data = epics_signal_r(
|
|
64
|
+
self.array_data = epics_signal_r(np.ndarray, f"pva://{prefix}PVA:ARRAY")
|
|
65
65
|
|
|
66
66
|
# Soft parameters for pin-tip detection.
|
|
67
67
|
self.preprocess_operation = soft_signal_rw(int, 10, name="preprocess")
|
|
@@ -99,9 +99,7 @@ class PinTipDetection(StandardReadable):
|
|
|
99
99
|
self._top_edge_setter(results.edge_top)
|
|
100
100
|
self._bottom_edge_setter(results.edge_bottom)
|
|
101
101
|
|
|
102
|
-
async def _get_tip_and_edge_data(
|
|
103
|
-
self, array_data: Array1D[np.uint8]
|
|
104
|
-
) -> SampleLocation:
|
|
102
|
+
async def _get_tip_and_edge_data(self, array_data: np.ndarray) -> SampleLocation:
|
|
105
103
|
"""
|
|
106
104
|
Gets the location of the pin tip and the top and bottom edges.
|
|
107
105
|
"""
|
dodal/devices/undulator_dcm.py
CHANGED
|
@@ -5,6 +5,7 @@ from ophyd_async.core import AsyncStatus, StandardReadable
|
|
|
5
5
|
|
|
6
6
|
from dodal.common.beamlines.beamline_parameters import get_beamline_parameters
|
|
7
7
|
|
|
8
|
+
from ..log import LOGGER
|
|
8
9
|
from .dcm import DCM
|
|
9
10
|
from .undulator import Undulator
|
|
10
11
|
|
|
@@ -59,3 +60,6 @@ class UndulatorDCM(StandardReadable, Movable):
|
|
|
59
60
|
self.dcm.energy_in_kev.set(value, timeout=ENERGY_TIMEOUT_S),
|
|
60
61
|
self.undulator.set(value),
|
|
61
62
|
)
|
|
63
|
+
# DCM Perp pitch
|
|
64
|
+
LOGGER.info(f"Adjusting DCM offset to {self.dcm_fixed_offset_mm} mm")
|
|
65
|
+
await self.dcm.offset_in_mm.set(self.dcm_fixed_offset_mm)
|
dodal/adsim.py
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
|
|
3
|
-
from dodal.devices.adsim import SimStage
|
|
4
|
-
from dodal.devices.areadetector import AdSimDetector
|
|
5
|
-
|
|
6
|
-
from .utils import get_hostname
|
|
7
|
-
|
|
8
|
-
# Default prefix to hostname unless overriden with export PREFIX=<prefix>
|
|
9
|
-
PREFIX: str = os.environ.get("PREFIX", get_hostname())
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def stage(name: str = "sim_motors") -> SimStage:
|
|
13
|
-
return SimStage(name=name, prefix=f"{PREFIX}-MO-SIM-01:")
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def det(name: str = "adsim") -> AdSimDetector:
|
|
17
|
-
return AdSimDetector(name=name, prefix=f"{PREFIX}-AD-SIM-01:")
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
from collections.abc import Mapping
|
|
2
|
-
from typing import Any
|
|
3
|
-
|
|
4
|
-
from ophyd import EpicsSignal, Signal
|
|
5
|
-
from ophyd.areadetector.base import ADComponent as Cpt
|
|
6
|
-
from ophyd.areadetector.detectors import DetectorBase
|
|
7
|
-
|
|
8
|
-
from .adutils import Hdf5Writer, SingleTriggerV33, SynchronisedAdDriverBase
|
|
9
|
-
|
|
10
|
-
_ACQUIRE_BUFFER_PERIOD = 0.2
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class AdAravisDetector(SingleTriggerV33, DetectorBase):
|
|
14
|
-
cam = Cpt(SynchronisedAdDriverBase, suffix="DET:")
|
|
15
|
-
hdf = Cpt(
|
|
16
|
-
Hdf5Writer,
|
|
17
|
-
suffix="HDF5:",
|
|
18
|
-
root="",
|
|
19
|
-
write_path_template="",
|
|
20
|
-
)
|
|
21
|
-
_priming_settings: Mapping[Signal, Any]
|
|
22
|
-
|
|
23
|
-
def __init__(self, *args, **kwargs) -> None:
|
|
24
|
-
super().__init__(*args, **kwargs)
|
|
25
|
-
self.hdf.kind = "normal"
|
|
26
|
-
|
|
27
|
-
# Values for staging
|
|
28
|
-
self.stage_sigs = {
|
|
29
|
-
# Get stage to wire up the plugins
|
|
30
|
-
self.hdf.nd_array_port: self.cam.port_name.get(),
|
|
31
|
-
# Reset array counter on stage
|
|
32
|
-
self.cam.array_counter: 0,
|
|
33
|
-
# Set image mode to multiple on stage so we have the option, can still
|
|
34
|
-
# set num_images to 1
|
|
35
|
-
self.cam.image_mode: "Multiple",
|
|
36
|
-
# For now, this Ophyd device does not support hardware
|
|
37
|
-
# triggered scanning, disable on stage
|
|
38
|
-
self.cam.trigger_mode: "Off",
|
|
39
|
-
**self.stage_sigs, # type: ignore
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
# Settings to apply when priming plugins during pre-stage
|
|
43
|
-
self._priming_settings = {
|
|
44
|
-
self.hdf.enable: 1,
|
|
45
|
-
self.hdf.nd_array_port: self.cam.port_name.get(),
|
|
46
|
-
self.cam.array_callbacks: 1,
|
|
47
|
-
self.cam.image_mode: "Single",
|
|
48
|
-
self.cam.trigger_mode: "Off",
|
|
49
|
-
# Take the quickest possible frame
|
|
50
|
-
self.cam.acquire_time: 6.3e-05,
|
|
51
|
-
self.cam.acquire_period: 0.003,
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
# Signals that control driver and hdf writer should be put_complete to
|
|
55
|
-
# avoid race conditions during priming
|
|
56
|
-
for signal in set(self.stage_sigs.keys()).union(
|
|
57
|
-
set(self._priming_settings.keys())
|
|
58
|
-
):
|
|
59
|
-
if isinstance(signal, EpicsSignal):
|
|
60
|
-
signal.put_complete = True
|
|
61
|
-
self.cam.acquire.put_complete = True
|
|
62
|
-
|
|
63
|
-
def stage(self, *args, **kwargs) -> list[object]:
|
|
64
|
-
# We have to manually set the acquire period bcause the EPICS driver
|
|
65
|
-
# doesn't do it for us. If acquire time is a staged signal, we use the
|
|
66
|
-
# stage value to calculate the acquire period, otherwise we perform
|
|
67
|
-
# a caget and use the current acquire time.
|
|
68
|
-
if self.cam.acquire_time in self.stage_sigs:
|
|
69
|
-
acquire_time = self.stage_sigs[self.cam.acquire_time]
|
|
70
|
-
else:
|
|
71
|
-
acquire_time = self.cam.acquire_time.get()
|
|
72
|
-
self.stage_sigs[self.cam.acquire_period] = (
|
|
73
|
-
float(acquire_time) + _ACQUIRE_BUFFER_PERIOD
|
|
74
|
-
)
|
|
75
|
-
|
|
76
|
-
# Ensure detector warmed up
|
|
77
|
-
self._prime_hdf()
|
|
78
|
-
|
|
79
|
-
# Now calling the super method should set the acquire period
|
|
80
|
-
return super().stage(*args, **kwargs)
|
|
81
|
-
|
|
82
|
-
def _prime_hdf(self) -> None:
|
|
83
|
-
"""
|
|
84
|
-
Take a single frame and pipe it through the HDF5 writer plugin
|
|
85
|
-
"""
|
|
86
|
-
|
|
87
|
-
# Backup state and ensure we are not acquiring
|
|
88
|
-
reset_to = {signal: signal.get() for signal in self._priming_settings.keys()}
|
|
89
|
-
self.cam.acquire.set(0).wait(timeout=10)
|
|
90
|
-
|
|
91
|
-
# Apply all settings for acquisition
|
|
92
|
-
for signal, value in self._priming_settings.items():
|
|
93
|
-
# Ensure that .wait really will wait until the PV is set including its RBV
|
|
94
|
-
signal.set(value).wait(timeout=10)
|
|
95
|
-
|
|
96
|
-
# Acquire a frame
|
|
97
|
-
self.cam.acquire.set(1).wait(timeout=10)
|
|
98
|
-
|
|
99
|
-
# Revert settings to previous values
|
|
100
|
-
for signal, value in reversed(reset_to.items()):
|
|
101
|
-
signal.set(value).wait(timeout=10)
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
from ophyd.areadetector.base import ADComponent as Cpt
|
|
2
|
-
from ophyd.areadetector.detectors import DetectorBase
|
|
3
|
-
|
|
4
|
-
from .adutils import Hdf5Writer, SingleTriggerV33, SynchronisedAdDriverBase
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class AdSimDetector(SingleTriggerV33, DetectorBase):
|
|
8
|
-
cam = Cpt(SynchronisedAdDriverBase, suffix="CAM:", lazy=True)
|
|
9
|
-
hdf = Cpt(
|
|
10
|
-
Hdf5Writer,
|
|
11
|
-
suffix="HDF5:",
|
|
12
|
-
root="",
|
|
13
|
-
write_path_template="",
|
|
14
|
-
lazy=True,
|
|
15
|
-
)
|
|
16
|
-
|
|
17
|
-
def __init__(self, *args, **kwargs) -> None:
|
|
18
|
-
super().__init__(*args, **kwargs)
|
|
19
|
-
self.hdf.kind = "normal"
|
|
20
|
-
|
|
21
|
-
self.stage_sigs = {
|
|
22
|
-
# Get stage to wire up the plugins
|
|
23
|
-
self.hdf.nd_array_port: self.cam.port_name.get(),
|
|
24
|
-
# Reset array counter on stage
|
|
25
|
-
self.cam.array_counter: 0,
|
|
26
|
-
# Set image mode to multiple on stage so we have the option, can still
|
|
27
|
-
# set num_images to 1
|
|
28
|
-
self.cam.image_mode: "Multiple",
|
|
29
|
-
# For now, this Ophyd device does not support hardware
|
|
30
|
-
# triggered scanning, disable on stage
|
|
31
|
-
self.cam.trigger_mode: "Internal",
|
|
32
|
-
**self.stage_sigs, # type: ignore
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
def stage(self, *args, **kwargs) -> list[object]:
|
|
36
|
-
# We have to manually set the acquire period bcause the EPICS driver
|
|
37
|
-
# doesn't do it for us. If acquire time is a staged signal, we use the
|
|
38
|
-
# stage value to calculate the acquire period, otherwise we perform
|
|
39
|
-
# a caget and use the current acquire time.
|
|
40
|
-
if self.cam.acquire_time in self.stage_sigs:
|
|
41
|
-
acquire_time = self.stage_sigs[self.cam.acquire_time]
|
|
42
|
-
else:
|
|
43
|
-
acquire_time = self.cam.acquire_time.get()
|
|
44
|
-
self.stage_sigs[self.cam.acquire_period] = acquire_time
|
|
45
|
-
|
|
46
|
-
# Now calling the super method should set the acquire period
|
|
47
|
-
return super().stage(*args, **kwargs)
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import time as ttime
|
|
2
|
-
|
|
3
|
-
from ophyd import Component as Cpt
|
|
4
|
-
from ophyd import DetectorBase, Device, EpicsSignal, EpicsSignalRO, Staged
|
|
5
|
-
from ophyd.areadetector import ADTriggerStatus, TriggerBase
|
|
6
|
-
from ophyd.areadetector.cam import AreaDetectorCam
|
|
7
|
-
from ophyd.areadetector.filestore_mixins import FileStoreHDF5, FileStoreIterativeWrite
|
|
8
|
-
from ophyd.areadetector.plugins import HDF5Plugin
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class SingleTriggerV33(TriggerBase):
|
|
12
|
-
_status_type = ADTriggerStatus
|
|
13
|
-
|
|
14
|
-
def __init__(self, *args, image_name=None, **kwargs):
|
|
15
|
-
super().__init__(*args, **kwargs)
|
|
16
|
-
if image_name is None:
|
|
17
|
-
# Ensure that this mixin is part of valid device with name
|
|
18
|
-
assert isinstance(self, Device)
|
|
19
|
-
image_name = "_".join([self.name, "image"])
|
|
20
|
-
self._image_name = image_name
|
|
21
|
-
|
|
22
|
-
def trigger(self):
|
|
23
|
-
"Trigger one acquisition."
|
|
24
|
-
if self._staged != Staged.yes:
|
|
25
|
-
raise RuntimeError(
|
|
26
|
-
"This detector is not ready to trigger."
|
|
27
|
-
"Call the stage() method before triggering."
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
self._status = self._status_type(self)
|
|
31
|
-
|
|
32
|
-
def _acq_done(*args, **kwargs):
|
|
33
|
-
# TODO sort out if anything useful in here
|
|
34
|
-
self._status._finished() # noqa: SLF001
|
|
35
|
-
|
|
36
|
-
self._acquisition_signal.put(1, use_complete=True, callback=_acq_done)
|
|
37
|
-
# Ensure that this mixin is part of valid Detector with generate_datum
|
|
38
|
-
assert isinstance(self, DetectorBase)
|
|
39
|
-
self.generate_datum(self._image_name, ttime.time())
|
|
40
|
-
return self._status
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
class SynchronisedAdDriverBase(AreaDetectorCam):
|
|
44
|
-
"""
|
|
45
|
-
Base Ophyd device to control an AreaDetector driver and
|
|
46
|
-
syncrhonise it on other AreaDetector plugins, even non-blocking ones.
|
|
47
|
-
"""
|
|
48
|
-
|
|
49
|
-
adcore_version = Cpt(EpicsSignalRO, "ADCoreVersion_RBV", string=True, kind="config")
|
|
50
|
-
driver_version = Cpt(EpicsSignalRO, "DriverVersion_RBV", string=True, kind="config")
|
|
51
|
-
wait_for_plugins = Cpt(EpicsSignal, "WaitForPlugins", string=True, kind="config")
|
|
52
|
-
|
|
53
|
-
def stage(self, *args, **kwargs):
|
|
54
|
-
# Makes the detector allow non-blocking AD plugins but makes Ophyd use
|
|
55
|
-
# the AcquireBusy PV to determine when an acquisition is complete
|
|
56
|
-
self.ensure_nonblocking()
|
|
57
|
-
return super().stage(*args, **kwargs)
|
|
58
|
-
|
|
59
|
-
def ensure_nonblocking(self):
|
|
60
|
-
self.stage_sigs["wait_for_plugins"] = "Yes"
|
|
61
|
-
if self.parent is not None:
|
|
62
|
-
for c in self.parent.component_names:
|
|
63
|
-
cpt = getattr(self.parent, c)
|
|
64
|
-
if cpt is self:
|
|
65
|
-
continue
|
|
66
|
-
if hasattr(cpt, "ensure_nonblocking"):
|
|
67
|
-
cpt.ensure_nonblocking()
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
# ophyd code to be removed, only used for adim
|
|
71
|
-
# https://github.com/DiamondLightSource/dodal/issues/404
|
|
72
|
-
class Hdf5Writer(HDF5Plugin, FileStoreHDF5, FileStoreIterativeWrite): # type: ignore
|
|
73
|
-
""" """
|
|
74
|
-
|
|
75
|
-
pool_max_buffers = None
|
|
76
|
-
file_number_sync = None
|
|
77
|
-
file_number_write = None
|
|
78
|
-
|
|
79
|
-
def get_frames_per_point(self):
|
|
80
|
-
assert isinstance(self.parent, DetectorBase)
|
|
81
|
-
return self.parent.cam.num_images.get()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|