mx-bluesky 1.2.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 (94) hide show
  1. mx_bluesky/__init__.py +8 -3
  2. mx_bluesky/__main__.py +12 -7
  3. mx_bluesky/_version.py +2 -2
  4. mx_bluesky/beamlines/i04/callbacks/murko_callback.py +14 -4
  5. mx_bluesky/beamlines/i04/thawing_plan.py +49 -11
  6. mx_bluesky/beamlines/i24/serial/__init__.py +3 -0
  7. mx_bluesky/beamlines/i24/serial/dcid.py +19 -21
  8. mx_bluesky/beamlines/i24/serial/extruder/i24ssx_Extruder_Collect_py3v2.py +69 -91
  9. mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/DiamondChipI24-py3v1.edl +2 -5
  10. mx_bluesky/beamlines/i24/serial/fixed_target/ft_utils.py +0 -1
  11. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Collect_py3v1.py +111 -143
  12. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Manager_py3v1.py +141 -222
  13. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_StartUp_py3v1.py +7 -216
  14. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_moveonclick.py +18 -17
  15. mx_bluesky/beamlines/i24/serial/log.py +58 -49
  16. mx_bluesky/beamlines/i24/serial/parameters/constants.py +0 -1
  17. mx_bluesky/beamlines/i24/serial/parameters/fixed_target/cs/cs_maker.json +3 -3
  18. mx_bluesky/beamlines/i24/serial/run_extruder.sh +30 -5
  19. mx_bluesky/beamlines/i24/serial/run_fixed_target.sh +30 -5
  20. mx_bluesky/beamlines/i24/serial/run_serial.py +24 -8
  21. mx_bluesky/beamlines/i24/serial/setup_beamline/ca.py +0 -2
  22. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_beamline.py +79 -81
  23. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_detector.py +9 -20
  24. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_zebra_plans.py +26 -28
  25. mx_bluesky/beamlines/i24/serial/write_nexus.py +11 -11
  26. mx_bluesky/common/__init__.py +0 -0
  27. mx_bluesky/common/device_setup_plans/read_hardware_for_setup.py +14 -0
  28. mx_bluesky/common/external_interaction/config_server.py +46 -0
  29. mx_bluesky/common/parameters/components.py +258 -0
  30. mx_bluesky/common/parameters/constants.py +138 -0
  31. mx_bluesky/common/parameters/gridscan.py +94 -0
  32. mx_bluesky/common/parameters/robot_load.py +16 -0
  33. mx_bluesky/common/plans/__init__.py +1 -0
  34. mx_bluesky/common/plans/do_fgs.py +121 -0
  35. mx_bluesky/common/utils/log.py +118 -0
  36. mx_bluesky/{hyperion → common/utils}/tracing.py +2 -2
  37. mx_bluesky/hyperion/__main__.py +13 -10
  38. mx_bluesky/hyperion/device_setup_plans/dcm_pitch_roll_mirror_adjuster.py +31 -26
  39. mx_bluesky/hyperion/device_setup_plans/read_hardware_for_setup.py +6 -12
  40. mx_bluesky/hyperion/device_setup_plans/setup_oav.py +6 -12
  41. mx_bluesky/hyperion/device_setup_plans/setup_panda.py +5 -6
  42. mx_bluesky/hyperion/device_setup_plans/setup_zebra.py +49 -18
  43. mx_bluesky/hyperion/device_setup_plans/smargon.py +6 -6
  44. mx_bluesky/hyperion/device_setup_plans/utils.py +2 -2
  45. mx_bluesky/hyperion/device_setup_plans/xbpm_feedback.py +4 -4
  46. mx_bluesky/hyperion/experiment_plans/__init__.py +4 -0
  47. mx_bluesky/hyperion/experiment_plans/change_aperture_then_move_plan.py +83 -0
  48. mx_bluesky/hyperion/experiment_plans/common/xrc_result.py +47 -0
  49. mx_bluesky/hyperion/experiment_plans/experiment_registry.py +9 -9
  50. mx_bluesky/hyperion/experiment_plans/flyscan_xray_centre_plan.py +145 -161
  51. mx_bluesky/hyperion/experiment_plans/grid_detect_then_xray_centre_plan.py +56 -22
  52. mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py +52 -10
  53. mx_bluesky/hyperion/experiment_plans/oav_grid_detection_plan.py +21 -20
  54. mx_bluesky/hyperion/experiment_plans/oav_snapshot_plan.py +11 -14
  55. mx_bluesky/hyperion/experiment_plans/optimise_attenuation_plan.py +2 -2
  56. mx_bluesky/hyperion/experiment_plans/pin_centre_then_xray_centre_plan.py +40 -21
  57. mx_bluesky/hyperion/experiment_plans/pin_tip_centring_plan.py +19 -19
  58. mx_bluesky/hyperion/experiment_plans/robot_load_and_change_energy.py +21 -21
  59. mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +51 -13
  60. mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +24 -7
  61. mx_bluesky/hyperion/experiment_plans/set_energy_plan.py +5 -6
  62. mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +1 -2
  63. mx_bluesky/hyperion/external_interaction/callbacks/common/abstract_event.py +66 -0
  64. mx_bluesky/hyperion/external_interaction/callbacks/common/ispyb_mapping.py +1 -1
  65. mx_bluesky/hyperion/external_interaction/callbacks/grid_detection_callback.py +30 -25
  66. mx_bluesky/hyperion/external_interaction/callbacks/ispyb_callback_base.py +29 -12
  67. mx_bluesky/hyperion/external_interaction/callbacks/log_uid_tag_callback.py +1 -1
  68. mx_bluesky/hyperion/external_interaction/callbacks/robot_load/ispyb_callback.py +1 -1
  69. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py +7 -4
  70. mx_bluesky/hyperion/external_interaction/callbacks/rotation/nexus_callback.py +5 -3
  71. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/ispyb_callback.py +28 -20
  72. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/nexus_callback.py +5 -4
  73. mx_bluesky/hyperion/external_interaction/config_server.py +11 -28
  74. mx_bluesky/hyperion/external_interaction/ispyb/exp_eye_store.py +1 -1
  75. mx_bluesky/hyperion/external_interaction/ispyb/ispyb_store.py +1 -1
  76. mx_bluesky/hyperion/external_interaction/nexus/nexus_utils.py +2 -2
  77. mx_bluesky/hyperion/external_interaction/nexus/write_nexus.py +1 -1
  78. mx_bluesky/hyperion/log.py +0 -84
  79. mx_bluesky/hyperion/parameters/components.py +4 -251
  80. mx_bluesky/hyperion/parameters/constants.py +22 -119
  81. mx_bluesky/hyperion/parameters/gridscan.py +35 -74
  82. mx_bluesky/hyperion/parameters/load_centre_collect.py +16 -11
  83. mx_bluesky/hyperion/parameters/rotation.py +23 -10
  84. mx_bluesky/hyperion/utils/utils.py +17 -0
  85. mx_bluesky/hyperion/utils/validation.py +5 -6
  86. {mx_bluesky-1.2.0.dist-info → mx_bluesky-1.4.1a0.dist-info}/METADATA +36 -33
  87. {mx_bluesky-1.2.0.dist-info → mx_bluesky-1.4.1a0.dist-info}/RECORD +91 -81
  88. {mx_bluesky-1.2.0.dist-info → mx_bluesky-1.4.1a0.dist-info}/WHEEL +1 -1
  89. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Mapping_py3v1.py +0 -161
  90. mx_bluesky/example.py +0 -19
  91. mx_bluesky/hyperion/parameters/robot_load.py +0 -16
  92. {mx_bluesky-1.2.0.dist-info → mx_bluesky-1.4.1a0.dist-info}/LICENSE +0 -0
  93. {mx_bluesky-1.2.0.dist-info → mx_bluesky-1.4.1a0.dist-info}/entry_points.txt +0 -0
  94. {mx_bluesky-1.2.0.dist-info → mx_bluesky-1.4.1a0.dist-info}/top_level.txt +0 -0
@@ -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
@@ -12,7 +14,7 @@ from dodal.devices.detector.detector_motion import DetectorMotion
12
14
  from dodal.devices.eiger import EigerDetector
13
15
  from dodal.devices.fast_grid_scan import PandAFastGridScan, ZebraFastGridScan
14
16
  from dodal.devices.flux import Flux
15
- from dodal.devices.focusing_mirror import FocusingMirrorWithStripes, VFMMirrorVoltages
17
+ from dodal.devices.focusing_mirror import FocusingMirrorWithStripes, MirrorVoltages
16
18
  from dodal.devices.motors import XYZPositioner
17
19
  from dodal.devices.oav.oav_detector import OAV
18
20
  from dodal.devices.oav.pin_image_recognition import PinTipDetection
@@ -31,15 +33,23 @@ from dodal.devices.zocalo import ZocaloResults
31
33
  from dodal.log import LOGGER
32
34
  from ophyd_async.fastcs.panda import HDFPanda
33
35
 
36
+ from mx_bluesky.common.parameters.constants import OavConstants
37
+ from mx_bluesky.common.parameters.gridscan import RobotLoadThenCentre
34
38
  from mx_bluesky.hyperion.device_setup_plans.utils import (
35
39
  fill_in_energy_if_not_supplied,
36
40
  start_preparing_data_collection_then_do_plan,
37
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
+ )
38
48
  from mx_bluesky.hyperion.experiment_plans.grid_detect_then_xray_centre_plan import (
39
49
  GridDetectThenXRayCentreComposite,
40
50
  )
41
51
  from mx_bluesky.hyperion.experiment_plans.pin_centre_then_xray_centre_plan import (
42
- pin_centre_then_xray_centre_plan,
52
+ pin_centre_then_flyscan_plan,
43
53
  )
44
54
  from mx_bluesky.hyperion.experiment_plans.robot_load_and_change_energy import (
45
55
  RobotLoadAndEnergyChangeComposite,
@@ -47,10 +57,9 @@ from mx_bluesky.hyperion.experiment_plans.robot_load_and_change_energy import (
47
57
  robot_load_and_change_energy_plan,
48
58
  )
49
59
  from mx_bluesky.hyperion.parameters.constants import CONST
50
- from mx_bluesky.hyperion.parameters.gridscan import RobotLoadThenCentre
51
60
 
52
61
 
53
- @dataclasses.dataclass
62
+ @pydantic.dataclasses.dataclass(config={"arbitrary_types_allowed": True})
54
63
  class RobotLoadThenCentreComposite:
55
64
  # common fields
56
65
  xbpm_feedback: XBPMFeedback
@@ -78,7 +87,7 @@ class RobotLoadThenCentreComposite:
78
87
 
79
88
  # SetEnergyComposite fields
80
89
  vfm: FocusingMirrorWithStripes
81
- vfm_mirror_voltages: VFMMirrorVoltages
90
+ mirror_voltages: MirrorVoltages
82
91
  dcm: DCM
83
92
  undulator_dcm: UndulatorDCM
84
93
 
@@ -87,6 +96,10 @@ class RobotLoadThenCentreComposite:
87
96
  webcam: Webcam
88
97
  lower_gonio: XYZPositioner
89
98
 
99
+ @property
100
+ def sample_motors(self):
101
+ return self.smargon
102
+
90
103
 
91
104
  def create_devices(context: BlueskyContext) -> RobotLoadThenCentreComposite:
92
105
  from mx_bluesky.hyperion.utils.context import device_composite_from_context
@@ -94,32 +107,57 @@ def create_devices(context: BlueskyContext) -> RobotLoadThenCentreComposite:
94
107
  return device_composite_from_context(context, RobotLoadThenCentreComposite)
95
108
 
96
109
 
97
- def centring_plan_from_robot_load_params(
110
+ def _flyscan_plan_from_robot_load_params(
98
111
  composite: RobotLoadThenCentreComposite,
99
112
  params: RobotLoadThenCentre,
113
+ oav_config_file: str = OavConstants.OAV_CONFIG_JSON,
100
114
  ):
101
- yield from pin_centre_then_xray_centre_plan(
115
+ yield from pin_centre_then_flyscan_plan(
102
116
  cast(GridDetectThenXRayCentreComposite, composite),
103
117
  params.pin_centre_then_xray_centre_params(),
104
118
  )
105
119
 
106
120
 
107
- def robot_load_then_centre_plan(
121
+ def _robot_load_then_flyscan_plan(
108
122
  composite: RobotLoadThenCentreComposite,
109
123
  params: RobotLoadThenCentre,
124
+ oav_config_file: str = OavConstants.OAV_CONFIG_JSON,
110
125
  ):
111
126
  yield from robot_load_and_change_energy_plan(
112
127
  cast(RobotLoadAndEnergyChangeComposite, composite),
113
128
  params.robot_load_params(),
114
129
  )
115
130
 
116
- yield from centring_plan_from_robot_load_params(composite, params)
131
+ yield from _flyscan_plan_from_robot_load_params(composite, params, oav_config_file)
117
132
 
118
133
 
119
134
  def robot_load_then_centre(
120
135
  composite: RobotLoadThenCentreComposite,
121
136
  parameters: RobotLoadThenCentre,
122
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."""
123
161
  eiger: EigerDetector = composite.eiger
124
162
 
125
163
  # TODO: get these from one source of truth #254
@@ -135,13 +173,13 @@ def robot_load_then_centre(
135
173
  doing_chi_change = parameters.chi_start_deg is not None
136
174
 
137
175
  if doing_sample_load:
138
- plan = robot_load_then_centre_plan(
176
+ plan = _robot_load_then_flyscan_plan(
139
177
  composite,
140
178
  parameters,
141
179
  )
142
180
  LOGGER.info("Pin not loaded, loading and centring")
143
181
  elif doing_chi_change:
144
- plan = centring_plan_from_robot_load_params(composite, parameters)
182
+ plan = _flyscan_plan_from_robot_load_params(composite, parameters)
145
183
  LOGGER.info("Pin already loaded but chi changed so centring")
146
184
  else:
147
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,8 +24,11 @@ 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
 
29
+ from mx_bluesky.common.device_setup_plans.read_hardware_for_setup import (
30
+ read_hardware_for_zocalo,
31
+ )
27
32
  from mx_bluesky.hyperion.device_setup_plans.manipulate_sample import (
28
33
  cleanup_sample_environment,
29
34
  move_phi_chi_omega,
@@ -32,7 +37,6 @@ from mx_bluesky.hyperion.device_setup_plans.manipulate_sample import (
32
37
  )
33
38
  from mx_bluesky.hyperion.device_setup_plans.read_hardware_for_setup import (
34
39
  read_hardware_during_collection,
35
- read_hardware_for_zocalo,
36
40
  read_hardware_pre_collection,
37
41
  )
38
42
  from mx_bluesky.hyperion.device_setup_plans.setup_zebra import (
@@ -60,7 +64,7 @@ from mx_bluesky.hyperion.parameters.rotation import (
60
64
  from mx_bluesky.hyperion.utils.context import device_composite_from_context
61
65
 
62
66
 
63
- @dataclasses.dataclass
67
+ @pydantic.dataclasses.dataclass(config={"arbitrary_types_allowed": True})
64
68
  class RotationScanComposite(OavSnapshotComposite):
65
69
  """All devices which are directly or indirectly required by this plan"""
66
70
 
@@ -252,7 +256,7 @@ def rotation_scan_plan(
252
256
  composite.undulator,
253
257
  composite.synchrotron,
254
258
  composite.s4_slit_gaps,
255
- composite.robot,
259
+ composite.dcm,
256
260
  composite.smargon,
257
261
  )
258
262
 
@@ -327,6 +331,13 @@ def _move_and_rotation(
327
331
  yield from setup_beamline_for_OAV(
328
332
  composite.smargon, composite.backlight, composite.aperture_scatterguard
329
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
+ )
330
341
  yield from oav_snapshot_plan(composite, params, oav_params)
331
342
  yield from rotation_scan_plan(
332
343
  composite,
@@ -348,7 +359,7 @@ def rotation_scan(
348
359
  md={
349
360
  "subplan_name": CONST.PLAN.ROTATION_OUTER,
350
361
  CONST.TRIGGER.ZOCALO: CONST.PLAN.ROTATION_MAIN,
351
- "zocalo_environment": parameters.zocalo_environment,
362
+ "zocalo_environment": CONST.ZOCALO_ENV,
352
363
  "hyperion_parameters": parameters.model_dump_json(),
353
364
  "activate_callbacks": [
354
365
  "RotationISPyBCallback",
@@ -379,7 +390,7 @@ def rotation_scan(
379
390
  rotation_with_cleanup_and_stage(params),
380
391
  group=CONST.WAIT.ROTATION_READY_FOR_DC,
381
392
  )
382
- yield from bps.unstage(eiger)
393
+ yield from bps.unstage(eiger) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
383
394
 
384
395
  yield from rotation_scan_plan_with_stage_and_cleanup(parameters)
385
396
 
@@ -407,6 +418,11 @@ def multi_rotation_scan(
407
418
  }
408
419
  )
409
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
+ )
410
426
  @bpp.finalize_decorator(lambda: _cleanup_plan(composite))
411
427
  def _multi_rotation_scan():
412
428
  for single_scan in parameters.single_rotation_scans:
@@ -416,6 +432,7 @@ def multi_rotation_scan(
416
432
  md={
417
433
  "subplan_name": CONST.PLAN.ROTATION_OUTER,
418
434
  CONST.TRIGGER.ZOCALO: CONST.PLAN.ROTATION_MAIN,
435
+ "zocalo_environment": CONST.ZOCALO_ENV,
419
436
  "hyperion_parameters": single_scan.model_dump_json(),
420
437
  }
421
438
  )
@@ -5,12 +5,11 @@
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
13
- from dodal.devices.focusing_mirror import FocusingMirrorWithStripes, VFMMirrorVoltages
12
+ from dodal.devices.focusing_mirror import FocusingMirrorWithStripes, MirrorVoltages
14
13
  from dodal.devices.undulator_dcm import UndulatorDCM
15
14
  from dodal.devices.xbpm_feedback import XBPMFeedback
16
15
 
@@ -24,10 +23,10 @@ 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
- vfm_mirror_voltages: VFMMirrorVoltages
29
+ mirror_voltages: MirrorVoltages
31
30
  dcm: DCM
32
31
  undulator_dcm: UndulatorDCM
33
32
  xbpm_feedback: XBPMFeedback
@@ -42,7 +41,7 @@ def _set_energy_plan(
42
41
  yield from dcm_pitch_roll_mirror_adjuster.adjust_dcm_pitch_roll_vfm_from_lut(
43
42
  composite.undulator_dcm,
44
43
  composite.vfm,
45
- composite.vfm_mirror_voltages,
44
+ composite.mirror_voltages,
46
45
  energy_kev,
47
46
  )
48
47
  yield from bps.wait(group=UNDULATOR_GROUP)
@@ -7,6 +7,7 @@ from bluesky.callbacks.zmq import Proxy, RemoteDispatcher
7
7
  from dodal.log import LOGGER as dodal_logger
8
8
  from dodal.log import set_up_all_logging_handlers
9
9
 
10
+ from mx_bluesky.common.utils.log import _get_logging_dir, tag_filter
10
11
  from mx_bluesky.hyperion.external_interaction.callbacks.log_uid_tag_callback import (
11
12
  LogUidTaggingCallback,
12
13
  )
@@ -31,8 +32,6 @@ from mx_bluesky.hyperion.external_interaction.callbacks.zocalo_callback import (
31
32
  from mx_bluesky.hyperion.log import (
32
33
  ISPYB_LOGGER,
33
34
  NEXUS_LOGGER,
34
- _get_logging_dir,
35
- tag_filter,
36
35
  )
37
36
  from mx_bluesky.hyperion.parameters.cli import parse_callback_dev_mode_arg
38
37
  from mx_bluesky.hyperion.parameters.constants import CONST
@@ -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__
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from mx_bluesky.common.parameters.components import DiffractionExperimentWithSample
3
4
  from mx_bluesky.hyperion.external_interaction.ispyb.data_model import (
4
5
  DataCollectionGroupInfo,
5
6
  DataCollectionInfo,
@@ -11,7 +12,6 @@ from mx_bluesky.hyperion.external_interaction.ispyb.ispyb_store import (
11
12
  from mx_bluesky.hyperion.external_interaction.ispyb.ispyb_utils import (
12
13
  get_current_time_string,
13
14
  )
14
- from mx_bluesky.hyperion.parameters.components import DiffractionExperimentWithSample
15
15
 
16
16
 
17
17
  def populate_data_collection_group(params: DiffractionExperimentWithSample):
@@ -2,7 +2,6 @@ from typing import TypedDict
2
2
 
3
3
  import numpy as np
4
4
  from bluesky.callbacks import CallbackBase
5
- from dodal.devices.oav.oav_detector import OAVConfigParams
6
5
  from dodal.devices.oav.utils import calculate_x_y_z_of_pixel
7
6
  from event_model.documents import Event
8
7
 
@@ -26,21 +25,19 @@ class GridParamUpdate(TypedDict):
26
25
  class GridDetectionCallback(CallbackBase):
27
26
  def __init__(
28
27
  self,
29
- oav_params: OAVConfigParams,
30
28
  *args,
31
29
  ) -> None:
32
30
  super().__init__(*args)
33
- self.oav_params = oav_params
34
- self.start_positions: list = []
31
+ self.start_positions_mm: list = []
35
32
  self.box_numbers: list = []
36
33
 
37
34
  def event(self, doc: Event):
38
35
  data = doc.get("data")
39
- top_left_x_px = data["oav_grid_snapshot_top_left_x"]
40
- box_width_px = data["oav_grid_snapshot_box_width"]
36
+ top_left_x_px = data["oav-grid_snapshot-top_left_x"]
37
+ box_width_px = data["oav-grid_snapshot-box_width"]
41
38
  x_of_centre_of_first_box_px = top_left_x_px + box_width_px / 2
42
39
 
43
- top_left_y_px = data["oav_grid_snapshot_top_left_y"]
40
+ top_left_y_px = data["oav-grid_snapshot-top_left_y"]
44
41
  y_of_centre_of_first_box_px = top_left_y_px + box_width_px / 2
45
42
 
46
43
  smargon_omega = data["smargon-omega"]
@@ -53,36 +50,44 @@ class GridDetectionCallback(CallbackBase):
53
50
  y_of_centre_of_first_box_px,
54
51
  )
55
52
 
56
- position_grid_start = calculate_x_y_z_of_pixel(
57
- current_xyz, smargon_omega, centre_of_first_box, self.oav_params
58
- )
53
+ microns_per_pixel_x = data["oav-microns_per_pixel_x"]
54
+ microns_per_pixel_y = data["oav-microns_per_pixel_y"]
55
+ beam_x = data["oav-beam_centre_i"]
56
+ beam_y = data["oav-beam_centre_j"]
59
57
 
60
- LOGGER.info(f"Calculated start position {position_grid_start}")
58
+ position_grid_start_mm = calculate_x_y_z_of_pixel(
59
+ current_xyz,
60
+ smargon_omega,
61
+ centre_of_first_box,
62
+ (beam_x, beam_y),
63
+ (microns_per_pixel_x, microns_per_pixel_y),
64
+ )
65
+ LOGGER.info(f"Calculated start position {position_grid_start_mm}")
61
66
 
62
- self.start_positions.append(position_grid_start)
67
+ self.start_positions_mm.append(position_grid_start_mm)
63
68
  self.box_numbers.append(
64
69
  (
65
- data["oav_grid_snapshot_num_boxes_x"],
66
- data["oav_grid_snapshot_num_boxes_y"],
70
+ data["oav-grid_snapshot-num_boxes_x"],
71
+ data["oav-grid_snapshot-num_boxes_y"],
67
72
  )
68
73
  )
69
74
 
70
- self.x_step_size_mm = box_width_px * self.oav_params.micronsPerXPixel / 1000
71
- self.y_step_size_mm = box_width_px * self.oav_params.micronsPerYPixel / 1000
72
- self.z_step_size_mm = box_width_px * self.oav_params.micronsPerYPixel / 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
73
78
  return doc
74
79
 
75
80
  def get_grid_parameters(self) -> GridParamUpdate:
76
81
  return {
77
- "x_start_um": self.start_positions[0][0],
78
- "y_start_um": self.start_positions[0][1],
79
- "y2_start_um": self.start_positions[0][1],
80
- "z_start_um": self.start_positions[1][2],
81
- "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,
82
87
  "x_steps": self.box_numbers[0][0],
83
88
  "y_steps": self.box_numbers[0][1],
84
89
  "z_steps": self.box_numbers[1][1],
85
- "x_step_size_um": self.x_step_size_mm,
86
- "y_step_size_um": self.y_step_size_mm,
87
- "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,
88
93
  }
@@ -5,9 +5,12 @@ from collections.abc import Callable, Sequence
5
5
  from typing import TYPE_CHECKING, Any, TypeVar, cast
6
6
 
7
7
  from dodal.beamline_specific_utils.i03 import beam_size_from_aperture
8
+ from dodal.devices.detector import DetectorParams
8
9
  from dodal.devices.detector.det_resolution import resolution
9
10
  from dodal.devices.synchrotron import SynchrotronMode
10
11
 
12
+ from mx_bluesky.common.parameters.components import DiffractionExperimentWithSample
13
+ from mx_bluesky.common.utils.log import set_dcgid_tag
11
14
  from mx_bluesky.hyperion.external_interaction.callbacks.plan_reactive_callback import (
12
15
  PlanReactiveCallback,
13
16
  )
@@ -21,8 +24,7 @@ from mx_bluesky.hyperion.external_interaction.ispyb.ispyb_store import (
21
24
  StoreInIspyb,
22
25
  )
23
26
  from mx_bluesky.hyperion.external_interaction.ispyb.ispyb_utils import get_ispyb_config
24
- from mx_bluesky.hyperion.log import ISPYB_LOGGER, set_dcgid_tag
25
- from mx_bluesky.hyperion.parameters.components import DiffractionExperimentWithSample
27
+ from mx_bluesky.hyperion.log import ISPYB_LOGGER
26
28
  from mx_bluesky.hyperion.parameters.constants import CONST
27
29
  from mx_bluesky.hyperion.utils.utils import convert_eV_to_angstrom
28
30
 
@@ -33,6 +35,25 @@ if TYPE_CHECKING:
33
35
  from event_model.documents import Event, EventDescriptor, RunStart, RunStop
34
36
 
35
37
 
38
+ def _update_based_on_energy(
39
+ doc: Event,
40
+ detector_params: DetectorParams,
41
+ data_collection_info: DataCollectionInfo,
42
+ ):
43
+ """If energy has been read as part of this reading then add it into the data
44
+ collection info along with the other fields that depend on it."""
45
+ if energy_kev := doc["data"].get("dcm-energy_in_kev", None):
46
+ energy_ev = energy_kev * 1000
47
+ wavelength_angstroms = convert_eV_to_angstrom(energy_ev)
48
+ data_collection_info.wavelength = wavelength_angstroms
49
+ data_collection_info.resolution = resolution(
50
+ detector_params,
51
+ wavelength_angstroms,
52
+ detector_params.detector_distance,
53
+ )
54
+ return data_collection_info
55
+
56
+
36
57
  class BaseISPyBCallback(PlanReactiveCallback):
37
58
  def __init__(
38
59
  self,
@@ -109,6 +130,9 @@ class BaseISPyBCallback(PlanReactiveCallback):
109
130
  slitgap_horizontal=doc["data"]["s4_slit_gaps_xgap"],
110
131
  slitgap_vertical=doc["data"]["s4_slit_gaps_ygap"],
111
132
  )
133
+ hwscan_data_collection_info = _update_based_on_energy(
134
+ doc, self.params.detector_params, hwscan_data_collection_info
135
+ )
112
136
  hwscan_position_info = DataCollectionPositionInfo(
113
137
  pos_x=float(doc["data"]["smargon-x"]),
114
138
  pos_y=float(doc["data"]["smargon-y"]),
@@ -137,16 +161,9 @@ class BaseISPyBCallback(PlanReactiveCallback):
137
161
  if transmission := doc["data"]["attenuator-actual_transmission"]:
138
162
  # Ispyb wants the transmission in a percentage, we use fractions
139
163
  hwscan_data_collection_info.transmission = transmission * 100
140
- event_energy = doc["data"]["dcm-energy_in_kev"]
141
- if event_energy:
142
- energy_ev = event_energy * 1000
143
- wavelength_angstroms = convert_eV_to_angstrom(energy_ev)
144
- hwscan_data_collection_info.wavelength = wavelength_angstroms
145
- hwscan_data_collection_info.resolution = resolution(
146
- self.params.detector_params,
147
- wavelength_angstroms,
148
- self.params.detector_params.detector_distance,
149
- )
164
+ hwscan_data_collection_info = _update_based_on_energy(
165
+ doc, self.params.detector_params, hwscan_data_collection_info
166
+ )
150
167
  scan_data_infos = self.populate_info_for_update(
151
168
  hwscan_data_collection_info, None, self.params
152
169
  )
@@ -1,7 +1,7 @@
1
1
  from bluesky.callbacks import CallbackBase
2
2
  from event_model import RunStart, RunStop
3
3
 
4
- from mx_bluesky.hyperion.log import set_uid_tag
4
+ from mx_bluesky.common.utils.log import set_uid_tag
5
5
 
6
6
 
7
7
  class LogUidTaggingCallback(CallbackBase):
@@ -62,7 +62,7 @@ class RobotLoadISPyBCallback(PlanReactiveCallback):
62
62
  self.action_id is not None
63
63
  ), "ISPyB Robot load callback event called unexpectedly"
64
64
  barcode = doc["data"]["robot-barcode"]
65
- oav_snapshot = doc["data"]["oav_snapshot_last_saved_path"]
65
+ oav_snapshot = doc["data"]["oav-snapshot-last_saved_path"]
66
66
  webcam_snapshot = doc["data"]["webcam-last_saved_path"]
67
67
  # I03 uses webcam/oav snapshots in place of before/after snapshots
68
68
  self.expeye.update_barcode_and_snapshots(
@@ -3,6 +3,8 @@ from __future__ import annotations
3
3
  from collections.abc import Callable, Sequence
4
4
  from typing import TYPE_CHECKING, Any, cast
5
5
 
6
+ from mx_bluesky.common.parameters.components import IspybExperimentType
7
+ from mx_bluesky.common.utils.log import set_dcgid_tag
6
8
  from mx_bluesky.hyperion.external_interaction.callbacks.common.ispyb_mapping import (
7
9
  populate_data_collection_group,
8
10
  populate_remaining_data_collection_info,
@@ -22,8 +24,7 @@ from mx_bluesky.hyperion.external_interaction.ispyb.ispyb_store import (
22
24
  IspybIds,
23
25
  StoreInIspyb,
24
26
  )
25
- from mx_bluesky.hyperion.log import ISPYB_LOGGER, set_dcgid_tag
26
- from mx_bluesky.hyperion.parameters.components import IspybExperimentType
27
+ from mx_bluesky.hyperion.log import ISPYB_LOGGER
27
28
  from mx_bluesky.hyperion.parameters.constants import CONST
28
29
  from mx_bluesky.hyperion.parameters.rotation import RotationScan
29
30
 
@@ -60,7 +61,9 @@ class RotationISPyBCallback(BaseISPyBCallback):
60
61
  ISPYB_LOGGER.info(
61
62
  "ISPyB callback received start document with experiment parameters."
62
63
  )
63
- 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)
64
67
  dcgid = (
65
68
  self.ispyb_ids.data_collection_group_id
66
69
  if (self.params.sample_id == self.last_sample_id)
@@ -157,7 +160,7 @@ class RotationISPyBCallback(BaseISPyBCallback):
157
160
  data_collection_info = DataCollectionInfo(
158
161
  **{
159
162
  f"xtal_snapshot{self._oav_snapshot_event_idx}": data.get(
160
- "oav_snapshot_last_saved_path"
163
+ "oav-snapshot-last_saved_path"
161
164
  )
162
165
  }
163
166
  )
@@ -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
  )