mx-bluesky 1.4.9__py3-none-any.whl → 1.5.1__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 (57) hide show
  1. mx_bluesky/_version.py +2 -2
  2. mx_bluesky/beamlines/i24/serial/__init__.py +4 -2
  3. mx_bluesky/beamlines/i24/serial/blueapi_config.yaml +4 -0
  4. mx_bluesky/beamlines/i24/serial/extruder/i24ssx_Extruder_Collect_py3v2.py +8 -8
  5. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Collect_py3v1.py +36 -4
  6. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Manager_py3v1.py +6 -5
  7. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_beamline.py +2 -2
  8. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_detector.py +5 -5
  9. mx_bluesky/beamlines/i24/serial/web_gui_plans/general_plans.py +79 -10
  10. mx_bluesky/common/device_setup_plans/manipulate_sample.py +4 -1
  11. mx_bluesky/common/device_setup_plans/robot_load_unload.py +123 -0
  12. mx_bluesky/common/device_setup_plans/utils.py +49 -0
  13. mx_bluesky/common/{plans → experiment_plans}/common_flyscan_xray_centre_plan.py +12 -19
  14. mx_bluesky/{hyperion/experiment_plans/grid_detect_then_xray_centre_plan.py → common/experiment_plans/common_grid_detect_then_xray_centre_plan.py} +108 -136
  15. mx_bluesky/common/{plans → experiment_plans}/inner_plans/do_fgs.py +1 -1
  16. mx_bluesky/common/experiment_plans/oav_grid_detection_plan.py +5 -13
  17. mx_bluesky/{hyperion → common}/experiment_plans/oav_snapshot_plan.py +5 -2
  18. mx_bluesky/common/external_interaction/ispyb/exp_eye_store.py +26 -24
  19. mx_bluesky/common/external_interaction/ispyb/ispyb_store.py +0 -1
  20. mx_bluesky/common/external_interaction/nexus/write_nexus.py +2 -2
  21. mx_bluesky/common/parameters/components.py +8 -3
  22. mx_bluesky/common/parameters/constants.py +4 -3
  23. mx_bluesky/common/parameters/device_composites.py +65 -0
  24. mx_bluesky/common/utils/__init__.py +0 -0
  25. mx_bluesky/common/xrc_result.py +25 -2
  26. mx_bluesky/hyperion/device_setup_plans/utils.py +0 -48
  27. mx_bluesky/hyperion/experiment_plans/__init__.py +3 -3
  28. mx_bluesky/hyperion/experiment_plans/experiment_registry.py +3 -3
  29. mx_bluesky/hyperion/experiment_plans/hyperion_flyscan_xray_centre_plan.py +46 -41
  30. mx_bluesky/hyperion/experiment_plans/hyperion_grid_detect_then_xray_centre_plan.py +60 -0
  31. mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py +26 -8
  32. mx_bluesky/hyperion/experiment_plans/pin_centre_then_xray_centre_plan.py +26 -15
  33. mx_bluesky/hyperion/experiment_plans/robot_load_and_change_energy.py +21 -75
  34. mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +10 -8
  35. mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +20 -15
  36. mx_bluesky/hyperion/external_interaction/agamemnon.py +4 -4
  37. mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +1 -1
  38. mx_bluesky/hyperion/external_interaction/callbacks/{robot_load → robot_actions}/ispyb_callback.py +28 -19
  39. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py +1 -1
  40. mx_bluesky/hyperion/external_interaction/callbacks/snapshot_callback.py +3 -0
  41. mx_bluesky/hyperion/external_interaction/config_server.py +0 -11
  42. mx_bluesky/hyperion/parameters/constants.py +1 -6
  43. mx_bluesky/hyperion/parameters/device_composites.py +5 -27
  44. mx_bluesky/hyperion/parameters/gridscan.py +2 -6
  45. mx_bluesky/hyperion/parameters/load_centre_collect.py +15 -0
  46. mx_bluesky/hyperion/parameters/rotation.py +7 -3
  47. {mx_bluesky-1.4.9.dist-info → mx_bluesky-1.5.1.dist-info}/METADATA +5 -4
  48. {mx_bluesky-1.4.9.dist-info → mx_bluesky-1.5.1.dist-info}/RECORD +56 -52
  49. mx_bluesky/hyperion/utils/validation.py +0 -196
  50. /mx_bluesky/common/{plans → experiment_plans}/__init__.py +0 -0
  51. /mx_bluesky/common/{plans → experiment_plans}/inner_plans/__init__ .py +0 -0
  52. /mx_bluesky/common/{plans → experiment_plans}/read_hardware.py +0 -0
  53. /mx_bluesky/common/{plans → experiment_plans}/write_sample_status.py +0 -0
  54. {mx_bluesky-1.4.9.dist-info → mx_bluesky-1.5.1.dist-info}/WHEEL +0 -0
  55. {mx_bluesky-1.4.9.dist-info → mx_bluesky-1.5.1.dist-info}/entry_points.txt +0 -0
  56. {mx_bluesky-1.4.9.dist-info → mx_bluesky-1.5.1.dist-info}/licenses/LICENSE +0 -0
  57. {mx_bluesky-1.4.9.dist-info → mx_bluesky-1.5.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,65 @@
1
+ import pydantic
2
+ from dodal.devices.aperturescatterguard import (
3
+ ApertureScatterguard,
4
+ )
5
+ from dodal.devices.attenuator.attenuator import BinaryFilterAttenuator
6
+ from dodal.devices.backlight import Backlight
7
+ from dodal.devices.common_dcm import BaseDCM
8
+ from dodal.devices.detector.detector_motion import DetectorMotion
9
+ from dodal.devices.eiger import EigerDetector
10
+ from dodal.devices.fast_grid_scan import (
11
+ ZebraFastGridScan,
12
+ )
13
+ from dodal.devices.flux import Flux
14
+ from dodal.devices.i03 import Beamstop
15
+ from dodal.devices.oav.oav_detector import OAV
16
+ from dodal.devices.oav.pin_image_recognition import PinTipDetection
17
+ from dodal.devices.robot import BartRobot
18
+ from dodal.devices.s4_slit_gaps import S4SlitGaps
19
+ from dodal.devices.smargon import Smargon
20
+ from dodal.devices.synchrotron import Synchrotron
21
+ from dodal.devices.undulator import Undulator
22
+ from dodal.devices.xbpm_feedback import XBPMFeedback
23
+ from dodal.devices.zebra.zebra import Zebra
24
+ from dodal.devices.zebra.zebra_controlled_shutter import ZebraShutter
25
+ from dodal.devices.zocalo import ZocaloResults
26
+
27
+
28
+ @pydantic.dataclasses.dataclass(config={"arbitrary_types_allowed": True})
29
+ class FlyScanEssentialDevices:
30
+ eiger: EigerDetector
31
+ synchrotron: Synchrotron
32
+ zocalo: ZocaloResults
33
+ smargon: Smargon
34
+
35
+
36
+ @pydantic.dataclasses.dataclass(config={"arbitrary_types_allowed": True})
37
+ class OavGridDetectionComposite:
38
+ """All devices which are directly or indirectly required by this plan"""
39
+
40
+ backlight: Backlight
41
+ oav: OAV
42
+ smargon: Smargon
43
+ pin_tip_detection: PinTipDetection
44
+
45
+
46
+ @pydantic.dataclasses.dataclass(config={"arbitrary_types_allowed": True})
47
+ class GridDetectThenXRayCentreComposite(FlyScanEssentialDevices):
48
+ """All devices which are directly or indirectly required by this plan"""
49
+
50
+ aperture_scatterguard: ApertureScatterguard
51
+ attenuator: BinaryFilterAttenuator
52
+ backlight: Backlight
53
+ beamstop: Beamstop
54
+ dcm: BaseDCM
55
+ detector_motion: DetectorMotion
56
+ zebra_fast_grid_scan: ZebraFastGridScan
57
+ flux: Flux
58
+ oav: OAV
59
+ pin_tip_detection: PinTipDetection
60
+ s4_slit_gaps: S4SlitGaps
61
+ undulator: Undulator
62
+ xbpm_feedback: XBPMFeedback
63
+ zebra: Zebra
64
+ robot: BartRobot
65
+ sample_shutter: ZebraShutter
File without changes
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import dataclasses
4
+ from collections import defaultdict
4
5
  from collections.abc import Callable, Sequence
5
6
  from functools import partial
6
7
 
@@ -10,6 +11,7 @@ from event_model import RunStart
10
11
 
11
12
  from mx_bluesky.common.parameters.components import (
12
13
  MultiXtalSelection,
14
+ TopNByMaxCountForEachSampleSelection,
13
15
  TopNByMaxCountSelection,
14
16
  )
15
17
 
@@ -39,18 +41,21 @@ class XRayCentreResult:
39
41
  containing the crystal
40
42
  max_count: The maximum spot count encountered in any one grid box in the crystal
41
43
  total_count: The total count across all boxes in the crystal.
44
+ sample_id: The sample id associated with the centre.
42
45
  """
43
46
 
44
47
  centre_of_mass_mm: np.ndarray
45
48
  bounding_box_mm: tuple[np.ndarray, np.ndarray]
46
49
  max_count: int
47
50
  total_count: int
51
+ sample_id: int | None
48
52
 
49
53
  def __eq__(self, o):
50
54
  return (
51
55
  isinstance(o, XRayCentreResult)
52
56
  and o.max_count == self.max_count
53
57
  and o.total_count == self.total_count
58
+ and o.sample_id == self.sample_id
54
59
  and all(o.centre_of_mass_mm == self.centre_of_mass_mm)
55
60
  and all(o.bounding_box_mm[0] == self.bounding_box_mm[0])
56
61
  and all(o.bounding_box_mm[1] == self.bounding_box_mm[1])
@@ -64,9 +69,27 @@ def top_n_by_max_count(
64
69
  return sorted_hits[:n]
65
70
 
66
71
 
72
+ def top_n_by_max_count_for_each_sample(
73
+ unfiltered: Sequence[XRayCentreResult], n: int
74
+ ) -> Sequence[XRayCentreResult]:
75
+ xrc_results_by_sample_id: dict[int | None, list[XRayCentreResult]] = defaultdict(
76
+ list[XRayCentreResult]
77
+ )
78
+ for result in unfiltered:
79
+ xrc_results_by_sample_id[result.sample_id].append(result)
80
+ return [
81
+ result
82
+ for results in xrc_results_by_sample_id.values()
83
+ for result in sorted(results, key=lambda x: x.max_count, reverse=True)[:n]
84
+ ]
85
+
86
+
67
87
  def resolve_selection_fn(
68
88
  params: MultiXtalSelection,
69
89
  ) -> Callable[[Sequence[XRayCentreResult]], Sequence[XRayCentreResult]]:
70
- if isinstance(params, TopNByMaxCountSelection):
71
- return partial(top_n_by_max_count, n=params.n)
90
+ match params:
91
+ case TopNByMaxCountSelection():
92
+ return partial(top_n_by_max_count, n=params.n)
93
+ case TopNByMaxCountForEachSampleSelection():
94
+ return partial(top_n_by_max_count_for_each_sample, n=params.n)
72
95
  raise ValueError(f"Invalid selection function {params.name}")
@@ -1,20 +1,8 @@
1
- from collections.abc import Generator
2
-
3
1
  from bluesky import plan_stubs as bps
4
- from bluesky import preprocessors as bpp
5
- from bluesky.utils import Msg
6
2
  from dodal.devices.detector import (
7
3
  DetectorParams,
8
4
  )
9
- from dodal.devices.detector.detector_motion import DetectorMotion, ShutterState
10
- from dodal.devices.eiger import EigerDetector
11
5
  from dodal.devices.i03.dcm import DCM
12
- from dodal.devices.mx_phase1.beamstop import Beamstop, BeamstopPositions
13
-
14
- from mx_bluesky.common.device_setup_plans.position_detector import (
15
- set_detector_z_position,
16
- set_shutter,
17
- )
18
6
 
19
7
 
20
8
  def fill_in_energy_if_not_supplied(dcm: DCM, detector_params: DetectorParams):
@@ -22,39 +10,3 @@ def fill_in_energy_if_not_supplied(dcm: DCM, detector_params: DetectorParams):
22
10
  actual_energy_ev = 1000 * (yield from bps.rd(dcm.energy_in_kev))
23
11
  detector_params.expected_energy_ev = actual_energy_ev
24
12
  return detector_params
25
-
26
-
27
- def start_preparing_data_collection_then_do_plan(
28
- beamstop: Beamstop,
29
- eiger: EigerDetector,
30
- detector_motion: DetectorMotion,
31
- detector_distance_mm: float | None,
32
- plan_to_run: Generator[Msg, None, None],
33
- group="ready_for_data_collection",
34
- ) -> Generator[Msg, None, None]:
35
- """Starts preparing for the next data collection and then runs the
36
- given plan.
37
-
38
- Preparation consists of:
39
- * Arming the Eiger
40
- * Moving the detector to the specified position
41
- * Opening the detect shutter
42
- If the plan fails it will disarm the eiger.
43
- """
44
-
45
- def wrapped_plan():
46
- yield from bps.abs_set(eiger.do_arm, 1, group=group) # type: ignore # Fix types in ophyd-async (https://github.com/DiamondLightSource/mx-bluesky/issues/855)
47
- yield from bps.abs_set(
48
- beamstop.selected_pos, BeamstopPositions.DATA_COLLECTION, group=group
49
- )
50
- if detector_distance_mm:
51
- yield from set_detector_z_position(
52
- detector_motion, detector_distance_mm, group
53
- )
54
- yield from set_shutter(detector_motion, ShutterState.OPEN, group)
55
- yield from plan_to_run
56
-
57
- yield from bpp.contingency_wrapper(
58
- wrapped_plan(),
59
- except_plan=lambda e: (yield from bps.stop(eiger)), # type: ignore # Fix types in ophyd-async (https://github.com/DiamondLightSource/mx-bluesky/issues/855)
60
- )
@@ -3,8 +3,8 @@
3
3
  The __all__ list in here are the plans that are externally available from outside Hyperion.
4
4
  """
5
5
 
6
- from mx_bluesky.hyperion.experiment_plans.grid_detect_then_xray_centre_plan import (
7
- grid_detect_then_xray_centre,
6
+ from mx_bluesky.hyperion.experiment_plans.hyperion_grid_detect_then_xray_centre_plan import (
7
+ hyperion_grid_detect_then_xray_centre,
8
8
  )
9
9
  from mx_bluesky.hyperion.experiment_plans.load_centre_collect_full_plan import (
10
10
  load_centre_collect_full,
@@ -17,7 +17,7 @@ from mx_bluesky.hyperion.experiment_plans.rotation_scan_plan import (
17
17
  )
18
18
 
19
19
  __all__ = [
20
- "grid_detect_then_xray_centre",
20
+ "hyperion_grid_detect_then_xray_centre",
21
21
  "pin_tip_centre_then_xray_centre",
22
22
  "rotation_scan",
23
23
  "load_centre_collect_full",
@@ -5,7 +5,7 @@ from typing import TypedDict
5
5
 
6
6
  import mx_bluesky.hyperion.experiment_plans.rotation_scan_plan as rotation_scan_plan
7
7
  from mx_bluesky.hyperion.experiment_plans import (
8
- grid_detect_then_xray_centre_plan,
8
+ hyperion_grid_detect_then_xray_centre_plan,
9
9
  load_centre_collect_full_plan,
10
10
  pin_centre_then_xray_centre_plan,
11
11
  )
@@ -38,8 +38,8 @@ class ExperimentRegistryEntry(TypedDict):
38
38
 
39
39
 
40
40
  PLAN_REGISTRY: dict[str, ExperimentRegistryEntry] = {
41
- "grid_detect_then_xray_centre": {
42
- "setup": grid_detect_then_xray_centre_plan.create_devices,
41
+ "hyperion_grid_detect_then_xray_centre": {
42
+ "setup": hyperion_grid_detect_then_xray_centre_plan.create_devices,
43
43
  "param_type": GridScanWithEdgeDetect,
44
44
  },
45
45
  "pin_tip_centre_then_xray_centre": {
@@ -9,7 +9,7 @@ from dodal.devices.fast_grid_scan import (
9
9
  set_fast_grid_scan_params,
10
10
  )
11
11
 
12
- from mx_bluesky.common.plans.common_flyscan_xray_centre_plan import (
12
+ from mx_bluesky.common.experiment_plans.common_flyscan_xray_centre_plan import (
13
13
  construct_beamline_specific_FGS_features,
14
14
  )
15
15
  from mx_bluesky.common.utils.log import LOGGER
@@ -34,51 +34,50 @@ class SmargonSpeedException(Exception):
34
34
 
35
35
 
36
36
  def construct_hyperion_specific_features(
37
- fgs_composite: HyperionFlyScanXRayCentreComposite,
38
- parameters: HyperionSpecifiedThreeDGridScan,
37
+ xrc_composite: HyperionFlyScanXRayCentreComposite,
38
+ xrc_parameters: HyperionSpecifiedThreeDGridScan,
39
39
  ):
40
40
  """
41
41
  Get all the information needed to do the Hyperion-specific parts of the XRC flyscan.
42
42
  """
43
-
44
43
  signals_to_read_pre_flyscan = [
45
- fgs_composite.undulator.current_gap,
46
- fgs_composite.synchrotron.synchrotron_mode,
47
- fgs_composite.s4_slit_gaps.xgap,
48
- fgs_composite.s4_slit_gaps.ygap,
49
- fgs_composite.smargon.x,
50
- fgs_composite.smargon.y,
51
- fgs_composite.smargon.z,
52
- fgs_composite.dcm.energy_in_kev,
44
+ xrc_composite.undulator.current_gap,
45
+ xrc_composite.synchrotron.synchrotron_mode,
46
+ xrc_composite.s4_slit_gaps.xgap,
47
+ xrc_composite.s4_slit_gaps.ygap,
48
+ xrc_composite.smargon.x,
49
+ xrc_composite.smargon.y,
50
+ xrc_composite.smargon.z,
51
+ xrc_composite.dcm.energy_in_kev,
53
52
  ]
54
53
 
55
54
  signals_to_read_during_collection = [
56
- fgs_composite.aperture_scatterguard,
57
- fgs_composite.attenuator.actual_transmission,
58
- fgs_composite.flux.flux_reading,
59
- fgs_composite.dcm.energy_in_kev,
60
- fgs_composite.eiger.bit_depth,
55
+ xrc_composite.aperture_scatterguard,
56
+ xrc_composite.attenuator.actual_transmission,
57
+ xrc_composite.flux.flux_reading,
58
+ xrc_composite.dcm.energy_in_kev,
59
+ xrc_composite.eiger.bit_depth,
61
60
  ]
62
61
 
63
- if parameters.features.use_panda_for_gridscan:
62
+ if xrc_parameters.features.use_panda_for_gridscan:
64
63
  setup_trigger_plan = _panda_triggering_setup
65
64
  tidy_plan = _panda_tidy
66
65
  set_flyscan_params_plan = partial(
67
66
  set_fast_grid_scan_params,
68
- fgs_composite.panda_fast_grid_scan,
69
- parameters.panda_FGS_params,
67
+ xrc_composite.panda_fast_grid_scan,
68
+ xrc_parameters.panda_FGS_params,
70
69
  )
71
- fgs_motors = fgs_composite.panda_fast_grid_scan
70
+ fgs_motors = xrc_composite.panda_fast_grid_scan
72
71
 
73
72
  else:
74
73
  setup_trigger_plan = _zebra_triggering_setup
75
74
  tidy_plan = partial(_generic_tidy, group="flyscan_zebra_tidy", wait=True)
76
75
  set_flyscan_params_plan = partial(
77
76
  set_fast_grid_scan_params,
78
- fgs_composite.zebra_fast_grid_scan,
79
- parameters.FGS_params,
77
+ xrc_composite.zebra_fast_grid_scan,
78
+ xrc_parameters.FGS_params,
80
79
  )
81
- fgs_motors = fgs_composite.zebra_fast_grid_scan
80
+ fgs_motors = xrc_composite.zebra_fast_grid_scan
82
81
  return construct_beamline_specific_FGS_features(
83
82
  setup_trigger_plan,
84
83
  tidy_plan,
@@ -91,47 +90,53 @@ def construct_hyperion_specific_features(
91
90
 
92
91
 
93
92
  def _generic_tidy(
94
- fgs_composite: HyperionFlyScanXRayCentreComposite, group, wait=True
93
+ xrc_composite: HyperionFlyScanXRayCentreComposite, group, wait=True
95
94
  ) -> MsgGenerator:
96
95
  LOGGER.info("Tidying up Zebra")
97
96
  yield from tidy_up_zebra_after_gridscan(
98
- fgs_composite.zebra, fgs_composite.sample_shutter, group=group, wait=wait
97
+ xrc_composite.zebra, xrc_composite.sample_shutter, group=group, wait=wait
99
98
  )
100
99
  LOGGER.info("Tidying up Zocalo")
101
100
  # make sure we don't consume any other results
102
- yield from bps.unstage(fgs_composite.zocalo, group=group, wait=wait)
101
+ yield from bps.unstage(xrc_composite.zocalo, group=group, wait=wait)
103
102
 
104
103
  # Turn off dev/shm streaming to avoid filling disk, see https://github.com/DiamondLightSource/hyperion/issues/1395
105
104
  LOGGER.info("Turning off Eiger dev/shm streaming")
106
- yield from bps.abs_set(fgs_composite.eiger.odin.fan.dev_shm_enable, 0) # type: ignore # Fix types in ophyd-async (https://github.com/DiamondLightSource/mx-bluesky/issues/855)
105
+ # Fix types in ophyd-async (https://github.com/DiamondLightSource/mx-bluesky/issues/855)
106
+ yield from bps.abs_set(
107
+ xrc_composite.eiger.odin.fan.dev_shm_enable, # type: ignore
108
+ 0,
109
+ group=group,
110
+ wait=wait,
111
+ )
107
112
 
108
113
 
109
- def _panda_tidy(fgs_composite: HyperionFlyScanXRayCentreComposite):
114
+ def _panda_tidy(xrc_composite: HyperionFlyScanXRayCentreComposite):
110
115
  group = "panda_flyscan_tidy"
111
116
  LOGGER.info("Disabling panda blocks")
112
- yield from disarm_panda_for_gridscan(fgs_composite.panda, group)
113
- yield from _generic_tidy(fgs_composite, group, False)
117
+ yield from disarm_panda_for_gridscan(xrc_composite.panda, group)
118
+ yield from _generic_tidy(xrc_composite, group, False)
114
119
  yield from bps.wait(group, timeout=10)
115
- yield from bps.unstage(fgs_composite.panda)
120
+ yield from bps.unstage(xrc_composite.panda)
116
121
 
117
122
 
118
123
  def _zebra_triggering_setup(
119
- fgs_composite: HyperionFlyScanXRayCentreComposite,
124
+ xrc_composite: HyperionFlyScanXRayCentreComposite,
120
125
  parameters: HyperionSpecifiedThreeDGridScan,
121
126
  ) -> MsgGenerator:
122
127
  yield from setup_zebra_for_gridscan(
123
- fgs_composite.zebra, fgs_composite.sample_shutter, wait=True
128
+ xrc_composite.zebra, xrc_composite.sample_shutter, wait=True
124
129
  )
125
130
 
126
131
 
127
132
  def _panda_triggering_setup(
128
- fgs_composite: HyperionFlyScanXRayCentreComposite,
133
+ xrc_composite: HyperionFlyScanXRayCentreComposite,
129
134
  parameters: HyperionSpecifiedThreeDGridScan,
130
135
  ) -> MsgGenerator:
131
136
  LOGGER.info("Setting up Panda for flyscan")
132
137
 
133
138
  run_up_distance_mm = yield from bps.rd(
134
- fgs_composite.panda_fast_grid_scan.run_up_distance_mm
139
+ xrc_composite.panda_fast_grid_scan.run_up_distance_mm
135
140
  )
136
141
 
137
142
  # Set the time between x steps pv
@@ -140,7 +145,7 @@ def _panda_triggering_setup(
140
145
  time_between_x_steps_ms = (DEADTIME_S + parameters.exposure_time_s) * 1e3
141
146
 
142
147
  smargon_speed_limit_mm_per_s = yield from bps.rd(
143
- fgs_composite.smargon.x.max_velocity
148
+ xrc_composite.smargon.x.max_velocity
144
149
  )
145
150
 
146
151
  sample_velocity_mm_per_s = (
@@ -161,7 +166,7 @@ def _panda_triggering_setup(
161
166
  )
162
167
 
163
168
  yield from bps.mv(
164
- fgs_composite.panda_fast_grid_scan.time_between_x_steps_ms, # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
169
+ xrc_composite.panda_fast_grid_scan.time_between_x_steps_ms, # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
165
170
  time_between_x_steps_ms, # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
166
171
  )
167
172
 
@@ -169,9 +174,9 @@ def _panda_triggering_setup(
169
174
  yield from set_panda_directory(directory_provider_root)
170
175
 
171
176
  yield from setup_panda_for_flyscan(
172
- fgs_composite.panda,
177
+ xrc_composite.panda,
173
178
  parameters.panda_FGS_params,
174
- fgs_composite.smargon,
179
+ xrc_composite.smargon,
175
180
  parameters.exposure_time_s,
176
181
  time_between_x_steps_ms,
177
182
  sample_velocity_mm_per_s,
@@ -179,5 +184,5 @@ def _panda_triggering_setup(
179
184
 
180
185
  LOGGER.info("Setting up Zebra for panda flyscan")
181
186
  yield from setup_zebra_for_panda_flyscan(
182
- fgs_composite.zebra, fgs_composite.sample_shutter, wait=True
187
+ xrc_composite.zebra, xrc_composite.sample_shutter, wait=True
183
188
  )
@@ -0,0 +1,60 @@
1
+ from __future__ import annotations
2
+
3
+ from blueapi.core import BlueskyContext
4
+ from bluesky.utils import MsgGenerator
5
+ from dodal.plans.preprocessors.verify_undulator_gap import (
6
+ verify_undulator_gap_before_run_decorator,
7
+ )
8
+
9
+ from mx_bluesky.common.experiment_plans.common_grid_detect_then_xray_centre_plan import (
10
+ grid_detect_then_xray_centre,
11
+ )
12
+ from mx_bluesky.common.parameters.constants import OavConstants, PlanNameConstants
13
+ from mx_bluesky.common.preprocessors.preprocessors import (
14
+ transmission_and_xbpm_feedback_for_collection_decorator,
15
+ )
16
+ from mx_bluesky.common.utils.context import device_composite_from_context
17
+ from mx_bluesky.hyperion.experiment_plans.hyperion_flyscan_xray_centre_plan import (
18
+ construct_hyperion_specific_features,
19
+ )
20
+ from mx_bluesky.hyperion.parameters.device_composites import (
21
+ HyperionGridDetectThenXRayCentreComposite,
22
+ )
23
+ from mx_bluesky.hyperion.parameters.gridscan import (
24
+ GridScanWithEdgeDetect,
25
+ HyperionSpecifiedThreeDGridScan,
26
+ )
27
+
28
+
29
+ def create_devices(
30
+ context: BlueskyContext,
31
+ ) -> HyperionGridDetectThenXRayCentreComposite:
32
+ return device_composite_from_context(
33
+ context, HyperionGridDetectThenXRayCentreComposite
34
+ )
35
+
36
+
37
+ def hyperion_grid_detect_then_xray_centre(
38
+ composite: HyperionGridDetectThenXRayCentreComposite,
39
+ parameters: GridScanWithEdgeDetect,
40
+ oav_config: str = OavConstants.OAV_CONFIG_JSON,
41
+ ) -> MsgGenerator:
42
+ """
43
+ A plan which combines the collection of snapshots from the OAV and the determination
44
+ of the grid dimensions to use for the following grid scan.
45
+ """
46
+
47
+ @verify_undulator_gap_before_run_decorator(composite)
48
+ @transmission_and_xbpm_feedback_for_collection_decorator(
49
+ composite, parameters.transmission_frac, PlanNameConstants.GRIDSCAN_OUTER
50
+ )
51
+ def plan_to_perform():
52
+ yield from grid_detect_then_xray_centre(
53
+ composite=composite,
54
+ parameters=parameters,
55
+ xrc_params_type=HyperionSpecifiedThreeDGridScan,
56
+ construct_beamline_specific=construct_hyperion_specific_features,
57
+ oav_config=oav_config,
58
+ )
59
+
60
+ yield from plan_to_perform()
@@ -76,17 +76,29 @@ def load_centre_collect_full(
76
76
  flyscan_event_handler,
77
77
  )
78
78
 
79
- locations_to_collect_um: list[np.ndarray] = []
79
+ locations_to_collect_um: list[np.ndarray]
80
+ samples_to_collect: list[int]
80
81
 
81
82
  if flyscan_event_handler.xray_centre_results:
82
83
  selection_func = flyscan_result.resolve_selection_fn(
83
84
  parameters.selection_params
84
85
  )
85
86
  hits = selection_func(flyscan_event_handler.xray_centre_results)
86
- locations_to_collect_um = [hit.centre_of_mass_mm * 1000 for hit in hits]
87
+ hits_to_collect = []
88
+ for hit in hits:
89
+ if hit.sample_id is None:
90
+ LOGGER.warning(
91
+ f"Diffracting centre {hit} not collected because no sample id was assigned."
92
+ )
93
+ else:
94
+ hits_to_collect.append(hit)
87
95
 
96
+ locations_to_collect_um = [
97
+ hit.centre_of_mass_mm * 1000 for hit in hits_to_collect
98
+ ]
99
+ samples_to_collect = [hit.sample_id for hit in hits_to_collect]
88
100
  LOGGER.info(
89
- f"Selected hits {hits} using {selection_func}, args={parameters.selection_params}"
101
+ f"Selected hits {hits_to_collect} using {selection_func}, args={parameters.selection_params}"
90
102
  )
91
103
  else:
92
104
  # If the xray centring hasn't found a result but has not thrown an error it
@@ -98,6 +110,7 @@ def load_centre_collect_full(
98
110
  locations_to_collect_um = [
99
111
  np.array([initial_x_mm, initial_y_mm, initial_z_mm]) * 1000
100
112
  ]
113
+ samples_to_collect = [parameters.sample_id]
101
114
 
102
115
  multi_rotation = parameters.multi_rotation_scan
103
116
  rotation_template = multi_rotation.rotation_scans.copy()
@@ -108,9 +121,11 @@ def load_centre_collect_full(
108
121
 
109
122
  generator = rotation_scan_generator(is_alternating)
110
123
  next(generator)
111
- for location in locations_to_collect_um:
124
+ for location, sample_id in zip(
125
+ locations_to_collect_um, samples_to_collect, strict=True
126
+ ):
112
127
  for rot in rotation_template:
113
- combination = generator.send((rot, location))
128
+ combination = generator.send((rot, location, sample_id))
114
129
  multi_rotation.rotation_scans.append(combination)
115
130
  multi_rotation = RotationScan.model_validate(multi_rotation)
116
131
 
@@ -125,8 +140,10 @@ def load_centre_collect_full(
125
140
 
126
141
  def rotation_scan_generator(
127
142
  is_alternating: bool,
128
- ) -> Generator[RotationScanPerSweep, tuple[RotationScanPerSweep, np.ndarray], None]:
129
- scan_template, location = yield # type: ignore
143
+ ) -> Generator[
144
+ RotationScanPerSweep, tuple[RotationScanPerSweep, np.ndarray, int], None
145
+ ]:
146
+ scan_template, location, sample_id = yield # type: ignore
130
147
  next_rotation_direction = scan_template.rotation_direction
131
148
  while True:
132
149
  scan = scan_template.model_copy()
@@ -135,6 +152,7 @@ def rotation_scan_generator(
135
152
  scan.y_start_um,
136
153
  scan.z_start_um,
137
154
  ) = location
155
+ scan.sample_id = sample_id
138
156
  if is_alternating:
139
157
  if next_rotation_direction != scan.rotation_direction:
140
158
  # If originally specified direction of the current scan is different
@@ -146,4 +164,4 @@ def rotation_scan_generator(
146
164
  scan.rotation_direction = next_rotation_direction
147
165
  next_rotation_direction = next_rotation_direction.opposite
148
166
 
149
- scan_template, location = yield scan
167
+ scan_template, location, sample_id = yield scan
@@ -9,9 +9,18 @@ from dodal.devices.eiger import EigerDetector
9
9
  from dodal.devices.oav.oav_parameters import OAVParameters
10
10
 
11
11
  from mx_bluesky.common.device_setup_plans.manipulate_sample import move_phi_chi_omega
12
+ from mx_bluesky.common.device_setup_plans.utils import (
13
+ start_preparing_data_collection_then_do_plan,
14
+ )
12
15
  from mx_bluesky.common.experiment_plans.change_aperture_then_move_plan import (
13
16
  change_aperture_then_move_to_xtal,
14
17
  )
18
+ from mx_bluesky.common.experiment_plans.common_grid_detect_then_xray_centre_plan import (
19
+ detect_grid_and_do_gridscan,
20
+ )
21
+ from mx_bluesky.common.experiment_plans.oav_snapshot_plan import (
22
+ setup_beamline_for_OAV,
23
+ )
15
24
  from mx_bluesky.common.external_interaction.callbacks.xray_centre.ispyb_callback import (
16
25
  ispyb_activation_wrapper,
17
26
  )
@@ -19,32 +28,33 @@ from mx_bluesky.common.parameters.constants import OavConstants
19
28
  from mx_bluesky.common.utils.context import device_composite_from_context
20
29
  from mx_bluesky.common.utils.log import LOGGER
21
30
  from mx_bluesky.common.xrc_result import XRayCentreEventHandler
22
- from mx_bluesky.hyperion.device_setup_plans.utils import (
23
- start_preparing_data_collection_then_do_plan,
24
- )
25
- from mx_bluesky.hyperion.experiment_plans.grid_detect_then_xray_centre_plan import (
26
- GridDetectThenXRayCentreComposite,
27
- detect_grid_and_do_gridscan,
28
- )
29
- from mx_bluesky.hyperion.experiment_plans.oav_snapshot_plan import (
30
- setup_beamline_for_OAV,
31
+ from mx_bluesky.hyperion.experiment_plans.hyperion_flyscan_xray_centre_plan import (
32
+ construct_hyperion_specific_features,
31
33
  )
32
34
  from mx_bluesky.hyperion.experiment_plans.pin_tip_centring_plan import (
33
35
  PinTipCentringComposite,
34
36
  pin_tip_centre_plan,
35
37
  )
36
38
  from mx_bluesky.hyperion.parameters.constants import CONST
39
+ from mx_bluesky.hyperion.parameters.device_composites import (
40
+ HyperionGridDetectThenXRayCentreComposite,
41
+ )
37
42
  from mx_bluesky.hyperion.parameters.gridscan import (
38
43
  GridScanWithEdgeDetect,
44
+ HyperionSpecifiedThreeDGridScan,
39
45
  PinTipCentreThenXrayCentre,
40
46
  )
41
47
 
42
48
 
43
- def create_devices(context: BlueskyContext) -> GridDetectThenXRayCentreComposite:
49
+ def create_devices(
50
+ context: BlueskyContext,
51
+ ) -> HyperionGridDetectThenXRayCentreComposite:
44
52
  """
45
- GridDetectThenXRayCentreComposite contains all the devices we need, reuse that.
53
+ HyperionGridDetectThenXRayCentreComposite contains all the devices we need, reuse that.
46
54
  """
47
- return device_composite_from_context(context, GridDetectThenXRayCentreComposite)
55
+ return device_composite_from_context(
56
+ context, HyperionGridDetectThenXRayCentreComposite
57
+ )
48
58
 
49
59
 
50
60
  def create_parameters_for_grid_detection(
@@ -60,7 +70,7 @@ def create_parameters_for_grid_detection(
60
70
 
61
71
 
62
72
  def pin_centre_then_flyscan_plan(
63
- composite: GridDetectThenXRayCentreComposite,
73
+ composite: HyperionGridDetectThenXRayCentreComposite,
64
74
  parameters: PinTipCentreThenXrayCentre,
65
75
  oav_config_file: str = OavConstants.OAV_CONFIG_JSON,
66
76
  ):
@@ -92,20 +102,21 @@ def pin_centre_then_flyscan_plan(
92
102
  )
93
103
 
94
104
  grid_detect_params = create_parameters_for_grid_detection(parameters)
95
-
96
105
  oav_params = OAVParameters("xrayCentring", oav_config_file)
97
106
 
98
107
  yield from detect_grid_and_do_gridscan(
99
108
  composite,
100
109
  grid_detect_params,
101
110
  oav_params,
111
+ HyperionSpecifiedThreeDGridScan,
112
+ construct_hyperion_specific_features,
102
113
  )
103
114
 
104
115
  yield from ispyb_activation_wrapper(_pin_centre_then_flyscan_plan(), parameters)
105
116
 
106
117
 
107
118
  def pin_tip_centre_then_xray_centre(
108
- composite: GridDetectThenXRayCentreComposite,
119
+ composite: HyperionGridDetectThenXRayCentreComposite,
109
120
  parameters: PinTipCentreThenXrayCentre,
110
121
  oav_config_file: str = OavConstants.OAV_CONFIG_JSON,
111
122
  ) -> MsgGenerator: