mx-bluesky 1.4.0__py3-none-any.whl → 1.4.1a0__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/thawing_plan.py +1 -1
- mx_bluesky/beamlines/i24/serial/dcid.py +19 -21
- mx_bluesky/beamlines/i24/serial/extruder/i24ssx_Extruder_Collect_py3v2.py +2 -2
- mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/DiamondChipI24-py3v1.edl +1 -4
- mx_bluesky/beamlines/i24/serial/fixed_target/ft_utils.py +0 -1
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Collect_py3v1.py +14 -24
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Manager_py3v1.py +18 -76
- 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/constants.py +0 -1
- mx_bluesky/beamlines/i24/serial/run_fixed_target.sh +4 -3
- mx_bluesky/beamlines/i24/serial/setup_beamline/setup_beamline.py +78 -80
- 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 +11 -11
- mx_bluesky/common/external_interaction/config_server.py +46 -0
- mx_bluesky/common/parameters/components.py +52 -15
- mx_bluesky/common/parameters/constants.py +6 -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/setup_panda.py +4 -4
- mx_bluesky/hyperion/device_setup_plans/setup_zebra.py +1 -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 +131 -89
- mx_bluesky/hyperion/experiment_plans/grid_detect_then_xray_centre_plan.py +50 -18
- mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py +52 -10
- mx_bluesky/hyperion/experiment_plans/oav_grid_detection_plan.py +2 -2
- mx_bluesky/hyperion/experiment_plans/oav_snapshot_plan.py +3 -9
- 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 +2 -2
- mx_bluesky/hyperion/experiment_plans/robot_load_and_change_energy.py +6 -10
- mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +46 -11
- mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +18 -3
- mx_bluesky/hyperion/experiment_plans/set_energy_plan.py +2 -3
- mx_bluesky/hyperion/external_interaction/callbacks/common/abstract_event.py +66 -0
- mx_bluesky/hyperion/external_interaction/callbacks/grid_detection_callback.py +15 -15
- 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/xray_centre/ispyb_callback.py +5 -2
- 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/ispyb/exp_eye_store.py +1 -1
- 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.1a0.dist-info}/METADATA +35 -34
- {mx_bluesky-1.4.0.dist-info → mx_bluesky-1.4.1a0.dist-info}/RECORD +62 -58
- {mx_bluesky-1.4.0.dist-info → mx_bluesky-1.4.1a0.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.1a0.dist-info}/LICENSE +0 -0
- {mx_bluesky-1.4.0.dist-info → mx_bluesky-1.4.1a0.dist-info}/entry_points.txt +0 -0
- {mx_bluesky-1.4.0.dist-info → mx_bluesky-1.4.1a0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from functools import cache
|
|
3
|
+
|
|
4
|
+
from daq_config_server.client import ConfigServer
|
|
5
|
+
from pydantic import BaseModel, Field, model_validator
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class FeatureFlags(BaseModel, ABC):
|
|
9
|
+
"""Abstract class to use ConfigServer to toggle features for an experiment
|
|
10
|
+
|
|
11
|
+
A module wanting to use FeatureFlags should inherit this class, add boolean features
|
|
12
|
+
as attributes, and implement a get_config_server method, which returns a cached creation of
|
|
13
|
+
ConfigServer. See HyperionFeatureFlags for an example
|
|
14
|
+
|
|
15
|
+
Values supplied upon class instantiation will always take priority over the config server. If connection to the server cannot
|
|
16
|
+
be made AND values were not supplied, attributes will use their default values
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
# Feature values supplied at construction will override values from the config server
|
|
20
|
+
overriden_features: dict = Field(default_factory=dict, exclude=True)
|
|
21
|
+
|
|
22
|
+
@staticmethod
|
|
23
|
+
@cache
|
|
24
|
+
@abstractmethod
|
|
25
|
+
def get_config_server() -> ConfigServer: ...
|
|
26
|
+
|
|
27
|
+
@model_validator(mode="before")
|
|
28
|
+
@classmethod
|
|
29
|
+
def mark_overridden_features(cls, values):
|
|
30
|
+
assert isinstance(values, dict)
|
|
31
|
+
values["overriden_features"] = values.copy()
|
|
32
|
+
return values
|
|
33
|
+
|
|
34
|
+
def _get_flags(self):
|
|
35
|
+
flags = type(self).get_config_server().best_effort_get_all_feature_flags()
|
|
36
|
+
return {f: flags[f] for f in flags if f in self.model_fields.keys()}
|
|
37
|
+
|
|
38
|
+
def update_self_from_server(self):
|
|
39
|
+
"""Used to update the feature flags from the server during a plan. Where there are flags which were explicitly set from externally supplied parameters, these values will be used instead."""
|
|
40
|
+
for flag, value in self._get_flags().items():
|
|
41
|
+
updated_value = (
|
|
42
|
+
value
|
|
43
|
+
if flag not in self.overriden_features.keys()
|
|
44
|
+
else self.overriden_features[flag]
|
|
45
|
+
)
|
|
46
|
+
setattr(self, flag, updated_value)
|
|
@@ -1,21 +1,33 @@
|
|
|
1
|
-
import
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
2
4
|
from abc import abstractmethod
|
|
3
5
|
from collections.abc import Sequence
|
|
4
6
|
from enum import StrEnum
|
|
5
7
|
from pathlib import Path
|
|
6
|
-
from typing import SupportsInt
|
|
8
|
+
from typing import Literal, SupportsInt, cast
|
|
7
9
|
|
|
8
10
|
from dodal.devices.aperturescatterguard import ApertureValue
|
|
9
11
|
from dodal.devices.detector import (
|
|
10
12
|
DetectorParams,
|
|
11
13
|
TriggerMode,
|
|
12
14
|
)
|
|
13
|
-
from pydantic import
|
|
15
|
+
from pydantic import (
|
|
16
|
+
BaseModel,
|
|
17
|
+
ConfigDict,
|
|
18
|
+
Field,
|
|
19
|
+
field_validator,
|
|
20
|
+
model_validator,
|
|
21
|
+
)
|
|
14
22
|
from pydantic_extra_types.semantic_version import SemanticVersion
|
|
15
23
|
from scanspec.core import AxesPoints
|
|
16
24
|
from semver import Version
|
|
17
25
|
|
|
18
|
-
from mx_bluesky.common.parameters.constants import
|
|
26
|
+
from mx_bluesky.common.parameters.constants import (
|
|
27
|
+
TEST_MODE,
|
|
28
|
+
DetectorParamConstants,
|
|
29
|
+
GridscanParamConstants,
|
|
30
|
+
)
|
|
19
31
|
|
|
20
32
|
PARAMETER_VERSION = Version.parse("5.2.0")
|
|
21
33
|
|
|
@@ -100,11 +112,6 @@ class MxBlueskyParameters(BaseModel):
|
|
|
100
112
|
), f"Parameter version too new! This version of hyperion uses {PARAMETER_VERSION}"
|
|
101
113
|
return version
|
|
102
114
|
|
|
103
|
-
@classmethod
|
|
104
|
-
def from_json(cls, input: str | None):
|
|
105
|
-
assert input is not None
|
|
106
|
-
return cls(**json.loads(input))
|
|
107
|
-
|
|
108
115
|
|
|
109
116
|
class WithSnapshot(BaseModel):
|
|
110
117
|
snapshot_directory: Path
|
|
@@ -146,12 +153,12 @@ class DiffractionExperiment(
|
|
|
146
153
|
|
|
147
154
|
@model_validator(mode="before")
|
|
148
155
|
@classmethod
|
|
149
|
-
def
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
156
|
+
def validate_directories(cls, values):
|
|
157
|
+
os.makedirs(values["storage_directory"], exist_ok=True)
|
|
158
|
+
|
|
159
|
+
values["snapshot_directory"] = values.get(
|
|
160
|
+
"snapshot_directory",
|
|
161
|
+
Path(values["storage_directory"], "snapshots").as_posix(),
|
|
155
162
|
)
|
|
156
163
|
return values
|
|
157
164
|
|
|
@@ -176,6 +183,14 @@ class WithScan(BaseModel):
|
|
|
176
183
|
def num_images(self) -> int: ...
|
|
177
184
|
|
|
178
185
|
|
|
186
|
+
class WithPandaGridScan(BaseModel):
|
|
187
|
+
"""For experiments which use a PandA for constant-motion grid scans"""
|
|
188
|
+
|
|
189
|
+
panda_runup_distance_mm: float = Field(
|
|
190
|
+
default=GridscanParamConstants.PANDA_RUN_UP_DISTANCE_MM
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
|
|
179
194
|
class SplitScan(BaseModel):
|
|
180
195
|
@property
|
|
181
196
|
@abstractmethod
|
|
@@ -193,6 +208,28 @@ class WithSample(BaseModel):
|
|
|
193
208
|
class DiffractionExperimentWithSample(DiffractionExperiment, WithSample): ...
|
|
194
209
|
|
|
195
210
|
|
|
211
|
+
class MultiXtalSelection(BaseModel):
|
|
212
|
+
name: str
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
class TopNByMaxCountSelection(MultiXtalSelection):
|
|
216
|
+
name: Literal["TopNByMaxCount"] = "TopNByMaxCount" # pyright: ignore [reportIncompatibleVariableOverride]
|
|
217
|
+
n: int
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
class WithCentreSelection(BaseModel):
|
|
221
|
+
select_centres: TopNByMaxCountSelection = Field(
|
|
222
|
+
discriminator="name", default=TopNByMaxCountSelection(n=1)
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
@property
|
|
226
|
+
def selection_params(self) -> MultiXtalSelection:
|
|
227
|
+
"""A helper property because pydantic does not allow polymorphism with base classes
|
|
228
|
+
# only type unions"""
|
|
229
|
+
cast1 = cast(MultiXtalSelection, self.select_centres)
|
|
230
|
+
return cast1
|
|
231
|
+
|
|
232
|
+
|
|
196
233
|
class OptionalXyzStarts(BaseModel):
|
|
197
234
|
x_start_um: float | None = None
|
|
198
235
|
y_start_um: float | None = None
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from enum import Enum
|
|
2
2
|
|
|
3
3
|
from dodal.devices.aperturescatterguard import ApertureValue
|
|
4
|
+
from dodal.devices.detector import EIGER2_X_16M_SIZE
|
|
4
5
|
from dodal.devices.zocalo.zocalo_constants import ZOCALO_ENV as ZOCALO_ENV_FROM_DODAL
|
|
5
6
|
from dodal.utils import get_beamline_name
|
|
6
7
|
from pydantic.dataclasses import dataclass
|
|
@@ -19,6 +20,7 @@ class DocDescriptorNames:
|
|
|
19
20
|
HARDWARE_READ_PRE = "read_hardware_for_callbacks_pre_collection"
|
|
20
21
|
HARDWARE_READ_DURING = "read_hardware_for_callbacks_during_collection"
|
|
21
22
|
ZOCALO_HW_READ = "zocalo_read_hardware_plan"
|
|
23
|
+
FLYSCAN_RESULTS = "flyscan_results_obtained"
|
|
22
24
|
|
|
23
25
|
|
|
24
26
|
@dataclass(frozen=True)
|
|
@@ -47,6 +49,7 @@ class PlanNameConstants:
|
|
|
47
49
|
ROTATION_MULTI = "multi_rotation_wrapper"
|
|
48
50
|
ROTATION_OUTER = "rotation_scan_with_cleanup"
|
|
49
51
|
ROTATION_MAIN = "rotation_scan_main"
|
|
52
|
+
FLYSCAN_RESULTS = "xray_centre_results"
|
|
50
53
|
|
|
51
54
|
|
|
52
55
|
@dataclass(frozen=True)
|
|
@@ -64,6 +67,7 @@ class HardwareConstants:
|
|
|
64
67
|
OAV_REFRESH_DELAY = 0.3
|
|
65
68
|
PANDA_FGS_RUN_UP_DEFAULT = 0.17
|
|
66
69
|
CRYOJET_MARGIN_MM = 0.2
|
|
70
|
+
THAWING_TIME = 20
|
|
67
71
|
|
|
68
72
|
|
|
69
73
|
@dataclass(frozen=True)
|
|
@@ -87,8 +91,9 @@ class DetectorParamConstants:
|
|
|
87
91
|
BEAM_XY_LUT_PATH = (
|
|
88
92
|
"tests/test_data/test_det_dist_converter.txt"
|
|
89
93
|
if TEST_MODE
|
|
90
|
-
else "/dls_sw/{BEAMLINE}/software/daq_configuration/lookup/DetDistToBeamXYConverter.txt"
|
|
94
|
+
else f"/dls_sw/{BEAMLINE}/software/daq_configuration/lookup/DetDistToBeamXYConverter.txt"
|
|
91
95
|
)
|
|
96
|
+
DETECTOR = EIGER2_X_16M_SIZE
|
|
92
97
|
|
|
93
98
|
|
|
94
99
|
@dataclass(frozen=True)
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from dodal.devices.aperturescatterguard import ApertureValue
|
|
6
|
+
from dodal.devices.detector import (
|
|
7
|
+
DetectorParams,
|
|
8
|
+
)
|
|
9
|
+
from pydantic import Field
|
|
10
|
+
|
|
11
|
+
from mx_bluesky.common.parameters.components import (
|
|
12
|
+
DiffractionExperimentWithSample,
|
|
13
|
+
IspybExperimentType,
|
|
14
|
+
OptionalGonioAngleStarts,
|
|
15
|
+
WithScan,
|
|
16
|
+
XyzStarts,
|
|
17
|
+
)
|
|
18
|
+
from mx_bluesky.common.parameters.constants import (
|
|
19
|
+
DetectorParamConstants,
|
|
20
|
+
GridscanParamConstants,
|
|
21
|
+
HardwareConstants,
|
|
22
|
+
)
|
|
23
|
+
from mx_bluesky.common.parameters.robot_load import RobotLoadAndEnergyChange
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class GridCommon(
|
|
27
|
+
DiffractionExperimentWithSample,
|
|
28
|
+
OptionalGonioAngleStarts,
|
|
29
|
+
):
|
|
30
|
+
grid_width_um: float = Field(default=GridscanParamConstants.WIDTH_UM)
|
|
31
|
+
exposure_time_s: float = Field(default=GridscanParamConstants.EXPOSURE_TIME_S)
|
|
32
|
+
use_roi_mode: bool = Field(default=GridscanParamConstants.USE_ROI)
|
|
33
|
+
|
|
34
|
+
ispyb_experiment_type: IspybExperimentType = Field(
|
|
35
|
+
default=IspybExperimentType.GRIDSCAN_3D
|
|
36
|
+
)
|
|
37
|
+
selected_aperture: ApertureValue | None = Field(default=ApertureValue.SMALL)
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def detector_params(self):
|
|
41
|
+
self.det_dist_to_beam_converter_path = (
|
|
42
|
+
self.det_dist_to_beam_converter_path
|
|
43
|
+
or DetectorParamConstants.BEAM_XY_LUT_PATH
|
|
44
|
+
)
|
|
45
|
+
optional_args = {}
|
|
46
|
+
if self.run_number:
|
|
47
|
+
optional_args["run_number"] = self.run_number
|
|
48
|
+
assert (
|
|
49
|
+
self.detector_distance_mm is not None
|
|
50
|
+
), "Detector distance must be filled before generating DetectorParams"
|
|
51
|
+
os.makedirs(self.storage_directory, exist_ok=True)
|
|
52
|
+
return DetectorParams(
|
|
53
|
+
detector_size_constants=DetectorParamConstants.DETECTOR,
|
|
54
|
+
expected_energy_ev=self.demand_energy_ev,
|
|
55
|
+
exposure_time=self.exposure_time_s,
|
|
56
|
+
directory=self.storage_directory,
|
|
57
|
+
prefix=self.file_name,
|
|
58
|
+
detector_distance=self.detector_distance_mm,
|
|
59
|
+
omega_start=self.omega_start_deg or 0,
|
|
60
|
+
omega_increment=0,
|
|
61
|
+
num_images_per_trigger=1,
|
|
62
|
+
num_triggers=self.num_images,
|
|
63
|
+
use_roi_mode=self.use_roi_mode,
|
|
64
|
+
det_dist_to_beam_converter_path=self.det_dist_to_beam_converter_path,
|
|
65
|
+
trigger_mode=self.trigger_mode,
|
|
66
|
+
**optional_args,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class RobotLoadThenCentre(GridCommon):
|
|
71
|
+
thawing_time: float = Field(default=HardwareConstants.THAWING_TIME)
|
|
72
|
+
|
|
73
|
+
def robot_load_params(self):
|
|
74
|
+
my_params = self.model_dump()
|
|
75
|
+
return RobotLoadAndEnergyChange(**my_params)
|
|
76
|
+
|
|
77
|
+
def pin_centre_then_xray_centre_params(self):
|
|
78
|
+
my_params = self.model_dump()
|
|
79
|
+
del my_params["thawing_time"]
|
|
80
|
+
return PinTipCentreThenXrayCentre(**my_params)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class GridScanWithEdgeDetect(GridCommon):
|
|
84
|
+
box_size_um: float = Field(default=GridscanParamConstants.BOX_WIDTH_UM)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class PinTipCentreThenXrayCentre(GridCommon):
|
|
88
|
+
tip_offset_um: float = 0
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class SpecifiedGrid(XyzStarts, WithScan):
|
|
92
|
+
"""A specified grid is one which has defined values for the start position,
|
|
93
|
+
grid and box sizes, etc., as opposed to parameters for a plan which will create
|
|
94
|
+
those parameters at some point (e.g. through optical pin detection)."""
|
|
@@ -7,10 +7,10 @@ from mx_bluesky.common.parameters.components import (
|
|
|
7
7
|
WithSnapshot,
|
|
8
8
|
WithVisit,
|
|
9
9
|
)
|
|
10
|
-
from mx_bluesky.
|
|
10
|
+
from mx_bluesky.common.parameters.constants import HardwareConstants
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class RobotLoadAndEnergyChange(
|
|
14
14
|
MxBlueskyParameters, WithSample, WithSnapshot, WithOptionalEnergyChange, WithVisit
|
|
15
15
|
):
|
|
16
|
-
thawing_time: float = Field(default=
|
|
16
|
+
thawing_time: float = Field(default=HardwareConstants.THAWING_TIME)
|
|
@@ -3,7 +3,7 @@ from time import time
|
|
|
3
3
|
|
|
4
4
|
import bluesky.plan_stubs as bps
|
|
5
5
|
import bluesky.preprocessors as bpp
|
|
6
|
-
from
|
|
6
|
+
from bluesky.utils import MsgGenerator
|
|
7
7
|
from dodal.devices.eiger import EigerDetector
|
|
8
8
|
from dodal.devices.fast_grid_scan import FastGridScanCommon
|
|
9
9
|
from dodal.devices.synchrotron import Synchrotron
|
|
@@ -11,7 +11,7 @@ from dodal.devices.zocalo.zocalo_results import (
|
|
|
11
11
|
ZOCALO_STAGE_GROUP,
|
|
12
12
|
)
|
|
13
13
|
from dodal.log import LOGGER
|
|
14
|
-
from dodal.
|
|
14
|
+
from dodal.plan_stubs.check_topup import check_topup_and_wait_if_necessary
|
|
15
15
|
from scanspec.core import AxesPoints, Axis
|
|
16
16
|
|
|
17
17
|
from mx_bluesky.common.device_setup_plans.read_hardware_for_setup import (
|
mx_bluesky/common/utils/log.py
CHANGED
mx_bluesky/hyperion/__main__.py
CHANGED
|
@@ -7,9 +7,10 @@ from queue import Queue
|
|
|
7
7
|
from traceback import format_exception
|
|
8
8
|
from typing import Any
|
|
9
9
|
|
|
10
|
-
from blueapi.core import BlueskyContext
|
|
10
|
+
from blueapi.core import BlueskyContext
|
|
11
11
|
from bluesky.callbacks.zmq import Publisher
|
|
12
12
|
from bluesky.run_engine import RunEngine
|
|
13
|
+
from bluesky.utils import MsgGenerator
|
|
13
14
|
from flask import Flask, request
|
|
14
15
|
from flask_restful import Api, Resource
|
|
15
16
|
from pydantic.dataclasses import dataclass
|
|
@@ -4,7 +4,7 @@ from importlib import resources
|
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
|
|
6
6
|
import bluesky.plan_stubs as bps
|
|
7
|
-
from
|
|
7
|
+
from bluesky.utils import MsgGenerator
|
|
8
8
|
from dodal.common.beamlines.beamline_utils import get_path_provider
|
|
9
9
|
from dodal.devices.fast_grid_scan import PandAGridScanParams
|
|
10
10
|
from ophyd_async.core import load_device
|
|
@@ -63,12 +63,12 @@ def _get_seq_table(
|
|
|
63
63
|
An instance of SeqTable describing the panda sequencer table
|
|
64
64
|
"""
|
|
65
65
|
|
|
66
|
-
start_of_grid_x_counts = int(parameters.
|
|
66
|
+
start_of_grid_x_counts = int(parameters.x_start_mm * MM_TO_ENCODER_COUNTS)
|
|
67
67
|
|
|
68
68
|
# x_start is the first trigger point, so we need to travel to x_steps-1 for the final trigger point
|
|
69
69
|
end_of_grid_x_counts = int(
|
|
70
70
|
start_of_grid_x_counts
|
|
71
|
-
+ (parameters.
|
|
71
|
+
+ (parameters.x_step_size_mm * (parameters.x_steps - 1) * MM_TO_ENCODER_COUNTS)
|
|
72
72
|
)
|
|
73
73
|
|
|
74
74
|
exposure_distance_x_counts = int(exposure_distance_mm * MM_TO_ENCODER_COUNTS)
|
|
@@ -140,7 +140,7 @@ def setup_panda_for_flyscan(
|
|
|
140
140
|
"""
|
|
141
141
|
assert parameters.x_steps > 0
|
|
142
142
|
assert time_between_x_steps_ms * 1000 >= exposure_time_s
|
|
143
|
-
assert sample_velocity_mm_per_s * exposure_time_s < parameters.
|
|
143
|
+
assert sample_velocity_mm_per_s * exposure_time_s < parameters.x_step_size_mm
|
|
144
144
|
|
|
145
145
|
yield from bps.stage(panda, group="panda-config")
|
|
146
146
|
|
|
@@ -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,
|