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
|
@@ -1,105 +1,107 @@
|
|
|
1
|
-
import logging
|
|
2
1
|
import os
|
|
3
2
|
import pathlib
|
|
4
3
|
import pprint
|
|
5
4
|
import time
|
|
6
5
|
from datetime import datetime
|
|
7
|
-
from typing import Literal
|
|
8
6
|
|
|
9
7
|
import requests
|
|
10
8
|
|
|
11
9
|
from mx_bluesky.beamlines.i24.serial.fixed_target.ft_utils import ChipType, MappingType
|
|
10
|
+
from mx_bluesky.beamlines.i24.serial.log import SSX_LOGGER
|
|
12
11
|
from mx_bluesky.beamlines.i24.serial.parameters import (
|
|
13
12
|
ExtruderParameters,
|
|
14
13
|
FixedTargetParameters,
|
|
15
14
|
)
|
|
16
|
-
from mx_bluesky.beamlines.i24.serial.setup_beamline import Eiger, caget
|
|
17
|
-
|
|
18
|
-
logger = logging.getLogger("I24ssx.nexus_writer")
|
|
15
|
+
from mx_bluesky.beamlines.i24.serial.setup_beamline import Eiger, caget
|
|
19
16
|
|
|
20
17
|
|
|
21
18
|
def call_nexgen(
|
|
22
19
|
chip_prog_dict: dict | None,
|
|
23
|
-
start_time: datetime,
|
|
24
20
|
parameters: ExtruderParameters | FixedTargetParameters,
|
|
25
|
-
|
|
26
|
-
|
|
21
|
+
wavelength_in_a: float,
|
|
22
|
+
beam_center_in_pix: tuple[float, float],
|
|
23
|
+
start_time: datetime | None = None,
|
|
27
24
|
):
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
"""Call the nexus writer by sending a request to nexgen-server.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
chip_prog_dict (dict | None): Dictionary containing most of the information \
|
|
29
|
+
passed to the program runner for the collection. Only used for fixed target.
|
|
30
|
+
start_time
|
|
31
|
+
parameters (SerialAndLaserExperiment): Collection parameters.
|
|
32
|
+
wavelength_in_a (float): Wavelength, in A.
|
|
33
|
+
beam_center_in_pix (list[float]): Beam center position on detector, in pixels.
|
|
34
|
+
start_time (datetime, optional): Collection start time.
|
|
35
|
+
|
|
36
|
+
Raises:
|
|
37
|
+
ValueError: For a wrong experiment type passed (either unknwon or not matched \
|
|
38
|
+
to parameter model).
|
|
30
39
|
|
|
40
|
+
"""
|
|
31
41
|
current_chip_map = None
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
42
|
+
match parameters:
|
|
43
|
+
case FixedTargetParameters():
|
|
44
|
+
if not (
|
|
45
|
+
parameters.map_type == MappingType.NoMap
|
|
46
|
+
or parameters.chip.chip_type == ChipType.Custom
|
|
47
|
+
):
|
|
48
|
+
# NOTE Nexgen server is still on nexgen v0.7.2 (fully working for ssx)
|
|
49
|
+
# Will need to be updated, for correctness sake map needs to be None.
|
|
50
|
+
current_chip_map = (
|
|
51
|
+
"/dls_sw/i24/scripts/fastchips/litemaps/currentchip.map"
|
|
52
|
+
)
|
|
53
|
+
pump_status = bool(parameters.pump_repeat)
|
|
54
|
+
total_numb_imgs = parameters.total_num_images
|
|
55
|
+
case ExtruderParameters():
|
|
56
|
+
total_numb_imgs = parameters.num_images
|
|
57
|
+
pump_status = parameters.pump_status
|
|
48
58
|
|
|
49
|
-
filename_prefix =
|
|
50
|
-
meta_h5 =
|
|
51
|
-
pathlib.Path(parameters.visit)
|
|
52
|
-
/ parameters.directory
|
|
53
|
-
/ f"{filename_prefix}_meta.h5"
|
|
54
|
-
)
|
|
59
|
+
filename_prefix = parameters.filename
|
|
60
|
+
meta_h5 = parameters.visit / parameters.directory / f"{filename_prefix}_meta.h5"
|
|
55
61
|
t0 = time.time()
|
|
56
62
|
max_wait = 60 # seconds
|
|
57
|
-
|
|
63
|
+
SSX_LOGGER.info(f"Watching for {meta_h5}")
|
|
58
64
|
while time.time() - t0 < max_wait:
|
|
59
65
|
if meta_h5.exists():
|
|
60
|
-
|
|
66
|
+
SSX_LOGGER.info(f"Found {meta_h5} after {time.time() - t0:.1f} seconds")
|
|
61
67
|
time.sleep(5)
|
|
62
68
|
break
|
|
63
|
-
|
|
69
|
+
SSX_LOGGER.debug(f"Waiting for {meta_h5}")
|
|
64
70
|
time.sleep(1)
|
|
65
71
|
if not meta_h5.exists():
|
|
66
|
-
|
|
67
|
-
return
|
|
68
|
-
|
|
69
|
-
transmission = (float(caget(Eiger.pv.transmission)),)
|
|
72
|
+
SSX_LOGGER.warning(f"Giving up waiting for {meta_h5} after {max_wait} seconds")
|
|
73
|
+
return
|
|
70
74
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
)
|
|
75
|
+
bit_depth = int(caget(Eiger.pv.bit_depth))
|
|
76
|
+
SSX_LOGGER.debug(
|
|
77
|
+
f"Call to nexgen server with the following chip definition: \n{chip_prog_dict}"
|
|
78
|
+
)
|
|
76
79
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
+
access_token = pathlib.Path("/scratch/ssx_nexgen.key").read_text().strip()
|
|
81
|
+
url = "https://ssx-nexgen.diamond.ac.uk/ssx_eiger/write"
|
|
82
|
+
headers = {"Authorization": f"Bearer {access_token}"}
|
|
80
83
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
return False
|
|
84
|
+
payload = {
|
|
85
|
+
"beamline": "i24",
|
|
86
|
+
"beam_center": beam_center_in_pix,
|
|
87
|
+
"chipmap": current_chip_map,
|
|
88
|
+
"chip_info": chip_prog_dict,
|
|
89
|
+
"det_dist": parameters.detector_distance_mm,
|
|
90
|
+
"exp_time": parameters.exposure_time_s,
|
|
91
|
+
"expt_type": parameters.nexgen_experiment_type,
|
|
92
|
+
"filename": filename_prefix,
|
|
93
|
+
"num_imgs": total_numb_imgs,
|
|
94
|
+
"pump_status": pump_status,
|
|
95
|
+
"pump_exp": parameters.laser_dwell_s,
|
|
96
|
+
"pump_delay": parameters.laser_delay_s,
|
|
97
|
+
"transmission": parameters.transmission,
|
|
98
|
+
"visitpath": os.fspath(meta_h5.parent),
|
|
99
|
+
"wavelength": wavelength_in_a,
|
|
100
|
+
"bit_depth": bit_depth,
|
|
101
|
+
"start_time": start_time,
|
|
102
|
+
}
|
|
103
|
+
SSX_LOGGER.info(f"Sending POST request to {url} with payload:")
|
|
104
|
+
SSX_LOGGER.info(pprint.pformat(payload))
|
|
105
|
+
response = requests.post(url, headers=headers, json=payload)
|
|
106
|
+
response.raise_for_status()
|
|
107
|
+
SSX_LOGGER.info(f"Response: {response.text} (status code: {response.status_code})")
|
|
@@ -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
|
|
@@ -18,7 +19,9 @@ class DocDescriptorNames:
|
|
|
18
19
|
OAV_GRID_SNAPSHOT_TRIGGERED = "snapshot_to_ispyb"
|
|
19
20
|
HARDWARE_READ_PRE = "read_hardware_for_callbacks_pre_collection"
|
|
20
21
|
HARDWARE_READ_DURING = "read_hardware_for_callbacks_during_collection"
|
|
22
|
+
SAMPLE_HANDLING_EXCEPTION = "sample_handling_exception"
|
|
21
23
|
ZOCALO_HW_READ = "zocalo_read_hardware_plan"
|
|
24
|
+
FLYSCAN_RESULTS = "flyscan_results_obtained"
|
|
22
25
|
|
|
23
26
|
|
|
24
27
|
@dataclass(frozen=True)
|
|
@@ -34,6 +37,7 @@ class OavConstants:
|
|
|
34
37
|
|
|
35
38
|
@dataclass(frozen=True)
|
|
36
39
|
class PlanNameConstants:
|
|
40
|
+
LOAD_CENTRE_COLLECT = "load_centre_collect"
|
|
37
41
|
# Robot load subplan
|
|
38
42
|
ROBOT_LOAD = "robot_load"
|
|
39
43
|
# Gridscan
|
|
@@ -43,10 +47,14 @@ class PlanNameConstants:
|
|
|
43
47
|
GRIDSCAN_AND_MOVE = "run_gridscan_and_move"
|
|
44
48
|
GRIDSCAN_MAIN = "run_gridscan"
|
|
45
49
|
DO_FGS = "do_fgs"
|
|
50
|
+
# IspyB callback activation
|
|
51
|
+
ISPYB_ACTIVATION = "ispyb_activation"
|
|
52
|
+
ROBOT_LOAD_AND_SNAPSHOTS = "robot_load_and_snapshots"
|
|
46
53
|
# Rotation scan
|
|
47
54
|
ROTATION_MULTI = "multi_rotation_wrapper"
|
|
48
55
|
ROTATION_OUTER = "rotation_scan_with_cleanup"
|
|
49
56
|
ROTATION_MAIN = "rotation_scan_main"
|
|
57
|
+
FLYSCAN_RESULTS = "xray_centre_results"
|
|
50
58
|
|
|
51
59
|
|
|
52
60
|
@dataclass(frozen=True)
|
|
@@ -64,6 +72,7 @@ class HardwareConstants:
|
|
|
64
72
|
OAV_REFRESH_DELAY = 0.3
|
|
65
73
|
PANDA_FGS_RUN_UP_DEFAULT = 0.17
|
|
66
74
|
CRYOJET_MARGIN_MM = 0.2
|
|
75
|
+
THAWING_TIME = 20
|
|
67
76
|
|
|
68
77
|
|
|
69
78
|
@dataclass(frozen=True)
|
|
@@ -87,8 +96,9 @@ class DetectorParamConstants:
|
|
|
87
96
|
BEAM_XY_LUT_PATH = (
|
|
88
97
|
"tests/test_data/test_det_dist_converter.txt"
|
|
89
98
|
if TEST_MODE
|
|
90
|
-
else "/dls_sw/{BEAMLINE}/software/daq_configuration/lookup/DetDistToBeamXYConverter.txt"
|
|
99
|
+
else f"/dls_sw/{BEAMLINE}/software/daq_configuration/lookup/DetDistToBeamXYConverter.txt"
|
|
91
100
|
)
|
|
101
|
+
DETECTOR = EIGER2_X_16M_SIZE
|
|
92
102
|
|
|
93
103
|
|
|
94
104
|
@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
|
|
@@ -19,6 +19,7 @@ from mx_bluesky.hyperion.utils.utils import (
|
|
|
19
19
|
|
|
20
20
|
MIRROR_VOLTAGE_GROUP = "MIRROR_VOLTAGE_GROUP"
|
|
21
21
|
DCM_GROUP = "DCM_GROUP"
|
|
22
|
+
YAW_LAT_TIMEOUT_S = 30
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
def _apply_and_wait_for_voltages_to_settle(
|
|
@@ -46,31 +47,42 @@ def _apply_and_wait_for_voltages_to_settle(
|
|
|
46
47
|
for voltage_channel, required_voltage in zip(
|
|
47
48
|
channels.values(), required_voltages, strict=True
|
|
48
49
|
):
|
|
49
|
-
LOGGER.
|
|
50
|
+
LOGGER.info(
|
|
50
51
|
f"Applying and waiting for voltage {voltage_channel.name} = {required_voltage}"
|
|
51
52
|
)
|
|
52
53
|
yield from bps.abs_set(
|
|
53
|
-
voltage_channel, required_voltage, group=MIRROR_VOLTAGE_GROUP
|
|
54
|
+
voltage_channel, required_voltage, group=MIRROR_VOLTAGE_GROUP, wait=True
|
|
54
55
|
)
|
|
55
56
|
|
|
56
|
-
yield from bps.wait(group=MIRROR_VOLTAGE_GROUP)
|
|
57
|
-
|
|
58
57
|
|
|
59
58
|
def adjust_mirror_stripe(
|
|
60
59
|
energy_kev, mirror: FocusingMirrorWithStripes, mirror_voltages: MirrorVoltages
|
|
61
60
|
):
|
|
62
61
|
"""Feedback should be OFF prior to entry, in order to prevent
|
|
63
62
|
feedback from making unnecessary corrections while beam is being adjusted."""
|
|
64
|
-
|
|
63
|
+
mirror_config = mirror.energy_to_stripe(energy_kev)
|
|
65
64
|
|
|
66
65
|
LOGGER.info(
|
|
67
|
-
f"Adjusting mirror stripe for {energy_kev}keV selecting {stripe} stripe"
|
|
66
|
+
f"Adjusting mirror stripe for {energy_kev}keV selecting {mirror_config['stripe']} stripe"
|
|
68
67
|
)
|
|
69
|
-
yield from bps.abs_set(mirror.stripe, stripe, wait=True)
|
|
68
|
+
yield from bps.abs_set(mirror.stripe, mirror_config["stripe"], wait=True)
|
|
70
69
|
yield from bps.trigger(mirror.apply_stripe)
|
|
71
70
|
|
|
71
|
+
# yaw, lat cannot be done simultaneously
|
|
72
|
+
LOGGER.info(f"Adjusting {mirror.name} lat to {mirror_config['lat_mm']}")
|
|
73
|
+
yield from bps.abs_set(
|
|
74
|
+
mirror.x_mm, mirror_config["lat_mm"], wait=True, timeout=YAW_LAT_TIMEOUT_S
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
LOGGER.info(f"Adjusting {mirror.name} yaw to {mirror_config['yaw_mrad']}")
|
|
78
|
+
yield from bps.abs_set(
|
|
79
|
+
mirror.yaw_mrad, mirror_config["yaw_mrad"], wait=True, timeout=YAW_LAT_TIMEOUT_S
|
|
80
|
+
)
|
|
81
|
+
|
|
72
82
|
LOGGER.info("Adjusting mirror voltages...")
|
|
73
|
-
yield from _apply_and_wait_for_voltages_to_settle(
|
|
83
|
+
yield from _apply_and_wait_for_voltages_to_settle(
|
|
84
|
+
mirror_config["stripe"], mirror_voltages
|
|
85
|
+
)
|
|
74
86
|
|
|
75
87
|
|
|
76
88
|
def adjust_dcm_pitch_roll_vfm_from_lut(
|
|
@@ -109,31 +121,9 @@ def adjust_dcm_pitch_roll_vfm_from_lut(
|
|
|
109
121
|
yield from dcm_roll_adjuster(DCM_GROUP)
|
|
110
122
|
LOGGER.info("Waiting for DCM roll adjust to complete...")
|
|
111
123
|
|
|
112
|
-
# DCM Perp pitch
|
|
113
|
-
offset_mm = undulator_dcm.dcm_fixed_offset_mm
|
|
114
|
-
LOGGER.info(f"Adjusting DCM offset to {offset_mm} mm")
|
|
115
|
-
yield from bps.abs_set(dcm.offset_in_mm, offset_mm, group=DCM_GROUP)
|
|
116
|
-
|
|
117
124
|
#
|
|
118
|
-
# Adjust
|
|
125
|
+
# Adjust vfm mirror stripe and mirror voltages
|
|
119
126
|
#
|
|
120
127
|
|
|
121
|
-
# No need to change HFM
|
|
122
|
-
|
|
123
|
-
# Assumption is focus mode is already set to "sample"
|
|
124
|
-
# not sure how we check this
|
|
125
|
-
|
|
126
128
|
# VFM Stripe selection
|
|
127
129
|
yield from adjust_mirror_stripe(energy_kev, vfm, mirror_voltages)
|
|
128
|
-
yield from bps.wait(DCM_GROUP)
|
|
129
|
-
|
|
130
|
-
# VFM Adjust - for I03 this table always returns the same value
|
|
131
|
-
vfm_lut = vfm.bragg_to_lat_lookup_table_path
|
|
132
|
-
assert vfm_lut is not None
|
|
133
|
-
vfm_x_adjuster = lookup_table_adjuster(
|
|
134
|
-
linear_interpolation_lut(vfm_lut),
|
|
135
|
-
vfm.x_mm,
|
|
136
|
-
bragg_deg,
|
|
137
|
-
)
|
|
138
|
-
LOGGER.info("Waiting for VFM Lat (Horizontal Translation) to complete...")
|
|
139
|
-
yield from vfm_x_adjuster()
|
|
@@ -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
|
|