mx-bluesky 1.5.9__py3-none-any.whl → 1.5.11__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 (87) hide show
  1. mx_bluesky/_version.py +2 -2
  2. mx_bluesky/beamlines/aithre_lasershaping/goniometer_controls.py +2 -2
  3. mx_bluesky/beamlines/i02_1/parameters/gridscan.py +1 -1
  4. mx_bluesky/beamlines/i04/experiment_plans/i04_grid_detect_then_xray_centre_plan.py +9 -9
  5. mx_bluesky/beamlines/i04/thawing_plan.py +9 -9
  6. mx_bluesky/beamlines/i24/jungfrau_commissioning/experiment_plans/do_darks.py +123 -0
  7. mx_bluesky/beamlines/i24/jungfrau_commissioning/plan_stubs/__init__.py +1 -0
  8. mx_bluesky/beamlines/i24/jungfrau_commissioning/{do_external_acquisition.py → plan_stubs/do_external_acquisition.py} +8 -7
  9. mx_bluesky/beamlines/i24/jungfrau_commissioning/{do_internal_acquisition.py → plan_stubs/do_internal_acquisition.py} +4 -3
  10. mx_bluesky/beamlines/i24/jungfrau_commissioning/{plan_utils.py → plan_stubs/plan_utils.py} +21 -28
  11. mx_bluesky/beamlines/i24/serial/__init__.py +7 -5
  12. mx_bluesky/beamlines/i24/serial/dcid.py +3 -3
  13. mx_bluesky/beamlines/i24/serial/extruder/{i24ssx_Extruder_Collect_py3v2.py → i24ssx_extruder_collect_py3v2.py} +65 -35
  14. mx_bluesky/beamlines/i24/serial/fixed_target/{i24ssx_Chip_Collect_py3v1.py → i24ssx_chip_collect_py3v1.py} +5 -5
  15. mx_bluesky/beamlines/i24/serial/fixed_target/{i24ssx_Chip_Manager_py3v1.py → i24ssx_chip_manager_py3v1.py} +46 -46
  16. mx_bluesky/beamlines/i24/serial/fixed_target/{i24ssx_Chip_StartUp_py3v1.py → i24ssx_chip_startup_py3v1.py} +3 -3
  17. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_moveonclick.py +33 -33
  18. mx_bluesky/beamlines/i24/serial/log.py +11 -11
  19. mx_bluesky/beamlines/i24/serial/parameters/fixed_target/cs/cs_maker.json +3 -3
  20. mx_bluesky/beamlines/i24/serial/setup_beamline/ca.py +0 -12
  21. mx_bluesky/beamlines/i24/serial/setup_beamline/pv.py +13 -32
  22. mx_bluesky/beamlines/i24/serial/setup_beamline/pv_abstract.py +5 -5
  23. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_beamline.py +22 -249
  24. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_detector.py +2 -2
  25. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_zebra_plans.py +4 -4
  26. mx_bluesky/beamlines/i24/serial/web_gui_plans/general_plans.py +102 -15
  27. mx_bluesky/beamlines/i24/serial/write_nexus.py +4 -4
  28. mx_bluesky/common/device_setup_plans/robot_load_unload.py +2 -2
  29. mx_bluesky/common/device_setup_plans/setup_oav.py +1 -1
  30. mx_bluesky/common/device_setup_plans/xbpm_feedback.py +4 -4
  31. mx_bluesky/common/experiment_plans/common_flyscan_xray_centre_plan.py +9 -9
  32. mx_bluesky/common/experiment_plans/common_grid_detect_then_xray_centre_plan.py +2 -2
  33. mx_bluesky/common/experiment_plans/inner_plans/do_fgs.py +2 -2
  34. mx_bluesky/common/experiment_plans/inner_plans/read_hardware.py +5 -5
  35. mx_bluesky/common/experiment_plans/inner_plans/udc_default_state.py +22 -1
  36. mx_bluesky/common/experiment_plans/inner_plans/write_sample_status.py +2 -2
  37. mx_bluesky/common/experiment_plans/oav_grid_detection_plan.py +2 -2
  38. mx_bluesky/common/experiment_plans/oav_snapshot_plan.py +1 -1
  39. mx_bluesky/common/external_interaction/callbacks/common/ispyb_callback_base.py +3 -3
  40. mx_bluesky/common/external_interaction/callbacks/common/plan_reactive_callback.py +1 -1
  41. mx_bluesky/common/external_interaction/callbacks/common/zocalo_callback.py +2 -2
  42. mx_bluesky/common/external_interaction/callbacks/sample_handling/sample_handling_callback.py +3 -3
  43. mx_bluesky/common/external_interaction/callbacks/xray_centre/ispyb_callback.py +7 -5
  44. mx_bluesky/common/external_interaction/callbacks/xray_centre/nexus_callback.py +2 -2
  45. mx_bluesky/common/external_interaction/config_server.py +2 -2
  46. mx_bluesky/common/external_interaction/ispyb/exp_eye_store.py +4 -2
  47. mx_bluesky/common/external_interaction/ispyb/ispyb_store.py +0 -1
  48. mx_bluesky/common/external_interaction/nexus/nexus_utils.py +2 -2
  49. mx_bluesky/common/external_interaction/nexus/write_nexus.py +3 -3
  50. mx_bluesky/common/parameters/constants.py +1 -1
  51. mx_bluesky/common/parameters/device_composites.py +2 -2
  52. mx_bluesky/common/parameters/gridscan.py +2 -2
  53. mx_bluesky/common/utils/exceptions.py +9 -7
  54. mx_bluesky/common/utils/log.py +4 -4
  55. mx_bluesky/common/utils/tracing.py +5 -5
  56. mx_bluesky/common/utils/utils.py +8 -8
  57. mx_bluesky/hyperion/__main__.py +5 -5
  58. mx_bluesky/hyperion/baton_handler.py +15 -8
  59. mx_bluesky/hyperion/device_setup_plans/smargon.py +5 -5
  60. mx_bluesky/hyperion/device_setup_plans/utils.py +1 -1
  61. mx_bluesky/hyperion/experiment_plans/experiment_registry.py +1 -1
  62. mx_bluesky/hyperion/experiment_plans/hyperion_flyscan_xray_centre_plan.py +19 -18
  63. mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py +54 -40
  64. mx_bluesky/hyperion/experiment_plans/optimise_attenuation_plan.py +9 -9
  65. mx_bluesky/hyperion/experiment_plans/pin_centre_then_xray_centre_plan.py +2 -2
  66. mx_bluesky/hyperion/experiment_plans/pin_tip_centring_plan.py +5 -5
  67. mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +3 -3
  68. mx_bluesky/hyperion/external_interaction/agamemnon.py +2 -2
  69. mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +2 -2
  70. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py +1 -1
  71. mx_bluesky/hyperion/external_interaction/callbacks/rotation/nexus_callback.py +2 -2
  72. mx_bluesky/hyperion/external_interaction/config_server.py +2 -2
  73. mx_bluesky/hyperion/parameters/constants.py +2 -2
  74. mx_bluesky/hyperion/parameters/device_composites.py +2 -2
  75. mx_bluesky/hyperion/parameters/gridscan.py +4 -4
  76. mx_bluesky/hyperion/parameters/rotation.py +4 -6
  77. mx_bluesky/hyperion/plan_runner.py +6 -6
  78. mx_bluesky/hyperion/runner.py +10 -8
  79. mx_bluesky/hyperion/utils/context.py +6 -1
  80. mx_bluesky/jupyter_example.ipynb +3 -3
  81. {mx_bluesky-1.5.9.dist-info → mx_bluesky-1.5.11.dist-info}/METADATA +7 -6
  82. {mx_bluesky-1.5.9.dist-info → mx_bluesky-1.5.11.dist-info}/RECORD +87 -85
  83. /mx_bluesky/beamlines/i24/jungfrau_commissioning/{__init__.py → experiment_plans/__init__.py} +0 -0
  84. {mx_bluesky-1.5.9.dist-info → mx_bluesky-1.5.11.dist-info}/WHEEL +0 -0
  85. {mx_bluesky-1.5.9.dist-info → mx_bluesky-1.5.11.dist-info}/entry_points.txt +0 -0
  86. {mx_bluesky-1.5.9.dist-info → mx_bluesky-1.5.11.dist-info}/licenses/LICENSE +0 -0
  87. {mx_bluesky-1.5.9.dist-info → mx_bluesky-1.5.11.dist-info}/top_level.txt +0 -0
@@ -7,20 +7,22 @@ from bluesky.preprocessors import contingency_wrapper
7
7
  from bluesky.utils import Msg
8
8
 
9
9
 
10
- class WarningException(Exception):
10
+ class WarningError(
11
+ Exception
12
+ ): # see https://github.com/DiamondLightSource/mx-bluesky/issues/1394 on naming
11
13
  """An exception used when we want to warn GDA of a
12
14
  problem but continue with UDC anyway"""
13
15
 
14
16
  pass
15
17
 
16
18
 
17
- class ISPyBDepositionNotMade(Exception):
19
+ class ISPyBDepositionNotMadeError(Exception):
18
20
  """Raised when the ISPyB or Zocalo callbacks can't access ISPyB deposition numbers."""
19
21
 
20
22
  pass
21
23
 
22
24
 
23
- class SampleException(WarningException):
25
+ class SampleError(WarningError):
24
26
  """An exception which identifies an issue relating to the sample."""
25
27
 
26
28
  def __str__(self):
@@ -36,7 +38,7 @@ class SampleException(WarningException):
36
38
  T = TypeVar("T")
37
39
 
38
40
 
39
- class CrystalNotFoundException(SampleException):
41
+ class CrystalNotFoundError(SampleError):
40
42
  """Raised if grid detection completed normally but no crystal was found."""
41
43
 
42
44
  def __init__(self, *args):
@@ -49,7 +51,7 @@ def catch_exception_and_warn(
49
51
  *args,
50
52
  **kwargs,
51
53
  ) -> Generator[Msg, None, T]:
52
- """A plan wrapper to catch a specific exception and instead raise a WarningException,
54
+ """A plan wrapper to catch a specific exception and instead raise a WarningError,
53
55
  so that UDC is not halted
54
56
 
55
57
  Example usage:
@@ -58,12 +60,12 @@ def catch_exception_and_warn(
58
60
  ...
59
61
  yield from catch_exception_and_warn(ExceptionA, plan_which_can_raise_exception_a, **args, **kwargs)'
60
62
 
61
- This will catch ExceptionA raised by the plan and instead raise a WarningException
63
+ This will catch ExceptionA raised by the plan and instead raise a WarningError
62
64
  """
63
65
 
64
66
  def warn_if_exception_matches(exception: Exception):
65
67
  if isinstance(exception, exception_to_catch):
66
- raise SampleException(str(exception)) from exception
68
+ raise SampleError(str(exception)) from exception
67
69
  yield from null()
68
70
 
69
71
  return (
@@ -10,11 +10,11 @@ from dodal.log import (
10
10
  integrate_bluesky_and_ophyd_logging,
11
11
  set_up_all_logging_handlers,
12
12
  )
13
- from dodal.log import LOGGER as dodal_logger
13
+ from dodal.log import LOGGER as DODAL_LOGGER
14
14
 
15
15
  LOGGER = logging.getLogger("MX-Bluesky")
16
16
  LOGGER.setLevel("DEBUG")
17
- LOGGER.parent = dodal_logger
17
+ LOGGER.parent = DODAL_LOGGER
18
18
 
19
19
  ISPYB_ZOCALO_CALLBACK_LOGGER = logging.getLogger("ISPyB and Zocalo callbacks")
20
20
  ISPYB_ZOCALO_CALLBACK_LOGGER.setLevel(logging.DEBUG)
@@ -68,7 +68,7 @@ def do_default_logging_setup(
68
68
  and bluesky and ophyd-async are optionally included."""
69
69
  logging_path, debug_logging_path = _get_logging_dirs(dev_mode)
70
70
  handlers = set_up_all_logging_handlers(
71
- dodal_logger,
71
+ DODAL_LOGGER,
72
72
  logging_path,
73
73
  file_name,
74
74
  dev_mode,
@@ -78,7 +78,7 @@ def do_default_logging_setup(
78
78
  )
79
79
 
80
80
  if integrate_all_logs:
81
- integrate_bluesky_and_ophyd_logging(dodal_logger)
81
+ integrate_bluesky_and_ophyd_logging(DODAL_LOGGER)
82
82
 
83
83
  handlers["graylog_handler"].addFilter(tag_filter)
84
84
 
@@ -11,18 +11,18 @@ from opentelemetry.sdk.trace.export import BatchSpanProcessor
11
11
  def setup_tracing(service_name: str = "Hyperion"):
12
12
  resource = Resource(attributes={SERVICE_NAME: service_name})
13
13
 
14
- traceProvider = TracerProvider(resource=resource)
14
+ trace_provider = TracerProvider(resource=resource)
15
15
  processor = BatchSpanProcessor(
16
16
  OTLPSpanExporter(endpoint="http://0.0.0.0:4318/v1/traces")
17
17
  )
18
- traceProvider.add_span_processor(processor)
19
- trace.set_tracer_provider(traceProvider)
18
+ trace_provider.add_span_processor(processor)
19
+ trace.set_tracer_provider(trace_provider)
20
20
 
21
21
  reader = PeriodicExportingMetricReader(
22
22
  OTLPMetricExporter(endpoint="http://0.0.0.0:4318/v1/metrics")
23
23
  )
24
- meterProvider = MeterProvider(resource=resource, metric_readers=[reader])
25
- metrics.set_meter_provider(meterProvider)
24
+ meter_provider = MeterProvider(resource=resource, metric_readers=[reader])
25
+ metrics.set_meter_provider(meter_provider)
26
26
 
27
27
 
28
28
  TRACER = trace.get_tracer(__name__)
@@ -4,23 +4,23 @@ from math import asin
4
4
  from scanspec.core import AxesPoints, Axis
5
5
  from scipy.constants import physical_constants
6
6
 
7
- hc_in_eV_and_Angstrom: float = (
7
+ hc_in_ev_and_angstrom: float = (
8
8
  physical_constants["speed of light in vacuum"][0]
9
9
  * physical_constants["Planck constant in eV/Hz"][0]
10
10
  * 1e10 # Angstroms per metre
11
11
  )
12
12
 
13
13
 
14
- def interconvert_eV_Angstrom(wavelength_or_energy: float) -> float:
15
- return hc_in_eV_and_Angstrom / wavelength_or_energy
14
+ def interconvert_ev_angstrom(wavelength_or_energy: float) -> float:
15
+ return hc_in_ev_and_angstrom / wavelength_or_energy
16
16
 
17
17
 
18
- def convert_eV_to_angstrom(hv: float) -> float:
19
- return interconvert_eV_Angstrom(hv)
18
+ def convert_ev_to_angstrom(hv: float) -> float:
19
+ return interconvert_ev_angstrom(hv)
20
20
 
21
21
 
22
- def convert_angstrom_to_eV(wavelength: float) -> float:
23
- return interconvert_eV_Angstrom(wavelength)
22
+ def convert_angstrom_to_ev(wavelength: float) -> float:
23
+ return interconvert_ev_angstrom(wavelength)
24
24
 
25
25
 
26
26
  def number_of_frames_from_scan_spec(scan_points: AxesPoints[Axis]):
@@ -37,6 +37,6 @@ def energy_to_bragg_angle(energy_kev: float, d_a: float) -> float:
37
37
  Returns:
38
38
  The bragg angle in degrees
39
39
  """
40
- wavelength_a = convert_eV_to_angstrom(energy_kev * 1000)
40
+ wavelength_a = convert_ev_to_angstrom(energy_kev * 1000)
41
41
  d = d_a
42
42
  return asin(wavelength_a / (2 * d)) * 180 / math.pi
@@ -22,7 +22,7 @@ from mx_bluesky.common.utils.log import (
22
22
  from mx_bluesky.hyperion.baton_handler import run_forever
23
23
  from mx_bluesky.hyperion.experiment_plans.experiment_registry import (
24
24
  PLAN_REGISTRY,
25
- PlanNotFound,
25
+ PlanNotFoundError,
26
26
  )
27
27
  from mx_bluesky.hyperion.external_interaction.agamemnon import (
28
28
  compare_params,
@@ -48,16 +48,16 @@ from mx_bluesky.hyperion.utils.context import setup_context
48
48
  def compose_start_args(context: BlueskyContext, plan_name: str, action: Actions):
49
49
  experiment_registry_entry = PLAN_REGISTRY.get(plan_name)
50
50
  if experiment_registry_entry is None:
51
- raise PlanNotFound(f"Experiment plan '{plan_name}' not found in registry.")
51
+ raise PlanNotFoundError(f"Experiment plan '{plan_name}' not found in registry.")
52
52
 
53
53
  experiment_internal_param_type = experiment_registry_entry.get("param_type")
54
54
  plan = context.plan_functions.get(plan_name)
55
55
  if experiment_internal_param_type is None:
56
- raise PlanNotFound(
56
+ raise PlanNotFoundError(
57
57
  f"Corresponding internal param type for '{plan_name}' not found in registry."
58
58
  )
59
59
  if plan is None:
60
- raise PlanNotFound(
60
+ raise PlanNotFoundError(
61
61
  f"Experiment plan '{plan_name}' not found in context. Context has {context.plan_functions.keys()}"
62
62
  )
63
63
  try:
@@ -115,7 +115,7 @@ class StopOrStatus(Resource):
115
115
  status_and_message = StatusAndMessage(Status.FAILED, f"{action} not understood")
116
116
  if action == Actions.STATUS.value:
117
117
  LOGGER.debug(
118
- f"Runner received status request - state of the runner object is: {self.runner.__dict__} - state of the RE is: {self.runner.RE.__dict__}"
118
+ f"Runner received status request - state of the runner object is: {self.runner.__dict__} - state of the run_engine is: {self.runner.run_engine.__dict__}"
119
119
  )
120
120
  status_and_message = self.runner.current_status
121
121
  return asdict(status_and_message)
@@ -9,6 +9,7 @@ from bluesky.utils import MsgGenerator, RunEngineInterrupted
9
9
  from dodal.common.beamlines.commissioning_mode import set_commissioning_signal
10
10
  from dodal.devices.aperturescatterguard import ApertureScatterguard
11
11
  from dodal.devices.baton import Baton
12
+ from dodal.devices.detector.detector_motion import DetectorMotion, ShutterState
12
13
  from dodal.devices.motors import XYZStage
13
14
  from dodal.devices.robot import BartRobot
14
15
  from dodal.devices.smargon import Smargon
@@ -38,7 +39,7 @@ from mx_bluesky.hyperion.external_interaction.agamemnon import (
38
39
  from mx_bluesky.hyperion.external_interaction.alerting.constants import Subjects
39
40
  from mx_bluesky.hyperion.parameters.components import Wait
40
41
  from mx_bluesky.hyperion.parameters.load_centre_collect import LoadCentreCollect
41
- from mx_bluesky.hyperion.plan_runner import PlanException, PlanRunner
42
+ from mx_bluesky.hyperion.plan_runner import PlanError, PlanRunner
42
43
  from mx_bluesky.hyperion.utils.context import (
43
44
  clear_all_device_caches,
44
45
  setup_devices,
@@ -53,7 +54,7 @@ def run_forever(runner: PlanRunner):
53
54
  while True:
54
55
  try:
55
56
  run_udc_when_requested(runner.context, runner)
56
- except PlanException as e:
57
+ except PlanError as e:
57
58
  LOGGER.info(
58
59
  "Caught exception during plan execution, stopped and waiting for baton.",
59
60
  exc_info=e,
@@ -64,7 +65,7 @@ def run_forever(runner: PlanRunner):
64
65
  # RunEngine.abort() will have been called and we will get RunEngineInterrupted
65
66
  LOGGER.info(
66
67
  f"RunEngine was interrupted. Runner state is {runner.current_status}, "
67
- f"run engine is {runner.RE.state}"
68
+ f"run engine is {runner.run_engine.state}"
68
69
  )
69
70
 
70
71
 
@@ -93,7 +94,7 @@ def run_udc_when_requested(context: BlueskyContext, runner: PlanRunner):
93
94
  * A user requests the baton away from Hyperion
94
95
  * Hyperion releases the baton when Agamemnon has no more instructions
95
96
  * The RunEngine raises a RequestAbort exception, most likely due to a shutdown command
96
- * A plan raises an exception not of type WarningException (which is then wrapped as a PlanException)
97
+ * A plan raises an exception not of type WarningError (which is then wrapped as a PlanError)
97
98
  Args:
98
99
  baton: The baton device
99
100
  runner: The runner
@@ -109,7 +110,7 @@ def run_udc_when_requested(context: BlueskyContext, runner: PlanRunner):
109
110
  baton, runner, current_visit
110
111
  )
111
112
  if current_visit:
112
- yield from _perform_robot_unload(runner.context, current_visit)
113
+ yield from _clean_up_udc(runner.context, current_visit)
113
114
 
114
115
  def release_baton() -> MsgGenerator:
115
116
  # If hyperion has given up the baton itself we need to also release requested
@@ -145,13 +146,13 @@ def _initialise_udc(context: BlueskyContext, dev_mode: bool):
145
146
 
146
147
  def _wait_for_hyperion_requested(baton: Baton):
147
148
  LOGGER.debug("Hyperion waiting for baton...")
148
- SLEEP_PER_CHECK = 0.1
149
+ sleep_per_check = 0.1
149
150
  while True:
150
151
  requested_user = yield from bps.rd(baton.requested_user)
151
152
  if requested_user == HYPERION_USER:
152
153
  LOGGER.debug("Baton requested for Hyperion")
153
154
  break
154
- yield from bps.sleep(SLEEP_PER_CHECK)
155
+ yield from bps.sleep(sleep_per_check)
155
156
 
156
157
 
157
158
  def _fetch_and_process_agamemnon_instruction(
@@ -239,11 +240,17 @@ def _unrequest_baton(baton: Baton) -> MsgGenerator[str]:
239
240
  return requested_user
240
241
 
241
242
 
242
- def _perform_robot_unload(context: BlueskyContext, visit: str) -> MsgGenerator:
243
+ def _clean_up_udc(context: BlueskyContext, visit: str) -> MsgGenerator:
244
+ cleanup_group = "cleanup"
243
245
  robot = find_device_in_context(context, "robot", BartRobot)
244
246
  smargon = find_device_in_context(context, "smargon", Smargon)
245
247
  aperture_scatterguard = find_device_in_context(
246
248
  context, "aperture_scatterguard", ApertureScatterguard
247
249
  )
248
250
  lower_gonio = find_device_in_context(context, "lower_gonio", XYZStage)
251
+ detector_motion = find_device_in_context(context, "detector_motion", DetectorMotion)
252
+ yield from bps.abs_set(
253
+ detector_motion.shutter, ShutterState.CLOSED, group=cleanup_group
254
+ )
249
255
  yield from robot_unload(robot, smargon, aperture_scatterguard, lower_gonio, visit)
256
+ yield from bps.wait(cleanup_group)
@@ -2,23 +2,23 @@ import numpy as np
2
2
  from bluesky import plan_stubs as bps
3
3
  from bluesky.utils import FailedStatus
4
4
  from dodal.devices.smargon import CombinedMove, Smargon
5
- from ophyd_async.epics.motor import MotorLimitsException
5
+ from ophyd_async.epics.motor import MotorLimitsError
6
6
 
7
- from mx_bluesky.common.utils.exceptions import SampleException
7
+ from mx_bluesky.common.utils.exceptions import SampleError
8
8
 
9
9
 
10
10
  def move_smargon_warn_on_out_of_range(
11
11
  smargon: Smargon, position: np.ndarray | list[float] | tuple[float, float, float]
12
12
  ):
13
- """Throws a SampleException if the specified position is out of range for the
13
+ """Throws a SampleError if the specified position is out of range for the
14
14
  smargon. Otherwise moves to that position. The check is from ophyd-async"""
15
15
  try:
16
16
  yield from bps.mv(
17
17
  smargon, CombinedMove(x=position[0], y=position[1], z=position[2])
18
18
  )
19
19
  except FailedStatus as fs:
20
- if isinstance(fs.__cause__, MotorLimitsException):
21
- raise SampleException(
20
+ if isinstance(fs.__cause__, MotorLimitsError):
21
+ raise SampleError(
22
22
  "Pin tip centring failed - pin too long/short/bent and out of range"
23
23
  ) from fs.__cause__
24
24
  else:
@@ -7,6 +7,6 @@ from dodal.devices.i03.dcm import DCM
7
7
 
8
8
  def fill_in_energy_if_not_supplied(dcm: DCM, detector_params: DetectorParams):
9
9
  if not detector_params.expected_energy_ev:
10
- actual_energy_ev = 1000 * (yield from bps.rd(dcm.energy_in_kev))
10
+ actual_energy_ev = 1000 * (yield from bps.rd(dcm.energy_in_keV))
11
11
  detector_params.expected_energy_ev = actual_energy_ev
12
12
  return detector_params
@@ -57,5 +57,5 @@ PLAN_REGISTRY: dict[str, ExperimentRegistryEntry] = {
57
57
  }
58
58
 
59
59
 
60
- class PlanNotFound(Exception):
60
+ class PlanNotFoundError(Exception):
61
61
  pass
@@ -15,7 +15,7 @@ from mx_bluesky.common.device_setup_plans.setup_zebra_and_shutter import (
15
15
  tidy_up_zebra_after_gridscan,
16
16
  )
17
17
  from mx_bluesky.common.experiment_plans.common_flyscan_xray_centre_plan import (
18
- construct_beamline_specific_FGS_features,
18
+ construct_beamline_specific_fast_gridscan_features,
19
19
  )
20
20
  from mx_bluesky.common.utils.log import LOGGER
21
21
  from mx_bluesky.hyperion.device_setup_plans.setup_panda import (
@@ -35,7 +35,7 @@ from mx_bluesky.hyperion.parameters.device_composites import (
35
35
  from mx_bluesky.hyperion.parameters.gridscan import HyperionSpecifiedThreeDGridScan
36
36
 
37
37
 
38
- class SmargonSpeedException(Exception):
38
+ class SmargonSpeedError(Exception):
39
39
  pass
40
40
 
41
41
 
@@ -54,14 +54,14 @@ def construct_hyperion_specific_features(
54
54
  xrc_composite.smargon.x,
55
55
  xrc_composite.smargon.y,
56
56
  xrc_composite.smargon.z,
57
- xrc_composite.dcm.energy_in_kev,
57
+ xrc_composite.dcm.energy_in_keV,
58
58
  ]
59
59
 
60
60
  signals_to_read_during_collection = [
61
61
  xrc_composite.aperture_scatterguard,
62
62
  xrc_composite.attenuator.actual_transmission,
63
63
  xrc_composite.flux.flux_reading,
64
- xrc_composite.dcm.energy_in_kev,
64
+ xrc_composite.dcm.energy_in_keV,
65
65
  xrc_composite.eiger.bit_depth,
66
66
  ]
67
67
 
@@ -73,7 +73,7 @@ def construct_hyperion_specific_features(
73
73
  set_flyscan_params_plan = partial(
74
74
  set_fast_grid_scan_params,
75
75
  xrc_composite.panda_fast_grid_scan,
76
- xrc_parameters.panda_FGS_params,
76
+ xrc_parameters.panda_fast_gridscan_params,
77
77
  )
78
78
  fgs_motors = xrc_composite.panda_fast_grid_scan
79
79
 
@@ -91,10 +91,10 @@ def construct_hyperion_specific_features(
91
91
  set_flyscan_params_plan = partial(
92
92
  set_fast_grid_scan_params,
93
93
  xrc_composite.zebra_fast_grid_scan,
94
- xrc_parameters.FGS_params,
94
+ xrc_parameters.fast_gridscan_params,
95
95
  )
96
96
  fgs_motors = xrc_composite.zebra_fast_grid_scan
97
- return construct_beamline_specific_FGS_features(
97
+ return construct_beamline_specific_fast_gridscan_features(
98
98
  setup_trigger_plan,
99
99
  tidy_plan,
100
100
  set_flyscan_params_plan,
@@ -112,8 +112,8 @@ def _panda_tidy(xrc_composite: HyperionFlyScanXRayCentreComposite):
112
112
  yield from tidy_up_zebra_after_gridscan(
113
113
  xrc_composite.zebra, xrc_composite.sample_shutter, group=group, wait=False
114
114
  )
115
+ yield from bps.unstage(xrc_composite.panda, group=group)
115
116
  yield from bps.wait(group, timeout=10)
116
- yield from bps.unstage(xrc_composite.panda)
117
117
 
118
118
 
119
119
  def _panda_triggering_setup(
@@ -126,35 +126,36 @@ def _panda_triggering_setup(
126
126
  xrc_composite.panda_fast_grid_scan.run_up_distance_mm
127
127
  )
128
128
 
129
- # Set the time between x steps pv
130
- DEADTIME_S = 1e-6 # according to https://www.dectris.com/en/detectors/x-ray-detectors/eiger2/eiger2-for-synchrotrons/eiger2-x/
129
+ detector_deadtime_s = 1e-4 # This value was empirically found to be safer than the documented deadtime in the Eiger manual
131
130
 
132
- time_between_x_steps_ms = (DEADTIME_S + parameters.exposure_time_s) * 1e3
131
+ time_between_x_steps_ms = (detector_deadtime_s + parameters.exposure_time_s) * 1e3
133
132
 
134
133
  smargon_speed_limit_mm_per_s = yield from bps.rd(
135
134
  xrc_composite.smargon.x.max_velocity
136
135
  )
137
136
 
138
137
  sample_velocity_mm_per_s = (
139
- parameters.panda_FGS_params.x_step_size_mm * 1e3 / time_between_x_steps_ms
138
+ parameters.panda_fast_gridscan_params.x_step_size_mm
139
+ * 1e3
140
+ / time_between_x_steps_ms
140
141
  )
141
142
  if sample_velocity_mm_per_s > smargon_speed_limit_mm_per_s:
142
- raise SmargonSpeedException(
143
+ raise SmargonSpeedError(
143
144
  f"Smargon speed was calculated from x step size\
144
- {parameters.panda_FGS_params.x_step_size_mm}mm and\
145
+ {parameters.panda_fast_gridscan_params.x_step_size_mm}mm and\
145
146
  time_between_x_steps_ms {time_between_x_steps_ms} as\
146
147
  {sample_velocity_mm_per_s}mm/s. The smargon's speed limit is\
147
148
  {smargon_speed_limit_mm_per_s}mm/s."
148
149
  )
149
150
  else:
150
151
  LOGGER.info(
151
- f"Panda grid scan: Smargon speed set to {smargon_speed_limit_mm_per_s} mm/s"
152
+ f"Panda grid scan: Smargon speed set to {sample_velocity_mm_per_s} mm/s"
152
153
  f" and using a run-up distance of {run_up_distance_mm}"
153
154
  )
154
155
 
155
156
  yield from bps.mv(
156
- xrc_composite.panda_fast_grid_scan.time_between_x_steps_ms, # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
157
- time_between_x_steps_ms, # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
157
+ xrc_composite.panda_fast_grid_scan.time_between_x_steps_ms,
158
+ time_between_x_steps_ms,
158
159
  )
159
160
 
160
161
  directory_provider_root = Path(parameters.storage_directory)
@@ -162,7 +163,7 @@ def _panda_triggering_setup(
162
163
 
163
164
  yield from setup_panda_for_flyscan(
164
165
  xrc_composite.panda,
165
- parameters.panda_FGS_params,
166
+ parameters.panda_fast_gridscan_params,
166
167
  xrc_composite.smargon,
167
168
  parameters.exposure_time_s,
168
169
  time_between_x_steps_ms,
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from collections.abc import Generator
3
+ from collections.abc import Generator, Sequence
4
4
 
5
5
  import bluesky.plan_stubs as bps
6
6
  import numpy as np
@@ -14,7 +14,7 @@ from dodal.devices.oav.oav_parameters import OAVParameters
14
14
  import mx_bluesky.common.xrc_result as flyscan_result
15
15
  from mx_bluesky.common.parameters.components import WithSnapshot
16
16
  from mx_bluesky.common.utils.context import device_composite_from_context
17
- from mx_bluesky.common.utils.exceptions import CrystalNotFoundException
17
+ from mx_bluesky.common.utils.exceptions import CrystalNotFoundError
18
18
  from mx_bluesky.common.utils.log import LOGGER
19
19
  from mx_bluesky.common.xrc_result import XRayCentreEventHandler
20
20
  from mx_bluesky.hyperion.experiment_plans.robot_load_then_centre_plan import (
@@ -93,47 +93,18 @@ def load_centre_collect_full(
93
93
  ),
94
94
  flyscan_event_handler,
95
95
  )
96
- except CrystalNotFoundException:
96
+ except CrystalNotFoundError:
97
97
  if parameters.select_centres.ignore_xtal_not_found:
98
98
  LOGGER.info("Ignoring crystal not found due to parameter settings.")
99
99
  else:
100
100
  raise
101
101
 
102
- locations_to_collect_um: list[np.ndarray]
103
- samples_to_collect: list[int]
104
-
105
- if flyscan_event_handler.xray_centre_results:
106
- selection_func = flyscan_result.resolve_selection_fn(
107
- parameters.selection_params
108
- )
109
- hits = selection_func(flyscan_event_handler.xray_centre_results)
110
- hits_to_collect = []
111
- for hit in hits:
112
- if hit.sample_id is None:
113
- LOGGER.warning(
114
- f"Diffracting centre {hit} not collected because no sample id was assigned."
115
- )
116
- else:
117
- hits_to_collect.append(hit)
118
-
119
- locations_to_collect_um = [
120
- hit.centre_of_mass_mm * 1000 for hit in hits_to_collect
121
- ]
122
- samples_to_collect = [hit.sample_id for hit in hits_to_collect]
123
- LOGGER.info(
124
- f"Selected hits {hits_to_collect} using {selection_func}, args={parameters.selection_params}"
102
+ sample_ids_and_locations = yield from (
103
+ _samples_and_locations_to_collect(
104
+ flyscan_event_handler.xray_centre_results, parameters, composite
125
105
  )
126
- else:
127
- # If the xray centring hasn't found a result but has not thrown an error it
128
- # means that we do not need to recentre and can collect where we are
129
- initial_x_mm = yield from bps.rd(composite.smargon.x.user_readback)
130
- initial_y_mm = yield from bps.rd(composite.smargon.y.user_readback)
131
- initial_z_mm = yield from bps.rd(composite.smargon.z.user_readback)
132
-
133
- locations_to_collect_um = [
134
- np.array([initial_x_mm, initial_y_mm, initial_z_mm]) * 1000
135
- ]
136
- samples_to_collect = [parameters.sample_id]
106
+ )
107
+ sample_ids_and_locations.sort(key=_x_coordinate)
137
108
 
138
109
  multi_rotation = parameters.multi_rotation_scan
139
110
  rotation_template = multi_rotation.rotation_scans.copy()
@@ -144,9 +115,7 @@ def load_centre_collect_full(
144
115
 
145
116
  generator = rotation_scan_generator(is_alternating)
146
117
  next(generator)
147
- for location, sample_id in zip(
148
- locations_to_collect_um, samples_to_collect, strict=True
149
- ):
118
+ for sample_id, location in sample_ids_and_locations:
150
119
  for rot in rotation_template:
151
120
  combination = generator.send((rot, location, sample_id))
152
121
  multi_rotation.rotation_scans.append(combination)
@@ -161,6 +130,51 @@ def load_centre_collect_full(
161
130
  yield from plan_with_callback_subs()
162
131
 
163
132
 
133
+ def _samples_and_locations_to_collect(
134
+ xrc_results: Sequence[flyscan_result.XRayCentreResult] | None,
135
+ parameters: LoadCentreCollect,
136
+ composite: LoadCentreCollectComposite,
137
+ ) -> MsgGenerator[list[tuple[int, np.ndarray]]]:
138
+ if xrc_results:
139
+ selection_func = flyscan_result.resolve_selection_fn(
140
+ parameters.selection_params
141
+ )
142
+ hits = selection_func(xrc_results)
143
+ hits_to_collect = []
144
+ for hit in hits:
145
+ if hit.sample_id is None:
146
+ LOGGER.warning(
147
+ f"Diffracting centre {hit} not collected because no sample id was assigned."
148
+ )
149
+ else:
150
+ hits_to_collect.append(hit)
151
+
152
+ samples_and_locations = [
153
+ (hit.sample_id, hit.centre_of_mass_mm * 1000) for hit in hits_to_collect
154
+ ]
155
+ LOGGER.info(
156
+ f"Selected hits {hits_to_collect} using {selection_func}, args={parameters.selection_params}"
157
+ )
158
+ return samples_and_locations
159
+ else:
160
+ # If the xray centring hasn't found a result but has not thrown an error it
161
+ # means that we do not need to recentre and can collect where we are
162
+ initial_x_mm = yield from bps.rd(composite.smargon.x.user_readback)
163
+ initial_y_mm = yield from bps.rd(composite.smargon.y.user_readback)
164
+ initial_z_mm = yield from bps.rd(composite.smargon.z.user_readback)
165
+
166
+ return [
167
+ (
168
+ parameters.sample_id,
169
+ np.array([initial_x_mm, initial_y_mm, initial_z_mm]) * 1000,
170
+ )
171
+ ]
172
+
173
+
174
+ def _x_coordinate(sample_and_location: tuple[int, np.ndarray]) -> float:
175
+ return sample_and_location[1][0] # type: ignore
176
+
177
+
164
178
  def rotation_scan_generator(
165
179
  is_alternating: bool,
166
180
  ) -> Generator[
@@ -13,7 +13,7 @@ from mx_bluesky.common.utils.context import device_composite_from_context
13
13
  from mx_bluesky.common.utils.log import LOGGER
14
14
 
15
15
 
16
- class AttenuationOptimisationFailedException(Exception):
16
+ class AttenuationOptimisationFailedError(Exception):
17
17
  pass
18
18
 
19
19
 
@@ -59,7 +59,7 @@ def check_parameters(
59
59
 
60
60
  if upper_transmission < lower_transmission:
61
61
  raise ValueError(
62
- f"Upper transmission limit {upper_transmission} must be greater than lower tranmission limit {lower_transmission}"
62
+ f"Upper transmission limit {upper_transmission} must be greater than lower transmission limit {lower_transmission}"
63
63
  )
64
64
 
65
65
  if not upper_transmission >= initial_transmission >= lower_transmission:
@@ -80,7 +80,7 @@ def calculate_new_direction(direction: Direction, deadtime, deadtime_threshold):
80
80
  if deadtime > deadtime_threshold:
81
81
  direction = Direction.NEGATIVE
82
82
  LOGGER.info(
83
- "Found tranmission to go above deadtime threshold. Reducing transmission..."
83
+ "Found transmission to go above deadtime threshold. Reducing transmission..."
84
84
  )
85
85
  return direction
86
86
 
@@ -111,7 +111,7 @@ def deadtime_calc_new_transmission(
111
111
  Minimum expected transmission. Raise an error if transmission goes lower.
112
112
 
113
113
  Raises:
114
- AttenuationOptimisationFailedException:
114
+ AttenuationOptimisationFailedError:
115
115
  This error is thrown if the transmission goes below the expected value or if the maximum cycles are reached
116
116
 
117
117
  Returns:
@@ -124,7 +124,7 @@ def deadtime_calc_new_transmission(
124
124
  else:
125
125
  transmission /= increment
126
126
  if transmission < lower_transmission_limit:
127
- raise AttenuationOptimisationFailedException(
127
+ raise AttenuationOptimisationFailedError(
128
128
  "Calculated transmission is below expected limit"
129
129
  )
130
130
  return transmission
@@ -218,7 +218,7 @@ def deadtime_optimisation(
218
218
  Minimum expected transmission. Raise an error if transmission goes lower.
219
219
 
220
220
  Raises:
221
- AttenuationOptimisationFailedException:
221
+ AttenuationOptimisationFailedError:
222
222
  This error is thrown if the transmission goes below the expected value or the maximum cycles are reached
223
223
 
224
224
  Returns:
@@ -262,7 +262,7 @@ def deadtime_optimisation(
262
262
  break
263
263
 
264
264
  if cycle == max_cycles - 1:
265
- raise AttenuationOptimisationFailedException(
265
+ raise AttenuationOptimisationFailedError(
266
266
  f"Unable to optimise attenuation after maximum cycles.\
267
267
  Deadtime did not get lower than threshold: {deadtime_threshold} in maximum cycles {max_cycles}"
268
268
  )
@@ -367,12 +367,12 @@ def total_counts_optimisation(
367
367
  if transmission > upper_transmission_limit:
368
368
  transmission = upper_transmission_limit
369
369
  elif transmission < lower_transmission_limit:
370
- raise AttenuationOptimisationFailedException(
370
+ raise AttenuationOptimisationFailedError(
371
371
  f"Transmission has gone below lower threshold {lower_transmission_limit}"
372
372
  )
373
373
 
374
374
  if cycle == max_cycles - 1:
375
- raise AttenuationOptimisationFailedException(
375
+ raise AttenuationOptimisationFailedError(
376
376
  f"Unable to optimise attenuation after maximum cycles.\
377
377
  Total count is not within limits: {lower_count_limit} <= {total_count}\
378
378
  <= {upper_count_limit}"