mx-bluesky 1.4.0__py3-none-any.whl → 1.4.1__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/i04/redis_to_murko_forwarder.py +178 -0
- mx_bluesky/beamlines/i04/thawing_plan.py +1 -1
- mx_bluesky/beamlines/i24/serial/dcid.py +143 -171
- mx_bluesky/beamlines/i24/serial/extruder/EX-gui-edm/DiamondExtruder-I24-py3v1.edl +1 -1
- mx_bluesky/beamlines/i24/serial/extruder/i24ssx_Extruder_Collect_py3v2.py +54 -21
- mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/DiamondChipI24-py3v1.edl +2 -5
- mx_bluesky/beamlines/i24/serial/fixed_target/ft_utils.py +0 -1
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Collect_py3v1.py +67 -50
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Manager_py3v1.py +26 -79
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_StartUp_py3v1.py +0 -199
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_moveonclick.py +4 -6
- mx_bluesky/beamlines/i24/serial/log.py +1 -1
- mx_bluesky/beamlines/i24/serial/parameters/__init__.py +4 -0
- mx_bluesky/beamlines/i24/serial/parameters/constants.py +6 -1
- mx_bluesky/beamlines/i24/serial/parameters/experiment_parameters.py +42 -15
- mx_bluesky/beamlines/i24/serial/run_fixed_target.sh +4 -3
- mx_bluesky/beamlines/i24/serial/setup_beamline/pv.py +2 -0
- mx_bluesky/beamlines/i24/serial/setup_beamline/setup_beamline.py +103 -81
- mx_bluesky/beamlines/i24/serial/setup_beamline/setup_detector.py +1 -2
- mx_bluesky/beamlines/i24/serial/setup_beamline/setup_zebra_plans.py +24 -26
- mx_bluesky/beamlines/i24/serial/write_nexus.py +74 -72
- mx_bluesky/common/external_interaction/config_server.py +46 -0
- mx_bluesky/common/parameters/components.py +52 -15
- mx_bluesky/common/parameters/constants.py +11 -1
- mx_bluesky/common/parameters/gridscan.py +94 -0
- mx_bluesky/{hyperion → common}/parameters/robot_load.py +2 -2
- mx_bluesky/common/plans/do_fgs.py +2 -2
- mx_bluesky/common/utils/log.py +2 -0
- mx_bluesky/hyperion/__main__.py +2 -1
- mx_bluesky/hyperion/device_setup_plans/dcm_pitch_roll_mirror_adjuster.py +21 -31
- mx_bluesky/hyperion/device_setup_plans/setup_panda.py +4 -4
- mx_bluesky/hyperion/device_setup_plans/setup_zebra.py +1 -1
- mx_bluesky/hyperion/device_setup_plans/smargon.py +3 -3
- mx_bluesky/hyperion/exceptions.py +13 -1
- mx_bluesky/hyperion/experiment_plans/__init__.py +4 -0
- mx_bluesky/hyperion/experiment_plans/change_aperture_then_move_plan.py +83 -0
- mx_bluesky/hyperion/experiment_plans/common/xrc_result.py +47 -0
- mx_bluesky/hyperion/experiment_plans/experiment_registry.py +9 -9
- mx_bluesky/hyperion/experiment_plans/flyscan_xray_centre_plan.py +133 -97
- mx_bluesky/hyperion/experiment_plans/grid_detect_then_xray_centre_plan.py +42 -18
- mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py +75 -9
- mx_bluesky/hyperion/experiment_plans/oav_grid_detection_plan.py +2 -2
- mx_bluesky/hyperion/experiment_plans/oav_snapshot_plan.py +1 -1
- mx_bluesky/hyperion/experiment_plans/optimise_attenuation_plan.py +2 -2
- mx_bluesky/hyperion/experiment_plans/pin_centre_then_xray_centre_plan.py +36 -17
- mx_bluesky/hyperion/experiment_plans/pin_tip_centring_plan.py +5 -5
- mx_bluesky/hyperion/experiment_plans/robot_load_and_change_energy.py +28 -28
- mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +64 -16
- mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +11 -3
- mx_bluesky/hyperion/experiment_plans/set_energy_plan.py +10 -10
- mx_bluesky/hyperion/external_interaction/callbacks/__init__.py +0 -4
- mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +4 -0
- mx_bluesky/hyperion/external_interaction/callbacks/common/abstract_event.py +66 -0
- mx_bluesky/hyperion/external_interaction/callbacks/common/callback_util.py +5 -0
- mx_bluesky/hyperion/external_interaction/callbacks/grid_detection_callback.py +15 -15
- mx_bluesky/hyperion/external_interaction/callbacks/robot_load/ispyb_callback.py +18 -10
- mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py +3 -1
- mx_bluesky/hyperion/external_interaction/callbacks/rotation/nexus_callback.py +5 -3
- mx_bluesky/hyperion/external_interaction/callbacks/sample_handling/__init__.py +0 -0
- mx_bluesky/hyperion/external_interaction/callbacks/sample_handling/sample_handling_callback.py +84 -0
- mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/ispyb_callback.py +15 -9
- mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/nexus_callback.py +5 -4
- mx_bluesky/hyperion/external_interaction/config_server.py +8 -37
- mx_bluesky/hyperion/external_interaction/exceptions.py +0 -9
- mx_bluesky/hyperion/external_interaction/ispyb/exp_eye_store.py +65 -15
- mx_bluesky/hyperion/parameters/components.py +4 -9
- mx_bluesky/hyperion/parameters/constants.py +0 -1
- mx_bluesky/hyperion/parameters/gridscan.py +33 -76
- mx_bluesky/hyperion/parameters/load_centre_collect.py +14 -9
- mx_bluesky/hyperion/parameters/rotation.py +15 -6
- {mx_bluesky-1.4.0.dist-info → mx_bluesky-1.4.1.dist-info}/METADATA +35 -34
- {mx_bluesky-1.4.0.dist-info → mx_bluesky-1.4.1.dist-info}/RECORD +77 -70
- {mx_bluesky-1.4.0.dist-info → mx_bluesky-1.4.1.dist-info}/WHEEL +1 -1
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Mapping_py3v1.py +0 -150
- {mx_bluesky-1.4.0.dist-info → mx_bluesky-1.4.1.dist-info}/LICENSE +0 -0
- {mx_bluesky-1.4.0.dist-info → mx_bluesky-1.4.1.dist-info}/entry_points.txt +0 -0
- {mx_bluesky-1.4.0.dist-info → mx_bluesky-1.4.1.dist-info}/top_level.txt +0 -0
|
@@ -2,17 +2,17 @@ import numpy as np
|
|
|
2
2
|
from bluesky import plan_stubs as bps
|
|
3
3
|
from dodal.devices.smargon import Smargon
|
|
4
4
|
|
|
5
|
-
from mx_bluesky.hyperion.exceptions import
|
|
5
|
+
from mx_bluesky.hyperion.exceptions import SampleException
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
def move_smargon_warn_on_out_of_range(
|
|
9
9
|
smargon: Smargon, position: np.ndarray | list[float] | tuple[float, float, float]
|
|
10
10
|
):
|
|
11
|
-
"""Throws a
|
|
11
|
+
"""Throws a SampleException if the specified position is out of range for the
|
|
12
12
|
smargon. Otherwise moves to that position."""
|
|
13
13
|
limits = yield from smargon.get_xyz_limits()
|
|
14
14
|
if not limits.position_valid(position):
|
|
15
|
-
raise
|
|
15
|
+
raise SampleException(
|
|
16
16
|
"Pin tip centring failed - pin too long/short/bent and out of range"
|
|
17
17
|
)
|
|
18
18
|
yield from bps.mv(
|
|
@@ -13,9 +13,21 @@ class WarningException(Exception):
|
|
|
13
13
|
pass
|
|
14
14
|
|
|
15
15
|
|
|
16
|
+
class SampleException(WarningException):
|
|
17
|
+
"""An exception which identifies an issue relating to the sample."""
|
|
18
|
+
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
16
22
|
T = TypeVar("T")
|
|
17
23
|
|
|
18
24
|
|
|
25
|
+
class CrystalNotFoundException(SampleException):
|
|
26
|
+
"""Raised if grid detection completed normally but no crystal was found."""
|
|
27
|
+
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
|
|
19
31
|
def catch_exception_and_warn(
|
|
20
32
|
exception_to_catch: type[Exception],
|
|
21
33
|
func: Callable[..., Generator[Msg, None, T]],
|
|
@@ -36,7 +48,7 @@ def catch_exception_and_warn(
|
|
|
36
48
|
|
|
37
49
|
def warn_if_exception_matches(exception: Exception):
|
|
38
50
|
if isinstance(exception, exception_to_catch):
|
|
39
|
-
raise
|
|
51
|
+
raise SampleException(str(exception)) from exception
|
|
40
52
|
yield from null()
|
|
41
53
|
|
|
42
54
|
return (
|
|
@@ -9,6 +9,9 @@ from mx_bluesky.hyperion.experiment_plans.flyscan_xray_centre_plan import (
|
|
|
9
9
|
from mx_bluesky.hyperion.experiment_plans.grid_detect_then_xray_centre_plan import (
|
|
10
10
|
grid_detect_then_xray_centre,
|
|
11
11
|
)
|
|
12
|
+
from mx_bluesky.hyperion.experiment_plans.load_centre_collect_full_plan import (
|
|
13
|
+
load_centre_collect_full,
|
|
14
|
+
)
|
|
12
15
|
from mx_bluesky.hyperion.experiment_plans.pin_centre_then_xray_centre_plan import (
|
|
13
16
|
pin_tip_centre_then_xray_centre,
|
|
14
17
|
)
|
|
@@ -27,4 +30,5 @@ __all__ = [
|
|
|
27
30
|
"pin_tip_centre_then_xray_centre",
|
|
28
31
|
"multi_rotation_scan",
|
|
29
32
|
"robot_load_then_centre",
|
|
33
|
+
"load_centre_collect_full",
|
|
30
34
|
]
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import bluesky.plan_stubs as bps
|
|
2
|
+
import bluesky.preprocessors as bpp
|
|
3
|
+
import numpy
|
|
4
|
+
from dodal.devices.aperturescatterguard import ApertureScatterguard, ApertureValue
|
|
5
|
+
from dodal.devices.smargon import Smargon, StubPosition
|
|
6
|
+
|
|
7
|
+
from mx_bluesky.common.utils.tracing import TRACER
|
|
8
|
+
from mx_bluesky.hyperion.device_setup_plans.manipulate_sample import move_x_y_z
|
|
9
|
+
from mx_bluesky.hyperion.experiment_plans.common.xrc_result import XRayCentreResult
|
|
10
|
+
from mx_bluesky.hyperion.log import LOGGER
|
|
11
|
+
from mx_bluesky.hyperion.parameters.gridscan import HyperionThreeDGridScan
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def change_aperture_then_move_to_xtal(
|
|
15
|
+
best_hit: XRayCentreResult,
|
|
16
|
+
smargon: Smargon,
|
|
17
|
+
aperture_scatterguard: ApertureScatterguard,
|
|
18
|
+
parameters: HyperionThreeDGridScan | None = None,
|
|
19
|
+
):
|
|
20
|
+
"""For the given x-ray centring result,
|
|
21
|
+
* Change the aperture so that the beam size is comparable to the crystal size
|
|
22
|
+
* Centre on the centre-of-mass
|
|
23
|
+
* Reset the stub offsets if specified by params"""
|
|
24
|
+
if best_hit.bounding_box_mm is not None:
|
|
25
|
+
bounding_box_size = numpy.abs(
|
|
26
|
+
best_hit.bounding_box_mm[1] - best_hit.bounding_box_mm[0]
|
|
27
|
+
)
|
|
28
|
+
with TRACER.start_span("change_aperture"):
|
|
29
|
+
yield from _set_aperture_for_bbox_mm(
|
|
30
|
+
aperture_scatterguard, bounding_box_size
|
|
31
|
+
)
|
|
32
|
+
else:
|
|
33
|
+
LOGGER.warning("No bounding box size received")
|
|
34
|
+
|
|
35
|
+
# once we have the results, go to the appropriate position
|
|
36
|
+
LOGGER.info("Moving to centre of mass.")
|
|
37
|
+
with TRACER.start_span("move_to_result"):
|
|
38
|
+
x, y, z = best_hit.centre_of_mass_mm
|
|
39
|
+
yield from move_x_y_z(smargon, x, y, z, wait=True)
|
|
40
|
+
|
|
41
|
+
# TODO support for setting stub offsets in multipin
|
|
42
|
+
# https://github.com/DiamondLightSource/mx-bluesky/issues/552
|
|
43
|
+
if parameters and parameters.FGS_params.set_stub_offsets:
|
|
44
|
+
LOGGER.info("Recentring smargon co-ordinate system to this point.")
|
|
45
|
+
yield from bps.mv(
|
|
46
|
+
# See: https://github.com/bluesky/bluesky/issues/1809
|
|
47
|
+
smargon.stub_offsets, # type: ignore
|
|
48
|
+
StubPosition.CURRENT_AS_CENTER, # type: ignore
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _set_aperture_for_bbox_mm(
|
|
53
|
+
aperture_device: ApertureScatterguard, bbox_size_mm: list[float] | numpy.ndarray
|
|
54
|
+
):
|
|
55
|
+
# TODO confirm correction factor see https://github.com/DiamondLightSource/mx-bluesky/issues/618
|
|
56
|
+
ASSUMED_BOX_SIZE_MM = 0.020
|
|
57
|
+
bbox_size_boxes = [round(mm / ASSUMED_BOX_SIZE_MM) for mm in bbox_size_mm]
|
|
58
|
+
yield from set_aperture_for_bbox_size(aperture_device, bbox_size_boxes)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def set_aperture_for_bbox_size(
|
|
62
|
+
aperture_device: ApertureScatterguard,
|
|
63
|
+
bbox_size: list[int] | numpy.ndarray,
|
|
64
|
+
):
|
|
65
|
+
# bbox_size is [x,y,z], for i03 we only care about x
|
|
66
|
+
new_selected_aperture = (
|
|
67
|
+
ApertureValue.MEDIUM if bbox_size[0] < 2 else ApertureValue.LARGE
|
|
68
|
+
)
|
|
69
|
+
LOGGER.info(
|
|
70
|
+
f"Setting aperture to {new_selected_aperture} based on bounding box size {bbox_size}."
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
@bpp.set_run_key_decorator("change_aperture")
|
|
74
|
+
@bpp.run_decorator(
|
|
75
|
+
md={
|
|
76
|
+
"subplan_name": "change_aperture",
|
|
77
|
+
"aperture_size": new_selected_aperture.value,
|
|
78
|
+
}
|
|
79
|
+
)
|
|
80
|
+
def set_aperture():
|
|
81
|
+
yield from bps.abs_set(aperture_device, new_selected_aperture)
|
|
82
|
+
|
|
83
|
+
yield from set_aperture()
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import dataclasses
|
|
4
|
+
from collections.abc import Callable, Sequence
|
|
5
|
+
from functools import partial
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
from mx_bluesky.common.parameters.components import (
|
|
10
|
+
MultiXtalSelection,
|
|
11
|
+
TopNByMaxCountSelection,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclasses.dataclass
|
|
16
|
+
class XRayCentreResult:
|
|
17
|
+
"""Represents information about a hit from an X-ray centring."""
|
|
18
|
+
|
|
19
|
+
centre_of_mass_mm: np.ndarray
|
|
20
|
+
bounding_box_mm: tuple[np.ndarray, np.ndarray]
|
|
21
|
+
max_count: int
|
|
22
|
+
total_count: int
|
|
23
|
+
|
|
24
|
+
def __eq__(self, o):
|
|
25
|
+
return (
|
|
26
|
+
isinstance(o, XRayCentreResult)
|
|
27
|
+
and o.max_count == self.max_count
|
|
28
|
+
and o.total_count == self.total_count
|
|
29
|
+
and all(o.centre_of_mass_mm == self.centre_of_mass_mm)
|
|
30
|
+
and all(o.bounding_box_mm[0] == self.bounding_box_mm[0])
|
|
31
|
+
and all(o.bounding_box_mm[1] == self.bounding_box_mm[1])
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def top_n_by_max_count(
|
|
36
|
+
unfiltered: Sequence[XRayCentreResult], n: int
|
|
37
|
+
) -> Sequence[XRayCentreResult]:
|
|
38
|
+
sorted_hits = sorted(unfiltered, key=lambda result: result.max_count, reverse=True)
|
|
39
|
+
return sorted_hits[:n]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def resolve_selection_fn(
|
|
43
|
+
params: MultiXtalSelection,
|
|
44
|
+
) -> Callable[[Sequence[XRayCentreResult]], Sequence[XRayCentreResult]]:
|
|
45
|
+
if isinstance(params, TopNByMaxCountSelection):
|
|
46
|
+
return partial(top_n_by_max_count, n=params.n)
|
|
47
|
+
raise ValueError(f"Invalid selection function {params.name}")
|
|
@@ -5,6 +5,11 @@ from typing import TypedDict
|
|
|
5
5
|
|
|
6
6
|
import mx_bluesky.hyperion.experiment_plans.flyscan_xray_centre_plan as flyscan_xray_centre_plan
|
|
7
7
|
import mx_bluesky.hyperion.experiment_plans.rotation_scan_plan as rotation_scan_plan
|
|
8
|
+
from mx_bluesky.common.parameters.gridscan import (
|
|
9
|
+
GridScanWithEdgeDetect,
|
|
10
|
+
PinTipCentreThenXrayCentre,
|
|
11
|
+
RobotLoadThenCentre,
|
|
12
|
+
)
|
|
8
13
|
from mx_bluesky.hyperion.experiment_plans import (
|
|
9
14
|
grid_detect_then_xray_centre_plan,
|
|
10
15
|
load_centre_collect_full_plan,
|
|
@@ -18,12 +23,7 @@ from mx_bluesky.hyperion.external_interaction.callbacks.common.callback_util imp
|
|
|
18
23
|
create_robot_load_and_centre_callbacks,
|
|
19
24
|
create_rotation_callbacks,
|
|
20
25
|
)
|
|
21
|
-
from mx_bluesky.hyperion.parameters.gridscan import
|
|
22
|
-
GridScanWithEdgeDetect,
|
|
23
|
-
PinTipCentreThenXrayCentre,
|
|
24
|
-
RobotLoadThenCentre,
|
|
25
|
-
ThreeDGridScan,
|
|
26
|
-
)
|
|
26
|
+
from mx_bluesky.hyperion.parameters.gridscan import HyperionThreeDGridScan
|
|
27
27
|
from mx_bluesky.hyperion.parameters.load_centre_collect import LoadCentreCollect
|
|
28
28
|
from mx_bluesky.hyperion.parameters.rotation import MultiRotationScan, RotationScan
|
|
29
29
|
|
|
@@ -39,7 +39,7 @@ def do_nothing():
|
|
|
39
39
|
class ExperimentRegistryEntry(TypedDict):
|
|
40
40
|
setup: Callable
|
|
41
41
|
param_type: type[
|
|
42
|
-
|
|
42
|
+
HyperionThreeDGridScan
|
|
43
43
|
| GridScanWithEdgeDetect
|
|
44
44
|
| RotationScan
|
|
45
45
|
| MultiRotationScan
|
|
@@ -53,7 +53,7 @@ class ExperimentRegistryEntry(TypedDict):
|
|
|
53
53
|
PLAN_REGISTRY: dict[str, ExperimentRegistryEntry] = {
|
|
54
54
|
"flyscan_xray_centre": {
|
|
55
55
|
"setup": flyscan_xray_centre_plan.create_devices,
|
|
56
|
-
"param_type":
|
|
56
|
+
"param_type": HyperionThreeDGridScan,
|
|
57
57
|
"callbacks_factory": create_gridscan_callbacks,
|
|
58
58
|
},
|
|
59
59
|
"grid_detect_then_xray_centre": {
|
|
@@ -81,7 +81,7 @@ PLAN_REGISTRY: dict[str, ExperimentRegistryEntry] = {
|
|
|
81
81
|
"param_type": MultiRotationScan,
|
|
82
82
|
"callbacks_factory": create_rotation_callbacks,
|
|
83
83
|
},
|
|
84
|
-
"
|
|
84
|
+
"load_centre_collect_full": {
|
|
85
85
|
"setup": load_centre_collect_full_plan.create_devices,
|
|
86
86
|
"param_type": LoadCentreCollect,
|
|
87
87
|
"callbacks_factory": create_load_centre_collect_callbacks,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import dataclasses
|
|
4
|
-
from collections.abc import Callable
|
|
4
|
+
from collections.abc import Callable, Sequence
|
|
5
5
|
from functools import partial
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
from typing import Protocol
|
|
@@ -9,10 +9,12 @@ from typing import Protocol
|
|
|
9
9
|
import bluesky.plan_stubs as bps
|
|
10
10
|
import bluesky.preprocessors as bpp
|
|
11
11
|
import numpy as np
|
|
12
|
-
|
|
12
|
+
import pydantic
|
|
13
|
+
from blueapi.core import BlueskyContext
|
|
14
|
+
from bluesky.callbacks import CallbackBase
|
|
15
|
+
from bluesky.utils import MsgGenerator
|
|
13
16
|
from dodal.devices.aperturescatterguard import (
|
|
14
17
|
ApertureScatterguard,
|
|
15
|
-
ApertureValue,
|
|
16
18
|
)
|
|
17
19
|
from dodal.devices.attenuator import Attenuator
|
|
18
20
|
from dodal.devices.backlight import Backlight
|
|
@@ -29,7 +31,7 @@ from dodal.devices.fast_grid_scan import (
|
|
|
29
31
|
from dodal.devices.flux import Flux
|
|
30
32
|
from dodal.devices.robot import BartRobot
|
|
31
33
|
from dodal.devices.s4_slit_gaps import S4SlitGaps
|
|
32
|
-
from dodal.devices.smargon import Smargon
|
|
34
|
+
from dodal.devices.smargon import Smargon
|
|
33
35
|
from dodal.devices.synchrotron import Synchrotron
|
|
34
36
|
from dodal.devices.undulator import Undulator
|
|
35
37
|
from dodal.devices.xbpm_feedback import XBPMFeedback
|
|
@@ -38,14 +40,15 @@ from dodal.devices.zebra_controlled_shutter import ZebraShutter
|
|
|
38
40
|
from dodal.devices.zocalo.zocalo_results import (
|
|
39
41
|
ZOCALO_READING_PLAN_NAME,
|
|
40
42
|
ZOCALO_STAGE_GROUP,
|
|
43
|
+
XrcResult,
|
|
41
44
|
ZocaloResults,
|
|
42
45
|
get_full_processing_results,
|
|
43
46
|
)
|
|
47
|
+
from event_model import RunStart
|
|
44
48
|
from ophyd_async.fastcs.panda import HDFPanda
|
|
45
49
|
|
|
46
50
|
from mx_bluesky.common.plans.do_fgs import kickoff_and_complete_gridscan
|
|
47
51
|
from mx_bluesky.common.utils.tracing import TRACER
|
|
48
|
-
from mx_bluesky.hyperion.device_setup_plans.manipulate_sample import move_x_y_z
|
|
49
52
|
from mx_bluesky.hyperion.device_setup_plans.read_hardware_for_setup import (
|
|
50
53
|
read_hardware_during_collection,
|
|
51
54
|
read_hardware_pre_collection,
|
|
@@ -63,10 +66,17 @@ from mx_bluesky.hyperion.device_setup_plans.setup_zebra import (
|
|
|
63
66
|
from mx_bluesky.hyperion.device_setup_plans.xbpm_feedback import (
|
|
64
67
|
transmission_and_xbpm_feedback_for_collection_decorator,
|
|
65
68
|
)
|
|
66
|
-
from mx_bluesky.hyperion.exceptions import
|
|
69
|
+
from mx_bluesky.hyperion.exceptions import CrystalNotFoundException, SampleException
|
|
70
|
+
from mx_bluesky.hyperion.experiment_plans.change_aperture_then_move_plan import (
|
|
71
|
+
change_aperture_then_move_to_xtal,
|
|
72
|
+
)
|
|
73
|
+
from mx_bluesky.hyperion.experiment_plans.common.xrc_result import XRayCentreResult
|
|
74
|
+
from mx_bluesky.hyperion.external_interaction.callbacks.xray_centre.ispyb_callback import (
|
|
75
|
+
ispyb_activation_wrapper,
|
|
76
|
+
)
|
|
67
77
|
from mx_bluesky.hyperion.log import LOGGER
|
|
68
78
|
from mx_bluesky.hyperion.parameters.constants import CONST
|
|
69
|
-
from mx_bluesky.hyperion.parameters.gridscan import
|
|
79
|
+
from mx_bluesky.hyperion.parameters.gridscan import HyperionThreeDGridScan
|
|
70
80
|
from mx_bluesky.hyperion.utils.context import device_composite_from_context
|
|
71
81
|
|
|
72
82
|
|
|
@@ -74,13 +84,7 @@ class SmargonSpeedException(Exception):
|
|
|
74
84
|
pass
|
|
75
85
|
|
|
76
86
|
|
|
77
|
-
|
|
78
|
-
"""Raised if grid detection completed normally but no crystal was found."""
|
|
79
|
-
|
|
80
|
-
pass
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
@dataclasses.dataclass
|
|
87
|
+
@pydantic.dataclasses.dataclass(config={"arbitrary_types_allowed": True})
|
|
84
88
|
class FlyScanXRayCentreComposite:
|
|
85
89
|
"""All devices which are directly or indirectly required by this plan"""
|
|
86
90
|
|
|
@@ -109,26 +113,29 @@ class FlyScanXRayCentreComposite:
|
|
|
109
113
|
return self.smargon
|
|
110
114
|
|
|
111
115
|
|
|
116
|
+
class XRayCentreEventHandler(CallbackBase):
|
|
117
|
+
def __init__(self):
|
|
118
|
+
super().__init__()
|
|
119
|
+
self.xray_centre_results: Sequence[XRayCentreResult] | None = None
|
|
120
|
+
|
|
121
|
+
def start(self, doc: RunStart) -> RunStart | None:
|
|
122
|
+
if "xray_centre_results" in doc:
|
|
123
|
+
self.xray_centre_results = [
|
|
124
|
+
XRayCentreResult(**result_dict)
|
|
125
|
+
for result_dict in doc["xray_centre_results"] # type: ignore
|
|
126
|
+
]
|
|
127
|
+
return doc
|
|
128
|
+
|
|
129
|
+
|
|
112
130
|
def create_devices(context: BlueskyContext) -> FlyScanXRayCentreComposite:
|
|
113
131
|
"""Creates the devices required for the plan and connect to them"""
|
|
114
132
|
return device_composite_from_context(context, FlyScanXRayCentreComposite)
|
|
115
133
|
|
|
116
134
|
|
|
117
|
-
def
|
|
118
|
-
composite: FlyScanXRayCentreComposite,
|
|
119
|
-
parameters: ThreeDGridScan,
|
|
135
|
+
def flyscan_xray_centre_no_move(
|
|
136
|
+
composite: FlyScanXRayCentreComposite, parameters: HyperionThreeDGridScan
|
|
120
137
|
) -> MsgGenerator:
|
|
121
|
-
"""
|
|
122
|
-
|
|
123
|
-
The ispyb handler should be added to the whole gridscan as we want to capture errors
|
|
124
|
-
at any point in it.
|
|
125
|
-
|
|
126
|
-
Args:
|
|
127
|
-
parameters (ThreeDGridScan): The parameters to run the scan.
|
|
128
|
-
|
|
129
|
-
Returns:
|
|
130
|
-
Generator: The plan for the gridscan
|
|
131
|
-
"""
|
|
138
|
+
"""Perform a flyscan and determine the centres of interest"""
|
|
132
139
|
parameters.features.update_self_from_server()
|
|
133
140
|
composite.eiger.set_detector_parameters(parameters.detector_params)
|
|
134
141
|
composite.zocalo.zocalo_environment = CONST.ZOCALO_ENV
|
|
@@ -152,25 +159,66 @@ def flyscan_xray_centre(
|
|
|
152
159
|
composite.attenuator,
|
|
153
160
|
parameters.transmission_frac,
|
|
154
161
|
)
|
|
155
|
-
def
|
|
162
|
+
def run_gridscan_and_fetch_and_tidy(
|
|
156
163
|
fgs_composite: FlyScanXRayCentreComposite,
|
|
157
|
-
params:
|
|
164
|
+
params: HyperionThreeDGridScan,
|
|
158
165
|
feature_controlled: _FeatureControlled,
|
|
159
|
-
):
|
|
160
|
-
yield from
|
|
166
|
+
) -> MsgGenerator:
|
|
167
|
+
yield from run_gridscan_and_fetch_results(
|
|
168
|
+
fgs_composite, params, feature_controlled
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
yield from run_gridscan_and_fetch_and_tidy(
|
|
172
|
+
composite, parameters, feature_controlled
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def flyscan_xray_centre(
|
|
177
|
+
composite: FlyScanXRayCentreComposite,
|
|
178
|
+
parameters: HyperionThreeDGridScan,
|
|
179
|
+
) -> MsgGenerator:
|
|
180
|
+
"""Create the plan to run the grid scan based on provided parameters.
|
|
181
|
+
|
|
182
|
+
The ispyb handler should be added to the whole gridscan as we want to capture errors
|
|
183
|
+
at any point in it.
|
|
161
184
|
|
|
162
|
-
|
|
185
|
+
Args:
|
|
186
|
+
parameters (ThreeDGridScan): The parameters to run the scan.
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
Generator: The plan for the gridscan
|
|
190
|
+
"""
|
|
191
|
+
xrc_event_handler = XRayCentreEventHandler()
|
|
192
|
+
|
|
193
|
+
@bpp.subs_decorator(xrc_event_handler)
|
|
194
|
+
def flyscan_and_fetch_results() -> MsgGenerator:
|
|
195
|
+
yield from ispyb_activation_wrapper(
|
|
196
|
+
flyscan_xray_centre_no_move(composite, parameters), parameters
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
yield from flyscan_and_fetch_results()
|
|
200
|
+
|
|
201
|
+
xray_centre_results = xrc_event_handler.xray_centre_results
|
|
202
|
+
assert (
|
|
203
|
+
xray_centre_results
|
|
204
|
+
), "Flyscan result event not received or no crystal found and exception not raised"
|
|
205
|
+
yield from change_aperture_then_move_to_xtal(
|
|
206
|
+
xray_centre_results[0],
|
|
207
|
+
composite.smargon,
|
|
208
|
+
composite.aperture_scatterguard,
|
|
209
|
+
parameters,
|
|
210
|
+
)
|
|
163
211
|
|
|
164
212
|
|
|
165
213
|
@bpp.set_run_key_decorator(CONST.PLAN.GRIDSCAN_AND_MOVE)
|
|
166
214
|
@bpp.run_decorator(md={"subplan_name": CONST.PLAN.GRIDSCAN_AND_MOVE})
|
|
167
|
-
def
|
|
215
|
+
def run_gridscan_and_fetch_results(
|
|
168
216
|
fgs_composite: FlyScanXRayCentreComposite,
|
|
169
|
-
parameters:
|
|
217
|
+
parameters: HyperionThreeDGridScan,
|
|
170
218
|
feature_controlled: _FeatureControlled,
|
|
171
219
|
) -> MsgGenerator:
|
|
172
220
|
"""A multi-run plan which runs a gridscan, gets the results from zocalo
|
|
173
|
-
and
|
|
221
|
+
and fires an event with the centres of mass determined by zocalo"""
|
|
174
222
|
|
|
175
223
|
# We get the initial motor positions so we can return to them on zocalo failure
|
|
176
224
|
initial_xyz = np.array(
|
|
@@ -198,37 +246,17 @@ def run_gridscan_and_move(
|
|
|
198
246
|
)
|
|
199
247
|
LOGGER.info("Zocalo triggered and read, interpreting results.")
|
|
200
248
|
xrc_results = yield from get_full_processing_results(fgs_composite.zocalo)
|
|
201
|
-
LOGGER.info(f"Got xray
|
|
249
|
+
LOGGER.info(f"Got xray centres, top 5: {xrc_results[:5]}")
|
|
202
250
|
if xrc_results:
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
)
|
|
208
|
-
with TRACER.start_span("change_aperture"):
|
|
209
|
-
bbox_size = np.abs(
|
|
210
|
-
np.array(best_result["bounding_box"][1])
|
|
211
|
-
- np.array(best_result["bounding_box"][0])
|
|
212
|
-
)
|
|
213
|
-
yield from set_aperture_for_bbox_size(
|
|
214
|
-
fgs_composite.aperture_scatterguard, bbox_size
|
|
215
|
-
)
|
|
251
|
+
flyscan_results = [
|
|
252
|
+
_xrc_result_in_boxes_to_result_in_mm(xr, parameters)
|
|
253
|
+
for xr in xrc_results
|
|
254
|
+
]
|
|
216
255
|
else:
|
|
217
256
|
LOGGER.warning("No X-ray centre received")
|
|
218
257
|
raise CrystalNotFoundException()
|
|
258
|
+
yield from _fire_xray_centre_result_event(flyscan_results)
|
|
219
259
|
|
|
220
|
-
# once we have the results, go to the appropriate position
|
|
221
|
-
LOGGER.info("Moving to centre of mass.")
|
|
222
|
-
with TRACER.start_span("move_to_result"):
|
|
223
|
-
x, y, z = xray_centre
|
|
224
|
-
yield from move_x_y_z(fgs_composite.sample_motors, x, y, z, wait=True)
|
|
225
|
-
|
|
226
|
-
if parameters.FGS_params.set_stub_offsets:
|
|
227
|
-
LOGGER.info("Recentring smargon co-ordinate system to this point.")
|
|
228
|
-
yield from bps.mv(
|
|
229
|
-
fgs_composite.sample_motors.stub_offsets, # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
|
|
230
|
-
StubPosition.CURRENT_AS_CENTER, # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
|
|
231
|
-
)
|
|
232
260
|
finally:
|
|
233
261
|
# Turn off dev/shm streaming to avoid filling disk, see https://github.com/DiamondLightSource/hyperion/issues/1395
|
|
234
262
|
LOGGER.info("Turning off Eiger dev/shm streaming")
|
|
@@ -239,11 +267,44 @@ def run_gridscan_and_move(
|
|
|
239
267
|
yield from bps.wait()
|
|
240
268
|
|
|
241
269
|
|
|
270
|
+
def _xrc_result_in_boxes_to_result_in_mm(
|
|
271
|
+
xrc_result: XrcResult, parameters: HyperionThreeDGridScan
|
|
272
|
+
) -> XRayCentreResult:
|
|
273
|
+
fgs_params = parameters.FGS_params
|
|
274
|
+
xray_centre = fgs_params.grid_position_to_motor_position(
|
|
275
|
+
np.array(xrc_result["centre_of_mass"])
|
|
276
|
+
)
|
|
277
|
+
return XRayCentreResult(
|
|
278
|
+
centre_of_mass_mm=xray_centre,
|
|
279
|
+
bounding_box_mm=(
|
|
280
|
+
fgs_params.grid_position_to_motor_position(
|
|
281
|
+
np.array(xrc_result["bounding_box"][0])
|
|
282
|
+
),
|
|
283
|
+
fgs_params.grid_position_to_motor_position(
|
|
284
|
+
np.array(xrc_result["bounding_box"][1])
|
|
285
|
+
),
|
|
286
|
+
),
|
|
287
|
+
max_count=xrc_result["max_count"],
|
|
288
|
+
total_count=xrc_result["total_count"],
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
@bpp.set_run_key_decorator(CONST.PLAN.FLYSCAN_RESULTS)
|
|
293
|
+
def _fire_xray_centre_result_event(results: Sequence[XRayCentreResult]):
|
|
294
|
+
def empty_plan():
|
|
295
|
+
return iter([])
|
|
296
|
+
|
|
297
|
+
yield from bpp.run_wrapper(
|
|
298
|
+
empty_plan(),
|
|
299
|
+
md={"xray_centre_results": [dataclasses.asdict(r) for r in results]},
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
|
|
242
303
|
@bpp.set_run_key_decorator(CONST.PLAN.GRIDSCAN_MAIN)
|
|
243
304
|
@bpp.run_decorator(md={"subplan_name": CONST.PLAN.GRIDSCAN_MAIN})
|
|
244
305
|
def run_gridscan(
|
|
245
306
|
fgs_composite: FlyScanXRayCentreComposite,
|
|
246
|
-
parameters:
|
|
307
|
+
parameters: HyperionThreeDGridScan,
|
|
247
308
|
feature_controlled: _FeatureControlled,
|
|
248
309
|
md={ # noqa
|
|
249
310
|
"plan_name": CONST.PLAN.GRIDSCAN_MAIN,
|
|
@@ -311,32 +372,7 @@ def wait_for_gridscan_valid(fgs_motors: FastGridScanCommon, timeout=0.5):
|
|
|
311
372
|
LOGGER.info("Gridscan scan valid and position counter reset")
|
|
312
373
|
return
|
|
313
374
|
yield from bps.sleep(SLEEP_PER_CHECK)
|
|
314
|
-
raise
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
def set_aperture_for_bbox_size(
|
|
318
|
-
aperture_device: ApertureScatterguard,
|
|
319
|
-
bbox_size: list[int] | np.ndarray,
|
|
320
|
-
):
|
|
321
|
-
# bbox_size is [x,y,z], for i03 we only care about x
|
|
322
|
-
new_selected_aperture = (
|
|
323
|
-
ApertureValue.MEDIUM if bbox_size[0] < 2 else ApertureValue.LARGE
|
|
324
|
-
)
|
|
325
|
-
LOGGER.info(
|
|
326
|
-
f"Setting aperture to {new_selected_aperture} based on bounding box size {bbox_size}."
|
|
327
|
-
)
|
|
328
|
-
|
|
329
|
-
@bpp.set_run_key_decorator("change_aperture")
|
|
330
|
-
@bpp.run_decorator(
|
|
331
|
-
md={
|
|
332
|
-
"subplan_name": "change_aperture",
|
|
333
|
-
"aperture_size": new_selected_aperture.value,
|
|
334
|
-
}
|
|
335
|
-
)
|
|
336
|
-
def set_aperture():
|
|
337
|
-
yield from bps.abs_set(aperture_device, new_selected_aperture)
|
|
338
|
-
|
|
339
|
-
yield from set_aperture()
|
|
375
|
+
raise SampleException("Scan invalid - pin too long/short/bent and out of range")
|
|
340
376
|
|
|
341
377
|
|
|
342
378
|
@dataclasses.dataclass
|
|
@@ -350,7 +386,7 @@ class _FeatureControlled:
|
|
|
350
386
|
def __call__(
|
|
351
387
|
self,
|
|
352
388
|
fgs_composite: FlyScanXRayCentreComposite,
|
|
353
|
-
parameters:
|
|
389
|
+
parameters: HyperionThreeDGridScan,
|
|
354
390
|
initial_xyz: np.ndarray,
|
|
355
391
|
) -> MsgGenerator: ...
|
|
356
392
|
|
|
@@ -362,7 +398,7 @@ class _FeatureControlled:
|
|
|
362
398
|
|
|
363
399
|
def _get_feature_controlled(
|
|
364
400
|
fgs_composite: FlyScanXRayCentreComposite,
|
|
365
|
-
parameters:
|
|
401
|
+
parameters: HyperionThreeDGridScan,
|
|
366
402
|
):
|
|
367
403
|
if parameters.features.use_panda_for_gridscan:
|
|
368
404
|
return _FeatureControlled(
|
|
@@ -411,7 +447,7 @@ def _panda_tidy(fgs_composite: FlyScanXRayCentreComposite):
|
|
|
411
447
|
|
|
412
448
|
def _zebra_triggering_setup(
|
|
413
449
|
fgs_composite: FlyScanXRayCentreComposite,
|
|
414
|
-
parameters:
|
|
450
|
+
parameters: HyperionThreeDGridScan,
|
|
415
451
|
initial_xyz: np.ndarray,
|
|
416
452
|
):
|
|
417
453
|
yield from setup_zebra_for_gridscan(
|
|
@@ -421,7 +457,7 @@ def _zebra_triggering_setup(
|
|
|
421
457
|
|
|
422
458
|
def _panda_triggering_setup(
|
|
423
459
|
fgs_composite: FlyScanXRayCentreComposite,
|
|
424
|
-
parameters:
|
|
460
|
+
parameters: HyperionThreeDGridScan,
|
|
425
461
|
initial_xyz: np.ndarray,
|
|
426
462
|
):
|
|
427
463
|
LOGGER.info("Setting up Panda for flyscan")
|
|
@@ -440,15 +476,15 @@ def _panda_triggering_setup(
|
|
|
440
476
|
)
|
|
441
477
|
|
|
442
478
|
sample_velocity_mm_per_s = (
|
|
443
|
-
parameters.panda_FGS_params.
|
|
479
|
+
parameters.panda_FGS_params.x_step_size_mm * 1e3 / time_between_x_steps_ms
|
|
444
480
|
)
|
|
445
481
|
if sample_velocity_mm_per_s > smargon_speed_limit_mm_per_s:
|
|
446
482
|
raise SmargonSpeedException(
|
|
447
483
|
f"Smargon speed was calculated from x step size\
|
|
448
|
-
{parameters.panda_FGS_params.
|
|
484
|
+
{parameters.panda_FGS_params.x_step_size_mm}mm and\
|
|
449
485
|
time_between_x_steps_ms {time_between_x_steps_ms} as\
|
|
450
|
-
{sample_velocity_mm_per_s}. The smargon's speed limit is\
|
|
451
|
-
{smargon_speed_limit_mm_per_s}
|
|
486
|
+
{sample_velocity_mm_per_s}mm/s. The smargon's speed limit is\
|
|
487
|
+
{smargon_speed_limit_mm_per_s}mm/s."
|
|
452
488
|
)
|
|
453
489
|
else:
|
|
454
490
|
LOGGER.info(
|