dls-dodal 1.29.2__py3-none-any.whl → 1.29.4__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.29.2
3
+ Version: 1.29.4
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
@@ -215,12 +215,12 @@ Description-Content-Type: text/x-rst
215
215
  License-File: LICENSE
216
216
  Requires-Dist: click
217
217
  Requires-Dist: ophyd
218
- Requires-Dist: ophyd-async >=0.3.1
218
+ Requires-Dist: ophyd-async <0.4.0
219
219
  Requires-Dist: bluesky
220
220
  Requires-Dist: pyepics
221
221
  Requires-Dist: dataclasses-json
222
222
  Requires-Dist: pillow
223
- Requires-Dist: zocalo
223
+ Requires-Dist: zocalo >=0.32.0
224
224
  Requires-Dist: requests
225
225
  Requires-Dist: graypy
226
226
  Requires-Dist: pydantic
@@ -1,10 +1,10 @@
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=eOWWacfqG9vwurNfs90FChgA65Fn-DDQGkxIrhBgYv8,413
3
+ dodal/_version.py,sha256=dGQcJRhmSkKpwrkZVgo13Sd0AVHTIBb9i0csee9mzMo,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=dfo1rfYrGG8oIm2HkNxaa_ldVs4vJKtgWSoKe1Z_Xno,8533
7
- dodal/utils.py,sha256=aH-W94t6NFOoGHZ7awbUKY8_k7qIYDourCFs3MKIjjA,10024
7
+ dodal/utils.py,sha256=SFDmUOLtxLe5OdNdVgS_EftNpX0ihxduBwq6tufw8s4,10174
8
8
  dodal/beamline_specific_utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
9
  dodal/beamline_specific_utils/i03.py,sha256=Ixe1anFQl-kwRJubmQx28TIW4Zw8qDxpElNNNapWQHI,396
10
10
  dodal/beamlines/README.md,sha256=K9MkL_GomxlsoTB7Mz-_dJA5NNSbmCfMiutchGg3C8o,404
@@ -15,7 +15,7 @@ dodal/beamlines/i04_1.py,sha256=KDxSUQNhIs_NFiRaLY-Jiory0DeN7Y0ErvGuoTrwCDU,4731
15
15
  dodal/beamlines/i20_1.py,sha256=MaPgONHqpoZuBtkiKEzYtViJnKBM2_ekeP4OdbmuXHE,1158
16
16
  dodal/beamlines/i22.py,sha256=3VFdA4Wc7O40-64lwUtUBIN23fH4JVNbLKJ1JLjy9as,9870
17
17
  dodal/beamlines/i23.py,sha256=2j5qLoqE_hg9ETHqNkOVu7LLkVB8qalgXeORnVYKN_I,1075
18
- dodal/beamlines/i24.py,sha256=dCMQGcBZ6ADZ6_rEDFcV2BPHGKBC9iVFvfxewxVts4k,6111
18
+ dodal/beamlines/i24.py,sha256=tPSrWArwRd0fb59HyHFRGR1FdscM7t9_hNPk0edhh4o,6583
19
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
@@ -33,17 +33,17 @@ dodal/devices/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
33
  dodal/devices/adsim.py,sha256=dMU0TKIuiODHYFHQOH4_5UvB8iJtaJEtjqaEDGjcU-w,311
34
34
  dodal/devices/aperture.py,sha256=0MtTzKMDZ5DVAz0DE0kXI0M76VCp0y9vFsrMggEMpxk,586
35
35
  dodal/devices/aperturescatterguard.py,sha256=2JJsEPJGJHrI0ztv1cSaP7H5T6qdzDfUcN-VEQ39B8o,11012
36
- dodal/devices/attenuator.py,sha256=OD7fElTIMHWk7ZopPqEu29lionm7WwgC0-Kvl8vBIb0,2599
36
+ dodal/devices/attenuator.py,sha256=viK1iccNekX6ZvR_ZmSwj5JdM1j2B8pcTg8qWDdmzhQ,2584
37
37
  dodal/devices/backlight.py,sha256=vsNGZB4C_mVMafllvJlOTghsfv6UqALMKUMLXu3WZ5k,1115
38
38
  dodal/devices/beamstop.py,sha256=8L3qhlk-3ZBOp10xK1i8qZqYTGOXX1mVF1MgXoN0dfg,215
39
39
  dodal/devices/cryostream.py,sha256=6MU4rXIOL33C-8F3DVfAtv0ZnwiysTtawjkeePd5IrQ,332
40
40
  dodal/devices/dcm.py,sha256=vfyGYDzfSwTiNqlzkfNjkrL-Q1hNVSgJddvJ5Un_lvg,1610
41
- dodal/devices/eiger.py,sha256=NE4tHdqgUZpUxJLQbd5lLUIHZcpeotppexJGlDNByzM,13868
42
- dodal/devices/eiger_odin.py,sha256=U5Byb7uNwDdNscBRp7yBYQrsjKrKXl2l5WdSpL09lAw,6980
41
+ dodal/devices/eiger.py,sha256=HpZIZ6Y8iffo9GxmuFJmnyRCa5Tl0BKSKU2_wVccHDo,14069
42
+ dodal/devices/eiger_odin.py,sha256=zKNu5OaIQ9HkflfxKCi87Yr1kEG2gXfxzj0KPv0XzCk,6960
43
43
  dodal/devices/fast_grid_scan.py,sha256=BEj96j78r60JPPJoOMP-XXG-_9yURFTuu-pp2LcqQmY,12452
44
44
  dodal/devices/fluorescence_detector_motion.py,sha256=RrXfPmJzWnAjcjp9u0AnJEfjvWPMKburVTySB2hxYbw,181
45
45
  dodal/devices/flux.py,sha256=RtPStHw7Mad0igVKntKWVZfuZn2clokVJqH14HLix6M,198
46
- dodal/devices/focusing_mirror.py,sha256=aRqBkE3OgaXpH6lP3v1VbSYgHsMMbSsPPXzeyAGf_Pg,6435
46
+ dodal/devices/focusing_mirror.py,sha256=v6TPglvmHiv9H05Lssv1LY5Nr9RBHQISLzL4SJ_QIbk,5890
47
47
  dodal/devices/hutch_shutter.py,sha256=nZ3gRbYIVJsXLlpZMWT4UEYUFFQP1MwMe8Oy304QsqE,3360
48
48
  dodal/devices/ipin.py,sha256=OGMXwAE4KDDonZRPFkUmR9Vsk6X4Ox-hEvPT5drP-mQ,208
49
49
  dodal/devices/linkam3.py,sha256=TPhiQ1D9i_HIlKHAlfnVfX7H6aPOAeXPEJLdmvwdKWQ,3776
@@ -51,7 +51,7 @@ dodal/devices/logging_ophyd_device.py,sha256=xw4lbyqq5_ehESGterVEfubJsBiJTWvBp5b
51
51
  dodal/devices/motors.py,sha256=16ID2jFJ35h6ZrFp76nJG_oQg6uDrupgcbvcbmjlc7c,300
52
52
  dodal/devices/p45.py,sha256=jzBW2fGRhIbGzSRs5Fgupxro6aqE611n1RTcrTTG-yY,1047
53
53
  dodal/devices/qbpm1.py,sha256=OY7-WbdxMiLGUK8Z57ezwqSXbHxoPP-y3GvBgj9kgMA,220
54
- dodal/devices/robot.py,sha256=RcSqBPMMUpWyP7LoyifatII5GWup-vFYZutniM60m_k,4231
54
+ dodal/devices/robot.py,sha256=5WQ9kF5m8xhHhipBycsycDV0-_2IBNBkcwuSWP-9-1I,4337
55
55
  dodal/devices/s4_slit_gaps.py,sha256=j3kgF9WfGFaU9xdUuiAh-QqI5u_vhiAftaDVINt91SM,243
56
56
  dodal/devices/scatterguard.py,sha256=0qnvhoo3RjLsrxVgIoDJpryqunlgMVgaTsoyKRC2g4Y,331
57
57
  dodal/devices/scintillator.py,sha256=4Dej1a6HRom9GRwTDsaTKGfvloP20POUqIeHqsI8-R8,184
@@ -64,7 +64,7 @@ dodal/devices/thawer.py,sha256=hIdZOzCNloY7CtSvdE2gk4vCMMoOtaIA4dPH_k0OwFg,1527
64
64
  dodal/devices/turbo_slit.py,sha256=W3ZRIqDhq4iMhr5GcIiWvl2U1GaPtGanqkL7upQOZTY,1132
65
65
  dodal/devices/undulator.py,sha256=kn84MQpuBHtQj7H7HeBoAYKXu5buGKvTgs3tf2gdEdw,2074
66
66
  dodal/devices/undulator_dcm.py,sha256=TC9fO55r1YIG_88PPbGGtzfjcRJcaoC2ny51JiDOEX4,5199
67
- dodal/devices/webcam.py,sha256=FXYcxQdOOCRIMAf8jMWlDVAhSEs4ycGCnoODvHb-apM,1554
67
+ dodal/devices/webcam.py,sha256=dvNWJ6hHQR7BUsMRC9TH4XiCCofVhlgZ8HYfVCvd2og,1367
68
68
  dodal/devices/xbpm_feedback.py,sha256=8QHYKHo9ksZo30olbFM-tHpCHcJRFozgHKVJijv3Gck,1986
69
69
  dodal/devices/zebra.py,sha256=9Zkq5I3-gcP6qfDBnPEAtFU4QJ-VJyp7cHvB79ZfLHk,9186
70
70
  dodal/devices/zebra_controlled_shutter.py,sha256=MqX4KE6w0FliZRDBltswcLCNSsp6vQrD_iBY640IljI,1094
@@ -72,12 +72,12 @@ dodal/devices/areadetector/__init__.py,sha256=8IwLxuZMW0MOJpJp_ZDdlaE20hrtsH_PXW
72
72
  dodal/devices/areadetector/adaravis.py,sha256=pwbmmnakarjhD59XoyAIXJdakS-nqDG09Xmwq17AVw4,3787
73
73
  dodal/devices/areadetector/adsim.py,sha256=3U7kS93RM3Xeh-XWKjeuw5jXbIGWAbrs59LfxtvB7OU,1907
74
74
  dodal/devices/areadetector/adutils.py,sha256=JIx1_sYlehpLtEXcwOEuzVoMplsLdKVW7OWv5eiJqgE,2576
75
- dodal/devices/areadetector/plugins/MJPG.py,sha256=pLuEZiRGgCwJM1ONA5jdetGLo6O0OVhvmcVkkYtPeR8,4159
75
+ dodal/devices/areadetector/plugins/MJPG.py,sha256=2ISGUg9JxJYzEH640WUIsvFwKolSTywkTyPy9wz6f_k,4064
76
76
  dodal/devices/detector/__init__.py,sha256=XEwjopgTtBq93RRuFthVVVI9DT1jUvpOJzWOHantJpU,104
77
77
  dodal/devices/detector/det_dim_constants.py,sha256=MZ4w2nsTKzj4eN7yGsSs1pqKWIuU4vc6UzcSll02uWg,2305
78
78
  dodal/devices/detector/det_dist_to_beam_converter.py,sha256=f6JFp-eEB2v8NzZg27UrN0VDP5CMjRnaPU6BTA7_n_s,1937
79
79
  dodal/devices/detector/det_resolution.py,sha256=aQkKp24LpRGiwzPAQM3wLVa4ANw32HdrKc2kftHfKQA,3253
80
- dodal/devices/detector/detector.py,sha256=arP27DbrgOjYZhE6Ibp9kDBglfmqZpPBk53S5ItsrvE,4756
80
+ dodal/devices/detector/detector.py,sha256=7IYhndhT9CYCft0MrN1mAnopHqKYbaCRPRGLic76srw,4740
81
81
  dodal/devices/detector/detector_motion.py,sha256=REREva2kyPcIzOZmahN9rT0jDSuUbV0qUDl4IcBnutA,1221
82
82
  dodal/devices/i03/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
83
83
  dodal/devices/i04/transfocator.py,sha256=uieByXIj0JRbmvMB_om5NOAEbEJkzfkCD24bl2aEo1g,3154
@@ -89,6 +89,7 @@ dodal/devices/i24/I24_detector_motion.py,sha256=Joqr1orgeNvRS7n01bjaO-4Yu4obb8fn
89
89
  dodal/devices/i24/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
90
90
  dodal/devices/i24/aperture.py,sha256=kKfHli5oKp-j-qZhZoXTRK81SAUNyhpI6VRvtw0SkZA,850
91
91
  dodal/devices/i24/beamstop.py,sha256=28hQowTvgN5Zw38tkDh32h2ceyN-2GE8bAaGPvDOt5U,1234
92
+ dodal/devices/i24/dcm.py,sha256=Hx5pQ-SHEQ_bLCOnpQYDm6-t-it7nNDmUvopGyiGA2w,1991
92
93
  dodal/devices/i24/dual_backlight.py,sha256=Th-RKr28aFxE8LCT_mdN9KkRIVw0BHLGKkI0ienfRZU,2049
93
94
  dodal/devices/i24/i24_vgonio.py,sha256=Igqs7687z6lyhGVeJEDtDmPachYxU48MUH2BF0RpK9Q,461
94
95
  dodal/devices/i24/pmac.py,sha256=pN54myYvzqPl7iW0Vsp59J1EiV_gtn0xQGwbsKJpiYE,3876
@@ -96,16 +97,16 @@ dodal/devices/oav/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU
96
97
  dodal/devices/oav/grid_overlay.py,sha256=FRtjcFd420XY8MEQ9sWedL0i4pK-KUJOSxh2C5zM3PA,5232
97
98
  dodal/devices/oav/microns_for_zoom_levels.json,sha256=5PA71RzldFTp0eTUGPmov0MjxHe583mzvfor5f3thXI,1208
98
99
  dodal/devices/oav/oav_calculations.py,sha256=wt71vFcyQrr98FvX8oyUM2n5vmKi3K7PyOTuWp0gq5w,1665
99
- dodal/devices/oav/oav_detector.py,sha256=JtzRdFQVXUdVK4Qyd9knDhsfkK6tsXoD_rIWDpLdpD4,3654
100
+ dodal/devices/oav/oav_detector.py,sha256=PlRIuMYSk46o6Aywel9tMZYh1WzMaBhfn3_GWqCas7w,3681
100
101
  dodal/devices/oav/oav_errors.py,sha256=cc4mGnaTiAc5WIlOt_BIYOc7CRSkrCdnBaavfAJ0pXY,754
101
102
  dodal/devices/oav/oav_parameters.py,sha256=4XybkhKeG7IEjPRfx0PVM9KNenuyN0rAGWBZG7H3zvQ,7941
102
- dodal/devices/oav/utils.py,sha256=zbUDvNETDoCtclj5jNzxz1XBt5mQlWBbxUrhRP7pZrU,3663
103
- dodal/devices/oav/pin_image_recognition/__init__.py,sha256=qEX3BRnrcP1BLZD-f_smHiMMPLJPkWQZQbIWTbW25JA,6499
103
+ dodal/devices/oav/utils.py,sha256=BkTk0aTqqhIHCZInhcUAYjCRPxumPOlTg9m21skncTc,3018
104
+ dodal/devices/oav/pin_image_recognition/__init__.py,sha256=Eve6oY9EYGOSw1x-N--xjgyIEzbZE7TTwGR-Q2NZprQ,6441
104
105
  dodal/devices/oav/pin_image_recognition/manual_test.py,sha256=h1Rto6ZDCB3jWhjSy9N8ECxRN583iYDJr9LxrTJ8kfE,903
105
106
  dodal/devices/oav/pin_image_recognition/utils.py,sha256=-7-Zs-331UVTq_AZrfdF-zwZdmMn7eitTkBSqnBrxnk,8620
106
107
  dodal/devices/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
107
108
  dodal/devices/util/adjuster_plans.py,sha256=2AYaywQP_LbA2KJ6Op3cok8GoRtj696utrSSDfaJtBY,875
108
- dodal/devices/util/epics_util.py,sha256=eQr-ImBnADpBL_6XWr3_q9yuMe55Lu0h3j9L1fG4Jws,4714
109
+ dodal/devices/util/epics_util.py,sha256=4AOGqYjF0ITzs4c7PTZZnHge81Zheg4gZbFonVW7Lko,4728
109
110
  dodal/devices/util/lookup_tables.py,sha256=Up-0BlARt79TIEM76SkDyn9LtTFLxPUcaEPZv6D6bws,2141
110
111
  dodal/devices/util/motor_utils.py,sha256=pNY-aUk9LxaIWeDr5rpMS6udiB9j19wcCXkNDLp1uA0,257
111
112
  dodal/devices/xspress3/xspress3.py,sha256=29elzI3JtceryKeMWXhcP9nWl0tlSdnTZhltCitet6A,4668
@@ -116,9 +117,9 @@ dodal/devices/zocalo/zocalo_results.py,sha256=U4Vk4OF-eL8w0BR-fbw3k4jyRo6G3Ywaf8
116
117
  dodal/parameters/experiment_parameter_base.py,sha256=O7JamfuJ5cYHkPf9tsHJPqn-OMHTAGouigvM1cDFehE,313
117
118
  dodal/plans/check_topup.py,sha256=Pj6Eu8fa6nvoW4awrMxvzE_ftpLfYz8bN0QDLRw0Yuk,2989
118
119
  dodal/plans/data_session_metadata.py,sha256=QNx9rb1EfGBHb21eFekIi7KjNhC0PL-SVKBCggDuNeg,1650
119
- dls_dodal-1.29.2.dist-info/LICENSE,sha256=tAkwu8-AdEyGxGoSvJ2gVmQdcicWw3j1ZZueVV74M-E,11357
120
- dls_dodal-1.29.2.dist-info/METADATA,sha256=CGmJ2TuJkSbjLoFhxLZpWn-BkN5zmeGzwzpFtFM1jTU,16942
121
- dls_dodal-1.29.2.dist-info/WHEEL,sha256=y4mX-SOX4fYIkonsAGA5N0Oy-8_gI4FXw5HNI1xqvWg,91
122
- dls_dodal-1.29.2.dist-info/entry_points.txt,sha256=wpzz9FsTiYxI8OBwLKX9V9ResLwThBSmtRMcPwII0FA,46
123
- dls_dodal-1.29.2.dist-info/top_level.txt,sha256=xIozdmZk_wmMV4wugpq9-6eZs0vgADNUKz3j2UAwlhc,6
124
- dls_dodal-1.29.2.dist-info/RECORD,,
120
+ dls_dodal-1.29.4.dist-info/LICENSE,sha256=tAkwu8-AdEyGxGoSvJ2gVmQdcicWw3j1ZZueVV74M-E,11357
121
+ dls_dodal-1.29.4.dist-info/METADATA,sha256=biJK1f1BESw8LgPh4Rl0GrxC9cMJ2WCYxP5NqGPBzak,16950
122
+ dls_dodal-1.29.4.dist-info/WHEEL,sha256=-oYQCr74JF3a37z2nRlQays_SX2MqOANoqVjBBAP2yE,91
123
+ dls_dodal-1.29.4.dist-info/entry_points.txt,sha256=wpzz9FsTiYxI8OBwLKX9V9ResLwThBSmtRMcPwII0FA,46
124
+ dls_dodal-1.29.4.dist-info/top_level.txt,sha256=xIozdmZk_wmMV4wugpq9-6eZs0vgADNUKz3j2UAwlhc,6
125
+ dls_dodal-1.29.4.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (70.2.0)
2
+ Generator: setuptools (71.0.3)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
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.29.2'
16
- __version_tuple__ = version_tuple = (1, 29, 2)
15
+ __version__ = version = '1.29.4'
16
+ __version_tuple__ = version_tuple = (1, 29, 4)
dodal/beamlines/i24.py CHANGED
@@ -5,6 +5,7 @@ from dodal.devices.eiger import EigerDetector
5
5
  from dodal.devices.hutch_shutter import HutchShutter
6
6
  from dodal.devices.i24.aperture import Aperture
7
7
  from dodal.devices.i24.beamstop import Beamstop
8
+ from dodal.devices.i24.dcm import DCM
8
9
  from dodal.devices.i24.dual_backlight import DualBacklight
9
10
  from dodal.devices.i24.I24_detector_motion import DetectorMotion
10
11
  from dodal.devices.i24.i24_vgonio import VGonio
@@ -82,6 +83,19 @@ def detector_motion(
82
83
  )
83
84
 
84
85
 
86
+ def dcm(wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False) -> DCM:
87
+ """Get the i24 DCM device, instantiate it if it hasn't already been.
88
+ If this is called when already instantiated in i24, it will return the existing object.
89
+ """
90
+ return device_instantiation(
91
+ device_factory=DCM,
92
+ name="dcm",
93
+ prefix="",
94
+ wait=wait_for_connection,
95
+ fake=fake_with_ophyd_sim,
96
+ )
97
+
98
+
85
99
  @skip_device(lambda: BL == "s24")
86
100
  def eiger(
87
101
  wait_for_connection: bool = True,
@@ -8,7 +8,6 @@ from ophyd import Component, Device, DeviceStatus, EpicsSignal, EpicsSignalRO, S
8
8
  from PIL import Image, ImageDraw
9
9
 
10
10
  from dodal.devices.oav.oav_parameters import OAVConfigParams
11
- from dodal.devices.oav.utils import save_thumbnail
12
11
  from dodal.log import LOGGER
13
12
 
14
13
 
@@ -50,9 +49,6 @@ class MJPG(Device, ABC):
50
49
 
51
50
  LOGGER.info(f"Saving image to {path}")
52
51
  image.save(path)
53
-
54
- save_thumbnail(Path(path), image)
55
-
56
52
  self.last_saved_path.put(path)
57
53
 
58
54
  def trigger(self):
@@ -48,7 +48,7 @@ class Attenuator(StandardReadable, Movable):
48
48
  super().__init__(name)
49
49
 
50
50
  @AsyncStatus.wrap
51
- async def set(self, transmission: float):
51
+ async def set(self, value: float):
52
52
  """Set the transmission to the fractional (0-1) value given.
53
53
 
54
54
  The attenuator IOC will then insert filters to reach the desired transmission for
@@ -58,8 +58,8 @@ class Attenuator(StandardReadable, Movable):
58
58
 
59
59
  LOGGER.debug("Using current energy ")
60
60
  await self._use_current_energy.trigger()
61
- LOGGER.info(f"Setting desired transmission to {transmission}")
62
- await self._desired_transmission.set(transmission)
61
+ LOGGER.info(f"Setting desired transmission to {value}")
62
+ await self._desired_transmission.set(value)
63
63
  LOGGER.debug("Sending change filter command")
64
64
  await self._change.trigger()
65
65
 
@@ -67,7 +67,7 @@ class Attenuator(StandardReadable, Movable):
67
67
  *[
68
68
  wait_for_value(
69
69
  self._filters_in_position[i],
70
- await self._calculated_filter_states[i].get_value(),
70
+ bool(await self._calculated_filter_states[i].get_value()),
71
71
  None,
72
72
  )
73
73
  for i in range(16)
@@ -54,13 +54,14 @@ class DetectorParams(BaseModel):
54
54
  DetectorSizeConstants: lambda d: d.det_type_string,
55
55
  }
56
56
 
57
- @root_validator(pre=True, skip_on_failure=True) # type: ignore # should be replaced with model_validator once move to pydantic 2 is complete
57
+ # should be replaced with model_validator once move to pydantic 2 is complete
58
+ @root_validator(pre=True)
58
59
  def create_beamxy_and_runnumber(cls, values: dict[str, Any]) -> dict[str, Any]:
59
60
  values["beam_xy_converter"] = DetectorDistanceToBeamXYConverter(
60
61
  values["det_dist_to_beam_converter_path"]
61
62
  )
62
63
  if values.get("run_number") is None:
63
- values["run_number"] = get_run_number(values["directory"])
64
+ values["run_number"] = get_run_number(values["directory"], values["prefix"])
64
65
  return values
65
66
 
66
67
  @validator("detector_size_constants", pre=True)
dodal/devices/eiger.py CHANGED
@@ -2,7 +2,7 @@ from enum import Enum
2
2
 
3
3
  from ophyd import Component, Device, EpicsSignalRO, Signal
4
4
  from ophyd.areadetector.cam import EigerDetectorCam
5
- from ophyd.status import AndStatus, Status, SubscriptionStatus
5
+ from ophyd.status import AndStatus, Status, StatusBase
6
6
 
7
7
  from dodal.devices.detector import DetectorParams, TriggerMode
8
8
  from dodal.devices.eiger_odin import EigerOdin
@@ -27,6 +27,7 @@ class InternalEigerTriggerMode(Enum):
27
27
  class EigerDetector(Device):
28
28
  class ArmingSignal(Signal):
29
29
  def set(self, value, *, timeout=None, settle_time=None, **kwargs):
30
+ assert isinstance(self.parent, EigerDetector)
30
31
  return self.parent.async_stage()
31
32
 
32
33
  do_arm = Component(ArmingSignal)
@@ -38,10 +39,12 @@ class EigerDetector(Device):
38
39
 
39
40
  STALE_PARAMS_TIMEOUT = 60
40
41
  GENERAL_STATUS_TIMEOUT = 10
42
+ # Long timeout for meta file to compensate for filesystem issues
43
+ META_FILE_READY_TIMEOUT = 30
41
44
  ALL_FRAMES_TIMEOUT = 120
42
45
  ARMING_TIMEOUT = 60
43
46
 
44
- filewriters_finished: SubscriptionStatus
47
+ filewriters_finished: StatusBase
45
48
 
46
49
  detector_params: DetectorParams | None = None
47
50
 
@@ -155,7 +158,7 @@ class EigerDetector(Device):
155
158
  def enable_roi_mode(self):
156
159
  return self.change_roi_mode(True)
157
160
 
158
- def change_roi_mode(self, enable: bool) -> Status:
161
+ def change_roi_mode(self, enable: bool) -> StatusBase:
159
162
  assert self.detector_params is not None
160
163
  detector_dimensions = (
161
164
  self.detector_params.detector_size_constants.roi_size_pixels
@@ -206,7 +209,7 @@ class EigerDetector(Device):
206
209
  )
207
210
  return status
208
211
 
209
- def set_odin_pvs(self) -> Status:
212
+ def set_odin_pvs(self) -> StatusBase:
210
213
  assert self.detector_params is not None
211
214
  file_prefix = self.detector_params.full_filename
212
215
  status = self.odin.file_writer.file_path.set(
@@ -264,7 +267,7 @@ class EigerDetector(Device):
264
267
  status.set_finished()
265
268
  return status
266
269
 
267
- def set_num_triggers_and_captures(self) -> Status:
270
+ def set_num_triggers_and_captures(self) -> StatusBase:
268
271
  """Sets the number of triggers and the number of images for the Eiger to capture
269
272
  during the datacollection. The number of images is the number of images per
270
273
  trigger.
@@ -295,7 +298,7 @@ class EigerDetector(Device):
295
298
 
296
299
  return status
297
300
 
298
- def _wait_for_odin_status(self) -> Status:
301
+ def _wait_for_odin_status(self) -> StatusBase:
299
302
  self.forward_bit_depth_to_filewriter()
300
303
  await_value(self.odin.meta.active, 1).wait(self.GENERAL_STATUS_TIMEOUT)
301
304
 
@@ -304,11 +307,11 @@ class EigerDetector(Device):
304
307
  )
305
308
  LOGGER.info("Eiger staging: awaiting odin metadata")
306
309
  status &= await_value(
307
- self.odin.meta.ready, 1, timeout=self.GENERAL_STATUS_TIMEOUT
310
+ self.odin.meta.ready, 1, timeout=self.META_FILE_READY_TIMEOUT
308
311
  )
309
312
  return status
310
313
 
311
- def _wait_fan_ready(self) -> Status:
314
+ def _wait_fan_ready(self) -> StatusBase:
312
315
  self.filewriters_finished = self.odin.create_finished_status()
313
316
  LOGGER.info("Eiger staging: awaiting odin fan ready")
314
317
  return await_value(self.odin.fan.ready, 1, self.GENERAL_STATUS_TIMEOUT)
@@ -332,6 +335,7 @@ class EigerDetector(Device):
332
335
 
333
336
  def do_arming_chain(self) -> Status:
334
337
  functions_to_do_arm = []
338
+ assert self.detector_params
335
339
  detector_params: DetectorParams = self.detector_params
336
340
  if detector_params.use_roi_mode:
337
341
  functions_to_do_arm.append(self.enable_roi_mode)
@@ -3,7 +3,7 @@ from typing import List, Tuple
3
3
  from ophyd import Component, Device, EpicsSignal, EpicsSignalRO, EpicsSignalWithRBV
4
4
  from ophyd.areadetector.plugins import HDF5Plugin_V22
5
5
  from ophyd.sim import NullStatus
6
- from ophyd.status import Status, SubscriptionStatus
6
+ from ophyd.status import StatusBase
7
7
 
8
8
  from dodal.devices.status import await_value
9
9
 
@@ -120,7 +120,7 @@ class EigerOdin(Device):
120
120
  meta = Component(OdinMetaListener, "OD:META:")
121
121
  nodes = Component(OdinNodesStatus, "")
122
122
 
123
- def create_finished_status(self) -> SubscriptionStatus:
123
+ def create_finished_status(self) -> StatusBase:
124
124
  writing_finished = await_value(self.meta.ready, 0)
125
125
  for node_pv in self.nodes.nodes:
126
126
  writing_finished &= await_value(node_pv.writing, 0)
@@ -157,7 +157,7 @@ class EigerOdin(Device):
157
157
 
158
158
  return not errors, "\n".join(errors)
159
159
 
160
- def stop(self) -> Status:
160
+ def stop(self) -> StatusBase:
161
161
  """Stop odin manually"""
162
162
  status = self.file_writer.capture.set(0)
163
163
  status &= self.meta.stop_writing.set(1)
@@ -1,12 +1,16 @@
1
- from enum import Enum, IntEnum
2
- from typing import Any
3
-
4
- from ophyd import Component, Device, EpicsSignal
5
- from ophyd.status import Status, StatusBase
6
- from ophyd_async.core import StandardReadable
1
+ from enum import Enum
2
+
3
+ from ophyd_async.core import (
4
+ AsyncStatus,
5
+ Device,
6
+ DeviceVector,
7
+ StandardReadable,
8
+ observe_value,
9
+ )
7
10
  from ophyd_async.core.signal import soft_signal_r_and_setter
8
11
  from ophyd_async.epics.motion import Motor
9
12
  from ophyd_async.epics.signal import (
13
+ epics_signal_r,
10
14
  epics_signal_rw,
11
15
  epics_signal_x,
12
16
  )
@@ -32,11 +36,11 @@ class MirrorStripe(str, Enum):
32
36
  PLATINUM = "Platinum"
33
37
 
34
38
 
35
- class MirrorVoltageDemand(IntEnum):
36
- N_A = 0
37
- OK = 1
38
- FAIL = 2
39
- SLEW = 3
39
+ class MirrorVoltageDemand(str, Enum):
40
+ N_A = "N/A"
41
+ OK = "OK"
42
+ FAIL = "FAIL"
43
+ SLEW = "SLEW"
40
44
 
41
45
 
42
46
  class MirrorVoltageDevice(Device):
@@ -44,14 +48,16 @@ class MirrorVoltageDevice(Device):
44
48
  the demanded voltage setpoint is accepted, without blocking the caller as this process can take significant time.
45
49
  """
46
50
 
47
- _actual_v: EpicsSignal = Component(EpicsSignal, "R")
48
- _setpoint_v: EpicsSignal = Component(EpicsSignal, "D")
49
- _demand_accepted: EpicsSignal = Component(EpicsSignal, "DSEV")
51
+ def __init__(self, name: str = "", prefix: str = ""):
52
+ self._actual_v = epics_signal_r(int, prefix + "R")
53
+ self._setpoint_v = epics_signal_rw(int, prefix + "D")
54
+ self._demand_accepted = epics_signal_r(MirrorVoltageDemand, prefix + "DSEV")
55
+ super().__init__(name=name)
50
56
 
51
- def set(self, value, *args, **kwargs) -> StatusBase:
57
+ @AsyncStatus.wrap
58
+ async def set(self, value, *args, **kwargs):
52
59
  """Combine the following operations into a single set:
53
60
  1. apply the value to the setpoint PV
54
- 2. Return to the caller with a Status future
55
61
  3. Wait until demand is accepted
56
62
  4. When either demand is accepted or DEFAULT_SETTLE_TIME expires, signal the result on the Status
57
63
  """
@@ -59,66 +65,60 @@ class MirrorVoltageDevice(Device):
59
65
  setpoint_v = self._setpoint_v
60
66
  demand_accepted = self._demand_accepted
61
67
 
62
- if demand_accepted.get() != MirrorVoltageDemand.OK:
68
+ if await demand_accepted.get_value() != MirrorVoltageDemand.OK:
63
69
  raise AssertionError(
64
70
  f"Attempted to set {setpoint_v.name} when demand is not accepted."
65
71
  )
66
72
 
67
- if setpoint_v.get() == value:
73
+ if await setpoint_v.get_value() == value:
68
74
  LOGGER.debug(f"{setpoint_v.name} already at {value} - skipping set")
69
- return Status(success=True, done=True)
75
+ return
70
76
 
71
77
  LOGGER.debug(f"setting {setpoint_v.name} to {value}")
72
- demand_accepted_status = Status(self, DEFAULT_SETTLE_TIME_S)
73
-
74
- subscription: dict[str, Any] = {"handle": None}
75
78
 
76
- def demand_check_callback(old_value, value, **kwargs):
77
- LOGGER.debug(f"Got event old={old_value} new={value} for {setpoint_v.name}")
78
- if old_value != MirrorVoltageDemand.OK and value == MirrorVoltageDemand.OK:
79
- LOGGER.debug(f"Demand accepted for {setpoint_v.name}")
80
- subs_handle = subscription.pop("handle", None)
81
- if subs_handle is None:
82
- raise AssertionError("Demand accepted before set attempted")
83
- demand_accepted.unsubscribe(subs_handle)
84
-
85
- demand_accepted_status.set_finished()
86
- # else timeout handled by parent demand_accepted_status
79
+ # Register an observer up front to ensure we don't miss events after we
80
+ # perform the set
81
+ demand_accepted_iterator = observe_value(
82
+ demand_accepted, timeout=DEFAULT_SETTLE_TIME_S
83
+ )
84
+ # discard the current value (OK) so we can await a subsequent change
85
+ await anext(demand_accepted_iterator)
86
+ await setpoint_v.set(value)
87
+
88
+ # The set should always change to SLEW regardless of whether we are
89
+ # already at the set point, then change back to OK/FAIL depending on
90
+ # success
91
+ accepted_value = await anext(demand_accepted_iterator)
92
+ assert accepted_value == MirrorVoltageDemand.SLEW
93
+ LOGGER.debug(
94
+ f"Demand not accepted for {setpoint_v.name}, waiting for acceptance..."
95
+ )
96
+ while MirrorVoltageDemand.SLEW == (
97
+ accepted_value := await anext(demand_accepted_iterator)
98
+ ):
99
+ pass
87
100
 
88
- subscription["handle"] = demand_accepted.subscribe(demand_check_callback)
89
- setpoint_status = setpoint_v.set(value)
90
- status = setpoint_status & demand_accepted_status
91
- return status
101
+ if accepted_value != MirrorVoltageDemand.OK:
102
+ raise AssertionError(
103
+ f"Voltage slew failed for {setpoint_v.name}, new state={accepted_value}"
104
+ )
92
105
 
93
106
 
94
- class VFMMirrorVoltages(Device):
95
- def __init__(self, *args, daq_configuration_path: str, **kwargs):
96
- super().__init__(*args, **kwargs)
107
+ class VFMMirrorVoltages(StandardReadable):
108
+ def __init__(
109
+ self, name: str, prefix: str, *args, daq_configuration_path: str, **kwargs
110
+ ):
97
111
  self.voltage_lookup_table_path = (
98
112
  daq_configuration_path + "/json/mirrorFocus.json"
99
113
  )
100
-
101
- _channel14_voltage_device = Component(MirrorVoltageDevice, "BM:V14")
102
- _channel15_voltage_device = Component(MirrorVoltageDevice, "BM:V15")
103
- _channel16_voltage_device = Component(MirrorVoltageDevice, "BM:V16")
104
- _channel17_voltage_device = Component(MirrorVoltageDevice, "BM:V17")
105
- _channel18_voltage_device = Component(MirrorVoltageDevice, "BM:V18")
106
- _channel19_voltage_device = Component(MirrorVoltageDevice, "BM:V19")
107
- _channel20_voltage_device = Component(MirrorVoltageDevice, "BM:V20")
108
- _channel21_voltage_device = Component(MirrorVoltageDevice, "BM:V21")
109
-
110
- @property
111
- def voltage_channels(self) -> list[MirrorVoltageDevice]:
112
- return [
113
- self._channel14_voltage_device,
114
- self._channel15_voltage_device,
115
- self._channel16_voltage_device,
116
- self._channel17_voltage_device,
117
- self._channel18_voltage_device,
118
- self._channel19_voltage_device,
119
- self._channel20_voltage_device,
120
- self._channel21_voltage_device,
121
- ]
114
+ with self.add_children_as_readables():
115
+ self.voltage_channels = DeviceVector(
116
+ {
117
+ i - 14: MirrorVoltageDevice(prefix=f"{prefix}BM:V{i}")
118
+ for i in range(14, 22)
119
+ }
120
+ )
121
+ super().__init__(*args, name=name, **kwargs)
122
122
 
123
123
 
124
124
  class FocusingMirror(StandardReadable):
@@ -0,0 +1,42 @@
1
+ from ophyd_async.core import StandardReadable
2
+ from ophyd_async.epics.motion import Motor
3
+ from ophyd_async.epics.signal import epics_signal_r
4
+
5
+
6
+ class DCM(StandardReadable):
7
+ """
8
+ A double crystal monocromator device, used to select the beam energy.
9
+ """
10
+
11
+ def __init__(self, prefix: str, name: str = "") -> None:
12
+ with self.add_children_as_readables():
13
+ # Motors
14
+ self.bragg_in_degrees = Motor(prefix + "-MO-DCM-01:BRAGG")
15
+ self.x_translation_in_mm = Motor(prefix + "-MO-DCM-01:X")
16
+ self.offset_in_mm = Motor(prefix + "-MO-DCM-01:OFFSET")
17
+ self.gap_in_mm = Motor(prefix + "-MO-DCM-01:GAP")
18
+ self.energy_in_kev = Motor(prefix + "-MO-DCM-01:ENERGY")
19
+ self.xtal1_roll = Motor(prefix + "-MO-DCM-01:XTAL1:ROLL")
20
+ self.xtal2_roll = Motor(prefix + "-MO-DCM-01:XTAL2:ROLL")
21
+ self.xtal2_pitch = Motor(prefix + "-MO-DCM-01:XTAL2:PITCH")
22
+
23
+ # Wavelength is calculated in epics from the energy
24
+ self.wavelength_in_a = epics_signal_r(float, prefix + "-MO-DCM-01:LAMBDA")
25
+
26
+ # Temperatures
27
+ self.xtal1_temp = epics_signal_r(float, prefix + "-DI-DCM-01:PT100-1")
28
+ self.xtal1_heater_temp = epics_signal_r(
29
+ float, prefix + "-DI-DCM-01:PT100-2"
30
+ )
31
+ self.xtal2_temp = epics_signal_r(float, prefix + "-DI-DCM-01:PT100-4")
32
+ self.xtal2_heater_temp = epics_signal_r(
33
+ float, prefix + "-DI-DCM-01:PT100-5"
34
+ )
35
+
36
+ self.roll_plate_temp = epics_signal_r(float, prefix + "-DI-DCM-01:PT100-3")
37
+ self.pitch_plate_temp = epics_signal_r(float, prefix + "-DI-DCM-01:PT100-6")
38
+ self.backplate_temp = epics_signal_r(float, prefix + "-DI-DCM-01:PT100-7")
39
+ self.b1_plate_temp = epics_signal_r(float, prefix + "-DI-DCM-01:PT100-7")
40
+ self.gap_temp = epics_signal_r(float, prefix + "-DI-DCM-01:TC-1")
41
+
42
+ super().__init__(name)
@@ -46,6 +46,7 @@ class ZoomController(Device):
46
46
  sxst = Component(EpicsSignal, "MP:SELECT.SXST")
47
47
 
48
48
  def set_flatfield_on_zoom_level_one(self, value):
49
+ self.parent: "OAV"
49
50
  flat_applied = self.parent.proc.port_name.get()
50
51
  no_flat_applied = self.parent.cam.port_name.get()
51
52
  return self.parent.grid_snapshot.input_plugin.set(
@@ -50,11 +50,13 @@ class PinTipDetection(StandardReadable):
50
50
  self._prefix: str = prefix
51
51
  self._name = name
52
52
 
53
- self.triggered_tip, _ = soft_signal_r_and_setter(Tip, name="triggered_tip")
54
- self.triggered_top_edge, _ = soft_signal_r_and_setter(
53
+ self.triggered_tip, self._tip_setter = soft_signal_r_and_setter(
54
+ Tip, name="triggered_tip"
55
+ )
56
+ self.triggered_top_edge, self._top_edge_setter = soft_signal_r_and_setter(
55
57
  NDArray[np.uint32], name="triggered_top_edge"
56
58
  )
57
- self.triggered_bottom_edge, _ = soft_signal_r_and_setter(
59
+ self.triggered_bottom_edge, self._bottom_edge_setter = soft_signal_r_and_setter(
58
60
  NDArray[np.uint32], name="triggered_bottom_edge"
59
61
  )
60
62
  self.array_data = epics_signal_r(NDArray[np.uint8], f"pva://{prefix}PVA:ARRAY")
@@ -85,14 +87,14 @@ class PinTipDetection(StandardReadable):
85
87
 
86
88
  super().__init__(name=name)
87
89
 
88
- async def _set_triggered_values(self, results: SampleLocation):
90
+ def _set_triggered_values(self, results: SampleLocation):
89
91
  tip = (results.tip_x, results.tip_y)
90
92
  if tip == self.INVALID_POSITION:
91
93
  raise InvalidPinException
92
94
  else:
93
- await self.triggered_tip._backend.put(tip)
94
- await self.triggered_top_edge._backend.put(results.edge_top)
95
- await self.triggered_bottom_edge._backend.put(results.edge_bottom)
95
+ self._tip_setter(tip)
96
+ self._top_edge_setter(results.edge_top)
97
+ self._bottom_edge_setter(results.edge_bottom)
96
98
 
97
99
  async def _get_tip_and_edge_data(
98
100
  self, array_data: NDArray[np.uint8]
@@ -150,7 +152,7 @@ class PinTipDetection(StandardReadable):
150
152
  async for value in observe_value(self.array_data):
151
153
  try:
152
154
  location = await self._get_tip_and_edge_data(value)
153
- await self._set_triggered_values(location)
155
+ self._set_triggered_values(location)
154
156
  except Exception as e:
155
157
  LOGGER.warn(
156
158
  f"Failed to detect pin-tip location, will retry with next image: {e}"
@@ -166,6 +168,6 @@ class PinTipDetection(StandardReadable):
166
168
  LOGGER.error(
167
169
  f"No tip found in {await self.validity_timeout.get_value()} seconds."
168
170
  )
169
- await self.triggered_tip._backend.put(self.INVALID_POSITION)
170
- await self.triggered_bottom_edge._backend.put(np.array([]))
171
- await self.triggered_top_edge._backend.put(np.array([]))
171
+ self._tip_setter(self.INVALID_POSITION)
172
+ self._bottom_edge_setter(np.array([]))
173
+ self._top_edge_setter(np.array([]))
@@ -1,17 +1,14 @@
1
1
  from enum import IntEnum
2
- from pathlib import Path
3
2
  from typing import Generator, Tuple
4
3
 
5
4
  import bluesky.plan_stubs as bps
6
5
  import numpy as np
7
6
  from bluesky.utils import Msg
8
- from PIL.Image import Image
9
7
 
10
8
  from dodal.devices.oav.oav_calculations import camera_coordinates_to_xyz
11
- from dodal.devices.oav.oav_parameters import OAVConfigParams
9
+ from dodal.devices.oav.oav_detector import OAVConfigParams
12
10
  from dodal.devices.oav.pin_image_recognition import PinTipDetection
13
11
  from dodal.devices.smargon import Smargon
14
- from dodal.log import LOGGER
15
12
 
16
13
  Pixel = Tuple[int, int]
17
14
 
@@ -110,14 +107,3 @@ def wait_for_tip_to_be_found(
110
107
  raise PinNotFoundException(f"No pin found after {timeout} seconds")
111
108
 
112
109
  return found_tip # type: ignore
113
-
114
-
115
- def save_thumbnail(full_file_path: Path, full_image: Image, new_height=192):
116
- """Scales an image down to have the height specified in new_height and saves it
117
- to the same location as the full image with a t appended to the filename"""
118
- thumbnail_path = full_file_path.with_stem(full_file_path.stem + "t")
119
- LOGGER.info(f"Saving thumbnail to {thumbnail_path}")
120
- full_size = full_image.size
121
- new_width = (new_height / full_size[1]) * full_size[0]
122
- full_image.thumbnail((new_width, new_height))
123
- full_image.save(thumbnail_path.as_posix())
dodal/devices/robot.py CHANGED
@@ -60,7 +60,8 @@ class BartRobot(StandardReadable, Movable):
60
60
  self.program_running = epics_signal_r(bool, prefix + "PROGRAM_RUNNING")
61
61
  self.program_name = epics_signal_r(str, prefix + "PROGRAM_NAME")
62
62
  self.error_str = epics_signal_r(str, prefix + "PRG_ERR_MSG")
63
- self.error_code = epics_signal_r(int, prefix + "PRG_ERR_CODE")
63
+ # Change error_code to int type when https://github.com/bluesky/ophyd-async/issues/280 released
64
+ self.error_code = epics_signal_r(float, prefix + "PRG_ERR_CODE")
64
65
  super().__init__(name=name)
65
66
 
66
67
  async def pin_mounted_or_no_pin_found(self):
@@ -1,5 +1,5 @@
1
1
  from functools import partial
2
- from typing import Callable
2
+ from typing import Callable, Sequence
3
3
 
4
4
  from bluesky.protocols import Movable
5
5
  from ophyd import Component, EpicsSignal
@@ -26,7 +26,7 @@ def epics_signal_put_wait(pv_name: str, wait: float = 3.0) -> Component[EpicsSig
26
26
 
27
27
 
28
28
  def run_functions_without_blocking(
29
- functions_to_chain: list[Callable[[], StatusBase]],
29
+ functions_to_chain: Sequence[Callable[[], StatusBase]],
30
30
  timeout: float = 60.0,
31
31
  associated_obj: OphydDevice | None = None,
32
32
  ) -> Status:
dodal/devices/webcam.py CHANGED
@@ -1,13 +1,10 @@
1
- import io
2
1
  from pathlib import Path
3
2
 
4
3
  import aiofiles
5
4
  from aiohttp import ClientSession
6
5
  from bluesky.protocols import Triggerable
7
6
  from ophyd_async.core import AsyncStatus, StandardReadable, soft_signal_rw
8
- from PIL import Image
9
7
 
10
- from dodal.devices.oav.utils import save_thumbnail
11
8
  from dodal.log import LOGGER
12
9
 
13
10
 
@@ -26,10 +23,8 @@ class Webcam(StandardReadable, Triggerable):
26
23
  async with session.get(self.url) as response:
27
24
  response.raise_for_status()
28
25
  LOGGER.info(f"Saving webcam image from {self.url} to {file_path}")
29
- data = await response.read()
30
26
  async with aiofiles.open(file_path, "wb") as file:
31
- await file.write(data)
32
- save_thumbnail(Path(file_path), Image.open(io.BytesIO(data)))
27
+ await file.write((await response.read()))
33
28
 
34
29
  @AsyncStatus.wrap
35
30
  async def trigger(self) -> None:
dodal/utils.py CHANGED
@@ -322,10 +322,15 @@ def _find_next_run_number_from_files(file_names: List[str]) -> int:
322
322
  return max(valid_numbers) + 1 if valid_numbers else 1
323
323
 
324
324
 
325
- def get_run_number(directory: str) -> int:
326
- """Looks at the numbers coming from all nexus files with the format "xxx_(any number}.nxs", and returns the highest number + 1,
327
- or 1 if there are no numbers found"""
328
- nexus_file_names = [file for file in os.listdir(directory) if file.endswith(".nxs")]
325
+ def get_run_number(directory: str, prefix: str = "") -> int:
326
+ """Looks at the numbers coming from all nexus files with the format
327
+ "{prefix}_(any number}.nxs", and returns the highest number + 1, or 1 if there are
328
+ no matching numbers found. If no prefix is given, considers all files in the dir."""
329
+ nexus_file_names = [
330
+ file
331
+ for file in os.listdir(directory)
332
+ if file.endswith(".nxs") and file.startswith(prefix)
333
+ ]
329
334
 
330
335
  if len(nexus_file_names) == 0:
331
336
  return 1