mx-bluesky 1.4.0__py3-none-any.whl → 1.4.1a0__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 (63) hide show
  1. mx_bluesky/_version.py +2 -2
  2. mx_bluesky/beamlines/i04/thawing_plan.py +1 -1
  3. mx_bluesky/beamlines/i24/serial/dcid.py +19 -21
  4. mx_bluesky/beamlines/i24/serial/extruder/i24ssx_Extruder_Collect_py3v2.py +2 -2
  5. mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/DiamondChipI24-py3v1.edl +1 -4
  6. mx_bluesky/beamlines/i24/serial/fixed_target/ft_utils.py +0 -1
  7. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Collect_py3v1.py +14 -24
  8. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Manager_py3v1.py +18 -76
  9. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_StartUp_py3v1.py +0 -199
  10. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_moveonclick.py +4 -6
  11. mx_bluesky/beamlines/i24/serial/log.py +1 -1
  12. mx_bluesky/beamlines/i24/serial/parameters/constants.py +0 -1
  13. mx_bluesky/beamlines/i24/serial/run_fixed_target.sh +4 -3
  14. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_beamline.py +78 -80
  15. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_detector.py +1 -2
  16. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_zebra_plans.py +24 -26
  17. mx_bluesky/beamlines/i24/serial/write_nexus.py +11 -11
  18. mx_bluesky/common/external_interaction/config_server.py +46 -0
  19. mx_bluesky/common/parameters/components.py +52 -15
  20. mx_bluesky/common/parameters/constants.py +6 -1
  21. mx_bluesky/common/parameters/gridscan.py +94 -0
  22. mx_bluesky/{hyperion → common}/parameters/robot_load.py +2 -2
  23. mx_bluesky/common/plans/do_fgs.py +2 -2
  24. mx_bluesky/common/utils/log.py +2 -0
  25. mx_bluesky/hyperion/__main__.py +2 -1
  26. mx_bluesky/hyperion/device_setup_plans/setup_panda.py +4 -4
  27. mx_bluesky/hyperion/device_setup_plans/setup_zebra.py +1 -1
  28. mx_bluesky/hyperion/experiment_plans/__init__.py +4 -0
  29. mx_bluesky/hyperion/experiment_plans/change_aperture_then_move_plan.py +83 -0
  30. mx_bluesky/hyperion/experiment_plans/common/xrc_result.py +47 -0
  31. mx_bluesky/hyperion/experiment_plans/experiment_registry.py +9 -9
  32. mx_bluesky/hyperion/experiment_plans/flyscan_xray_centre_plan.py +131 -89
  33. mx_bluesky/hyperion/experiment_plans/grid_detect_then_xray_centre_plan.py +50 -18
  34. mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py +52 -10
  35. mx_bluesky/hyperion/experiment_plans/oav_grid_detection_plan.py +2 -2
  36. mx_bluesky/hyperion/experiment_plans/oav_snapshot_plan.py +3 -9
  37. mx_bluesky/hyperion/experiment_plans/optimise_attenuation_plan.py +2 -2
  38. mx_bluesky/hyperion/experiment_plans/pin_centre_then_xray_centre_plan.py +36 -17
  39. mx_bluesky/hyperion/experiment_plans/pin_tip_centring_plan.py +2 -2
  40. mx_bluesky/hyperion/experiment_plans/robot_load_and_change_energy.py +6 -10
  41. mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +46 -11
  42. mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +18 -3
  43. mx_bluesky/hyperion/experiment_plans/set_energy_plan.py +2 -3
  44. mx_bluesky/hyperion/external_interaction/callbacks/common/abstract_event.py +66 -0
  45. mx_bluesky/hyperion/external_interaction/callbacks/grid_detection_callback.py +15 -15
  46. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py +3 -1
  47. mx_bluesky/hyperion/external_interaction/callbacks/rotation/nexus_callback.py +5 -3
  48. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/ispyb_callback.py +5 -2
  49. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/nexus_callback.py +5 -4
  50. mx_bluesky/hyperion/external_interaction/config_server.py +8 -37
  51. mx_bluesky/hyperion/external_interaction/ispyb/exp_eye_store.py +1 -1
  52. mx_bluesky/hyperion/parameters/components.py +4 -9
  53. mx_bluesky/hyperion/parameters/constants.py +0 -1
  54. mx_bluesky/hyperion/parameters/gridscan.py +33 -76
  55. mx_bluesky/hyperion/parameters/load_centre_collect.py +14 -9
  56. mx_bluesky/hyperion/parameters/rotation.py +15 -6
  57. {mx_bluesky-1.4.0.dist-info → mx_bluesky-1.4.1a0.dist-info}/METADATA +35 -34
  58. {mx_bluesky-1.4.0.dist-info → mx_bluesky-1.4.1a0.dist-info}/RECORD +62 -58
  59. {mx_bluesky-1.4.0.dist-info → mx_bluesky-1.4.1a0.dist-info}/WHEEL +1 -1
  60. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Mapping_py3v1.py +0 -150
  61. {mx_bluesky-1.4.0.dist-info → mx_bluesky-1.4.1a0.dist-info}/LICENSE +0 -0
  62. {mx_bluesky-1.4.0.dist-info → mx_bluesky-1.4.1a0.dist-info}/entry_points.txt +0 -0
  63. {mx_bluesky-1.4.0.dist-info → mx_bluesky-1.4.1a0.dist-info}/top_level.txt +0 -0
@@ -2,15 +2,27 @@ from __future__ import annotations
2
2
 
3
3
  import json
4
4
 
5
- from blueapi.core import BlueskyContext, MsgGenerator
5
+ import bluesky.preprocessors as bpp
6
+ from blueapi.core import BlueskyContext
7
+ from bluesky.utils import MsgGenerator
6
8
  from dodal.devices.eiger import EigerDetector
7
9
  from dodal.devices.oav.oav_parameters import OAVParameters
8
10
 
9
11
  from mx_bluesky.common.parameters.constants import OavConstants
12
+ from mx_bluesky.common.parameters.gridscan import (
13
+ GridScanWithEdgeDetect,
14
+ PinTipCentreThenXrayCentre,
15
+ )
10
16
  from mx_bluesky.hyperion.device_setup_plans.manipulate_sample import move_phi_chi_omega
11
17
  from mx_bluesky.hyperion.device_setup_plans.utils import (
12
18
  start_preparing_data_collection_then_do_plan,
13
19
  )
20
+ from mx_bluesky.hyperion.experiment_plans.change_aperture_then_move_plan import (
21
+ change_aperture_then_move_to_xtal,
22
+ )
23
+ from mx_bluesky.hyperion.experiment_plans.flyscan_xray_centre_plan import (
24
+ XRayCentreEventHandler,
25
+ )
14
26
  from mx_bluesky.hyperion.experiment_plans.grid_detect_then_xray_centre_plan import (
15
27
  GridDetectThenXRayCentreComposite,
16
28
  detect_grid_and_do_gridscan,
@@ -27,10 +39,6 @@ from mx_bluesky.hyperion.external_interaction.callbacks.xray_centre.ispyb_callba
27
39
  )
28
40
  from mx_bluesky.hyperion.log import LOGGER
29
41
  from mx_bluesky.hyperion.parameters.constants import CONST
30
- from mx_bluesky.hyperion.parameters.gridscan import (
31
- GridScanWithEdgeDetect,
32
- PinTipCentreThenXrayCentre,
33
- )
34
42
  from mx_bluesky.hyperion.utils.context import device_composite_from_context
35
43
 
36
44
 
@@ -53,13 +61,12 @@ def create_parameters_for_grid_detection(
53
61
  return grid_detect_and_xray_centre
54
62
 
55
63
 
56
- def pin_centre_then_xray_centre_plan(
64
+ def pin_centre_then_flyscan_plan(
57
65
  composite: GridDetectThenXRayCentreComposite,
58
66
  parameters: PinTipCentreThenXrayCentre,
59
67
  oav_config_file: str = OavConstants.OAV_CONFIG_JSON,
60
68
  ):
61
- """Plan that perfoms a pin tip centre followed by an xray centre to completely
62
- centre the sample"""
69
+ """Plan that performs a pin tip centre followed by a flyscan to determine the centres of interest"""
63
70
 
64
71
  pin_tip_centring_composite = PinTipCentringComposite(
65
72
  oav=composite.oav,
@@ -68,7 +75,7 @@ def pin_centre_then_xray_centre_plan(
68
75
  pin_tip_detection=composite.pin_tip_detection,
69
76
  )
70
77
 
71
- def _pin_centre_then_xray_centre_plan():
78
+ def _pin_centre_then_flyscan_plan():
72
79
  yield from setup_beamline_for_OAV(
73
80
  composite.smargon, composite.backlight, composite.aperture_scatterguard
74
81
  )
@@ -96,7 +103,7 @@ def pin_centre_then_xray_centre_plan(
96
103
  oav_params,
97
104
  )
98
105
 
99
- yield from ispyb_activation_wrapper(_pin_centre_then_xray_centre_plan(), parameters)
106
+ yield from ispyb_activation_wrapper(_pin_centre_then_flyscan_plan(), parameters)
100
107
 
101
108
 
102
109
  def pin_tip_centre_then_xray_centre(
@@ -105,15 +112,27 @@ def pin_tip_centre_then_xray_centre(
105
112
  oav_config_file: str = OavConstants.OAV_CONFIG_JSON,
106
113
  ) -> MsgGenerator:
107
114
  """Starts preparing for collection then performs the pin tip centre and xray centre"""
108
-
109
115
  eiger: EigerDetector = composite.eiger
110
116
 
111
117
  eiger.set_detector_parameters(parameters.detector_params)
112
118
 
113
- return start_preparing_data_collection_then_do_plan(
114
- eiger,
115
- composite.detector_motion,
116
- parameters.detector_params.detector_distance,
117
- pin_centre_then_xray_centre_plan(composite, parameters, oav_config_file),
118
- group=CONST.WAIT.GRID_READY_FOR_DC,
119
+ flyscan_event_handler = XRayCentreEventHandler()
120
+
121
+ @bpp.subs_decorator(flyscan_event_handler)
122
+ def pin_centre_flyscan_then_fetch_results() -> MsgGenerator:
123
+ yield from start_preparing_data_collection_then_do_plan(
124
+ eiger,
125
+ composite.detector_motion,
126
+ parameters.detector_params.detector_distance,
127
+ pin_centre_then_flyscan_plan(composite, parameters, oav_config_file),
128
+ group=CONST.WAIT.GRID_READY_FOR_DC,
129
+ )
130
+
131
+ yield from pin_centre_flyscan_then_fetch_results()
132
+ flyscan_results = flyscan_event_handler.xray_centre_results
133
+ assert (
134
+ flyscan_results
135
+ ), "Flyscan result event not received or no crystal found and exception not raised"
136
+ yield from change_aperture_then_move_to_xtal(
137
+ flyscan_results[0], composite.smargon, composite.aperture_scatterguard
119
138
  )
@@ -1,7 +1,7 @@
1
- import dataclasses
2
1
  from collections.abc import Generator
3
2
 
4
3
  import bluesky.plan_stubs as bps
4
+ import pydantic
5
5
  from blueapi.core import BlueskyContext
6
6
  from bluesky.utils import Msg
7
7
  from dodal.devices.backlight import Backlight
@@ -27,7 +27,7 @@ from mx_bluesky.hyperion.utils.context import device_composite_from_context
27
27
  DEFAULT_STEP_SIZE = 0.5
28
28
 
29
29
 
30
- @dataclasses.dataclass
30
+ @pydantic.dataclasses.dataclass(config={"arbitrary_types_allowed": True})
31
31
  class PinTipCentringComposite:
32
32
  """All devices which are directly or indirectly required by this plan"""
33
33
 
@@ -1,6 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- import dataclasses
4
3
  from collections.abc import Generator
5
4
  from datetime import datetime
6
5
  from pathlib import Path
@@ -8,9 +7,10 @@ from typing import cast
8
7
 
9
8
  import bluesky.plan_stubs as bps
10
9
  import bluesky.preprocessors as bpp
10
+ import pydantic
11
11
  from blueapi.core import BlueskyContext
12
12
  from bluesky.utils import Msg
13
- from dodal.devices.aperturescatterguard import ApertureScatterguard, ApertureValue
13
+ from dodal.devices.aperturescatterguard import ApertureScatterguard
14
14
  from dodal.devices.attenuator import Attenuator
15
15
  from dodal.devices.dcm import DCM
16
16
  from dodal.devices.focusing_mirror import FocusingMirrorWithStripes, MirrorVoltages
@@ -22,18 +22,18 @@ from dodal.devices.thawer import Thawer
22
22
  from dodal.devices.undulator_dcm import UndulatorDCM
23
23
  from dodal.devices.webcam import Webcam
24
24
  from dodal.devices.xbpm_feedback import XBPMFeedback
25
- from dodal.plans.motor_util_plans import MoveTooLarge, home_and_reset_wrapper
25
+ from dodal.plan_stubs.motor_utils import MoveTooLarge, home_and_reset_wrapper
26
26
 
27
+ from mx_bluesky.common.parameters.robot_load import RobotLoadAndEnergyChange
27
28
  from mx_bluesky.hyperion.experiment_plans.set_energy_plan import (
28
29
  SetEnergyComposite,
29
30
  set_energy_plan,
30
31
  )
31
32
  from mx_bluesky.hyperion.log import LOGGER
32
33
  from mx_bluesky.hyperion.parameters.constants import CONST
33
- from mx_bluesky.hyperion.parameters.robot_load import RobotLoadAndEnergyChange
34
34
 
35
35
 
36
- @dataclasses.dataclass
36
+ @pydantic.dataclasses.dataclass(config={"arbitrary_types_allowed": True})
37
37
  class RobotLoadAndEnergyChangeComposite:
38
38
  # SetEnergyComposite fields
39
39
  vfm: FocusingMirrorWithStripes
@@ -94,11 +94,7 @@ def take_robot_snapshots(oav: OAV, webcam: Webcam, directory: Path):
94
94
  def prepare_for_robot_load(
95
95
  aperture_scatterguard: ApertureScatterguard, smargon: Smargon
96
96
  ):
97
- yield from bps.abs_set(
98
- aperture_scatterguard,
99
- ApertureValue.ROBOT_LOAD,
100
- group="prepare_robot_load",
101
- )
97
+ yield from bps.trigger(aperture_scatterguard.move_out, group="prepare_robot_load")
102
98
 
103
99
  yield from bps.mv(smargon.stub_offsets, StubPosition.RESET_TO_ROBOT_LOAD) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
104
100
 
@@ -1,9 +1,11 @@
1
1
  from __future__ import annotations
2
2
 
3
- import dataclasses
4
3
  from typing import cast
5
4
 
6
- from blueapi.core import BlueskyContext, MsgGenerator
5
+ import bluesky.preprocessors as bpp
6
+ import pydantic
7
+ from blueapi.core import BlueskyContext
8
+ from bluesky.utils import MsgGenerator
7
9
  from dodal.devices.aperturescatterguard import ApertureScatterguard
8
10
  from dodal.devices.attenuator import Attenuator
9
11
  from dodal.devices.backlight import Backlight
@@ -32,15 +34,22 @@ from dodal.log import LOGGER
32
34
  from ophyd_async.fastcs.panda import HDFPanda
33
35
 
34
36
  from mx_bluesky.common.parameters.constants import OavConstants
37
+ from mx_bluesky.common.parameters.gridscan import RobotLoadThenCentre
35
38
  from mx_bluesky.hyperion.device_setup_plans.utils import (
36
39
  fill_in_energy_if_not_supplied,
37
40
  start_preparing_data_collection_then_do_plan,
38
41
  )
42
+ from mx_bluesky.hyperion.experiment_plans.change_aperture_then_move_plan import (
43
+ change_aperture_then_move_to_xtal,
44
+ )
45
+ from mx_bluesky.hyperion.experiment_plans.flyscan_xray_centre_plan import (
46
+ XRayCentreEventHandler,
47
+ )
39
48
  from mx_bluesky.hyperion.experiment_plans.grid_detect_then_xray_centre_plan import (
40
49
  GridDetectThenXRayCentreComposite,
41
50
  )
42
51
  from mx_bluesky.hyperion.experiment_plans.pin_centre_then_xray_centre_plan import (
43
- pin_centre_then_xray_centre_plan,
52
+ pin_centre_then_flyscan_plan,
44
53
  )
45
54
  from mx_bluesky.hyperion.experiment_plans.robot_load_and_change_energy import (
46
55
  RobotLoadAndEnergyChangeComposite,
@@ -48,10 +57,9 @@ from mx_bluesky.hyperion.experiment_plans.robot_load_and_change_energy import (
48
57
  robot_load_and_change_energy_plan,
49
58
  )
50
59
  from mx_bluesky.hyperion.parameters.constants import CONST
51
- from mx_bluesky.hyperion.parameters.gridscan import RobotLoadThenCentre
52
60
 
53
61
 
54
- @dataclasses.dataclass
62
+ @pydantic.dataclasses.dataclass(config={"arbitrary_types_allowed": True})
55
63
  class RobotLoadThenCentreComposite:
56
64
  # common fields
57
65
  xbpm_feedback: XBPMFeedback
@@ -88,6 +96,10 @@ class RobotLoadThenCentreComposite:
88
96
  webcam: Webcam
89
97
  lower_gonio: XYZPositioner
90
98
 
99
+ @property
100
+ def sample_motors(self):
101
+ return self.smargon
102
+
91
103
 
92
104
  def create_devices(context: BlueskyContext) -> RobotLoadThenCentreComposite:
93
105
  from mx_bluesky.hyperion.utils.context import device_composite_from_context
@@ -95,18 +107,18 @@ def create_devices(context: BlueskyContext) -> RobotLoadThenCentreComposite:
95
107
  return device_composite_from_context(context, RobotLoadThenCentreComposite)
96
108
 
97
109
 
98
- def centring_plan_from_robot_load_params(
110
+ def _flyscan_plan_from_robot_load_params(
99
111
  composite: RobotLoadThenCentreComposite,
100
112
  params: RobotLoadThenCentre,
101
113
  oav_config_file: str = OavConstants.OAV_CONFIG_JSON,
102
114
  ):
103
- yield from pin_centre_then_xray_centre_plan(
115
+ yield from pin_centre_then_flyscan_plan(
104
116
  cast(GridDetectThenXRayCentreComposite, composite),
105
117
  params.pin_centre_then_xray_centre_params(),
106
118
  )
107
119
 
108
120
 
109
- def robot_load_then_centre_plan(
121
+ def _robot_load_then_flyscan_plan(
110
122
  composite: RobotLoadThenCentreComposite,
111
123
  params: RobotLoadThenCentre,
112
124
  oav_config_file: str = OavConstants.OAV_CONFIG_JSON,
@@ -116,13 +128,36 @@ def robot_load_then_centre_plan(
116
128
  params.robot_load_params(),
117
129
  )
118
130
 
119
- yield from centring_plan_from_robot_load_params(composite, params, oav_config_file)
131
+ yield from _flyscan_plan_from_robot_load_params(composite, params, oav_config_file)
120
132
 
121
133
 
122
134
  def robot_load_then_centre(
123
135
  composite: RobotLoadThenCentreComposite,
124
136
  parameters: RobotLoadThenCentre,
125
137
  ) -> MsgGenerator:
138
+ """Perform pin-tip detection followed by a flyscan to determine centres of interest.
139
+ Performs a robot load if necessary. Centre on the best diffracting centre.
140
+ """
141
+
142
+ xray_centre_event_handler = XRayCentreEventHandler()
143
+
144
+ yield from bpp.subs_wrapper(
145
+ robot_load_then_xray_centre(composite, parameters), xray_centre_event_handler
146
+ )
147
+ flyscan_results = xray_centre_event_handler.xray_centre_results
148
+ if flyscan_results is not None:
149
+ yield from change_aperture_then_move_to_xtal(
150
+ flyscan_results[0], composite.smargon, composite.aperture_scatterguard
151
+ )
152
+ # else no chi change, no need to recentre.
153
+
154
+
155
+ def robot_load_then_xray_centre(
156
+ composite: RobotLoadThenCentreComposite,
157
+ parameters: RobotLoadThenCentre,
158
+ ) -> MsgGenerator:
159
+ """Perform pin-tip detection followed by a flyscan to determine centres of interest.
160
+ Performs a robot load if necessary."""
126
161
  eiger: EigerDetector = composite.eiger
127
162
 
128
163
  # TODO: get these from one source of truth #254
@@ -138,13 +173,13 @@ def robot_load_then_centre(
138
173
  doing_chi_change = parameters.chi_start_deg is not None
139
174
 
140
175
  if doing_sample_load:
141
- plan = robot_load_then_centre_plan(
176
+ plan = _robot_load_then_flyscan_plan(
142
177
  composite,
143
178
  parameters,
144
179
  )
145
180
  LOGGER.info("Pin not loaded, loading and centring")
146
181
  elif doing_chi_change:
147
- plan = centring_plan_from_robot_load_params(composite, parameters)
182
+ plan = _flyscan_plan_from_robot_load_params(composite, parameters)
148
183
  LOGGER.info("Pin already loaded but chi changed so centring")
149
184
  else:
150
185
  LOGGER.info("Pin already loaded and chi not changed so doing nothing")
@@ -4,7 +4,9 @@ import dataclasses
4
4
 
5
5
  import bluesky.plan_stubs as bps
6
6
  import bluesky.preprocessors as bpp
7
- from blueapi.core import BlueskyContext, MsgGenerator
7
+ import pydantic
8
+ from blueapi.core import BlueskyContext
9
+ from bluesky.utils import MsgGenerator
8
10
  from dodal.devices.aperturescatterguard import ApertureScatterguard
9
11
  from dodal.devices.attenuator import Attenuator
10
12
  from dodal.devices.backlight import Backlight
@@ -22,7 +24,7 @@ from dodal.devices.undulator import Undulator
22
24
  from dodal.devices.xbpm_feedback import XBPMFeedback
23
25
  from dodal.devices.zebra import RotationDirection, Zebra
24
26
  from dodal.devices.zebra_controlled_shutter import ZebraShutter
25
- from dodal.plans.check_topup import check_topup_and_wait_if_necessary
27
+ from dodal.plan_stubs.check_topup import check_topup_and_wait_if_necessary
26
28
 
27
29
  from mx_bluesky.common.device_setup_plans.read_hardware_for_setup import (
28
30
  read_hardware_for_zocalo,
@@ -62,7 +64,7 @@ from mx_bluesky.hyperion.parameters.rotation import (
62
64
  from mx_bluesky.hyperion.utils.context import device_composite_from_context
63
65
 
64
66
 
65
- @dataclasses.dataclass
67
+ @pydantic.dataclasses.dataclass(config={"arbitrary_types_allowed": True})
66
68
  class RotationScanComposite(OavSnapshotComposite):
67
69
  """All devices which are directly or indirectly required by this plan"""
68
70
 
@@ -329,6 +331,13 @@ def _move_and_rotation(
329
331
  yield from setup_beamline_for_OAV(
330
332
  composite.smargon, composite.backlight, composite.aperture_scatterguard
331
333
  )
334
+ yield from bps.wait(group=CONST.WAIT.READY_FOR_OAV)
335
+ if params.selected_aperture:
336
+ yield from bps.abs_set(
337
+ composite.aperture_scatterguard.aperture_outside_beam,
338
+ params.selected_aperture,
339
+ group=CONST.WAIT.ROTATION_READY_FOR_DC,
340
+ )
332
341
  yield from oav_snapshot_plan(composite, params, oav_params)
333
342
  yield from rotation_scan_plan(
334
343
  composite,
@@ -409,6 +418,11 @@ def multi_rotation_scan(
409
418
  }
410
419
  )
411
420
  @bpp.stage_decorator([eiger])
421
+ @transmission_and_xbpm_feedback_for_collection_decorator(
422
+ composite.xbpm_feedback,
423
+ composite.attenuator,
424
+ parameters.transmission_frac,
425
+ )
412
426
  @bpp.finalize_decorator(lambda: _cleanup_plan(composite))
413
427
  def _multi_rotation_scan():
414
428
  for single_scan in parameters.single_rotation_scans:
@@ -418,6 +432,7 @@ def multi_rotation_scan(
418
432
  md={
419
433
  "subplan_name": CONST.PLAN.ROTATION_OUTER,
420
434
  CONST.TRIGGER.ZOCALO: CONST.PLAN.ROTATION_MAIN,
435
+ "zocalo_environment": CONST.ZOCALO_ENV,
421
436
  "hyperion_parameters": single_scan.model_dump_json(),
422
437
  }
423
438
  )
@@ -5,8 +5,7 @@
5
5
  * reenable feedback
6
6
  """
7
7
 
8
- import dataclasses
9
-
8
+ import pydantic
10
9
  from bluesky import plan_stubs as bps
11
10
  from dodal.devices.attenuator import Attenuator
12
11
  from dodal.devices.dcm import DCM
@@ -24,7 +23,7 @@ DESIRED_TRANSMISSION_FRACTION = 0.1
24
23
  UNDULATOR_GROUP = "UNDULATOR_GROUP"
25
24
 
26
25
 
27
- @dataclasses.dataclass
26
+ @pydantic.dataclasses.dataclass(config={"arbitrary_types_allowed": True})
28
27
  class SetEnergyComposite:
29
28
  vfm: FocusingMirrorWithStripes
30
29
  mirror_voltages: MirrorVoltages
@@ -0,0 +1,66 @@
1
+ import builtins
2
+ import dataclasses
3
+ import time
4
+ from abc import ABC
5
+ from typing import Literal
6
+
7
+ from bluesky.protocols import Readable, Reading
8
+ from event_model import DataKey
9
+
10
+
11
+ @dataclasses.dataclass(frozen=True)
12
+ class AbstractEvent(Readable, ABC):
13
+ """An abstract superclass that can be extended to provide lightweight software events
14
+ for bluesky plans, without having to incur the overhead of creating ophyd-async devices
15
+ specifically for the purpose.
16
+
17
+ The currently supported types for field annotations in the event are ``str``, ``int``, ``float``, ``bool``
18
+
19
+ In future array types may be supported.
20
+
21
+ Examples:
22
+ Subclasses should extend this class and decorate with::
23
+
24
+ @dataclasses.dataclass(frozen=True)
25
+
26
+ To raise an event, simply construct the event and then ``read`` it as you would a device::
27
+
28
+ yield from bps.create("MY_EVENT_NAME")
29
+ my_event = MyEvent(an_int=1)
30
+ yield from bps.read(my_event)
31
+ yield from bps.save()
32
+ """
33
+
34
+ def read(self) -> dict[str, Reading]:
35
+ return {
36
+ f.name: AbstractEvent._reading_from_value(getattr(self, f.name))
37
+ for f in dataclasses.fields(self)
38
+ }
39
+
40
+ def describe(self) -> dict[str, DataKey]:
41
+ return {
42
+ f.name: DataKey(dtype=AbstractEvent._dtype_of(f.type), shape=[], source="")
43
+ for f in dataclasses.fields(self)
44
+ }
45
+
46
+ @classmethod
47
+ def _reading_from_value(cls, value):
48
+ return Reading(timestamp=time.time(), value=value)
49
+
50
+ @classmethod
51
+ def _dtype_of(cls, t) -> Literal["string", "number", "boolean", "integer"]:
52
+ match t:
53
+ case builtins.str:
54
+ return "string"
55
+ case builtins.bool:
56
+ return "boolean"
57
+ case builtins.int:
58
+ return "integer"
59
+ case builtins.float:
60
+ return "number"
61
+ # TODO array support
62
+ raise ValueError(f"Unsupported type for AbstractEvent: {t}")
63
+
64
+ @property
65
+ def name(self) -> str:
66
+ return type(self).__name__
@@ -28,7 +28,7 @@ class GridDetectionCallback(CallbackBase):
28
28
  *args,
29
29
  ) -> None:
30
30
  super().__init__(*args)
31
- self.start_positions: list = []
31
+ self.start_positions_mm: list = []
32
32
  self.box_numbers: list = []
33
33
 
34
34
  def event(self, doc: Event):
@@ -55,16 +55,16 @@ class GridDetectionCallback(CallbackBase):
55
55
  beam_x = data["oav-beam_centre_i"]
56
56
  beam_y = data["oav-beam_centre_j"]
57
57
 
58
- position_grid_start = calculate_x_y_z_of_pixel(
58
+ position_grid_start_mm = calculate_x_y_z_of_pixel(
59
59
  current_xyz,
60
60
  smargon_omega,
61
61
  centre_of_first_box,
62
62
  (beam_x, beam_y),
63
63
  (microns_per_pixel_x, microns_per_pixel_y),
64
64
  )
65
- LOGGER.info(f"Calculated start position {position_grid_start}")
65
+ LOGGER.info(f"Calculated start position {position_grid_start_mm}")
66
66
 
67
- self.start_positions.append(position_grid_start)
67
+ self.start_positions_mm.append(position_grid_start_mm)
68
68
  self.box_numbers.append(
69
69
  (
70
70
  data["oav-grid_snapshot-num_boxes_x"],
@@ -72,22 +72,22 @@ class GridDetectionCallback(CallbackBase):
72
72
  )
73
73
  )
74
74
 
75
- self.x_step_size_mm = box_width_px * microns_per_pixel_x / 1000
76
- self.y_step_size_mm = box_width_px * microns_per_pixel_y / 1000
77
- self.z_step_size_mm = box_width_px * microns_per_pixel_y / 1000
75
+ self.x_step_size_um = box_width_px * microns_per_pixel_x
76
+ self.y_step_size_um = box_width_px * microns_per_pixel_y
77
+ self.z_step_size_um = box_width_px * microns_per_pixel_y
78
78
  return doc
79
79
 
80
80
  def get_grid_parameters(self) -> GridParamUpdate:
81
81
  return {
82
- "x_start_um": self.start_positions[0][0],
83
- "y_start_um": self.start_positions[0][1],
84
- "y2_start_um": self.start_positions[0][1],
85
- "z_start_um": self.start_positions[1][2],
86
- "z2_start_um": self.start_positions[1][2],
82
+ "x_start_um": self.start_positions_mm[0][0] * 1000,
83
+ "y_start_um": self.start_positions_mm[0][1] * 1000,
84
+ "y2_start_um": self.start_positions_mm[0][1] * 1000,
85
+ "z_start_um": self.start_positions_mm[1][2] * 1000,
86
+ "z2_start_um": self.start_positions_mm[1][2] * 1000,
87
87
  "x_steps": self.box_numbers[0][0],
88
88
  "y_steps": self.box_numbers[0][1],
89
89
  "z_steps": self.box_numbers[1][1],
90
- "x_step_size_um": self.x_step_size_mm,
91
- "y_step_size_um": self.y_step_size_mm,
92
- "z_step_size_um": self.z_step_size_mm,
90
+ "x_step_size_um": self.x_step_size_um,
91
+ "y_step_size_um": self.y_step_size_um,
92
+ "z_step_size_um": self.z_step_size_um,
93
93
  }
@@ -61,7 +61,9 @@ class RotationISPyBCallback(BaseISPyBCallback):
61
61
  ISPYB_LOGGER.info(
62
62
  "ISPyB callback received start document with experiment parameters."
63
63
  )
64
- self.params = RotationScan.from_json(doc.get("hyperion_parameters"))
64
+ hyperion_params = doc.get("hyperion_parameters")
65
+ assert isinstance(hyperion_params, str)
66
+ self.params = RotationScan.model_validate_json(hyperion_params)
65
67
  dcgid = (
66
68
  self.ispyb_ids.data_collection_group_id
67
69
  if (self.params.sample_id == self.last_sample_id)
@@ -78,12 +78,14 @@ class RotationNexusFileCallback(PlanReactiveCallback):
78
78
  self.meta_data_run_number = doc.get("meta_data_run_number")
79
79
  if doc.get("subplan_name") == CONST.PLAN.ROTATION_OUTER:
80
80
  self.run_uid = doc.get("uid")
81
- json_params = doc.get("hyperion_parameters")
81
+ hyperion_params = doc.get("hyperion_parameters")
82
+ assert isinstance(hyperion_params, str)
82
83
  NEXUS_LOGGER.info(
83
- f"Nexus writer received start document with experiment parameters {json_params}"
84
+ f"Nexus writer received start document with experiment parameters {hyperion_params}"
84
85
  )
85
- parameters = RotationScan.from_json(json_params)
86
+ parameters = RotationScan.model_validate_json(hyperion_params)
86
87
  NEXUS_LOGGER.info("Setting up nexus file...")
88
+
87
89
  det_size = (
88
90
  parameters.detector_params.detector_size_constants.det_size_pixels
89
91
  )
@@ -5,8 +5,8 @@ from time import time
5
5
  from typing import TYPE_CHECKING, Any
6
6
 
7
7
  import numpy as np
8
- from blueapi.core import MsgGenerator
9
8
  from bluesky import preprocessors as bpp
9
+ from bluesky.utils import MsgGenerator
10
10
  from dodal.devices.zocalo.zocalo_results import (
11
11
  ZOCALO_READING_PLAN_NAME,
12
12
  get_processing_results_from_event,
@@ -98,7 +98,9 @@ class GridscanISPyBCallback(BaseISPyBCallback):
98
98
  "ISPyB callback received start document with experiment parameters and "
99
99
  f"uid: {self.uid_to_finalize_on}"
100
100
  )
101
- self.params = GridCommon.from_json(doc.get("hyperion_parameters"))
101
+ hyperion_params = doc.get("hyperion_parameters")
102
+ assert isinstance(hyperion_params, str)
103
+ self.params = GridCommon.model_validate_json(hyperion_params)
102
104
  self.ispyb = StoreInIspyb(self.ispyb_config)
103
105
  data_collection_group_info = populate_data_collection_group(self.params)
104
106
 
@@ -152,6 +154,7 @@ class GridscanISPyBCallback(BaseISPyBCallback):
152
154
  ISPYB_LOGGER.info(
153
155
  f"Amending comment based on Zocalo reading doc: {format_doc_for_log(doc)}"
154
156
  )
157
+
155
158
  raw_results = get_processing_results_from_event("zocalo", doc)
156
159
  if len(raw_results) > 0:
157
160
  for n, res in enumerate(raw_results):
@@ -12,7 +12,7 @@ from mx_bluesky.hyperion.external_interaction.nexus.nexus_utils import (
12
12
  from mx_bluesky.hyperion.external_interaction.nexus.write_nexus import NexusWriter
13
13
  from mx_bluesky.hyperion.log import NEXUS_LOGGER
14
14
  from mx_bluesky.hyperion.parameters.constants import CONST
15
- from mx_bluesky.hyperion.parameters.gridscan import ThreeDGridScan
15
+ from mx_bluesky.hyperion.parameters.gridscan import HyperionThreeDGridScan
16
16
 
17
17
  if TYPE_CHECKING:
18
18
  from event_model.documents import Event, EventDescriptor, RunStart
@@ -45,11 +45,12 @@ class GridscanNexusFileCallback(PlanReactiveCallback):
45
45
 
46
46
  def activity_gated_start(self, doc: RunStart):
47
47
  if doc.get("subplan_name") == CONST.PLAN.GRIDSCAN_OUTER:
48
- json_params = doc.get("hyperion_parameters")
48
+ hyperion_params = doc.get("hyperion_parameters")
49
+ assert isinstance(hyperion_params, str)
49
50
  NEXUS_LOGGER.info(
50
- f"Nexus writer received start document with experiment parameters {json_params}"
51
+ f"Nexus writer received start document with experiment parameters {hyperion_params}"
51
52
  )
52
- parameters = ThreeDGridScan.from_json(json_params)
53
+ parameters = HyperionThreeDGridScan.model_validate_json(hyperion_params)
53
54
  d_size = parameters.detector_params.detector_size_constants.det_size_pixels
54
55
  grid_n_img_1 = parameters.scan_indices[1]
55
56
  grid_n_img_2 = parameters.num_images - grid_n_img_1
@@ -1,47 +1,18 @@
1
+ from functools import cache
2
+
1
3
  from daq_config_server.client import ConfigServer
2
- from pydantic import BaseModel, Field, model_validator
3
4
 
5
+ from mx_bluesky.common.external_interaction.config_server import FeatureFlags
4
6
  from mx_bluesky.hyperion.log import LOGGER
5
7
  from mx_bluesky.hyperion.parameters.constants import CONST
6
8
 
7
- _CONFIG_SERVER: ConfigServer | None = None
8
-
9
9
 
10
- def config_server() -> ConfigServer:
11
- global _CONFIG_SERVER
12
- if _CONFIG_SERVER is None:
13
- _CONFIG_SERVER = ConfigServer(CONST.CONFIG_SERVER_URL, LOGGER)
14
- return _CONFIG_SERVER
10
+ class HyperionFeatureFlags(FeatureFlags):
11
+ @staticmethod
12
+ @cache
13
+ def get_config_server() -> ConfigServer:
14
+ return ConfigServer(CONST.CONFIG_SERVER_URL, LOGGER)
15
15
 
16
-
17
- class FeatureFlags(BaseModel):
18
- # The default value will be used as the fallback when doing a best-effort fetch
19
- # from the service
20
16
  use_panda_for_gridscan: bool = CONST.I03.USE_PANDA_FOR_GRIDSCAN
21
17
  compare_cpu_and_gpu_zocalo: bool = CONST.I03.COMPARE_CPU_AND_GPU_ZOCALO
22
18
  set_stub_offsets: bool = CONST.I03.SET_STUB_OFFSETS
23
-
24
- # Feature values supplied at construction will override values from the config server
25
- overriden_features: dict = Field(default_factory=dict, exclude=True)
26
-
27
- @model_validator(mode="before")
28
- @classmethod
29
- def mark_overridden_features(cls, values):
30
- assert isinstance(values, dict)
31
- values["overriden_features"] = values.copy()
32
- return values
33
-
34
- @classmethod
35
- def _get_flags(cls):
36
- flags = config_server().best_effort_get_all_feature_flags()
37
- return {f: flags[f] for f in flags if f in cls.model_fields.keys()}
38
-
39
- def update_self_from_server(self):
40
- """Used to update the feature flags from the server during a plan. Where there are flags which were explicitly set from externally supplied parameters, these values will be used instead."""
41
- for flag, value in self._get_flags().items():
42
- updated_value = (
43
- value
44
- if flag not in self.overriden_features.keys()
45
- else self.overriden_features[flag]
46
- )
47
- setattr(self, flag, updated_value)