mx-bluesky 1.1.0__py3-none-any.whl → 1.4.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 (81) hide show
  1. mx_bluesky/__init__.py +8 -3
  2. mx_bluesky/__main__.py +12 -7
  3. mx_bluesky/_version.py +2 -2
  4. mx_bluesky/beamlines/i04/callbacks/murko_callback.py +14 -4
  5. mx_bluesky/beamlines/i04/thawing_plan.py +48 -10
  6. mx_bluesky/beamlines/i24/serial/__init__.py +3 -0
  7. mx_bluesky/beamlines/i24/serial/extruder/i24ssx_Extruder_Collect_py3v2.py +68 -90
  8. mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/DiamondChipI24-py3v1.edl +1 -1
  9. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Collect_py3v1.py +104 -126
  10. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Manager_py3v1.py +139 -162
  11. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Mapping_py3v1.py +25 -36
  12. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_StartUp_py3v1.py +24 -34
  13. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_moveonclick.py +14 -11
  14. mx_bluesky/beamlines/i24/serial/log.py +58 -49
  15. mx_bluesky/beamlines/i24/serial/parameters/fixed_target/cs/cs_maker.json +3 -3
  16. mx_bluesky/beamlines/i24/serial/run_extruder.sh +30 -5
  17. mx_bluesky/beamlines/i24/serial/run_fixed_target.sh +31 -7
  18. mx_bluesky/beamlines/i24/serial/run_serial.py +24 -8
  19. mx_bluesky/beamlines/i24/serial/setup_beamline/ca.py +0 -2
  20. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_beamline.py +1 -1
  21. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_detector.py +8 -18
  22. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_zebra_plans.py +2 -2
  23. mx_bluesky/common/__init__.py +0 -0
  24. mx_bluesky/common/device_setup_plans/read_hardware_for_setup.py +14 -0
  25. mx_bluesky/common/parameters/components.py +221 -0
  26. mx_bluesky/common/parameters/constants.py +133 -0
  27. mx_bluesky/common/plans/__init__.py +1 -0
  28. mx_bluesky/common/plans/do_fgs.py +121 -0
  29. mx_bluesky/common/utils/log.py +116 -0
  30. mx_bluesky/{hyperion → common/utils}/tracing.py +2 -2
  31. mx_bluesky/hyperion/__main__.py +11 -9
  32. mx_bluesky/hyperion/device_setup_plans/dcm_pitch_roll_mirror_adjuster.py +31 -26
  33. mx_bluesky/hyperion/device_setup_plans/read_hardware_for_setup.py +6 -12
  34. mx_bluesky/hyperion/device_setup_plans/setup_oav.py +6 -12
  35. mx_bluesky/hyperion/device_setup_plans/setup_panda.py +1 -2
  36. mx_bluesky/hyperion/device_setup_plans/setup_zebra.py +48 -17
  37. mx_bluesky/hyperion/device_setup_plans/smargon.py +6 -6
  38. mx_bluesky/hyperion/device_setup_plans/utils.py +13 -2
  39. mx_bluesky/hyperion/device_setup_plans/xbpm_feedback.py +4 -4
  40. mx_bluesky/hyperion/experiment_plans/experiment_registry.py +9 -0
  41. mx_bluesky/hyperion/experiment_plans/flyscan_xray_centre_plan.py +59 -108
  42. mx_bluesky/hyperion/experiment_plans/grid_detect_then_xray_centre_plan.py +7 -5
  43. mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py +46 -0
  44. mx_bluesky/hyperion/experiment_plans/oav_grid_detection_plan.py +19 -18
  45. mx_bluesky/hyperion/experiment_plans/oav_snapshot_plan.py +8 -5
  46. mx_bluesky/hyperion/experiment_plans/pin_centre_then_xray_centre_plan.py +4 -4
  47. mx_bluesky/hyperion/experiment_plans/pin_tip_centring_plan.py +17 -17
  48. mx_bluesky/hyperion/experiment_plans/robot_load_and_change_energy.py +241 -0
  49. mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +24 -181
  50. mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +6 -4
  51. mx_bluesky/hyperion/experiment_plans/set_energy_plan.py +3 -11
  52. mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +1 -2
  53. mx_bluesky/hyperion/external_interaction/callbacks/common/callback_util.py +18 -0
  54. mx_bluesky/hyperion/external_interaction/callbacks/common/ispyb_mapping.py +1 -9
  55. mx_bluesky/hyperion/external_interaction/callbacks/grid_detection_callback.py +18 -13
  56. mx_bluesky/hyperion/external_interaction/callbacks/ispyb_callback_base.py +32 -15
  57. mx_bluesky/hyperion/external_interaction/callbacks/log_uid_tag_callback.py +1 -1
  58. mx_bluesky/hyperion/external_interaction/callbacks/robot_load/ispyb_callback.py +3 -5
  59. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py +4 -3
  60. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/ispyb_callback.py +23 -18
  61. mx_bluesky/hyperion/external_interaction/config_server.py +22 -10
  62. mx_bluesky/hyperion/external_interaction/ispyb/ispyb_store.py +1 -1
  63. mx_bluesky/hyperion/external_interaction/ispyb/ispyb_utils.py +0 -2
  64. mx_bluesky/hyperion/external_interaction/nexus/nexus_utils.py +2 -2
  65. mx_bluesky/hyperion/external_interaction/nexus/write_nexus.py +1 -1
  66. mx_bluesky/hyperion/log.py +0 -84
  67. mx_bluesky/hyperion/parameters/components.py +1 -242
  68. mx_bluesky/hyperion/parameters/constants.py +22 -118
  69. mx_bluesky/hyperion/parameters/gridscan.py +20 -11
  70. mx_bluesky/hyperion/parameters/load_centre_collect.py +50 -0
  71. mx_bluesky/hyperion/parameters/robot_load.py +16 -0
  72. mx_bluesky/hyperion/parameters/rotation.py +9 -5
  73. mx_bluesky/hyperion/utils/utils.py +17 -0
  74. mx_bluesky/hyperion/utils/validation.py +5 -6
  75. {mx_bluesky-1.1.0.dist-info → mx_bluesky-1.4.0.dist-info}/METADATA +4 -2
  76. {mx_bluesky-1.1.0.dist-info → mx_bluesky-1.4.0.dist-info}/RECORD +80 -70
  77. {mx_bluesky-1.1.0.dist-info → mx_bluesky-1.4.0.dist-info}/WHEEL +1 -1
  78. mx_bluesky/example.py +0 -19
  79. {mx_bluesky-1.1.0.dist-info → mx_bluesky-1.4.0.dist-info}/LICENSE +0 -0
  80. {mx_bluesky-1.1.0.dist-info → mx_bluesky-1.4.0.dist-info}/entry_points.txt +0 -0
  81. {mx_bluesky-1.1.0.dist-info → mx_bluesky-1.4.0.dist-info}/top_level.txt +0 -0
@@ -7,12 +7,14 @@ import mx_bluesky.hyperion.experiment_plans.flyscan_xray_centre_plan as flyscan_
7
7
  import mx_bluesky.hyperion.experiment_plans.rotation_scan_plan as rotation_scan_plan
8
8
  from mx_bluesky.hyperion.experiment_plans import (
9
9
  grid_detect_then_xray_centre_plan,
10
+ load_centre_collect_full_plan,
10
11
  pin_centre_then_xray_centre_plan,
11
12
  robot_load_then_centre_plan,
12
13
  )
13
14
  from mx_bluesky.hyperion.external_interaction.callbacks.common.callback_util import (
14
15
  CallbacksFactory,
15
16
  create_gridscan_callbacks,
17
+ create_load_centre_collect_callbacks,
16
18
  create_robot_load_and_centre_callbacks,
17
19
  create_rotation_callbacks,
18
20
  )
@@ -22,6 +24,7 @@ from mx_bluesky.hyperion.parameters.gridscan import (
22
24
  RobotLoadThenCentre,
23
25
  ThreeDGridScan,
24
26
  )
27
+ from mx_bluesky.hyperion.parameters.load_centre_collect import LoadCentreCollect
25
28
  from mx_bluesky.hyperion.parameters.rotation import MultiRotationScan, RotationScan
26
29
 
27
30
 
@@ -42,6 +45,7 @@ class ExperimentRegistryEntry(TypedDict):
42
45
  | MultiRotationScan
43
46
  | PinTipCentreThenXrayCentre
44
47
  | RobotLoadThenCentre
48
+ | LoadCentreCollect
45
49
  ]
46
50
  callbacks_factory: CallbacksFactory
47
51
 
@@ -77,6 +81,11 @@ PLAN_REGISTRY: dict[str, ExperimentRegistryEntry] = {
77
81
  "param_type": MultiRotationScan,
78
82
  "callbacks_factory": create_rotation_callbacks,
79
83
  },
84
+ "load_centre_collect_full_plan": {
85
+ "setup": load_centre_collect_full_plan.create_devices,
86
+ "param_type": LoadCentreCollect,
87
+ "callbacks_factory": create_load_centre_collect_callbacks,
88
+ },
80
89
  }
81
90
 
82
91
 
@@ -4,7 +4,6 @@ import dataclasses
4
4
  from collections.abc import Callable
5
5
  from functools import partial
6
6
  from pathlib import Path
7
- from time import time
8
7
  from typing import Protocol
9
8
 
10
9
  import bluesky.plan_stubs as bps
@@ -40,16 +39,15 @@ from dodal.devices.zocalo.zocalo_results import (
40
39
  ZOCALO_READING_PLAN_NAME,
41
40
  ZOCALO_STAGE_GROUP,
42
41
  ZocaloResults,
43
- get_processing_result,
42
+ get_full_processing_results,
44
43
  )
45
- from dodal.plans.check_topup import check_topup_and_wait_if_necessary
46
44
  from ophyd_async.fastcs.panda import HDFPanda
47
- from scanspec.core import AxesPoints, Axis
48
45
 
46
+ from mx_bluesky.common.plans.do_fgs import kickoff_and_complete_gridscan
47
+ from mx_bluesky.common.utils.tracing import TRACER
49
48
  from mx_bluesky.hyperion.device_setup_plans.manipulate_sample import move_x_y_z
50
49
  from mx_bluesky.hyperion.device_setup_plans.read_hardware_for_setup import (
51
50
  read_hardware_during_collection,
52
- read_hardware_for_zocalo,
53
51
  read_hardware_pre_collection,
54
52
  )
55
53
  from mx_bluesky.hyperion.device_setup_plans.setup_panda import (
@@ -69,7 +67,6 @@ from mx_bluesky.hyperion.exceptions import WarningException
69
67
  from mx_bluesky.hyperion.log import LOGGER
70
68
  from mx_bluesky.hyperion.parameters.constants import CONST
71
69
  from mx_bluesky.hyperion.parameters.gridscan import ThreeDGridScan
72
- from mx_bluesky.hyperion.tracing import TRACER
73
70
  from mx_bluesky.hyperion.utils.context import device_composite_from_context
74
71
 
75
72
 
@@ -77,6 +74,12 @@ class SmargonSpeedException(Exception):
77
74
  pass
78
75
 
79
76
 
77
+ class CrystalNotFoundException(WarningException):
78
+ """Raised if grid detection completed normally but no crystal was found."""
79
+
80
+ pass
81
+
82
+
80
83
  @dataclasses.dataclass
81
84
  class FlyScanXRayCentreComposite:
82
85
  """All devices which are directly or indirectly required by this plan"""
@@ -128,8 +131,8 @@ def flyscan_xray_centre(
128
131
  """
129
132
  parameters.features.update_self_from_server()
130
133
  composite.eiger.set_detector_parameters(parameters.detector_params)
131
- composite.zocalo.zocalo_environment = parameters.zocalo_environment
132
- composite.zocalo.use_cpu_and_gpu = parameters.use_cpu_and_gpu_zocalo
134
+ composite.zocalo.zocalo_environment = CONST.ZOCALO_ENV
135
+ composite.zocalo.use_cpu_and_gpu = parameters.features.compare_cpu_and_gpu_zocalo
133
136
 
134
137
  feature_controlled = _get_feature_controlled(composite, parameters)
135
138
 
@@ -137,8 +140,6 @@ def flyscan_xray_centre(
137
140
  @bpp.run_decorator( # attach experiment metadata to the start document
138
141
  md={
139
142
  "subplan_name": CONST.PLAN.GRIDSCAN_OUTER,
140
- CONST.TRIGGER.ZOCALO: CONST.PLAN.DO_FGS,
141
- "zocalo_environment": parameters.zocalo_environment,
142
143
  "hyperion_parameters": parameters.model_dump_json(),
143
144
  "activate_callbacks": [
144
145
  "GridscanNexusFileCallback",
@@ -190,47 +191,52 @@ def run_gridscan_and_move(
190
191
 
191
192
  LOGGER.info("Grid scan finished, getting results.")
192
193
 
193
- with TRACER.start_span("wait_for_zocalo"):
194
- yield from bps.trigger_and_read(
195
- [fgs_composite.zocalo], name=ZOCALO_READING_PLAN_NAME
196
- )
197
- LOGGER.info("Zocalo triggered and read, interpreting results.")
198
- xray_centre, bbox_size = yield from get_processing_result(fgs_composite.zocalo)
199
- LOGGER.info(f"Got xray centre: {xray_centre}, bbox size: {bbox_size}")
200
- if xray_centre is not None:
201
- xray_centre = parameters.FGS_params.grid_position_to_motor_position(
202
- xray_centre
194
+ try:
195
+ with TRACER.start_span("wait_for_zocalo"):
196
+ yield from bps.trigger_and_read(
197
+ [fgs_composite.zocalo], name=ZOCALO_READING_PLAN_NAME
203
198
  )
204
- else:
205
- xray_centre = initial_xyz
206
- LOGGER.warning("No X-ray centre received")
207
- if bbox_size is not None:
208
- with TRACER.start_span("change_aperture"):
209
- yield from set_aperture_for_bbox_size(
210
- fgs_composite.aperture_scatterguard, bbox_size
199
+ LOGGER.info("Zocalo triggered and read, interpreting results.")
200
+ xrc_results = yield from get_full_processing_results(fgs_composite.zocalo)
201
+ LOGGER.info(f"Got xray centring results: {xrc_results}")
202
+ if xrc_results:
203
+ best_result = xrc_results[0]
204
+ xrc_centre_grid_coords = best_result["centre_of_mass"]
205
+ xray_centre = parameters.FGS_params.grid_position_to_motor_position(
206
+ np.array(xrc_centre_grid_coords)
211
207
  )
212
- else:
213
- LOGGER.warning("No bounding box size received")
214
-
215
- # once we have the results, go to the appropriate position
216
- LOGGER.info("Moving to centre of mass.")
217
- with TRACER.start_span("move_to_result"):
218
- x, y, z = xray_centre
219
- yield from move_x_y_z(fgs_composite.sample_motors, x, y, z, wait=True)
220
-
221
- if parameters.FGS_params.set_stub_offsets:
222
- LOGGER.info("Recentring smargon co-ordinate system to this point.")
223
- yield from bps.mv(
224
- fgs_composite.sample_motors.stub_offsets, StubPosition.CURRENT_AS_CENTER
225
- )
226
-
227
- # Turn off dev/shm streaming to avoid filling disk, see https://github.com/DiamondLightSource/hyperion/issues/1395
228
- LOGGER.info("Turning off Eiger dev/shm streaming")
229
- yield from bps.abs_set(fgs_composite.eiger.odin.fan.dev_shm_enable, 0)
208
+ with TRACER.start_span("change_aperture"):
209
+ bbox_size = np.abs(
210
+ np.array(best_result["bounding_box"][1])
211
+ - np.array(best_result["bounding_box"][0])
212
+ )
213
+ yield from set_aperture_for_bbox_size(
214
+ fgs_composite.aperture_scatterguard, bbox_size
215
+ )
216
+ else:
217
+ LOGGER.warning("No X-ray centre received")
218
+ raise CrystalNotFoundException()
219
+
220
+ # once we have the results, go to the appropriate position
221
+ LOGGER.info("Moving to centre of mass.")
222
+ with TRACER.start_span("move_to_result"):
223
+ x, y, z = xray_centre
224
+ yield from move_x_y_z(fgs_composite.sample_motors, x, y, z, wait=True)
225
+
226
+ if parameters.FGS_params.set_stub_offsets:
227
+ LOGGER.info("Recentring smargon co-ordinate system to this point.")
228
+ yield from bps.mv(
229
+ fgs_composite.sample_motors.stub_offsets, # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
230
+ StubPosition.CURRENT_AS_CENTER, # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
231
+ )
232
+ finally:
233
+ # Turn off dev/shm streaming to avoid filling disk, see https://github.com/DiamondLightSource/hyperion/issues/1395
234
+ LOGGER.info("Turning off Eiger dev/shm streaming")
235
+ yield from bps.abs_set(fgs_composite.eiger.odin.fan.dev_shm_enable, 0) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
230
236
 
231
- # Wait on everything before returning to GDA (particularly apertures), can be removed
232
- # when we do not return to GDA here
233
- yield from bps.wait()
237
+ # Wait on everything before returning to GDA (particularly apertures), can be removed
238
+ # when we do not return to GDA here
239
+ yield from bps.wait()
234
240
 
235
241
 
236
242
  @bpp.set_run_key_decorator(CONST.PLAN.GRIDSCAN_MAIN)
@@ -257,7 +263,7 @@ def run_gridscan(
257
263
  fgs_composite.undulator,
258
264
  fgs_composite.synchrotron,
259
265
  fgs_composite.s4_slit_gaps,
260
- fgs_composite.robot,
266
+ fgs_composite.dcm,
261
267
  fgs_composite.smargon,
262
268
  )
263
269
 
@@ -278,7 +284,7 @@ def run_gridscan(
278
284
 
279
285
  LOGGER.info("Waiting for arming to finish")
280
286
  yield from bps.wait(CONST.WAIT.GRID_READY_FOR_DC)
281
- yield from bps.stage(fgs_composite.eiger)
287
+ yield from bps.stage(fgs_composite.eiger) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
282
288
 
283
289
  yield from kickoff_and_complete_gridscan(
284
290
  feature_controlled.fgs_motors,
@@ -286,66 +292,11 @@ def run_gridscan(
286
292
  fgs_composite.synchrotron,
287
293
  [parameters.scan_points_first_grid, parameters.scan_points_second_grid],
288
294
  parameters.scan_indices,
289
- do_during_run=read_during_collection,
295
+ plan_during_collection=read_during_collection,
290
296
  )
291
297
  yield from bps.abs_set(feature_controlled.fgs_motors.z_steps, 0, wait=False)
292
298
 
293
299
 
294
- def kickoff_and_complete_gridscan(
295
- gridscan: FastGridScanCommon,
296
- eiger: EigerDetector,
297
- synchrotron: Synchrotron,
298
- scan_points: list[AxesPoints[Axis]],
299
- scan_start_indices: list[int],
300
- do_during_run: Callable[[], MsgGenerator] | None = None,
301
- ):
302
- @TRACER.start_as_current_span(CONST.PLAN.DO_FGS)
303
- @bpp.set_run_key_decorator(CONST.PLAN.DO_FGS)
304
- @bpp.run_decorator(
305
- md={
306
- "subplan_name": CONST.PLAN.DO_FGS,
307
- "scan_points": scan_points,
308
- "scan_start_indices": scan_start_indices,
309
- }
310
- )
311
- @bpp.contingency_decorator(
312
- except_plan=lambda e: (yield from bps.stop(eiger)),
313
- else_plan=lambda: (yield from bps.unstage(eiger)),
314
- )
315
- def do_fgs():
316
- # Check topup gate
317
- expected_images = yield from bps.rd(gridscan.expected_images)
318
- exposure_sec_per_image = yield from bps.rd(eiger.cam.acquire_time)
319
- LOGGER.info("waiting for topup if necessary...")
320
- yield from check_topup_and_wait_if_necessary(
321
- synchrotron,
322
- expected_images * exposure_sec_per_image,
323
- 30.0,
324
- )
325
- yield from read_hardware_for_zocalo(eiger)
326
- LOGGER.info("Wait for all moves with no assigned group")
327
- yield from bps.wait()
328
- LOGGER.info("kicking off FGS")
329
- yield from bps.kickoff(gridscan, wait=True)
330
- gridscan_start_time = time()
331
- LOGGER.info("Waiting for Zocalo device queue to have been cleared...")
332
- yield from bps.wait(
333
- ZOCALO_STAGE_GROUP
334
- ) # Make sure ZocaloResults queue is clear and ready to accept our new data
335
- if do_during_run:
336
- LOGGER.info(f"Running {do_during_run} during FGS")
337
- yield from do_during_run()
338
- LOGGER.info("completing FGS")
339
- yield from bps.complete(gridscan, wait=True)
340
-
341
- # Remove this logging statement once metrics have been added
342
- LOGGER.info(
343
- f"Gridscan motion program took {round(time()-gridscan_start_time,2)} to complete"
344
- )
345
-
346
- yield from do_fgs()
347
-
348
-
349
300
  def wait_for_gridscan_valid(fgs_motors: FastGridScanCommon, timeout=0.5):
350
301
  LOGGER.info("Waiting for valid fgs_params")
351
302
  SLEEP_PER_CHECK = 0.1
@@ -506,8 +457,8 @@ def _panda_triggering_setup(
506
457
  )
507
458
 
508
459
  yield from bps.mv(
509
- fgs_composite.panda_fast_grid_scan.time_between_x_steps_ms,
510
- time_between_x_steps_ms,
460
+ fgs_composite.panda_fast_grid_scan.time_between_x_steps_ms, # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
461
+ time_between_x_steps_ms, # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
511
462
  )
512
463
 
513
464
  directory_provider_root = Path(parameters.storage_directory)
@@ -15,7 +15,7 @@ from dodal.devices.eiger import EigerDetector
15
15
  from dodal.devices.fast_grid_scan import PandAFastGridScan, ZebraFastGridScan
16
16
  from dodal.devices.flux import Flux
17
17
  from dodal.devices.oav.oav_detector import OAV
18
- from dodal.devices.oav.oav_parameters import OAV_CONFIG_JSON, OAVParameters
18
+ from dodal.devices.oav.oav_parameters import OAVParameters
19
19
  from dodal.devices.oav.pin_image_recognition import PinTipDetection
20
20
  from dodal.devices.robot import BartRobot
21
21
  from dodal.devices.s4_slit_gaps import S4SlitGaps
@@ -28,6 +28,7 @@ from dodal.devices.zebra_controlled_shutter import ZebraShutter
28
28
  from dodal.devices.zocalo import ZocaloResults
29
29
  from ophyd_async.fastcs.panda import HDFPanda
30
30
 
31
+ from mx_bluesky.common.parameters.constants import OavConstants
31
32
  from mx_bluesky.hyperion.device_setup_plans.manipulate_sample import (
32
33
  move_aperture_if_required,
33
34
  )
@@ -109,7 +110,7 @@ def detect_grid_and_do_gridscan(
109
110
  ):
110
111
  snapshot_template = f"{parameters.detector_params.prefix}_{parameters.detector_params.run_number}_{{angle}}"
111
112
 
112
- grid_params_callback = GridDetectionCallback(composite.oav.parameters)
113
+ grid_params_callback = GridDetectionCallback()
113
114
 
114
115
  @bpp.subs_decorator([grid_params_callback])
115
116
  def run_grid_detection_plan(
@@ -129,7 +130,8 @@ def detect_grid_and_do_gridscan(
129
130
  oav_params,
130
131
  snapshot_template,
131
132
  str(snapshot_dir),
132
- grid_width_microns=parameters.grid_width_um,
133
+ parameters.grid_width_um,
134
+ parameters.box_size_um,
133
135
  )
134
136
 
135
137
  yield from run_grid_detection_plan(
@@ -178,7 +180,7 @@ def detect_grid_and_do_gridscan(
178
180
  def grid_detect_then_xray_centre(
179
181
  composite: GridDetectThenXRayCentreComposite,
180
182
  parameters: GridScanWithEdgeDetect,
181
- oav_config: str = OAV_CONFIG_JSON,
183
+ oav_config: str = OavConstants.OAV_CONFIG_JSON,
182
184
  ) -> MsgGenerator:
183
185
  """
184
186
  A plan which combines the collection of snapshots from the OAV and the determination
@@ -204,6 +206,6 @@ def grid_detect_then_xray_centre(
204
206
  eiger,
205
207
  composite.detector_motion,
206
208
  parameters.detector_params.detector_distance,
207
- plan_to_perform,
209
+ plan_to_perform, # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809 and MsgGenerator should allow for return values
208
210
  group=CONST.WAIT.GRID_READY_FOR_DC,
209
211
  )
@@ -0,0 +1,46 @@
1
+ import dataclasses
2
+
3
+ from blueapi.core import BlueskyContext
4
+ from dodal.devices.oav.oav_parameters import OAVParameters
5
+
6
+ from mx_bluesky.hyperion.experiment_plans.robot_load_then_centre_plan import (
7
+ RobotLoadThenCentreComposite,
8
+ robot_load_then_centre,
9
+ )
10
+ from mx_bluesky.hyperion.experiment_plans.rotation_scan_plan import (
11
+ RotationScanComposite,
12
+ multi_rotation_scan,
13
+ )
14
+ from mx_bluesky.hyperion.parameters.load_centre_collect import LoadCentreCollect
15
+ from mx_bluesky.hyperion.utils.context import device_composite_from_context
16
+
17
+
18
+ @dataclasses.dataclass
19
+ class LoadCentreCollectComposite(RobotLoadThenCentreComposite, RotationScanComposite):
20
+ """Composite that provides access to the required devices."""
21
+
22
+ pass
23
+
24
+
25
+ def create_devices(context: BlueskyContext) -> LoadCentreCollectComposite:
26
+ """Create the necessary devices for the plan."""
27
+ return device_composite_from_context(context, LoadCentreCollectComposite)
28
+
29
+
30
+ def load_centre_collect_full_plan(
31
+ composite: LoadCentreCollectComposite,
32
+ params: LoadCentreCollect,
33
+ oav_params: OAVParameters | None = None,
34
+ ):
35
+ """Attempt a complete data collection experiment, consisting of the following:
36
+ * Load the sample if necessary
37
+ * Move to the specified goniometer start angles
38
+ * Perform optical centring, then X-ray centring
39
+ * If X-ray centring finds a diffracting centre then move to that centre and
40
+ * do a collection with the specified parameters.
41
+ """
42
+ if not oav_params:
43
+ oav_params = OAVParameters(context="xrayCentring")
44
+ yield from robot_load_then_centre(composite, params.robot_load_then_centre)
45
+
46
+ yield from multi_rotation_scan(composite, params.multi_rotation_scan, oav_params)
@@ -63,7 +63,7 @@ def grid_detection_plan(
63
63
  snapshot_template: str,
64
64
  snapshot_dir: str,
65
65
  grid_width_microns: float,
66
- box_size_um: float = 20,
66
+ box_size_um: float,
67
67
  ):
68
68
  """
69
69
  Creates the parameters for two grids that are 90 degrees from each other and
@@ -74,7 +74,7 @@ def grid_detection_plan(
74
74
  parameters (OAVParameters): Object containing parameters for setting up the OAV
75
75
  snapshot_template (str): A template for the name of the snapshots, expected to be filled in with an angle
76
76
  snapshot_dir (str): The location to save snapshots
77
- grid_width_microns (int): The width of the grid to scan in microns
77
+ grid_width_microns (float): The width of the grid to scan in microns
78
78
  box_size_um (float): The size of each box of the grid in microns
79
79
  """
80
80
  oav: OAV = composite.oav
@@ -90,16 +90,17 @@ def grid_detection_plan(
90
90
 
91
91
  LOGGER.info("OAV Centring: Camera set up")
92
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
93
+ microns_per_pixel_x = yield from bps.rd(oav.microns_per_pixel_x)
94
+ microns_per_pixel_y = yield from bps.rd(oav.microns_per_pixel_y)
97
95
 
98
- grid_width_pixels = int(grid_width_microns / oav.parameters.micronsPerXPixel)
96
+ box_size_x_pixels = box_size_um / microns_per_pixel_x
97
+ box_size_y_pixels = box_size_um / microns_per_pixel_y
98
+
99
+ grid_width_pixels = int(grid_width_microns / microns_per_pixel_x)
99
100
 
100
101
  # The FGS uses -90 so we need to match it
101
102
  for angle in [0, -90]:
102
- yield from bps.mv(smargon.omega, angle)
103
+ yield from bps.mv(smargon.omega, angle) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
103
104
  # need to wait for the OAV image to update
104
105
  # See #673 for improvements
105
106
  yield from bps.sleep(CONST.HARDWARE.OAV_REFRESH_DELAY)
@@ -115,7 +116,7 @@ def grid_detection_plan(
115
116
  (yield from bps.rd(pin_tip_detection.triggered_bottom_edge))
116
117
  )
117
118
 
118
- full_image_height_px = yield from bps.rd(oav.cam.array_size.array_size_y)
119
+ full_image_height_px = yield from bps.rd(oav.cam.array_size_y)
119
120
 
120
121
  # only use the area from the start of the pin onwards
121
122
  top_edge = top_edge[tip_x_px : tip_x_px + grid_width_pixels]
@@ -151,20 +152,20 @@ def grid_detection_plan(
151
152
 
152
153
  upper_left = (tip_x_px, min_y)
153
154
 
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)
155
+ yield from bps.abs_set(oav.grid_snapshot.top_left_x, upper_left[0]) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
156
+ yield from bps.abs_set(oav.grid_snapshot.top_left_y, upper_left[1]) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
157
+ yield from bps.abs_set(oav.grid_snapshot.box_width, box_size_x_pixels) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
158
+ yield from bps.abs_set(oav.grid_snapshot.num_boxes_x, x_steps) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
159
+ yield from bps.abs_set(oav.grid_snapshot.num_boxes_y, y_steps) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
159
160
 
160
161
  snapshot_filename = snapshot_template.format(angle=abs(angle))
161
162
 
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)
163
+ yield from bps.abs_set(oav.grid_snapshot.filename, snapshot_filename) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
164
+ yield from bps.abs_set(oav.grid_snapshot.directory, snapshot_dir) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
165
+ yield from bps.trigger(oav.grid_snapshot, wait=True) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
165
166
  yield from bps.create(CONST.DESCRIPTORS.OAV_GRID_SNAPSHOT_TRIGGERED)
166
167
 
167
- yield from bps.read(oav.grid_snapshot)
168
+ yield from bps.read(oav) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
168
169
  yield from bps.read(smargon)
169
170
  yield from bps.save()
170
171
 
@@ -9,8 +9,8 @@ from dodal.devices.oav.oav_detector import OAV
9
9
  from dodal.devices.oav.oav_parameters import OAVParameters
10
10
  from dodal.devices.smargon import Smargon
11
11
 
12
+ from mx_bluesky.common.parameters.components import WithSnapshot
12
13
  from mx_bluesky.hyperion.device_setup_plans.setup_oav import setup_general_oav_params
13
- from mx_bluesky.hyperion.parameters.components import WithSnapshot
14
14
  from mx_bluesky.hyperion.parameters.constants import CONST, DocDescriptorNames
15
15
 
16
16
  OAV_SNAPSHOT_SETUP_SHOT = "oav_snapshot_setup_shot"
@@ -61,7 +61,8 @@ def _setup_oav(
61
61
  ):
62
62
  yield from setup_general_oav_params(composite.oav, oav_parameters)
63
63
  yield from bps.abs_set(
64
- composite.oav.snapshot.directory, str(parameters.snapshot_directory)
64
+ composite.oav.snapshot.directory, # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
65
+ str(parameters.snapshot_directory),
65
66
  )
66
67
 
67
68
 
@@ -72,10 +73,12 @@ def _take_oav_snapshot(composite: OavSnapshotComposite, omega: float):
72
73
  time_now = datetime.now()
73
74
  filename = f"{time_now.strftime('%H%M%S')}_oav_snapshot_{omega:.0f}"
74
75
  yield from bps.abs_set(
75
- composite.oav.snapshot.filename, filename, group=OAV_SNAPSHOT_SETUP_SHOT
76
+ composite.oav.snapshot.filename, # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
77
+ filename,
78
+ group=OAV_SNAPSHOT_SETUP_SHOT,
76
79
  )
77
80
  yield from bps.wait(group=OAV_SNAPSHOT_SETUP_SHOT)
78
- yield from bps.trigger(composite.oav.snapshot, wait=True)
81
+ yield from bps.trigger(composite.oav.snapshot, wait=True) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
79
82
  yield from bps.create(DocDescriptorNames.OAV_ROTATION_SNAPSHOT_TRIGGERED)
80
- yield from bps.read(composite.oav.snapshot)
83
+ yield from bps.read(composite.oav.snapshot) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
81
84
  yield from bps.save()
@@ -4,8 +4,9 @@ import json
4
4
 
5
5
  from blueapi.core import BlueskyContext, MsgGenerator
6
6
  from dodal.devices.eiger import EigerDetector
7
- from dodal.devices.oav.oav_parameters import OAV_CONFIG_JSON, OAVParameters
7
+ from dodal.devices.oav.oav_parameters import OAVParameters
8
8
 
9
+ from mx_bluesky.common.parameters.constants import OavConstants
9
10
  from mx_bluesky.hyperion.device_setup_plans.manipulate_sample import move_phi_chi_omega
10
11
  from mx_bluesky.hyperion.device_setup_plans.utils import (
11
12
  start_preparing_data_collection_then_do_plan,
@@ -55,11 +56,10 @@ def create_parameters_for_grid_detection(
55
56
  def pin_centre_then_xray_centre_plan(
56
57
  composite: GridDetectThenXRayCentreComposite,
57
58
  parameters: PinTipCentreThenXrayCentre,
58
- oav_config_file: str = OAV_CONFIG_JSON,
59
+ oav_config_file: str = OavConstants.OAV_CONFIG_JSON,
59
60
  ):
60
61
  """Plan that perfoms a pin tip centre followed by an xray centre to completely
61
62
  centre the sample"""
62
- oav_config_file = parameters.oav_centring_file
63
63
 
64
64
  pin_tip_centring_composite = PinTipCentringComposite(
65
65
  oav=composite.oav,
@@ -102,7 +102,7 @@ def pin_centre_then_xray_centre_plan(
102
102
  def pin_tip_centre_then_xray_centre(
103
103
  composite: GridDetectThenXRayCentreComposite,
104
104
  parameters: PinTipCentreThenXrayCentre,
105
- oav_config_file: str = OAV_CONFIG_JSON,
105
+ oav_config_file: str = OavConstants.OAV_CONFIG_JSON,
106
106
  ) -> MsgGenerator:
107
107
  """Starts preparing for collection then performs the pin tip centre and xray centre"""
108
108
 
@@ -7,7 +7,7 @@ from bluesky.utils import Msg
7
7
  from dodal.devices.backlight import Backlight
8
8
  from dodal.devices.oav.oav_detector import OAV
9
9
  from dodal.devices.oav.oav_parameters import OAV_CONFIG_JSON, OAVParameters
10
- from dodal.devices.oav.pin_image_recognition import PinTipDetection
10
+ from dodal.devices.oav.pin_image_recognition import PinTipDetection, Tip
11
11
  from dodal.devices.oav.utils import (
12
12
  Pixel,
13
13
  get_move_required_so_that_beam_is_at_pixel,
@@ -43,11 +43,11 @@ def create_devices(context: BlueskyContext) -> PinTipCentringComposite:
43
43
 
44
44
  def trigger_and_return_pin_tip(
45
45
  pin_tip: PinTipDetection,
46
- ) -> Generator[Msg, None, Pixel]:
46
+ ) -> Generator[Msg, None, Tip]:
47
47
  yield from bps.trigger(pin_tip, wait=True)
48
48
  tip_x_y_px = yield from bps.rd(pin_tip.triggered_tip)
49
49
  LOGGER.info(f"Pin tip found at {tip_x_y_px}")
50
- return tip_x_y_px # type: ignore
50
+ return tip_x_y_px
51
51
 
52
52
 
53
53
  def move_pin_into_view(
@@ -74,16 +74,16 @@ def move_pin_into_view(
74
74
  Tuple[int, int]: The location of the pin tip in pixels
75
75
  """
76
76
 
77
- def pin_tip_valid(pin_x: float):
78
- return pin_x != 0 and pin_x != pin_tip_device.INVALID_POSITION[0]
77
+ def pin_tip_valid(pin_xy: Tip):
78
+ return not all(pin_xy == pin_tip_device.INVALID_POSITION) and pin_xy[0] != 0
79
79
 
80
80
  for _ in range(max_steps):
81
- tip_x_px, tip_y_px = yield from trigger_and_return_pin_tip(pin_tip_device)
81
+ tip_xy_px = yield from trigger_and_return_pin_tip(pin_tip_device)
82
82
 
83
- if pin_tip_valid(tip_x_px):
84
- return (tip_x_px, tip_y_px)
83
+ if pin_tip_valid(tip_xy_px):
84
+ return (int(tip_xy_px[0]), int(tip_xy_px[1]))
85
85
 
86
- if tip_x_px == 0:
86
+ if tip_xy_px[0] == 0:
87
87
  # Pin is off in the -ve direction
88
88
  step_size_mm = -step_size_mm
89
89
 
@@ -97,19 +97,19 @@ def move_pin_into_view(
97
97
  f"Pin tip is off screen, and moving {step_size_mm} mm would cross limits, "
98
98
  f"moving to {move_within_limits} instead"
99
99
  )
100
- yield from bps.mv(smargon.x, move_within_limits)
100
+ yield from bps.mv(smargon.x, move_within_limits) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
101
101
 
102
102
  # Some time for the view to settle after the move
103
103
  yield from bps.sleep(CONST.HARDWARE.OAV_REFRESH_DELAY)
104
104
 
105
- tip_x_px, tip_y_px = yield from trigger_and_return_pin_tip(pin_tip_device)
105
+ tip_xy_px = yield from trigger_and_return_pin_tip(pin_tip_device)
106
106
 
107
- if not pin_tip_valid(tip_x_px):
107
+ if not pin_tip_valid(tip_xy_px):
108
108
  raise WarningException(
109
109
  "Pin tip centring failed - pin too long/short/bent and out of range"
110
110
  )
111
111
  else:
112
- return (tip_x_px, tip_y_px)
112
+ return (int(tip_xy_px[0]), int(tip_xy_px[1]))
113
113
 
114
114
 
115
115
  def pin_tip_centre_plan(
@@ -132,13 +132,13 @@ def pin_tip_centre_plan(
132
132
  pin_tip_setup = composite.pin_tip_detection
133
133
  pin_tip_detect = composite.pin_tip_detection
134
134
 
135
- assert oav.parameters.micronsPerXPixel is not None
136
- tip_offset_px = int(tip_offset_microns / oav.parameters.micronsPerXPixel)
135
+ microns_per_pixel_x = yield from bps.rd(oav.microns_per_pixel_x)
136
+ tip_offset_px = int(tip_offset_microns / microns_per_pixel_x)
137
137
 
138
138
  def offset_and_move(tip: Pixel):
139
139
  pixel_to_move_to = (tip[0] + tip_offset_px, tip[1])
140
140
  position_mm = yield from get_move_required_so_that_beam_is_at_pixel(
141
- smargon, pixel_to_move_to, oav.parameters
141
+ smargon, pixel_to_move_to, oav
142
142
  )
143
143
  LOGGER.info(f"Tip centring moving to : {position_mm}")
144
144
  yield from move_smargon_warn_on_out_of_range(smargon, position_mm)
@@ -154,7 +154,7 @@ def pin_tip_centre_plan(
154
154
  tip = yield from move_pin_into_view(pin_tip_detect, smargon)
155
155
  yield from offset_and_move(tip)
156
156
 
157
- yield from bps.mvr(smargon.omega, 90)
157
+ yield from bps.mvr(smargon.omega, 90) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
158
158
 
159
159
  # need to wait for the OAV image to update
160
160
  # See #673 for improvements