mx-bluesky 0.0.2__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 (150) hide show
  1. mx_bluesky/__main__.py +1 -2
  2. mx_bluesky/_version.py +14 -2
  3. mx_bluesky/beamlines/i04/__init__.py +3 -0
  4. mx_bluesky/beamlines/i04/callbacks/murko_callback.py +45 -0
  5. mx_bluesky/beamlines/i04/thawing_plan.py +85 -0
  6. mx_bluesky/beamlines/i24/serial/__init__.py +49 -0
  7. mx_bluesky/beamlines/i24/serial/blueapi_config.yaml +12 -0
  8. mx_bluesky/{I24 → beamlines/i24}/serial/dcid.py +53 -41
  9. mx_bluesky/{I24 → beamlines/i24}/serial/extruder/EX-gui-edm/DetStage.edl +3 -4
  10. mx_bluesky/{I24 → beamlines/i24}/serial/extruder/EX-gui-edm/DiamondExtruder-I24-py3v1.edl +28 -32
  11. mx_bluesky/{I24 → beamlines/i24}/serial/extruder/EX-gui-edm/microdrop_alignment.edl +0 -1
  12. mx_bluesky/beamlines/i24/serial/extruder/i24ssx_Extruder_Collect_py3v2.py +516 -0
  13. mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/CustomChip_py3v1.edl +3 -4
  14. mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/DetStage.edl +3 -4
  15. mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/DiamondChipI24-py3v1.edl +273 -223
  16. mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/ME14E-GeneralPurpose.edl +0 -1
  17. mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/MappingLite-oxford_py3v1.edl +12 -13
  18. mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/PMAC_Command.edl +0 -1
  19. mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/Shutter_Control.edl +0 -1
  20. mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/microdrop_alignment.edl +0 -1
  21. mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/nudgechip.edl +0 -1
  22. mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/pumpprobe-py3v1.edl +273 -143
  23. mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/short1-laser.png +0 -0
  24. mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/short2-laser.png +0 -0
  25. mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/ft_utils.py +24 -1
  26. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Collect_py3v1.py +808 -0
  27. mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/i24ssx_Chip_Manager_py3v1.py +377 -416
  28. mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/i24ssx_Chip_Mapping_py3v1.py +34 -40
  29. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_StartUp_py3v1.py +328 -0
  30. mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/i24ssx_moveonclick.py +66 -48
  31. mx_bluesky/{I24 → beamlines/i24}/serial/log.py +66 -19
  32. mx_bluesky/beamlines/i24/serial/parameters/__init__.py +15 -0
  33. mx_bluesky/beamlines/i24/serial/parameters/constants.py +47 -0
  34. mx_bluesky/beamlines/i24/serial/parameters/experiment_parameters.py +103 -0
  35. mx_bluesky/beamlines/i24/serial/parameters/fixed_target/cs/cs_maker.json +9 -0
  36. mx_bluesky/{I24 → beamlines/i24}/serial/parameters/fixed_target/cs/motor_direction.txt +1 -1
  37. mx_bluesky/{I24 → beamlines/i24}/serial/parameters/fixed_target/pvar_files/minichip-oxford.pvar +1 -1
  38. mx_bluesky/beamlines/i24/serial/parameters/utils.py +42 -0
  39. mx_bluesky/beamlines/i24/serial/run_extruder.sh +19 -0
  40. mx_bluesky/beamlines/i24/serial/run_fixed_target.sh +22 -0
  41. mx_bluesky/beamlines/i24/serial/run_serial.py +36 -0
  42. mx_bluesky/{I24 → beamlines/i24}/serial/set_visit_directory.sh +6 -1
  43. mx_bluesky/{I24 → beamlines/i24}/serial/setup_beamline/pv.py +1 -62
  44. mx_bluesky/{I24 → beamlines/i24}/serial/setup_beamline/pv_abstract.py +6 -7
  45. mx_bluesky/{I24 → beamlines/i24}/serial/setup_beamline/setup_beamline.py +90 -269
  46. mx_bluesky/{I24 → beamlines/i24}/serial/setup_beamline/setup_detector.py +47 -40
  47. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_zebra_plans.py +459 -0
  48. mx_bluesky/beamlines/i24/serial/start_blueapi.sh +28 -0
  49. mx_bluesky/beamlines/i24/serial/write_nexus.py +105 -0
  50. mx_bluesky/example.py +4 -4
  51. mx_bluesky/hyperion/__init__.py +1 -0
  52. mx_bluesky/hyperion/__main__.py +374 -0
  53. mx_bluesky/hyperion/device_setup_plans/__init__.py +0 -0
  54. mx_bluesky/hyperion/device_setup_plans/dcm_pitch_roll_mirror_adjuster.py +134 -0
  55. mx_bluesky/hyperion/device_setup_plans/manipulate_sample.py +110 -0
  56. mx_bluesky/hyperion/device_setup_plans/position_detector.py +16 -0
  57. mx_bluesky/hyperion/device_setup_plans/read_hardware_for_setup.py +60 -0
  58. mx_bluesky/hyperion/device_setup_plans/setup_oav.py +87 -0
  59. mx_bluesky/hyperion/device_setup_plans/setup_panda.py +210 -0
  60. mx_bluesky/hyperion/device_setup_plans/setup_zebra.py +214 -0
  61. mx_bluesky/hyperion/device_setup_plans/smargon.py +25 -0
  62. mx_bluesky/hyperion/device_setup_plans/utils.py +44 -0
  63. mx_bluesky/hyperion/device_setup_plans/xbpm_feedback.py +93 -0
  64. mx_bluesky/hyperion/exceptions.py +47 -0
  65. mx_bluesky/hyperion/experiment_plans/__init__.py +30 -0
  66. mx_bluesky/hyperion/experiment_plans/experiment_registry.py +84 -0
  67. mx_bluesky/hyperion/experiment_plans/flyscan_xray_centre_plan.py +528 -0
  68. mx_bluesky/hyperion/experiment_plans/grid_detect_then_xray_centre_plan.py +209 -0
  69. mx_bluesky/hyperion/experiment_plans/oav_grid_detection_plan.py +173 -0
  70. mx_bluesky/hyperion/experiment_plans/oav_snapshot_plan.py +81 -0
  71. mx_bluesky/hyperion/experiment_plans/optimise_attenuation_plan.py +463 -0
  72. mx_bluesky/hyperion/experiment_plans/pin_centre_then_xray_centre_plan.py +119 -0
  73. mx_bluesky/hyperion/experiment_plans/pin_tip_centring_plan.py +164 -0
  74. mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +322 -0
  75. mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +436 -0
  76. mx_bluesky/hyperion/experiment_plans/set_energy_plan.py +68 -0
  77. mx_bluesky/hyperion/external_interaction/__init__.py +9 -0
  78. mx_bluesky/hyperion/external_interaction/callbacks/__init__.py +10 -0
  79. mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +148 -0
  80. mx_bluesky/hyperion/external_interaction/callbacks/aperture_change_callback.py +22 -0
  81. mx_bluesky/hyperion/external_interaction/callbacks/common/__init__.py +0 -0
  82. mx_bluesky/hyperion/external_interaction/callbacks/common/callback_util.py +46 -0
  83. mx_bluesky/hyperion/external_interaction/callbacks/common/ispyb_mapping.py +70 -0
  84. mx_bluesky/hyperion/external_interaction/callbacks/grid_detection_callback.py +88 -0
  85. mx_bluesky/hyperion/external_interaction/callbacks/ispyb_callback_base.py +203 -0
  86. mx_bluesky/hyperion/external_interaction/callbacks/log_uid_tag_callback.py +20 -0
  87. mx_bluesky/hyperion/external_interaction/callbacks/logging_callback.py +29 -0
  88. mx_bluesky/hyperion/external_interaction/callbacks/plan_reactive_callback.py +101 -0
  89. mx_bluesky/hyperion/external_interaction/callbacks/robot_load/ispyb_callback.py +88 -0
  90. mx_bluesky/hyperion/external_interaction/callbacks/rotation/__init__.py +0 -0
  91. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py +174 -0
  92. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_mapping.py +17 -0
  93. mx_bluesky/hyperion/external_interaction/callbacks/rotation/nexus_callback.py +102 -0
  94. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/__init__.py +0 -0
  95. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/ispyb_callback.py +269 -0
  96. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/ispyb_mapping.py +53 -0
  97. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/nexus_callback.py +95 -0
  98. mx_bluesky/hyperion/external_interaction/callbacks/zocalo_callback.py +92 -0
  99. mx_bluesky/hyperion/external_interaction/config_server.py +35 -0
  100. mx_bluesky/hyperion/external_interaction/exceptions.py +13 -0
  101. mx_bluesky/hyperion/external_interaction/ispyb/__init__.py +0 -0
  102. mx_bluesky/hyperion/external_interaction/ispyb/data_model.py +95 -0
  103. mx_bluesky/hyperion/external_interaction/ispyb/exp_eye_store.py +125 -0
  104. mx_bluesky/hyperion/external_interaction/ispyb/ispyb_store.py +276 -0
  105. mx_bluesky/hyperion/external_interaction/ispyb/ispyb_utils.py +29 -0
  106. mx_bluesky/hyperion/external_interaction/nexus/__init__.py +0 -0
  107. mx_bluesky/hyperion/external_interaction/nexus/nexus_utils.py +148 -0
  108. mx_bluesky/hyperion/external_interaction/nexus/write_nexus.py +114 -0
  109. mx_bluesky/hyperion/log.py +99 -0
  110. mx_bluesky/hyperion/parameters/__init__.py +2 -0
  111. mx_bluesky/hyperion/parameters/cli.py +68 -0
  112. mx_bluesky/hyperion/parameters/components.py +253 -0
  113. mx_bluesky/hyperion/parameters/constants.py +158 -0
  114. mx_bluesky/hyperion/parameters/gridscan.py +216 -0
  115. mx_bluesky/hyperion/parameters/rotation.py +160 -0
  116. mx_bluesky/hyperion/resources/panda/panda-gridscan.yaml +964 -0
  117. mx_bluesky/hyperion/tracing.py +28 -0
  118. mx_bluesky/hyperion/utils/context.py +84 -0
  119. mx_bluesky/hyperion/utils/utils.py +25 -0
  120. mx_bluesky/hyperion/utils/validation.py +196 -0
  121. mx_bluesky/jupyter_example.ipynb +3 -2
  122. {mx_bluesky-0.0.2.dist-info → mx_bluesky-1.1.0.dist-info}/METADATA +53 -32
  123. mx_bluesky-1.1.0.dist-info/RECORD +136 -0
  124. {mx_bluesky-0.0.2.dist-info → mx_bluesky-1.1.0.dist-info}/WHEEL +1 -1
  125. mx_bluesky-1.1.0.dist-info/entry_points.txt +8 -0
  126. mx_bluesky/I24/serial/extruder/i24ssx_Extruder_Collect_py3v2.py +0 -476
  127. mx_bluesky/I24/serial/fixed_target/FT-gui-edm/ME14E-motors.edl +0 -1874
  128. mx_bluesky/I24/serial/fixed_target/i24ssx_Chip_Collect_py3v1.py +0 -706
  129. mx_bluesky/I24/serial/fixed_target/i24ssx_Chip_StartUp_py3v1.py +0 -463
  130. mx_bluesky/I24/serial/parameters/__init__.py +0 -5
  131. mx_bluesky/I24/serial/parameters/constants.py +0 -39
  132. mx_bluesky/I24/serial/parameters/fixed_target/cs/cs_maker.json +0 -9
  133. mx_bluesky/I24/serial/parameters/fixed_target/cs/fiducial_1.txt +0 -4
  134. mx_bluesky/I24/serial/parameters/fixed_target/cs/fiducial_2.txt +0 -4
  135. mx_bluesky/I24/serial/parameters/fixed_target/litemaps/currentchip.map +0 -81
  136. mx_bluesky/I24/serial/parameters/fixed_target/parameters.txt +0 -13
  137. mx_bluesky/I24/serial/run_serial.py +0 -52
  138. mx_bluesky/I24/serial/write_nexus.py +0 -113
  139. mx_bluesky-0.0.2.dist-info/RECORD +0 -58
  140. mx_bluesky-0.0.2.dist-info/entry_points.txt +0 -4
  141. /mx_bluesky/{I24 → beamlines}/__init__.py +0 -0
  142. /mx_bluesky/{I24/serial → beamlines/i24}/__init__.py +0 -0
  143. /mx_bluesky/{I24 → beamlines/i24}/serial/extruder/__init__.py +0 -0
  144. /mx_bluesky/{I24 → beamlines/i24}/serial/fixed_target/__init__.py +0 -0
  145. /mx_bluesky/{I24 → beamlines/i24}/serial/parameters/fixed_target/pvar_files/oxford.pvar +0 -0
  146. /mx_bluesky/{I24 → beamlines/i24}/serial/run_ssx.sh +0 -0
  147. /mx_bluesky/{I24 → beamlines/i24}/serial/setup_beamline/__init__.py +0 -0
  148. /mx_bluesky/{I24 → beamlines/i24}/serial/setup_beamline/ca.py +0 -0
  149. {mx_bluesky-0.0.2.dist-info → mx_bluesky-1.1.0.dist-info}/LICENSE +0 -0
  150. {mx_bluesky-0.0.2.dist-info → mx_bluesky-1.1.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,47 @@
1
+ from collections.abc import Callable, Generator
2
+ from typing import TypeVar
3
+
4
+ from bluesky.plan_stubs import null
5
+ from bluesky.preprocessors import contingency_wrapper
6
+ from bluesky.utils import Msg
7
+
8
+
9
+ class WarningException(Exception):
10
+ """An exception used when we want to warn GDA of a
11
+ problem but continue with UDC anyway"""
12
+
13
+ pass
14
+
15
+
16
+ T = TypeVar("T")
17
+
18
+
19
+ def catch_exception_and_warn(
20
+ exception_to_catch: type[Exception],
21
+ func: Callable[..., Generator[Msg, None, T]],
22
+ *args,
23
+ **kwargs,
24
+ ) -> Generator[Msg, None, T]:
25
+ """A plan wrapper to catch a specific exception and instead raise a WarningException,
26
+ so that UDC is not halted
27
+
28
+ Example usage:
29
+
30
+ 'def plan_which_can_raise_exception_a(*args, **kwargs):
31
+ ...
32
+ yield from catch_exception_and_warn(ExceptionA, plan_which_can_raise_exception_a, **args, **kwargs)'
33
+
34
+ This will catch ExceptionA raised by the plan and instead raise a WarningException
35
+ """
36
+
37
+ def warn_if_exception_matches(exception: Exception):
38
+ if isinstance(exception, exception_to_catch):
39
+ raise WarningException(str(exception))
40
+ yield from null()
41
+
42
+ return (
43
+ yield from contingency_wrapper(
44
+ func(*args, **kwargs),
45
+ except_plan=warn_if_exception_matches,
46
+ )
47
+ )
@@ -0,0 +1,30 @@
1
+ """This module contains the experimental plans which hyperion can run.
2
+
3
+ The __all__ list in here are the plans that are externally available from outside Hyperion.
4
+ """
5
+
6
+ from mx_bluesky.hyperion.experiment_plans.flyscan_xray_centre_plan import (
7
+ flyscan_xray_centre,
8
+ )
9
+ from mx_bluesky.hyperion.experiment_plans.grid_detect_then_xray_centre_plan import (
10
+ grid_detect_then_xray_centre,
11
+ )
12
+ from mx_bluesky.hyperion.experiment_plans.pin_centre_then_xray_centre_plan import (
13
+ pin_tip_centre_then_xray_centre,
14
+ )
15
+ from mx_bluesky.hyperion.experiment_plans.robot_load_then_centre_plan import (
16
+ robot_load_then_centre,
17
+ )
18
+ from mx_bluesky.hyperion.experiment_plans.rotation_scan_plan import (
19
+ multi_rotation_scan,
20
+ rotation_scan,
21
+ )
22
+
23
+ __all__ = [
24
+ "flyscan_xray_centre",
25
+ "grid_detect_then_xray_centre",
26
+ "rotation_scan",
27
+ "pin_tip_centre_then_xray_centre",
28
+ "multi_rotation_scan",
29
+ "robot_load_then_centre",
30
+ ]
@@ -0,0 +1,84 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Callable
4
+ from typing import TypedDict
5
+
6
+ import mx_bluesky.hyperion.experiment_plans.flyscan_xray_centre_plan as flyscan_xray_centre_plan
7
+ import mx_bluesky.hyperion.experiment_plans.rotation_scan_plan as rotation_scan_plan
8
+ from mx_bluesky.hyperion.experiment_plans import (
9
+ grid_detect_then_xray_centre_plan,
10
+ pin_centre_then_xray_centre_plan,
11
+ robot_load_then_centre_plan,
12
+ )
13
+ from mx_bluesky.hyperion.external_interaction.callbacks.common.callback_util import (
14
+ CallbacksFactory,
15
+ create_gridscan_callbacks,
16
+ create_robot_load_and_centre_callbacks,
17
+ create_rotation_callbacks,
18
+ )
19
+ from mx_bluesky.hyperion.parameters.gridscan import (
20
+ GridScanWithEdgeDetect,
21
+ PinTipCentreThenXrayCentre,
22
+ RobotLoadThenCentre,
23
+ ThreeDGridScan,
24
+ )
25
+ from mx_bluesky.hyperion.parameters.rotation import MultiRotationScan, RotationScan
26
+
27
+
28
+ def not_implemented():
29
+ raise NotImplementedError
30
+
31
+
32
+ def do_nothing():
33
+ pass
34
+
35
+
36
+ class ExperimentRegistryEntry(TypedDict):
37
+ setup: Callable
38
+ param_type: type[
39
+ ThreeDGridScan
40
+ | GridScanWithEdgeDetect
41
+ | RotationScan
42
+ | MultiRotationScan
43
+ | PinTipCentreThenXrayCentre
44
+ | RobotLoadThenCentre
45
+ ]
46
+ callbacks_factory: CallbacksFactory
47
+
48
+
49
+ PLAN_REGISTRY: dict[str, ExperimentRegistryEntry] = {
50
+ "flyscan_xray_centre": {
51
+ "setup": flyscan_xray_centre_plan.create_devices,
52
+ "param_type": ThreeDGridScan,
53
+ "callbacks_factory": create_gridscan_callbacks,
54
+ },
55
+ "grid_detect_then_xray_centre": {
56
+ "setup": grid_detect_then_xray_centre_plan.create_devices,
57
+ "param_type": GridScanWithEdgeDetect,
58
+ "callbacks_factory": create_gridscan_callbacks,
59
+ },
60
+ "rotation_scan": {
61
+ "setup": rotation_scan_plan.create_devices,
62
+ "param_type": RotationScan,
63
+ "callbacks_factory": create_rotation_callbacks,
64
+ },
65
+ "pin_tip_centre_then_xray_centre": {
66
+ "setup": pin_centre_then_xray_centre_plan.create_devices,
67
+ "param_type": PinTipCentreThenXrayCentre,
68
+ "callbacks_factory": create_gridscan_callbacks,
69
+ },
70
+ "robot_load_then_centre": {
71
+ "setup": robot_load_then_centre_plan.create_devices,
72
+ "param_type": RobotLoadThenCentre,
73
+ "callbacks_factory": create_robot_load_and_centre_callbacks,
74
+ },
75
+ "multi_rotation_scan": {
76
+ "setup": rotation_scan_plan.create_devices,
77
+ "param_type": MultiRotationScan,
78
+ "callbacks_factory": create_rotation_callbacks,
79
+ },
80
+ }
81
+
82
+
83
+ class PlanNotFound(Exception):
84
+ pass
@@ -0,0 +1,528 @@
1
+ from __future__ import annotations
2
+
3
+ import dataclasses
4
+ from collections.abc import Callable
5
+ from functools import partial
6
+ from pathlib import Path
7
+ from time import time
8
+ from typing import Protocol
9
+
10
+ import bluesky.plan_stubs as bps
11
+ import bluesky.preprocessors as bpp
12
+ import numpy as np
13
+ from blueapi.core import BlueskyContext, MsgGenerator
14
+ from dodal.devices.aperturescatterguard import (
15
+ ApertureScatterguard,
16
+ ApertureValue,
17
+ )
18
+ from dodal.devices.attenuator import Attenuator
19
+ from dodal.devices.backlight import Backlight
20
+ from dodal.devices.dcm import DCM
21
+ from dodal.devices.eiger import EigerDetector
22
+ from dodal.devices.fast_grid_scan import (
23
+ FastGridScanCommon,
24
+ PandAFastGridScan,
25
+ ZebraFastGridScan,
26
+ )
27
+ from dodal.devices.fast_grid_scan import (
28
+ set_fast_grid_scan_params as set_flyscan_params,
29
+ )
30
+ from dodal.devices.flux import Flux
31
+ from dodal.devices.robot import BartRobot
32
+ from dodal.devices.s4_slit_gaps import S4SlitGaps
33
+ from dodal.devices.smargon import Smargon, StubPosition
34
+ from dodal.devices.synchrotron import Synchrotron
35
+ from dodal.devices.undulator import Undulator
36
+ from dodal.devices.xbpm_feedback import XBPMFeedback
37
+ from dodal.devices.zebra import Zebra
38
+ from dodal.devices.zebra_controlled_shutter import ZebraShutter
39
+ from dodal.devices.zocalo.zocalo_results import (
40
+ ZOCALO_READING_PLAN_NAME,
41
+ ZOCALO_STAGE_GROUP,
42
+ ZocaloResults,
43
+ get_processing_result,
44
+ )
45
+ from dodal.plans.check_topup import check_topup_and_wait_if_necessary
46
+ from ophyd_async.fastcs.panda import HDFPanda
47
+ from scanspec.core import AxesPoints, Axis
48
+
49
+ from mx_bluesky.hyperion.device_setup_plans.manipulate_sample import move_x_y_z
50
+ from mx_bluesky.hyperion.device_setup_plans.read_hardware_for_setup import (
51
+ read_hardware_during_collection,
52
+ read_hardware_for_zocalo,
53
+ read_hardware_pre_collection,
54
+ )
55
+ from mx_bluesky.hyperion.device_setup_plans.setup_panda import (
56
+ disarm_panda_for_gridscan,
57
+ set_panda_directory,
58
+ setup_panda_for_flyscan,
59
+ )
60
+ from mx_bluesky.hyperion.device_setup_plans.setup_zebra import (
61
+ setup_zebra_for_gridscan,
62
+ setup_zebra_for_panda_flyscan,
63
+ tidy_up_zebra_after_gridscan,
64
+ )
65
+ from mx_bluesky.hyperion.device_setup_plans.xbpm_feedback import (
66
+ transmission_and_xbpm_feedback_for_collection_decorator,
67
+ )
68
+ from mx_bluesky.hyperion.exceptions import WarningException
69
+ from mx_bluesky.hyperion.log import LOGGER
70
+ from mx_bluesky.hyperion.parameters.constants import CONST
71
+ from mx_bluesky.hyperion.parameters.gridscan import ThreeDGridScan
72
+ from mx_bluesky.hyperion.tracing import TRACER
73
+ from mx_bluesky.hyperion.utils.context import device_composite_from_context
74
+
75
+
76
+ class SmargonSpeedException(Exception):
77
+ pass
78
+
79
+
80
+ @dataclasses.dataclass
81
+ class FlyScanXRayCentreComposite:
82
+ """All devices which are directly or indirectly required by this plan"""
83
+
84
+ aperture_scatterguard: ApertureScatterguard
85
+ attenuator: Attenuator
86
+ backlight: Backlight
87
+ dcm: DCM
88
+ eiger: EigerDetector
89
+ zebra_fast_grid_scan: ZebraFastGridScan
90
+ flux: Flux
91
+ s4_slit_gaps: S4SlitGaps
92
+ smargon: Smargon
93
+ undulator: Undulator
94
+ synchrotron: Synchrotron
95
+ xbpm_feedback: XBPMFeedback
96
+ zebra: Zebra
97
+ zocalo: ZocaloResults
98
+ panda: HDFPanda
99
+ panda_fast_grid_scan: PandAFastGridScan
100
+ robot: BartRobot
101
+ sample_shutter: ZebraShutter
102
+
103
+ @property
104
+ def sample_motors(self) -> Smargon:
105
+ """Convenience alias with a more user-friendly name"""
106
+ return self.smargon
107
+
108
+
109
+ def create_devices(context: BlueskyContext) -> FlyScanXRayCentreComposite:
110
+ """Creates the devices required for the plan and connect to them"""
111
+ return device_composite_from_context(context, FlyScanXRayCentreComposite)
112
+
113
+
114
+ def flyscan_xray_centre(
115
+ composite: FlyScanXRayCentreComposite,
116
+ parameters: ThreeDGridScan,
117
+ ) -> MsgGenerator:
118
+ """Create the plan to run the grid scan based on provided parameters.
119
+
120
+ The ispyb handler should be added to the whole gridscan as we want to capture errors
121
+ at any point in it.
122
+
123
+ Args:
124
+ parameters (ThreeDGridScan): The parameters to run the scan.
125
+
126
+ Returns:
127
+ Generator: The plan for the gridscan
128
+ """
129
+ parameters.features.update_self_from_server()
130
+ 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
133
+
134
+ feature_controlled = _get_feature_controlled(composite, parameters)
135
+
136
+ @bpp.set_run_key_decorator(CONST.PLAN.GRIDSCAN_OUTER)
137
+ @bpp.run_decorator( # attach experiment metadata to the start document
138
+ md={
139
+ "subplan_name": CONST.PLAN.GRIDSCAN_OUTER,
140
+ CONST.TRIGGER.ZOCALO: CONST.PLAN.DO_FGS,
141
+ "zocalo_environment": parameters.zocalo_environment,
142
+ "hyperion_parameters": parameters.model_dump_json(),
143
+ "activate_callbacks": [
144
+ "GridscanNexusFileCallback",
145
+ ],
146
+ }
147
+ )
148
+ @bpp.finalize_decorator(lambda: feature_controlled.tidy_plan(composite))
149
+ @transmission_and_xbpm_feedback_for_collection_decorator(
150
+ composite.xbpm_feedback,
151
+ composite.attenuator,
152
+ parameters.transmission_frac,
153
+ )
154
+ def run_gridscan_and_move_and_tidy(
155
+ fgs_composite: FlyScanXRayCentreComposite,
156
+ params: ThreeDGridScan,
157
+ feature_controlled: _FeatureControlled,
158
+ ):
159
+ yield from run_gridscan_and_move(fgs_composite, params, feature_controlled)
160
+
161
+ return run_gridscan_and_move_and_tidy(composite, parameters, feature_controlled)
162
+
163
+
164
+ @bpp.set_run_key_decorator(CONST.PLAN.GRIDSCAN_AND_MOVE)
165
+ @bpp.run_decorator(md={"subplan_name": CONST.PLAN.GRIDSCAN_AND_MOVE})
166
+ def run_gridscan_and_move(
167
+ fgs_composite: FlyScanXRayCentreComposite,
168
+ parameters: ThreeDGridScan,
169
+ feature_controlled: _FeatureControlled,
170
+ ) -> MsgGenerator:
171
+ """A multi-run plan which runs a gridscan, gets the results from zocalo
172
+ and moves to the centre of mass determined by zocalo"""
173
+
174
+ # We get the initial motor positions so we can return to them on zocalo failure
175
+ initial_xyz = np.array(
176
+ [
177
+ (yield from bps.rd(fgs_composite.sample_motors.x)),
178
+ (yield from bps.rd(fgs_composite.sample_motors.y)),
179
+ (yield from bps.rd(fgs_composite.sample_motors.z)),
180
+ ]
181
+ )
182
+
183
+ yield from feature_controlled.setup_trigger(fgs_composite, parameters, initial_xyz)
184
+
185
+ LOGGER.info("Starting grid scan")
186
+ yield from bps.stage(
187
+ fgs_composite.zocalo, group=ZOCALO_STAGE_GROUP
188
+ ) # connect to zocalo and make sure the queue is clear
189
+ yield from run_gridscan(fgs_composite, parameters, feature_controlled)
190
+
191
+ LOGGER.info("Grid scan finished, getting results.")
192
+
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
203
+ )
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
211
+ )
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)
230
+
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()
234
+
235
+
236
+ @bpp.set_run_key_decorator(CONST.PLAN.GRIDSCAN_MAIN)
237
+ @bpp.run_decorator(md={"subplan_name": CONST.PLAN.GRIDSCAN_MAIN})
238
+ def run_gridscan(
239
+ fgs_composite: FlyScanXRayCentreComposite,
240
+ parameters: ThreeDGridScan,
241
+ feature_controlled: _FeatureControlled,
242
+ md={ # noqa
243
+ "plan_name": CONST.PLAN.GRIDSCAN_MAIN,
244
+ },
245
+ ):
246
+ sample_motors = fgs_composite.sample_motors
247
+
248
+ # Currently gridscan only works for omega 0, see #
249
+ with TRACER.start_span("moving_omega_to_0"):
250
+ yield from bps.abs_set(sample_motors.omega, 0)
251
+
252
+ # We only subscribe to the communicator callback for run_gridscan, so this is where
253
+ # we should generate an event reading the values which need to be included in the
254
+ # ispyb deposition
255
+ with TRACER.start_span("ispyb_hardware_readings"):
256
+ yield from read_hardware_pre_collection(
257
+ fgs_composite.undulator,
258
+ fgs_composite.synchrotron,
259
+ fgs_composite.s4_slit_gaps,
260
+ fgs_composite.robot,
261
+ fgs_composite.smargon,
262
+ )
263
+
264
+ read_during_collection = partial(
265
+ read_hardware_during_collection,
266
+ fgs_composite.aperture_scatterguard,
267
+ fgs_composite.attenuator,
268
+ fgs_composite.flux,
269
+ fgs_composite.dcm,
270
+ fgs_composite.eiger,
271
+ )
272
+
273
+ LOGGER.info("Setting fgs params")
274
+ yield from feature_controlled.set_flyscan_params()
275
+
276
+ LOGGER.info("Waiting for gridscan validity check")
277
+ yield from wait_for_gridscan_valid(feature_controlled.fgs_motors)
278
+
279
+ LOGGER.info("Waiting for arming to finish")
280
+ yield from bps.wait(CONST.WAIT.GRID_READY_FOR_DC)
281
+ yield from bps.stage(fgs_composite.eiger)
282
+
283
+ yield from kickoff_and_complete_gridscan(
284
+ feature_controlled.fgs_motors,
285
+ fgs_composite.eiger,
286
+ fgs_composite.synchrotron,
287
+ [parameters.scan_points_first_grid, parameters.scan_points_second_grid],
288
+ parameters.scan_indices,
289
+ do_during_run=read_during_collection,
290
+ )
291
+ yield from bps.abs_set(feature_controlled.fgs_motors.z_steps, 0, wait=False)
292
+
293
+
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
+ def wait_for_gridscan_valid(fgs_motors: FastGridScanCommon, timeout=0.5):
350
+ LOGGER.info("Waiting for valid fgs_params")
351
+ SLEEP_PER_CHECK = 0.1
352
+ times_to_check = int(timeout / SLEEP_PER_CHECK)
353
+ for _ in range(times_to_check):
354
+ scan_invalid = yield from bps.rd(fgs_motors.scan_invalid)
355
+ pos_counter = yield from bps.rd(fgs_motors.position_counter)
356
+ LOGGER.debug(
357
+ f"Scan invalid: {scan_invalid} and position counter: {pos_counter}"
358
+ )
359
+ if not scan_invalid and pos_counter == 0:
360
+ LOGGER.info("Gridscan scan valid and position counter reset")
361
+ return
362
+ yield from bps.sleep(SLEEP_PER_CHECK)
363
+ raise WarningException("Scan invalid - pin too long/short/bent and out of range")
364
+
365
+
366
+ def set_aperture_for_bbox_size(
367
+ aperture_device: ApertureScatterguard,
368
+ bbox_size: list[int] | np.ndarray,
369
+ ):
370
+ # bbox_size is [x,y,z], for i03 we only care about x
371
+ new_selected_aperture = (
372
+ ApertureValue.MEDIUM if bbox_size[0] < 2 else ApertureValue.LARGE
373
+ )
374
+ LOGGER.info(
375
+ f"Setting aperture to {new_selected_aperture} based on bounding box size {bbox_size}."
376
+ )
377
+
378
+ @bpp.set_run_key_decorator("change_aperture")
379
+ @bpp.run_decorator(
380
+ md={
381
+ "subplan_name": "change_aperture",
382
+ "aperture_size": new_selected_aperture.value,
383
+ }
384
+ )
385
+ def set_aperture():
386
+ yield from bps.abs_set(aperture_device, new_selected_aperture)
387
+
388
+ yield from set_aperture()
389
+
390
+
391
+ @dataclasses.dataclass
392
+ class _FeatureControlled:
393
+ class _ZebraSetup(Protocol):
394
+ def __call__(
395
+ self, zebra: Zebra, group="setup_zebra_for_gridscan", wait=True
396
+ ) -> MsgGenerator: ...
397
+
398
+ class _ExtraSetup(Protocol):
399
+ def __call__(
400
+ self,
401
+ fgs_composite: FlyScanXRayCentreComposite,
402
+ parameters: ThreeDGridScan,
403
+ initial_xyz: np.ndarray,
404
+ ) -> MsgGenerator: ...
405
+
406
+ setup_trigger: _ExtraSetup
407
+ tidy_plan: Callable[[FlyScanXRayCentreComposite], MsgGenerator]
408
+ set_flyscan_params: Callable[[], MsgGenerator]
409
+ fgs_motors: FastGridScanCommon
410
+
411
+
412
+ def _get_feature_controlled(
413
+ fgs_composite: FlyScanXRayCentreComposite,
414
+ parameters: ThreeDGridScan,
415
+ ):
416
+ if parameters.features.use_panda_for_gridscan:
417
+ return _FeatureControlled(
418
+ setup_trigger=_panda_triggering_setup,
419
+ tidy_plan=_panda_tidy,
420
+ set_flyscan_params=partial(
421
+ set_flyscan_params,
422
+ fgs_composite.panda_fast_grid_scan,
423
+ parameters.panda_FGS_params,
424
+ ),
425
+ fgs_motors=fgs_composite.panda_fast_grid_scan,
426
+ )
427
+ else:
428
+ return _FeatureControlled(
429
+ setup_trigger=_zebra_triggering_setup,
430
+ tidy_plan=partial(_generic_tidy, group="flyscan_zebra_tidy", wait=True),
431
+ set_flyscan_params=partial(
432
+ set_flyscan_params,
433
+ fgs_composite.zebra_fast_grid_scan,
434
+ parameters.FGS_params,
435
+ ),
436
+ fgs_motors=fgs_composite.zebra_fast_grid_scan,
437
+ )
438
+
439
+
440
+ def _generic_tidy(
441
+ fgs_composite: FlyScanXRayCentreComposite, group, wait=True
442
+ ) -> MsgGenerator:
443
+ LOGGER.info("Tidying up Zebra")
444
+ yield from tidy_up_zebra_after_gridscan(
445
+ fgs_composite.zebra, fgs_composite.sample_shutter, group=group, wait=wait
446
+ )
447
+ LOGGER.info("Tidying up Zocalo")
448
+ # make sure we don't consume any other results
449
+ yield from bps.unstage(fgs_composite.zocalo, group=group, wait=wait)
450
+
451
+
452
+ def _panda_tidy(fgs_composite: FlyScanXRayCentreComposite):
453
+ group = "panda_flyscan_tidy"
454
+ LOGGER.info("Disabling panda blocks")
455
+ yield from disarm_panda_for_gridscan(fgs_composite.panda, group)
456
+ yield from _generic_tidy(fgs_composite, group, False)
457
+ yield from bps.wait(group, timeout=10)
458
+ yield from bps.unstage(fgs_composite.panda)
459
+
460
+
461
+ def _zebra_triggering_setup(
462
+ fgs_composite: FlyScanXRayCentreComposite,
463
+ parameters: ThreeDGridScan,
464
+ initial_xyz: np.ndarray,
465
+ ):
466
+ yield from setup_zebra_for_gridscan(
467
+ fgs_composite.zebra, fgs_composite.sample_shutter, wait=True
468
+ )
469
+
470
+
471
+ def _panda_triggering_setup(
472
+ fgs_composite: FlyScanXRayCentreComposite,
473
+ parameters: ThreeDGridScan,
474
+ initial_xyz: np.ndarray,
475
+ ):
476
+ LOGGER.info("Setting up Panda for flyscan")
477
+
478
+ run_up_distance_mm = yield from bps.rd(
479
+ fgs_composite.panda_fast_grid_scan.run_up_distance_mm
480
+ )
481
+
482
+ # Set the time between x steps pv
483
+ DEADTIME_S = 1e-6 # according to https://www.dectris.com/en/detectors/x-ray-detectors/eiger2/eiger2-for-synchrotrons/eiger2-x/
484
+
485
+ time_between_x_steps_ms = (DEADTIME_S + parameters.exposure_time_s) * 1e3
486
+
487
+ smargon_speed_limit_mm_per_s = yield from bps.rd(
488
+ fgs_composite.smargon.x.max_velocity
489
+ )
490
+
491
+ sample_velocity_mm_per_s = (
492
+ parameters.panda_FGS_params.x_step_size * 1e3 / time_between_x_steps_ms
493
+ )
494
+ if sample_velocity_mm_per_s > smargon_speed_limit_mm_per_s:
495
+ raise SmargonSpeedException(
496
+ f"Smargon speed was calculated from x step size\
497
+ {parameters.panda_FGS_params.x_step_size} and\
498
+ time_between_x_steps_ms {time_between_x_steps_ms} as\
499
+ {sample_velocity_mm_per_s}. The smargon's speed limit is\
500
+ {smargon_speed_limit_mm_per_s} mm/s."
501
+ )
502
+ else:
503
+ LOGGER.info(
504
+ f"Panda grid scan: Smargon speed set to {smargon_speed_limit_mm_per_s} mm/s"
505
+ f" and using a run-up distance of {run_up_distance_mm}"
506
+ )
507
+
508
+ yield from bps.mv(
509
+ fgs_composite.panda_fast_grid_scan.time_between_x_steps_ms,
510
+ time_between_x_steps_ms,
511
+ )
512
+
513
+ directory_provider_root = Path(parameters.storage_directory)
514
+ yield from set_panda_directory(directory_provider_root)
515
+
516
+ yield from setup_panda_for_flyscan(
517
+ fgs_composite.panda,
518
+ parameters.panda_FGS_params,
519
+ initial_xyz[0],
520
+ parameters.exposure_time_s,
521
+ time_between_x_steps_ms,
522
+ sample_velocity_mm_per_s,
523
+ )
524
+
525
+ LOGGER.info("Setting up Zebra for panda flyscan")
526
+ yield from setup_zebra_for_panda_flyscan(
527
+ fgs_composite.zebra, fgs_composite.sample_shutter, wait=True
528
+ )