dls-dodal 1.68.0__py3-none-any.whl → 2.0.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.
Files changed (292) hide show
  1. {dls_dodal-1.68.0.dist-info → dls_dodal-2.0.0.dist-info}/METADATA +1 -31
  2. dls_dodal-2.0.0.dist-info/RECORD +354 -0
  3. {dls_dodal-1.68.0.dist-info → dls_dodal-2.0.0.dist-info}/WHEEL +1 -1
  4. dodal/_version.py +2 -2
  5. dodal/beamlines/__init__.py +10 -17
  6. dodal/beamlines/adsim.py +40 -33
  7. dodal/beamlines/b01_1.py +11 -0
  8. dodal/beamlines/b07.py +17 -21
  9. dodal/beamlines/b07_1.py +20 -22
  10. dodal/beamlines/b07_shared.py +12 -0
  11. dodal/beamlines/b16.py +1 -1
  12. dodal/beamlines/b21.py +15 -6
  13. dodal/beamlines/i02_1.py +17 -45
  14. dodal/beamlines/i02_2.py +6 -12
  15. dodal/beamlines/i03.py +8 -5
  16. dodal/beamlines/i03_supervisor.py +19 -0
  17. dodal/beamlines/i04.py +87 -184
  18. dodal/beamlines/i05.py +9 -39
  19. dodal/beamlines/i05_1.py +4 -13
  20. dodal/beamlines/i05_shared.py +51 -0
  21. dodal/beamlines/i06_1.py +26 -0
  22. dodal/beamlines/{i06.py → i06_shared.py} +25 -14
  23. dodal/beamlines/i07.py +14 -16
  24. dodal/beamlines/i09.py +76 -29
  25. dodal/beamlines/i09_1.py +25 -56
  26. dodal/beamlines/i09_1_shared.py +61 -0
  27. dodal/beamlines/i09_2.py +6 -100
  28. dodal/beamlines/i09_2_shared.py +110 -0
  29. dodal/beamlines/i10.py +60 -54
  30. dodal/beamlines/i10_1.py +99 -10
  31. dodal/beamlines/{i10_optics.py → i10_shared.py} +80 -66
  32. dodal/beamlines/i11.py +31 -18
  33. dodal/beamlines/i13_1.py +1 -1
  34. dodal/beamlines/i15.py +6 -6
  35. dodal/beamlines/i15_1.py +6 -6
  36. dodal/beamlines/i16.py +11 -0
  37. dodal/beamlines/i17.py +37 -28
  38. dodal/beamlines/i18.py +3 -4
  39. dodal/beamlines/i19_1.py +95 -34
  40. dodal/beamlines/i19_2.py +68 -52
  41. dodal/beamlines/i19_optics.py +26 -13
  42. dodal/beamlines/i20_1.py +17 -11
  43. dodal/beamlines/i21.py +44 -29
  44. dodal/beamlines/i22.py +19 -4
  45. dodal/beamlines/i23.py +20 -27
  46. dodal/beamlines/i24.py +64 -113
  47. dodal/beamlines/k07.py +99 -5
  48. dodal/beamlines/p38.py +3 -3
  49. dodal/beamlines/p60.py +35 -14
  50. dodal/beamlines/p99.py +16 -15
  51. dodal/beamlines/training_rig.py +20 -12
  52. dodal/cli.py +36 -2
  53. dodal/common/__init__.py +2 -1
  54. dodal/common/beamlines/beamline_parameters.py +2 -1
  55. dodal/common/beamlines/beamline_utils.py +11 -9
  56. dodal/common/beamlines/commissioning_mode.py +6 -3
  57. dodal/common/coordination.py +12 -14
  58. dodal/common/crystal_metadata.py +5 -8
  59. dodal/common/device_utils.py +4 -3
  60. dodal/common/maths.py +87 -19
  61. dodal/common/udc_directory_provider.py +13 -8
  62. dodal/common/visit.py +18 -21
  63. dodal/common/watcher_utils.py +13 -12
  64. dodal/device_manager.py +94 -54
  65. dodal/devices/aperturescatterguard.py +26 -27
  66. dodal/devices/areadetector/plugins/cam.py +1 -3
  67. dodal/devices/areadetector/plugins/mjpg.py +6 -5
  68. dodal/devices/attenuator/attenuator.py +12 -11
  69. dodal/devices/beamlines/b07/__init__.py +3 -0
  70. dodal/devices/{b07_1 → beamlines/b07_1}/__init__.py +2 -2
  71. dodal/devices/{b07_1 → beamlines/b07_1}/ccmc.py +5 -10
  72. dodal/devices/{b16 → beamlines/b16}/detector.py +2 -3
  73. dodal/devices/{i02_1 → beamlines/i02_1}/fast_grid_scan.py +2 -3
  74. dodal/devices/{i02_1 → beamlines/i02_1}/sample_motors.py +1 -1
  75. dodal/devices/{i03 → beamlines/i03}/beamsize.py +11 -7
  76. dodal/devices/{i03 → beamlines/i03}/dcm.py +1 -2
  77. dodal/devices/{i03 → beamlines/i03}/undulator_dcm.py +4 -5
  78. dodal/devices/beamlines/i04/beam_centre.py +151 -0
  79. dodal/devices/{i04 → beamlines/i04}/beamsize.py +11 -7
  80. dodal/devices/beamlines/i04/max_pixel.py +25 -0
  81. dodal/devices/{i04 → beamlines/i04}/murko_results.py +23 -8
  82. dodal/devices/{i04 → beamlines/i04}/transfocator.py +10 -15
  83. dodal/devices/beamlines/i05/__init__.py +3 -0
  84. dodal/devices/beamlines/i06_shared/__init__.py +3 -0
  85. dodal/devices/beamlines/i06_shared/i06_enum.py +7 -0
  86. dodal/devices/{i07 → beamlines/i07}/dcm.py +2 -3
  87. dodal/devices/{i07 → beamlines/i07}/id.py +8 -9
  88. dodal/devices/beamlines/i09/__init__.py +3 -0
  89. dodal/devices/{i09_1_shared → beamlines/i09_1_shared}/hard_energy.py +5 -6
  90. dodal/devices/{i09_1_shared → beamlines/i09_1_shared}/hard_undulator_functions.py +19 -16
  91. dodal/devices/{i10 → beamlines/i10}/diagnostics.py +4 -3
  92. dodal/devices/{i10 → beamlines/i10}/i10_apple2.py +37 -51
  93. dodal/devices/{i10 → beamlines/i10}/rasor/rasor_current_amp.py +1 -24
  94. dodal/devices/{i10 → beamlines/i10}/rasor/rasor_motors.py +2 -2
  95. dodal/devices/{i10 → beamlines/i10}/slits.py +5 -3
  96. dodal/devices/beamlines/i10_1/__init__.py +9 -0
  97. dodal/devices/beamlines/i10_1/electromagnet/magnet.py +16 -0
  98. dodal/devices/beamlines/i10_1/electromagnet/stages.py +14 -0
  99. dodal/devices/beamlines/i10_1/scaler_cards.py +13 -0
  100. dodal/devices/{i11 → beamlines/i11}/cyberstar_blower.py +1 -1
  101. dodal/devices/{i11 → beamlines/i11}/diff_stages.py +4 -6
  102. dodal/devices/{i11 → beamlines/i11}/mythen.py +3 -4
  103. dodal/devices/{i11 → beamlines/i11}/nx100robot.py +6 -6
  104. dodal/devices/{i11 → beamlines/i11}/spinner.py +1 -1
  105. dodal/devices/{i13_1 → beamlines/i13_1}/merlin.py +1 -1
  106. dodal/devices/{i15 → beamlines/i15}/dcm.py +1 -2
  107. dodal/devices/{i15 → beamlines/i15}/focussing_mirror.py +5 -5
  108. dodal/devices/{i15 → beamlines/i15}/jack.py +2 -2
  109. dodal/devices/{i15 → beamlines/i15}/multilayer_mirror.py +1 -1
  110. dodal/devices/{i17 → beamlines/i17}/i17_apple2.py +16 -22
  111. dodal/devices/{i18 → beamlines/i18}/diode.py +1 -1
  112. dodal/devices/{i19 → beamlines/i19}/access_controlled/attenuator_motor_squad.py +12 -8
  113. dodal/devices/{i19 → beamlines/i19}/access_controlled/blueapi_device.py +16 -15
  114. dodal/devices/beamlines/i19/access_controlled/piezo_control.py +72 -0
  115. dodal/devices/{i19 → beamlines/i19}/access_controlled/shutter.py +11 -9
  116. dodal/devices/{i19 → beamlines/i19}/backlight.py +3 -1
  117. dodal/devices/{i19 → beamlines/i19}/mapt_configuration.py +2 -1
  118. dodal/devices/{i19 → beamlines/i19}/pin_col_stages.py +11 -8
  119. dodal/devices/beamlines/i19/pin_tip.py +32 -0
  120. dodal/devices/beamlines/i21/__init__.py +3 -0
  121. dodal/devices/{i22 → beamlines/i22}/dcm.py +1 -2
  122. dodal/devices/{i22 → beamlines/i22}/fswitch.py +1 -3
  123. dodal/devices/{i22 → beamlines/i22}/nxsas.py +5 -4
  124. dodal/devices/{i24 → beamlines/i24}/beam_center.py +1 -1
  125. dodal/devices/{i24 → beamlines/i24}/beamstop.py +2 -2
  126. dodal/devices/{i24 → beamlines/i24}/commissioning_jungfrau.py +12 -12
  127. dodal/devices/{i24 → beamlines/i24}/dcm.py +1 -3
  128. dodal/devices/{i24 → beamlines/i24}/dual_backlight.py +3 -3
  129. dodal/devices/{i24 → beamlines/i24}/pmac.py +9 -7
  130. dodal/devices/{p60 → beamlines/p60}/lab_xray_source.py +1 -1
  131. dodal/devices/beamlines/p99/__init__.py +0 -0
  132. dodal/devices/{p99 → beamlines/p99}/andor2_point.py +11 -15
  133. dodal/devices/bimorph_mirror.py +22 -20
  134. dodal/devices/collimation_table.py +3 -2
  135. dodal/devices/common_dcm.py +30 -20
  136. dodal/devices/controllers.py +2 -2
  137. dodal/devices/cryostream.py +8 -0
  138. dodal/devices/current_amplifiers/current_amplifier.py +16 -18
  139. dodal/devices/current_amplifiers/current_amplifier_detector.py +9 -10
  140. dodal/devices/current_amplifiers/femto.py +8 -9
  141. dodal/devices/current_amplifiers/sr570.py +16 -16
  142. dodal/devices/current_amplifiers/struck_scaler_counter.py +5 -5
  143. dodal/devices/detector/det_resolution.py +9 -8
  144. dodal/devices/detector/detector.py +4 -2
  145. dodal/devices/diamond_filter.py +3 -4
  146. dodal/devices/eiger.py +32 -17
  147. dodal/devices/eiger_odin.py +1 -1
  148. dodal/devices/electron_analyser/base/__init__.py +3 -3
  149. dodal/devices/electron_analyser/base/base_controller.py +32 -21
  150. dodal/devices/electron_analyser/base/base_detector.py +15 -20
  151. dodal/devices/electron_analyser/base/base_driver_io.py +39 -46
  152. dodal/devices/electron_analyser/base/base_enums.py +0 -5
  153. dodal/devices/electron_analyser/base/base_region.py +29 -31
  154. dodal/devices/electron_analyser/base/base_util.py +18 -16
  155. dodal/devices/electron_analyser/base/energy_sources.py +35 -40
  156. dodal/devices/electron_analyser/specs/specs_detector.py +7 -6
  157. dodal/devices/electron_analyser/vgscienta/vgscienta_detector.py +7 -6
  158. dodal/devices/eurotherm.py +3 -2
  159. dodal/devices/fast_grid_scan.py +31 -34
  160. dodal/devices/fast_shutter.py +125 -39
  161. dodal/devices/flux.py +1 -1
  162. dodal/devices/focusing_mirror.py +29 -11
  163. dodal/devices/hutch_shutter.py +6 -6
  164. dodal/devices/insertion_device/__init__.py +20 -8
  165. dodal/devices/insertion_device/apple2_controller.py +371 -0
  166. dodal/devices/insertion_device/apple2_undulator.py +184 -587
  167. dodal/devices/insertion_device/apple_knot_controller.py +222 -0
  168. dodal/devices/insertion_device/energy.py +161 -0
  169. dodal/devices/insertion_device/energy_motor_lookup.py +21 -28
  170. dodal/devices/insertion_device/lookup_table_models.py +47 -52
  171. dodal/devices/insertion_device/polarisation.py +36 -0
  172. dodal/devices/ipin.py +1 -1
  173. dodal/devices/linkam3.py +7 -5
  174. dodal/devices/motors.py +107 -19
  175. dodal/devices/mx_phase1/beamstop.py +2 -4
  176. dodal/devices/oav/oav_calculations.py +20 -13
  177. dodal/devices/oav/oav_detector.py +92 -22
  178. dodal/devices/oav/oav_parameters.py +4 -9
  179. dodal/devices/oav/oav_to_redis_forwarder.py +22 -18
  180. dodal/devices/oav/pin_image_recognition/__init__.py +4 -6
  181. dodal/devices/oav/pin_image_recognition/manual_test.py +1 -2
  182. dodal/devices/oav/pin_image_recognition/utils.py +30 -32
  183. dodal/devices/oav/snapshots/grid_overlay.py +10 -9
  184. dodal/devices/oav/snapshots/snapshot_image_processing.py +15 -13
  185. dodal/devices/oav/utils.py +20 -6
  186. dodal/devices/p45.py +3 -9
  187. dodal/devices/pgm.py +8 -14
  188. dodal/devices/pressure_jump_cell.py +93 -32
  189. dodal/devices/qbpm.py +1 -3
  190. dodal/devices/robot.py +45 -20
  191. dodal/devices/s4_slit_gaps.py +1 -1
  192. dodal/devices/selectable_source.py +41 -0
  193. dodal/devices/slits.py +2 -5
  194. dodal/devices/smargon.py +2 -3
  195. dodal/devices/temperture_controller/lakeshore/lakeshore.py +38 -64
  196. dodal/devices/temperture_controller/lakeshore/lakeshore_io.py +21 -35
  197. dodal/devices/tetramm.py +7 -7
  198. dodal/devices/turbo_slit.py +8 -7
  199. dodal/devices/undulator.py +42 -56
  200. dodal/devices/util/adjuster_plans.py +2 -3
  201. dodal/devices/util/epics_util.py +10 -10
  202. dodal/devices/util/lookup_tables.py +17 -18
  203. dodal/devices/v2f.py +2 -3
  204. dodal/devices/watsonmarlow323_pump.py +1 -1
  205. dodal/devices/xbpm_feedback.py +3 -2
  206. dodal/devices/xspress3/xspress3.py +8 -11
  207. dodal/devices/xspress3/xspress3_channel.py +3 -6
  208. dodal/devices/zebra/zebra.py +21 -7
  209. dodal/devices/zebra/zebra_constants_mapping.py +12 -7
  210. dodal/devices/zebra/zebra_controlled_shutter.py +2 -1
  211. dodal/devices/zocalo/zocalo_interaction.py +14 -14
  212. dodal/devices/zocalo/zocalo_results.py +33 -33
  213. dodal/log.py +23 -20
  214. dodal/plan_stubs/check_topup.py +15 -15
  215. dodal/plan_stubs/data_session.py +6 -6
  216. dodal/plan_stubs/motor_utils.py +22 -18
  217. dodal/plan_stubs/pressure_jump_cell.py +18 -0
  218. dodal/plan_stubs/wrapped.py +40 -55
  219. dodal/plans/bimorph.py +63 -52
  220. dodal/plans/configure_arm_trigger_and_disarm_detector.py +0 -1
  221. dodal/plans/device_setup_plans/__init__.py +5 -0
  222. dodal/plans/device_setup_plans/setup_pin_tip_params.py +63 -0
  223. dodal/plans/preprocessors/verify_undulator_gap.py +10 -8
  224. dodal/plans/spec_path.py +3 -5
  225. dodal/plans/verify_undulator_gap.py +1 -2
  226. dodal/plans/wrapped.py +4 -3
  227. dodal/testing/__init__.py +0 -0
  228. dodal/testing/electron_analyser/device_factory.py +5 -7
  229. dodal/testing/fixtures/devices/apple2.py +38 -0
  230. dodal/testing/fixtures/run_engine.py +3 -7
  231. dodal/testing/fixtures/utils.py +1 -2
  232. dodal/utils.py +60 -58
  233. dls_dodal-1.68.0.dist-info/RECORD +0 -330
  234. dodal/beamline_specific_utils/i05_shared.py +0 -14
  235. dodal/devices/b07/__init__.py +0 -3
  236. dodal/devices/i04/max_pixel.py +0 -38
  237. dodal/devices/i05/__init__.py +0 -3
  238. dodal/devices/i09/__init__.py +0 -3
  239. dodal/devices/i21/__init__.py +0 -5
  240. {dls_dodal-1.68.0.dist-info → dls_dodal-2.0.0.dist-info}/entry_points.txt +0 -0
  241. {dls_dodal-1.68.0.dist-info → dls_dodal-2.0.0.dist-info}/licenses/LICENSE +0 -0
  242. {dls_dodal-1.68.0.dist-info → dls_dodal-2.0.0.dist-info}/top_level.txt +0 -0
  243. /dodal/{beamline_specific_utils → devices/beamlines}/__init__.py +0 -0
  244. /dodal/devices/{b07 → beamlines/b07}/enums.py +0 -0
  245. /dodal/devices/{b07_1 → beamlines/b07_1}/enums.py +0 -0
  246. /dodal/devices/{b16 → beamlines/b16}/__init__.py +0 -0
  247. /dodal/devices/{i02_1 → beamlines/i02_1}/__init__.py +0 -0
  248. /dodal/devices/{i02_2 → beamlines/i02_2}/__init__.py +0 -0
  249. /dodal/devices/{i03 → beamlines/i03}/__init__.py +0 -0
  250. /dodal/devices/{i03 → beamlines/i03}/constants.py +0 -0
  251. /dodal/devices/{i04 → beamlines/i04}/__init__.py +0 -0
  252. /dodal/devices/{i04 → beamlines/i04}/constants.py +0 -0
  253. /dodal/devices/{i05 → beamlines/i05}/enums.py +0 -0
  254. /dodal/devices/{i07 → beamlines/i07}/__init__.py +0 -0
  255. /dodal/devices/{i09 → beamlines/i09}/enums.py +0 -0
  256. /dodal/devices/{i09_1 → beamlines/i09_1}/__init__.py +0 -0
  257. /dodal/devices/{i09_1 → beamlines/i09_1}/enums.py +0 -0
  258. /dodal/devices/{i09_1_shared → beamlines/i09_1_shared}/__init__.py +0 -0
  259. /dodal/devices/{i09_2_shared → beamlines/i09_2_shared}/__init__.py +0 -0
  260. /dodal/devices/{i09_2_shared → beamlines/i09_2_shared}/i09_apple2.py +0 -0
  261. /dodal/devices/{i10 → beamlines/i10}/__init__.py +0 -0
  262. /dodal/devices/{i10 → beamlines/i10}/i10_setting_data.py +0 -0
  263. /dodal/devices/{i10 → beamlines/i10}/mirrors.py +0 -0
  264. /dodal/devices/{i10 → beamlines/i10}/rasor/__init__.py +0 -0
  265. /dodal/devices/{i10 → beamlines/i10}/rasor/rasor_scaler_cards.py +0 -0
  266. /dodal/devices/{i11 → beamlines/i10_1/electromagnet}/__init__.py +0 -0
  267. /dodal/devices/{i13_1 → beamlines/i11}/__init__.py +0 -0
  268. /dodal/devices/{i15 → beamlines/i13_1}/__init__.py +0 -0
  269. /dodal/devices/{i13_1 → beamlines/i13_1}/merlin_controller.py +0 -0
  270. /dodal/devices/{i17 → beamlines/i15}/__init__.py +0 -0
  271. /dodal/devices/{i15 → beamlines/i15}/laue.py +0 -0
  272. /dodal/devices/{i15 → beamlines/i15}/motors.py +0 -0
  273. /dodal/devices/{i15 → beamlines/i15}/rail.py +0 -0
  274. /dodal/devices/{i18 → beamlines/i17}/__init__.py +0 -0
  275. /dodal/devices/{i19 → beamlines/i18}/__init__.py +0 -0
  276. /dodal/devices/{i18 → beamlines/i18}/kb_mirror.py +0 -0
  277. /dodal/devices/{i19/access_controlled → beamlines/i19}/__init__.py +0 -0
  278. /dodal/devices/{i20_1 → beamlines/i19/access_controlled}/__init__.py +0 -0
  279. /dodal/devices/{i19 → beamlines/i19}/access_controlled/hutch_access.py +0 -0
  280. /dodal/devices/{i19 → beamlines/i19}/beamstop.py +0 -0
  281. /dodal/devices/{i19 → beamlines/i19}/diffractometer.py +0 -0
  282. /dodal/devices/{i22 → beamlines/i20_1}/__init__.py +0 -0
  283. /dodal/devices/{i21 → beamlines/i21}/enums.py +0 -0
  284. /dodal/devices/{i24 → beamlines/i22}/__init__.py +0 -0
  285. /dodal/devices/{p99 → beamlines/i24}/__init__.py +0 -0
  286. /dodal/devices/{i24 → beamlines/i24}/aperture.py +0 -0
  287. /dodal/devices/{i24 → beamlines/i24}/focus_mirrors.py +0 -0
  288. /dodal/devices/{i24 → beamlines/i24}/vgonio.py +0 -0
  289. /dodal/devices/{p60 → beamlines/p60}/__init__.py +0 -0
  290. /dodal/devices/{p60 → beamlines/p60}/enums.py +0 -0
  291. /dodal/devices/{p99 → beamlines/p99}/sample_stage.py +0 -0
  292. /dodal/devices/insertion_device/{id_enum.py → enum.py} +0 -0
@@ -10,10 +10,9 @@ from dodal.devices.controllers import ConstantDeadTimeController
10
10
 
11
11
 
12
12
  def software_triggered_tiff_area_detector(prefix: str, deadtime: float = 0.0):
13
- """
14
- Wrapper for AreaDetector with fixed dead time (defaulted to 0)
13
+ """Wrapper for AreaDetector with fixed dead time (defaulted to 0)
15
14
  and a TIFF file writer.
16
- Most detectors in B16 could be configured like this
15
+ Most detectors in B16 could be configured like this.
17
16
  """
18
17
  return AreaDetector(
19
18
  writer=ADTIFFWriter.with_io(
@@ -11,8 +11,7 @@ from dodal.log import LOGGER
11
11
 
12
12
 
13
13
  class ZebraGridScanParamsTwoD(GridScanParamsCommon, WithDwellTime):
14
- """
15
- Params for 2D Zebra FGS. Adds on the dwell time, which is really the time
14
+ """Params for 2D Zebra FGS. Adds on the dwell time, which is really the time
16
15
  between trigger positions.
17
16
  """
18
17
 
@@ -23,7 +22,7 @@ class ZebraFastGridScanTwoD(FastGridScanCommon[ZebraGridScanParamsTwoD]):
23
22
  - No Z steps, Z step sizes, or Y2 start positions, or Z2 start
24
23
  - No scan valid PV - see https://github.com/DiamondLightSource/mx-bluesky/issues/1203
25
24
  - No program_number - see https://github.com/DiamondLightSource/mx-bluesky/issues/1203
26
- """
25
+ """ # noqa D415
27
26
 
28
27
  def __init__(
29
28
  self, prefix: str, motion_controller_prefix: str, name: str = ""
@@ -3,7 +3,7 @@ from ophyd_async.epics.motor import Motor
3
3
 
4
4
 
5
5
  class SampleMotors(StandardReadable):
6
- """Virtual Smaract motors on i02-1 (VMXm)"""
6
+ """Virtual Smaract motors on i02-1 (VMXm)."""
7
7
 
8
8
  def __init__(
9
9
  self,
@@ -1,11 +1,15 @@
1
1
  from ophyd_async.core import Reference, derived_signal_r
2
2
 
3
3
  from dodal.devices.aperturescatterguard import ApertureScatterguard
4
+ from dodal.devices.beamlines.i03.constants import BeamsizeConstants
4
5
  from dodal.devices.beamsize.beamsize import BeamsizeBase
5
- from dodal.devices.i03.constants import BeamsizeConstants
6
6
 
7
7
 
8
8
  class Beamsize(BeamsizeBase):
9
+ """Device that calculates the size of the beam by taking the minimum of the beam
10
+ dimensions and the aperture scatterguard diameter.
11
+ """
12
+
9
13
  def __init__(self, aperture_scatterguard: ApertureScatterguard, name=""):
10
14
  super().__init__(name=name)
11
15
  self._aperture_scatterguard_ref = Reference(aperture_scatterguard)
@@ -13,23 +17,23 @@ class Beamsize(BeamsizeBase):
13
17
  with self.add_children_as_readables():
14
18
  self.x_um = derived_signal_r(
15
19
  self._get_beamsize_x,
16
- aperture_radius=self._aperture_scatterguard_ref().radius,
20
+ aperture_diameter=self._aperture_scatterguard_ref().diameter,
17
21
  derived_units="µm",
18
22
  )
19
23
  self.y_um = derived_signal_r(
20
24
  self._get_beamsize_y,
21
- aperture_radius=self._aperture_scatterguard_ref().radius,
25
+ aperture_diameter=self._aperture_scatterguard_ref().diameter,
22
26
  derived_units="µm",
23
27
  )
24
28
 
25
29
  def _get_beamsize_x(
26
30
  self,
27
- aperture_radius: float,
31
+ aperture_diameter: float,
28
32
  ) -> float:
29
- return min(aperture_radius, BeamsizeConstants.BEAM_WIDTH_UM)
33
+ return min(aperture_diameter, BeamsizeConstants.BEAM_WIDTH_UM)
30
34
 
31
35
  def _get_beamsize_y(
32
36
  self,
33
- aperture_radius: float,
37
+ aperture_diameter: float,
34
38
  ) -> float:
35
- return min(aperture_radius, BeamsizeConstants.BEAM_HEIGHT_UM)
39
+ return min(aperture_diameter, BeamsizeConstants.BEAM_HEIGHT_UM)
@@ -18,8 +18,7 @@ from dodal.devices.common_dcm import (
18
18
  class DCM(
19
19
  DoubleCrystalMonochromatorWithDSpacing[PitchAndRollCrystal, StationaryCrystal]
20
20
  ):
21
- """
22
- A double crystal monochromator (DCM), used to select the energy of the beam.
21
+ """A double crystal monochromator (DCM), used to select the energy of the beam.
23
22
 
24
23
  perp describes the gap between the 2 DCM crystals which has to change as you alter
25
24
  the angle to select the requested energy.
@@ -4,7 +4,7 @@ from bluesky.protocols import Movable
4
4
  from ophyd_async.core import AsyncStatus, Reference, StandardReadable
5
5
 
6
6
  from dodal.common.beamlines.beamline_parameters import get_beamline_parameters
7
- from dodal.devices.i03.dcm import DCM
7
+ from dodal.devices.beamlines.i03.dcm import DCM
8
8
  from dodal.devices.undulator import UndulatorInKeV
9
9
  from dodal.log import LOGGER
10
10
 
@@ -12,9 +12,8 @@ ENERGY_TIMEOUT_S: float = 30.0
12
12
 
13
13
 
14
14
  class UndulatorDCM(StandardReadable, Movable[float]):
15
- """
16
- Composite device to handle changing beamline energies, wraps the Undulator and the
17
- DCM. The DCM has a motor which controls the beam energy, when it moves, the
15
+ """Composite device to handle changing beamline energies, wraps the Undulator and
16
+ the DCM. The DCM has a motor which controls the beam energy, when it moves, the
18
17
  Undulator gap may also have to change to enable emission at the new energy.
19
18
  The relationship between the two motor motor positions is provided via a lookup
20
19
  table.
@@ -24,7 +23,7 @@ class UndulatorDCM(StandardReadable, Movable[float]):
24
23
  a comprehensive way to set beam energy.
25
24
 
26
25
  This class will be removed in the future. Use the separate Undulator and DCM devices
27
- instead. See https://github.com/DiamondLightSource/dodal/issues/1092
26
+ instead. See https://github.com/DiamondLightSource/dodal/issues/1092.
28
27
  """
29
28
 
30
29
  DCM_PERP_TOLERANCE = 0.01
@@ -0,0 +1,151 @@
1
+ import math
2
+
3
+ import cv2
4
+ import numpy as np
5
+ from bluesky.protocols import Triggerable
6
+ from ophyd_async.core import (
7
+ AsyncStatus,
8
+ StandardReadable,
9
+ soft_signal_r_and_setter,
10
+ soft_signal_rw,
11
+ )
12
+ from ophyd_async.epics.core import (
13
+ epics_signal_r,
14
+ )
15
+
16
+ from dodal.devices.oav.utils import convert_to_gray_and_blur
17
+ from dodal.log import LOGGER
18
+
19
+ # Constant was chosen from trial and error with test images
20
+ ADDITIONAL_BINARY_THRESH = 20
21
+
22
+
23
+ def convert_image_to_binary(image: np.ndarray):
24
+ """Creates a binary image from OAV image array data.
25
+
26
+ Pixels of the input image are converted to one of two values (a high and a low value).
27
+ Otsu's method is used for automatic thresholding.
28
+ See https://docs.opencv.org/4.x/d7/d4d/tutorial_py_thresholding.html.
29
+ The threshold is increased by ADDITIONAL_BINARY_THRESH in order to get more of
30
+ the centre of the beam.
31
+ """
32
+ max_pixel_value = 255
33
+
34
+ blurred_image = convert_to_gray_and_blur(image)
35
+
36
+ threshold_value, _ = cv2.threshold(
37
+ blurred_image, 0, max_pixel_value, cv2.THRESH_BINARY + cv2.THRESH_OTSU
38
+ )
39
+
40
+ # Adjusting because the inner beam is less noisy compared to the outer
41
+ threshold_value += ADDITIONAL_BINARY_THRESH
42
+
43
+ _, thresholded_image = cv2.threshold(
44
+ blurred_image, threshold_value, max_pixel_value, cv2.THRESH_BINARY
45
+ )
46
+
47
+ LOGGER.info(f"Image binarised with threshold of {threshold_value}")
48
+ return thresholded_image
49
+
50
+
51
+ def round_half_up(x):
52
+ return int(math.floor(x + 0.5))
53
+
54
+
55
+ def get_roi(
56
+ image_arr: np.ndarray,
57
+ centre_x: int,
58
+ centre_y: int,
59
+ box_width: int = 200,
60
+ box_height: int = 200,
61
+ ) -> tuple[np.ndarray, tuple[int, int], tuple[int, int]]:
62
+ """Creates an ROI image array from a full screen image array, given a centre for the
63
+ ROI image and a width and height of the ROI box. Note that if the centre of the ROI
64
+ box is close to the edge of the full screen image, the box may be smaller than the
65
+ width and height provided, as the ROI will be trimmed to fit inside the full screen
66
+ image.
67
+
68
+ Args:
69
+ image_arr (np.ndarray): The full screen image array.
70
+ centre_x (int): The x coordinate of the centre of the ROI box.
71
+ centre_y (int): The y coordinate of the centre of the ROI box.
72
+ box_width (int, optional): The width of the ROI box. Defaults to 200.
73
+ box_height (int, optional): The height of the ROI box. Defaults to 200.
74
+
75
+ Returns:
76
+ tuple[np.ndarray, tuple[int, int], tuple[int, int]]: The ROI array, and (x, y)
77
+ coordinates of the top left and bottom right corners of the ROI box.
78
+ """
79
+ height, width = image_arr.shape[:2]
80
+ x_dist = (box_width) / 2
81
+ y_dist = (box_height) / 2
82
+
83
+ # Clip coordinates to stay within bounds
84
+ x_min = max(round_half_up(centre_x - x_dist), 0)
85
+ x_max = min(round_half_up(centre_x + x_dist), width) - 1
86
+ y_min = max(round_half_up(centre_y - y_dist), 0)
87
+ y_max = min(round_half_up(centre_y + y_dist), height) - 1
88
+
89
+ roi_arr = image_arr[y_min : y_max + 1, x_min : x_max + 1]
90
+
91
+ return roi_arr, (x_min, y_min), (x_max, y_max)
92
+
93
+
94
+ class CentreEllipseMethod(StandardReadable, Triggerable):
95
+ """Upon triggering, fits an ellipse to a binary image from the area detector defined
96
+ by the prefix.
97
+
98
+ This is used, in conjunction with a scintillator, to determine the centre of the
99
+ beam on the image.
100
+ """
101
+
102
+ def __init__(self, prefix: str, overlay_channel: int = 1, name: str = ""):
103
+ self.oav_array_signal = epics_signal_r(np.ndarray, f"pva://{prefix}PVA:ARRAY")
104
+
105
+ self.center_x_val, self._center_x_val_setter = soft_signal_r_and_setter(float)
106
+ self.center_y_val, self._center_y_val_setter = soft_signal_r_and_setter(float)
107
+
108
+ self.current_centre_x = epics_signal_r(
109
+ int, f"{prefix}OVER:{overlay_channel}:CenterX"
110
+ )
111
+ self.current_centre_y = epics_signal_r(
112
+ int, f"{prefix}OVER:{overlay_channel}:CenterY"
113
+ )
114
+
115
+ self.roi_box_size = soft_signal_rw(int, 300)
116
+
117
+ super().__init__(name)
118
+
119
+ def _fit_ellipse(self, binary_img: cv2.typing.MatLike) -> cv2.typing.RotatedRect:
120
+ contours, _ = cv2.findContours(
121
+ binary_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE
122
+ )
123
+ if not contours:
124
+ raise ValueError("No contours found in image.")
125
+
126
+ largest_contour = max(contours, key=cv2.contourArea)
127
+ if len(largest_contour) < 5:
128
+ raise ValueError(
129
+ f"Not enough points to fit an ellipse. Found {largest_contour} points and need at least 5."
130
+ )
131
+
132
+ return cv2.fitEllipse(largest_contour)
133
+
134
+ @AsyncStatus.wrap
135
+ async def trigger(self):
136
+ array_data = await self.oav_array_signal.get_value()
137
+ current_x = await self.current_centre_x.get_value()
138
+ current_y = await self.current_centre_y.get_value()
139
+ roi_box_size = await self.roi_box_size.get_value()
140
+
141
+ roi_data, top_left_corner, _ = get_roi(
142
+ array_data, current_x, current_y, roi_box_size, roi_box_size
143
+ )
144
+
145
+ roi_binary = convert_image_to_binary(roi_data)
146
+ ellipse_fit = self._fit_ellipse(roi_binary)
147
+ roi_centre_x = ellipse_fit[0][0]
148
+ roi_centre_y = ellipse_fit[0][1]
149
+ # convert back to full screen image coords and set beam centre
150
+ self._center_x_val_setter(roi_centre_x + top_left_corner[0])
151
+ self._center_y_val_setter(roi_centre_y + top_left_corner[1])
@@ -1,11 +1,15 @@
1
1
  from ophyd_async.core import Reference, derived_signal_r
2
2
 
3
3
  from dodal.devices.aperturescatterguard import ApertureScatterguard
4
+ from dodal.devices.beamlines.i04.transfocator import Transfocator
4
5
  from dodal.devices.beamsize.beamsize import BeamsizeBase
5
- from dodal.devices.i04.transfocator import Transfocator
6
6
 
7
7
 
8
8
  class Beamsize(BeamsizeBase):
9
+ """Device that calculates the size of the beam by taking the minimum of the
10
+ transfocator size and the aperture scatterguard diameter.
11
+ """
12
+
9
13
  def __init__(
10
14
  self,
11
15
  transfocator: Transfocator,
@@ -20,26 +24,26 @@ class Beamsize(BeamsizeBase):
20
24
  self.x_um = derived_signal_r(
21
25
  self._get_beamsize_x,
22
26
  transfocator_size_x=self._transfocator_ref().current_horizontal_size_rbv,
23
- aperture_radius=self._aperture_scatterguard_ref().radius,
27
+ aperture_diameter=self._aperture_scatterguard_ref().diameter,
24
28
  derived_units="µm",
25
29
  )
26
30
  self.y_um = derived_signal_r(
27
31
  self._get_beamsize_y,
28
32
  transfocator_size_y=self._transfocator_ref().current_vertical_size_rbv,
29
- aperture_radius=self._aperture_scatterguard_ref().radius,
33
+ aperture_diameter=self._aperture_scatterguard_ref().diameter,
30
34
  derived_units="µm",
31
35
  )
32
36
 
33
37
  def _get_beamsize_x(
34
38
  self,
35
39
  transfocator_size_x: float,
36
- aperture_radius: float,
40
+ aperture_diameter: float,
37
41
  ) -> float:
38
- return min(transfocator_size_x, aperture_radius)
42
+ return min(transfocator_size_x, aperture_diameter)
39
43
 
40
44
  def _get_beamsize_y(
41
45
  self,
42
46
  transfocator_size_y: float,
43
- aperture_radius: float,
47
+ aperture_diameter: float,
44
48
  ) -> float:
45
- return min(transfocator_size_y, aperture_radius)
49
+ return min(transfocator_size_y, aperture_diameter)
@@ -0,0 +1,25 @@
1
+ import numpy as np
2
+ from bluesky.protocols import Triggerable
3
+ from ophyd_async.core import AsyncStatus, StandardReadable, soft_signal_r_and_setter
4
+ from ophyd_async.epics.core import (
5
+ epics_signal_r,
6
+ )
7
+
8
+ from dodal.devices.oav.utils import convert_to_gray_and_blur
9
+
10
+
11
+ class MaxPixel(StandardReadable, Triggerable):
12
+ """Gets the max pixel (brightest pixel) from the image after some image processing."""
13
+
14
+ def __init__(self, prefix: str, name: str = "") -> None:
15
+ self.array_data = epics_signal_r(np.ndarray, f"pva://{prefix}PVA:ARRAY")
16
+ self.max_pixel_val, self._max_val_setter = soft_signal_r_and_setter(float)
17
+ super().__init__(name)
18
+
19
+ @AsyncStatus.wrap
20
+ async def trigger(self):
21
+ img_data = await self.array_data.get_value()
22
+ arr = convert_to_gray_and_blur(img_data)
23
+ max_val = float(np.max(arr))
24
+ assert isinstance(max_val, float)
25
+ self._max_val_setter(max_val)
@@ -13,9 +13,9 @@ from ophyd_async.core import (
13
13
  soft_signal_r_and_setter,
14
14
  soft_signal_rw,
15
15
  )
16
- from redis.asyncio import StrictRedis
16
+ from redis.asyncio import ConnectionError, StrictRedis
17
17
 
18
- from dodal.devices.i04.constants import RedisConstants
18
+ from dodal.devices.beamlines.i04.constants import RedisConstants
19
19
  from dodal.devices.oav.oav_calculations import (
20
20
  calculate_beam_distance,
21
21
  )
@@ -103,13 +103,25 @@ class MurkoResultsDevice(StandardReadable, Triggerable, Stageable):
103
103
  self.z_mm, self._z_mm_setter = soft_signal_r_and_setter(float)
104
104
  super().__init__(name=name)
105
105
 
106
+ async def _check_redis_connection(self):
107
+ try:
108
+ await self.redis_client.ping() # type: ignore
109
+ return True
110
+ except ConnectionError:
111
+ LOGGER.warning(
112
+ f"Failed to connect to redis: {self.redis_client}. Murko results device will not trigger"
113
+ )
114
+ return False
115
+
106
116
  def _reset(self):
107
117
  self._last_omega = None
108
118
  self._results: list[MurkoResult] = []
109
119
 
110
120
  @AsyncStatus.wrap
111
121
  async def stage(self):
112
- await self.pubsub.subscribe("murko-results")
122
+ self.redis_connected = await self._check_redis_connection()
123
+ if self.redis_connected:
124
+ await self.pubsub.subscribe("murko-results")
113
125
  self._x_mm_setter(0)
114
126
  self._y_mm_setter(0)
115
127
  self._z_mm_setter(0)
@@ -117,10 +129,13 @@ class MurkoResultsDevice(StandardReadable, Triggerable, Stageable):
117
129
  @AsyncStatus.wrap
118
130
  async def unstage(self):
119
131
  self._reset()
120
- await self.pubsub.unsubscribe()
132
+ if self.redis_connected:
133
+ await self.pubsub.unsubscribe()
121
134
 
122
135
  @AsyncStatus.wrap
123
136
  async def trigger(self):
137
+ if not self.redis_connected:
138
+ return
124
139
  sample_id = await self.sample_id.get_value()
125
140
  t_last_result = time.time()
126
141
  while True:
@@ -223,7 +238,6 @@ class MurkoResultsDevice(StandardReadable, Triggerable, Stageable):
223
238
  remove many of the outliers. Murko also occasionally picks a point in the bottom
224
239
  left corner, which can be removed by filtering results with a small x pixel.
225
240
  """
226
-
227
241
  LOGGER.info(f"Number of results before filtering: {len(self._results)}")
228
242
  sorted_results = sorted(self._results, key=lambda item: item.chosen_point_px[0])
229
243
 
@@ -263,15 +277,16 @@ class MurkoResultsDevice(StandardReadable, Triggerable, Stageable):
263
277
 
264
278
 
265
279
  def get_yz_least_squares(vertical_dists: list, omegas: list) -> tuple[float, float]:
266
- """Get the least squares solution for y and z from the vertical distances and omega angles.
280
+ """Get the least squares solution for y and z from the vertical distances and omega
281
+ angles.
267
282
 
268
283
  Args:
269
- v_dists (list): List of vertical distances from beam centre. Any units
284
+ vertical_dists (list): List of vertical distances from beam centre. Any units.
270
285
  omegas (list): List of omega angles in degrees.
271
286
 
272
287
  Returns:
273
288
  tuple[float, float]: y, z distances from centre, in whichever units
274
- v_dists came as.
289
+ v_dists came as.
275
290
  """
276
291
  thetas = np.radians(omegas)
277
292
  matrix = np.column_stack([np.cos(thetas), -np.sin(thetas)])
@@ -3,7 +3,6 @@ import asyncio
3
3
  from ophyd_async.core import (
4
4
  AsyncStatus,
5
5
  StandardReadable,
6
- observe_value,
7
6
  wait_for_value,
8
7
  )
9
8
  from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
@@ -49,24 +48,20 @@ class Transfocator(StandardReadable):
49
48
  @AsyncStatus.wrap
50
49
  async def set(self, value: float):
51
50
  """To set the beamsize on the transfocator we must:
52
- 1. Set the beamsize in the calculator part of the transfocator
53
- 2. Get the predicted number of lenses needed from this calculator
54
- 3. Enter this back into the device
55
- 4. Start the device moving
56
- 5. Wait for the start_rbv goes high and low again
51
+ 1. Set the beamsize in the calculator part of the transfocator.
52
+ 2. Get the predicted number of lenses needed from this calculator.
53
+ 3. Enter this back into the device.
54
+ 4. Start the device moving.
55
+ 5. Wait for the start_rbv goes high and low again.
57
56
  """
58
57
  LOGGER.info(f"Transfocator setting {value} beamsize")
59
58
 
60
- # Logic in the IOC calculates _num_lenses_calc_rbv when _vert_size_calc_sp changes
61
-
62
- # Register an observer before setting _vert_size_calc_sp to ensure we don't miss changes
63
- num_lenses_calc_iterator = observe_value(
64
- self._num_lenses_calc_rbv, timeout=self.TIMEOUT
65
- )
66
-
67
- await anext(num_lenses_calc_iterator)
68
59
  await self._vert_size_calc_sp.set(value)
69
- calc_lenses = await anext(num_lenses_calc_iterator)
60
+ # Logic in the IOC calculates _num_lenses_calc_rbv when _vert_size_calc_sp changes,
61
+ # but this isn't instant so we need a short sleep until
62
+ # https://jira.diamond.ac.uk/browse/I04-1100
63
+ await asyncio.sleep(0.1)
64
+ calc_lenses = await self._num_lenses_calc_rbv.get_value()
70
65
 
71
66
  async with periodic_reminder(
72
67
  f"Waiting for transfocator to insert {calc_lenses} into beam"
@@ -0,0 +1,3 @@
1
+ from dodal.devices.beamlines.i05.enums import Grating
2
+
3
+ __all__ = ["Grating"]
@@ -0,0 +1,3 @@
1
+ from .i06_enum import I06Grating
2
+
3
+ __all__ = ["I06Grating"]
@@ -0,0 +1,7 @@
1
+ from ophyd_async.core import StrictEnum
2
+
3
+
4
+ class I06Grating(StrictEnum):
5
+ GRATING_150 = "150 lines/mm"
6
+ GRATING_400 = "400 lines/mm"
7
+ GRATING_1200 = "1200 lines/mm"
@@ -9,9 +9,8 @@ from dodal.devices.common_dcm import (
9
9
 
10
10
 
11
11
  class DCM(DoubleCrystalMonochromator[PitchAndRollCrystal, StationaryCrystal]):
12
- """
13
- Device for i07's DCM, including temperature monitors and vertical motor which were
14
- included in GDA.
12
+ """Device for i07's DCM, including temperature monitors and vertical motor which
13
+ were included in GDA.
15
14
  """
16
15
 
17
16
  def __init__(
@@ -5,34 +5,33 @@ from dodal.devices.util.lookup_tables import energy_distance_table
5
5
 
6
6
 
7
7
  class InsertionDevice(UndulatorInKeV):
8
- """
9
- Insertion device for i07 including beamline-specific energy-gap lookup behaviour
8
+ """Insertion device for i07 including beamline-specific energy-gap lookup
9
+ behaviour.
10
10
  """
11
11
 
12
12
  def __init__(
13
13
  self,
14
- name: str,
15
14
  prefix: str,
16
15
  harmonic: UndulatorOrder,
17
16
  id_gap_lookup_table_path: str = "/dls_sw/i07/software/gda/config/lookupTables/"
18
17
  + "IIDCalibrationTable.txt",
19
- ) -> None:
18
+ name: str = "",
19
+ ):
20
20
  super().__init__(prefix, id_gap_lookup_table_path, name=name)
21
21
  self.harmonic = harmonic
22
22
 
23
23
  async def _get_gap_to_match_energy(self, energy_kev: float) -> float:
24
- """
25
- i07's energy scans remain on a particular harmonic while changing energy. The
24
+ """i07's energy scans remain on a particular harmonic while changing energy. The
26
25
  calibration table has one row for each harmonic, row contains max and min
27
26
  energies and their corresponding ID gaps. The requested energy is used to
28
27
  interpolate between these values, assuming a linear relationship on the relevant
29
28
  scale.
30
29
  """
31
- energy_to_distance_table: np.ndarray = await energy_distance_table(
30
+ energy_to_distance_table = await energy_distance_table(
32
31
  self.id_gap_lookup_table_path, comments="#", skiprows=2
33
32
  )
34
- harmonic_value: int = await self.harmonic.value.get_value()
33
+ harmonic_value = await self.harmonic.value.get_value()
35
34
 
36
- row: np.ndarray = energy_to_distance_table[harmonic_value - 1, :]
35
+ row = energy_to_distance_table[harmonic_value - 1, :]
37
36
  gap = np.interp(energy_kev, [row[1], row[2]], [row[3], row[4]])
38
37
  return gap
@@ -0,0 +1,3 @@
1
+ from dodal.devices.beamlines.i09.enums import Grating, LensMode, PassEnergy, PsuMode
2
+
3
+ __all__ = ["Grating", "LensMode", "PsuMode", "PassEnergy"]
@@ -12,17 +12,16 @@ from ophyd_async.core import (
12
12
  soft_signal_rw,
13
13
  )
14
14
 
15
- from dodal.devices.common_dcm import DoubleCrystalMonochromatorBase
16
- from dodal.devices.i09_1_shared.hard_undulator_functions import (
15
+ from dodal.devices.beamlines.i09_1_shared.hard_undulator_functions import (
17
16
  MAX_ENERGY_COLUMN,
18
17
  MIN_ENERGY_COLUMN,
19
18
  )
19
+ from dodal.devices.common_dcm import DoubleCrystalMonochromatorBase
20
20
  from dodal.devices.undulator import UndulatorInMm, UndulatorOrder
21
21
 
22
22
 
23
23
  class HardInsertionDeviceEnergy(StandardReadable, Movable[float]):
24
- """
25
- Compound device to link hard x-ray undulator gap and order to photon energy.
24
+ """Compound device to link hard x-ray undulator gap and order to photon energy.
26
25
  Setting the energy adjusts the undulator gap accordingly.
27
26
  """
28
27
 
@@ -83,8 +82,8 @@ class HardInsertionDeviceEnergy(StandardReadable, Movable[float]):
83
82
 
84
83
 
85
84
  class HardEnergy(StandardReadable, Locatable[float]):
86
- """
87
- Energy compound device that provides combined change of both DCM energy and undulator gap accordingly.
85
+ """Energy compound device that provides combined change of both DCM energy and
86
+ undulator gap accordingly.
88
87
  """
89
88
 
90
89
  def __init__(