mx-bluesky 1.4.8__py3-none-any.whl → 1.5.0__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 (67) hide show
  1. mx_bluesky/_version.py +2 -2
  2. mx_bluesky/beamlines/aithre_lasershaping/__init__.py +8 -0
  3. mx_bluesky/beamlines/aithre_lasershaping/beamline_safe.py +36 -0
  4. mx_bluesky/beamlines/aithre_lasershaping/goniometer_controls.py +43 -0
  5. mx_bluesky/beamlines/i24/serial/__init__.py +4 -2
  6. mx_bluesky/beamlines/i24/serial/blueapi_config.yaml +6 -1
  7. mx_bluesky/beamlines/i24/serial/dcid.py +5 -5
  8. mx_bluesky/beamlines/i24/serial/extruder/EX-gui-edm/DetStage.edl +2 -2
  9. mx_bluesky/beamlines/i24/serial/extruder/EX-gui-edm/DiamondExtruder-I24-py3v1.edl +9 -9
  10. mx_bluesky/beamlines/i24/serial/extruder/i24ssx_Extruder_Collect_py3v2.py +18 -3
  11. mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/DetStage.edl +2 -2
  12. mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/DiamondChipI24-py3v1.edl +14 -14
  13. mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/pumpprobe-py3v1.edl +2 -2
  14. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Collect_py3v1.py +55 -4
  15. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Manager_py3v1.py +9 -2
  16. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_beamline.py +0 -8
  17. mx_bluesky/beamlines/i24/serial/web_gui_plans/general_plans.py +77 -8
  18. mx_bluesky/common/device_setup_plans/manipulate_sample.py +9 -14
  19. mx_bluesky/common/device_setup_plans/utils.py +49 -0
  20. mx_bluesky/common/{plans → experiment_plans}/common_flyscan_xray_centre_plan.py +11 -19
  21. mx_bluesky/{hyperion/experiment_plans/grid_detect_then_xray_centre_plan.py → common/experiment_plans/common_grid_detect_then_xray_centre_plan.py} +108 -126
  22. mx_bluesky/common/{plans → experiment_plans}/inner_plans/do_fgs.py +1 -1
  23. mx_bluesky/common/experiment_plans/oav_grid_detection_plan.py +5 -13
  24. mx_bluesky/{hyperion → common}/experiment_plans/oav_snapshot_plan.py +8 -2
  25. mx_bluesky/common/external_interaction/nexus/nexus_utils.py +2 -2
  26. mx_bluesky/common/parameters/components.py +1 -1
  27. mx_bluesky/common/parameters/device_composites.py +65 -0
  28. mx_bluesky/common/utils/__init__.py +0 -0
  29. mx_bluesky/common/utils/log.py +12 -11
  30. mx_bluesky/hyperion/__main__.py +6 -11
  31. mx_bluesky/hyperion/baton_handler.py +8 -3
  32. mx_bluesky/hyperion/device_setup_plans/smargon.py +2 -7
  33. mx_bluesky/hyperion/device_setup_plans/utils.py +0 -47
  34. mx_bluesky/hyperion/experiment_plans/__init__.py +5 -5
  35. mx_bluesky/hyperion/experiment_plans/experiment_registry.py +6 -7
  36. mx_bluesky/hyperion/experiment_plans/hyperion_flyscan_xray_centre_plan.py +40 -41
  37. mx_bluesky/hyperion/experiment_plans/hyperion_grid_detect_then_xray_centre_plan.py +60 -0
  38. mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py +40 -10
  39. mx_bluesky/hyperion/experiment_plans/pin_centre_then_xray_centre_plan.py +26 -15
  40. mx_bluesky/hyperion/experiment_plans/robot_load_and_change_energy.py +2 -11
  41. mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +8 -6
  42. mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +33 -36
  43. mx_bluesky/hyperion/external_interaction/agamemnon.py +68 -62
  44. mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +1 -1
  45. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py +3 -3
  46. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_mapping.py +6 -3
  47. mx_bluesky/hyperion/external_interaction/callbacks/rotation/nexus_callback.py +2 -2
  48. mx_bluesky/hyperion/external_interaction/config_server.py +4 -1
  49. mx_bluesky/hyperion/parameters/cli.py +3 -10
  50. mx_bluesky/hyperion/parameters/constants.py +1 -1
  51. mx_bluesky/hyperion/parameters/device_composites.py +5 -27
  52. mx_bluesky/hyperion/parameters/load_centre_collect.py +4 -4
  53. mx_bluesky/hyperion/parameters/rotation.py +9 -8
  54. mx_bluesky/hyperion/utils/context.py +5 -2
  55. mx_bluesky/hyperion/utils/validation.py +11 -18
  56. {mx_bluesky-1.4.8.dist-info → mx_bluesky-1.5.0.dist-info}/METADATA +5 -5
  57. {mx_bluesky-1.4.8.dist-info → mx_bluesky-1.5.0.dist-info}/RECORD +65 -62
  58. {mx_bluesky-1.4.8.dist-info → mx_bluesky-1.5.0.dist-info}/WHEEL +1 -1
  59. mx_bluesky/common/device_setup_plans/check_beamstop.py +0 -27
  60. mx_bluesky/common/external_interaction/test_config_server.py +0 -38
  61. /mx_bluesky/common/{plans → experiment_plans}/__init__.py +0 -0
  62. /mx_bluesky/common/{plans → experiment_plans}/inner_plans/__init__ .py +0 -0
  63. /mx_bluesky/common/{plans → experiment_plans}/read_hardware.py +0 -0
  64. /mx_bluesky/common/{plans → experiment_plans}/write_sample_status.py +0 -0
  65. {mx_bluesky-1.4.8.dist-info → mx_bluesky-1.5.0.dist-info}/entry_points.txt +0 -0
  66. {mx_bluesky-1.4.8.dist-info → mx_bluesky-1.5.0.dist-info}/licenses/LICENSE +0 -0
  67. {mx_bluesky-1.4.8.dist-info → mx_bluesky-1.5.0.dist-info}/top_level.txt +0 -0
@@ -19,7 +19,7 @@ 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
21
21
  from dodal.devices.s4_slit_gaps import S4SlitGaps
22
- from dodal.devices.smargon import Smargon
22
+ from dodal.devices.smargon import CombinedMove, Smargon
23
23
  from dodal.devices.synchrotron import Synchrotron
24
24
  from dodal.devices.undulator import Undulator
25
25
  from dodal.devices.xbpm_feedback import XBPMFeedback
@@ -32,16 +32,22 @@ from dodal.plans.preprocessors.verify_undulator_gap import (
32
32
 
33
33
  from mx_bluesky.common.device_setup_plans.manipulate_sample import (
34
34
  cleanup_sample_environment,
35
- move_phi_chi_omega,
36
- move_x_y_z,
37
35
  setup_sample_environment,
38
36
  )
39
- from mx_bluesky.common.parameters.components import WithSnapshot
40
- from mx_bluesky.common.plans.read_hardware import (
37
+ from mx_bluesky.common.device_setup_plans.utils import (
38
+ start_preparing_data_collection_then_do_plan,
39
+ )
40
+ from mx_bluesky.common.experiment_plans.oav_snapshot_plan import (
41
+ OavSnapshotComposite,
42
+ oav_snapshot_plan,
43
+ setup_beamline_for_OAV,
44
+ )
45
+ from mx_bluesky.common.experiment_plans.read_hardware import (
41
46
  read_hardware_for_zocalo,
42
47
  standard_read_hardware_during_collection,
43
48
  standard_read_hardware_pre_collection,
44
49
  )
50
+ from mx_bluesky.common.parameters.components import WithSnapshot
45
51
  from mx_bluesky.common.preprocessors.preprocessors import (
46
52
  transmission_and_xbpm_feedback_for_collection_decorator,
47
53
  )
@@ -52,18 +58,10 @@ from mx_bluesky.hyperion.device_setup_plans.setup_zebra import (
52
58
  setup_zebra_for_rotation,
53
59
  tidy_up_zebra_after_rotation_scan,
54
60
  )
55
- from mx_bluesky.hyperion.device_setup_plans.utils import (
56
- start_preparing_data_collection_then_do_plan,
57
- )
58
- from mx_bluesky.hyperion.experiment_plans.oav_snapshot_plan import (
59
- OavSnapshotComposite,
60
- oav_snapshot_plan,
61
- setup_beamline_for_OAV,
62
- )
63
61
  from mx_bluesky.hyperion.parameters.constants import CONST
64
62
  from mx_bluesky.hyperion.parameters.rotation import (
65
- MultiRotationScan,
66
63
  RotationScan,
64
+ SingleRotationScan,
67
65
  )
68
66
 
69
67
 
@@ -118,7 +116,7 @@ class RotationMotionProfile:
118
116
 
119
117
 
120
118
  def calculate_motion_profile(
121
- params: RotationScan,
119
+ params: SingleRotationScan,
122
120
  motor_time_to_speed_s: float,
123
121
  max_velocity_deg_s: float,
124
122
  ) -> RotationMotionProfile:
@@ -212,7 +210,7 @@ def calculate_motion_profile(
212
210
 
213
211
  def rotation_scan_plan(
214
212
  composite: RotationScanComposite,
215
- params: RotationScan,
213
+ params: SingleRotationScan,
216
214
  motion_values: RotationMotionProfile,
217
215
  ):
218
216
  """A stub plan to collect diffraction images from a sample continuously rotating
@@ -319,7 +317,7 @@ def _cleanup_plan(composite: RotationScanComposite, **kwargs):
319
317
 
320
318
  def _move_and_rotation(
321
319
  composite: RotationScanComposite,
322
- params: RotationScan,
320
+ params: SingleRotationScan,
323
321
  oav_params: OAVParameters,
324
322
  ):
325
323
  motor_time_to_speed = yield from bps.rd(composite.smargon.omega.acceleration_time)
@@ -330,19 +328,18 @@ def _move_and_rotation(
330
328
  return num / 1000 if num else num
331
329
 
332
330
  LOGGER.info("moving to position (if specified)")
333
- yield from move_x_y_z(
331
+ yield from bps.abs_set(
334
332
  composite.smargon,
335
- _div_by_1000_if_not_none(params.x_start_um),
336
- _div_by_1000_if_not_none(params.y_start_um),
337
- _div_by_1000_if_not_none(params.z_start_um),
338
- group=CONST.WAIT.MOVE_GONIO_TO_START,
339
- )
340
- yield from move_phi_chi_omega(
341
- composite.smargon,
342
- params.phi_start_deg,
343
- params.chi_start_deg,
333
+ CombinedMove(
334
+ x=_div_by_1000_if_not_none(params.x_start_um),
335
+ y=_div_by_1000_if_not_none(params.y_start_um),
336
+ z=_div_by_1000_if_not_none(params.z_start_um),
337
+ phi=params.phi_start_deg,
338
+ chi=params.chi_start_deg,
339
+ ),
344
340
  group=CONST.WAIT.MOVE_GONIO_TO_START,
345
341
  )
342
+
346
343
  if params.take_snapshots:
347
344
  yield from bps.wait(CONST.WAIT.MOVE_GONIO_TO_START)
348
345
  yield from setup_beamline_for_OAV(
@@ -353,15 +350,15 @@ def _move_and_rotation(
353
350
  yield from bps.prepare(
354
351
  composite.aperture_scatterguard,
355
352
  params.selected_aperture,
356
- group=CONST.WAIT.ROTATION_READY_FOR_DC,
353
+ group=CONST.WAIT.PREPARE_APERTURE,
357
354
  )
358
355
  yield from oav_snapshot_plan(composite, params, oav_params)
359
356
  yield from rotation_scan_plan(composite, params, motion_values)
360
357
 
361
358
 
362
- def multi_rotation_scan(
359
+ def rotation_scan(
363
360
  composite: RotationScanComposite,
364
- parameters: MultiRotationScan,
361
+ parameters: RotationScan,
365
362
  oav_params: OAVParameters | None = None,
366
363
  ) -> MsgGenerator:
367
364
  @bpp.set_run_key_decorator(CONST.PLAN.ROTATION_MULTI_OUTER)
@@ -373,15 +370,15 @@ def multi_rotation_scan(
373
370
  ),
374
371
  }
375
372
  )
376
- def _wrapped_multi_rotation_scan():
377
- yield from multi_rotation_scan_internal(composite, parameters, oav_params)
373
+ def _wrapped_rotation_scan():
374
+ yield from rotation_scan_internal(composite, parameters, oav_params)
378
375
 
379
- yield from _wrapped_multi_rotation_scan()
376
+ yield from _wrapped_rotation_scan()
380
377
 
381
378
 
382
- def multi_rotation_scan_internal(
379
+ def rotation_scan_internal(
383
380
  composite: RotationScanComposite,
384
- parameters: MultiRotationScan,
381
+ parameters: RotationScan,
385
382
  oav_params: OAVParameters | None = None,
386
383
  ) -> MsgGenerator:
387
384
  parameters.features.update_self_from_server()
@@ -419,7 +416,7 @@ def multi_rotation_scan_internal(
419
416
  }
420
417
  )
421
418
  def rotation_scan_core(
422
- params: RotationScan,
419
+ params: SingleRotationScan,
423
420
  ):
424
421
  yield from _move_and_rotation(composite, params, oav_params)
425
422
 
@@ -2,6 +2,7 @@ import dataclasses
2
2
  import json
3
3
  import re
4
4
  import traceback
5
+ from collections.abc import Sequence
5
6
  from os import path
6
7
  from typing import Any, TypeVar
7
8
 
@@ -127,83 +128,88 @@ def get_param_version() -> SemanticVersion:
127
128
  return SemanticVersion.validate_from_str(str(PARAMETER_VERSION))
128
129
 
129
130
 
130
- def populate_parameters_from_agamemnon(agamemnon_params):
131
+ def populate_parameters_from_agamemnon(agamemnon_params) -> Sequence[LoadCentreCollect]:
131
132
  visit, detector_distance = get_withvisit_parameters_from_agamemnon(agamemnon_params)
132
133
  with_energy_params = get_withenergy_parameters_from_agamemnon(agamemnon_params)
133
134
  pin_type = get_pin_type_from_agamemnon_parameters(agamemnon_params)
134
- first_collection = agamemnon_params["collection"][0]
135
+ collections = agamemnon_params["collection"]
135
136
  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
- )
137
+
138
+ return [
139
+ LoadCentreCollect.model_validate(
140
+ {
141
+ "parameter_model_version": get_param_version(),
142
+ "visit": visit,
143
+ "detector_distance_mm": detector_distance,
144
+ "sample_id": agamemnon_params["sample"]["id"],
145
+ "sample_puck": agamemnon_params["sample"]["container"],
146
+ "sample_pin": agamemnon_params["sample"]["position"],
147
+ "select_centres": {
148
+ "name": "TopNByMaxCount",
149
+ "n": pin_type.expected_number_of_crystals,
150
+ },
151
+ "robot_load_then_centre": {
152
+ "storage_directory": str(visit_directory) + "/xraycentring",
153
+ "file_name": file_name,
154
+ "tip_offset_um": pin_type.full_width / 2,
155
+ "grid_width_um": pin_type.full_width,
156
+ "omega_start_deg": 0.0,
157
+ "chi_start_deg": collection["chi"],
158
+ "transmission_frac": 1.0,
159
+ "features": {"use_gpu_results": True},
160
+ **with_energy_params,
161
+ },
162
+ "multi_rotation_scan": {
163
+ "comment": collection["comment"],
164
+ "storage_directory": str(visit_directory),
165
+ "exposure_time_s": collection["exposure_time"],
166
+ "file_name": file_name,
167
+ "transmission_frac": collection["transmission"],
168
+ "rotation_increment_deg": collection["omega_increment"],
169
+ "ispyb_experiment_type": collection["experiment_type"],
170
+ "snapshot_omegas_deg": [0.0, 90.0, 180.0, 270.0],
171
+ "rotation_scans": [
172
+ {
173
+ "scan_width_deg": (
174
+ collection["number_of_images"]
175
+ * collection["omega_increment"]
176
+ ),
177
+ "omega_start_deg": collection["omega_start"],
178
+ "phi_start_deg": collection["phi_start"],
179
+ "chi_start_deg": collection["chi"],
180
+ "rotation_direction": "Positive",
181
+ }
182
+ ],
183
+ **with_energy_params,
184
+ },
185
+ }
186
+ )
187
+ for collection in collections
188
+ ]
184
189
 
185
190
 
186
- def create_parameters_from_agamemnon() -> LoadCentreCollect | None:
191
+ def create_parameters_from_agamemnon() -> Sequence[LoadCentreCollect]:
187
192
  beamline_name = get_beamline_name("i03")
188
193
  agamemnon_params = get_next_instruction(beamline_name)
189
194
  return (
190
- populate_parameters_from_agamemnon(agamemnon_params)
191
- if agamemnon_params
192
- else None
195
+ populate_parameters_from_agamemnon(agamemnon_params) if agamemnon_params else []
193
196
  )
194
197
 
195
198
 
196
- def compare_params(load_centre_collect_params):
199
+ def compare_params(load_centre_collect_params: LoadCentreCollect):
197
200
  try:
198
- parameters = create_parameters_from_agamemnon()
201
+ lcc_requests = create_parameters_from_agamemnon()
199
202
  # 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}"
203
+ if not lcc_requests:
204
+ LOGGER.info("Agamemnon returned no instructions")
205
+ else:
206
+ differences = DeepDiff(
207
+ lcc_requests[0], load_centre_collect_params, math_epsilon=1e-5
206
208
  )
209
+ if differences:
210
+ LOGGER.info(
211
+ f"Different parameters found when directly reading from Hyperion: {differences}"
212
+ )
207
213
  except (ValueError, KeyError):
208
214
  LOGGER.warning(f"Failed to compare parameters: {traceback.format_exc()}")
209
215
  except Exception:
@@ -93,7 +93,7 @@ def setup_logging(dev_mode: bool):
93
93
  (ISPYB_ZOCALO_CALLBACK_LOGGER, "hyperion_ispyb_callback.log"),
94
94
  (NEXUS_LOGGER, "hyperion_nexus_callback.log"),
95
95
  ]:
96
- logging_path, debug_logging_path = _get_logging_dirs()
96
+ logging_path, debug_logging_path = _get_logging_dirs(dev_mode)
97
97
  if logger.handlers == []:
98
98
  handlers = set_up_all_logging_handlers(
99
99
  logger,
@@ -25,7 +25,7 @@ from mx_bluesky.hyperion.external_interaction.callbacks.rotation.ispyb_mapping i
25
25
  populate_data_collection_info_for_rotation,
26
26
  )
27
27
  from mx_bluesky.hyperion.parameters.constants import CONST
28
- from mx_bluesky.hyperion.parameters.rotation import RotationScan
28
+ from mx_bluesky.hyperion.parameters.rotation import SingleRotationScan
29
29
 
30
30
  if TYPE_CHECKING:
31
31
  from event_model.documents import Event, RunStart, RunStop
@@ -62,7 +62,7 @@ class RotationISPyBCallback(BaseISPyBCallback):
62
62
  )
63
63
  hyperion_params = doc.get("mx_bluesky_parameters")
64
64
  assert isinstance(hyperion_params, str)
65
- self.params = RotationScan.model_validate_json(hyperion_params)
65
+ self.params = SingleRotationScan.model_validate_json(hyperion_params)
66
66
  dcgid = (
67
67
  self.ispyb_ids.data_collection_group_id
68
68
  if (self.params.sample_id == self.last_sample_id)
@@ -86,7 +86,7 @@ class RotationISPyBCallback(BaseISPyBCallback):
86
86
  ISPYB_ZOCALO_CALLBACK_LOGGER.info("Beginning ispyb deposition")
87
87
  data_collection_group_info = populate_data_collection_group(self.params)
88
88
  data_collection_info = populate_data_collection_info_for_rotation(
89
- cast(RotationScan, self.params)
89
+ cast(SingleRotationScan, self.params)
90
90
  )
91
91
  data_collection_info = populate_remaining_data_collection_info(
92
92
  self.params.comment,
@@ -1,17 +1,20 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from mx_bluesky.common.external_interaction.ispyb.data_model import DataCollectionInfo
4
- from mx_bluesky.hyperion.parameters.rotation import RotationScan
4
+ from mx_bluesky.hyperion.parameters.rotation import SingleRotationScan
5
5
 
6
6
 
7
- def populate_data_collection_info_for_rotation(params: RotationScan):
7
+ def populate_data_collection_info_for_rotation(params: SingleRotationScan):
8
8
  info = DataCollectionInfo(
9
9
  omega_start=params.omega_start_deg,
10
10
  data_collection_number=params.detector_params.run_number, # type:ignore # the validator always makes this int
11
11
  n_images=params.num_images,
12
12
  axis_range=params.rotation_increment_deg,
13
13
  axis_start=params.omega_start_deg,
14
- axis_end=(params.omega_start_deg + params.scan_width_deg),
14
+ axis_end=(
15
+ params.omega_start_deg
16
+ + params.scan_width_deg * params.rotation_direction.multiplier
17
+ ),
15
18
  kappa_start=params.kappa_start_deg,
16
19
  )
17
20
  return info
@@ -16,7 +16,7 @@ from mx_bluesky.common.external_interaction.nexus.nexus_utils import (
16
16
  from mx_bluesky.common.external_interaction.nexus.write_nexus import NexusWriter
17
17
  from mx_bluesky.common.utils.log import NEXUS_LOGGER
18
18
  from mx_bluesky.hyperion.parameters.constants import CONST
19
- from mx_bluesky.hyperion.parameters.rotation import RotationScan
19
+ from mx_bluesky.hyperion.parameters.rotation import SingleRotationScan
20
20
 
21
21
  if TYPE_CHECKING:
22
22
  from event_model.documents import Event, EventDescriptor, RunStart
@@ -85,7 +85,7 @@ class RotationNexusFileCallback(PlanReactiveCallback):
85
85
  NEXUS_LOGGER.info(
86
86
  f"Nexus writer received start document with experiment parameters {hyperion_params}"
87
87
  )
88
- parameters = RotationScan.model_validate_json(hyperion_params)
88
+ parameters = SingleRotationScan.model_validate_json(hyperion_params)
89
89
  NEXUS_LOGGER.info("Setting up nexus file...")
90
90
 
91
91
  det_size = (
@@ -21,7 +21,9 @@ class HyperionFeatureFlags(FeatureFlags):
21
21
  set_stub_offsets: If True then set the stub offsets after moving to the crystal (ignored for
22
22
  multi-centre)
23
23
  omega_flip: If True then invert the smargon omega motor rotation commands with respect to
24
- the hyperion request.
24
+ the hyperion request. See "Hyperion Coordinate Systems" in the documentation.
25
+ alternate_rotation_direction: If True then the for multi-sample pins the rotation direction of
26
+ successive rotation scans is alternated between positive and negative.
25
27
  """
26
28
 
27
29
  @staticmethod
@@ -41,3 +43,4 @@ class HyperionFeatureFlags(FeatureFlags):
41
43
  use_gpu_results: bool = CONST.I03.USE_GPU_RESULTS
42
44
  set_stub_offsets: bool = CONST.I03.SET_STUB_OFFSETS
43
45
  omega_flip: bool = CONST.I03.OMEGA_FLIP
46
+ alternate_rotation_direction: bool = CONST.I03.ALTERNATE_ROTATION_DIRECTION
@@ -8,7 +8,6 @@ from mx_bluesky._version import version
8
8
  @dataclass
9
9
  class HyperionArgs:
10
10
  dev_mode: bool = False
11
- verbose_event_logging: bool = False
12
11
 
13
12
 
14
13
  def _add_callback_relevant_args(parser: argparse.ArgumentParser) -> None:
@@ -29,16 +28,11 @@ def parse_callback_dev_mode_arg() -> bool:
29
28
 
30
29
 
31
30
  def parse_cli_args() -> HyperionArgs:
32
- """Parses all arguments relevant to hyperion. Returns an HyperionArgs dataclass with
33
- the fields: (verbose_event_logging: bool,
34
- dev_mode: bool)"""
31
+ """Parses all arguments relevant to hyperion.
32
+ Returns:
33
+ an HyperionArgs dataclass with the fields: (dev_mode: bool)"""
35
34
  parser = argparse.ArgumentParser()
36
35
  _add_callback_relevant_args(parser)
37
- parser.add_argument(
38
- "--verbose-event-logging",
39
- action="store_true",
40
- help="Log all bluesky event documents to graylog",
41
- )
42
36
  parser.add_argument(
43
37
  "--version",
44
38
  help="Print hyperion version string",
@@ -47,6 +41,5 @@ def parse_cli_args() -> HyperionArgs:
47
41
  )
48
42
  args = parser.parse_args()
49
43
  return HyperionArgs(
50
- verbose_event_logging=args.verbose_event_logging or False,
51
44
  dev_mode=args.dev or False,
52
45
  )
@@ -19,7 +19,6 @@ TEST_MODE = os.environ.get("HYPERION_TEST_MODE")
19
19
 
20
20
  @dataclass(frozen=True)
21
21
  class I03Constants:
22
- BASE_DATA_DIR = "/tmp/dls/i03/data/" if TEST_MODE else "/dls/i03/data/"
23
22
  BEAMLINE = "BL03S" if TEST_MODE else "BL03I"
24
23
  DETECTOR = EIGER2_X_16M_SIZE
25
24
  INSERTION_PREFIX = "SR03S" if TEST_MODE else "SR03I"
@@ -28,6 +27,7 @@ class I03Constants:
28
27
  USE_PANDA_FOR_GRIDSCAN = False
29
28
  SET_STUB_OFFSETS = False
30
29
  OMEGA_FLIP = True
30
+ ALTERNATE_ROTATION_DIRECTION = True
31
31
 
32
32
  # Turns on GPU processing for zocalo and logs a comparison between GPU and CPU-
33
33
  # processed results.
@@ -7,19 +7,14 @@ from dodal.devices.aperturescatterguard import (
7
7
  from dodal.devices.attenuator.attenuator import BinaryFilterAttenuator
8
8
  from dodal.devices.backlight import Backlight
9
9
  from dodal.devices.common_dcm import BaseDCM
10
- from dodal.devices.detector.detector_motion import DetectorMotion
11
10
  from dodal.devices.eiger import EigerDetector
12
11
  from dodal.devices.fast_grid_scan import (
13
12
  PandAFastGridScan,
14
13
  ZebraFastGridScan,
15
14
  )
16
15
  from dodal.devices.flux import Flux
17
- from dodal.devices.i03 import Beamstop
18
- from dodal.devices.oav.oav_detector import OAV
19
- from dodal.devices.oav.pin_image_recognition import PinTipDetection
20
16
  from dodal.devices.robot import BartRobot
21
17
  from dodal.devices.s4_slit_gaps import S4SlitGaps
22
- from dodal.devices.smargon import Smargon
23
18
  from dodal.devices.synchrotron import Synchrotron
24
19
  from dodal.devices.undulator import Undulator
25
20
  from dodal.devices.xbpm_feedback import XBPMFeedback
@@ -28,9 +23,12 @@ from dodal.devices.zebra.zebra_controlled_shutter import ZebraShutter
28
23
  from dodal.devices.zocalo import ZocaloResults
29
24
  from ophyd_async.fastcs.panda import HDFPanda
30
25
 
31
- from mx_bluesky.common.plans.common_flyscan_xray_centre_plan import (
26
+ from mx_bluesky.common.experiment_plans.common_flyscan_xray_centre_plan import (
32
27
  FlyScanEssentialDevices,
33
28
  )
29
+ from mx_bluesky.common.parameters.device_composites import (
30
+ GridDetectThenXRayCentreComposite,
31
+ )
34
32
 
35
33
 
36
34
  @pydantic.dataclasses.dataclass(config={"arbitrary_types_allowed": True})
@@ -57,28 +55,8 @@ class HyperionFlyScanXRayCentreComposite(FlyScanEssentialDevices):
57
55
 
58
56
 
59
57
  @pydantic.dataclasses.dataclass(config={"arbitrary_types_allowed": True})
60
- class GridDetectThenXRayCentreComposite:
58
+ class HyperionGridDetectThenXRayCentreComposite(GridDetectThenXRayCentreComposite):
61
59
  """All devices which are directly or indirectly required by this plan"""
62
60
 
63
- aperture_scatterguard: ApertureScatterguard
64
- attenuator: BinaryFilterAttenuator
65
- backlight: Backlight
66
- beamstop: Beamstop
67
- dcm: BaseDCM
68
- detector_motion: DetectorMotion
69
- eiger: EigerDetector
70
- zebra_fast_grid_scan: ZebraFastGridScan
71
- flux: Flux
72
- oav: OAV
73
- pin_tip_detection: PinTipDetection
74
- smargon: Smargon
75
- synchrotron: Synchrotron
76
- s4_slit_gaps: S4SlitGaps
77
- undulator: Undulator
78
- xbpm_feedback: XBPMFeedback
79
- zebra: Zebra
80
- zocalo: ZocaloResults
81
61
  panda: HDFPanda
82
62
  panda_fast_grid_scan: PandAFastGridScan
83
- robot: BartRobot
84
- sample_shutter: ZebraShutter
@@ -12,7 +12,7 @@ from mx_bluesky.hyperion.parameters.components import WithHyperionUDCFeatures
12
12
  from mx_bluesky.hyperion.parameters.robot_load import (
13
13
  RobotLoadThenCentre,
14
14
  )
15
- from mx_bluesky.hyperion.parameters.rotation import MultiRotationScan
15
+ from mx_bluesky.hyperion.parameters.rotation import RotationScan
16
16
 
17
17
  T = TypeVar("T", bound=BaseModel)
18
18
 
@@ -34,7 +34,7 @@ class LoadCentreCollect(
34
34
  pin-tip centre and rotation scan operations."""
35
35
 
36
36
  robot_load_then_centre: RobotLoadThenCentre
37
- multi_rotation_scan: MultiRotationScan
37
+ multi_rotation_scan: RotationScan
38
38
 
39
39
  @model_validator(mode="before")
40
40
  @classmethod
@@ -42,7 +42,7 @@ class LoadCentreCollect(
42
42
  allowed_keys = (
43
43
  LoadCentreCollect.model_fields.keys()
44
44
  | RobotLoadThenCentre.model_fields.keys()
45
- | MultiRotationScan.model_fields.keys()
45
+ | RotationScan.model_fields.keys()
46
46
  )
47
47
 
48
48
  disallowed_keys = values.keys() - allowed_keys
@@ -74,7 +74,7 @@ class LoadCentreCollect(
74
74
  values, values["robot_load_then_centre"], RobotLoadThenCentre
75
75
  )
76
76
  new_multi_rotation_scan_params = construct_from_values(
77
- values, values["multi_rotation_scan"], MultiRotationScan
77
+ values, values["multi_rotation_scan"], RotationScan
78
78
  )
79
79
  values["multi_rotation_scan"] = new_multi_rotation_scan_params
80
80
  values["robot_load_then_centre"] = new_robot_load_then_centre_params
@@ -42,7 +42,8 @@ class RotationScanPerSweep(OptionalGonioAngleStarts, OptionalXyzStarts):
42
42
  omega_start_deg: The initial angle of the rotation in degrees (default 0)
43
43
  scan_width_deg: The sweep of the rotation in degrees, this must be positive (default 360)
44
44
  rotation_direction: Indicates the direction of rotation, if RotationDirection.POSITIVE
45
- the final angle is obtained by adding scan_width_deg, otherwise by subtraction (default NEGATIVE)
45
+ the final angle is obtained by adding scan_width_deg, otherwise by subtraction (default NEGATIVE).
46
+ See "Hyperion Coordinate Systems" in the documentation.
46
47
  nexus_vds_start_img: The frame number of the first frame captured during the rotation
47
48
  """
48
49
 
@@ -104,7 +105,7 @@ class RotationExperiment(DiffractionExperimentWithSample, WithHyperionUDCFeature
104
105
  return aperture_position
105
106
 
106
107
 
107
- class RotationScan(WithScan, RotationScanPerSweep, RotationExperiment):
108
+ class SingleRotationScan(WithScan, RotationScanPerSweep, RotationExperiment):
108
109
  @property
109
110
  def detector_params(self):
110
111
  return self._detector_params(self.omega_start_deg)
@@ -129,12 +130,12 @@ class RotationScan(WithScan, RotationScanPerSweep, RotationExperiment):
129
130
  return int(self.scan_width_deg / self.rotation_increment_deg)
130
131
 
131
132
 
132
- class MultiRotationScan(RotationExperiment, SplitScan):
133
+ class RotationScan(RotationExperiment, SplitScan):
133
134
  rotation_scans: Annotated[list[RotationScanPerSweep], Len(min_length=1)]
134
135
 
135
- def _single_rotation_scan(self, scan: RotationScanPerSweep) -> RotationScan:
136
+ def _single_rotation_scan(self, scan: RotationScanPerSweep) -> SingleRotationScan:
136
137
  # self has everything from RotationExperiment
137
- allowed_keys = RotationScan.model_fields.keys() # type: ignore # mypy doesn't recognise this as a property...
138
+ allowed_keys = SingleRotationScan.model_fields.keys() # type: ignore # mypy doesn't recognise this as a property...
138
139
  params_dump = self.model_dump()
139
140
  # provided `scan` has everything from RotationScanPerSweep
140
141
  scan_dump = scan.model_dump()
@@ -142,13 +143,13 @@ class MultiRotationScan(RotationExperiment, SplitScan):
142
143
  k: v for k, v in (params_dump | scan_dump).items() if k in allowed_keys
143
144
  }
144
145
  # together they have everything for RotationScan
145
- rotation_scan = RotationScan(**rotation_scan_kv_pairs)
146
+ rotation_scan = SingleRotationScan(**rotation_scan_kv_pairs)
146
147
  return rotation_scan
147
148
 
148
149
  @model_validator(mode="after")
149
150
  @classmethod
150
151
  def correct_start_vds(cls, values: Any) -> Any:
151
- assert isinstance(values, MultiRotationScan)
152
+ assert isinstance(values, RotationScan)
152
153
  start_img = 0.0
153
154
  for scan in values.rotation_scans:
154
155
  scan.nexus_vds_start_img = int(start_img)
@@ -167,7 +168,7 @@ class MultiRotationScan(RotationExperiment, SplitScan):
167
168
  return self
168
169
 
169
170
  @property
170
- def single_rotation_scans(self) -> Iterator[RotationScan]:
171
+ def single_rotation_scans(self) -> Iterator[SingleRotationScan]:
171
172
  for scan in self.rotation_scans:
172
173
  yield self._single_rotation_scan(scan)
173
174
 
@@ -5,11 +5,14 @@ import mx_bluesky.hyperion.experiment_plans as hyperion_plans
5
5
  from mx_bluesky.common.utils.log import LOGGER
6
6
 
7
7
 
8
- def setup_context() -> BlueskyContext:
8
+ def setup_context(dev_mode: bool = False) -> BlueskyContext:
9
9
  context = BlueskyContext()
10
10
  context.with_plan_module(hyperion_plans)
11
11
 
12
- context.with_dodal_module(get_beamline_based_on_environment_variable())
12
+ context.with_dodal_module(
13
+ get_beamline_based_on_environment_variable(),
14
+ mock=dev_mode,
15
+ )
13
16
 
14
17
  LOGGER.info(f"Plans found in context: {context.plan_functions.keys()}")
15
18