mx-bluesky 0.3.1__py3-none-any.whl → 1.1.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 (138) hide show
  1. mx_bluesky/_version.py +2 -2
  2. mx_bluesky/beamlines/i04/__init__.py +3 -0
  3. mx_bluesky/{i04 → beamlines/i04}/thawing_plan.py +5 -4
  4. mx_bluesky/{i24 → beamlines/i24}/serial/blueapi_config.yaml +1 -1
  5. mx_bluesky/{i24 → beamlines/i24}/serial/dcid.py +2 -2
  6. mx_bluesky/{i24 → beamlines/i24}/serial/extruder/EX-gui-edm/DetStage.edl +3 -3
  7. mx_bluesky/{i24 → beamlines/i24}/serial/extruder/EX-gui-edm/DiamondExtruder-I24-py3v1.edl +7 -7
  8. mx_bluesky/{i24 → beamlines/i24}/serial/extruder/i24ssx_Extruder_Collect_py3v2.py +12 -9
  9. mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/CustomChip_py3v1.edl +3 -3
  10. mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/DetStage.edl +3 -3
  11. mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/DiamondChipI24-py3v1.edl +245 -200
  12. mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/MappingLite-oxford_py3v1.edl +4 -4
  13. mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/pumpprobe-py3v1.edl +8 -8
  14. mx_bluesky/beamlines/i24/serial/fixed_target/__init__.py +0 -0
  15. mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/i24ssx_Chip_Collect_py3v1.py +80 -70
  16. mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/i24ssx_Chip_Manager_py3v1.py +20 -21
  17. mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/i24ssx_Chip_Mapping_py3v1.py +5 -5
  18. mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/i24ssx_Chip_StartUp_py3v1.py +7 -4
  19. mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/i24ssx_moveonclick.py +59 -39
  20. mx_bluesky/{i24 → beamlines/i24}/serial/log.py +1 -9
  21. mx_bluesky/beamlines/i24/serial/parameters/__init__.py +15 -0
  22. mx_bluesky/{i24 → beamlines/i24}/serial/parameters/constants.py +1 -1
  23. mx_bluesky/{i24 → beamlines/i24}/serial/parameters/experiment_parameters.py +4 -25
  24. mx_bluesky/{i24 → beamlines/i24}/serial/parameters/utils.py +5 -3
  25. mx_bluesky/{i24 → beamlines/i24}/serial/run_serial.py +1 -1
  26. mx_bluesky/{i24 → beamlines/i24}/serial/setup_beamline/pv_abstract.py +1 -1
  27. mx_bluesky/{i24 → beamlines/i24}/serial/setup_beamline/setup_beamline.py +2 -2
  28. mx_bluesky/{i24 → beamlines/i24}/serial/setup_beamline/setup_detector.py +5 -5
  29. mx_bluesky/{i24 → beamlines/i24}/serial/write_nexus.py +6 -3
  30. mx_bluesky/hyperion/__init__.py +1 -0
  31. mx_bluesky/hyperion/__main__.py +374 -0
  32. mx_bluesky/hyperion/device_setup_plans/__init__.py +0 -0
  33. mx_bluesky/hyperion/device_setup_plans/dcm_pitch_roll_mirror_adjuster.py +134 -0
  34. mx_bluesky/hyperion/device_setup_plans/manipulate_sample.py +110 -0
  35. mx_bluesky/hyperion/device_setup_plans/position_detector.py +16 -0
  36. mx_bluesky/hyperion/device_setup_plans/read_hardware_for_setup.py +60 -0
  37. mx_bluesky/hyperion/device_setup_plans/setup_oav.py +87 -0
  38. mx_bluesky/hyperion/device_setup_plans/setup_panda.py +210 -0
  39. mx_bluesky/hyperion/device_setup_plans/setup_zebra.py +214 -0
  40. mx_bluesky/hyperion/device_setup_plans/smargon.py +25 -0
  41. mx_bluesky/hyperion/device_setup_plans/utils.py +44 -0
  42. mx_bluesky/hyperion/device_setup_plans/xbpm_feedback.py +93 -0
  43. mx_bluesky/hyperion/exceptions.py +47 -0
  44. mx_bluesky/hyperion/experiment_plans/__init__.py +30 -0
  45. mx_bluesky/hyperion/experiment_plans/experiment_registry.py +84 -0
  46. mx_bluesky/hyperion/experiment_plans/flyscan_xray_centre_plan.py +528 -0
  47. mx_bluesky/hyperion/experiment_plans/grid_detect_then_xray_centre_plan.py +209 -0
  48. mx_bluesky/hyperion/experiment_plans/oav_grid_detection_plan.py +173 -0
  49. mx_bluesky/hyperion/experiment_plans/oav_snapshot_plan.py +81 -0
  50. mx_bluesky/hyperion/experiment_plans/optimise_attenuation_plan.py +463 -0
  51. mx_bluesky/hyperion/experiment_plans/pin_centre_then_xray_centre_plan.py +119 -0
  52. mx_bluesky/hyperion/experiment_plans/pin_tip_centring_plan.py +164 -0
  53. mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +322 -0
  54. mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +436 -0
  55. mx_bluesky/hyperion/experiment_plans/set_energy_plan.py +68 -0
  56. mx_bluesky/hyperion/external_interaction/__init__.py +9 -0
  57. mx_bluesky/hyperion/external_interaction/callbacks/__init__.py +10 -0
  58. mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +148 -0
  59. mx_bluesky/hyperion/external_interaction/callbacks/aperture_change_callback.py +22 -0
  60. mx_bluesky/hyperion/external_interaction/callbacks/common/__init__.py +0 -0
  61. mx_bluesky/hyperion/external_interaction/callbacks/common/callback_util.py +46 -0
  62. mx_bluesky/hyperion/external_interaction/callbacks/common/ispyb_mapping.py +70 -0
  63. mx_bluesky/hyperion/external_interaction/callbacks/grid_detection_callback.py +88 -0
  64. mx_bluesky/hyperion/external_interaction/callbacks/ispyb_callback_base.py +203 -0
  65. mx_bluesky/hyperion/external_interaction/callbacks/log_uid_tag_callback.py +20 -0
  66. mx_bluesky/hyperion/external_interaction/callbacks/logging_callback.py +29 -0
  67. mx_bluesky/hyperion/external_interaction/callbacks/plan_reactive_callback.py +101 -0
  68. mx_bluesky/hyperion/external_interaction/callbacks/robot_load/ispyb_callback.py +88 -0
  69. mx_bluesky/hyperion/external_interaction/callbacks/rotation/__init__.py +0 -0
  70. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py +174 -0
  71. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_mapping.py +17 -0
  72. mx_bluesky/hyperion/external_interaction/callbacks/rotation/nexus_callback.py +102 -0
  73. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/__init__.py +0 -0
  74. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/ispyb_callback.py +269 -0
  75. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/ispyb_mapping.py +53 -0
  76. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/nexus_callback.py +95 -0
  77. mx_bluesky/hyperion/external_interaction/callbacks/zocalo_callback.py +92 -0
  78. mx_bluesky/hyperion/external_interaction/config_server.py +35 -0
  79. mx_bluesky/hyperion/external_interaction/exceptions.py +13 -0
  80. mx_bluesky/hyperion/external_interaction/ispyb/__init__.py +0 -0
  81. mx_bluesky/hyperion/external_interaction/ispyb/data_model.py +95 -0
  82. mx_bluesky/hyperion/external_interaction/ispyb/exp_eye_store.py +125 -0
  83. mx_bluesky/hyperion/external_interaction/ispyb/ispyb_store.py +276 -0
  84. mx_bluesky/hyperion/external_interaction/ispyb/ispyb_utils.py +29 -0
  85. mx_bluesky/hyperion/external_interaction/nexus/__init__.py +0 -0
  86. mx_bluesky/hyperion/external_interaction/nexus/nexus_utils.py +148 -0
  87. mx_bluesky/hyperion/external_interaction/nexus/write_nexus.py +114 -0
  88. mx_bluesky/hyperion/log.py +99 -0
  89. mx_bluesky/hyperion/parameters/__init__.py +2 -0
  90. mx_bluesky/hyperion/parameters/cli.py +68 -0
  91. mx_bluesky/{parameters → hyperion/parameters}/components.py +77 -24
  92. mx_bluesky/hyperion/parameters/constants.py +158 -0
  93. mx_bluesky/hyperion/parameters/gridscan.py +216 -0
  94. mx_bluesky/hyperion/parameters/rotation.py +160 -0
  95. mx_bluesky/hyperion/resources/panda/panda-gridscan.yaml +964 -0
  96. mx_bluesky/hyperion/tracing.py +28 -0
  97. mx_bluesky/hyperion/utils/context.py +84 -0
  98. mx_bluesky/hyperion/utils/utils.py +25 -0
  99. mx_bluesky/hyperion/utils/validation.py +196 -0
  100. mx_bluesky/jupyter_example.ipynb +3 -2
  101. {mx_bluesky-0.3.1.dist-info → mx_bluesky-1.1.0.dist-info}/METADATA +26 -11
  102. mx_bluesky-1.1.0.dist-info/RECORD +136 -0
  103. {mx_bluesky-0.3.1.dist-info → mx_bluesky-1.1.0.dist-info}/WHEEL +1 -1
  104. mx_bluesky-1.1.0.dist-info/entry_points.txt +8 -0
  105. mx_bluesky/i04/__init__.py +0 -3
  106. mx_bluesky/i24/serial/parameters/__init__.py +0 -15
  107. mx_bluesky/parameters/__init__.py +0 -31
  108. mx_bluesky-0.3.1.dist-info/RECORD +0 -67
  109. mx_bluesky-0.3.1.dist-info/entry_points.txt +0 -4
  110. /mx_bluesky/{i24 → beamlines}/__init__.py +0 -0
  111. /mx_bluesky/{i04 → beamlines/i04}/callbacks/murko_callback.py +0 -0
  112. /mx_bluesky/{i24/serial/extruder → beamlines/i24}/__init__.py +0 -0
  113. /mx_bluesky/{i24 → beamlines/i24}/serial/__init__.py +0 -0
  114. /mx_bluesky/{i24 → beamlines/i24}/serial/extruder/EX-gui-edm/microdrop_alignment.edl +0 -0
  115. /mx_bluesky/{i24/serial/fixed_target → beamlines/i24/serial/extruder}/__init__.py +0 -0
  116. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/ME14E-GeneralPurpose.edl +0 -0
  117. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/PMAC_Command.edl +0 -0
  118. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/Shutter_Control.edl +0 -0
  119. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/microdrop_alignment.edl +0 -0
  120. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/nudgechip.edl +0 -0
  121. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/short1-laser.png +0 -0
  122. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/short2-laser.png +0 -0
  123. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/ft_utils.py +0 -0
  124. /mx_bluesky/{i24 → beamlines/i24}/serial/parameters/fixed_target/cs/cs_maker.json +0 -0
  125. /mx_bluesky/{i24 → beamlines/i24}/serial/parameters/fixed_target/cs/motor_direction.txt +0 -0
  126. /mx_bluesky/{i24 → beamlines/i24}/serial/parameters/fixed_target/pvar_files/minichip-oxford.pvar +0 -0
  127. /mx_bluesky/{i24 → beamlines/i24}/serial/parameters/fixed_target/pvar_files/oxford.pvar +0 -0
  128. /mx_bluesky/{i24 → beamlines/i24}/serial/run_extruder.sh +0 -0
  129. /mx_bluesky/{i24 → beamlines/i24}/serial/run_fixed_target.sh +0 -0
  130. /mx_bluesky/{i24 → beamlines/i24}/serial/run_ssx.sh +0 -0
  131. /mx_bluesky/{i24 → beamlines/i24}/serial/set_visit_directory.sh +0 -0
  132. /mx_bluesky/{i24 → beamlines/i24}/serial/setup_beamline/__init__.py +0 -0
  133. /mx_bluesky/{i24 → beamlines/i24}/serial/setup_beamline/ca.py +0 -0
  134. /mx_bluesky/{i24 → beamlines/i24}/serial/setup_beamline/pv.py +0 -0
  135. /mx_bluesky/{i24 → beamlines/i24}/serial/setup_beamline/setup_zebra_plans.py +0 -0
  136. /mx_bluesky/{i24 → beamlines/i24}/serial/start_blueapi.sh +0 -0
  137. {mx_bluesky-0.3.1.dist-info → mx_bluesky-1.1.0.dist-info}/LICENSE +0 -0
  138. {mx_bluesky-0.3.1.dist-info → mx_bluesky-1.1.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,110 @@
1
+ from __future__ import annotations
2
+
3
+ import bluesky.plan_stubs as bps
4
+ from dodal.devices.aperturescatterguard import (
5
+ ApertureScatterguard,
6
+ ApertureValue,
7
+ )
8
+ from dodal.devices.backlight import Backlight, BacklightPosition
9
+ from dodal.devices.detector.detector_motion import DetectorMotion
10
+ from dodal.devices.smargon import Smargon
11
+
12
+ from mx_bluesky.hyperion.log import LOGGER
13
+
14
+ LOWER_DETECTOR_SHUTTER_AFTER_SCAN = True
15
+
16
+
17
+ def setup_sample_environment(
18
+ aperture_scatterguard: ApertureScatterguard,
19
+ aperture_position_gda_name: str | None,
20
+ backlight: Backlight,
21
+ group="setup_senv",
22
+ ):
23
+ """Move the aperture into required position, move out the backlight."""
24
+ aperture_value = (
25
+ None
26
+ if not aperture_position_gda_name
27
+ else ApertureValue(aperture_position_gda_name)
28
+ )
29
+ yield from move_aperture_if_required(
30
+ aperture_scatterguard, aperture_value, group=group
31
+ )
32
+ yield from bps.abs_set(backlight, BacklightPosition.OUT, group=group)
33
+
34
+
35
+ def move_aperture_if_required(
36
+ aperture_scatterguard: ApertureScatterguard,
37
+ aperture_value: ApertureValue | None,
38
+ group="move_aperture",
39
+ ):
40
+ if not aperture_value:
41
+ previous_aperture_position = yield from bps.rd(aperture_scatterguard)
42
+ assert isinstance(previous_aperture_position, ApertureValue)
43
+ LOGGER.info(
44
+ f"Using previously set aperture position {previous_aperture_position}"
45
+ )
46
+
47
+ else:
48
+ LOGGER.info(f"Setting aperture position to {aperture_value}")
49
+ yield from bps.abs_set(
50
+ aperture_scatterguard,
51
+ aperture_value,
52
+ group=group,
53
+ )
54
+
55
+
56
+ def cleanup_sample_environment(
57
+ detector_motion: DetectorMotion,
58
+ group="cleanup_senv",
59
+ ):
60
+ """Put the detector shutter back down"""
61
+
62
+ yield from bps.abs_set(
63
+ detector_motion.shutter,
64
+ int(not LOWER_DETECTOR_SHUTTER_AFTER_SCAN),
65
+ group=group,
66
+ )
67
+
68
+
69
+ def move_x_y_z(
70
+ smargon: Smargon,
71
+ x_mm: float | None = None,
72
+ y_mm: float | None = None,
73
+ z_mm: float | None = None,
74
+ wait=False,
75
+ group="move_x_y_z",
76
+ ):
77
+ """Move the x, y, and z axes of the given smargon to the specified position. All
78
+ axes are optional."""
79
+
80
+ LOGGER.info(f"Moving smargon to x, y, z: {(x_mm, y_mm, z_mm)}")
81
+ if x_mm:
82
+ yield from bps.abs_set(smargon.x, x_mm, group=group)
83
+ if y_mm:
84
+ yield from bps.abs_set(smargon.y, y_mm, group=group)
85
+ if z_mm:
86
+ yield from bps.abs_set(smargon.z, z_mm, group=group)
87
+ if wait:
88
+ yield from bps.wait(group)
89
+
90
+
91
+ def move_phi_chi_omega(
92
+ smargon: Smargon,
93
+ phi: float | None = None,
94
+ chi: float | None = None,
95
+ omega: float | None = None,
96
+ wait=False,
97
+ group="move_phi_chi_omega",
98
+ ):
99
+ """Move the x, y, and z axes of the given smargon to the specified position. All
100
+ axes are optional."""
101
+
102
+ LOGGER.info(f"Moving smargon to phi, chi, omega: {(phi, chi, omega)}")
103
+ if phi:
104
+ yield from bps.abs_set(smargon.phi, phi, group=group)
105
+ if chi:
106
+ yield from bps.abs_set(smargon.chi, chi, group=group)
107
+ if omega:
108
+ yield from bps.abs_set(smargon.omega, omega, group=group)
109
+ if wait:
110
+ yield from bps.wait(group)
@@ -0,0 +1,16 @@
1
+ from bluesky import plan_stubs as bps
2
+ from dodal.devices.detector.detector_motion import DetectorMotion, ShutterState
3
+
4
+ from mx_bluesky.hyperion.log import LOGGER
5
+
6
+
7
+ def set_detector_z_position(
8
+ detector_motion: DetectorMotion, detector_position: float, group=None
9
+ ):
10
+ LOGGER.info(f"Moving detector to {detector_position} ({group})")
11
+ yield from bps.abs_set(detector_motion.z, detector_position, group=group)
12
+
13
+
14
+ def set_shutter(detector_motion: DetectorMotion, state: ShutterState, group=None):
15
+ LOGGER.info(f"Setting shutter to {state} ({group})")
16
+ yield from bps.abs_set(detector_motion.shutter, state, group=group)
@@ -0,0 +1,60 @@
1
+ from __future__ import annotations
2
+
3
+ import bluesky.plan_stubs as bps
4
+ from dodal.devices.aperturescatterguard import ApertureScatterguard
5
+ from dodal.devices.attenuator import Attenuator
6
+ from dodal.devices.dcm import DCM
7
+ from dodal.devices.eiger import EigerDetector
8
+ from dodal.devices.flux import Flux
9
+ from dodal.devices.robot import BartRobot
10
+ from dodal.devices.s4_slit_gaps import S4SlitGaps
11
+ from dodal.devices.smargon import Smargon
12
+ from dodal.devices.synchrotron import Synchrotron
13
+ from dodal.devices.undulator import Undulator
14
+
15
+ from mx_bluesky.hyperion.log import LOGGER
16
+ from mx_bluesky.hyperion.parameters.constants import CONST
17
+
18
+
19
+ def read_hardware_pre_collection(
20
+ undulator: Undulator,
21
+ synchrotron: Synchrotron,
22
+ s4_slit_gaps: S4SlitGaps,
23
+ robot: BartRobot,
24
+ smargon: Smargon,
25
+ ):
26
+ LOGGER.info("Reading status of beamline for callbacks, pre collection.")
27
+ yield from bps.create(
28
+ name=CONST.DESCRIPTORS.HARDWARE_READ_PRE
29
+ ) # gives name to event *descriptor* document
30
+ yield from bps.read(undulator.current_gap)
31
+ yield from bps.read(synchrotron.synchrotron_mode)
32
+ yield from bps.read(s4_slit_gaps.xgap)
33
+ yield from bps.read(s4_slit_gaps.ygap)
34
+ yield from bps.read(smargon.x)
35
+ yield from bps.read(smargon.y)
36
+ yield from bps.read(smargon.z)
37
+ yield from bps.save()
38
+
39
+
40
+ def read_hardware_during_collection(
41
+ aperture_scatterguard: ApertureScatterguard,
42
+ attenuator: Attenuator,
43
+ flux: Flux,
44
+ dcm: DCM,
45
+ detector: EigerDetector,
46
+ ):
47
+ LOGGER.info("Reading status of beamline for callbacks, during collection.")
48
+ yield from bps.create(name=CONST.DESCRIPTORS.HARDWARE_READ_DURING)
49
+ yield from bps.read(aperture_scatterguard)
50
+ yield from bps.read(attenuator.actual_transmission)
51
+ yield from bps.read(flux.flux_reading)
52
+ yield from bps.read(dcm.energy_in_kev)
53
+ yield from bps.read(detector.bit_depth)
54
+ yield from bps.save()
55
+
56
+
57
+ def read_hardware_for_zocalo(detector: EigerDetector):
58
+ yield from bps.create(name=CONST.DESCRIPTORS.ZOCALO_HW_READ)
59
+ yield from bps.read(detector.odin.file_writer.id)
60
+ yield from bps.save()
@@ -0,0 +1,87 @@
1
+ from functools import partial
2
+
3
+ import bluesky.plan_stubs as bps
4
+ from dodal.devices.oav.oav_detector import OAV
5
+ from dodal.devices.oav.oav_errors import OAVError_ZoomLevelNotFound
6
+ from dodal.devices.oav.oav_parameters import OAVParameters
7
+ from dodal.devices.oav.pin_image_recognition import PinTipDetection
8
+ from dodal.devices.oav.utils import ColorMode
9
+
10
+ from mx_bluesky.hyperion.parameters.constants import CONST
11
+
12
+ # Helper function to make sure we set the waiting groups correctly
13
+ set_using_group = partial(bps.abs_set, group=CONST.WAIT.READY_FOR_OAV)
14
+
15
+
16
+ def setup_pin_tip_detection_params(
17
+ pin_tip_detect_device: PinTipDetection, parameters: OAVParameters
18
+ ):
19
+ # select which blur to apply to image
20
+ yield from set_using_group(
21
+ pin_tip_detect_device.preprocess_operation, parameters.preprocess
22
+ )
23
+
24
+ # sets length scale for blurring
25
+ yield from set_using_group(
26
+ pin_tip_detect_device.preprocess_ksize, parameters.preprocess_K_size
27
+ )
28
+
29
+ # Canny edge detect - lower
30
+ yield from set_using_group(
31
+ pin_tip_detect_device.canny_lower_threshold,
32
+ parameters.canny_edge_lower_threshold,
33
+ )
34
+
35
+ # Canny edge detect - upper
36
+ yield from set_using_group(
37
+ pin_tip_detect_device.canny_upper_threshold,
38
+ parameters.canny_edge_upper_threshold,
39
+ )
40
+
41
+ # "Close" morphological operation
42
+ yield from set_using_group(
43
+ pin_tip_detect_device.close_ksize, parameters.close_ksize
44
+ )
45
+
46
+ # Sample detection direction
47
+ yield from set_using_group(
48
+ pin_tip_detect_device.scan_direction, parameters.direction
49
+ )
50
+
51
+ # Minimum height
52
+ yield from set_using_group(
53
+ pin_tip_detect_device.min_tip_height,
54
+ parameters.minimum_height,
55
+ )
56
+
57
+
58
+ def setup_general_oav_params(oav: OAV, parameters: OAVParameters):
59
+ yield from set_using_group(oav.cam.color_mode, ColorMode.RGB1)
60
+ yield from set_using_group(oav.cam.acquire_period, parameters.acquire_period)
61
+ yield from set_using_group(oav.cam.acquire_time, parameters.exposure)
62
+ yield from set_using_group(oav.cam.gain, parameters.gain)
63
+
64
+ zoom_level_str = f"{float(parameters.zoom)}x"
65
+ if zoom_level_str not in oav.zoom_controller.allowed_zoom_levels:
66
+ raise OAVError_ZoomLevelNotFound(
67
+ f"Found {zoom_level_str} as a zoom level but expected one of {oav.zoom_controller.allowed_zoom_levels}"
68
+ )
69
+
70
+ yield from bps.abs_set(
71
+ oav.zoom_controller,
72
+ zoom_level_str,
73
+ wait=True,
74
+ )
75
+
76
+
77
+ def pre_centring_setup_oav(
78
+ oav: OAV,
79
+ parameters: OAVParameters,
80
+ pin_tip_detection_device: PinTipDetection,
81
+ ):
82
+ """
83
+ Setup OAV PVs with required values.
84
+ """
85
+ yield from setup_general_oav_params(oav, parameters)
86
+ yield from setup_pin_tip_detection_params(pin_tip_detection_device, parameters)
87
+ yield from bps.wait(CONST.WAIT.READY_FOR_OAV)
@@ -0,0 +1,210 @@
1
+ from datetime import datetime
2
+ from enum import Enum
3
+ from importlib import resources
4
+ from pathlib import Path
5
+
6
+ import bluesky.plan_stubs as bps
7
+ from blueapi.core import MsgGenerator
8
+ from dodal.common.beamlines.beamline_utils import get_path_provider
9
+ from dodal.devices.fast_grid_scan import PandAGridScanParams
10
+ from ophyd_async.core import load_device
11
+ from ophyd_async.fastcs.panda import (
12
+ HDFPanda,
13
+ SeqTable,
14
+ SeqTrigger,
15
+ )
16
+
17
+ import mx_bluesky.hyperion.resources.panda as panda_resource
18
+ from mx_bluesky.hyperion.log import LOGGER
19
+
20
+ MM_TO_ENCODER_COUNTS = 200000
21
+ GENERAL_TIMEOUT = 60
22
+ TICKS_PER_MS = 1000 # Panda sequencer prescaler will be set to us
23
+
24
+
25
+ class Enabled(Enum):
26
+ ENABLED = "ONE"
27
+ DISABLED = "ZERO"
28
+
29
+
30
+ class PcapArm(Enum):
31
+ ARMED = "Arm"
32
+ DISARMED = "Disarm"
33
+
34
+
35
+ def _get_seq_table(
36
+ parameters: PandAGridScanParams, exposure_distance_mm, time_between_steps_ms
37
+ ) -> SeqTable:
38
+ """
39
+ Generate the sequencer table for the panda.
40
+
41
+ - Sending a 'trigger' means trigger PCAP internally and send signal to Eiger via physical panda output
42
+
43
+ SEQUENCER TABLE:
44
+
45
+ 1. Wait for physical trigger from motion script to mark start of scan / change of direction
46
+ 2. Wait for POSA (X2) to be greater than X_START and send x_steps triggers every time_between_steps_ms
47
+ 3. Wait for physical trigger from motion script to mark change of direction
48
+ 4. Wait for POSA (X2) to be less than X_START + X_STEP_SIZE * x_steps + exposure distance, then
49
+ send x_steps triggers every time_between_steps_ms
50
+ 5. Go back to step one.
51
+
52
+ For a more detailed explanation and a diagram, see https://github.com/DiamondLightSource/hyperion/wiki/PandA-constant%E2%80%90motion-scanning
53
+
54
+ For documentation on Panda itself, see https://pandablocks.github.io/PandABlocks-FPGA/master/index.html
55
+
56
+ Args:
57
+ exposure_distance_mm: The distance travelled by the sample each time the detector is exposed: exposure time * sample velocity
58
+ time_between_steps_ms: The time taken to traverse between each grid step.
59
+ parameters: Parameters for the panda gridscan
60
+
61
+ Returns:
62
+ An instance of SeqTable describing the panda sequencer table
63
+ """
64
+
65
+ start_of_grid_x_counts = int(parameters.x_start * MM_TO_ENCODER_COUNTS)
66
+
67
+ # x_start is the first trigger point, so we need to travel to x_steps-1 for the final trigger point
68
+ end_of_grid_x_counts = int(
69
+ start_of_grid_x_counts
70
+ + (parameters.x_step_size * (parameters.x_steps - 1) * MM_TO_ENCODER_COUNTS)
71
+ )
72
+
73
+ exposure_distance_x_counts = int(exposure_distance_mm * MM_TO_ENCODER_COUNTS)
74
+
75
+ num_pulses = parameters.x_steps
76
+
77
+ delay_between_pulses = time_between_steps_ms * TICKS_PER_MS
78
+
79
+ PULSE_WIDTH_US = 1
80
+
81
+ assert delay_between_pulses > PULSE_WIDTH_US
82
+
83
+ # BITA_1 trigger wired from TTLIN1, this is the trigger input
84
+
85
+ # +ve direction scan
86
+
87
+ table = (
88
+ SeqTable.row(trigger=SeqTrigger.BITA_1, time2=1)
89
+ + SeqTable.row(
90
+ repeats=num_pulses,
91
+ trigger=SeqTrigger.POSA_GT,
92
+ position=start_of_grid_x_counts,
93
+ time1=PULSE_WIDTH_US,
94
+ outa1=True,
95
+ time2=delay_between_pulses - PULSE_WIDTH_US,
96
+ outa2=False,
97
+ )
98
+ +
99
+ # -ve direction scan
100
+ SeqTable.row(trigger=SeqTrigger.BITA_1, time2=1)
101
+ + SeqTable.row(
102
+ repeats=num_pulses,
103
+ trigger=SeqTrigger.POSA_LT,
104
+ position=end_of_grid_x_counts + exposure_distance_x_counts,
105
+ time1=PULSE_WIDTH_US,
106
+ outa1=True,
107
+ time2=delay_between_pulses - PULSE_WIDTH_US,
108
+ outa2=False,
109
+ )
110
+ )
111
+
112
+ return table
113
+
114
+
115
+ def setup_panda_for_flyscan(
116
+ panda: HDFPanda,
117
+ parameters: PandAGridScanParams,
118
+ initial_x: float,
119
+ exposure_time_s: float,
120
+ time_between_x_steps_ms: float,
121
+ sample_velocity_mm_per_s: float,
122
+ ) -> MsgGenerator:
123
+ """Configures the PandA device for a flyscan.
124
+ Sets PVs from a yaml file, calibrates the encoder, and
125
+ adjusts the sequencer table based off the grid parameters. Yaml file can be
126
+ created using ophyd_async.core.save_device()
127
+
128
+ Args:
129
+ panda (HDFPanda): The PandA Ophyd device
130
+ parameters (PandAGridScanParams): Grid parameters
131
+ initial_x (float): Motor positions at time of PandA setup
132
+ exposure_time_s (float): Detector exposure time per trigger
133
+ time_between_x_steps_ms (float): Time, in ms, between each trigger. Equal to deadtime + exposure time
134
+ sample_velocity_mm_per_s (float): Velocity of the sample in mm/s = x_step_size_mm * 1000 /
135
+ time_between_x_steps_ms
136
+ Returns:
137
+ MsgGenerator
138
+
139
+ Yields:
140
+ Iterator[MsgGenerator]
141
+ """
142
+ assert parameters.x_steps > 0
143
+ assert time_between_x_steps_ms * 1000 >= exposure_time_s
144
+ assert sample_velocity_mm_per_s * exposure_time_s < parameters.x_step_size
145
+
146
+ yield from bps.stage(panda, group="panda-config")
147
+
148
+ with resources.as_file(
149
+ resources.files(panda_resource) / "panda-gridscan.yaml"
150
+ ) as config_yaml_path:
151
+ yield from load_device(panda, str(config_yaml_path))
152
+
153
+ # Home the PandA X encoder using current motor position
154
+ yield from bps.abs_set(
155
+ panda.inenc[1].setp, # type: ignore
156
+ initial_x * MM_TO_ENCODER_COUNTS,
157
+ wait=True,
158
+ )
159
+
160
+ yield from bps.abs_set(panda.pulse[1].width, exposure_time_s, group="panda-config")
161
+
162
+ exposure_distance_mm = sample_velocity_mm_per_s * exposure_time_s
163
+
164
+ table = _get_seq_table(parameters, exposure_distance_mm, time_between_x_steps_ms)
165
+
166
+ yield from bps.abs_set(panda.seq[1].table, table, group="panda-config")
167
+
168
+ yield from bps.abs_set(
169
+ panda.pcap.enable, # type: ignore
170
+ Enabled.ENABLED.value,
171
+ group="panda-config",
172
+ )
173
+
174
+ # Values need to be set before blocks are enabled, so wait here
175
+ yield from bps.wait(group="panda-config", timeout=GENERAL_TIMEOUT)
176
+
177
+ LOGGER.info(f"PandA sequencer table has been set to: {str(table)}")
178
+ table_readback = yield from bps.rd(panda.seq[1].table)
179
+ LOGGER.debug(f"PandA sequencer table readback is: {str(table_readback)}")
180
+
181
+ yield from arm_panda_for_gridscan(panda)
182
+
183
+
184
+ def arm_panda_for_gridscan(panda: HDFPanda, group="arm_panda_gridscan"):
185
+ yield from bps.abs_set(panda.seq[1].enable, Enabled.ENABLED.value, group=group) # type: ignore
186
+ yield from bps.abs_set(panda.pulse[1].enable, Enabled.ENABLED.value, group=group) # type: ignore
187
+ yield from bps.abs_set(panda.counter[1].enable, Enabled.ENABLED.value, group=group) # type: ignore
188
+ yield from bps.abs_set(panda.pcap.arm, PcapArm.ARMED.value, group=group) # type: ignore
189
+ yield from bps.wait(group=group, timeout=GENERAL_TIMEOUT)
190
+ LOGGER.info("PandA has been armed")
191
+
192
+
193
+ def disarm_panda_for_gridscan(panda, group="disarm_panda_gridscan") -> MsgGenerator:
194
+ yield from bps.abs_set(panda.pcap.arm, PcapArm.DISARMED.value, group=group) # type: ignore
195
+ yield from bps.abs_set(panda.counter[1].enable, Enabled.DISABLED.value, group=group) # type: ignore
196
+ yield from bps.abs_set(panda.seq[1].enable, Enabled.DISABLED.value, group=group)
197
+ yield from bps.abs_set(panda.pulse[1].enable, Enabled.DISABLED.value, group=group)
198
+ yield from bps.abs_set(panda.pcap.enable, Enabled.DISABLED.value, group=group) # type: ignore
199
+ yield from bps.wait(group=group, timeout=GENERAL_TIMEOUT)
200
+
201
+
202
+ def set_panda_directory(panda_directory: Path) -> MsgGenerator:
203
+ """Updates the root folder which is used by the PandA's PCAP."""
204
+
205
+ suffix = datetime.now().strftime("_%Y%m%d%H%M%S")
206
+
207
+ async def set_panda_dir():
208
+ await get_path_provider().update(directory=panda_directory, suffix=suffix)
209
+
210
+ yield from bps.wait_for([set_panda_dir])
@@ -0,0 +1,214 @@
1
+ from collections.abc import Callable
2
+ from functools import wraps
3
+
4
+ import bluesky.plan_stubs as bps
5
+ import bluesky.preprocessors as bpp
6
+ from blueapi.core import MsgGenerator
7
+ from dodal.devices.zebra import (
8
+ AUTO_SHUTTER_GATE,
9
+ AUTO_SHUTTER_INPUT,
10
+ DISCONNECT,
11
+ IN1_TTL,
12
+ IN3_TTL,
13
+ IN4_TTL,
14
+ PC_GATE,
15
+ PC_PULSE,
16
+ TTL_DETECTOR,
17
+ TTL_PANDA,
18
+ TTL_XSPRESS3,
19
+ ArmDemand,
20
+ EncEnum,
21
+ I03Axes,
22
+ RotationDirection,
23
+ Zebra,
24
+ )
25
+ from dodal.devices.zebra_controlled_shutter import ZebraShutter, ZebraShutterControl
26
+
27
+ from mx_bluesky.hyperion.log import LOGGER
28
+
29
+ ZEBRA_STATUS_TIMEOUT = 30
30
+
31
+
32
+ def bluesky_retry(func: Callable):
33
+ """Decorator that will retry the decorated plan if it fails.
34
+
35
+ Use this with care as it knows nothing about the state of the world when things fail.
36
+ If it is possible that your plan fails when the beamline is in a transient state that
37
+ the plan could not act on do not use this decorator without doing some more intelligent
38
+ clean up.
39
+
40
+ You should avoid using this decorator often in general production as it hides errors,
41
+ instead it should be used only for debugging these underlying errors.
42
+ """
43
+
44
+ @wraps(func)
45
+ def newfunc(*args, **kwargs):
46
+ def log_and_retry(exception):
47
+ LOGGER.error(f"Function {func.__name__} failed with {exception}, retrying")
48
+ yield from func(*args, **kwargs)
49
+
50
+ yield from bpp.contingency_wrapper(
51
+ func(*args, **kwargs), except_plan=log_and_retry, auto_raise=False
52
+ )
53
+
54
+ return newfunc
55
+
56
+
57
+ def arm_zebra(zebra: Zebra):
58
+ yield from bps.abs_set(zebra.pc.arm, ArmDemand.ARM, wait=True)
59
+
60
+
61
+ def tidy_up_zebra_after_rotation_scan(
62
+ zebra: Zebra,
63
+ zebra_shutter: ZebraShutter,
64
+ group="tidy_up_zebra_after_rotation",
65
+ wait=True,
66
+ ):
67
+ yield from bps.abs_set(zebra.pc.arm, ArmDemand.DISARM, group=group)
68
+ yield from bps.abs_set(
69
+ zebra_shutter.control_mode, ZebraShutterControl.MANUAL, group=group
70
+ )
71
+ if wait:
72
+ yield from bps.wait(group, timeout=ZEBRA_STATUS_TIMEOUT)
73
+
74
+
75
+ def set_shutter_auto_input(zebra: Zebra, input: int, group="set_shutter_trigger"):
76
+ """Set the input that the shutter uses when set to auto.
77
+
78
+ For more details see the ZebraShutter device."""
79
+ auto_shutter_control = zebra.logic_gates.and_gates[AUTO_SHUTTER_GATE]
80
+ yield from bps.abs_set(
81
+ auto_shutter_control.sources[AUTO_SHUTTER_INPUT], input, group
82
+ )
83
+
84
+
85
+ @bluesky_retry
86
+ def setup_zebra_for_rotation(
87
+ zebra: Zebra,
88
+ zebra_shutter: ZebraShutter,
89
+ axis: EncEnum = I03Axes.OMEGA,
90
+ start_angle: float = 0,
91
+ scan_width: float = 360,
92
+ shutter_opening_deg: float = 2.5,
93
+ shutter_opening_s: float = 0.04,
94
+ direction: RotationDirection = RotationDirection.POSITIVE,
95
+ group: str = "setup_zebra_for_rotation",
96
+ wait: bool = True,
97
+ ):
98
+ """Set up the Zebra to collect a rotation dataset. Any plan using this is
99
+ responsible for setting the smargon velocity appropriately so that the desired
100
+ image width is achieved with the exposure time given here.
101
+
102
+ Parameters:
103
+ zebra: The zebra device to use
104
+ axis: I03 axes enum representing which axis to use for position
105
+ compare. Currently always omega.
106
+ start_angle: Position at which the scan should begin, in degrees.
107
+ scan_width: Total angle through which to collect, in degrees.
108
+ shutter_opening_deg:How many degrees of rotation it takes for the fast shutter
109
+ to open. Increases the gate width.
110
+ shutter_opening_s: How many seconds it takes for the fast shutter to open. The
111
+ detector pulse is delayed after the shutter signal by this
112
+ amount.
113
+ direction: RotationDirection enum for positive or negative.
114
+ Defaults to Positive.
115
+ group: A name for the group of statuses generated
116
+ wait: Block until all the settings have completed
117
+ """
118
+ if not isinstance(direction, RotationDirection):
119
+ raise ValueError(
120
+ "Disallowed rotation direction provided to Zebra setup plan. "
121
+ "Use RotationDirection.POSITIVE or RotationDirection.NEGATIVE."
122
+ )
123
+ yield from bps.abs_set(zebra.pc.dir, direction.value, group=group)
124
+ LOGGER.info("ZEBRA SETUP: START")
125
+ # Set gate start, adjust for shutter opening time if necessary
126
+ LOGGER.info(f"ZEBRA SETUP: degrees to adjust for shutter = {shutter_opening_deg}")
127
+ LOGGER.info(f"ZEBRA SETUP: start angle start: {start_angle}")
128
+ LOGGER.info(f"ZEBRA SETUP: start angle adjusted, gate start set to: {start_angle}")
129
+ yield from bps.abs_set(zebra.pc.gate_start, start_angle, group=group)
130
+ # set gate width to total width
131
+ yield from bps.abs_set(
132
+ zebra.pc.gate_width, scan_width + shutter_opening_deg, group=group
133
+ )
134
+ LOGGER.info(
135
+ f"Pulse start set to shutter open time, set to: {abs(shutter_opening_s)}"
136
+ )
137
+ yield from bps.abs_set(zebra.pc.pulse_start, abs(shutter_opening_s), group=group)
138
+ # Set gate position to be angle of interest
139
+ yield from bps.abs_set(zebra.pc.gate_trigger, axis.value, group=group)
140
+ # Trigger the shutter with the gate
141
+ yield from bps.abs_set(
142
+ zebra_shutter.control_mode, ZebraShutterControl.AUTO, group=group
143
+ )
144
+ yield from set_shutter_auto_input(zebra, PC_GATE, group=group)
145
+ # Trigger the detector with a pulse
146
+ yield from bps.abs_set(zebra.output.out_pvs[TTL_DETECTOR], PC_PULSE, group=group)
147
+ # Don't use the fluorescence detector
148
+ yield from bps.abs_set(zebra.output.out_pvs[TTL_XSPRESS3], DISCONNECT, group=group)
149
+ yield from bps.abs_set(zebra.output.pulse_1.input, DISCONNECT, group=group)
150
+ LOGGER.info(f"ZEBRA SETUP: END - {'' if wait else 'not'} waiting for completion")
151
+ if wait:
152
+ yield from bps.wait(group, timeout=ZEBRA_STATUS_TIMEOUT)
153
+
154
+
155
+ @bluesky_retry
156
+ def setup_zebra_for_gridscan(
157
+ zebra: Zebra,
158
+ zebra_shutter: ZebraShutter,
159
+ group="setup_zebra_for_gridscan",
160
+ wait=True,
161
+ ):
162
+ yield from bps.abs_set(zebra.output.out_pvs[TTL_DETECTOR], IN3_TTL, group=group)
163
+ yield from bps.abs_set(
164
+ zebra_shutter.control_mode, ZebraShutterControl.AUTO, group=group
165
+ )
166
+ yield from set_shutter_auto_input(zebra, IN4_TTL, group=group)
167
+ yield from bps.abs_set(zebra.output.out_pvs[TTL_XSPRESS3], DISCONNECT, group=group)
168
+ yield from bps.abs_set(zebra.output.pulse_1.input, DISCONNECT, group=group)
169
+
170
+ if wait:
171
+ yield from bps.wait(group, timeout=ZEBRA_STATUS_TIMEOUT)
172
+
173
+
174
+ @bluesky_retry
175
+ def tidy_up_zebra_after_gridscan(
176
+ zebra: Zebra,
177
+ zebra_shutter: ZebraShutter,
178
+ group="tidy_up_zebra_after_gridscan",
179
+ wait=True,
180
+ ) -> MsgGenerator:
181
+ yield from bps.abs_set(zebra.output.out_pvs[TTL_DETECTOR], PC_PULSE, group=group)
182
+ yield from bps.abs_set(
183
+ zebra_shutter.control_mode, ZebraShutterControl.MANUAL, group=group
184
+ )
185
+ yield from set_shutter_auto_input(zebra, PC_GATE, group=group)
186
+
187
+ if wait:
188
+ yield from bps.wait(group, timeout=ZEBRA_STATUS_TIMEOUT)
189
+
190
+
191
+ @bluesky_retry
192
+ def setup_zebra_for_panda_flyscan(
193
+ zebra: Zebra,
194
+ zebra_shutter: ZebraShutter,
195
+ group="setup_zebra_for_panda_flyscan",
196
+ wait=True,
197
+ ):
198
+ # Forwards eiger trigger signal from panda
199
+ yield from bps.abs_set(zebra.output.out_pvs[TTL_DETECTOR], IN1_TTL, group=group)
200
+
201
+ # Forwards signal from PPMAC to fast shutter. High while panda PLC is running
202
+ yield from bps.abs_set(
203
+ zebra_shutter.control_mode, ZebraShutterControl.AUTO, group=group
204
+ )
205
+ yield from set_shutter_auto_input(zebra, IN4_TTL, group=group)
206
+
207
+ yield from bps.abs_set(zebra.output.out_pvs[TTL_XSPRESS3], DISCONNECT, group=group)
208
+
209
+ yield from bps.abs_set(
210
+ zebra.output.out_pvs[TTL_PANDA], IN3_TTL, group=group
211
+ ) # Tells panda that motion is beginning/changing direction
212
+
213
+ if wait:
214
+ yield from bps.wait(group, timeout=ZEBRA_STATUS_TIMEOUT)