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.
- mx_bluesky/_version.py +2 -2
- mx_bluesky/beamlines/aithre_lasershaping/goniometer_controls.py +2 -2
- mx_bluesky/beamlines/i02_1/parameters/gridscan.py +1 -1
- mx_bluesky/beamlines/i04/experiment_plans/i04_grid_detect_then_xray_centre_plan.py +9 -9
- mx_bluesky/beamlines/i04/thawing_plan.py +9 -9
- mx_bluesky/beamlines/i24/jungfrau_commissioning/experiment_plans/do_darks.py +123 -0
- mx_bluesky/beamlines/i24/jungfrau_commissioning/plan_stubs/__init__.py +1 -0
- mx_bluesky/beamlines/i24/jungfrau_commissioning/{do_external_acquisition.py → plan_stubs/do_external_acquisition.py} +8 -7
- mx_bluesky/beamlines/i24/jungfrau_commissioning/{do_internal_acquisition.py → plan_stubs/do_internal_acquisition.py} +4 -3
- mx_bluesky/beamlines/i24/jungfrau_commissioning/{plan_utils.py → plan_stubs/plan_utils.py} +21 -28
- mx_bluesky/beamlines/i24/serial/__init__.py +7 -5
- mx_bluesky/beamlines/i24/serial/dcid.py +3 -3
- mx_bluesky/beamlines/i24/serial/extruder/{i24ssx_Extruder_Collect_py3v2.py → i24ssx_extruder_collect_py3v2.py} +65 -35
- mx_bluesky/beamlines/i24/serial/fixed_target/{i24ssx_Chip_Collect_py3v1.py → i24ssx_chip_collect_py3v1.py} +5 -5
- mx_bluesky/beamlines/i24/serial/fixed_target/{i24ssx_Chip_Manager_py3v1.py → i24ssx_chip_manager_py3v1.py} +46 -46
- mx_bluesky/beamlines/i24/serial/fixed_target/{i24ssx_Chip_StartUp_py3v1.py → i24ssx_chip_startup_py3v1.py} +3 -3
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_moveonclick.py +33 -33
- mx_bluesky/beamlines/i24/serial/log.py +11 -11
- mx_bluesky/beamlines/i24/serial/parameters/fixed_target/cs/cs_maker.json +3 -3
- mx_bluesky/beamlines/i24/serial/setup_beamline/ca.py +0 -12
- mx_bluesky/beamlines/i24/serial/setup_beamline/pv.py +13 -32
- mx_bluesky/beamlines/i24/serial/setup_beamline/pv_abstract.py +5 -5
- mx_bluesky/beamlines/i24/serial/setup_beamline/setup_beamline.py +22 -249
- mx_bluesky/beamlines/i24/serial/setup_beamline/setup_detector.py +2 -2
- mx_bluesky/beamlines/i24/serial/setup_beamline/setup_zebra_plans.py +4 -4
- mx_bluesky/beamlines/i24/serial/web_gui_plans/general_plans.py +102 -15
- mx_bluesky/beamlines/i24/serial/write_nexus.py +4 -4
- mx_bluesky/common/device_setup_plans/robot_load_unload.py +2 -2
- mx_bluesky/common/device_setup_plans/setup_oav.py +1 -1
- mx_bluesky/common/device_setup_plans/xbpm_feedback.py +4 -4
- mx_bluesky/common/experiment_plans/common_flyscan_xray_centre_plan.py +9 -9
- mx_bluesky/common/experiment_plans/common_grid_detect_then_xray_centre_plan.py +2 -2
- mx_bluesky/common/experiment_plans/inner_plans/do_fgs.py +2 -2
- mx_bluesky/common/experiment_plans/inner_plans/read_hardware.py +5 -5
- mx_bluesky/common/experiment_plans/inner_plans/udc_default_state.py +22 -1
- mx_bluesky/common/experiment_plans/inner_plans/write_sample_status.py +2 -2
- mx_bluesky/common/experiment_plans/oav_grid_detection_plan.py +2 -2
- mx_bluesky/common/experiment_plans/oav_snapshot_plan.py +1 -1
- mx_bluesky/common/external_interaction/callbacks/common/ispyb_callback_base.py +3 -3
- mx_bluesky/common/external_interaction/callbacks/common/plan_reactive_callback.py +1 -1
- mx_bluesky/common/external_interaction/callbacks/common/zocalo_callback.py +2 -2
- mx_bluesky/common/external_interaction/callbacks/sample_handling/sample_handling_callback.py +3 -3
- mx_bluesky/common/external_interaction/callbacks/xray_centre/ispyb_callback.py +7 -5
- mx_bluesky/common/external_interaction/callbacks/xray_centre/nexus_callback.py +2 -2
- mx_bluesky/common/external_interaction/config_server.py +2 -2
- mx_bluesky/common/external_interaction/ispyb/exp_eye_store.py +4 -2
- mx_bluesky/common/external_interaction/ispyb/ispyb_store.py +0 -1
- mx_bluesky/common/external_interaction/nexus/nexus_utils.py +2 -2
- mx_bluesky/common/external_interaction/nexus/write_nexus.py +3 -3
- mx_bluesky/common/parameters/constants.py +1 -1
- mx_bluesky/common/parameters/device_composites.py +2 -2
- mx_bluesky/common/parameters/gridscan.py +2 -2
- mx_bluesky/common/utils/exceptions.py +9 -7
- mx_bluesky/common/utils/log.py +4 -4
- mx_bluesky/common/utils/tracing.py +5 -5
- mx_bluesky/common/utils/utils.py +8 -8
- mx_bluesky/hyperion/__main__.py +5 -5
- mx_bluesky/hyperion/baton_handler.py +15 -8
- mx_bluesky/hyperion/device_setup_plans/smargon.py +5 -5
- mx_bluesky/hyperion/device_setup_plans/utils.py +1 -1
- mx_bluesky/hyperion/experiment_plans/experiment_registry.py +1 -1
- mx_bluesky/hyperion/experiment_plans/hyperion_flyscan_xray_centre_plan.py +19 -18
- mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py +54 -40
- mx_bluesky/hyperion/experiment_plans/optimise_attenuation_plan.py +9 -9
- mx_bluesky/hyperion/experiment_plans/pin_centre_then_xray_centre_plan.py +2 -2
- mx_bluesky/hyperion/experiment_plans/pin_tip_centring_plan.py +5 -5
- mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +3 -3
- mx_bluesky/hyperion/external_interaction/agamemnon.py +2 -2
- mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +2 -2
- mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py +1 -1
- mx_bluesky/hyperion/external_interaction/callbacks/rotation/nexus_callback.py +2 -2
- mx_bluesky/hyperion/external_interaction/config_server.py +2 -2
- mx_bluesky/hyperion/parameters/constants.py +2 -2
- mx_bluesky/hyperion/parameters/device_composites.py +2 -2
- mx_bluesky/hyperion/parameters/gridscan.py +4 -4
- mx_bluesky/hyperion/parameters/rotation.py +4 -6
- mx_bluesky/hyperion/plan_runner.py +6 -6
- mx_bluesky/hyperion/runner.py +10 -8
- mx_bluesky/hyperion/utils/context.py +6 -1
- mx_bluesky/jupyter_example.ipynb +3 -3
- {mx_bluesky-1.5.9.dist-info → mx_bluesky-1.5.11.dist-info}/METADATA +7 -6
- {mx_bluesky-1.5.9.dist-info → mx_bluesky-1.5.11.dist-info}/RECORD +87 -85
- /mx_bluesky/beamlines/i24/jungfrau_commissioning/{__init__.py → experiment_plans/__init__.py} +0 -0
- {mx_bluesky-1.5.9.dist-info → mx_bluesky-1.5.11.dist-info}/WHEEL +0 -0
- {mx_bluesky-1.5.9.dist-info → mx_bluesky-1.5.11.dist-info}/entry_points.txt +0 -0
- {mx_bluesky-1.5.9.dist-info → mx_bluesky-1.5.11.dist-info}/licenses/LICENSE +0 -0
- {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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
68
|
+
raise SampleError(str(exception)) from exception
|
|
67
69
|
yield from null()
|
|
68
70
|
|
|
69
71
|
return (
|
mx_bluesky/common/utils/log.py
CHANGED
|
@@ -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
|
|
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 =
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
19
|
-
trace.set_tracer_provider(
|
|
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
|
-
|
|
25
|
-
metrics.set_meter_provider(
|
|
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__)
|
mx_bluesky/common/utils/utils.py
CHANGED
|
@@ -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
|
-
|
|
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
|
|
15
|
-
return
|
|
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
|
|
19
|
-
return
|
|
18
|
+
def convert_ev_to_angstrom(hv: float) -> float:
|
|
19
|
+
return interconvert_ev_angstrom(hv)
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
def
|
|
23
|
-
return
|
|
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 =
|
|
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
|
mx_bluesky/hyperion/__main__.py
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
|
5
|
+
from ophyd_async.epics.motor import MotorLimitsError
|
|
6
6
|
|
|
7
|
-
from mx_bluesky.common.utils.exceptions import
|
|
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
|
|
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__,
|
|
21
|
-
raise
|
|
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.
|
|
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
|
|
@@ -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
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
94
|
+
xrc_parameters.fast_gridscan_params,
|
|
95
95
|
)
|
|
96
96
|
fgs_motors = xrc_composite.zebra_fast_grid_scan
|
|
97
|
-
return
|
|
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
|
-
#
|
|
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 = (
|
|
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.
|
|
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
|
|
143
|
+
raise SmargonSpeedError(
|
|
143
144
|
f"Smargon speed was calculated from x step size\
|
|
144
|
-
{parameters.
|
|
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 {
|
|
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,
|
|
157
|
-
time_between_x_steps_ms,
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
|
|
103
|
-
|
|
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
|
-
|
|
127
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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}"
|