mx-bluesky 1.5.15__py3-none-any.whl → 1.6.3__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 (88) hide show
  1. mx_bluesky/Getting started.ipynb +1 -0
  2. mx_bluesky/_version.py +2 -2
  3. mx_bluesky/beamlines/i02_1/parameters/gridscan.py +1 -1
  4. mx_bluesky/beamlines/i04/__init__.py +4 -0
  5. mx_bluesky/beamlines/i04/callbacks/murko_callback.py +18 -0
  6. mx_bluesky/beamlines/i04/experiment_plans/i04_grid_detect_then_xray_centre_plan.py +5 -4
  7. mx_bluesky/beamlines/i04/oav_centering_plans/oav_imaging.py +224 -10
  8. mx_bluesky/beamlines/i04/redis_to_murko_forwarder.py +5 -2
  9. mx_bluesky/beamlines/i04/thawing_plan.py +3 -2
  10. mx_bluesky/beamlines/i24/jungfrau_commissioning/__init__.py +13 -0
  11. mx_bluesky/beamlines/i24/jungfrau_commissioning/callbacks/__init__.py +0 -0
  12. mx_bluesky/beamlines/i24/jungfrau_commissioning/callbacks/metadata_writer.py +86 -0
  13. mx_bluesky/beamlines/i24/jungfrau_commissioning/composites.py +35 -0
  14. mx_bluesky/beamlines/i24/jungfrau_commissioning/experiment_plans/do_darks.py +19 -20
  15. mx_bluesky/beamlines/i24/jungfrau_commissioning/experiment_plans/rotation_scan_plan.py +292 -0
  16. mx_bluesky/beamlines/i24/jungfrau_commissioning/plan_stubs/do_external_acquisition.py +4 -9
  17. mx_bluesky/beamlines/i24/jungfrau_commissioning/plan_stubs/do_internal_acquisition.py +4 -5
  18. mx_bluesky/beamlines/i24/jungfrau_commissioning/plan_stubs/plan_utils.py +15 -19
  19. mx_bluesky/beamlines/i24/parameters/__init__.py +0 -0
  20. mx_bluesky/beamlines/i24/parameters/constants.py +9 -0
  21. mx_bluesky/beamlines/i24/serial/dcid.py +3 -3
  22. mx_bluesky/beamlines/i24/serial/extruder/i24ssx_extruder_collect_py3v2.py +7 -7
  23. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_chip_collect_py3v1.py +7 -7
  24. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_chip_manager_py3v1.py +3 -3
  25. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_moveonclick.py +3 -3
  26. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_beamline.py +5 -5
  27. mx_bluesky/beamlines/i24/serial/web_gui_plans/general_plans.py +7 -7
  28. mx_bluesky/beamlines/i24/serial/web_gui_plans/oav_plans.py +1 -1
  29. mx_bluesky/common/device_setup_plans/robot_load_unload.py +2 -24
  30. mx_bluesky/common/device_setup_plans/setup_zebra_and_shutter.py +5 -2
  31. mx_bluesky/common/experiment_plans/common_flyscan_xray_centre_plan.py +2 -2
  32. mx_bluesky/common/experiment_plans/inner_plans/read_hardware.py +1 -0
  33. mx_bluesky/common/experiment_plans/oav_grid_detection_plan.py +1 -1
  34. mx_bluesky/common/experiment_plans/pin_tip_centring_plan.py +2 -2
  35. mx_bluesky/common/experiment_plans/rotation/__init__.py +0 -0
  36. mx_bluesky/common/experiment_plans/rotation/rotation_utils.py +127 -0
  37. mx_bluesky/common/external_interaction/callbacks/common/ispyb_callback_base.py +13 -2
  38. mx_bluesky/common/external_interaction/callbacks/common/ispyb_mapping.py +0 -2
  39. mx_bluesky/common/external_interaction/ispyb/data_model.py +1 -1
  40. mx_bluesky/common/external_interaction/ispyb/exp_eye_store.py +1 -1
  41. mx_bluesky/common/parameters/components.py +17 -7
  42. mx_bluesky/common/parameters/constants.py +6 -0
  43. mx_bluesky/{hyperion → common}/parameters/rotation.py +10 -8
  44. mx_bluesky/common/preprocessors/preprocessors.py +98 -36
  45. mx_bluesky/hyperion/__main__.py +55 -22
  46. mx_bluesky/hyperion/baton_handler.py +24 -64
  47. mx_bluesky/hyperion/blueapi_config.yaml +17 -0
  48. mx_bluesky/hyperion/blueapi_dev_config.yaml +16 -0
  49. mx_bluesky/hyperion/blueapi_plans/__init__.py +96 -0
  50. mx_bluesky/hyperion/device_setup_plans/dcm_pitch_roll_mirror_adjuster.py +9 -7
  51. mx_bluesky/hyperion/device_setup_plans/setup_panda.py +1 -1
  52. mx_bluesky/hyperion/device_setup_plans/utils.py +1 -1
  53. mx_bluesky/hyperion/experiment_plans/experiment_registry.py +3 -1
  54. mx_bluesky/hyperion/experiment_plans/hyperion_flyscan_xray_centre_plan.py +1 -0
  55. mx_bluesky/hyperion/experiment_plans/hyperion_grid_detect_then_xray_centre_plan.py +2 -2
  56. mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py +3 -1
  57. mx_bluesky/hyperion/experiment_plans/pin_centre_then_xray_centre_plan.py +17 -6
  58. mx_bluesky/hyperion/experiment_plans/robot_load_and_change_energy.py +2 -5
  59. mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +3 -3
  60. mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +14 -128
  61. mx_bluesky/hyperion/experiment_plans/set_energy_plan.py +4 -4
  62. mx_bluesky/hyperion/experiment_plans/udc_default_state.py +19 -6
  63. mx_bluesky/hyperion/external_interaction/agamemnon.py +3 -8
  64. mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +121 -47
  65. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py +3 -1
  66. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_mapping.py +3 -1
  67. mx_bluesky/hyperion/external_interaction/callbacks/rotation/nexus_callback.py +6 -3
  68. mx_bluesky/hyperion/external_interaction/callbacks/stomp/__init__.py +0 -0
  69. mx_bluesky/hyperion/external_interaction/callbacks/stomp/dispatcher.py +33 -0
  70. mx_bluesky/hyperion/in_process_runner.py +132 -0
  71. mx_bluesky/hyperion/parameters/cli.py +43 -4
  72. mx_bluesky/hyperion/parameters/components.py +13 -0
  73. mx_bluesky/hyperion/parameters/constants.py +2 -9
  74. mx_bluesky/hyperion/parameters/device_composites.py +1 -1
  75. mx_bluesky/hyperion/parameters/load_centre_collect.py +3 -1
  76. mx_bluesky/hyperion/plan_runner.py +45 -66
  77. mx_bluesky/hyperion/plan_runner_api.py +3 -4
  78. mx_bluesky/hyperion/supervisor/__init__.py +3 -0
  79. mx_bluesky/hyperion/supervisor/_supervisor.py +116 -0
  80. mx_bluesky/hyperion/supervisor/client_config.yaml +6 -0
  81. mx_bluesky/hyperion/supervisor/supervisor_config.yaml +10 -0
  82. mx_bluesky/hyperion/supervisor/supervisor_dev_config.yaml +9 -0
  83. {mx_bluesky-1.5.15.dist-info → mx_bluesky-1.6.3.dist-info}/METADATA +3 -31
  84. {mx_bluesky-1.5.15.dist-info → mx_bluesky-1.6.3.dist-info}/RECORD +88 -68
  85. {mx_bluesky-1.5.15.dist-info → mx_bluesky-1.6.3.dist-info}/WHEEL +1 -1
  86. {mx_bluesky-1.5.15.dist-info → mx_bluesky-1.6.3.dist-info}/entry_points.txt +0 -0
  87. {mx_bluesky-1.5.15.dist-info → mx_bluesky-1.6.3.dist-info}/licenses/LICENSE +0 -0
  88. {mx_bluesky-1.5.15.dist-info → mx_bluesky-1.6.3.dist-info}/top_level.txt +0 -0
@@ -2,7 +2,7 @@ import bluesky.preprocessors as bpp
2
2
  from bluesky import plan_stubs as bps
3
3
  from bluesky.utils import MsgGenerator
4
4
  from dodal.common import inject
5
- from dodal.devices.i24.commissioning_jungfrau import CommissioningJungfrau
5
+ from dodal.devices.beamlines.i24.commissioning_jungfrau import CommissioningJungfrau
6
6
  from ophyd_async.fastcs.jungfrau import (
7
7
  AcquisitionType,
8
8
  GainMode,
@@ -13,7 +13,6 @@ from pydantic import PositiveInt
13
13
 
14
14
  from mx_bluesky.beamlines.i24.jungfrau_commissioning.plan_stubs.plan_utils import (
15
15
  fly_jungfrau,
16
- override_file_path,
17
16
  )
18
17
  from mx_bluesky.common.utils.log import LOGGER
19
18
 
@@ -25,8 +24,8 @@ def do_pedestal_darks(
25
24
  exp_time_s: float = 0.001,
26
25
  pedestal_frames: PositiveInt = 20,
27
26
  pedestal_loops: PositiveInt = 200,
27
+ filename: str = "pedestal_darks",
28
28
  jungfrau: CommissioningJungfrau = inject("jungfrau"),
29
- path_of_output_file: str | None = None,
30
29
  ) -> MsgGenerator:
31
30
  """Acquire darks in pedestal mode, using dynamic gain mode. This calibrates the offsets
32
31
  for the jungfrau, and must be performed before acquiring real data in dynamic gain mode.
@@ -46,18 +45,19 @@ def do_pedestal_darks(
46
45
  exp_time_s: Length of detector exposure for each frame.
47
46
  pedestal_frames: Number of frames acquired per pedestal loop.
48
47
  pedestal_loops: Number of times to acquire a set of pedestal_frames
48
+ filename: Name of output file
49
49
  jungfrau: Jungfrau device
50
- path_of_output_file: Absolute path of the detector file output, including file name. If None, then use the PathProvider
51
- set during Jungfrau device instantiation
52
50
  """
53
51
 
54
52
  @bpp.set_run_key_decorator(PEDESTAL_DARKS_RUN)
55
- @bpp.run_decorator(md={"subplan_name": PEDESTAL_DARKS_RUN})
53
+ @bpp.run_decorator(
54
+ md={
55
+ "subplan_name": PEDESTAL_DARKS_RUN,
56
+ "detector_file_template": filename,
57
+ }
58
+ )
56
59
  @bpp.stage_decorator([jungfrau])
57
60
  def _do_decorated_plan():
58
- if path_of_output_file:
59
- override_file_path(jungfrau, path_of_output_file)
60
-
61
61
  trigger_info = create_jungfrau_pedestal_triggering_info(
62
62
  exp_time_s, pedestal_frames, pedestal_loops
63
63
  )
@@ -67,12 +67,11 @@ def do_pedestal_darks(
67
67
  yield from bps.mv(
68
68
  jungfrau.drv.acquisition_type,
69
69
  AcquisitionType.PEDESTAL,
70
- jungfrau.drv.gain_mode,
71
- GainMode.DYNAMIC,
72
70
  )
73
71
  yield from fly_jungfrau(
74
72
  jungfrau,
75
73
  trigger_info,
74
+ GainMode.DYNAMIC,
76
75
  wait=True,
77
76
  log_on_percentage_prefix="Jungfrau pedestal dynamic gain mode darks triggers received",
78
77
  )
@@ -84,8 +83,8 @@ def do_non_pedestal_darks(
84
83
  gain_mode: GainMode,
85
84
  exp_time_s: float = 0.001,
86
85
  total_triggers: PositiveInt = 1000,
86
+ filename: str = "darks",
87
87
  jungfrau: CommissioningJungfrau = inject("jungfrau"),
88
- path_of_output_file: str | None = None,
89
88
  ) -> MsgGenerator:
90
89
  """Internally take a set of images at a given gain mode.
91
90
 
@@ -96,26 +95,26 @@ def do_non_pedestal_darks(
96
95
  exp_time_s: Length of detector exposure for each trigger.
97
96
  total_triggers: Total triggers for the dark scan.
98
97
  jungfrau: Jungfrau device
99
- path_of_output_file: Absolute path of the detector file output, including file name. If None, then use the PathProvider
100
- set during Jungfrau device instantiation
98
+ filename: Name of output file
101
99
  """
102
100
 
103
101
  @bpp.set_run_key_decorator(STANDARD_DARKS_RUN)
104
- @bpp.run_decorator(md={"subplan_name": STANDARD_DARKS_RUN})
102
+ @bpp.run_decorator(
103
+ md={
104
+ "subplan_name": STANDARD_DARKS_RUN,
105
+ "detector_file_template": filename,
106
+ }
107
+ )
105
108
  @bpp.stage_decorator([jungfrau])
106
109
  def _do_decorated_plan():
107
- if path_of_output_file:
108
- override_file_path(jungfrau, path_of_output_file)
109
-
110
110
  trigger_info = create_jungfrau_internal_triggering_info(
111
111
  total_triggers, exp_time_s
112
112
  )
113
113
 
114
- yield from bps.mv(jungfrau.drv.gain_mode, gain_mode)
115
-
116
114
  yield from fly_jungfrau(
117
115
  jungfrau,
118
116
  trigger_info,
117
+ gain_mode,
119
118
  wait=True,
120
119
  log_on_percentage_prefix=f"Jungfrau {gain_mode} gain mode darks triggers received",
121
120
  )
@@ -0,0 +1,292 @@
1
+ from functools import partial
2
+
3
+ import bluesky.plan_stubs as bps
4
+ import bluesky.preprocessors as bpp
5
+ from bluesky.preprocessors import run_decorator
6
+ from bluesky.utils import MsgGenerator
7
+ from dodal.devices.beamlines.i24.aperture import AperturePositions
8
+ from dodal.devices.beamlines.i24.beamstop import BeamstopPositions
9
+ from dodal.devices.beamlines.i24.commissioning_jungfrau import CommissioningJungfrau
10
+ from dodal.devices.beamlines.i24.dual_backlight import BacklightPositions
11
+ from dodal.devices.hutch_shutter import ShutterState
12
+ from dodal.devices.zebra.zebra import ArmDemand, I24Axes, Zebra
13
+ from dodal.devices.zebra.zebra_controlled_shutter import ZebraShutter
14
+ from ophyd_async.fastcs.jungfrau import (
15
+ GainMode,
16
+ create_jungfrau_external_triggering_info,
17
+ )
18
+ from pydantic import BaseModel, field_validator
19
+
20
+ from mx_bluesky.beamlines.i24.jungfrau_commissioning.callbacks.metadata_writer import (
21
+ JsonMetadataWriter,
22
+ )
23
+ from mx_bluesky.beamlines.i24.jungfrau_commissioning.composites import (
24
+ RotationScanComposite,
25
+ )
26
+ from mx_bluesky.beamlines.i24.jungfrau_commissioning.plan_stubs.plan_utils import (
27
+ JF_COMPLETE_GROUP,
28
+ fly_jungfrau,
29
+ )
30
+ from mx_bluesky.beamlines.i24.parameters.constants import (
31
+ PlanNameConstants,
32
+ )
33
+ from mx_bluesky.common.device_setup_plans.setup_zebra_and_shutter import (
34
+ setup_zebra_for_rotation,
35
+ tidy_up_zebra_after_rotation_scan,
36
+ )
37
+ from mx_bluesky.common.experiment_plans.inner_plans.read_hardware import (
38
+ read_hardware_plan,
39
+ )
40
+ from mx_bluesky.common.experiment_plans.rotation.rotation_utils import (
41
+ RotationMotionProfile,
42
+ calculate_motion_profile,
43
+ )
44
+ from mx_bluesky.common.parameters.components import PARAMETER_VERSION
45
+ from mx_bluesky.common.parameters.constants import (
46
+ USE_NUMTRACKER,
47
+ PlanGroupCheckpointConstants,
48
+ )
49
+ from mx_bluesky.common.parameters.rotation import (
50
+ SingleRotationScan,
51
+ )
52
+ from mx_bluesky.common.utils.log import LOGGER
53
+
54
+ READING_DUMP_FILENAME = "collection_info.json"
55
+
56
+ # Should be read from config file, see
57
+ # https://github.com/DiamondLightSource/mx-bluesky/issues/1502
58
+ JF_DET_STAGE_Y_POSITION_MM = 730
59
+ DEFAULT_DETECTOR_DISTANCE_MM = 200
60
+
61
+
62
+ class ExternalRotationScanParams(BaseModel):
63
+ transmission_fractions: list[float]
64
+ exposure_time_s: float
65
+ omega_start_deg: float = 0
66
+ rotation_increment_per_image_deg: float = 0.1
67
+ filename: str = "rotations"
68
+ detector_distance_mm: float = DEFAULT_DETECTOR_DISTANCE_MM
69
+ sample_id: int
70
+
71
+ @field_validator("transmission_fractions")
72
+ @classmethod
73
+ def validate_transmission_fractions(cls, values):
74
+ for v in values:
75
+ if not 0 <= v <= 1:
76
+ raise ValueError(
77
+ f"All transmission fractions must be between 0 and 1; got {v}"
78
+ )
79
+ return values
80
+
81
+
82
+ def _get_internal_rotation_params(
83
+ entry_params: ExternalRotationScanParams, transmission: float
84
+ ) -> SingleRotationScan:
85
+ return SingleRotationScan(
86
+ sample_id=entry_params.sample_id,
87
+ visit=USE_NUMTRACKER, # See https://github.com/DiamondLightSource/mx-bluesky/issues/1527
88
+ parameter_model_version=PARAMETER_VERSION,
89
+ file_name=entry_params.filename,
90
+ transmission_frac=transmission,
91
+ exposure_time_s=entry_params.exposure_time_s,
92
+ storage_directory=USE_NUMTRACKER,
93
+ )
94
+
95
+
96
+ class HutchClosedError(Exception): ...
97
+
98
+
99
+ def rotation_scan_plan(
100
+ composite: RotationScanComposite, params: ExternalRotationScanParams
101
+ ) -> MsgGenerator:
102
+ """BlueAPI entry point for i24 JF rotation scans"""
103
+
104
+ for transmission in params.transmission_fractions:
105
+ rotation_params = _get_internal_rotation_params(params, transmission)
106
+ yield from single_rotation_plan(composite, rotation_params)
107
+
108
+
109
+ def set_up_beamline_for_rotation(
110
+ composite: RotationScanComposite,
111
+ det_z_mm: float,
112
+ transmission_frac: float,
113
+ ):
114
+ """Check hutch is open, then, in parallel, move backlight in,
115
+ move aperture in, move beamstop out and move det stages in. Wait for this parallel
116
+ move to finish."""
117
+
118
+ hutch_shutter_state: ShutterState = yield from bps.rd(
119
+ composite.hutch_shutter.status
120
+ )
121
+ LOGGER.info(f"Hutch shutter: {hutch_shutter_state}")
122
+ if hutch_shutter_state != ShutterState.OPEN:
123
+ LOGGER.error(f"Hutch shutter is not open! State is {hutch_shutter_state}")
124
+ raise HutchClosedError(
125
+ f"Hutch shutter is not open! State is {hutch_shutter_state}"
126
+ )
127
+
128
+ LOGGER.info(
129
+ "Making sure aperture and beamstop are in, detector stages are in position, backlight is out, and transmission is set..."
130
+ )
131
+ yield from bps.mv(
132
+ composite.aperture.position,
133
+ AperturePositions.IN,
134
+ composite.beamstop.pos_select,
135
+ BeamstopPositions.DATA_COLLECTION,
136
+ composite.det_stage.y,
137
+ JF_DET_STAGE_Y_POSITION_MM,
138
+ composite.backlight.backlight_position,
139
+ BacklightPositions.OUT,
140
+ composite.det_stage.z,
141
+ det_z_mm,
142
+ composite.attenuator,
143
+ transmission_frac,
144
+ )
145
+
146
+
147
+ def single_rotation_plan(
148
+ composite: RotationScanComposite,
149
+ params: SingleRotationScan,
150
+ ):
151
+ """A stub plan to collect diffraction images from a sample continuously rotating
152
+ about a fixed axis - for now this axis is limited to omega.
153
+ Needs additional setup of the sample environment and a wrapper to clean up."""
154
+
155
+ @bpp.set_run_key_decorator(PlanNameConstants.SINGLE_ROTATION_SCAN)
156
+ @run_decorator()
157
+ def _plan_in_run_decorator():
158
+ if not params.detector_distance_mm:
159
+ LOGGER.info(
160
+ f"Using default detector distance of {DEFAULT_DETECTOR_DISTANCE_MM} mm"
161
+ )
162
+ params.detector_distance_mm = DEFAULT_DETECTOR_DISTANCE_MM
163
+
164
+ yield from set_up_beamline_for_rotation(
165
+ composite, params.detector_distance_mm, params.transmission_frac
166
+ )
167
+
168
+ # This value isn't actually used, see https://github.com/DiamondLightSource/mx-bluesky/issues/1224
169
+ _motor_time_to_speed = 1
170
+ _max_velocity_deg_s = yield from bps.rd(composite.gonio.omega.max_velocity)
171
+
172
+ motion_values = calculate_motion_profile(
173
+ params, _motor_time_to_speed, _max_velocity_deg_s
174
+ )
175
+
176
+ # Callback which intercepts read documents and writes to json file,
177
+ # used for saving device metadata
178
+ metadata_writer = JsonMetadataWriter()
179
+
180
+ @bpp.subs_decorator([metadata_writer])
181
+ @bpp.set_run_key_decorator(PlanNameConstants.ROTATION_MAIN)
182
+ @bpp.run_decorator(
183
+ md={
184
+ "subplan_name": PlanNameConstants.ROTATION_MAIN,
185
+ "scan_points": [params.scan_points],
186
+ "rotation_scan_params": params.model_dump_json(),
187
+ "detector_file_template": params.file_name,
188
+ }
189
+ )
190
+ def _rotation_scan_plan(
191
+ motion_values: RotationMotionProfile,
192
+ composite: RotationScanComposite,
193
+ ):
194
+ _jf_trigger_info = create_jungfrau_external_triggering_info(
195
+ params.num_images, params.detector_params.exposure_time_s
196
+ )
197
+
198
+ axis = composite.gonio.omega
199
+
200
+ # can move to start as fast as possible
201
+ yield from bps.abs_set(
202
+ axis.velocity, motion_values.max_velocity_deg_s, wait=True
203
+ )
204
+ LOGGER.info(f"Moving omega to start value, {motion_values.start_scan_deg=}")
205
+ yield from bps.abs_set(
206
+ axis,
207
+ motion_values.start_motion_deg,
208
+ group=PlanGroupCheckpointConstants.ROTATION_READY_FOR_DC,
209
+ )
210
+
211
+ yield from setup_zebra_for_rotation(
212
+ composite.zebra,
213
+ composite.sample_shutter,
214
+ axis=I24Axes.OMEGA,
215
+ start_angle=motion_values.start_scan_deg,
216
+ scan_width=motion_values.scan_width_deg,
217
+ direction=motion_values.direction,
218
+ shutter_opening_deg=motion_values.shutter_opening_deg,
219
+ shutter_opening_s=motion_values.shutter_time_s,
220
+ )
221
+
222
+ yield from bps.wait(PlanGroupCheckpointConstants.ROTATION_READY_FOR_DC)
223
+
224
+ # Get ready for the actual scan
225
+ yield from bps.abs_set(
226
+ axis.velocity, motion_values.speed_for_rotation_deg_s, wait=True
227
+ )
228
+ yield from bps.abs_set(composite.zebra.pc.arm, ArmDemand.ARM, wait=True)
229
+
230
+ # Should check topup gate here, but not yet implemented,
231
+ # see https://github.com/DiamondLightSource/mx-bluesky/issues/1501
232
+
233
+ # Read hardware after preparing jungfrau so that device metadata output from callback is correct
234
+ # Whilst metadata is being written in bluesky we need to access the private writer here
235
+ read_hardware_partial = partial(
236
+ read_hardware_plan,
237
+ [
238
+ composite.dcm.energy_in_keV,
239
+ composite.dcm.wavelength_in_a,
240
+ composite.det_stage.z,
241
+ composite.jungfrau._writer.file_path, # noqa: SLF001 N
242
+ ],
243
+ PlanNameConstants.ROTATION_DEVICE_READ,
244
+ )
245
+
246
+ yield from fly_jungfrau(
247
+ composite.jungfrau,
248
+ _jf_trigger_info,
249
+ GainMode.DYNAMIC,
250
+ wait=False,
251
+ log_on_percentage_prefix="Jungfrau rotation scan triggers received",
252
+ read_hardware_after_prepare_plan=read_hardware_partial,
253
+ )
254
+
255
+ LOGGER.info("Executing rotation scan")
256
+ yield from bps.rel_set(
257
+ axis,
258
+ motion_values.distance_to_move_deg,
259
+ wait=False,
260
+ group=JF_COMPLETE_GROUP,
261
+ )
262
+
263
+ LOGGER.info(
264
+ "Waiting for omega to finish moving and for Jungfrau to receive correct number of triggers"
265
+ )
266
+ yield from bps.wait(group=JF_COMPLETE_GROUP)
267
+
268
+ yield from bpp.finalize_wrapper(
269
+ _rotation_scan_plan(motion_values, composite),
270
+ final_plan=partial(
271
+ _cleanup_plan,
272
+ composite.zebra,
273
+ composite.jungfrau,
274
+ composite.sample_shutter,
275
+ ),
276
+ )
277
+
278
+ yield from _plan_in_run_decorator()
279
+
280
+
281
+ def _cleanup_plan(
282
+ zebra: Zebra,
283
+ jf: CommissioningJungfrau,
284
+ zebra_shutter: ZebraShutter,
285
+ group="rotation cleanup",
286
+ ):
287
+ LOGGER.info("Tidying up Zebra and Jungfrau...")
288
+ yield from bps.unstage(jf, group=group)
289
+ yield from tidy_up_zebra_after_rotation_scan(
290
+ zebra, zebra_shutter, group=group, wait=False
291
+ )
292
+ yield from bps.wait(group=group)
@@ -1,24 +1,24 @@
1
1
  from bluesky.utils import MsgGenerator
2
2
  from dodal.common import inject
3
- from dodal.devices.i24.commissioning_jungfrau import CommissioningJungfrau
3
+ from dodal.devices.beamlines.i24.commissioning_jungfrau import CommissioningJungfrau
4
4
  from ophyd_async.core import (
5
5
  WatchableAsyncStatus,
6
6
  )
7
7
  from ophyd_async.fastcs.jungfrau import (
8
+ GainMode,
8
9
  create_jungfrau_external_triggering_info,
9
10
  )
10
11
  from pydantic import PositiveInt
11
12
 
12
13
  from mx_bluesky.beamlines.i24.jungfrau_commissioning.plan_stubs.plan_utils import (
13
14
  fly_jungfrau,
14
- override_file_path,
15
15
  )
16
16
 
17
17
 
18
18
  def do_external_acquisition(
19
19
  exp_time_s: float,
20
+ gain_mode: GainMode,
20
21
  total_triggers: PositiveInt = 1,
21
- output_file_path: str | None = None,
22
22
  wait: bool = False,
23
23
  jungfrau: CommissioningJungfrau = inject("commissioning_jungfrau"),
24
24
  ) -> MsgGenerator[WatchableAsyncStatus]:
@@ -32,14 +32,9 @@ def do_external_acquisition(
32
32
  exp_time_s: Length of detector exposure for each frame.
33
33
  total_triggers: Number of external triggers received before acquisition is marked as complete.
34
34
  jungfrau: Jungfrau device
35
- output_file_name: Absolute path of the detector file output, including file name. If None, then use the PathProvider
36
- set during jungfrau device instantiation
37
35
  wait: Optionally block until data collection is complete.
38
36
  """
39
37
 
40
- if output_file_path:
41
- override_file_path(jungfrau, output_file_path)
42
-
43
38
  trigger_info = create_jungfrau_external_triggering_info(total_triggers, exp_time_s)
44
- status = yield from fly_jungfrau(jungfrau, trigger_info, wait=wait)
39
+ status = yield from fly_jungfrau(jungfrau, trigger_info, gain_mode, wait=wait)
45
40
  return status
@@ -5,18 +5,19 @@ from ophyd_async.core import (
5
5
  WatchableAsyncStatus,
6
6
  )
7
7
  from ophyd_async.fastcs.jungfrau import (
8
+ GainMode,
8
9
  create_jungfrau_internal_triggering_info,
9
10
  )
10
11
  from pydantic import PositiveInt
11
12
 
12
13
  from mx_bluesky.beamlines.i24.jungfrau_commissioning.plan_stubs.plan_utils import (
13
14
  fly_jungfrau,
14
- override_file_path,
15
15
  )
16
16
 
17
17
 
18
18
  def do_internal_acquisition(
19
19
  exp_time_s: float,
20
+ gain_mode: GainMode,
20
21
  total_frames: PositiveInt = 1,
21
22
  jungfrau: CommissioningJungfrau = inject("jungfrau"),
22
23
  path_of_output_file: str | None = None,
@@ -31,6 +32,7 @@ def do_internal_acquisition(
31
32
 
32
33
  Args:
33
34
  exp_time_s: Length of detector exposure for each frame.
35
+ gain_mode: Which gain mode to put the Jungfrau into before starting the acquisition.
34
36
  total_frames: Number of frames taken after being internally triggered.
35
37
  period_between_frames_s: Time between each detector frame, including deadtime. Not needed if frames_per_triggers is 1.
36
38
  jungfrau: Jungfrau device
@@ -39,9 +41,6 @@ def do_internal_acquisition(
39
41
  wait: Optionally block until data collection is complete.
40
42
  """
41
43
 
42
- if path_of_output_file:
43
- override_file_path(jungfrau, path_of_output_file)
44
-
45
44
  trigger_info = create_jungfrau_internal_triggering_info(total_frames, exp_time_s)
46
- status = yield from fly_jungfrau(jungfrau, trigger_info, wait=wait)
45
+ status = yield from fly_jungfrau(jungfrau, trigger_info, gain_mode, wait=wait)
47
46
  return status
@@ -1,16 +1,15 @@
1
- from pathlib import PurePath
1
+ from collections.abc import Callable
2
2
  from typing import cast
3
3
 
4
4
  import bluesky.plan_stubs as bps
5
5
  from bluesky.utils import MsgGenerator
6
6
  from dodal.common.watcher_utils import log_on_percentage_complete
7
- from dodal.devices.i24.commissioning_jungfrau import CommissioningJungfrau
7
+ from dodal.devices.beamlines.i24.commissioning_jungfrau import CommissioningJungfrau
8
8
  from ophyd_async.core import (
9
- AutoIncrementingPathProvider,
10
- StaticFilenameProvider,
11
9
  TriggerInfo,
12
10
  WatchableAsyncStatus,
13
11
  )
12
+ from ophyd_async.fastcs.jungfrau import GainMode
14
13
 
15
14
  from mx_bluesky.common.utils.log import LOGGER
16
15
 
@@ -20,8 +19,11 @@ JF_COMPLETE_GROUP = "JF complete"
20
19
  def fly_jungfrau(
21
20
  jungfrau: CommissioningJungfrau,
22
21
  trigger_info: TriggerInfo,
22
+ gain_mode: GainMode,
23
23
  wait: bool = False,
24
24
  log_on_percentage_prefix="Jungfrau data collection triggers received",
25
+ read_hardware_after_prepare_plan: Callable[..., MsgGenerator]
26
+ | None = None, # Param needs refactor: https://github.com/DiamondLightSource/mx-bluesky/issues/819
25
27
  ) -> MsgGenerator[WatchableAsyncStatus]:
26
28
  """Stage, prepare, and kickoff Jungfrau with a configured TriggerInfo. Optionally wait
27
29
  for completion.
@@ -32,13 +34,21 @@ def fly_jungfrau(
32
34
  Args:
33
35
  jungfrau: Jungfrau device.
34
36
  trigger_info: TriggerInfo which should be acquired using jungfrau util functions.
37
+ gain_mode: Which gain mode to put the Jungfrau into before starting the acquisition.
35
38
  wait: Optionally block until data collection is complete.
36
39
  log_on_percentage_prefix: String that will be appended to the "percentage completion" logging message.
40
+ read_hardware_after_prepare_plan: Optionally add a plan which will be ran in between preparing the jungfrau and starting
41
+ acquisition. This is useful for reading devices after they have been prepared, especially since the file writing path
42
+ is calculated during prepare.
37
43
  """
38
44
 
45
+ LOGGER.info(f"Setting Jungfrau to gain mode {gain_mode}")
46
+ yield from bps.mv(jungfrau.drv.gain_mode, gain_mode)
39
47
  LOGGER.info("Preparing detector...")
40
48
  yield from bps.prepare(jungfrau, trigger_info, wait=True)
41
- LOGGER.info("Detector prepared. Starting acquisition")
49
+ LOGGER.info("Detector prepared")
50
+ if read_hardware_after_prepare_plan:
51
+ yield from read_hardware_after_prepare_plan()
42
52
  yield from bps.kickoff(jungfrau, wait=True)
43
53
  LOGGER.info("Waiting for acquisition to complete...")
44
54
  status = yield from bps.complete(jungfrau, group=JF_COMPLETE_GROUP)
@@ -50,17 +60,3 @@ def fly_jungfrau(
50
60
  if wait:
51
61
  yield from bps.wait(JF_COMPLETE_GROUP)
52
62
  return status
53
-
54
-
55
- def override_file_path(jungfrau: CommissioningJungfrau, path_of_output_file: str):
56
- """While we should generally use device instantiation to set the path,
57
- during commissioning, it is useful to be able to explicitly set the filename
58
- and path.
59
-
60
- This function must be called before the Jungfrau is prepared.
61
- """
62
- _file_path = PurePath(path_of_output_file)
63
- _new_filename_provider = StaticFilenameProvider(_file_path.name)
64
- jungfrau._writer._path_info = AutoIncrementingPathProvider( # noqa: SLF001
65
- _new_filename_provider, _file_path.parent
66
- )
File without changes
@@ -0,0 +1,9 @@
1
+ from pydantic.dataclasses import dataclass
2
+
3
+
4
+ @dataclass(frozen=True)
5
+ class PlanNameConstants:
6
+ ROTATION_DEVICE_READ = "ROTATION DEVICE READ"
7
+ SINGLE_ROTATION_SCAN = "OUTER SINGLE ROTATION SCAN"
8
+ MULTI_ROTATION_SCAN = "OUTER MULTI ROTATION SCAN"
9
+ ROTATION_MAIN = "ROTATION MAIN"
@@ -8,9 +8,9 @@ from functools import lru_cache
8
8
  import bluesky.plan_stubs as bps
9
9
  import requests
10
10
  from bluesky.utils import MsgGenerator
11
- from dodal.devices.i24.beam_center import DetectorBeamCenter
12
- from dodal.devices.i24.dcm import DCM
13
- from dodal.devices.i24.focus_mirrors import FocusMirrorsMode
11
+ from dodal.devices.beamlines.i24.beam_center import DetectorBeamCenter
12
+ from dodal.devices.beamlines.i24.dcm import DCM
13
+ from dodal.devices.beamlines.i24.focus_mirrors import FocusMirrorsMode
14
14
 
15
15
  from mx_bluesky.beamlines.i24.serial.fixed_target.ft_utils import PumpProbeSetting
16
16
  from mx_bluesky.beamlines.i24.serial.log import SSX_LOGGER
@@ -15,13 +15,13 @@ import bluesky.preprocessors as bpp
15
15
  from bluesky.utils import MsgGenerator
16
16
  from dodal.common import inject
17
17
  from dodal.devices.attenuator.attenuator import ReadOnlyAttenuator
18
+ from dodal.devices.beamlines.i24.aperture import Aperture
19
+ from dodal.devices.beamlines.i24.beam_center import DetectorBeamCenter
20
+ from dodal.devices.beamlines.i24.beamstop import Beamstop
21
+ from dodal.devices.beamlines.i24.dcm import DCM
22
+ from dodal.devices.beamlines.i24.dual_backlight import DualBacklight
23
+ from dodal.devices.beamlines.i24.focus_mirrors import FocusMirrorsMode
18
24
  from dodal.devices.hutch_shutter import HutchShutter, ShutterDemand
19
- from dodal.devices.i24.aperture import Aperture
20
- from dodal.devices.i24.beam_center import DetectorBeamCenter
21
- from dodal.devices.i24.beamstop import Beamstop
22
- from dodal.devices.i24.dcm import DCM
23
- from dodal.devices.i24.dual_backlight import DualBacklight
24
- from dodal.devices.i24.focus_mirrors import FocusMirrorsMode
25
25
  from dodal.devices.motors import YZStage
26
26
  from dodal.devices.zebra.zebra import Zebra
27
27
 
@@ -113,7 +113,7 @@ def laser_check(
113
113
  """
114
114
  SSX_LOGGER.debug(f"Laser check: {mode}")
115
115
 
116
- laser_ttl = zebra.mapping.outputs.TTL_PILATUS # Update with dodal changes
116
+ laser_ttl = zebra.mapping.outputs.TTL_JUNGFRAU
117
117
 
118
118
  if mode == "laseron":
119
119
  yield from bps.abs_set(
@@ -11,14 +11,14 @@ import bluesky.preprocessors as bpp
11
11
  from bluesky.utils import MsgGenerator
12
12
  from dodal.common import inject
13
13
  from dodal.devices.attenuator.attenuator import ReadOnlyAttenuator
14
+ from dodal.devices.beamlines.i24.aperture import Aperture
15
+ from dodal.devices.beamlines.i24.beam_center import DetectorBeamCenter
16
+ from dodal.devices.beamlines.i24.beamstop import Beamstop
17
+ from dodal.devices.beamlines.i24.dcm import DCM
18
+ from dodal.devices.beamlines.i24.dual_backlight import DualBacklight
19
+ from dodal.devices.beamlines.i24.focus_mirrors import FocusMirrorsMode
20
+ from dodal.devices.beamlines.i24.pmac import PMAC
14
21
  from dodal.devices.hutch_shutter import HutchShutter, ShutterDemand
15
- from dodal.devices.i24.aperture import Aperture
16
- from dodal.devices.i24.beam_center import DetectorBeamCenter
17
- from dodal.devices.i24.beamstop import Beamstop
18
- from dodal.devices.i24.dcm import DCM
19
- from dodal.devices.i24.dual_backlight import DualBacklight
20
- from dodal.devices.i24.focus_mirrors import FocusMirrorsMode
21
- from dodal.devices.i24.pmac import PMAC
22
22
  from dodal.devices.motors import YZStage
23
23
  from dodal.devices.zebra.zebra import Zebra
24
24
 
@@ -13,9 +13,9 @@ import numpy as np
13
13
  from bluesky.utils import MsgGenerator
14
14
  from dodal.common import inject
15
15
  from dodal.devices.attenuator.attenuator import ReadOnlyAttenuator
16
- from dodal.devices.i24.beamstop import Beamstop, BeamstopPositions
17
- from dodal.devices.i24.dual_backlight import BacklightPositions, DualBacklight
18
- from dodal.devices.i24.pmac import CS_STR, PMAC, EncReset, LaserSettings
16
+ from dodal.devices.beamlines.i24.beamstop import Beamstop, BeamstopPositions
17
+ from dodal.devices.beamlines.i24.dual_backlight import BacklightPositions, DualBacklight
18
+ from dodal.devices.beamlines.i24.pmac import CS_STR, PMAC, EncReset, LaserSettings
19
19
  from dodal.devices.motors import YZStage
20
20
 
21
21
  from mx_bluesky.beamlines.i24.serial.fixed_target.ft_utils import (
@@ -9,7 +9,7 @@ import bluesky.plan_stubs as bps
9
9
  import cv2 as cv
10
10
  from bluesky.run_engine import RunEngine
11
11
  from dodal.beamlines import i24
12
- from dodal.devices.i24.pmac import PMAC
12
+ from dodal.devices.beamlines.i24.pmac import PMAC
13
13
  from dodal.devices.oav.oav_detector import OAV
14
14
 
15
15
  from mx_bluesky.beamlines.i24.serial.fixed_target import (
@@ -222,6 +222,6 @@ def start_viewer(oav: OAV, pmac: PMAC, run_engine: RunEngine, oav1: str = OAV1_C
222
222
  if __name__ == "__main__":
223
223
  run_engine = RunEngine(call_returns_result=True)
224
224
  # Get devices out of dodal
225
- oav: OAV = i24.oav(connect_immediately=True)
226
- pmac: PMAC = i24.pmac(connect_immediately=True)
225
+ oav: OAV = i24.oav.build(connect_immediately=True)
226
+ pmac: PMAC = i24.pmac.build(connect_immediately=True)
227
227
  start_viewer(oav, pmac, run_engine)