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,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,322 @@
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, MsgGenerator
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.backlight import Backlight
16
+ from dodal.devices.dcm import DCM
17
+ from dodal.devices.detector.detector_motion import DetectorMotion
18
+ from dodal.devices.eiger import EigerDetector
19
+ from dodal.devices.fast_grid_scan import PandAFastGridScan, ZebraFastGridScan
20
+ from dodal.devices.flux import Flux
21
+ from dodal.devices.focusing_mirror import FocusingMirrorWithStripes, VFMMirrorVoltages
22
+ from dodal.devices.motors import XYZPositioner
23
+ from dodal.devices.oav.oav_detector import OAV
24
+ from dodal.devices.oav.pin_image_recognition import PinTipDetection
25
+ from dodal.devices.robot import BartRobot, SampleLocation
26
+ from dodal.devices.s4_slit_gaps import S4SlitGaps
27
+ from dodal.devices.smargon import Smargon, StubPosition
28
+ from dodal.devices.synchrotron import Synchrotron
29
+ from dodal.devices.thawer import Thawer
30
+ from dodal.devices.undulator import Undulator
31
+ from dodal.devices.undulator_dcm import UndulatorDCM
32
+ from dodal.devices.webcam import Webcam
33
+ from dodal.devices.xbpm_feedback import XBPMFeedback
34
+ from dodal.devices.zebra import Zebra
35
+ from dodal.devices.zebra_controlled_shutter import ZebraShutter
36
+ from dodal.devices.zocalo import ZocaloResults
37
+ from dodal.plans.motor_util_plans import MoveTooLarge, home_and_reset_wrapper
38
+ from ophyd_async.fastcs.panda import HDFPanda
39
+
40
+ from mx_bluesky.hyperion.device_setup_plans.utils import (
41
+ start_preparing_data_collection_then_do_plan,
42
+ )
43
+ from mx_bluesky.hyperion.experiment_plans.grid_detect_then_xray_centre_plan import (
44
+ GridDetectThenXRayCentreComposite,
45
+ )
46
+ from mx_bluesky.hyperion.experiment_plans.pin_centre_then_xray_centre_plan import (
47
+ pin_centre_then_xray_centre_plan,
48
+ )
49
+ from mx_bluesky.hyperion.experiment_plans.set_energy_plan import (
50
+ SetEnergyComposite,
51
+ read_energy,
52
+ set_energy_plan,
53
+ )
54
+ from mx_bluesky.hyperion.log import LOGGER
55
+ from mx_bluesky.hyperion.parameters.constants import CONST
56
+ from mx_bluesky.hyperion.parameters.gridscan import RobotLoadThenCentre
57
+
58
+
59
+ @dataclasses.dataclass
60
+ class RobotLoadThenCentreComposite:
61
+ # common fields
62
+ xbpm_feedback: XBPMFeedback
63
+ attenuator: Attenuator
64
+
65
+ # GridDetectThenXRayCentreComposite fields
66
+ aperture_scatterguard: ApertureScatterguard
67
+ backlight: Backlight
68
+ detector_motion: DetectorMotion
69
+ eiger: EigerDetector
70
+ zebra_fast_grid_scan: ZebraFastGridScan
71
+ flux: Flux
72
+ oav: OAV
73
+ pin_tip_detection: PinTipDetection
74
+ smargon: Smargon
75
+ synchrotron: Synchrotron
76
+ s4_slit_gaps: S4SlitGaps
77
+ undulator: Undulator
78
+ zebra: Zebra
79
+ zocalo: ZocaloResults
80
+ panda: HDFPanda
81
+ panda_fast_grid_scan: PandAFastGridScan
82
+ thawer: Thawer
83
+ sample_shutter: ZebraShutter
84
+
85
+ # SetEnergyComposite fields
86
+ vfm: FocusingMirrorWithStripes
87
+ vfm_mirror_voltages: VFMMirrorVoltages
88
+ dcm: DCM
89
+ undulator_dcm: UndulatorDCM
90
+
91
+ # RobotLoad fields
92
+ robot: BartRobot
93
+ webcam: Webcam
94
+ lower_gonio: XYZPositioner
95
+
96
+
97
+ def create_devices(context: BlueskyContext) -> RobotLoadThenCentreComposite:
98
+ from mx_bluesky.hyperion.utils.context import device_composite_from_context
99
+
100
+ return device_composite_from_context(context, RobotLoadThenCentreComposite)
101
+
102
+
103
+ def wait_for_smargon_not_disabled(smargon: Smargon, timeout=60):
104
+ """Waits for the smargon disabled flag to go low. The robot hardware is responsible
105
+ for setting this to low when it is safe to move. It does this through a physical
106
+ connection between the robot and the smargon.
107
+ """
108
+ LOGGER.info("Waiting for smargon enabled")
109
+ SLEEP_PER_CHECK = 0.1
110
+ times_to_check = int(timeout / SLEEP_PER_CHECK)
111
+ for _ in range(times_to_check):
112
+ smargon_disabled = yield from bps.rd(smargon.disabled)
113
+ if not smargon_disabled:
114
+ LOGGER.info("Smargon now enabled")
115
+ return
116
+ yield from bps.sleep(SLEEP_PER_CHECK)
117
+ raise TimeoutError(
118
+ "Timed out waiting for smargon to become enabled after robot load"
119
+ )
120
+
121
+
122
+ def take_robot_snapshots(oav: OAV, webcam: Webcam, directory: Path):
123
+ time_now = datetime.now()
124
+ snapshot_format = f"{time_now.strftime('%H%M%S')}_{{device}}_after_load"
125
+ for device in [oav.snapshot, webcam]:
126
+ yield from bps.abs_set(
127
+ device.filename, snapshot_format.format(device=device.name)
128
+ )
129
+ yield from bps.abs_set(device.directory, str(directory))
130
+ # Note: should be able to use `wait=True` after https://github.com/bluesky/bluesky/issues/1795
131
+ yield from bps.trigger(device, group="snapshots")
132
+ yield from bps.wait("snapshots")
133
+
134
+
135
+ def prepare_for_robot_load(composite: RobotLoadThenCentreComposite):
136
+ yield from bps.abs_set(
137
+ composite.aperture_scatterguard,
138
+ ApertureValue.ROBOT_LOAD,
139
+ group="prepare_robot_load",
140
+ )
141
+
142
+ yield from bps.mv(composite.smargon.stub_offsets, StubPosition.RESET_TO_ROBOT_LOAD)
143
+
144
+ # fmt: off
145
+ yield from bps.mv(composite.smargon.x, 0,
146
+ composite.smargon.y, 0,
147
+ composite.smargon.z, 0,
148
+ composite.smargon.omega, 0,
149
+ composite.smargon.chi, 0,
150
+ composite.smargon.phi, 0)
151
+ # fmt: on
152
+
153
+ yield from bps.wait("prepare_robot_load")
154
+
155
+
156
+ def do_robot_load(
157
+ composite: RobotLoadThenCentreComposite,
158
+ sample_location: SampleLocation,
159
+ demand_energy_ev: float | None,
160
+ thawing_time: float,
161
+ ):
162
+ yield from bps.abs_set(
163
+ composite.robot,
164
+ sample_location,
165
+ group="robot_load",
166
+ )
167
+
168
+ if demand_energy_ev:
169
+ yield from set_energy_plan(
170
+ demand_energy_ev / 1000,
171
+ cast(SetEnergyComposite, composite),
172
+ )
173
+
174
+ yield from bps.wait("robot_load")
175
+
176
+ yield from bps.abs_set(
177
+ composite.thawer.thaw_for_time_s, thawing_time, group="thawing_finished"
178
+ )
179
+ yield from wait_for_smargon_not_disabled(composite.smargon)
180
+
181
+
182
+ def raise_exception_if_moved_out_of_cryojet(exception):
183
+ yield from bps.null()
184
+ if isinstance(exception, MoveTooLarge):
185
+ raise Exception(
186
+ f"Moving {exception.axis} back to {exception.position} after \
187
+ robot load would move it out of the cryojet. The max safe \
188
+ distance is {exception.maximum_move}"
189
+ )
190
+
191
+
192
+ def _pin_already_loaded(
193
+ robot: BartRobot, pin_to_load: int, puck_to_load: int
194
+ ) -> Generator[Msg, None, bool]:
195
+ current_puck = yield from bps.rd(robot.current_puck)
196
+ current_pin = yield from bps.rd(robot.current_pin)
197
+ return int(current_puck) == puck_to_load and int(current_pin) == pin_to_load
198
+
199
+
200
+ def robot_load_and_snapshots(
201
+ composite: RobotLoadThenCentreComposite,
202
+ params: RobotLoadThenCentre,
203
+ location: SampleLocation,
204
+ ):
205
+ robot_load_plan = do_robot_load(
206
+ composite,
207
+ location,
208
+ params.demand_energy_ev,
209
+ params.thawing_time,
210
+ )
211
+
212
+ # The lower gonio must be in the correct position for the robot load and we
213
+ # want to put it back afterwards. Note we don't wait the robot is interlocked
214
+ # to the lower gonio and the move is quicker than the robot takes to get to the
215
+ # load position.
216
+ yield from bpp.contingency_wrapper(
217
+ home_and_reset_wrapper(
218
+ robot_load_plan,
219
+ composite.lower_gonio,
220
+ BartRobot.LOAD_TOLERANCE_MM,
221
+ CONST.HARDWARE.CRYOJET_MARGIN_MM,
222
+ "lower_gonio",
223
+ wait_for_all=False,
224
+ ),
225
+ except_plan=raise_exception_if_moved_out_of_cryojet,
226
+ )
227
+
228
+ yield from take_robot_snapshots(
229
+ composite.oav, composite.webcam, params.snapshot_directory
230
+ )
231
+
232
+ yield from bps.create(name=CONST.DESCRIPTORS.ROBOT_LOAD)
233
+ yield from bps.read(composite.robot.barcode)
234
+ yield from bps.read(composite.oav.snapshot)
235
+ yield from bps.read(composite.webcam)
236
+ yield from bps.save()
237
+
238
+ yield from bps.wait("reset-lower_gonio")
239
+
240
+
241
+ def centring_plan_from_robot_load_params(
242
+ composite: RobotLoadThenCentreComposite,
243
+ params: RobotLoadThenCentre,
244
+ ):
245
+ yield from pin_centre_then_xray_centre_plan(
246
+ cast(GridDetectThenXRayCentreComposite, composite),
247
+ params.pin_centre_then_xray_centre_params(),
248
+ )
249
+
250
+
251
+ def robot_load_then_centre_plan(
252
+ composite: RobotLoadThenCentreComposite,
253
+ params: RobotLoadThenCentre,
254
+ sample_location: SampleLocation,
255
+ ):
256
+ yield from prepare_for_robot_load(composite)
257
+ yield from bpp.run_wrapper(
258
+ robot_load_and_snapshots(composite, params, sample_location),
259
+ md={
260
+ "subplan_name": CONST.PLAN.ROBOT_LOAD,
261
+ "metadata": {
262
+ "visit_path": str(params.visit_directory),
263
+ "sample_id": params.sample_id,
264
+ "sample_puck": params.sample_puck,
265
+ "sample_pin": params.sample_pin,
266
+ },
267
+ "activate_callbacks": [
268
+ "RobotLoadISPyBCallback",
269
+ ],
270
+ },
271
+ )
272
+
273
+ yield from centring_plan_from_robot_load_params(composite, params)
274
+
275
+
276
+ def robot_load_then_centre(
277
+ composite: RobotLoadThenCentreComposite,
278
+ parameters: RobotLoadThenCentre,
279
+ ) -> MsgGenerator:
280
+ eiger: EigerDetector = composite.eiger
281
+
282
+ # TODO: get these from one source of truth #254
283
+ assert parameters.sample_puck is not None
284
+ assert parameters.sample_pin is not None
285
+
286
+ doing_sample_load = not (
287
+ yield from _pin_already_loaded(
288
+ composite.robot, parameters.sample_pin, parameters.sample_puck
289
+ )
290
+ )
291
+
292
+ doing_chi_change = parameters.chi_start_deg is not None
293
+
294
+ if doing_sample_load:
295
+ plan = robot_load_then_centre_plan(
296
+ composite,
297
+ parameters,
298
+ SampleLocation(parameters.sample_puck, parameters.sample_pin),
299
+ )
300
+ LOGGER.info("Pin not loaded, loading and centring")
301
+ elif doing_chi_change:
302
+ plan = centring_plan_from_robot_load_params(composite, parameters)
303
+ LOGGER.info("Pin already loaded but chi changed so centring")
304
+ else:
305
+ LOGGER.info("Pin already loaded and chi not changed so doing nothing")
306
+ return
307
+
308
+ detector_params = parameters.detector_params
309
+ if not detector_params.expected_energy_ev:
310
+ actual_energy_ev = 1000 * (
311
+ yield from read_energy(cast(SetEnergyComposite, composite))
312
+ )
313
+ detector_params.expected_energy_ev = actual_energy_ev
314
+ eiger.set_detector_parameters(detector_params)
315
+
316
+ yield from start_preparing_data_collection_then_do_plan(
317
+ eiger,
318
+ composite.detector_motion,
319
+ parameters.detector_distance_mm,
320
+ plan,
321
+ group=CONST.WAIT.GRID_READY_FOR_DC,
322
+ )