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,209 @@
1
+ from __future__ import annotations
2
+
3
+ import dataclasses
4
+ from pathlib import Path
5
+
6
+ from blueapi.core import BlueskyContext, MsgGenerator
7
+ from bluesky import plan_stubs as bps
8
+ from bluesky import preprocessors as bpp
9
+ from dodal.devices.aperturescatterguard import ApertureScatterguard
10
+ from dodal.devices.attenuator import Attenuator
11
+ from dodal.devices.backlight import Backlight, BacklightPosition
12
+ from dodal.devices.dcm import DCM
13
+ from dodal.devices.detector.detector_motion import DetectorMotion
14
+ from dodal.devices.eiger import EigerDetector
15
+ from dodal.devices.fast_grid_scan import PandAFastGridScan, ZebraFastGridScan
16
+ from dodal.devices.flux import Flux
17
+ from dodal.devices.oav.oav_detector import OAV
18
+ from dodal.devices.oav.oav_parameters import OAV_CONFIG_JSON, OAVParameters
19
+ from dodal.devices.oav.pin_image_recognition import PinTipDetection
20
+ from dodal.devices.robot import BartRobot
21
+ from dodal.devices.s4_slit_gaps import S4SlitGaps
22
+ from dodal.devices.smargon import Smargon
23
+ from dodal.devices.synchrotron import Synchrotron
24
+ from dodal.devices.undulator import Undulator
25
+ from dodal.devices.xbpm_feedback import XBPMFeedback
26
+ from dodal.devices.zebra import Zebra
27
+ from dodal.devices.zebra_controlled_shutter import ZebraShutter
28
+ from dodal.devices.zocalo import ZocaloResults
29
+ from ophyd_async.fastcs.panda import HDFPanda
30
+
31
+ from mx_bluesky.hyperion.device_setup_plans.manipulate_sample import (
32
+ move_aperture_if_required,
33
+ )
34
+ from mx_bluesky.hyperion.device_setup_plans.utils import (
35
+ start_preparing_data_collection_then_do_plan,
36
+ )
37
+ from mx_bluesky.hyperion.experiment_plans.flyscan_xray_centre_plan import (
38
+ FlyScanXRayCentreComposite as FlyScanXRayCentreComposite,
39
+ )
40
+ from mx_bluesky.hyperion.experiment_plans.flyscan_xray_centre_plan import (
41
+ flyscan_xray_centre,
42
+ )
43
+ from mx_bluesky.hyperion.experiment_plans.oav_grid_detection_plan import (
44
+ OavGridDetectionComposite,
45
+ grid_detection_plan,
46
+ )
47
+ from mx_bluesky.hyperion.external_interaction.callbacks.grid_detection_callback import (
48
+ GridDetectionCallback,
49
+ GridParamUpdate,
50
+ )
51
+ from mx_bluesky.hyperion.external_interaction.callbacks.xray_centre.ispyb_callback import (
52
+ ispyb_activation_wrapper,
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 (
57
+ GridScanWithEdgeDetect,
58
+ ThreeDGridScan,
59
+ )
60
+ from mx_bluesky.hyperion.utils.context import device_composite_from_context
61
+
62
+
63
+ @dataclasses.dataclass
64
+ class GridDetectThenXRayCentreComposite:
65
+ """All devices which are directly or indirectly required by this plan"""
66
+
67
+ aperture_scatterguard: ApertureScatterguard
68
+ attenuator: Attenuator
69
+ backlight: Backlight
70
+ dcm: DCM
71
+ detector_motion: DetectorMotion
72
+ eiger: EigerDetector
73
+ zebra_fast_grid_scan: ZebraFastGridScan
74
+ flux: Flux
75
+ oav: OAV
76
+ pin_tip_detection: PinTipDetection
77
+ smargon: Smargon
78
+ synchrotron: Synchrotron
79
+ s4_slit_gaps: S4SlitGaps
80
+ undulator: Undulator
81
+ xbpm_feedback: XBPMFeedback
82
+ zebra: Zebra
83
+ zocalo: ZocaloResults
84
+ panda: HDFPanda
85
+ panda_fast_grid_scan: PandAFastGridScan
86
+ robot: BartRobot
87
+ sample_shutter: ZebraShutter
88
+
89
+
90
+ def create_devices(context: BlueskyContext) -> GridDetectThenXRayCentreComposite:
91
+ return device_composite_from_context(context, GridDetectThenXRayCentreComposite)
92
+
93
+
94
+ def create_parameters_for_flyscan_xray_centre(
95
+ grid_scan_with_edge_params: GridScanWithEdgeDetect,
96
+ grid_parameters: GridParamUpdate,
97
+ ) -> ThreeDGridScan:
98
+ params_json = grid_scan_with_edge_params.model_dump()
99
+ params_json.update(grid_parameters)
100
+ flyscan_xray_centre_parameters = ThreeDGridScan(**params_json)
101
+ LOGGER.info(f"Parameters for FGS: {flyscan_xray_centre_parameters}")
102
+ return flyscan_xray_centre_parameters
103
+
104
+
105
+ def detect_grid_and_do_gridscan(
106
+ composite: GridDetectThenXRayCentreComposite,
107
+ parameters: GridScanWithEdgeDetect,
108
+ oav_params: OAVParameters,
109
+ ):
110
+ snapshot_template = f"{parameters.detector_params.prefix}_{parameters.detector_params.run_number}_{{angle}}"
111
+
112
+ grid_params_callback = GridDetectionCallback(composite.oav.parameters)
113
+
114
+ @bpp.subs_decorator([grid_params_callback])
115
+ def run_grid_detection_plan(
116
+ oav_params,
117
+ snapshot_template,
118
+ snapshot_dir: Path,
119
+ ):
120
+ grid_detect_composite = OavGridDetectionComposite(
121
+ backlight=composite.backlight,
122
+ oav=composite.oav,
123
+ smargon=composite.smargon,
124
+ pin_tip_detection=composite.pin_tip_detection,
125
+ )
126
+
127
+ yield from grid_detection_plan(
128
+ grid_detect_composite,
129
+ oav_params,
130
+ snapshot_template,
131
+ str(snapshot_dir),
132
+ grid_width_microns=parameters.grid_width_um,
133
+ )
134
+
135
+ yield from run_grid_detection_plan(
136
+ oav_params,
137
+ snapshot_template,
138
+ parameters.snapshot_directory,
139
+ )
140
+
141
+ yield from bps.abs_set(
142
+ composite.backlight, BacklightPosition.OUT, group=CONST.WAIT.GRID_READY_FOR_DC
143
+ )
144
+
145
+ yield from move_aperture_if_required(
146
+ composite.aperture_scatterguard,
147
+ parameters.selected_aperture,
148
+ group=CONST.WAIT.GRID_READY_FOR_DC,
149
+ )
150
+
151
+ yield from flyscan_xray_centre(
152
+ FlyScanXRayCentreComposite(
153
+ aperture_scatterguard=composite.aperture_scatterguard,
154
+ attenuator=composite.attenuator,
155
+ backlight=composite.backlight,
156
+ eiger=composite.eiger,
157
+ panda_fast_grid_scan=composite.panda_fast_grid_scan,
158
+ flux=composite.flux,
159
+ s4_slit_gaps=composite.s4_slit_gaps,
160
+ smargon=composite.smargon,
161
+ undulator=composite.undulator,
162
+ synchrotron=composite.synchrotron,
163
+ xbpm_feedback=composite.xbpm_feedback,
164
+ zebra=composite.zebra,
165
+ zocalo=composite.zocalo,
166
+ panda=composite.panda,
167
+ zebra_fast_grid_scan=composite.zebra_fast_grid_scan,
168
+ dcm=composite.dcm,
169
+ robot=composite.robot,
170
+ sample_shutter=composite.sample_shutter,
171
+ ),
172
+ create_parameters_for_flyscan_xray_centre(
173
+ parameters, grid_params_callback.get_grid_parameters()
174
+ ),
175
+ )
176
+
177
+
178
+ def grid_detect_then_xray_centre(
179
+ composite: GridDetectThenXRayCentreComposite,
180
+ parameters: GridScanWithEdgeDetect,
181
+ oav_config: str = OAV_CONFIG_JSON,
182
+ ) -> MsgGenerator:
183
+ """
184
+ A plan which combines the collection of snapshots from the OAV and the determination
185
+ of the grid dimensions to use for the following grid scan.
186
+ """
187
+
188
+ eiger: EigerDetector = composite.eiger
189
+
190
+ eiger.set_detector_parameters(parameters.detector_params)
191
+
192
+ oav_params = OAVParameters("xrayCentring", oav_config)
193
+
194
+ plan_to_perform = ispyb_activation_wrapper(
195
+ detect_grid_and_do_gridscan(
196
+ composite,
197
+ parameters,
198
+ oav_params,
199
+ ),
200
+ parameters,
201
+ )
202
+
203
+ return start_preparing_data_collection_then_do_plan(
204
+ eiger,
205
+ composite.detector_motion,
206
+ parameters.detector_params.detector_distance,
207
+ plan_to_perform,
208
+ group=CONST.WAIT.GRID_READY_FOR_DC,
209
+ )
@@ -0,0 +1,173 @@
1
+ from __future__ import annotations
2
+
3
+ import dataclasses
4
+ import math
5
+ from typing import TYPE_CHECKING
6
+
7
+ import bluesky.plan_stubs as bps
8
+ import numpy as np
9
+ from blueapi.core import BlueskyContext
10
+ from dodal.devices.backlight import Backlight
11
+ from dodal.devices.oav.oav_detector import OAV
12
+ from dodal.devices.oav.pin_image_recognition import PinTipDetection
13
+ from dodal.devices.oav.pin_image_recognition.utils import NONE_VALUE
14
+ from dodal.devices.oav.utils import PinNotFoundException, wait_for_tip_to_be_found
15
+ from dodal.devices.smargon import Smargon
16
+
17
+ from mx_bluesky.hyperion.device_setup_plans.setup_oav import (
18
+ pre_centring_setup_oav,
19
+ )
20
+ from mx_bluesky.hyperion.exceptions import catch_exception_and_warn
21
+ from mx_bluesky.hyperion.log import LOGGER
22
+ from mx_bluesky.hyperion.parameters.constants import CONST
23
+ from mx_bluesky.hyperion.utils.context import device_composite_from_context
24
+
25
+ if TYPE_CHECKING:
26
+ from dodal.devices.oav.oav_parameters import OAVParameters
27
+
28
+
29
+ @dataclasses.dataclass
30
+ class OavGridDetectionComposite:
31
+ """All devices which are directly or indirectly required by this plan"""
32
+
33
+ backlight: Backlight
34
+ oav: OAV
35
+ smargon: Smargon
36
+ pin_tip_detection: PinTipDetection
37
+
38
+
39
+ def create_devices(context: BlueskyContext) -> OavGridDetectionComposite:
40
+ return device_composite_from_context(context, OavGridDetectionComposite)
41
+
42
+
43
+ def get_min_and_max_y_of_pin(
44
+ top: np.ndarray, bottom: np.ndarray, full_image_height_px: int
45
+ ) -> tuple[int, int]:
46
+ """Gives the minimum and maximum y that would cover the whole pin.
47
+
48
+ First filters out where no edge was found or the edge covers the full image.
49
+ If this results in no edges found then returns a min/max that covers the full image
50
+ """
51
+ filtered_top = top[np.where((top != 0) & (top != NONE_VALUE))]
52
+ min_y = min(filtered_top) if len(filtered_top) else 0
53
+ filtered_bottom = bottom[
54
+ np.where((bottom != full_image_height_px) & (bottom != NONE_VALUE))
55
+ ]
56
+ max_y = max(filtered_bottom) if len(filtered_bottom) else full_image_height_px
57
+ return min_y, max_y
58
+
59
+
60
+ def grid_detection_plan(
61
+ composite: OavGridDetectionComposite,
62
+ parameters: OAVParameters,
63
+ snapshot_template: str,
64
+ snapshot_dir: str,
65
+ grid_width_microns: float,
66
+ box_size_um: float = 20,
67
+ ):
68
+ """
69
+ Creates the parameters for two grids that are 90 degrees from each other and
70
+ encompass the whole of the sample as it appears in the OAV.
71
+
72
+ Args:
73
+ composite (OavGridDetectionComposite): Composite containing devices for doing a grid detection.
74
+ parameters (OAVParameters): Object containing parameters for setting up the OAV
75
+ snapshot_template (str): A template for the name of the snapshots, expected to be filled in with an angle
76
+ snapshot_dir (str): The location to save snapshots
77
+ grid_width_microns (int): The width of the grid to scan in microns
78
+ box_size_um (float): The size of each box of the grid in microns
79
+ """
80
+ oav: OAV = composite.oav
81
+ smargon: Smargon = composite.smargon
82
+ pin_tip_detection: PinTipDetection = composite.pin_tip_detection
83
+
84
+ LOGGER.info("OAV Centring: Starting grid detection centring")
85
+
86
+ yield from bps.wait()
87
+
88
+ # Set relevant PVs to whatever the config dictates.
89
+ yield from pre_centring_setup_oav(oav, parameters, pin_tip_detection)
90
+
91
+ LOGGER.info("OAV Centring: Camera set up")
92
+
93
+ assert isinstance(oav.parameters.micronsPerXPixel, float)
94
+ box_size_x_pixels = box_size_um / oav.parameters.micronsPerXPixel
95
+ assert isinstance(oav.parameters.micronsPerYPixel, float)
96
+ box_size_y_pixels = box_size_um / oav.parameters.micronsPerYPixel
97
+
98
+ grid_width_pixels = int(grid_width_microns / oav.parameters.micronsPerXPixel)
99
+
100
+ # The FGS uses -90 so we need to match it
101
+ for angle in [0, -90]:
102
+ yield from bps.mv(smargon.omega, angle)
103
+ # need to wait for the OAV image to update
104
+ # See #673 for improvements
105
+ yield from bps.sleep(CONST.HARDWARE.OAV_REFRESH_DELAY)
106
+
107
+ tip_x_px, tip_y_px = yield from catch_exception_and_warn(
108
+ PinNotFoundException, wait_for_tip_to_be_found, pin_tip_detection
109
+ )
110
+
111
+ LOGGER.info(f"Tip is at x,y: {tip_x_px},{tip_y_px}")
112
+
113
+ top_edge = np.array((yield from bps.rd(pin_tip_detection.triggered_top_edge)))
114
+ bottom_edge = np.array(
115
+ (yield from bps.rd(pin_tip_detection.triggered_bottom_edge))
116
+ )
117
+
118
+ full_image_height_px = yield from bps.rd(oav.cam.array_size.array_size_y)
119
+
120
+ # only use the area from the start of the pin onwards
121
+ top_edge = top_edge[tip_x_px : tip_x_px + grid_width_pixels]
122
+ bottom_edge = bottom_edge[tip_x_px : tip_x_px + grid_width_pixels]
123
+ LOGGER.info(f"OAV Edge detection top: {list(top_edge)}")
124
+ LOGGER.info(f"OAV Edge detection bottom: {list(bottom_edge)}")
125
+
126
+ min_y, max_y = (
127
+ float(n)
128
+ for n in get_min_and_max_y_of_pin(
129
+ top_edge, bottom_edge, full_image_height_px
130
+ )
131
+ )
132
+
133
+ grid_height_px: float = max_y - min_y
134
+
135
+ y_steps: int = math.ceil(grid_height_px / box_size_y_pixels)
136
+
137
+ # Panda not configured to run a half complete snake so enforce even rows on first grid
138
+ # See https://github.com/DiamondLightSource/hyperion/wiki/PandA-constant%E2%80%90motion-scanning#motion-program-summary
139
+ if y_steps % 2 and angle == 0:
140
+ LOGGER.debug(
141
+ f"Forcing number of rows in first grid to be even: Adding an extra row onto bottom of first grid and shifting grid upwards by {box_size_y_pixels/2}"
142
+ )
143
+ y_steps += 1
144
+ min_y -= box_size_y_pixels / 2
145
+ max_y += box_size_y_pixels / 2
146
+ grid_height_px += box_size_y_pixels
147
+
148
+ LOGGER.info(f"Drawing snapshot {grid_width_pixels} by {grid_height_px}")
149
+
150
+ x_steps = math.ceil(grid_width_pixels / box_size_x_pixels)
151
+
152
+ upper_left = (tip_x_px, min_y)
153
+
154
+ yield from bps.abs_set(oav.grid_snapshot.top_left_x, upper_left[0])
155
+ yield from bps.abs_set(oav.grid_snapshot.top_left_y, upper_left[1])
156
+ yield from bps.abs_set(oav.grid_snapshot.box_width, box_size_x_pixels)
157
+ yield from bps.abs_set(oav.grid_snapshot.num_boxes_x, x_steps)
158
+ yield from bps.abs_set(oav.grid_snapshot.num_boxes_y, y_steps)
159
+
160
+ snapshot_filename = snapshot_template.format(angle=abs(angle))
161
+
162
+ yield from bps.abs_set(oav.grid_snapshot.filename, snapshot_filename)
163
+ yield from bps.abs_set(oav.grid_snapshot.directory, snapshot_dir)
164
+ yield from bps.trigger(oav.grid_snapshot, wait=True)
165
+ yield from bps.create(CONST.DESCRIPTORS.OAV_GRID_SNAPSHOT_TRIGGERED)
166
+
167
+ yield from bps.read(oav.grid_snapshot)
168
+ yield from bps.read(smargon)
169
+ yield from bps.save()
170
+
171
+ LOGGER.info(
172
+ f"Grid calculated at {angle}: {x_steps} by {y_steps} steps starting at {upper_left}px"
173
+ )
@@ -0,0 +1,81 @@
1
+ from datetime import datetime
2
+ from typing import Protocol
3
+
4
+ from blueapi.core import MsgGenerator
5
+ from bluesky import plan_stubs as bps
6
+ from dodal.devices.aperturescatterguard import ApertureScatterguard, ApertureValue
7
+ from dodal.devices.backlight import Backlight, BacklightPosition
8
+ from dodal.devices.oav.oav_detector import OAV
9
+ from dodal.devices.oav.oav_parameters import OAVParameters
10
+ from dodal.devices.smargon import Smargon
11
+
12
+ from mx_bluesky.hyperion.device_setup_plans.setup_oav import setup_general_oav_params
13
+ from mx_bluesky.hyperion.parameters.components import WithSnapshot
14
+ from mx_bluesky.hyperion.parameters.constants import CONST, DocDescriptorNames
15
+
16
+ OAV_SNAPSHOT_SETUP_SHOT = "oav_snapshot_setup_shot"
17
+ OAV_SNAPSHOT_GROUP = "oav_snapshot_group"
18
+
19
+
20
+ class OavSnapshotComposite(Protocol):
21
+ smargon: Smargon
22
+ oav: OAV
23
+ aperture_scatterguard: ApertureScatterguard
24
+ backlight: Backlight
25
+
26
+
27
+ def setup_beamline_for_OAV(
28
+ smargon: Smargon,
29
+ backlight: Backlight,
30
+ aperture_scatterguard: ApertureScatterguard,
31
+ group=CONST.WAIT.READY_FOR_OAV,
32
+ ):
33
+ max_vel = yield from bps.rd(smargon.omega.max_velocity)
34
+ yield from bps.abs_set(smargon.omega.velocity, max_vel, group=group)
35
+ yield from bps.abs_set(backlight, BacklightPosition.IN, group=group)
36
+ yield from bps.abs_set(
37
+ aperture_scatterguard,
38
+ ApertureValue.ROBOT_LOAD,
39
+ group=group,
40
+ )
41
+
42
+
43
+ def oav_snapshot_plan(
44
+ composite: OavSnapshotComposite,
45
+ parameters: WithSnapshot,
46
+ oav_parameters: OAVParameters,
47
+ wait: bool = True,
48
+ ) -> MsgGenerator:
49
+ if not parameters.take_snapshots:
50
+ return
51
+ yield from bps.wait(group=CONST.WAIT.READY_FOR_OAV)
52
+ yield from _setup_oav(composite, parameters, oav_parameters)
53
+ for omega in parameters.snapshot_omegas_deg or []:
54
+ yield from _take_oav_snapshot(composite, omega)
55
+
56
+
57
+ def _setup_oav(
58
+ composite: OavSnapshotComposite,
59
+ parameters: WithSnapshot,
60
+ oav_parameters: OAVParameters,
61
+ ):
62
+ yield from setup_general_oav_params(composite.oav, oav_parameters)
63
+ yield from bps.abs_set(
64
+ composite.oav.snapshot.directory, str(parameters.snapshot_directory)
65
+ )
66
+
67
+
68
+ def _take_oav_snapshot(composite: OavSnapshotComposite, omega: float):
69
+ yield from bps.abs_set(
70
+ composite.smargon.omega, omega, group=OAV_SNAPSHOT_SETUP_SHOT
71
+ )
72
+ time_now = datetime.now()
73
+ filename = f"{time_now.strftime('%H%M%S')}_oav_snapshot_{omega:.0f}"
74
+ yield from bps.abs_set(
75
+ composite.oav.snapshot.filename, filename, group=OAV_SNAPSHOT_SETUP_SHOT
76
+ )
77
+ yield from bps.wait(group=OAV_SNAPSHOT_SETUP_SHOT)
78
+ yield from bps.trigger(composite.oav.snapshot, wait=True)
79
+ yield from bps.create(DocDescriptorNames.OAV_ROTATION_SNAPSHOT_TRIGGERED)
80
+ yield from bps.read(composite.oav.snapshot)
81
+ yield from bps.save()