mx-bluesky 1.4.6__py3-none-any.whl → 1.4.8__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 (95) hide show
  1. mx_bluesky/_version.py +2 -2
  2. mx_bluesky/beamlines/aithre_lasershaping/__init__.py +13 -0
  3. mx_bluesky/beamlines/aithre_lasershaping/check_goniometer_performance.py +29 -0
  4. mx_bluesky/beamlines/aithre_lasershaping/goniometer_controls.py +18 -0
  5. mx_bluesky/beamlines/i04/redis_to_murko_forwarder.py +35 -29
  6. mx_bluesky/beamlines/i04/thawing_plan.py +18 -3
  7. mx_bluesky/beamlines/i23/__init__.py +3 -0
  8. mx_bluesky/beamlines/i23/serial.py +71 -0
  9. mx_bluesky/beamlines/i24/serial/__init__.py +2 -0
  10. mx_bluesky/beamlines/i24/serial/extruder/EX-gui-edm/DiamondExtruder-I24-py3v1.edl +12 -12
  11. mx_bluesky/beamlines/i24/serial/extruder/i24ssx_Extruder_Collect_py3v2.py +36 -30
  12. mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/pumpprobe-py3v1.edl +3 -3
  13. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Collect_py3v1.py +15 -66
  14. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Manager_py3v1.py +8 -10
  15. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_moveonclick.py +10 -3
  16. mx_bluesky/beamlines/i24/serial/log.py +9 -9
  17. mx_bluesky/beamlines/i24/serial/parameters/utils.py +36 -7
  18. mx_bluesky/beamlines/i24/serial/set_visit_directory.sh +1 -1
  19. mx_bluesky/beamlines/i24/serial/setup_beamline/pv.py +16 -17
  20. mx_bluesky/beamlines/i24/serial/setup_beamline/pv_abstract.py +4 -4
  21. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_beamline.py +51 -52
  22. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_detector.py +3 -2
  23. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_zebra_plans.py +9 -7
  24. mx_bluesky/beamlines/i24/serial/web_gui_plans/general_plans.py +71 -11
  25. mx_bluesky/beamlines/i24/serial/write_nexus.py +6 -5
  26. mx_bluesky/{hyperion → common}/device_setup_plans/check_beamstop.py +1 -1
  27. mx_bluesky/{hyperion → common}/device_setup_plans/manipulate_sample.py +1 -1
  28. mx_bluesky/{hyperion → common}/device_setup_plans/setup_oav.py +12 -6
  29. mx_bluesky/common/device_setup_plans/xbpm_feedback.py +45 -0
  30. mx_bluesky/{hyperion → common}/experiment_plans/change_aperture_then_move_plan.py +13 -29
  31. mx_bluesky/{hyperion → common}/experiment_plans/oav_grid_detection_plan.py +6 -6
  32. mx_bluesky/common/external_interaction/callbacks/common/ispyb_callback_base.py +8 -9
  33. mx_bluesky/common/external_interaction/callbacks/common/ispyb_mapping.py +1 -1
  34. mx_bluesky/common/external_interaction/callbacks/common/zocalo_callback.py +18 -15
  35. mx_bluesky/{hyperion → common}/external_interaction/callbacks/sample_handling/sample_handling_callback.py +16 -4
  36. mx_bluesky/common/external_interaction/callbacks/xray_centre/ispyb_callback.py +50 -45
  37. mx_bluesky/common/external_interaction/callbacks/xray_centre/nexus_callback.py +2 -1
  38. mx_bluesky/common/external_interaction/ispyb/data_model.py +1 -0
  39. mx_bluesky/common/external_interaction/ispyb/ispyb_store.py +18 -2
  40. mx_bluesky/common/external_interaction/ispyb/ispyb_utils.py +4 -4
  41. mx_bluesky/common/external_interaction/nexus/nexus_utils.py +1 -1
  42. mx_bluesky/common/parameters/components.py +22 -2
  43. mx_bluesky/common/parameters/constants.py +6 -16
  44. mx_bluesky/common/parameters/gridscan.py +36 -32
  45. mx_bluesky/common/plans/common_flyscan_xray_centre_plan.py +316 -0
  46. mx_bluesky/common/plans/inner_plans/__init__ .py +0 -0
  47. mx_bluesky/common/plans/read_hardware.py +3 -3
  48. mx_bluesky/common/plans/write_sample_status.py +46 -0
  49. mx_bluesky/common/preprocessors/__init__.py +0 -0
  50. mx_bluesky/common/preprocessors/preprocessors.py +105 -0
  51. mx_bluesky/common/protocols/__init__.py +0 -0
  52. mx_bluesky/common/protocols/protocols.py +10 -0
  53. mx_bluesky/common/utils/log.py +15 -12
  54. mx_bluesky/hyperion/__main__.py +5 -24
  55. mx_bluesky/hyperion/baton_handler.py +84 -0
  56. mx_bluesky/hyperion/device_setup_plans/dcm_pitch_roll_mirror_adjuster.py +4 -4
  57. mx_bluesky/hyperion/device_setup_plans/setup_panda.py +5 -1
  58. mx_bluesky/hyperion/device_setup_plans/setup_zebra.py +0 -33
  59. mx_bluesky/hyperion/device_setup_plans/utils.py +4 -4
  60. mx_bluesky/hyperion/experiment_plans/__init__.py +0 -10
  61. mx_bluesky/hyperion/experiment_plans/experiment_registry.py +0 -16
  62. mx_bluesky/hyperion/experiment_plans/grid_detect_then_xray_centre_plan.py +71 -88
  63. mx_bluesky/hyperion/experiment_plans/hyperion_flyscan_xray_centre_plan.py +183 -0
  64. mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py +17 -8
  65. mx_bluesky/hyperion/experiment_plans/oav_snapshot_plan.py +29 -8
  66. mx_bluesky/hyperion/experiment_plans/pin_centre_then_xray_centre_plan.py +4 -4
  67. mx_bluesky/hyperion/experiment_plans/pin_tip_centring_plan.py +6 -4
  68. mx_bluesky/hyperion/experiment_plans/robot_load_and_change_energy.py +11 -3
  69. mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +9 -34
  70. mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +35 -68
  71. mx_bluesky/hyperion/experiment_plans/set_energy_plan.py +27 -8
  72. mx_bluesky/hyperion/external_interaction/agamemnon.py +140 -10
  73. mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +17 -9
  74. mx_bluesky/hyperion/external_interaction/callbacks/snapshot_callback.py +259 -0
  75. mx_bluesky/hyperion/parameters/cli.py +2 -10
  76. mx_bluesky/hyperion/parameters/constants.py +0 -5
  77. mx_bluesky/hyperion/parameters/device_composites.py +40 -5
  78. mx_bluesky/hyperion/parameters/gridscan.py +9 -58
  79. mx_bluesky/hyperion/parameters/rotation.py +1 -5
  80. mx_bluesky/hyperion/utils/context.py +2 -5
  81. mx_bluesky/hyperion/utils/validation.py +13 -10
  82. {mx_bluesky-1.4.6.dist-info → mx_bluesky-1.4.8.dist-info}/METADATA +10 -9
  83. {mx_bluesky-1.4.6.dist-info → mx_bluesky-1.4.8.dist-info}/RECORD +92 -79
  84. {mx_bluesky-1.4.6.dist-info → mx_bluesky-1.4.8.dist-info}/WHEEL +1 -1
  85. mx_bluesky/common/external_interaction/callbacks/common/aperture_change_callback.py +0 -22
  86. mx_bluesky/hyperion/device_setup_plans/xbpm_feedback.py +0 -103
  87. mx_bluesky/hyperion/experiment_plans/flyscan_xray_centre_plan.py +0 -466
  88. /mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/{short1-laser.png → s1l.png} +0 -0
  89. /mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/{short2-laser.png → s2l.png} +0 -0
  90. /mx_bluesky/{hyperion → common}/device_setup_plans/position_detector.py +0 -0
  91. /mx_bluesky/{hyperion → common}/external_interaction/callbacks/sample_handling/__init__.py +0 -0
  92. /mx_bluesky/common/plans/{do_fgs.py → inner_plans/do_fgs.py} +0 -0
  93. {mx_bluesky-1.4.6.dist-info → mx_bluesky-1.4.8.dist-info}/entry_points.txt +0 -0
  94. {mx_bluesky-1.4.6.dist-info → mx_bluesky-1.4.8.dist-info/licenses}/LICENSE +0 -0
  95. {mx_bluesky-1.4.6.dist-info → mx_bluesky-1.4.8.dist-info}/top_level.txt +0 -0
@@ -3,7 +3,6 @@ from __future__ import annotations
3
3
  from math import isclose
4
4
  from typing import cast
5
5
 
6
- import bluesky.preprocessors as bpp
7
6
  import pydantic
8
7
  from blueapi.core import BlueskyContext
9
8
  from bluesky import plan_stubs as bps
@@ -11,13 +10,14 @@ from bluesky.utils import MsgGenerator
11
10
  from dodal.devices.aperturescatterguard import ApertureScatterguard
12
11
  from dodal.devices.attenuator.attenuator import BinaryFilterAttenuator
13
12
  from dodal.devices.backlight import Backlight
14
- from dodal.devices.dcm import DCM
15
13
  from dodal.devices.detector.detector_motion import DetectorMotion
16
14
  from dodal.devices.eiger import EigerDetector
17
15
  from dodal.devices.fast_grid_scan import PandAFastGridScan, ZebraFastGridScan
18
16
  from dodal.devices.flux import Flux
19
17
  from dodal.devices.focusing_mirror import FocusingMirrorWithStripes, MirrorVoltages
20
- from dodal.devices.i03.beamstop import Beamstop
18
+ from dodal.devices.i03 import Beamstop
19
+ from dodal.devices.i03.dcm import DCM
20
+ from dodal.devices.i03.undulator_dcm import UndulatorDCM
21
21
  from dodal.devices.motors import XYZPositioner
22
22
  from dodal.devices.oav.oav_detector import OAV
23
23
  from dodal.devices.oav.pin_image_recognition import PinTipDetection
@@ -27,7 +27,6 @@ from dodal.devices.smargon import Smargon
27
27
  from dodal.devices.synchrotron import Synchrotron
28
28
  from dodal.devices.thawer import Thawer
29
29
  from dodal.devices.undulator import Undulator
30
- from dodal.devices.undulator_dcm import UndulatorDCM
31
30
  from dodal.devices.webcam import Webcam
32
31
  from dodal.devices.xbpm_feedback import XBPMFeedback
33
32
  from dodal.devices.zebra.zebra import Zebra
@@ -37,14 +36,10 @@ from dodal.log import LOGGER
37
36
  from ophyd_async.fastcs.panda import HDFPanda
38
37
 
39
38
  from mx_bluesky.common.parameters.constants import OavConstants
40
- from mx_bluesky.common.xrc_result import XRayCentreEventHandler
41
39
  from mx_bluesky.hyperion.device_setup_plans.utils import (
42
40
  fill_in_energy_if_not_supplied,
43
41
  start_preparing_data_collection_then_do_plan,
44
42
  )
45
- from mx_bluesky.hyperion.experiment_plans.change_aperture_then_move_plan import (
46
- change_aperture_then_move_to_xtal,
47
- )
48
43
  from mx_bluesky.hyperion.experiment_plans.grid_detect_then_xray_centre_plan import (
49
44
  GridDetectThenXRayCentreComposite,
50
45
  )
@@ -117,6 +112,7 @@ def _flyscan_plan_from_robot_load_params(
117
112
  yield from pin_centre_then_flyscan_plan(
118
113
  cast(GridDetectThenXRayCentreComposite, composite),
119
114
  params.pin_centre_then_xray_centre_params,
115
+ oav_config_file,
120
116
  )
121
117
 
122
118
 
@@ -133,30 +129,10 @@ def _robot_load_then_flyscan_plan(
133
129
  yield from _flyscan_plan_from_robot_load_params(composite, params, oav_config_file)
134
130
 
135
131
 
136
- def robot_load_then_centre(
137
- composite: RobotLoadThenCentreComposite,
138
- parameters: RobotLoadThenCentre,
139
- ) -> MsgGenerator:
140
- """Perform pin-tip detection followed by a flyscan to determine centres of interest.
141
- Performs a robot load if necessary. Centre on the best diffracting centre.
142
- """
143
-
144
- xray_centre_event_handler = XRayCentreEventHandler()
145
-
146
- yield from bpp.subs_wrapper(
147
- robot_load_then_xray_centre(composite, parameters), xray_centre_event_handler
148
- )
149
- flyscan_results = xray_centre_event_handler.xray_centre_results
150
- if flyscan_results is not None:
151
- yield from change_aperture_then_move_to_xtal(
152
- flyscan_results[0], composite.smargon, composite.aperture_scatterguard
153
- )
154
- # else no chi change, no need to recentre.
155
-
156
-
157
132
  def robot_load_then_xray_centre(
158
133
  composite: RobotLoadThenCentreComposite,
159
134
  parameters: RobotLoadThenCentre,
135
+ oav_config_file: str = OavConstants.OAV_CONFIG_JSON,
160
136
  ) -> MsgGenerator:
161
137
  """Perform pin-tip detection followed by a flyscan to determine centres of interest.
162
138
  Performs a robot load if necessary."""
@@ -180,10 +156,7 @@ def robot_load_then_xray_centre(
180
156
 
181
157
  if doing_sample_load:
182
158
  LOGGER.info("Pin not loaded, loading and centring")
183
- plan = _robot_load_then_flyscan_plan(
184
- composite,
185
- parameters,
186
- )
159
+ plan = _robot_load_then_flyscan_plan(composite, parameters, oav_config_file)
187
160
  else:
188
161
  # Robot load normally sets the energy so we should do this explicitly if no load is
189
162
  # being done
@@ -194,7 +167,9 @@ def robot_load_then_xray_centre(
194
167
  )
195
168
 
196
169
  if doing_chi_change:
197
- plan = _flyscan_plan_from_robot_load_params(composite, parameters)
170
+ plan = _flyscan_plan_from_robot_load_params(
171
+ composite, parameters, oav_config_file
172
+ )
198
173
  LOGGER.info("Pin already loaded but chi changed so centring")
199
174
  else:
200
175
  LOGGER.info("Pin already loaded and chi not changed so doing nothing")
@@ -10,11 +10,11 @@ from bluesky.utils import MsgGenerator
10
10
  from dodal.devices.aperturescatterguard import ApertureScatterguard
11
11
  from dodal.devices.attenuator.attenuator import BinaryFilterAttenuator
12
12
  from dodal.devices.backlight import Backlight
13
- from dodal.devices.dcm import DCM
14
13
  from dodal.devices.detector.detector_motion import DetectorMotion
15
14
  from dodal.devices.eiger import EigerDetector
16
15
  from dodal.devices.flux import Flux
17
- from dodal.devices.i03.beamstop import Beamstop
16
+ from dodal.devices.i03 import Beamstop
17
+ from dodal.devices.i03.dcm import DCM
18
18
  from dodal.devices.oav.oav_detector import OAV
19
19
  from dodal.devices.oav.oav_parameters import OAVParameters
20
20
  from dodal.devices.robot import BartRobot
@@ -26,20 +26,27 @@ from dodal.devices.xbpm_feedback import XBPMFeedback
26
26
  from dodal.devices.zebra.zebra import RotationDirection, Zebra
27
27
  from dodal.devices.zebra.zebra_controlled_shutter import ZebraShutter
28
28
  from dodal.plan_stubs.check_topup import check_topup_and_wait_if_necessary
29
+ from dodal.plans.preprocessors.verify_undulator_gap import (
30
+ verify_undulator_gap_before_run_decorator,
31
+ )
29
32
 
33
+ from mx_bluesky.common.device_setup_plans.manipulate_sample import (
34
+ cleanup_sample_environment,
35
+ move_phi_chi_omega,
36
+ move_x_y_z,
37
+ setup_sample_environment,
38
+ )
39
+ from mx_bluesky.common.parameters.components import WithSnapshot
30
40
  from mx_bluesky.common.plans.read_hardware import (
31
41
  read_hardware_for_zocalo,
32
42
  standard_read_hardware_during_collection,
33
43
  standard_read_hardware_pre_collection,
34
44
  )
45
+ from mx_bluesky.common.preprocessors.preprocessors import (
46
+ transmission_and_xbpm_feedback_for_collection_decorator,
47
+ )
35
48
  from mx_bluesky.common.utils.context import device_composite_from_context
36
49
  from mx_bluesky.common.utils.log import LOGGER
37
- from mx_bluesky.hyperion.device_setup_plans.manipulate_sample import (
38
- cleanup_sample_environment,
39
- move_phi_chi_omega,
40
- move_x_y_z,
41
- setup_sample_environment,
42
- )
43
50
  from mx_bluesky.hyperion.device_setup_plans.setup_zebra import (
44
51
  arm_zebra,
45
52
  setup_zebra_for_rotation,
@@ -48,9 +55,6 @@ from mx_bluesky.hyperion.device_setup_plans.setup_zebra import (
48
55
  from mx_bluesky.hyperion.device_setup_plans.utils import (
49
56
  start_preparing_data_collection_then_do_plan,
50
57
  )
51
- from mx_bluesky.hyperion.device_setup_plans.xbpm_feedback import (
52
- transmission_and_xbpm_feedback_for_collection_decorator,
53
- )
54
58
  from mx_bluesky.hyperion.experiment_plans.oav_snapshot_plan import (
55
59
  OavSnapshotComposite,
56
60
  oav_snapshot_plan,
@@ -352,66 +356,30 @@ def _move_and_rotation(
352
356
  group=CONST.WAIT.ROTATION_READY_FOR_DC,
353
357
  )
354
358
  yield from oav_snapshot_plan(composite, params, oav_params)
355
- yield from rotation_scan_plan(
356
- composite,
357
- params,
358
- motion_values,
359
- )
359
+ yield from rotation_scan_plan(composite, params, motion_values)
360
360
 
361
361
 
362
- def rotation_scan(
362
+ def multi_rotation_scan(
363
363
  composite: RotationScanComposite,
364
- parameters: RotationScan,
364
+ parameters: MultiRotationScan,
365
365
  oav_params: OAVParameters | None = None,
366
366
  ) -> MsgGenerator:
367
- parameters.features.update_self_from_server()
368
-
369
- if not oav_params:
370
- oav_params = OAVParameters(context="xrayCentring")
371
-
372
- @bpp.set_run_key_decorator("rotation_scan")
373
- @bpp.run_decorator( # attach experiment metadata to the start document
367
+ @bpp.set_run_key_decorator(CONST.PLAN.ROTATION_MULTI_OUTER)
368
+ @bpp.run_decorator(
374
369
  md={
375
- "subplan_name": CONST.PLAN.ROTATION_OUTER,
376
- "mx_bluesky_parameters": parameters.model_dump_json(),
377
- "activate_callbacks": [
378
- "RotationISPyBCallback",
379
- "RotationNexusFileCallback",
380
- ],
370
+ "activate_callbacks": ["BeamDrawingCallback"],
371
+ "with_snapshot": parameters.model_dump_json(
372
+ include=WithSnapshot.model_fields.keys() # type: ignore
373
+ ),
381
374
  }
382
375
  )
383
- @transmission_and_xbpm_feedback_for_collection_decorator(
384
- composite.undulator,
385
- composite.xbpm_feedback,
386
- composite.attenuator,
387
- composite.dcm,
388
- parameters.transmission_frac,
389
- )
390
- def rotation_scan_plan_with_stage_and_cleanup(
391
- params: RotationScan,
392
- ):
393
- eiger: EigerDetector = composite.eiger
394
- eiger.set_detector_parameters(params.detector_params)
395
-
396
- @bpp.finalize_decorator(lambda: _cleanup_plan(composite))
397
- def rotation_with_cleanup_and_stage(params: RotationScan):
398
- yield from _move_and_rotation(composite, params, oav_params)
399
-
400
- LOGGER.info("setting up and staging eiger...")
401
- yield from start_preparing_data_collection_then_do_plan(
402
- composite.beamstop,
403
- eiger,
404
- composite.detector_motion,
405
- params.detector_distance_mm,
406
- rotation_with_cleanup_and_stage(params),
407
- group=CONST.WAIT.ROTATION_READY_FOR_DC,
408
- )
409
- yield from bps.unstage(eiger)
376
+ def _wrapped_multi_rotation_scan():
377
+ yield from multi_rotation_scan_internal(composite, parameters, oav_params)
410
378
 
411
- yield from rotation_scan_plan_with_stage_and_cleanup(parameters)
379
+ yield from _wrapped_multi_rotation_scan()
412
380
 
413
381
 
414
- def multi_rotation_scan(
382
+ def multi_rotation_scan_internal(
415
383
  composite: RotationScanComposite,
416
384
  parameters: MultiRotationScan,
417
385
  oav_params: OAVParameters | None = None,
@@ -422,6 +390,10 @@ def multi_rotation_scan(
422
390
  eiger: EigerDetector = composite.eiger
423
391
  eiger.set_detector_parameters(parameters.detector_params)
424
392
 
393
+ @transmission_and_xbpm_feedback_for_collection_decorator(
394
+ composite,
395
+ parameters.transmission_frac,
396
+ )
425
397
  @bpp.set_run_key_decorator("multi_rotation_scan")
426
398
  @bpp.run_decorator(
427
399
  md={
@@ -434,17 +406,11 @@ def multi_rotation_scan(
434
406
  ],
435
407
  }
436
408
  )
437
- @transmission_and_xbpm_feedback_for_collection_decorator(
438
- composite.undulator,
439
- composite.xbpm_feedback,
440
- composite.attenuator,
441
- composite.dcm,
442
- parameters.transmission_frac,
443
- )
444
409
  @bpp.finalize_decorator(lambda: _cleanup_plan(composite))
445
410
  def _multi_rotation_scan():
446
411
  for single_scan in parameters.single_rotation_scans:
447
412
 
413
+ @verify_undulator_gap_before_run_decorator(composite)
448
414
  @bpp.set_run_key_decorator("rotation_scan")
449
415
  @bpp.run_decorator( # attach experiment metadata to the start document
450
416
  md={
@@ -459,6 +425,8 @@ def multi_rotation_scan(
459
425
 
460
426
  yield from rotation_scan_core(single_scan)
461
427
 
428
+ yield from bps.unstage(eiger)
429
+
462
430
  LOGGER.info("setting up and staging eiger...")
463
431
  yield from start_preparing_data_collection_then_do_plan(
464
432
  composite.beamstop,
@@ -468,4 +436,3 @@ def multi_rotation_scan(
468
436
  _multi_rotation_scan(),
469
437
  group=CONST.WAIT.ROTATION_READY_FOR_DC,
470
438
  )
471
- yield from bps.unstage(eiger)
@@ -5,18 +5,21 @@
5
5
  * reenable feedback
6
6
  """
7
7
 
8
+ import bluesky.preprocessors as bpp
8
9
  import pydantic
9
10
  from bluesky import plan_stubs as bps
10
11
  from dodal.devices.attenuator.attenuator import BinaryFilterAttenuator
11
- from dodal.devices.dcm import DCM
12
12
  from dodal.devices.focusing_mirror import FocusingMirrorWithStripes, MirrorVoltages
13
- from dodal.devices.undulator_dcm import UndulatorDCM
13
+ from dodal.devices.i03.dcm import DCM
14
+ from dodal.devices.i03.undulator_dcm import UndulatorDCM
15
+ from dodal.devices.undulator import Undulator
14
16
  from dodal.devices.xbpm_feedback import XBPMFeedback
15
17
 
16
- from mx_bluesky.hyperion.device_setup_plans import dcm_pitch_roll_mirror_adjuster
17
- from mx_bluesky.hyperion.device_setup_plans.xbpm_feedback import (
18
+ from mx_bluesky.common.parameters.constants import PlanNameConstants
19
+ from mx_bluesky.common.preprocessors.preprocessors import (
18
20
  transmission_and_xbpm_feedback_for_collection_wrapper,
19
21
  )
22
+ from mx_bluesky.hyperion.device_setup_plans import dcm_pitch_roll_mirror_adjuster
20
23
 
21
24
  DESIRED_TRANSMISSION_FRACTION = 0.1
22
25
 
@@ -33,6 +36,17 @@ class SetEnergyComposite:
33
36
  attenuator: BinaryFilterAttenuator
34
37
 
35
38
 
39
+ # Remove composite after https://github.com/DiamondLightSource/dodal/issues/1092
40
+ @pydantic.dataclasses.dataclass(config={"arbitrary_types_allowed": True})
41
+ class XBPMWrapperComposite:
42
+ undulator: Undulator
43
+ xbpm_feedback: XBPMFeedback
44
+ attenuator: BinaryFilterAttenuator
45
+ dcm: DCM
46
+
47
+
48
+ @bpp.set_run_key_decorator(PlanNameConstants.SET_ENERGY)
49
+ @bpp.run_decorator()
36
50
  def _set_energy_plan(
37
51
  energy_kev,
38
52
  composite: SetEnergyComposite,
@@ -51,12 +65,17 @@ def set_energy_plan(
51
65
  energy_ev: float | None,
52
66
  composite: SetEnergyComposite,
53
67
  ):
68
+ # Remove conversion after https://github.com/DiamondLightSource/dodal/issues/1092
69
+ composite_for_wrapper = XBPMWrapperComposite(
70
+ composite.undulator_dcm.undulator_ref._obj, # noqa: SLF001
71
+ composite.xbpm_feedback,
72
+ composite.attenuator,
73
+ composite.dcm,
74
+ )
75
+
54
76
  if energy_ev:
55
77
  yield from transmission_and_xbpm_feedback_for_collection_wrapper(
56
78
  _set_energy_plan(energy_ev / 1000, composite),
57
- composite.undulator_dcm.undulator_ref(),
58
- composite.xbpm_feedback,
59
- composite.attenuator,
60
- composite.dcm,
79
+ composite_for_wrapper,
61
80
  DESIRED_TRANSMISSION_FRACTION,
62
81
  )
@@ -1,14 +1,25 @@
1
1
  import dataclasses
2
2
  import json
3
3
  import re
4
- from typing import TypeVar
4
+ import traceback
5
+ from os import path
6
+ from typing import Any, TypeVar
5
7
 
6
8
  import requests
9
+ from deepdiff.diff import DeepDiff
7
10
  from dodal.utils import get_beamline_name
8
-
9
- from mx_bluesky.common.parameters.components import WithVisit
10
- from mx_bluesky.common.parameters.constants import GridscanParamConstants
11
+ from jsonschema import ValidationError
12
+ from pydantic_extra_types.semantic_version import SemanticVersion
13
+
14
+ from mx_bluesky.common.parameters.components import (
15
+ PARAMETER_VERSION,
16
+ WithVisit,
17
+ )
18
+ from mx_bluesky.common.parameters.constants import (
19
+ GridscanParamConstants,
20
+ )
11
21
  from mx_bluesky.common.utils.log import LOGGER
22
+ from mx_bluesky.common.utils.utils import convert_angstrom_to_eV
12
23
  from mx_bluesky.hyperion.parameters.load_centre_collect import LoadCentreCollect
13
24
 
14
25
  T = TypeVar("T", bound=WithVisit)
@@ -16,6 +27,7 @@ AGAMEMNON_URL = "http://agamemnon.diamond.ac.uk/"
16
27
  MULTIPIN_PREFIX = "multipin"
17
28
  MULTIPIN_FORMAT_DESC = "Expected multipin format is multipin_{number_of_wells}x{well_size}+{distance_between_tip_and_first_well}"
18
29
  MULTIPIN_REGEX = rf"^{MULTIPIN_PREFIX}_(\d+)x(\d+(?:\.\d+)?)\+(\d+(?:\.\d+)?)$"
30
+ MX_GENERAL_ROOT_REGEX = r"^/dls/(?P<beamline>[^/]+)/data/[^/]*/(?P<visit>[^/]+)(?:/|$)"
19
31
 
20
32
 
21
33
  @dataclasses.dataclass
@@ -60,7 +72,7 @@ def _get_parameters_from_url(url: str) -> dict:
60
72
  raise KeyError(f"Unexpected json from agamemnon: {response_json}") from e
61
73
 
62
74
 
63
- def _get_pin_type_from_agamemnon_parameters(parameters: dict) -> PinType:
75
+ def get_pin_type_from_agamemnon_parameters(parameters: dict) -> PinType:
64
76
  loop_type_name: str | None = parameters["sample"]["loopType"]
65
77
  if loop_type_name:
66
78
  regex_search = re.search(MULTIPIN_REGEX, loop_type_name)
@@ -81,15 +93,130 @@ def get_next_instruction(beamline: str) -> dict:
81
93
  return _get_parameters_from_url(AGAMEMNON_URL + f"getnextcollect/{beamline}")
82
94
 
83
95
 
84
- def get_pin_type_from_agamemnon(beamline: str) -> PinType:
85
- params = get_next_instruction(beamline)
86
- return _get_pin_type_from_agamemnon_parameters(params)
96
+ def get_withvisit_parameters_from_agamemnon(parameters: dict) -> tuple:
97
+ try:
98
+ prefix = parameters["prefix"]
99
+ collection = parameters["collection"]
100
+ # Assuming distance is identical for multiple collections. Remove after https://github.com/DiamondLightSource/mx-bluesky/issues/773
101
+ detector_distance = collection[0]["distance"]
102
+ except KeyError as e:
103
+ raise KeyError("Unexpected json from agamemnon") from e
104
+
105
+ match = re.match(MX_GENERAL_ROOT_REGEX, prefix) if prefix else None
106
+
107
+ if match:
108
+ return (match.group("visit"), detector_distance)
109
+
110
+ raise ValueError(
111
+ f"Agamemnon prefix '{prefix}' does not match MX-General root structure"
112
+ )
113
+
114
+
115
+ def get_withenergy_parameters_from_agamemnon(parameters: dict) -> dict[str, Any]:
116
+ try:
117
+ first_collection: dict = parameters["collection"][0]
118
+ wavelength = first_collection.get("wavelength")
119
+ assert isinstance(wavelength, float)
120
+ demand_energy_ev = convert_angstrom_to_eV(wavelength)
121
+ return {"demand_energy_ev": demand_energy_ev}
122
+ except (KeyError, IndexError, AttributeError, TypeError):
123
+ return {"demand_energy_ev": None}
124
+
125
+
126
+ def get_param_version() -> SemanticVersion:
127
+ return SemanticVersion.validate_from_str(str(PARAMETER_VERSION))
128
+
129
+
130
+ def populate_parameters_from_agamemnon(agamemnon_params):
131
+ visit, detector_distance = get_withvisit_parameters_from_agamemnon(agamemnon_params)
132
+ with_energy_params = get_withenergy_parameters_from_agamemnon(agamemnon_params)
133
+ pin_type = get_pin_type_from_agamemnon_parameters(agamemnon_params)
134
+ first_collection = agamemnon_params["collection"][0]
135
+ visit_directory, file_name = path.split(agamemnon_params["prefix"])
136
+ return LoadCentreCollect.model_validate(
137
+ {
138
+ "parameter_model_version": get_param_version(),
139
+ "visit": visit,
140
+ "detector_distance_mm": detector_distance,
141
+ "sample_id": agamemnon_params["sample"]["id"],
142
+ "sample_puck": agamemnon_params["sample"]["container"],
143
+ "sample_pin": agamemnon_params["sample"]["position"],
144
+ "select_centres": {
145
+ "name": "TopNByMaxCount",
146
+ "n": pin_type.expected_number_of_crystals,
147
+ },
148
+ "robot_load_then_centre": {
149
+ "storage_directory": str(visit_directory) + "/xraycentring",
150
+ "file_name": file_name,
151
+ "tip_offset_um": pin_type.full_width / 2,
152
+ "grid_width_um": pin_type.full_width,
153
+ "omega_start_deg": 0.0,
154
+ "chi_start_deg": first_collection["chi"],
155
+ "transmission_frac": 1.0,
156
+ "features": {"use_gpu_results": True},
157
+ **with_energy_params,
158
+ },
159
+ "multi_rotation_scan": {
160
+ "comment": first_collection["comment"],
161
+ "storage_directory": str(visit_directory),
162
+ "exposure_time_s": first_collection["exposure_time"],
163
+ "file_name": file_name,
164
+ "transmission_frac": first_collection["transmission"],
165
+ "rotation_increment_deg": first_collection["omega_increment"],
166
+ "ispyb_experiment_type": first_collection["experiment_type"],
167
+ "snapshot_omegas_deg": [0.0, 90.0, 180.0, 270.0],
168
+ "rotation_scans": [
169
+ {
170
+ "scan_width_deg": (
171
+ first_collection["number_of_images"]
172
+ * first_collection["omega_increment"]
173
+ ),
174
+ "omega_start_deg": first_collection["omega_start"],
175
+ "phi_start_deg": first_collection["phi_start"],
176
+ "chi_start_deg": first_collection["chi"],
177
+ "rotation_direction": "Positive",
178
+ }
179
+ ],
180
+ **with_energy_params,
181
+ },
182
+ }
183
+ )
184
+
185
+
186
+ def create_parameters_from_agamemnon() -> LoadCentreCollect | None:
187
+ beamline_name = get_beamline_name("i03")
188
+ agamemnon_params = get_next_instruction(beamline_name)
189
+ return (
190
+ populate_parameters_from_agamemnon(agamemnon_params)
191
+ if agamemnon_params
192
+ else None
193
+ )
194
+
195
+
196
+ def compare_params(load_centre_collect_params):
197
+ try:
198
+ parameters = create_parameters_from_agamemnon()
199
+ # Log differences against GDA populated parameters
200
+ differences = DeepDiff(
201
+ parameters, load_centre_collect_params, math_epsilon=1e-5
202
+ )
203
+ if differences:
204
+ LOGGER.info(
205
+ f"Different parameters found when directly reading from Hyperion: {differences}"
206
+ )
207
+ except (ValueError, KeyError):
208
+ LOGGER.warning(f"Failed to compare parameters: {traceback.format_exc()}")
209
+ except Exception:
210
+ LOGGER.warning(
211
+ f"Unexpected error occurred. Failed to compare parameters: {traceback.format_exc()}"
212
+ )
87
213
 
88
214
 
89
215
  def update_params_from_agamemnon(parameters: T) -> T:
90
216
  try:
91
217
  beamline_name = get_beamline_name("i03")
92
- pin_type = get_pin_type_from_agamemnon(beamline_name)
218
+ agamemnon_params = get_next_instruction(beamline_name)
219
+ pin_type = get_pin_type_from_agamemnon_parameters(agamemnon_params)
93
220
  if isinstance(parameters, LoadCentreCollect):
94
221
  parameters.robot_load_then_centre.tip_offset_um = pin_type.full_width / 2
95
222
  parameters.robot_load_then_centre.grid_width_um = pin_type.full_width
@@ -99,6 +226,9 @@ def update_params_from_agamemnon(parameters: T) -> T:
99
226
  # Before we do https://github.com/DiamondLightSource/mx-bluesky/issues/226
100
227
  # this will give no snapshots but that's preferable
101
228
  parameters.multi_rotation_scan.snapshot_omegas_deg = []
229
+ except (ValueError, ValidationError) as e:
230
+ LOGGER.warning(f"Failed to update parameters: {e}")
102
231
  except Exception as e:
103
- LOGGER.warning(f"Failed to get pin type from agamemnon, using single pin {e}")
232
+ LOGGER.warning(f"Unexpected error occurred. Failed to update parameters: {e}")
233
+
104
234
  return parameters
@@ -1,7 +1,7 @@
1
1
  import logging
2
2
  from collections.abc import Callable, Sequence
3
3
  from threading import Thread
4
- from time import sleep
4
+ from time import sleep # noqa
5
5
 
6
6
  from bluesky.callbacks import CallbackBase
7
7
  from bluesky.callbacks.zmq import Proxy, RemoteDispatcher
@@ -14,6 +14,9 @@ from mx_bluesky.common.external_interaction.callbacks.common.log_uid_tag_callbac
14
14
  from mx_bluesky.common.external_interaction.callbacks.common.zocalo_callback import (
15
15
  ZocaloCallback,
16
16
  )
17
+ from mx_bluesky.common.external_interaction.callbacks.sample_handling.sample_handling_callback import (
18
+ SampleHandlingCallback,
19
+ )
17
20
  from mx_bluesky.common.external_interaction.callbacks.xray_centre.ispyb_callback import (
18
21
  GridscanISPyBCallback,
19
22
  )
@@ -23,7 +26,7 @@ from mx_bluesky.common.external_interaction.callbacks.xray_centre.nexus_callback
23
26
  from mx_bluesky.common.utils.log import (
24
27
  ISPYB_ZOCALO_CALLBACK_LOGGER,
25
28
  NEXUS_LOGGER,
26
- _get_logging_dir,
29
+ _get_logging_dirs,
27
30
  tag_filter,
28
31
  )
29
32
  from mx_bluesky.hyperion.external_interaction.callbacks.robot_load.ispyb_callback import (
@@ -35,8 +38,8 @@ from mx_bluesky.hyperion.external_interaction.callbacks.rotation.ispyb_callback
35
38
  from mx_bluesky.hyperion.external_interaction.callbacks.rotation.nexus_callback import (
36
39
  RotationNexusFileCallback,
37
40
  )
38
- from mx_bluesky.hyperion.external_interaction.callbacks.sample_handling.sample_handling_callback import (
39
- SampleHandlingCallback,
41
+ from mx_bluesky.hyperion.external_interaction.callbacks.snapshot_callback import (
42
+ BeamDrawingCallback,
40
43
  )
41
44
  from mx_bluesky.hyperion.parameters.cli import parse_callback_dev_mode_arg
42
45
  from mx_bluesky.hyperion.parameters.constants import CONST
@@ -67,15 +70,18 @@ def create_rotation_callbacks() -> tuple[
67
70
  return (
68
71
  RotationNexusFileCallback(),
69
72
  RotationISPyBCallback(
70
- emit=ZocaloCallback(CONST.PLAN.ROTATION_MAIN, CONST.ZOCALO_ENV)
73
+ emit=ZocaloCallback(CONST.PLAN.ROTATION_MULTI, CONST.ZOCALO_ENV)
71
74
  ),
72
75
  )
73
76
 
74
77
 
75
78
  def setup_callbacks() -> list[CallbackBase]:
79
+ rot_nexus_cb, rot_ispyb_cb = create_rotation_callbacks()
80
+ snapshot_cb = BeamDrawingCallback(emit=rot_ispyb_cb)
76
81
  return [
77
82
  *create_gridscan_callbacks(),
78
- *create_rotation_callbacks(),
83
+ rot_nexus_cb,
84
+ snapshot_cb,
79
85
  LogUidTaggingCallback(),
80
86
  RobotLoadISPyBCallback(),
81
87
  SampleHandlingCallback(),
@@ -87,14 +93,16 @@ def setup_logging(dev_mode: bool):
87
93
  (ISPYB_ZOCALO_CALLBACK_LOGGER, "hyperion_ispyb_callback.log"),
88
94
  (NEXUS_LOGGER, "hyperion_nexus_callback.log"),
89
95
  ]:
96
+ logging_path, debug_logging_path = _get_logging_dirs()
90
97
  if logger.handlers == []:
91
98
  handlers = set_up_all_logging_handlers(
92
99
  logger,
93
- _get_logging_dir(),
100
+ logging_path,
94
101
  filename,
95
102
  dev_mode,
96
- error_log_buffer_lines=ERROR_LOG_BUFFER_LINES,
97
- graylog_port=CONST.GRAYLOG_PORT,
103
+ ERROR_LOG_BUFFER_LINES,
104
+ CONST.GRAYLOG_PORT,
105
+ debug_logging_path,
98
106
  )
99
107
  handlers["graylog_handler"].addFilter(tag_filter)
100
108
  log_info(f"Loggers initialised with dev_mode={dev_mode}")