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
@@ -7,35 +7,32 @@ from functools import partial
7
7
  import bluesky.plan_stubs as bps
8
8
  import bluesky.preprocessors as bpp
9
9
  import numpy as np
10
- import pydantic
11
10
  from bluesky.protocols import Readable
12
11
  from bluesky.utils import MsgGenerator
13
- from dodal.devices.eiger import EigerDetector
14
12
  from dodal.devices.fast_grid_scan import (
15
13
  FastGridScanCommon,
16
14
  )
17
- from dodal.devices.smargon import Smargon
18
- from dodal.devices.synchrotron import Synchrotron
19
15
  from dodal.devices.zocalo import ZocaloResults
20
16
  from dodal.devices.zocalo.zocalo_results import (
21
17
  XrcResult,
22
18
  get_full_processing_results,
23
19
  )
24
20
 
21
+ from mx_bluesky.common.experiment_plans.inner_plans.do_fgs import (
22
+ ZOCALO_STAGE_GROUP,
23
+ kickoff_and_complete_gridscan,
24
+ )
25
+ from mx_bluesky.common.experiment_plans.read_hardware import (
26
+ read_hardware_plan,
27
+ )
25
28
  from mx_bluesky.common.parameters.constants import (
26
29
  DocDescriptorNames,
27
30
  GridscanParamConstants,
28
31
  PlanGroupCheckpointConstants,
29
32
  PlanNameConstants,
30
33
  )
34
+ from mx_bluesky.common.parameters.device_composites import FlyScanEssentialDevices
31
35
  from mx_bluesky.common.parameters.gridscan import SpecifiedThreeDGridScan
32
- from mx_bluesky.common.plans.inner_plans.do_fgs import (
33
- ZOCALO_STAGE_GROUP,
34
- kickoff_and_complete_gridscan,
35
- )
36
- from mx_bluesky.common.plans.read_hardware import (
37
- read_hardware_plan,
38
- )
39
36
  from mx_bluesky.common.utils.exceptions import (
40
37
  CrystalNotFoundException,
41
38
  SampleException,
@@ -45,14 +42,6 @@ from mx_bluesky.common.utils.tracing import TRACER
45
42
  from mx_bluesky.common.xrc_result import XRayCentreResult
46
43
 
47
44
 
48
- @pydantic.dataclasses.dataclass(config={"arbitrary_types_allowed": True})
49
- class FlyScanEssentialDevices:
50
- eiger: EigerDetector
51
- synchrotron: Synchrotron
52
- zocalo: ZocaloResults
53
- smargon: Smargon
54
-
55
-
56
45
  @dataclasses.dataclass
57
46
  class BeamlineSpecificFGSFeatures:
58
47
  setup_trigger_plan: Callable[..., MsgGenerator]
@@ -251,6 +240,9 @@ def run_gridscan(
251
240
  parameters.scan_indices,
252
241
  plan_during_collection=beamline_specific.read_during_collection_plan,
253
242
  )
243
+
244
+ # GDA's gridscans requires Z steps to be at 0, so make sure we leave this device
245
+ # in a GDA-happy state.
254
246
  yield from bps.abs_set(beamline_specific.fgs_motors.z_steps, 0, wait=False)
255
247
 
256
248
 
@@ -296,6 +288,7 @@ def _xrc_result_in_boxes_to_result_in_mm(
296
288
  ),
297
289
  max_count=xrc_result["max_count"],
298
290
  total_count=xrc_result["total_count"],
291
+ sample_id=xrc_result["sample_id"],
299
292
  )
300
293
 
301
294
 
@@ -1,8 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from pathlib import Path
4
+ from typing import Protocol, TypeVar
4
5
 
5
- from blueapi.core import BlueskyContext
6
6
  from bluesky import plan_stubs as bps
7
7
  from bluesky import preprocessors as bpp
8
8
  from bluesky.preprocessors import subs_decorator
@@ -10,20 +10,27 @@ from bluesky.utils import MsgGenerator
10
10
  from dodal.devices.backlight import BacklightPosition
11
11
  from dodal.devices.eiger import EigerDetector
12
12
  from dodal.devices.oav.oav_parameters import OAVParameters
13
- from dodal.plans.preprocessors.verify_undulator_gap import (
14
- verify_undulator_gap_before_run_decorator,
15
- )
16
13
 
17
14
  from mx_bluesky.common.device_setup_plans.manipulate_sample import (
18
15
  move_aperture_if_required,
19
16
  )
17
+ from mx_bluesky.common.device_setup_plans.utils import (
18
+ start_preparing_data_collection_then_do_plan,
19
+ )
20
20
  from mx_bluesky.common.experiment_plans.change_aperture_then_move_plan import (
21
21
  change_aperture_then_move_to_xtal,
22
22
  )
23
+ from mx_bluesky.common.experiment_plans.common_flyscan_xray_centre_plan import (
24
+ BeamlineSpecificFGSFeatures,
25
+ common_flyscan_xray_centre,
26
+ )
23
27
  from mx_bluesky.common.experiment_plans.oav_grid_detection_plan import (
24
28
  OavGridDetectionComposite,
25
29
  grid_detection_plan,
26
30
  )
31
+ from mx_bluesky.common.experiment_plans.oav_snapshot_plan import (
32
+ setup_beamline_for_OAV,
33
+ )
27
34
  from mx_bluesky.common.external_interaction.callbacks.common.grid_detection_callback import (
28
35
  GridDetectionCallback,
29
36
  GridParamUpdate,
@@ -31,62 +38,97 @@ from mx_bluesky.common.external_interaction.callbacks.common.grid_detection_call
31
38
  from mx_bluesky.common.external_interaction.callbacks.xray_centre.ispyb_callback import (
32
39
  ispyb_activation_wrapper,
33
40
  )
34
- from mx_bluesky.common.parameters.constants import OavConstants
35
- from mx_bluesky.common.parameters.gridscan import GridCommon
36
- from mx_bluesky.common.plans.common_flyscan_xray_centre_plan import (
37
- BeamlineSpecificFGSFeatures,
38
- common_flyscan_xray_centre,
41
+ from mx_bluesky.common.parameters.constants import (
42
+ OavConstants,
43
+ PlanGroupCheckpointConstants,
39
44
  )
40
- from mx_bluesky.common.preprocessors.preprocessors import (
41
- transmission_and_xbpm_feedback_for_collection_decorator,
45
+ from mx_bluesky.common.parameters.device_composites import (
46
+ FlyScanEssentialDevices,
47
+ GridDetectThenXRayCentreComposite,
42
48
  )
43
- from mx_bluesky.common.utils.context import device_composite_from_context
49
+ from mx_bluesky.common.parameters.gridscan import GridCommon, SpecifiedThreeDGridScan
44
50
  from mx_bluesky.common.utils.log import LOGGER
45
51
  from mx_bluesky.common.xrc_result import XRayCentreEventHandler
46
- from mx_bluesky.hyperion.device_setup_plans.utils import (
47
- start_preparing_data_collection_then_do_plan,
48
- )
49
- from mx_bluesky.hyperion.experiment_plans.hyperion_flyscan_xray_centre_plan import (
50
- construct_hyperion_specific_features,
51
- )
52
- from mx_bluesky.hyperion.experiment_plans.oav_snapshot_plan import (
53
- setup_beamline_for_OAV,
54
- )
55
- from mx_bluesky.hyperion.parameters.constants import CONST
56
- from mx_bluesky.hyperion.parameters.device_composites import (
57
- GridDetectThenXRayCentreComposite,
58
- HyperionFlyScanXRayCentreComposite,
52
+
53
+ TFlyScanEssentialDevices = TypeVar(
54
+ "TFlyScanEssentialDevices", bound=FlyScanEssentialDevices, contravariant=True
59
55
  )
60
- from mx_bluesky.hyperion.parameters.gridscan import (
61
- GridScanWithEdgeDetect,
62
- HyperionSpecifiedThreeDGridScan,
56
+ TSpecifiedThreeDGridScan = TypeVar(
57
+ "TSpecifiedThreeDGridScan", bound=SpecifiedThreeDGridScan, contravariant=True
63
58
  )
64
59
 
65
60
 
66
- def create_devices(context: BlueskyContext) -> GridDetectThenXRayCentreComposite:
67
- return device_composite_from_context(context, GridDetectThenXRayCentreComposite)
61
+ def grid_detect_then_xray_centre(
62
+ composite: GridDetectThenXRayCentreComposite,
63
+ parameters: GridCommon,
64
+ xrc_params_type: type[SpecifiedThreeDGridScan],
65
+ construct_beamline_specific: ConstructBeamlineSpecificFeatures,
66
+ oav_config: str = OavConstants.OAV_CONFIG_JSON,
67
+ ) -> MsgGenerator:
68
+ """
69
+ A plan which combines the collection of snapshots from the OAV and the determination
70
+ of the grid dimensions to use for the following grid scan.
71
+ """
68
72
 
73
+ eiger: EigerDetector = composite.eiger
69
74
 
70
- def create_parameters_for_flyscan_xray_centre(
71
- grid_scan_with_edge_params: GridCommon,
72
- grid_parameters: GridParamUpdate,
73
- ) -> HyperionSpecifiedThreeDGridScan:
74
- params_json = grid_scan_with_edge_params.model_dump()
75
- params_json.update(grid_parameters)
76
- flyscan_xray_centre_parameters = HyperionSpecifiedThreeDGridScan(**params_json)
77
- LOGGER.info(f"Parameters for FGS: {flyscan_xray_centre_parameters}")
78
- return flyscan_xray_centre_parameters
75
+ eiger.set_detector_parameters(parameters.detector_params)
76
+
77
+ oav_params = OAVParameters("xrayCentring", oav_config)
78
+
79
+ flyscan_event_handler = XRayCentreEventHandler()
80
+
81
+ @subs_decorator(flyscan_event_handler)
82
+ def plan_to_perform():
83
+ yield from ispyb_activation_wrapper(
84
+ detect_grid_and_do_gridscan(
85
+ composite,
86
+ parameters,
87
+ oav_params,
88
+ xrc_params_type,
89
+ construct_beamline_specific,
90
+ ),
91
+ parameters,
92
+ )
93
+
94
+ yield from start_preparing_data_collection_then_do_plan(
95
+ composite.beamstop,
96
+ eiger,
97
+ composite.detector_motion,
98
+ parameters.detector_params.detector_distance,
99
+ plan_to_perform(),
100
+ group=PlanGroupCheckpointConstants.GRID_READY_FOR_DC,
101
+ )
102
+
103
+ assert flyscan_event_handler.xray_centre_results, (
104
+ "Flyscan result event not received or no crystal found and exception not raised"
105
+ )
106
+
107
+ yield from change_aperture_then_move_to_xtal(
108
+ flyscan_event_handler.xray_centre_results[0],
109
+ composite.smargon,
110
+ composite.aperture_scatterguard,
111
+ )
79
112
 
80
113
 
81
114
  def detect_grid_and_do_gridscan(
82
115
  composite: GridDetectThenXRayCentreComposite,
83
116
  parameters: GridCommon,
84
117
  oav_params: OAVParameters,
118
+ xrc_params_type: type[SpecifiedThreeDGridScan],
119
+ construct_beamline_specific: ConstructBeamlineSpecificFeatures,
85
120
  ):
86
121
  snapshot_template = f"{parameters.detector_params.prefix}_{parameters.detector_params.run_number}_{{angle}}"
87
122
 
88
123
  grid_params_callback = GridDetectionCallback()
89
124
 
125
+ yield from setup_beamline_for_OAV(
126
+ composite.smargon,
127
+ composite.backlight,
128
+ composite.aperture_scatterguard,
129
+ wait=True,
130
+ )
131
+
90
132
  @bpp.subs_decorator([grid_params_callback])
91
133
  def run_grid_detection_plan(
92
134
  oav_params,
@@ -109,19 +151,12 @@ def detect_grid_and_do_gridscan(
109
151
  parameters.box_size_um,
110
152
  )
111
153
 
112
- yield from setup_beamline_for_OAV(
113
- composite.smargon,
114
- composite.backlight,
115
- composite.aperture_scatterguard,
116
- wait=True,
117
- )
118
-
119
154
  if parameters.selected_aperture:
120
155
  # Start moving the aperture/scatterguard into position without moving it in
121
156
  yield from bps.prepare(
122
157
  composite.aperture_scatterguard,
123
158
  parameters.selected_aperture,
124
- group=CONST.WAIT.PREPARE_APERTURE,
159
+ group=PlanGroupCheckpointConstants.PREPARE_APERTURE,
125
160
  )
126
161
 
127
162
  yield from run_grid_detection_plan(
@@ -131,105 +166,42 @@ def detect_grid_and_do_gridscan(
131
166
  )
132
167
 
133
168
  yield from bps.abs_set(
134
- composite.backlight, BacklightPosition.OUT, group=CONST.WAIT.GRID_READY_FOR_DC
169
+ composite.backlight,
170
+ BacklightPosition.OUT,
171
+ group=PlanGroupCheckpointConstants.GRID_READY_FOR_DC,
135
172
  )
136
173
 
137
- yield from bps.wait(CONST.WAIT.PREPARE_APERTURE)
174
+ yield from bps.wait(PlanGroupCheckpointConstants.PREPARE_APERTURE)
138
175
  yield from move_aperture_if_required(
139
176
  composite.aperture_scatterguard,
140
177
  parameters.selected_aperture,
141
- group=CONST.WAIT.GRID_READY_FOR_DC,
178
+ group=PlanGroupCheckpointConstants.GRID_READY_FOR_DC,
142
179
  )
143
-
144
- xrc_composite = HyperionFlyScanXRayCentreComposite(
145
- aperture_scatterguard=composite.aperture_scatterguard,
146
- attenuator=composite.attenuator,
147
- backlight=composite.backlight,
148
- eiger=composite.eiger,
149
- panda_fast_grid_scan=composite.panda_fast_grid_scan,
150
- flux=composite.flux,
151
- s4_slit_gaps=composite.s4_slit_gaps,
152
- smargon=composite.smargon,
153
- undulator=composite.undulator,
154
- synchrotron=composite.synchrotron,
155
- xbpm_feedback=composite.xbpm_feedback,
156
- zebra=composite.zebra,
157
- zocalo=composite.zocalo,
158
- panda=composite.panda,
159
- zebra_fast_grid_scan=composite.zebra_fast_grid_scan,
160
- dcm=composite.dcm,
161
- robot=composite.robot,
162
- sample_shutter=composite.sample_shutter,
180
+ xrc_params = create_parameters_for_flyscan_xray_centre(
181
+ parameters, grid_params_callback.get_grid_parameters(), xrc_params_type
163
182
  )
183
+ beamline_specific = construct_beamline_specific(composite, xrc_params)
164
184
 
165
- params = create_parameters_for_flyscan_xray_centre(
166
- parameters, grid_params_callback.get_grid_parameters()
167
- )
185
+ yield from common_flyscan_xray_centre(composite, xrc_params, beamline_specific)
168
186
 
169
- beamline_specific = construct_hyperion_specific_features(xrc_composite, params)
170
187
 
171
- yield from _gridscan_with_undulator_checks(xrc_composite, params, beamline_specific)
172
-
173
-
174
- def _gridscan_with_undulator_checks(
175
- composite: HyperionFlyScanXRayCentreComposite,
176
- params: HyperionSpecifiedThreeDGridScan,
177
- beamline_specific: BeamlineSpecificFGSFeatures,
188
+ class ConstructBeamlineSpecificFeatures(
189
+ Protocol[TFlyScanEssentialDevices, TSpecifiedThreeDGridScan]
178
190
  ):
179
- @transmission_and_xbpm_feedback_for_collection_decorator(
180
- composite, params.transmission_frac
181
- )
182
- @verify_undulator_gap_before_run_decorator(composite)
183
- def _inner():
184
- yield from common_flyscan_xray_centre(composite, params, beamline_specific)
185
-
186
- yield from _inner()
191
+ def __call__(
192
+ self,
193
+ xrc_composite: TFlyScanEssentialDevices,
194
+ xrc_parameters: TSpecifiedThreeDGridScan,
195
+ ) -> BeamlineSpecificFGSFeatures: ...
187
196
 
188
197
 
189
- def grid_detect_then_xray_centre(
190
- composite: GridDetectThenXRayCentreComposite,
191
- parameters: GridScanWithEdgeDetect,
192
- oav_config: str = OavConstants.OAV_CONFIG_JSON,
193
- ) -> MsgGenerator:
194
- """
195
- A plan which combines the collection of snapshots from the OAV and the determination
196
- of the grid dimensions to use for the following grid scan.
197
- """
198
-
199
- eiger: EigerDetector = composite.eiger
200
-
201
- eiger.set_detector_parameters(parameters.detector_params)
202
-
203
- oav_params = OAVParameters("xrayCentring", oav_config)
204
-
205
- flyscan_event_handler = XRayCentreEventHandler()
206
-
207
- @subs_decorator(flyscan_event_handler)
208
- def plan_to_perform():
209
- yield from ispyb_activation_wrapper(
210
- detect_grid_and_do_gridscan(
211
- composite,
212
- parameters,
213
- oav_params,
214
- ),
215
- parameters,
216
- )
217
-
218
- yield from start_preparing_data_collection_then_do_plan(
219
- composite.beamstop,
220
- eiger,
221
- composite.detector_motion,
222
- parameters.detector_params.detector_distance,
223
- plan_to_perform(),
224
- group=CONST.WAIT.GRID_READY_FOR_DC,
225
- )
226
-
227
- assert flyscan_event_handler.xray_centre_results, (
228
- "Flyscan result event not received or no crystal found and exception not raised"
229
- )
230
-
231
- yield from change_aperture_then_move_to_xtal(
232
- flyscan_event_handler.xray_centre_results[0],
233
- composite.smargon,
234
- composite.aperture_scatterguard,
235
- )
198
+ def create_parameters_for_flyscan_xray_centre(
199
+ parameters: GridCommon,
200
+ grid_parameters: GridParamUpdate,
201
+ xrc_params_type: type[SpecifiedThreeDGridScan],
202
+ ) -> SpecifiedThreeDGridScan:
203
+ params_json = parameters.model_dump()
204
+ params_json.update(grid_parameters)
205
+ flyscan_xray_centre_parameters = xrc_params_type(**params_json)
206
+ LOGGER.info(f"Parameters for FGS: {flyscan_xray_centre_parameters}")
207
+ return flyscan_xray_centre_parameters
@@ -14,10 +14,10 @@ from dodal.log import LOGGER
14
14
  from dodal.plan_stubs.check_topup import check_topup_and_wait_if_necessary
15
15
  from scanspec.core import AxesPoints, Axis
16
16
 
17
+ from mx_bluesky.common.experiment_plans.read_hardware import read_hardware_for_zocalo
17
18
  from mx_bluesky.common.parameters.constants import (
18
19
  PlanNameConstants,
19
20
  )
20
- from mx_bluesky.common.plans.read_hardware import read_hardware_for_zocalo
21
21
  from mx_bluesky.common.utils.tracing import TRACER
22
22
 
23
23
 
@@ -5,9 +5,7 @@ from typing import TYPE_CHECKING
5
5
 
6
6
  import bluesky.plan_stubs as bps
7
7
  import numpy as np
8
- import pydantic
9
8
  from blueapi.core import BlueskyContext
10
- from dodal.devices.backlight import Backlight
11
9
  from dodal.devices.oav.oav_detector import OAV
12
10
  from dodal.devices.oav.pin_image_recognition import PinTipDetection
13
11
  from dodal.devices.oav.pin_image_recognition.utils import NONE_VALUE
@@ -17,7 +15,11 @@ from dodal.devices.smargon import Smargon
17
15
  from mx_bluesky.common.device_setup_plans.setup_oav import (
18
16
  pre_centring_setup_oav,
19
17
  )
20
- from mx_bluesky.common.parameters.constants import DocDescriptorNames, HardwareConstants
18
+ from mx_bluesky.common.parameters.constants import (
19
+ DocDescriptorNames,
20
+ HardwareConstants,
21
+ )
22
+ from mx_bluesky.common.parameters.device_composites import OavGridDetectionComposite
21
23
  from mx_bluesky.common.utils.context import device_composite_from_context
22
24
  from mx_bluesky.common.utils.exceptions import catch_exception_and_warn
23
25
  from mx_bluesky.common.utils.log import LOGGER
@@ -26,16 +28,6 @@ if TYPE_CHECKING:
26
28
  from dodal.devices.oav.oav_parameters import OAVParameters
27
29
 
28
30
 
29
- @pydantic.dataclasses.dataclass(config={"arbitrary_types_allowed": True})
30
- class OavGridDetectionComposite:
31
- """All devices which are directly or indirectly required by this plan"""
32
-
33
- backlight: Backlight
34
- oav: OAV
35
- smargon: Smargon
36
- pin_tip_detection: PinTipDetection
37
-
38
-
39
31
  def create_devices(context: BlueskyContext) -> OavGridDetectionComposite:
40
32
  return device_composite_from_context(context, OavGridDetectionComposite)
41
33
 
@@ -11,7 +11,10 @@ from dodal.devices.smargon import Smargon
11
11
 
12
12
  from mx_bluesky.common.device_setup_plans.setup_oav import setup_general_oav_params
13
13
  from mx_bluesky.common.parameters.components import WithSnapshot
14
- from mx_bluesky.hyperion.parameters.constants import CONST, DocDescriptorNames
14
+ from mx_bluesky.common.parameters.constants import (
15
+ DocDescriptorNames,
16
+ PlanGroupCheckpointConstants,
17
+ )
15
18
 
16
19
  OAV_SNAPSHOT_SETUP_SHOT = "oav_snapshot_setup_shot"
17
20
  OAV_SNAPSHOT_GROUP = "oav_snapshot_group"
@@ -28,7 +31,7 @@ def setup_beamline_for_OAV(
28
31
  smargon: Smargon,
29
32
  backlight: Backlight,
30
33
  aperture_scatterguard: ApertureScatterguard,
31
- group=CONST.WAIT.READY_FOR_OAV,
34
+ group=PlanGroupCheckpointConstants.READY_FOR_OAV,
32
35
  wait=False,
33
36
  ):
34
37
  max_vel = yield from bps.rd(smargon.omega.max_velocity)
@@ -1,7 +1,9 @@
1
1
  import configparser
2
2
  from dataclasses import dataclass
3
3
  from enum import StrEnum
4
+ from typing import Any, Literal
4
5
 
6
+ from event_model.documents import Event
5
7
  from requests import JSONDecodeError, patch, post
6
8
  from requests.auth import AuthBase
7
9
 
@@ -63,6 +65,19 @@ assert all(len(value) <= 20 for value in BLSampleStatus), (
63
65
  )
64
66
 
65
67
 
68
+ def create_update_data_from_event_doc(
69
+ mapping: dict[str, str], event: Event
70
+ ) -> dict[str, Any]:
71
+ """Given a mapping between bluesky event data and an event itself this function will
72
+ create a dict that can be used to update exp-eye."""
73
+ event_data = event["data"]
74
+ return {
75
+ target_key: event_data[source_key]
76
+ for source_key, target_key in mapping.items()
77
+ if source_key in event_data
78
+ }
79
+
80
+
66
81
  class ExpeyeInteraction:
67
82
  """Exposes functionality from the Expeye core API"""
68
83
 
@@ -74,24 +89,22 @@ class ExpeyeInteraction:
74
89
  self._base_url = url
75
90
  self._auth = BearerAuth(token)
76
91
 
77
- def start_load(
92
+ def start_robot_action(
78
93
  self,
94
+ action_type: Literal["LOAD", "UNLOAD"],
79
95
  proposal_reference: str,
80
96
  visit_number: int,
81
97
  sample_id: int,
82
- dewar_location: int,
83
- container_location: int,
84
98
  ) -> RobotActionID:
85
- """Create a robot load entry in ispyb.
99
+ """Create a robot action entry in ispyb.
86
100
 
87
101
  Args:
102
+ action_type ("LOAD" | "UNLOAD"): The robot action being performed
88
103
  proposal_reference (str): The proposal of the experiment e.g. cm37235
89
104
  visit_number (int): The visit number for the proposal, usually this can be
90
105
  found added to the end of the proposal e.g. the data for
91
106
  visit number 2 of proposal cm37235 is in cm37235-2
92
107
  sample_id (int): The id of the sample in the database
93
- dewar_location (int): Which puck in the dewar the sample is in
94
- container_location (int): Which pin in that puck has the sample
95
108
 
96
109
  Returns:
97
110
  RobotActionID: The id of the robot load action that is created
@@ -102,39 +115,28 @@ class ExpeyeInteraction:
102
115
 
103
116
  data = {
104
117
  "startTimestamp": get_current_time_string(),
118
+ "actionType": action_type,
105
119
  "sampleId": sample_id,
106
- "actionType": "LOAD",
107
- "containerLocation": container_location,
108
- "dewarLocation": dewar_location,
109
120
  }
110
121
  response = _send_and_get_response(self._auth, url, data, post)
111
122
  return response["robotActionId"]
112
123
 
113
- def update_barcode_and_snapshots(
124
+ def update_robot_action(
114
125
  self,
115
126
  action_id: RobotActionID,
116
- barcode: str,
117
- snapshot_before_path: str,
118
- snapshot_after_path: str,
127
+ data: dict[str, Any],
119
128
  ):
120
- """Update the barcode and snapshots of an existing robot action.
129
+ """Update an existing robot action to contain additional info.
121
130
 
122
131
  Args:
123
132
  action_id (RobotActionID): The id of the action to update
124
- barcode (str): The barcode to give the action
125
- snapshot_before_path (str): Path to the snapshot before robot load
126
- snapshot_after_path (str): Path to the snapshot after robot load
133
+ data (dict): The data to update with, where the keys match those expected
134
+ by exp-eye.
127
135
  """
128
136
  url = self._base_url + self.UPDATE_ROBOT_ACTION.format(action_id=action_id)
129
-
130
- data = {
131
- "sampleBarcode": barcode,
132
- "xtalSnapshotBefore": snapshot_before_path,
133
- "xtalSnapshotAfter": snapshot_after_path,
134
- }
135
137
  _send_and_get_response(self._auth, url, data, patch)
136
138
 
137
- def end_load(self, action_id: RobotActionID, status: str, reason: str):
139
+ def end_robot_action(self, action_id: RobotActionID, status: str, reason: str):
138
140
  """Finish an existing robot action, providing final information about how it went
139
141
 
140
142
  Args:
@@ -41,7 +41,6 @@ class IspybIds(BaseModel):
41
41
  class StoreInIspyb:
42
42
  def __init__(self, ispyb_config: str) -> None:
43
43
  self.ISPYB_CONFIG_PATH: str = ispyb_config
44
- self._data_collection_group_id: int | None
45
44
 
46
45
  def begin_deposition(
47
46
  self,
@@ -20,13 +20,13 @@ from mx_bluesky.common.external_interaction.nexus.nexus_utils import (
20
20
  create_goniometer_axes,
21
21
  get_start_and_predicted_end_time,
22
22
  )
23
- from mx_bluesky.common.parameters.components import DiffractionExperimentWithSample
23
+ from mx_bluesky.common.parameters.components import DiffractionExperiment
24
24
 
25
25
 
26
26
  class NexusWriter:
27
27
  def __init__(
28
28
  self,
29
- parameters: DiffractionExperimentWithSample,
29
+ parameters: DiffractionExperiment,
30
30
  data_shape: tuple[int, int, int],
31
31
  scan_points: AxesPoints,
32
32
  *,
@@ -136,7 +136,7 @@ class WithSnapshot(BaseModel):
136
136
 
137
137
  @model_validator(mode="after")
138
138
  def _validate_omegas_with_grid_snapshots(self) -> Self:
139
- assert not self.use_grid_snapshots or self.snapshot_omegas_deg is None, (
139
+ assert not self.use_grid_snapshots or not self.snapshot_omegas_deg, (
140
140
  "snapshot_omegas may not be specified with use_grid_snapshots"
141
141
  )
142
142
  return self
@@ -238,9 +238,14 @@ class TopNByMaxCountSelection(MultiXtalSelection):
238
238
  n: int
239
239
 
240
240
 
241
+ class TopNByMaxCountForEachSampleSelection(MultiXtalSelection):
242
+ name: Literal["TopNByMaxCountForEachSample"] = "TopNByMaxCountForEachSample" # pyright: ignore [reportIncompatibleVariableOverride]
243
+ n: int
244
+
245
+
241
246
  class WithCentreSelection(BaseModel):
242
- select_centres: TopNByMaxCountSelection = Field(
243
- discriminator="name", default=TopNByMaxCountSelection(n=1)
247
+ select_centres: TopNByMaxCountSelection | TopNByMaxCountForEachSampleSelection = (
248
+ Field(discriminator="name", default=TopNByMaxCountSelection(n=1))
244
249
  )
245
250
 
246
251
  @property
@@ -15,8 +15,8 @@ TEST_MODE = BEAMLINE == "test"
15
15
 
16
16
  @dataclass(frozen=True)
17
17
  class DocDescriptorNames:
18
- # Robot load event descriptor
19
- ROBOT_LOAD = "robot_load"
18
+ # Robot load/unload event descriptor
19
+ ROBOT_UPDATE = "robot_update"
20
20
  # For callbacks to use
21
21
  OAV_ROTATION_SNAPSHOT_TRIGGERED = "rotation_snapshot_triggered"
22
22
  OAV_GRID_SNAPSHOT_TRIGGERED = "snapshot_to_ispyb"
@@ -41,8 +41,9 @@ class OavConstants:
41
41
  @dataclass(frozen=True)
42
42
  class PlanNameConstants:
43
43
  LOAD_CENTRE_COLLECT = "load_centre_collect"
44
- # Robot load subplan
44
+ # Robot subplans
45
45
  ROBOT_LOAD = "robot_load"
46
+ ROBOT_UNLOAD = "robot_unload"
46
47
  # Gridscan
47
48
  GRID_DETECT_AND_DO_GRIDSCAN = "grid_detect_and_do_gridscan"
48
49
  GRID_DETECT_INNER = "grid_detect"