dls-dodal 1.27.0__py3-none-any.whl → 1.28.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dls-dodal
3
- Version: 1.27.0
3
+ Version: 1.28.0
4
4
  Summary: Ophyd devices and other utils that could be used across DLS beamlines
5
5
  Author-email: Dominic Oram <dominic.oram@diamond.ac.uk>
6
6
  License: Apache License
@@ -1,6 +1,6 @@
1
1
  dodal/__init__.py,sha256=y-VRpfiX-Lm5nchB9N0VfMy_6dwFqVxpSn5SiAQql9I,114
2
2
  dodal/__main__.py,sha256=kP2S2RPitnOWpNGokjZ1Yq-1umOtp5sNOZk2B3tBPLM,111
3
- dodal/_version.py,sha256=XlqTK6aXcDzaFgsrMUHCuU_TY8zt9cjwfCWklVgy2Hg,413
3
+ dodal/_version.py,sha256=RW6USZoeFR_9ttdXBH9tb0lK1erQEYot3NzmBpWXDMs,413
4
4
  dodal/adsim.py,sha256=OW2dcS7ciD4Yq9WFw4PN_c5Bwccrmu7R-zr-u6ZCbQM,497
5
5
  dodal/cli.py,sha256=z0UBESrNrq6Kq4rttp4uHcwS1fnOnRkKBRDHSriPpGY,2058
6
6
  dodal/log.py,sha256=grK5-c-V6UjMERwDqYPdKbc_BpycrNb8hP0uteLOVCY,8296
@@ -9,14 +9,14 @@ dodal/beamline_specific_utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5N
9
9
  dodal/beamline_specific_utils/i03.py,sha256=Ixe1anFQl-kwRJubmQx28TIW4Zw8qDxpElNNNapWQHI,396
10
10
  dodal/beamlines/README.md,sha256=K9MkL_GomxlsoTB7Mz-_dJA5NNSbmCfMiutchGg3C8o,404
11
11
  dodal/beamlines/__init__.py,sha256=U0dQYFEUloCdQOs24zyfpPTncJXOO4cDcfHSevVOAw4,2890
12
- dodal/beamlines/i03.py,sha256=nOWm9iUYOwqUDAkBXC4UkYmN2GGD1MbTgyCCMO_YXBw,16394
12
+ dodal/beamlines/i03.py,sha256=LcE9Vir0cCjt9pxO2wcevlM0NTP6NBCxp6DNtShPV0E,16914
13
13
  dodal/beamlines/i04.py,sha256=JOyNcUnC3wva4no2MHKp6b8gOKAcQXL_c4cBo7oneVs,13034
14
14
  dodal/beamlines/i04_1.py,sha256=KDxSUQNhIs_NFiRaLY-Jiory0DeN7Y0ErvGuoTrwCDU,4731
15
15
  dodal/beamlines/i20_1.py,sha256=XJpey1-XZnCVpjK6-tODRYeBofqV44BL8lK5OSdQgiI,746
16
- dodal/beamlines/i22.py,sha256=TlXgAuRYM4JS733tyZyLMYDyC7ajwg85_XzYI5qh46U,7574
16
+ dodal/beamlines/i22.py,sha256=Lk5S5AHu9hCnqlJJHZj1fhSDm1sTOgEVMe1S20nVqeY,8786
17
17
  dodal/beamlines/i23.py,sha256=iEFkrA4sPQsRLGAmeD263jPMX4u2SF1NK4_KYqnVwow,1402
18
18
  dodal/beamlines/i24.py,sha256=LIe8tu_ZJsoeQH7tYRZHSvqQ9C9zBGOXO6rb1DPCYjQ,4595
19
- dodal/beamlines/p38.py,sha256=0uRL4GVs1sGobr0BZpDDbtStNJcipT6o4FvoA_OWLaE,7120
19
+ dodal/beamlines/p38.py,sha256=TC78u4GwEnj6X0r5cnHhqNdBbbDRnh8lY8pEucBZjEU,8006
20
20
  dodal/beamlines/p45.py,sha256=TNIkC-SBfj0ayZtlLLXW9xCSi5CzJkO8XpAMIo8fjao,2957
21
21
  dodal/common/__init__.py,sha256=ZC4ICKUDB0BDxRaVy8nmqclVmDBne-dPtk6UJsoFq6I,258
22
22
  dodal/common/coordination.py,sha256=OxIjDiO1-9A9KESRPFtzwkvvQlavbgA5RHemlbubBPg,1168
@@ -40,15 +40,14 @@ dodal/devices/cryostream.py,sha256=6MU4rXIOL33C-8F3DVfAtv0ZnwiysTtawjkeePd5IrQ,3
40
40
  dodal/devices/dcm.py,sha256=vfyGYDzfSwTiNqlzkfNjkrL-Q1hNVSgJddvJ5Un_lvg,1610
41
41
  dodal/devices/eiger.py,sha256=NE4tHdqgUZpUxJLQbd5lLUIHZcpeotppexJGlDNByzM,13868
42
42
  dodal/devices/eiger_odin.py,sha256=U5Byb7uNwDdNscBRp7yBYQrsjKrKXl2l5WdSpL09lAw,6980
43
- dodal/devices/fast_grid_scan.py,sha256=_jsiUxpMO2rAGDhTFPN8J35e6E0oreLpKYi6W8wQGzs,12798
43
+ dodal/devices/fast_grid_scan.py,sha256=qp0jdzhTjkDtaf7ngdPcegcFLlp1EmWUDbU_knCBDpc,13438
44
44
  dodal/devices/fluorescence_detector_motion.py,sha256=RrXfPmJzWnAjcjp9u0AnJEfjvWPMKburVTySB2hxYbw,181
45
45
  dodal/devices/flux.py,sha256=RtPStHw7Mad0igVKntKWVZfuZn2clokVJqH14HLix6M,198
46
46
  dodal/devices/focusing_mirror.py,sha256=aRqBkE3OgaXpH6lP3v1VbSYgHsMMbSsPPXzeyAGf_Pg,6435
47
47
  dodal/devices/ipin.py,sha256=OGMXwAE4KDDonZRPFkUmR9Vsk6X4Ox-hEvPT5drP-mQ,208
48
48
  dodal/devices/linkam3.py,sha256=TPhiQ1D9i_HIlKHAlfnVfX7H6aPOAeXPEJLdmvwdKWQ,3776
49
49
  dodal/devices/logging_ophyd_device.py,sha256=xw4lbyqq5_ehESGterVEfubJsBiJTWvBp5b9k62gSkg,666
50
- dodal/devices/lower_gonio_stages.py,sha256=oJ_Xeuqs5E8AWAoZX52jD1qV6pCSjCOjdwnXFKz_xjw,229
51
- dodal/devices/motors.py,sha256=aKtMv5q_4b1eFhzyuk2-D6zDsY_6cqAmG59y5LWUz1s,1328
50
+ dodal/devices/motors.py,sha256=T0wKGpRKhgduAQj3jE8CGmxy3cWMAFl9_glyjVZwjnA,1473
52
51
  dodal/devices/p45.py,sha256=jzBW2fGRhIbGzSRs5Fgupxro6aqE611n1RTcrTTG-yY,1047
53
52
  dodal/devices/qbpm1.py,sha256=OY7-WbdxMiLGUK8Z57ezwqSXbHxoPP-y3GvBgj9kgMA,220
54
53
  dodal/devices/robot.py,sha256=V5Gk-e1ZczU7y8SqU4_1YXYQJ6knoVFtXLZmIDY9MI4,2530
@@ -59,7 +58,7 @@ dodal/devices/slits.py,sha256=URru9VN2N19KqeUPDZaBmyKYn0_JJiE0Vko4sZpfsl8,601
59
58
  dodal/devices/smargon.py,sha256=ml96h7E1C31qPo8jocAepSouIVXgpIR0vuMF99nZjqM,2964
60
59
  dodal/devices/status.py,sha256=TuUGidZ4Ar-WCRc_sX0wn58DmL6brj1pMr8rNF5Z6VU,1198
61
60
  dodal/devices/synchrotron.py,sha256=E5vcSum-zoD5vIZxa2Xcl0gAkeRqY6a-AfZQICCwLHg,1947
62
- dodal/devices/tetramm.py,sha256=dHA15KkJhBHwtmbivDua_aVqFbvK47TVQG7KXmn8jQ8,8041
61
+ dodal/devices/tetramm.py,sha256=Xdy3dXFo4Wn6HHsRo4e1tTgMzvDa8MwkVvcDFZxFrYQ,8280
63
62
  dodal/devices/thawer.py,sha256=L5OYSdzGvx6dIkGgcTbITAbFAm0OKEVVqYBb4MPstOg,382
64
63
  dodal/devices/turbo_slit.py,sha256=W3ZRIqDhq4iMhr5GcIiWvl2U1GaPtGanqkL7upQOZTY,1132
65
64
  dodal/devices/undulator.py,sha256=kn84MQpuBHtQj7H7HeBoAYKXu5buGKvTgs3tf2gdEdw,2074
@@ -77,11 +76,12 @@ dodal/devices/detector/__init__.py,sha256=XEwjopgTtBq93RRuFthVVVI9DT1jUvpOJzWOHa
77
76
  dodal/devices/detector/det_dim_constants.py,sha256=MZ4w2nsTKzj4eN7yGsSs1pqKWIuU4vc6UzcSll02uWg,2305
78
77
  dodal/devices/detector/det_dist_to_beam_converter.py,sha256=f6JFp-eEB2v8NzZg27UrN0VDP5CMjRnaPU6BTA7_n_s,1937
79
78
  dodal/devices/detector/det_resolution.py,sha256=aQkKp24LpRGiwzPAQM3wLVa4ANw32HdrKc2kftHfKQA,3253
80
- dodal/devices/detector/detector.py,sha256=UDw_hLbhfUo_ECasGQ_Ud8bVQzE9D8uaAMagVAa_R4g,4647
79
+ dodal/devices/detector/detector.py,sha256=arP27DbrgOjYZhE6Ibp9kDBglfmqZpPBk53S5ItsrvE,4756
81
80
  dodal/devices/detector/detector_motion.py,sha256=REREva2kyPcIzOZmahN9rT0jDSuUbV0qUDl4IcBnutA,1221
82
81
  dodal/devices/i03/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
83
82
  dodal/devices/i04/transfocator.py,sha256=uieByXIj0JRbmvMB_om5NOAEbEJkzfkCD24bl2aEo1g,3154
84
83
  dodal/devices/i20_1/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
84
+ dodal/devices/i22/dcm.py,sha256=ZJL-K7GdJxyH-0wKsV0psX8iaFYJINpqBZljfAyuLbs,6046
85
85
  dodal/devices/i22/fswitch.py,sha256=AdYtnkCBuhivyJGZqelg_7sjB2pHN7vl1JTtlO4vHo4,3061
86
86
  dodal/devices/i23/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
87
87
  dodal/devices/i23/gonio.py,sha256=cxqD2Kd578c4-K4h2sHdJjoap2gPGC46Qr4SPudHLTs,864
@@ -97,7 +97,7 @@ dodal/devices/oav/oav_calculations.py,sha256=wt71vFcyQrr98FvX8oyUM2n5vmKi3K7PyOT
97
97
  dodal/devices/oav/oav_detector.py,sha256=JtzRdFQVXUdVK4Qyd9knDhsfkK6tsXoD_rIWDpLdpD4,3654
98
98
  dodal/devices/oav/oav_errors.py,sha256=cc4mGnaTiAc5WIlOt_BIYOc7CRSkrCdnBaavfAJ0pXY,754
99
99
  dodal/devices/oav/oav_parameters.py,sha256=4XybkhKeG7IEjPRfx0PVM9KNenuyN0rAGWBZG7H3zvQ,7941
100
- dodal/devices/oav/utils.py,sha256=KBvwlacwxt0KVV6ybc0nb6wPbxeAF3NfAMKWeJ5XtcU,999
100
+ dodal/devices/oav/utils.py,sha256=BkTk0aTqqhIHCZInhcUAYjCRPxumPOlTg9m21skncTc,3018
101
101
  dodal/devices/oav/pin_image_recognition/__init__.py,sha256=qEX3BRnrcP1BLZD-f_smHiMMPLJPkWQZQbIWTbW25JA,6499
102
102
  dodal/devices/oav/pin_image_recognition/manual_test.py,sha256=h1Rto6ZDCB3jWhjSy9N8ECxRN583iYDJr9LxrTJ8kfE,903
103
103
  dodal/devices/oav/pin_image_recognition/utils.py,sha256=-7-Zs-331UVTq_AZrfdF-zwZdmMn7eitTkBSqnBrxnk,8620
@@ -114,9 +114,9 @@ dodal/devices/zocalo/zocalo_results.py,sha256=U4Vk4OF-eL8w0BR-fbw3k4jyRo6G3Ywaf8
114
114
  dodal/parameters/experiment_parameter_base.py,sha256=O7JamfuJ5cYHkPf9tsHJPqn-OMHTAGouigvM1cDFehE,313
115
115
  dodal/plans/check_topup.py,sha256=VOkHak88_r-pdTsSnwAJnbvlK2_UhKnO5I36pJmWKvQ,2985
116
116
  dodal/plans/data_session_metadata.py,sha256=QNx9rb1EfGBHb21eFekIi7KjNhC0PL-SVKBCggDuNeg,1650
117
- dls_dodal-1.27.0.dist-info/LICENSE,sha256=tAkwu8-AdEyGxGoSvJ2gVmQdcicWw3j1ZZueVV74M-E,11357
118
- dls_dodal-1.27.0.dist-info/METADATA,sha256=mkrOjI4F9mwAZOTOLTSPRfNZu5W1wqlqDo3FOOWn6qs,16837
119
- dls_dodal-1.27.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
120
- dls_dodal-1.27.0.dist-info/entry_points.txt,sha256=wpzz9FsTiYxI8OBwLKX9V9ResLwThBSmtRMcPwII0FA,46
121
- dls_dodal-1.27.0.dist-info/top_level.txt,sha256=xIozdmZk_wmMV4wugpq9-6eZs0vgADNUKz3j2UAwlhc,6
122
- dls_dodal-1.27.0.dist-info/RECORD,,
117
+ dls_dodal-1.28.0.dist-info/LICENSE,sha256=tAkwu8-AdEyGxGoSvJ2gVmQdcicWw3j1ZZueVV74M-E,11357
118
+ dls_dodal-1.28.0.dist-info/METADATA,sha256=1EiYYnvAHwt0rilBYosSJaPDpF34SdatvnYUuqud9lE,16837
119
+ dls_dodal-1.28.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
120
+ dls_dodal-1.28.0.dist-info/entry_points.txt,sha256=wpzz9FsTiYxI8OBwLKX9V9ResLwThBSmtRMcPwII0FA,46
121
+ dls_dodal-1.28.0.dist-info/top_level.txt,sha256=xIozdmZk_wmMV4wugpq9-6eZs0vgADNUKz3j2UAwlhc,6
122
+ dls_dodal-1.28.0.dist-info/RECORD,,
dodal/_version.py CHANGED
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '1.27.0'
16
- __version_tuple__ = version_tuple = (1, 27, 0)
15
+ __version__ = version = '1.28.0'
16
+ __version_tuple__ = version_tuple = (1, 28, 0)
dodal/beamlines/i03.py CHANGED
@@ -17,6 +17,7 @@ from dodal.devices.eiger import EigerDetector
17
17
  from dodal.devices.fast_grid_scan import PandAFastGridScan, ZebraFastGridScan
18
18
  from dodal.devices.flux import Flux
19
19
  from dodal.devices.focusing_mirror import FocusingMirrorWithStripes, VFMMirrorVoltages
20
+ from dodal.devices.motors import XYZPositioner
20
21
  from dodal.devices.oav.oav_detector import OAV, OAVConfigParams
21
22
  from dodal.devices.oav.pin_image_recognition import PinTipDetection
22
23
  from dodal.devices.qbpm1 import QBPM1
@@ -208,7 +209,7 @@ def zebra_fast_grid_scan(
208
209
  return device_instantiation(
209
210
  device_factory=ZebraFastGridScan,
210
211
  name="zebra_fast_grid_scan",
211
- prefix="-MO-SGON-01:FGS:",
212
+ prefix="-MO-SGON-01:",
212
213
  wait=wait_for_connection,
213
214
  fake=fake_with_ophyd_sim,
214
215
  )
@@ -224,7 +225,7 @@ def panda_fast_grid_scan(
224
225
  return device_instantiation(
225
226
  device_factory=PandAFastGridScan,
226
227
  name="panda_fast_grid_scan",
227
- prefix="-MO-SGON-01:PGS:",
228
+ prefix="-MO-SGON-01:",
228
229
  wait=wait_for_connection,
229
230
  fake=fake_with_ophyd_sim,
230
231
  )
@@ -495,3 +496,18 @@ def thawer(
495
496
  wait_for_connection,
496
497
  fake_with_ophyd_sim,
497
498
  )
499
+
500
+
501
+ def lower_gonio_positioner(
502
+ wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False
503
+ ) -> XYZPositioner:
504
+ """Get the i03 lower gonio device, instantiate it if it hasn't already been.
505
+ If this is called when already instantiated in i03, it will return the existing object.
506
+ """
507
+ return device_instantiation(
508
+ XYZPositioner,
509
+ "lower_gonio_positioner",
510
+ "-MO-GONP-01:",
511
+ wait_for_connection,
512
+ fake_with_ophyd_sim,
513
+ )
dodal/beamlines/i22.py CHANGED
@@ -12,9 +12,11 @@ from dodal.common.beamlines.beamline_utils import set_beamline as set_utils_beam
12
12
  from dodal.common.beamlines.device_helpers import numbered_slits
13
13
  from dodal.common.visit import LocalDirectoryServiceClient, StaticVisitDirectoryProvider
14
14
  from dodal.devices.focusing_mirror import FocusingMirror
15
+ from dodal.devices.i22.dcm import CrystalMetadata, DoubleCrystalMonochromator
15
16
  from dodal.devices.i22.fswitch import FSwitch
16
17
  from dodal.devices.linkam3 import Linkam3
17
18
  from dodal.devices.slits import Slits
19
+ from dodal.devices.synchrotron import Synchrotron
18
20
  from dodal.devices.tetramm import TetrammDetector
19
21
  from dodal.devices.undulator import Undulator
20
22
  from dodal.log import set_beamline as set_log_beamline
@@ -53,6 +55,18 @@ def saxs(
53
55
  )
54
56
 
55
57
 
58
+ def synchrotron(
59
+ wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False
60
+ ) -> Synchrotron:
61
+ return device_instantiation(
62
+ Synchrotron,
63
+ "synchrotron",
64
+ "",
65
+ wait_for_connection,
66
+ fake_with_ophyd_sim,
67
+ )
68
+
69
+
56
70
  def waxs(
57
71
  wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False
58
72
  ) -> PilatusDetector:
@@ -122,6 +136,34 @@ def hfm(
122
136
  )
123
137
 
124
138
 
139
+ def dcm(
140
+ wait_for_connection: bool = True,
141
+ fake_with_ophyd_sim: bool = False,
142
+ ) -> DoubleCrystalMonochromator:
143
+ return device_instantiation(
144
+ DoubleCrystalMonochromator,
145
+ "dcm",
146
+ "",
147
+ wait_for_connection,
148
+ fake_with_ophyd_sim,
149
+ bl_prefix=False,
150
+ motion_prefix=f"{BeamlinePrefix(BL).beamline_prefix}-MO-DCM-01:",
151
+ temperature_prefix=f"{BeamlinePrefix(BL).beamline_prefix}-DI-DCM-01:",
152
+ crystal_1_metadata=CrystalMetadata(
153
+ usage="Bragg",
154
+ type="silicon",
155
+ reflection=(1, 1, 1),
156
+ d_spacing=3.13475,
157
+ ),
158
+ crystal_2_metadata=CrystalMetadata(
159
+ usage="Bragg",
160
+ type="silicon",
161
+ reflection=(1, 1, 1),
162
+ d_spacing=3.13475,
163
+ ),
164
+ )
165
+
166
+
125
167
  def undulator(
126
168
  wait_for_connection: bool = True,
127
169
  fake_with_ophyd_sim: bool = False,
@@ -283,6 +325,7 @@ def panda4(
283
325
  )
284
326
 
285
327
 
328
+ @skip_device
286
329
  def oav(
287
330
  wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False
288
331
  ) -> AravisDetector:
dodal/beamlines/p38.py CHANGED
@@ -12,6 +12,7 @@ from dodal.common.beamlines.beamline_utils import set_beamline as set_utils_beam
12
12
  from dodal.common.beamlines.device_helpers import numbered_slits
13
13
  from dodal.common.visit import LocalDirectoryServiceClient, StaticVisitDirectoryProvider
14
14
  from dodal.devices.focusing_mirror import FocusingMirror
15
+ from dodal.devices.i22.dcm import CrystalMetadata, DoubleCrystalMonochromator
15
16
  from dodal.devices.i22.fswitch import FSwitch
16
17
  from dodal.devices.linkam3 import Linkam3
17
18
  from dodal.devices.slits import Slits
@@ -214,6 +215,34 @@ def hfm(
214
215
  )
215
216
 
216
217
 
218
+ def dcm(
219
+ wait_for_connection: bool = True,
220
+ fake_with_ophyd_sim: bool = True,
221
+ ) -> DoubleCrystalMonochromator:
222
+ return device_instantiation(
223
+ DoubleCrystalMonochromator,
224
+ "dcm",
225
+ "",
226
+ wait_for_connection,
227
+ fake_with_ophyd_sim,
228
+ bl_prefix=False,
229
+ motion_prefix=f"{BeamlinePrefix(BL).beamline_prefix}-MO-DCM-01:",
230
+ temperature_prefix=f"{BeamlinePrefix(BL).beamline_prefix}-DI-DCM-01:",
231
+ crystal_1_metadata=CrystalMetadata(
232
+ usage="Bragg",
233
+ type="silicon",
234
+ reflection=(1, 1, 1),
235
+ d_spacing=3.13475,
236
+ ),
237
+ crystal_2_metadata=CrystalMetadata(
238
+ usage="Bragg",
239
+ type="silicon",
240
+ reflection=(1, 1, 1),
241
+ d_spacing=3.13475,
242
+ ),
243
+ )
244
+
245
+
217
246
  def undulator(
218
247
  wait_for_connection: bool = True,
219
248
  fake_with_ophyd_sim: bool = True,
@@ -67,7 +67,11 @@ class DetectorParams(BaseModel):
67
67
  def _parse_detector_size_constants(
68
68
  cls, det_type: str, values: dict[str, Any]
69
69
  ) -> DetectorSizeConstants:
70
- return constants_from_type(det_type)
70
+ return (
71
+ det_type
72
+ if isinstance(det_type, DetectorSizeConstants)
73
+ else constants_from_type(det_type)
74
+ )
71
75
 
72
76
  @validator("directory", pre=True)
73
77
  def _parse_directory(cls, directory: str, values: dict[str, Any]) -> str:
@@ -1,4 +1,4 @@
1
- from abc import ABC
1
+ from abc import ABC, abstractmethod
2
2
  from typing import Any, Generic, TypeVar
3
3
 
4
4
  import numpy as np
@@ -9,6 +9,7 @@ from ophyd_async.core import (
9
9
  Device,
10
10
  Signal,
11
11
  SignalR,
12
+ SignalRW,
12
13
  SoftSignalBackend,
13
14
  StandardReadable,
14
15
  wait_for_value,
@@ -16,8 +17,9 @@ from ophyd_async.core import (
16
17
  from ophyd_async.epics.signal import (
17
18
  epics_signal_r,
18
19
  epics_signal_rw,
20
+ epics_signal_rw_rbv,
21
+ epics_signal_x,
19
22
  )
20
- from ophyd_async.epics.signal.signal import epics_signal_rw_rbv
21
23
  from pydantic import validator
22
24
  from pydantic.dataclasses import dataclass
23
25
 
@@ -212,7 +214,7 @@ class PandAGridScanParams(GridScanParamsCommon):
212
214
  class MotionProgram(Device):
213
215
  def __init__(self, prefix: str, name: str = "") -> None:
214
216
  super().__init__(name)
215
- self.running = epics_signal_r(float, prefix + "PROGBITS")
217
+ self.running = epics_signal_r(int, prefix + "PROGBITS")
216
218
  self.program_number = epics_signal_r(float, prefix + "CS1:PROG_NUM")
217
219
 
218
220
 
@@ -240,38 +242,33 @@ class FastGridScanCommon(StandardReadable, ABC, Generic[ParamType]):
240
242
  See https://github.com/DiamondLightSource/hyperion/wiki/Coordinate-Systems for more
241
243
  """
242
244
 
243
- def __init__(self, prefix: str, name: str = "") -> None:
244
- super().__init__(name)
245
- self.x_steps = epics_signal_rw_rbv(int, "X_NUM_STEPS")
245
+ def __init__(self, prefix: str, smargon_prefix: str, name: str = "") -> None:
246
+ self.x_steps = epics_signal_rw_rbv(int, f"{prefix}X_NUM_STEPS")
246
247
  self.y_steps = epics_signal_rw_rbv(
247
- int, "X_NUM_STEPS"
248
+ int, f"{prefix}Y_NUM_STEPS"
248
249
  ) # Number of vertical steps during the first grid scan
249
250
  self.z_steps = epics_signal_rw_rbv(
250
- int, "X_NUM_STEPS"
251
+ int, f"{prefix}Z_NUM_STEPS"
251
252
  ) # Number of vertical steps during the second grid scan, after the rotation in omega
252
- self.x_step_size = epics_signal_rw_rbv(float, "X_STEP_SIZE")
253
- self.y_step_size = epics_signal_rw_rbv(float, "Y_STEP_SIZE")
254
- self.z_step_size = epics_signal_rw_rbv(float, "Z_STEP_SIZE")
255
- self.x_start = epics_signal_rw_rbv(float, "X_START")
256
- self.y1_start = epics_signal_rw_rbv(float, "Y_START")
257
- self.y2_start = epics_signal_rw_rbv(float, "Y2_START")
258
- self.z1_start = epics_signal_rw_rbv(float, "Z_START")
259
- self.z2_start = epics_signal_rw_rbv(float, "Z2_START")
260
-
261
- self.position_counter = epics_signal_rw(
262
- int, "POS_COUNTER", write_pv="POS_COUNTER_WRITE"
263
- )
264
- self.x_counter = epics_signal_r(int, "X_COUNTER")
265
- self.y_counter = epics_signal_r(int, "Y_COUNTER")
266
- self.scan_invalid = epics_signal_r(float, "SCAN_INVALID")
253
+ self.x_step_size = epics_signal_rw_rbv(float, f"{prefix}X_STEP_SIZE")
254
+ self.y_step_size = epics_signal_rw_rbv(float, f"{prefix}Y_STEP_SIZE")
255
+ self.z_step_size = epics_signal_rw_rbv(float, f"{prefix}Z_STEP_SIZE")
256
+ self.x_start = epics_signal_rw_rbv(float, f"{prefix}X_START")
257
+ self.y1_start = epics_signal_rw_rbv(float, f"{prefix}Y_START")
258
+ self.y2_start = epics_signal_rw_rbv(float, f"{prefix}Y2_START")
259
+ self.z1_start = epics_signal_rw_rbv(float, f"{prefix}Z_START")
260
+ self.z2_start = epics_signal_rw_rbv(float, f"{prefix}Z2_START")
261
+
262
+ self.scan_invalid = epics_signal_r(float, f"{prefix}SCAN_INVALID")
267
263
 
268
- self.run_cmd = epics_signal_rw(int, "RUN.PROC")
269
- self.stop_cmd = epics_signal_rw(int, "STOP.PROC")
270
- self.status = epics_signal_r(float, "SCAN_STATUS")
264
+ self.run_cmd = epics_signal_x(f"{prefix}RUN.PROC")
265
+ self.status = epics_signal_r(int, f"{prefix}SCAN_STATUS")
271
266
 
272
267
  self.expected_images = ExpectedImages(parent=self)
273
268
 
274
- self.motion_program = MotionProgram(prefix)
269
+ self.motion_program = MotionProgram(smargon_prefix)
270
+
271
+ self.position_counter = self._create_position_counter(prefix)
275
272
 
276
273
  # Kickoff timeout in seconds
277
274
  self.KICKOFF_TIMEOUT: float = 5.0
@@ -291,6 +288,7 @@ class FastGridScanCommon(StandardReadable, ABC, Generic[ParamType]):
291
288
  "z1_start": self.z1_start,
292
289
  "z2_start": self.z2_start,
293
290
  }
291
+ super().__init__(name)
294
292
 
295
293
  @AsyncStatus.wrap
296
294
  async def kickoff(self):
@@ -301,7 +299,7 @@ class FastGridScanCommon(StandardReadable, ABC, Generic[ParamType]):
301
299
  await wait_for_value(self.motion_program.running, 0, self.KICKOFF_TIMEOUT)
302
300
 
303
301
  LOGGER.debug("Running scan")
304
- await self.run_cmd.set(1)
302
+ await self.run_cmd.trigger()
305
303
  LOGGER.info("Waiting for FGS to start")
306
304
  await wait_for_value(self.status, 1, self.KICKOFF_TIMEOUT)
307
305
  LOGGER.debug("FGS kicked off")
@@ -310,6 +308,10 @@ class FastGridScanCommon(StandardReadable, ABC, Generic[ParamType]):
310
308
  async def complete(self):
311
309
  await wait_for_value(self.status, 0, self.COMPLETE_STATUS)
312
310
 
311
+ @abstractmethod
312
+ def _create_position_counter(self, prefix: str) -> SignalRW[int]:
313
+ pass
314
+
313
315
 
314
316
  class ZebraFastGridScan(FastGridScanCommon[ZebraGridScanParams]):
315
317
  """Device for standard Zebra FGS. In this scan, the goniometer's velocity profile follows a parabolic shape between X steps,
@@ -317,29 +319,46 @@ class ZebraFastGridScan(FastGridScanCommon[ZebraGridScanParams]):
317
319
  """
318
320
 
319
321
  def __init__(self, prefix: str, name: str = "") -> None:
320
- super().__init__(prefix, name)
321
-
322
+ full_prefix = prefix + "FGS:"
322
323
  # Time taken to travel between X steps
323
- self.dwell_time_ms = epics_signal_rw_rbv(float, "DWELL_TIME")
324
+ self.dwell_time_ms = epics_signal_rw_rbv(float, f"{full_prefix}DWELL_TIME")
325
+
326
+ self.x_counter = epics_signal_r(int, f"{full_prefix}X_COUNTER")
327
+ self.y_counter = epics_signal_r(int, f"{full_prefix}Y_COUNTER")
328
+
329
+ super().__init__(full_prefix, prefix, name)
330
+
324
331
  self.movable_params["dwell_time_ms"] = self.dwell_time_ms
325
332
 
333
+ def _create_position_counter(self, prefix: str):
334
+ return epics_signal_rw(
335
+ int, f"{prefix}POS_COUNTER", write_pv=f"{prefix}POS_COUNTER_WRITE"
336
+ )
337
+
326
338
 
327
339
  class PandAFastGridScan(FastGridScanCommon[PandAGridScanParams]):
328
340
  """Device for panda constant-motion scan"""
329
341
 
330
342
  def __init__(self, prefix: str, name: str = "") -> None:
331
- super().__init__(prefix, name)
343
+ full_prefix = prefix + "PGS:"
332
344
  self.time_between_x_steps_ms = (
333
345
  epics_signal_rw_rbv( # Used by motion controller to set goniometer velocity
334
- float, "TIME_BETWEEN_X_STEPS"
346
+ float, f"{full_prefix}TIME_BETWEEN_X_STEPS"
335
347
  )
336
348
  )
337
349
 
338
350
  # Distance before and after the grid given to allow goniometer to reach desired speed while it is within the
339
351
  # grid
340
- self.run_up_distance_mm = epics_signal_rw_rbv(float, "RUNUP_DISTANCE")
352
+ self.run_up_distance_mm = epics_signal_rw_rbv(
353
+ float, f"{full_prefix}RUNUP_DISTANCE"
354
+ )
355
+ super().__init__(full_prefix, prefix, name)
356
+
341
357
  self.movable_params["run_up_distance_mm"] = self.run_up_distance_mm
342
358
 
359
+ def _create_position_counter(self, prefix: str):
360
+ return epics_signal_rw(int, f"{prefix}Y_COUNTER")
361
+
343
362
 
344
363
  def set_fast_grid_scan_params(scan: FastGridScanCommon[ParamType], params: ParamType):
345
364
  to_move = []
@@ -0,0 +1,152 @@
1
+ import time
2
+ from dataclasses import dataclass
3
+ from typing import Dict, Literal
4
+
5
+ from bluesky.protocols import Reading
6
+ from event_model.documents.event_descriptor import DataKey
7
+ from ophyd_async.core import ConfigSignal, StandardReadable, soft_signal_r_and_setter
8
+ from ophyd_async.epics.motion import Motor
9
+ from ophyd_async.epics.signal import epics_signal_r
10
+
11
+ # Conversion constant for energy and wavelength, taken from the X-Ray data booklet
12
+ # Converts between energy in KeV and wavelength in angstrom
13
+ _CONVERSION_CONSTANT = 12.3984
14
+
15
+
16
+ @dataclass(frozen=True, unsafe_hash=True)
17
+ class CrystalMetadata:
18
+ """
19
+ Metadata used in the NeXus format,
20
+ see https://manual.nexusformat.org/classes/base_classes/NXcrystal.html
21
+ """
22
+
23
+ usage: Literal["Bragg", "Laue"] | None = None
24
+ type: str | None = None
25
+ reflection: tuple[int, int, int] | None = None
26
+ d_spacing: float | None = None
27
+
28
+
29
+ class DoubleCrystalMonochromator(StandardReadable):
30
+ """
31
+ A double crystal monochromator (DCM), used to select the energy of the beam.
32
+
33
+ perp describes the gap between the 2 DCM crystals which has to change as you alter
34
+ the angle to select the requested energy.
35
+
36
+ offset ensures that the beam exits the DCM at the same point, regardless of energy.
37
+ """
38
+
39
+ def __init__(
40
+ self,
41
+ motion_prefix: str,
42
+ temperature_prefix: str,
43
+ crystal_1_metadata: CrystalMetadata | None = None,
44
+ crystal_2_metadata: CrystalMetadata | None = None,
45
+ prefix: str = "",
46
+ name: str = "",
47
+ ) -> None:
48
+ with self.add_children_as_readables():
49
+ # Positionable Parameters
50
+ self.bragg = Motor(motion_prefix + "BRAGG")
51
+ self.offset = Motor(motion_prefix + "OFFSET")
52
+ self.perp = Motor(motion_prefix + "PERP")
53
+ self.energy = Motor(motion_prefix + "ENERGY")
54
+ self.crystal_1_roll = Motor(motion_prefix + "XTAL1:ROLL")
55
+ self.crystal_2_roll = Motor(motion_prefix + "XTAL2:ROLL")
56
+ self.crystal_2_pitch = Motor(motion_prefix + "XTAL2:PITCH")
57
+
58
+ # Temperatures
59
+ self.backplate_temp = epics_signal_r(float, temperature_prefix + "PT100-7")
60
+ self.perp_temp = epics_signal_r(float, temperature_prefix + "TC-1")
61
+ self.crystal_1_temp = epics_signal_r(float, temperature_prefix + "PT100-1")
62
+ self.crystal_1_heater_temp = epics_signal_r(
63
+ float, temperature_prefix + "PT100-2"
64
+ )
65
+ self.crystal_2_temp = epics_signal_r(float, temperature_prefix + "PT100-4")
66
+ self.crystal_2_heater_temp = epics_signal_r(
67
+ float, temperature_prefix + "PT100-5"
68
+ )
69
+
70
+ # Soft metadata
71
+ # If supplied include crystal details in output of read_configuration
72
+ crystal_1_metadata = crystal_1_metadata or CrystalMetadata()
73
+ crystal_2_metadata = crystal_2_metadata or CrystalMetadata()
74
+ with self.add_children_as_readables(ConfigSignal):
75
+ if crystal_1_metadata.usage is not None:
76
+ self.crystal_1_usage, _ = soft_signal_r_and_setter(
77
+ str, initial_value=crystal_1_metadata.usage
78
+ )
79
+ else:
80
+ self.crystal_1_usage = None
81
+ if crystal_1_metadata.type is not None:
82
+ self.crystal_1_type, _ = soft_signal_r_and_setter(
83
+ str, initial_value=crystal_1_metadata.type
84
+ )
85
+ else:
86
+ self.crystal_1_type = None
87
+ if crystal_1_metadata.reflection is not None:
88
+ self.crystal_1_reflection, _ = soft_signal_r_and_setter(
89
+ str, initial_value=crystal_1_metadata.reflection
90
+ )
91
+ else:
92
+ self.crystal_1_reflection = None
93
+ if crystal_1_metadata.d_spacing is not None:
94
+ self.crystal_1_d_spacing, _ = soft_signal_r_and_setter(
95
+ str, initial_value=crystal_1_metadata.d_spacing
96
+ )
97
+ else:
98
+ self.crystal_1_d_spacing = None
99
+ if crystal_2_metadata.usage is not None:
100
+ self.crystal_2_usage, _ = soft_signal_r_and_setter(
101
+ str, initial_value=crystal_2_metadata.usage
102
+ )
103
+ else:
104
+ self.crystal_2_usage = None
105
+ if crystal_2_metadata.type is not None:
106
+ self.crystal_2_type, _ = soft_signal_r_and_setter(
107
+ str, initial_value=crystal_2_metadata.type
108
+ )
109
+ else:
110
+ self.crystal_2_type = None
111
+ if crystal_2_metadata.reflection is not None:
112
+ self.crystal_2_reflection, _ = soft_signal_r_and_setter(
113
+ str, initial_value=crystal_2_metadata.reflection
114
+ )
115
+ else:
116
+ self.crystal_2_reflection = None
117
+ if crystal_2_metadata.d_spacing is not None:
118
+ self.crystal_2_d_spacing, _ = soft_signal_r_and_setter(
119
+ str, initial_value=crystal_2_metadata.d_spacing
120
+ )
121
+ else:
122
+ self.crystal_2_d_spacing = None
123
+
124
+ super().__init__(name)
125
+
126
+ async def describe(self) -> Dict[str, DataKey]:
127
+ default_describe = await super().describe()
128
+ return {
129
+ f"{self.name}-wavelength": DataKey(
130
+ dtype="number",
131
+ shape=[],
132
+ source=self.name,
133
+ units="angstrom",
134
+ ),
135
+ **default_describe,
136
+ }
137
+
138
+ async def read(self) -> Dict[str, Reading]:
139
+ default_reading = await super().read()
140
+ energy: float = default_reading[f"{self.name}-energy"]["value"]
141
+ if energy > 0.0:
142
+ wavelength = _CONVERSION_CONSTANT / energy
143
+ else:
144
+ wavelength = 0.0
145
+
146
+ return {
147
+ **default_reading,
148
+ f"{self.name}-wavelength": Reading(
149
+ value=wavelength,
150
+ timestamp=time.time(),
151
+ ),
152
+ }
dodal/devices/motors.py CHANGED
@@ -2,13 +2,17 @@ from dataclasses import dataclass
2
2
  from typing import List, Tuple
3
3
 
4
4
  import numpy as np
5
- from ophyd import Component, Device, EpicsMotor
5
+ from ophyd import EpicsMotor
6
+ from ophyd_async.core import Device
7
+ from ophyd_async.epics.motion import Motor
6
8
 
7
9
 
8
10
  class XYZPositioner(Device):
9
- x = Component(EpicsMotor, "X")
10
- y = Component(EpicsMotor, "Y")
11
- z = Component(EpicsMotor, "Z")
11
+ def __init__(self, prefix: str, name: str):
12
+ self.x = Motor(prefix + "X")
13
+ self.y = Motor(prefix + "Y")
14
+ self.z = Motor(prefix + "Z")
15
+ super().__init__(name)
12
16
 
13
17
 
14
18
  @dataclass
@@ -1,6 +1,20 @@
1
1
  from enum import IntEnum
2
+ from typing import Generator, Tuple
2
3
 
4
+ import bluesky.plan_stubs as bps
3
5
  import numpy as np
6
+ from bluesky.utils import Msg
7
+
8
+ from dodal.devices.oav.oav_calculations import camera_coordinates_to_xyz
9
+ from dodal.devices.oav.oav_detector import OAVConfigParams
10
+ from dodal.devices.oav.pin_image_recognition import PinTipDetection
11
+ from dodal.devices.smargon import Smargon
12
+
13
+ Pixel = Tuple[int, int]
14
+
15
+
16
+ class PinNotFoundException(Exception):
17
+ pass
4
18
 
5
19
 
6
20
  def bottom_right_from_top_left(
@@ -47,3 +61,49 @@ class EdgeOutputArrayImageType(IntEnum):
47
61
  PREPROCESSED = 2
48
62
  CANNY_EDGES = 3
49
63
  CLOSED_EDGES = 4
64
+
65
+
66
+ def get_move_required_so_that_beam_is_at_pixel(
67
+ smargon: Smargon, pixel: Pixel, oav_params: OAVConfigParams
68
+ ) -> Generator[Msg, None, np.ndarray]:
69
+ """Calculate the required move so that the given pixel is in the centre of the beam."""
70
+
71
+ current_motor_xyz = np.array(
72
+ [
73
+ (yield from bps.rd(smargon.x)),
74
+ (yield from bps.rd(smargon.y)),
75
+ (yield from bps.rd(smargon.z)),
76
+ ],
77
+ dtype=np.float64,
78
+ )
79
+ current_angle = yield from bps.rd(smargon.omega)
80
+
81
+ return calculate_x_y_z_of_pixel(current_motor_xyz, current_angle, pixel, oav_params)
82
+
83
+
84
+ def calculate_x_y_z_of_pixel(
85
+ current_x_y_z, current_omega, pixel: Pixel, oav_params: OAVConfigParams
86
+ ) -> np.ndarray:
87
+ beam_distance_px: Pixel = oav_params.calculate_beam_distance(*pixel)
88
+
89
+ assert oav_params.micronsPerXPixel
90
+ assert oav_params.micronsPerYPixel
91
+ return current_x_y_z + camera_coordinates_to_xyz(
92
+ beam_distance_px[0],
93
+ beam_distance_px[1],
94
+ current_omega,
95
+ oav_params.micronsPerXPixel,
96
+ oav_params.micronsPerYPixel,
97
+ )
98
+
99
+
100
+ def wait_for_tip_to_be_found(
101
+ ophyd_pin_tip_detection: PinTipDetection,
102
+ ) -> Generator[Msg, None, Pixel]:
103
+ yield from bps.trigger(ophyd_pin_tip_detection, wait=True)
104
+ found_tip = yield from bps.rd(ophyd_pin_tip_detection.triggered_tip)
105
+ if found_tip == ophyd_pin_tip_detection.INVALID_POSITION:
106
+ timeout = yield from bps.rd(ophyd_pin_tip_detection.validity_timeout)
107
+ raise PinNotFoundException(f"No pin found after {timeout} seconds")
108
+
109
+ return found_tip # type: ignore
dodal/devices/tetramm.py CHANGED
@@ -115,8 +115,13 @@ class TetrammController(DetectorControl):
115
115
  self,
116
116
  num: int,
117
117
  trigger: DetectorTrigger,
118
- exposure: float,
118
+ exposure: float | None = None,
119
119
  ) -> AsyncStatus:
120
+ if exposure is None:
121
+ raise ValueError(
122
+ "Tetramm does not support arm without exposure time. "
123
+ "Is this a software scan? Tetramm only supports hardware scans."
124
+ )
120
125
  self._validate_trigger(trigger)
121
126
 
122
127
  # trigger mode must be set first and on its own!
@@ -1,8 +0,0 @@
1
- from ophyd import Component as Cpt
2
- from ophyd import Device, EpicsMotor
3
-
4
-
5
- class GonioLowerStages(Device):
6
- x = Cpt(EpicsMotor, "-MO-GONP-01:X")
7
- y = Cpt(EpicsMotor, "-MO-GONP-01:Y")
8
- z = Cpt(EpicsMotor, "-MO-GONP-01:Z")