mx-bluesky 0.3.1__py3-none-any.whl → 1.2.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 (142) 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 +55 -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 +93 -0
  46. mx_bluesky/hyperion/experiment_plans/flyscan_xray_centre_plan.py +537 -0
  47. mx_bluesky/hyperion/experiment_plans/grid_detect_then_xray_centre_plan.py +209 -0
  48. mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py +46 -0
  49. mx_bluesky/hyperion/experiment_plans/oav_grid_detection_plan.py +173 -0
  50. mx_bluesky/hyperion/experiment_plans/oav_snapshot_plan.py +81 -0
  51. mx_bluesky/hyperion/experiment_plans/optimise_attenuation_plan.py +463 -0
  52. mx_bluesky/hyperion/experiment_plans/pin_centre_then_xray_centre_plan.py +119 -0
  53. mx_bluesky/hyperion/experiment_plans/pin_tip_centring_plan.py +164 -0
  54. mx_bluesky/hyperion/experiment_plans/robot_load_and_change_energy.py +237 -0
  55. mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +162 -0
  56. mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +436 -0
  57. mx_bluesky/hyperion/experiment_plans/set_energy_plan.py +60 -0
  58. mx_bluesky/hyperion/external_interaction/__init__.py +9 -0
  59. mx_bluesky/hyperion/external_interaction/callbacks/__init__.py +10 -0
  60. mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +148 -0
  61. mx_bluesky/hyperion/external_interaction/callbacks/aperture_change_callback.py +22 -0
  62. mx_bluesky/hyperion/external_interaction/callbacks/common/__init__.py +0 -0
  63. mx_bluesky/hyperion/external_interaction/callbacks/common/callback_util.py +64 -0
  64. mx_bluesky/hyperion/external_interaction/callbacks/common/ispyb_mapping.py +62 -0
  65. mx_bluesky/hyperion/external_interaction/callbacks/grid_detection_callback.py +88 -0
  66. mx_bluesky/hyperion/external_interaction/callbacks/ispyb_callback_base.py +203 -0
  67. mx_bluesky/hyperion/external_interaction/callbacks/log_uid_tag_callback.py +20 -0
  68. mx_bluesky/hyperion/external_interaction/callbacks/logging_callback.py +29 -0
  69. mx_bluesky/hyperion/external_interaction/callbacks/plan_reactive_callback.py +101 -0
  70. mx_bluesky/hyperion/external_interaction/callbacks/robot_load/ispyb_callback.py +86 -0
  71. mx_bluesky/hyperion/external_interaction/callbacks/rotation/__init__.py +0 -0
  72. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py +174 -0
  73. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_mapping.py +17 -0
  74. mx_bluesky/hyperion/external_interaction/callbacks/rotation/nexus_callback.py +102 -0
  75. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/__init__.py +0 -0
  76. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/ispyb_callback.py +269 -0
  77. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/ispyb_mapping.py +53 -0
  78. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/nexus_callback.py +95 -0
  79. mx_bluesky/hyperion/external_interaction/callbacks/zocalo_callback.py +92 -0
  80. mx_bluesky/hyperion/external_interaction/config_server.py +35 -0
  81. mx_bluesky/hyperion/external_interaction/exceptions.py +13 -0
  82. mx_bluesky/hyperion/external_interaction/ispyb/__init__.py +0 -0
  83. mx_bluesky/hyperion/external_interaction/ispyb/data_model.py +95 -0
  84. mx_bluesky/hyperion/external_interaction/ispyb/exp_eye_store.py +125 -0
  85. mx_bluesky/hyperion/external_interaction/ispyb/ispyb_store.py +276 -0
  86. mx_bluesky/hyperion/external_interaction/ispyb/ispyb_utils.py +27 -0
  87. mx_bluesky/hyperion/external_interaction/nexus/__init__.py +0 -0
  88. mx_bluesky/hyperion/external_interaction/nexus/nexus_utils.py +148 -0
  89. mx_bluesky/hyperion/external_interaction/nexus/write_nexus.py +114 -0
  90. mx_bluesky/hyperion/log.py +99 -0
  91. mx_bluesky/hyperion/parameters/__init__.py +2 -0
  92. mx_bluesky/hyperion/parameters/cli.py +68 -0
  93. mx_bluesky/{parameters → hyperion/parameters}/components.py +80 -26
  94. mx_bluesky/hyperion/parameters/constants.py +158 -0
  95. mx_bluesky/hyperion/parameters/gridscan.py +221 -0
  96. mx_bluesky/hyperion/parameters/load_centre_collect.py +50 -0
  97. mx_bluesky/hyperion/parameters/robot_load.py +16 -0
  98. mx_bluesky/hyperion/parameters/rotation.py +160 -0
  99. mx_bluesky/hyperion/resources/panda/panda-gridscan.yaml +964 -0
  100. mx_bluesky/hyperion/tracing.py +28 -0
  101. mx_bluesky/hyperion/utils/context.py +84 -0
  102. mx_bluesky/hyperion/utils/utils.py +25 -0
  103. mx_bluesky/hyperion/utils/validation.py +196 -0
  104. mx_bluesky/jupyter_example.ipynb +3 -2
  105. {mx_bluesky-0.3.1.dist-info → mx_bluesky-1.2.0.dist-info}/METADATA +26 -11
  106. mx_bluesky-1.2.0.dist-info/RECORD +140 -0
  107. {mx_bluesky-0.3.1.dist-info → mx_bluesky-1.2.0.dist-info}/WHEEL +1 -1
  108. mx_bluesky-1.2.0.dist-info/entry_points.txt +8 -0
  109. mx_bluesky/i04/__init__.py +0 -3
  110. mx_bluesky/i24/serial/parameters/__init__.py +0 -15
  111. mx_bluesky/parameters/__init__.py +0 -31
  112. mx_bluesky-0.3.1.dist-info/RECORD +0 -67
  113. mx_bluesky-0.3.1.dist-info/entry_points.txt +0 -4
  114. /mx_bluesky/{i24 → beamlines}/__init__.py +0 -0
  115. /mx_bluesky/{i04 → beamlines/i04}/callbacks/murko_callback.py +0 -0
  116. /mx_bluesky/{i24/serial/extruder → beamlines/i24}/__init__.py +0 -0
  117. /mx_bluesky/{i24 → beamlines/i24}/serial/__init__.py +0 -0
  118. /mx_bluesky/{i24 → beamlines/i24}/serial/extruder/EX-gui-edm/microdrop_alignment.edl +0 -0
  119. /mx_bluesky/{i24/serial/fixed_target → beamlines/i24/serial/extruder}/__init__.py +0 -0
  120. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/ME14E-GeneralPurpose.edl +0 -0
  121. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/PMAC_Command.edl +0 -0
  122. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/Shutter_Control.edl +0 -0
  123. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/microdrop_alignment.edl +0 -0
  124. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/nudgechip.edl +0 -0
  125. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/short1-laser.png +0 -0
  126. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/short2-laser.png +0 -0
  127. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/ft_utils.py +0 -0
  128. /mx_bluesky/{i24 → beamlines/i24}/serial/parameters/fixed_target/cs/cs_maker.json +0 -0
  129. /mx_bluesky/{i24 → beamlines/i24}/serial/parameters/fixed_target/cs/motor_direction.txt +0 -0
  130. /mx_bluesky/{i24 → beamlines/i24}/serial/parameters/fixed_target/pvar_files/minichip-oxford.pvar +0 -0
  131. /mx_bluesky/{i24 → beamlines/i24}/serial/parameters/fixed_target/pvar_files/oxford.pvar +0 -0
  132. /mx_bluesky/{i24 → beamlines/i24}/serial/run_extruder.sh +0 -0
  133. /mx_bluesky/{i24 → beamlines/i24}/serial/run_fixed_target.sh +0 -0
  134. /mx_bluesky/{i24 → beamlines/i24}/serial/run_ssx.sh +0 -0
  135. /mx_bluesky/{i24 → beamlines/i24}/serial/set_visit_directory.sh +0 -0
  136. /mx_bluesky/{i24 → beamlines/i24}/serial/setup_beamline/__init__.py +0 -0
  137. /mx_bluesky/{i24 → beamlines/i24}/serial/setup_beamline/ca.py +0 -0
  138. /mx_bluesky/{i24 → beamlines/i24}/serial/setup_beamline/pv.py +0 -0
  139. /mx_bluesky/{i24 → beamlines/i24}/serial/setup_beamline/setup_zebra_plans.py +0 -0
  140. /mx_bluesky/{i24 → beamlines/i24}/serial/start_blueapi.sh +0 -0
  141. {mx_bluesky-0.3.1.dist-info → mx_bluesky-1.2.0.dist-info}/LICENSE +0 -0
  142. {mx_bluesky-0.3.1.dist-info → mx_bluesky-1.2.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,164 @@
1
+ import dataclasses
2
+ from collections.abc import Generator
3
+
4
+ import bluesky.plan_stubs as bps
5
+ from blueapi.core import BlueskyContext
6
+ from bluesky.utils import Msg
7
+ from dodal.devices.backlight import Backlight
8
+ from dodal.devices.oav.oav_detector import OAV
9
+ from dodal.devices.oav.oav_parameters import OAV_CONFIG_JSON, OAVParameters
10
+ from dodal.devices.oav.pin_image_recognition import PinTipDetection
11
+ from dodal.devices.oav.utils import (
12
+ Pixel,
13
+ get_move_required_so_that_beam_is_at_pixel,
14
+ wait_for_tip_to_be_found,
15
+ )
16
+ from dodal.devices.smargon import Smargon
17
+
18
+ from mx_bluesky.hyperion.device_setup_plans.setup_oav import pre_centring_setup_oav
19
+ from mx_bluesky.hyperion.device_setup_plans.smargon import (
20
+ move_smargon_warn_on_out_of_range,
21
+ )
22
+ from mx_bluesky.hyperion.exceptions import WarningException
23
+ from mx_bluesky.hyperion.log import LOGGER
24
+ from mx_bluesky.hyperion.parameters.constants import CONST
25
+ from mx_bluesky.hyperion.utils.context import device_composite_from_context
26
+
27
+ DEFAULT_STEP_SIZE = 0.5
28
+
29
+
30
+ @dataclasses.dataclass
31
+ class PinTipCentringComposite:
32
+ """All devices which are directly or indirectly required by this plan"""
33
+
34
+ backlight: Backlight
35
+ oav: OAV
36
+ smargon: Smargon
37
+ pin_tip_detection: PinTipDetection
38
+
39
+
40
+ def create_devices(context: BlueskyContext) -> PinTipCentringComposite:
41
+ return device_composite_from_context(context, PinTipCentringComposite)
42
+
43
+
44
+ def trigger_and_return_pin_tip(
45
+ pin_tip: PinTipDetection,
46
+ ) -> Generator[Msg, None, Pixel]:
47
+ yield from bps.trigger(pin_tip, wait=True)
48
+ tip_x_y_px = yield from bps.rd(pin_tip.triggered_tip)
49
+ LOGGER.info(f"Pin tip found at {tip_x_y_px}")
50
+ return tip_x_y_px # type: ignore
51
+
52
+
53
+ def move_pin_into_view(
54
+ pin_tip_device: PinTipDetection,
55
+ smargon: Smargon,
56
+ step_size_mm: float = DEFAULT_STEP_SIZE,
57
+ max_steps: int = 2,
58
+ ) -> Generator[Msg, None, Pixel]:
59
+ """Attempt to move the pin into view and return the tip location in pixels if found.
60
+ The gonio x is moved in a number of discrete steps to find the pin. If the move
61
+ would take it past its limit, it moves to the limit instead.
62
+
63
+ Args:
64
+ pin_tip_device (PinTipDetection): The device being used to detect the pin
65
+ smargon (Smargon): The gonio to move the tip
66
+ step_size (float, optional): Distance to move the gonio (in mm) for each
67
+ step of the search. Defaults to 0.5.
68
+ max_steps (int, optional): The number of steps to search with. Defaults to 2.
69
+
70
+ Raises:
71
+ WarningException: Error if the pin tip is never found
72
+
73
+ Returns:
74
+ Tuple[int, int]: The location of the pin tip in pixels
75
+ """
76
+
77
+ def pin_tip_valid(pin_x: float):
78
+ return pin_x != 0 and pin_x != pin_tip_device.INVALID_POSITION[0]
79
+
80
+ for _ in range(max_steps):
81
+ tip_x_px, tip_y_px = yield from trigger_and_return_pin_tip(pin_tip_device)
82
+
83
+ if pin_tip_valid(tip_x_px):
84
+ return (tip_x_px, tip_y_px)
85
+
86
+ if tip_x_px == 0:
87
+ # Pin is off in the -ve direction
88
+ step_size_mm = -step_size_mm
89
+
90
+ smargon_x = yield from bps.rd(smargon.x.user_readback)
91
+ ideal_move_to_find_pin = float(smargon_x) + step_size_mm
92
+ high_limit = yield from bps.rd(smargon.x.high_limit_travel)
93
+ low_limit = yield from bps.rd(smargon.x.low_limit_travel)
94
+ move_within_limits = max(min(ideal_move_to_find_pin, high_limit), low_limit)
95
+ if move_within_limits != ideal_move_to_find_pin:
96
+ LOGGER.warning(
97
+ f"Pin tip is off screen, and moving {step_size_mm} mm would cross limits, "
98
+ f"moving to {move_within_limits} instead"
99
+ )
100
+ yield from bps.mv(smargon.x, move_within_limits)
101
+
102
+ # Some time for the view to settle after the move
103
+ yield from bps.sleep(CONST.HARDWARE.OAV_REFRESH_DELAY)
104
+
105
+ tip_x_px, tip_y_px = yield from trigger_and_return_pin_tip(pin_tip_device)
106
+
107
+ if not pin_tip_valid(tip_x_px):
108
+ raise WarningException(
109
+ "Pin tip centring failed - pin too long/short/bent and out of range"
110
+ )
111
+ else:
112
+ return (tip_x_px, tip_y_px)
113
+
114
+
115
+ def pin_tip_centre_plan(
116
+ composite: PinTipCentringComposite,
117
+ tip_offset_microns: float,
118
+ oav_config_file: str = OAV_CONFIG_JSON,
119
+ ):
120
+ """Finds the tip of the pin and moves to roughly the centre based on this tip. Does
121
+ this at both the current omega angle and +90 deg from this angle so as to get a
122
+ centre in 3D.
123
+
124
+ Args:
125
+ tip_offset_microns (float): The x offset from the tip where the centre is assumed
126
+ to be.
127
+ """
128
+ oav: OAV = composite.oav
129
+ smargon: Smargon = composite.smargon
130
+ oav_params = OAVParameters("pinTipCentring", oav_config_file)
131
+
132
+ pin_tip_setup = composite.pin_tip_detection
133
+ pin_tip_detect = composite.pin_tip_detection
134
+
135
+ assert oav.parameters.micronsPerXPixel is not None
136
+ tip_offset_px = int(tip_offset_microns / oav.parameters.micronsPerXPixel)
137
+
138
+ def offset_and_move(tip: Pixel):
139
+ pixel_to_move_to = (tip[0] + tip_offset_px, tip[1])
140
+ position_mm = yield from get_move_required_so_that_beam_is_at_pixel(
141
+ smargon, pixel_to_move_to, oav.parameters
142
+ )
143
+ LOGGER.info(f"Tip centring moving to : {position_mm}")
144
+ yield from move_smargon_warn_on_out_of_range(smargon, position_mm)
145
+
146
+ LOGGER.info(f"Tip offset in pixels: {tip_offset_px}")
147
+
148
+ # need to wait for the OAV image to update
149
+ # See #673 for improvements
150
+ yield from bps.sleep(0.3)
151
+
152
+ yield from pre_centring_setup_oav(oav, oav_params, pin_tip_setup)
153
+
154
+ tip = yield from move_pin_into_view(pin_tip_detect, smargon)
155
+ yield from offset_and_move(tip)
156
+
157
+ yield from bps.mvr(smargon.omega, 90)
158
+
159
+ # need to wait for the OAV image to update
160
+ # See #673 for improvements
161
+ yield from bps.sleep(0.3)
162
+
163
+ tip = yield from wait_for_tip_to_be_found(pin_tip_detect)
164
+ yield from offset_and_move(tip)
@@ -0,0 +1,237 @@
1
+ from __future__ import annotations
2
+
3
+ import dataclasses
4
+ from collections.abc import Generator
5
+ from datetime import datetime
6
+ from pathlib import Path
7
+ from typing import cast
8
+
9
+ import bluesky.plan_stubs as bps
10
+ import bluesky.preprocessors as bpp
11
+ from blueapi.core import BlueskyContext
12
+ from bluesky.utils import Msg
13
+ from dodal.devices.aperturescatterguard import ApertureScatterguard, ApertureValue
14
+ from dodal.devices.attenuator import Attenuator
15
+ from dodal.devices.dcm import DCM
16
+ from dodal.devices.focusing_mirror import FocusingMirrorWithStripes, VFMMirrorVoltages
17
+ from dodal.devices.motors import XYZPositioner
18
+ from dodal.devices.oav.oav_detector import OAV
19
+ from dodal.devices.robot import BartRobot, SampleLocation
20
+ from dodal.devices.smargon import Smargon, StubPosition
21
+ from dodal.devices.thawer import Thawer
22
+ from dodal.devices.undulator_dcm import UndulatorDCM
23
+ from dodal.devices.webcam import Webcam
24
+ from dodal.devices.xbpm_feedback import XBPMFeedback
25
+ from dodal.plans.motor_util_plans import MoveTooLarge, home_and_reset_wrapper
26
+
27
+ from mx_bluesky.hyperion.experiment_plans.set_energy_plan import (
28
+ SetEnergyComposite,
29
+ set_energy_plan,
30
+ )
31
+ from mx_bluesky.hyperion.log import LOGGER
32
+ from mx_bluesky.hyperion.parameters.constants import CONST
33
+ from mx_bluesky.hyperion.parameters.robot_load import RobotLoadAndEnergyChange
34
+
35
+
36
+ @dataclasses.dataclass
37
+ class RobotLoadAndEnergyChangeComposite:
38
+ # SetEnergyComposite fields
39
+ vfm: FocusingMirrorWithStripes
40
+ vfm_mirror_voltages: VFMMirrorVoltages
41
+ dcm: DCM
42
+ undulator_dcm: UndulatorDCM
43
+ xbpm_feedback: XBPMFeedback
44
+ attenuator: Attenuator
45
+
46
+ # RobotLoad fields
47
+ robot: BartRobot
48
+ webcam: Webcam
49
+ lower_gonio: XYZPositioner
50
+ thawer: Thawer
51
+ oav: OAV
52
+ smargon: Smargon
53
+ aperture_scatterguard: ApertureScatterguard
54
+
55
+
56
+ def create_devices(context: BlueskyContext) -> RobotLoadAndEnergyChangeComposite:
57
+ from mx_bluesky.hyperion.utils.context import device_composite_from_context
58
+
59
+ return device_composite_from_context(context, RobotLoadAndEnergyChangeComposite)
60
+
61
+
62
+ def wait_for_smargon_not_disabled(smargon: Smargon, timeout=60):
63
+ """Waits for the smargon disabled flag to go low. The robot hardware is responsible
64
+ for setting this to low when it is safe to move. It does this through a physical
65
+ connection between the robot and the smargon.
66
+ """
67
+ LOGGER.info("Waiting for smargon enabled")
68
+ SLEEP_PER_CHECK = 0.1
69
+ times_to_check = int(timeout / SLEEP_PER_CHECK)
70
+ for _ in range(times_to_check):
71
+ smargon_disabled = yield from bps.rd(smargon.disabled)
72
+ if not smargon_disabled:
73
+ LOGGER.info("Smargon now enabled")
74
+ return
75
+ yield from bps.sleep(SLEEP_PER_CHECK)
76
+ raise TimeoutError(
77
+ "Timed out waiting for smargon to become enabled after robot load"
78
+ )
79
+
80
+
81
+ def take_robot_snapshots(oav: OAV, webcam: Webcam, directory: Path):
82
+ time_now = datetime.now()
83
+ snapshot_format = f"{time_now.strftime('%H%M%S')}_{{device}}_after_load"
84
+ for device in [oav.snapshot, webcam]:
85
+ yield from bps.abs_set(
86
+ device.filename, snapshot_format.format(device=device.name)
87
+ )
88
+ yield from bps.abs_set(device.directory, str(directory))
89
+ # Note: should be able to use `wait=True` after https://github.com/bluesky/bluesky/issues/1795
90
+ yield from bps.trigger(device, group="snapshots")
91
+ yield from bps.wait("snapshots")
92
+
93
+
94
+ def prepare_for_robot_load(
95
+ aperture_scatterguard: ApertureScatterguard, smargon: Smargon
96
+ ):
97
+ yield from bps.abs_set(
98
+ aperture_scatterguard,
99
+ ApertureValue.ROBOT_LOAD,
100
+ group="prepare_robot_load",
101
+ )
102
+
103
+ yield from bps.mv(smargon.stub_offsets, StubPosition.RESET_TO_ROBOT_LOAD)
104
+
105
+ # fmt: off
106
+ yield from bps.mv(smargon.x, 0,
107
+ smargon.y, 0,
108
+ smargon.z, 0,
109
+ smargon.omega, 0,
110
+ smargon.chi, 0,
111
+ smargon.phi, 0)
112
+ # fmt: on
113
+
114
+ yield from bps.wait("prepare_robot_load")
115
+
116
+
117
+ def do_robot_load(
118
+ composite: RobotLoadAndEnergyChangeComposite,
119
+ sample_location: SampleLocation,
120
+ demand_energy_ev: float | None,
121
+ thawing_time: float,
122
+ ):
123
+ yield from bps.abs_set(
124
+ composite.robot,
125
+ sample_location,
126
+ group="robot_load",
127
+ )
128
+
129
+ if demand_energy_ev:
130
+ yield from set_energy_plan(
131
+ demand_energy_ev / 1000,
132
+ cast(SetEnergyComposite, composite),
133
+ )
134
+
135
+ yield from bps.wait("robot_load")
136
+
137
+ yield from bps.abs_set(
138
+ composite.thawer.thaw_for_time_s, thawing_time, group="thawing_finished"
139
+ )
140
+ yield from wait_for_smargon_not_disabled(composite.smargon)
141
+
142
+
143
+ def raise_exception_if_moved_out_of_cryojet(exception):
144
+ yield from bps.null()
145
+ if isinstance(exception, MoveTooLarge):
146
+ raise Exception(
147
+ f"Moving {exception.axis} back to {exception.position} after \
148
+ robot load would move it out of the cryojet. The max safe \
149
+ distance is {exception.maximum_move}"
150
+ )
151
+
152
+
153
+ def pin_already_loaded(
154
+ robot: BartRobot, sample_location: SampleLocation
155
+ ) -> Generator[Msg, None, bool]:
156
+ current_puck = yield from bps.rd(robot.current_puck)
157
+ current_pin = yield from bps.rd(robot.current_pin)
158
+ return (
159
+ int(current_puck) == sample_location.puck
160
+ and int(current_pin) == sample_location.pin
161
+ )
162
+
163
+
164
+ def robot_load_and_snapshots(
165
+ composite: RobotLoadAndEnergyChangeComposite,
166
+ location: SampleLocation,
167
+ snapshot_directory: Path,
168
+ thawing_time: float,
169
+ demand_energy_ev: float | None,
170
+ ):
171
+ robot_load_plan = do_robot_load(
172
+ composite,
173
+ location,
174
+ demand_energy_ev,
175
+ thawing_time,
176
+ )
177
+
178
+ # The lower gonio must be in the correct position for the robot load and we
179
+ # want to put it back afterwards. Note we don't wait the robot is interlocked
180
+ # to the lower gonio and the move is quicker than the robot takes to get to the
181
+ # load position.
182
+ yield from bpp.contingency_wrapper(
183
+ home_and_reset_wrapper(
184
+ robot_load_plan,
185
+ composite.lower_gonio,
186
+ BartRobot.LOAD_TOLERANCE_MM,
187
+ CONST.HARDWARE.CRYOJET_MARGIN_MM,
188
+ "lower_gonio",
189
+ wait_for_all=False,
190
+ ),
191
+ except_plan=raise_exception_if_moved_out_of_cryojet,
192
+ )
193
+
194
+ yield from take_robot_snapshots(composite.oav, composite.webcam, snapshot_directory)
195
+
196
+ yield from bps.create(name=CONST.DESCRIPTORS.ROBOT_LOAD)
197
+ yield from bps.read(composite.robot.barcode)
198
+ yield from bps.read(composite.oav.snapshot)
199
+ yield from bps.read(composite.webcam)
200
+ yield from bps.save()
201
+
202
+ yield from bps.wait("reset-lower_gonio")
203
+
204
+
205
+ def robot_load_and_change_energy_plan(
206
+ composite: RobotLoadAndEnergyChangeComposite,
207
+ params: RobotLoadAndEnergyChange,
208
+ ):
209
+ assert params.sample_puck is not None
210
+ assert params.sample_pin is not None
211
+
212
+ sample_location = SampleLocation(params.sample_puck, params.sample_pin)
213
+
214
+ yield from prepare_for_robot_load(
215
+ composite.aperture_scatterguard, composite.smargon
216
+ )
217
+ yield from bpp.run_wrapper(
218
+ robot_load_and_snapshots(
219
+ composite,
220
+ sample_location,
221
+ params.snapshot_directory,
222
+ params.thawing_time,
223
+ params.demand_energy_ev,
224
+ ),
225
+ md={
226
+ "subplan_name": CONST.PLAN.ROBOT_LOAD,
227
+ "metadata": {
228
+ "visit": params.visit,
229
+ "sample_id": params.sample_id,
230
+ "sample_puck": sample_location.puck,
231
+ "sample_pin": sample_location.pin,
232
+ },
233
+ "activate_callbacks": [
234
+ "RobotLoadISPyBCallback",
235
+ ],
236
+ },
237
+ )
@@ -0,0 +1,162 @@
1
+ from __future__ import annotations
2
+
3
+ import dataclasses
4
+ from typing import cast
5
+
6
+ from blueapi.core import BlueskyContext, MsgGenerator
7
+ from dodal.devices.aperturescatterguard import ApertureScatterguard
8
+ from dodal.devices.attenuator import Attenuator
9
+ from dodal.devices.backlight import Backlight
10
+ from dodal.devices.dcm import DCM
11
+ from dodal.devices.detector.detector_motion import DetectorMotion
12
+ from dodal.devices.eiger import EigerDetector
13
+ from dodal.devices.fast_grid_scan import PandAFastGridScan, ZebraFastGridScan
14
+ from dodal.devices.flux import Flux
15
+ from dodal.devices.focusing_mirror import FocusingMirrorWithStripes, VFMMirrorVoltages
16
+ from dodal.devices.motors import XYZPositioner
17
+ from dodal.devices.oav.oav_detector import OAV
18
+ from dodal.devices.oav.pin_image_recognition import PinTipDetection
19
+ from dodal.devices.robot import BartRobot, SampleLocation
20
+ from dodal.devices.s4_slit_gaps import S4SlitGaps
21
+ from dodal.devices.smargon import Smargon
22
+ from dodal.devices.synchrotron import Synchrotron
23
+ from dodal.devices.thawer import Thawer
24
+ from dodal.devices.undulator import Undulator
25
+ from dodal.devices.undulator_dcm import UndulatorDCM
26
+ from dodal.devices.webcam import Webcam
27
+ from dodal.devices.xbpm_feedback import XBPMFeedback
28
+ from dodal.devices.zebra import Zebra
29
+ from dodal.devices.zebra_controlled_shutter import ZebraShutter
30
+ from dodal.devices.zocalo import ZocaloResults
31
+ from dodal.log import LOGGER
32
+ from ophyd_async.fastcs.panda import HDFPanda
33
+
34
+ from mx_bluesky.hyperion.device_setup_plans.utils import (
35
+ fill_in_energy_if_not_supplied,
36
+ start_preparing_data_collection_then_do_plan,
37
+ )
38
+ from mx_bluesky.hyperion.experiment_plans.grid_detect_then_xray_centre_plan import (
39
+ GridDetectThenXRayCentreComposite,
40
+ )
41
+ from mx_bluesky.hyperion.experiment_plans.pin_centre_then_xray_centre_plan import (
42
+ pin_centre_then_xray_centre_plan,
43
+ )
44
+ from mx_bluesky.hyperion.experiment_plans.robot_load_and_change_energy import (
45
+ RobotLoadAndEnergyChangeComposite,
46
+ pin_already_loaded,
47
+ robot_load_and_change_energy_plan,
48
+ )
49
+ from mx_bluesky.hyperion.parameters.constants import CONST
50
+ from mx_bluesky.hyperion.parameters.gridscan import RobotLoadThenCentre
51
+
52
+
53
+ @dataclasses.dataclass
54
+ class RobotLoadThenCentreComposite:
55
+ # common fields
56
+ xbpm_feedback: XBPMFeedback
57
+ attenuator: Attenuator
58
+
59
+ # GridDetectThenXRayCentreComposite fields
60
+ aperture_scatterguard: ApertureScatterguard
61
+ backlight: Backlight
62
+ detector_motion: DetectorMotion
63
+ eiger: EigerDetector
64
+ zebra_fast_grid_scan: ZebraFastGridScan
65
+ flux: Flux
66
+ oav: OAV
67
+ pin_tip_detection: PinTipDetection
68
+ smargon: Smargon
69
+ synchrotron: Synchrotron
70
+ s4_slit_gaps: S4SlitGaps
71
+ undulator: Undulator
72
+ zebra: Zebra
73
+ zocalo: ZocaloResults
74
+ panda: HDFPanda
75
+ panda_fast_grid_scan: PandAFastGridScan
76
+ thawer: Thawer
77
+ sample_shutter: ZebraShutter
78
+
79
+ # SetEnergyComposite fields
80
+ vfm: FocusingMirrorWithStripes
81
+ vfm_mirror_voltages: VFMMirrorVoltages
82
+ dcm: DCM
83
+ undulator_dcm: UndulatorDCM
84
+
85
+ # RobotLoad fields
86
+ robot: BartRobot
87
+ webcam: Webcam
88
+ lower_gonio: XYZPositioner
89
+
90
+
91
+ def create_devices(context: BlueskyContext) -> RobotLoadThenCentreComposite:
92
+ from mx_bluesky.hyperion.utils.context import device_composite_from_context
93
+
94
+ return device_composite_from_context(context, RobotLoadThenCentreComposite)
95
+
96
+
97
+ def centring_plan_from_robot_load_params(
98
+ composite: RobotLoadThenCentreComposite,
99
+ params: RobotLoadThenCentre,
100
+ ):
101
+ yield from pin_centre_then_xray_centre_plan(
102
+ cast(GridDetectThenXRayCentreComposite, composite),
103
+ params.pin_centre_then_xray_centre_params(),
104
+ )
105
+
106
+
107
+ def robot_load_then_centre_plan(
108
+ composite: RobotLoadThenCentreComposite,
109
+ params: RobotLoadThenCentre,
110
+ ):
111
+ yield from robot_load_and_change_energy_plan(
112
+ cast(RobotLoadAndEnergyChangeComposite, composite),
113
+ params.robot_load_params(),
114
+ )
115
+
116
+ yield from centring_plan_from_robot_load_params(composite, params)
117
+
118
+
119
+ def robot_load_then_centre(
120
+ composite: RobotLoadThenCentreComposite,
121
+ parameters: RobotLoadThenCentre,
122
+ ) -> MsgGenerator:
123
+ eiger: EigerDetector = composite.eiger
124
+
125
+ # TODO: get these from one source of truth #254
126
+ assert parameters.sample_puck is not None
127
+ assert parameters.sample_pin is not None
128
+
129
+ sample_location = SampleLocation(parameters.sample_puck, parameters.sample_pin)
130
+
131
+ doing_sample_load = not (
132
+ yield from pin_already_loaded(composite.robot, sample_location)
133
+ )
134
+
135
+ doing_chi_change = parameters.chi_start_deg is not None
136
+
137
+ if doing_sample_load:
138
+ plan = robot_load_then_centre_plan(
139
+ composite,
140
+ parameters,
141
+ )
142
+ LOGGER.info("Pin not loaded, loading and centring")
143
+ elif doing_chi_change:
144
+ plan = centring_plan_from_robot_load_params(composite, parameters)
145
+ LOGGER.info("Pin already loaded but chi changed so centring")
146
+ else:
147
+ LOGGER.info("Pin already loaded and chi not changed so doing nothing")
148
+ return
149
+
150
+ detector_params = yield from fill_in_energy_if_not_supplied(
151
+ composite.dcm, parameters.detector_params
152
+ )
153
+
154
+ eiger.set_detector_parameters(detector_params)
155
+
156
+ yield from start_preparing_data_collection_then_do_plan(
157
+ eiger,
158
+ composite.detector_motion,
159
+ parameters.detector_distance_mm,
160
+ plan,
161
+ group=CONST.WAIT.GRID_READY_FOR_DC,
162
+ )