mx-bluesky 1.4.2__py3-none-any.whl → 1.4.4__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/i24/serial/dcid.py +3 -3
- mx_bluesky/beamlines/i24/serial/extruder/i24ssx_Extruder_Collect_py3v2.py +24 -9
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Collect_py3v1.py +13 -4
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Manager_py3v1.py +1 -1
- mx_bluesky/beamlines/i24/serial/parameters/__init__.py +2 -1
- mx_bluesky/beamlines/i24/serial/parameters/constants.py +13 -5
- mx_bluesky/beamlines/i24/serial/parameters/experiment_parameters.py +20 -4
- mx_bluesky/beamlines/i24/serial/setup_beamline/setup_beamline.py +40 -11
- mx_bluesky/beamlines/i24/serial/setup_beamline/setup_zebra_plans.py +81 -40
- mx_bluesky/{hyperion/external_interaction/callbacks → common/external_interaction/callbacks/common}/aperture_change_callback.py +1 -1
- mx_bluesky/{hyperion/external_interaction/callbacks → common/external_interaction/callbacks/common}/grid_detection_callback.py +19 -1
- mx_bluesky/{hyperion/external_interaction/callbacks → common/external_interaction/callbacks/common}/ispyb_callback_base.py +40 -34
- mx_bluesky/{hyperion → common}/external_interaction/callbacks/common/ispyb_mapping.py +4 -4
- mx_bluesky/{hyperion/external_interaction/callbacks → common/external_interaction/callbacks/common}/logging_callback.py +1 -1
- mx_bluesky/{hyperion/external_interaction/callbacks → common/external_interaction/callbacks/common}/zocalo_callback.py +13 -19
- mx_bluesky/{hyperion → common}/external_interaction/callbacks/xray_centre/ispyb_callback.py +39 -34
- mx_bluesky/{hyperion → common}/external_interaction/callbacks/xray_centre/ispyb_mapping.py +2 -2
- mx_bluesky/{hyperion → common}/external_interaction/callbacks/xray_centre/nexus_callback.py +20 -15
- mx_bluesky/common/external_interaction/config_server.py +11 -0
- mx_bluesky/common/external_interaction/ispyb/__init__.py +0 -0
- mx_bluesky/{hyperion → common}/external_interaction/ispyb/data_model.py +2 -0
- mx_bluesky/{hyperion → common}/external_interaction/ispyb/exp_eye_store.py +5 -5
- mx_bluesky/{hyperion → common}/external_interaction/ispyb/ispyb_store.py +20 -18
- mx_bluesky/{hyperion → common}/external_interaction/ispyb/ispyb_utils.py +2 -2
- mx_bluesky/common/external_interaction/nexus/__init__.py +0 -0
- mx_bluesky/{hyperion → common}/external_interaction/nexus/nexus_utils.py +21 -6
- mx_bluesky/{hyperion → common}/external_interaction/nexus/write_nexus.py +5 -5
- mx_bluesky/common/external_interaction/test_config_server.py +38 -0
- mx_bluesky/common/parameters/components.py +9 -8
- mx_bluesky/common/parameters/constants.py +1 -0
- mx_bluesky/common/parameters/gridscan.py +107 -53
- mx_bluesky/common/plans/do_fgs.py +4 -10
- mx_bluesky/{hyperion → common/utils}/exceptions.py +15 -1
- mx_bluesky/common/utils/log.py +17 -7
- mx_bluesky/hyperion/__main__.py +15 -14
- mx_bluesky/hyperion/device_setup_plans/check_beamstop.py +27 -0
- mx_bluesky/hyperion/device_setup_plans/dcm_pitch_roll_mirror_adjuster.py +13 -6
- mx_bluesky/hyperion/device_setup_plans/manipulate_sample.py +1 -1
- mx_bluesky/hyperion/device_setup_plans/position_detector.py +1 -1
- mx_bluesky/hyperion/device_setup_plans/read_hardware_for_setup.py +3 -3
- mx_bluesky/hyperion/device_setup_plans/setup_panda.py +21 -4
- mx_bluesky/hyperion/device_setup_plans/setup_zebra.py +62 -36
- mx_bluesky/hyperion/device_setup_plans/smargon.py +1 -1
- mx_bluesky/hyperion/device_setup_plans/utils.py +4 -0
- mx_bluesky/hyperion/device_setup_plans/xbpm_feedback.py +8 -8
- mx_bluesky/hyperion/experiment_plans/change_aperture_then_move_plan.py +28 -17
- mx_bluesky/hyperion/experiment_plans/common/xrc_result.py +10 -1
- mx_bluesky/hyperion/experiment_plans/experiment_registry.py +9 -9
- mx_bluesky/hyperion/experiment_plans/flyscan_xray_centre_plan.py +39 -49
- mx_bluesky/hyperion/experiment_plans/grid_detect_then_xray_centre_plan.py +22 -23
- mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py +4 -11
- mx_bluesky/hyperion/experiment_plans/oav_grid_detection_plan.py +3 -3
- mx_bluesky/hyperion/experiment_plans/optimise_attenuation_plan.py +6 -14
- mx_bluesky/hyperion/experiment_plans/pin_centre_then_xray_centre_plan.py +12 -11
- mx_bluesky/hyperion/experiment_plans/pin_tip_centring_plan.py +2 -2
- mx_bluesky/hyperion/experiment_plans/robot_load_and_change_energy.py +9 -4
- mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +10 -11
- mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +33 -17
- mx_bluesky/hyperion/experiment_plans/set_energy_plan.py +2 -2
- mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +32 -23
- mx_bluesky/hyperion/external_interaction/callbacks/common/callback_util.py +60 -34
- mx_bluesky/hyperion/external_interaction/callbacks/robot_load/ispyb_callback.py +22 -15
- mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py +25 -24
- mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_mapping.py +1 -1
- mx_bluesky/hyperion/external_interaction/callbacks/rotation/nexus_callback.py +13 -9
- mx_bluesky/hyperion/external_interaction/callbacks/sample_handling/sample_handling_callback.py +12 -46
- mx_bluesky/hyperion/external_interaction/config_server.py +15 -1
- mx_bluesky/hyperion/parameters/components.py +3 -2
- mx_bluesky/hyperion/parameters/constants.py +1 -0
- mx_bluesky/hyperion/parameters/gridscan.py +54 -89
- mx_bluesky/hyperion/parameters/load_centre_collect.py +51 -6
- mx_bluesky/hyperion/parameters/robot_load.py +40 -0
- mx_bluesky/hyperion/parameters/rotation.py +28 -3
- mx_bluesky/hyperion/utils/context.py +1 -1
- mx_bluesky/hyperion/utils/validation.py +4 -2
- {mx_bluesky-1.4.2.dist-info → mx_bluesky-1.4.4.dist-info}/METADATA +6 -6
- {mx_bluesky-1.4.2.dist-info → mx_bluesky-1.4.4.dist-info}/RECORD +89 -87
- {mx_bluesky-1.4.2.dist-info → mx_bluesky-1.4.4.dist-info}/WHEEL +1 -1
- mx_bluesky/common/parameters/robot_load.py +0 -16
- mx_bluesky/hyperion/external_interaction/exceptions.py +0 -4
- mx_bluesky/hyperion/log.py +0 -15
- /mx_bluesky/{hyperion/external_interaction/callbacks/xray_centre → common/external_interaction}/__init__.py +0 -0
- /mx_bluesky/{hyperion/external_interaction/ispyb → common/external_interaction/callbacks/common}/__init__.py +0 -0
- /mx_bluesky/{hyperion → common}/external_interaction/callbacks/common/abstract_event.py +0 -0
- /mx_bluesky/{hyperion/external_interaction/callbacks → common/external_interaction/callbacks/common}/log_uid_tag_callback.py +0 -0
- /mx_bluesky/{hyperion/external_interaction/callbacks → common/external_interaction/callbacks/common}/plan_reactive_callback.py +0 -0
- /mx_bluesky/{hyperion/external_interaction/nexus → common/external_interaction/callbacks/xray_centre}/__init__.py +0 -0
- /mx_bluesky/{hyperion → common}/utils/utils.py +0 -0
- {mx_bluesky-1.4.2.dist-info → mx_bluesky-1.4.4.dist-info}/LICENSE +0 -0
- {mx_bluesky-1.4.2.dist-info → mx_bluesky-1.4.4.dist-info}/entry_points.txt +0 -0
- {mx_bluesky-1.4.2.dist-info → mx_bluesky-1.4.4.dist-info}/top_level.txt +0 -0
|
@@ -11,18 +11,18 @@ from ispyb.sp.mxacquisition import MXAcquisition
|
|
|
11
11
|
from ispyb.strictordereddict import StrictOrderedDict
|
|
12
12
|
from pydantic import BaseModel
|
|
13
13
|
|
|
14
|
-
from mx_bluesky.common.
|
|
15
|
-
from mx_bluesky.hyperion.external_interaction.ispyb.data_model import (
|
|
14
|
+
from mx_bluesky.common.external_interaction.ispyb.data_model import (
|
|
16
15
|
DataCollectionGridInfo,
|
|
17
16
|
DataCollectionGroupInfo,
|
|
18
17
|
DataCollectionInfo,
|
|
19
18
|
ScanDataInfo,
|
|
20
19
|
)
|
|
21
|
-
from mx_bluesky.
|
|
20
|
+
from mx_bluesky.common.external_interaction.ispyb.ispyb_utils import (
|
|
22
21
|
get_current_time_string,
|
|
23
22
|
get_session_id_from_visit,
|
|
24
23
|
)
|
|
25
|
-
from mx_bluesky.
|
|
24
|
+
from mx_bluesky.common.utils.log import ISPYB_ZOCALO_CALLBACK_LOGGER
|
|
25
|
+
from mx_bluesky.common.utils.tracing import TRACER
|
|
26
26
|
|
|
27
27
|
if TYPE_CHECKING:
|
|
28
28
|
pass
|
|
@@ -62,12 +62,12 @@ class StoreInIspyb:
|
|
|
62
62
|
ispyb_ids,
|
|
63
63
|
scan_data_infos: Sequence[ScanDataInfo],
|
|
64
64
|
) -> IspybIds:
|
|
65
|
-
assert (
|
|
66
|
-
|
|
67
|
-
)
|
|
68
|
-
assert (
|
|
69
|
-
|
|
70
|
-
)
|
|
65
|
+
assert ispyb_ids.data_collection_group_id, (
|
|
66
|
+
"Attempted to store scan data without a collection group"
|
|
67
|
+
)
|
|
68
|
+
assert ispyb_ids.data_collection_ids, (
|
|
69
|
+
"Attempted to store scan data without a collection"
|
|
70
|
+
)
|
|
71
71
|
return self._begin_or_update_deposition(ispyb_ids, None, scan_data_infos)
|
|
72
72
|
|
|
73
73
|
def _begin_or_update_deposition(
|
|
@@ -87,7 +87,9 @@ class StoreInIspyb:
|
|
|
87
87
|
)
|
|
88
88
|
)
|
|
89
89
|
else:
|
|
90
|
-
assert ispyb_ids.data_collection_group_id,
|
|
90
|
+
assert ispyb_ids.data_collection_group_id, (
|
|
91
|
+
"Attempt to update data collection without a data collection group ID"
|
|
92
|
+
)
|
|
91
93
|
|
|
92
94
|
grid_ids = list(ispyb_ids.grid_ids)
|
|
93
95
|
data_collection_ids_out = list(ispyb_ids.data_collection_ids)
|
|
@@ -116,15 +118,15 @@ class StoreInIspyb:
|
|
|
116
118
|
return ispyb_ids
|
|
117
119
|
|
|
118
120
|
def end_deposition(self, ispyb_ids: IspybIds, success: str, reason: str):
|
|
119
|
-
assert (
|
|
120
|
-
|
|
121
|
-
)
|
|
122
|
-
assert (
|
|
123
|
-
|
|
124
|
-
)
|
|
121
|
+
assert ispyb_ids.data_collection_ids, (
|
|
122
|
+
"Can't end ISPyB deposition, data_collection IDs are missing"
|
|
123
|
+
)
|
|
124
|
+
assert ispyb_ids.data_collection_group_id is not None, (
|
|
125
|
+
"Cannot end ISPyB deposition without data collection group ID"
|
|
126
|
+
)
|
|
125
127
|
|
|
126
128
|
for id_ in ispyb_ids.data_collection_ids:
|
|
127
|
-
|
|
129
|
+
ISPYB_ZOCALO_CALLBACK_LOGGER.info(
|
|
128
130
|
f"End ispyb deposition with status '{success}' and reason '{reason}'."
|
|
129
131
|
)
|
|
130
132
|
if success == "fail" or success == "abort":
|
|
@@ -7,11 +7,11 @@ from ispyb import NoResult
|
|
|
7
7
|
from ispyb.connector.mysqlsp.main import ISPyBMySQLSPConnector as Connector
|
|
8
8
|
from ispyb.sp.core import Core
|
|
9
9
|
|
|
10
|
-
from mx_bluesky.
|
|
10
|
+
from mx_bluesky.common.parameters.constants import SimConstants
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
def get_ispyb_config():
|
|
14
|
-
return os.environ.get("ISPYB_CONFIG_PATH",
|
|
14
|
+
return os.environ.get("ISPYB_CONFIG_PATH", SimConstants.ISPYB_CONFIG)
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
def get_session_id_from_visit(conn: Connector, visit: str):
|
|
File without changes
|
|
@@ -2,16 +2,27 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import time
|
|
4
4
|
from datetime import UTC, datetime, timedelta
|
|
5
|
+
from enum import Enum
|
|
5
6
|
|
|
6
7
|
import numpy as np
|
|
7
8
|
from dodal.devices.detector import DetectorParams
|
|
8
|
-
from dodal.devices.zebra import RotationDirection
|
|
9
9
|
from nexgen.nxs_utils import Attenuator, Axis, Beam, Detector, EigerDetector, Goniometer
|
|
10
10
|
from nexgen.nxs_utils.axes import TransformationType
|
|
11
11
|
from numpy.typing import DTypeLike
|
|
12
12
|
|
|
13
|
-
from mx_bluesky.
|
|
14
|
-
from mx_bluesky.
|
|
13
|
+
from mx_bluesky.common.utils.log import NEXUS_LOGGER
|
|
14
|
+
from mx_bluesky.common.utils.utils import convert_eV_to_angstrom
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class AxisDirection(Enum):
|
|
18
|
+
"""
|
|
19
|
+
Identifies whether the omega axis of rotation is on the positive x-axis or
|
|
20
|
+
negative x-axis as per the Nexus standard:
|
|
21
|
+
https://journals.iucr.org/m/issues/2020/05/00/ti5018/ti5018.pdf
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
POSITIVE = 1
|
|
25
|
+
NEGATIVE = -1
|
|
15
26
|
|
|
16
27
|
|
|
17
28
|
def vds_type_based_on_bit_depth(detector_bit_depth: int) -> DTypeLike:
|
|
@@ -35,8 +46,8 @@ def create_goniometer_axes(
|
|
|
35
46
|
x_y_z_increments: tuple[float, float, float] = (0.0, 0.0, 0.0),
|
|
36
47
|
chi: float = 0.0,
|
|
37
48
|
phi: float = 0.0,
|
|
38
|
-
|
|
39
|
-
):
|
|
49
|
+
omega_axis_direction: AxisDirection = AxisDirection.NEGATIVE,
|
|
50
|
+
) -> Goniometer:
|
|
40
51
|
"""Returns a Nexgen 'Goniometer' object with the dependency chain of I03's Smargon
|
|
41
52
|
goniometer. If scan points is provided these values will be used in preference to
|
|
42
53
|
those from the params object.
|
|
@@ -50,13 +61,17 @@ def create_goniometer_axes(
|
|
|
50
61
|
x_y_z_increments: optionally, specify the increments between each image for
|
|
51
62
|
the x, y, and z axes. Will be ignored if scan_points
|
|
52
63
|
is provided.
|
|
64
|
+
omega_axis_direction: The direction of the omega axis, this determines the
|
|
65
|
+
coordinate space parity
|
|
66
|
+
Returns:
|
|
67
|
+
The created Goniometer object
|
|
53
68
|
"""
|
|
54
69
|
gonio_axes = [
|
|
55
70
|
Axis(
|
|
56
71
|
"omega",
|
|
57
72
|
".",
|
|
58
73
|
TransformationType.ROTATION,
|
|
59
|
-
(1.0 *
|
|
74
|
+
(1.0 * omega_axis_direction.value, 0.0, 0.0),
|
|
60
75
|
omega_start,
|
|
61
76
|
),
|
|
62
77
|
Axis(
|
|
@@ -8,19 +8,19 @@ from __future__ import annotations
|
|
|
8
8
|
import math
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
|
|
11
|
-
from dodal.devices.zebra import RotationDirection
|
|
12
11
|
from dodal.utils import get_beamline_name
|
|
13
12
|
from nexgen.nxs_utils import Attenuator, Beam, Detector, Goniometer, Source
|
|
14
13
|
from nexgen.nxs_write.nxmx_writer import NXmxFileWriter
|
|
15
14
|
from numpy.typing import DTypeLike
|
|
16
15
|
from scanspec.core import AxesPoints
|
|
17
16
|
|
|
18
|
-
from mx_bluesky.common.
|
|
19
|
-
|
|
17
|
+
from mx_bluesky.common.external_interaction.nexus.nexus_utils import (
|
|
18
|
+
AxisDirection,
|
|
20
19
|
create_detector_parameters,
|
|
21
20
|
create_goniometer_axes,
|
|
22
21
|
get_start_and_predicted_end_time,
|
|
23
22
|
)
|
|
23
|
+
from mx_bluesky.common.parameters.components import DiffractionExperimentWithSample
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
class NexusWriter:
|
|
@@ -39,7 +39,7 @@ class NexusWriter:
|
|
|
39
39
|
# detector arming event:
|
|
40
40
|
full_num_of_images: int | None = None,
|
|
41
41
|
meta_data_run_number: int | None = None,
|
|
42
|
-
|
|
42
|
+
axis_direction: AxisDirection = AxisDirection.NEGATIVE,
|
|
43
43
|
) -> None:
|
|
44
44
|
self.beam: Beam | None = None
|
|
45
45
|
self.attenuator: Attenuator | None = None
|
|
@@ -69,7 +69,7 @@ class NexusWriter:
|
|
|
69
69
|
self.scan_points,
|
|
70
70
|
chi=chi_start_deg,
|
|
71
71
|
phi=phi_start_deg,
|
|
72
|
-
|
|
72
|
+
omega_axis_direction=axis_direction,
|
|
73
73
|
)
|
|
74
74
|
|
|
75
75
|
def create_nexus_file(self, bit_depth: DTypeLike):
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from functools import cache
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from mx_bluesky.common.external_interaction.config_server import FeatureFlags
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class MockConfigServer:
|
|
9
|
+
def best_effort_get_all_feature_flags(self):
|
|
10
|
+
return {
|
|
11
|
+
"feature_a": False,
|
|
12
|
+
"feature_b": False,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class FakeFeatureFlags(FeatureFlags):
|
|
17
|
+
@staticmethod
|
|
18
|
+
@cache
|
|
19
|
+
def get_config_server() -> MockConfigServer: # type: ignore
|
|
20
|
+
return MockConfigServer()
|
|
21
|
+
|
|
22
|
+
feature_a: bool = False
|
|
23
|
+
feature_b: bool = False
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@pytest.fixture
|
|
27
|
+
def fake_feature_flags():
|
|
28
|
+
return FakeFeatureFlags(feature_a=False, feature_b=False)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_valid_overridden_features(fake_feature_flags: FakeFeatureFlags):
|
|
32
|
+
assert fake_feature_flags.feature_a is False
|
|
33
|
+
assert fake_feature_flags.feature_b is False
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_invalid_overridden_features():
|
|
37
|
+
with pytest.raises(ValueError, match="Invalid feature toggle"):
|
|
38
|
+
FakeFeatureFlags(feature_x=True) # type: ignore
|
|
@@ -29,7 +29,7 @@ from mx_bluesky.common.parameters.constants import (
|
|
|
29
29
|
GridscanParamConstants,
|
|
30
30
|
)
|
|
31
31
|
|
|
32
|
-
PARAMETER_VERSION = Version.parse("5.
|
|
32
|
+
PARAMETER_VERSION = Version.parse("5.3.0")
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
class RotationAxis(StrEnum):
|
|
@@ -83,6 +83,7 @@ class IspybExperimentType(StrEnum):
|
|
|
83
83
|
STILL = "Still"
|
|
84
84
|
SSX_CHIP = "SSX-Chip"
|
|
85
85
|
SSX_JET = "SSX-Jet"
|
|
86
|
+
METAL_ID = "Metal ID"
|
|
86
87
|
|
|
87
88
|
# Aliases for historic hyperion experiment type mapping
|
|
88
89
|
ROTATION = "SAD"
|
|
@@ -92,7 +93,6 @@ class IspybExperimentType(StrEnum):
|
|
|
92
93
|
|
|
93
94
|
class MxBlueskyParameters(BaseModel):
|
|
94
95
|
model_config = ConfigDict(
|
|
95
|
-
arbitrary_types_allowed=True,
|
|
96
96
|
extra="allow",
|
|
97
97
|
)
|
|
98
98
|
|
|
@@ -104,12 +104,12 @@ class MxBlueskyParameters(BaseModel):
|
|
|
104
104
|
@field_validator("parameter_model_version")
|
|
105
105
|
@classmethod
|
|
106
106
|
def _validate_version(cls, version: SemanticVersion):
|
|
107
|
-
assert (
|
|
108
|
-
version
|
|
109
|
-
)
|
|
110
|
-
assert (
|
|
111
|
-
version
|
|
112
|
-
)
|
|
107
|
+
assert version >= SemanticVersion(major=PARAMETER_VERSION.major), (
|
|
108
|
+
f"Parameter version too old! This version of hyperion uses {PARAMETER_VERSION}"
|
|
109
|
+
)
|
|
110
|
+
assert version <= SemanticVersion(major=PARAMETER_VERSION.major + 1), (
|
|
111
|
+
f"Parameter version too new! This version of hyperion uses {PARAMETER_VERSION}"
|
|
112
|
+
)
|
|
113
113
|
return version
|
|
114
114
|
|
|
115
115
|
|
|
@@ -150,6 +150,7 @@ class DiffractionExperiment(
|
|
|
150
150
|
transmission_frac: float = Field(default=0.1)
|
|
151
151
|
ispyb_experiment_type: IspybExperimentType
|
|
152
152
|
storage_directory: str
|
|
153
|
+
use_roi_mode: bool = Field(default=GridscanParamConstants.USE_ROI)
|
|
153
154
|
|
|
154
155
|
@model_validator(mode="before")
|
|
155
156
|
@classmethod
|
|
@@ -1,94 +1,148 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import os
|
|
4
|
-
|
|
5
3
|
from dodal.devices.aperturescatterguard import ApertureValue
|
|
6
|
-
from dodal.devices.
|
|
7
|
-
|
|
4
|
+
from dodal.devices.fast_grid_scan import (
|
|
5
|
+
ZebraGridScanParams,
|
|
8
6
|
)
|
|
9
|
-
from pydantic import Field
|
|
7
|
+
from pydantic import Field, PrivateAttr
|
|
8
|
+
from scanspec.core import Path as ScanPath
|
|
9
|
+
from scanspec.specs import Line, Static
|
|
10
10
|
|
|
11
11
|
from mx_bluesky.common.parameters.components import (
|
|
12
12
|
DiffractionExperimentWithSample,
|
|
13
13
|
IspybExperimentType,
|
|
14
14
|
OptionalGonioAngleStarts,
|
|
15
|
+
SplitScan,
|
|
16
|
+
WithOptionalEnergyChange,
|
|
15
17
|
WithScan,
|
|
16
18
|
XyzStarts,
|
|
17
19
|
)
|
|
18
20
|
from mx_bluesky.common.parameters.constants import (
|
|
19
|
-
DetectorParamConstants,
|
|
20
21
|
GridscanParamConstants,
|
|
21
22
|
HardwareConstants,
|
|
22
23
|
)
|
|
23
|
-
|
|
24
|
+
|
|
25
|
+
"""Parameter models in this file are abstract. They should be inherited by a top-level model"""
|
|
24
26
|
|
|
25
27
|
|
|
26
28
|
class GridCommon(
|
|
27
29
|
DiffractionExperimentWithSample,
|
|
28
30
|
OptionalGonioAngleStarts,
|
|
29
31
|
):
|
|
32
|
+
"""
|
|
33
|
+
Parameters used in every MX diffraction experiment using grids. This model should
|
|
34
|
+
be used by plans which have no knowledge of the grid specifications - i.e before
|
|
35
|
+
automatic grid detection has completed
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
box_size_um: float = Field(default=GridscanParamConstants.BOX_WIDTH_UM)
|
|
30
39
|
grid_width_um: float = Field(default=GridscanParamConstants.WIDTH_UM)
|
|
31
40
|
exposure_time_s: float = Field(default=GridscanParamConstants.EXPOSURE_TIME_S)
|
|
32
|
-
use_roi_mode: bool = Field(default=GridscanParamConstants.USE_ROI)
|
|
33
41
|
|
|
34
42
|
ispyb_experiment_type: IspybExperimentType = Field(
|
|
35
43
|
default=IspybExperimentType.GRIDSCAN_3D
|
|
36
44
|
)
|
|
37
45
|
selected_aperture: ApertureValue | None = Field(default=ApertureValue.SMALL)
|
|
38
46
|
|
|
47
|
+
tip_offset_um: float = Field(default=HardwareConstants.TIP_OFFSET_UM)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class SpecifiedGrid(XyzStarts, WithScan):
|
|
51
|
+
"""A specified grid is one which has defined values for the start position,
|
|
52
|
+
grid and box sizes, etc., as opposed to parameters for a plan which will create
|
|
53
|
+
those parameters at some point (e.g. through optical pin detection)."""
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class SpecifiedThreeDGridScan(
|
|
57
|
+
GridCommon,
|
|
58
|
+
SpecifiedGrid,
|
|
59
|
+
SplitScan,
|
|
60
|
+
WithOptionalEnergyChange,
|
|
61
|
+
):
|
|
62
|
+
"""Parameters representing a so-called 3D grid scan, which consists of doing a
|
|
63
|
+
gridscan in X and Y, followed by one in X and Z."""
|
|
64
|
+
|
|
65
|
+
grid1_omega_deg: float = Field(default=GridscanParamConstants.OMEGA_1) # type: ignore
|
|
66
|
+
grid2_omega_deg: float = Field(default=GridscanParamConstants.OMEGA_2)
|
|
67
|
+
x_step_size_um: float = Field(default=GridscanParamConstants.BOX_WIDTH_UM)
|
|
68
|
+
y_step_size_um: float = Field(default=GridscanParamConstants.BOX_WIDTH_UM)
|
|
69
|
+
z_step_size_um: float = Field(default=GridscanParamConstants.BOX_WIDTH_UM)
|
|
70
|
+
y2_start_um: float
|
|
71
|
+
z2_start_um: float
|
|
72
|
+
x_steps: int = Field(gt=0)
|
|
73
|
+
y_steps: int = Field(gt=0)
|
|
74
|
+
z_steps: int = Field(gt=0)
|
|
75
|
+
_set_stub_offsets: bool = PrivateAttr(default_factory=lambda: False)
|
|
76
|
+
|
|
39
77
|
@property
|
|
40
|
-
def
|
|
41
|
-
|
|
42
|
-
self.
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
self.
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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,
|
|
78
|
+
def FGS_params(self) -> ZebraGridScanParams:
|
|
79
|
+
return ZebraGridScanParams(
|
|
80
|
+
x_steps=self.x_steps,
|
|
81
|
+
y_steps=self.y_steps,
|
|
82
|
+
z_steps=self.z_steps,
|
|
83
|
+
x_step_size_mm=self.x_step_size_um / 1000,
|
|
84
|
+
y_step_size_mm=self.y_step_size_um / 1000,
|
|
85
|
+
z_step_size_mm=self.z_step_size_um / 1000,
|
|
86
|
+
x_start_mm=self.x_start_um / 1000,
|
|
87
|
+
y1_start_mm=self.y_start_um / 1000,
|
|
88
|
+
z1_start_mm=self.z_start_um / 1000,
|
|
89
|
+
y2_start_mm=self.y2_start_um / 1000,
|
|
90
|
+
z2_start_mm=self.z2_start_um / 1000,
|
|
91
|
+
set_stub_offsets=self._set_stub_offsets,
|
|
92
|
+
dwell_time_ms=self.exposure_time_s * 1000,
|
|
93
|
+
transmission_fraction=self.transmission_frac,
|
|
67
94
|
)
|
|
68
95
|
|
|
96
|
+
def do_set_stub_offsets(self, value: bool):
|
|
97
|
+
self._set_stub_offsets = value
|
|
69
98
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
99
|
+
@property
|
|
100
|
+
def grid_1_spec(self):
|
|
101
|
+
x_end = self.x_start_um + self.x_step_size_um * (self.x_steps - 1)
|
|
102
|
+
y1_end = self.y_start_um + self.y_step_size_um * (self.y_steps - 1)
|
|
103
|
+
grid_1_x = Line("sam_x", self.x_start_um, x_end, self.x_steps)
|
|
104
|
+
grid_1_y = Line("sam_y", self.y_start_um, y1_end, self.y_steps)
|
|
105
|
+
grid_1_z = Static("sam_z", self.z_start_um)
|
|
106
|
+
return grid_1_y.zip(grid_1_z) * ~grid_1_x
|
|
76
107
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
108
|
+
@property
|
|
109
|
+
def grid_2_spec(self):
|
|
110
|
+
x_end = self.x_start_um + self.x_step_size_um * (self.x_steps - 1)
|
|
111
|
+
z2_end = self.z2_start_um + self.z_step_size_um * (self.z_steps - 1)
|
|
112
|
+
grid_2_x = Line("sam_x", self.x_start_um, x_end, self.x_steps)
|
|
113
|
+
grid_2_z = Line("sam_z", self.z2_start_um, z2_end, self.z_steps)
|
|
114
|
+
grid_2_y = Static("sam_y", self.y2_start_um)
|
|
115
|
+
return grid_2_z.zip(grid_2_y) * ~grid_2_x
|
|
81
116
|
|
|
117
|
+
@property
|
|
118
|
+
def scan_indices(self):
|
|
119
|
+
"""The first index of each gridscan, useful for writing nexus files/VDS"""
|
|
120
|
+
return [
|
|
121
|
+
0,
|
|
122
|
+
len(ScanPath(self.grid_1_spec.calculate()).consume().midpoints["sam_x"]),
|
|
123
|
+
]
|
|
82
124
|
|
|
83
|
-
|
|
84
|
-
|
|
125
|
+
@property
|
|
126
|
+
def scan_spec(self):
|
|
127
|
+
"""A fully specified ScanSpec object representing both grids, with x, y, z and
|
|
128
|
+
omega positions."""
|
|
129
|
+
return self.grid_1_spec.concat(self.grid_2_spec)
|
|
85
130
|
|
|
131
|
+
@property
|
|
132
|
+
def scan_points(self):
|
|
133
|
+
"""A list of all the points in the scan_spec."""
|
|
134
|
+
return ScanPath(self.scan_spec.calculate()).consume().midpoints
|
|
86
135
|
|
|
87
|
-
|
|
88
|
-
|
|
136
|
+
@property
|
|
137
|
+
def scan_points_first_grid(self):
|
|
138
|
+
"""A list of all the points in the first grid scan."""
|
|
139
|
+
return ScanPath(self.grid_1_spec.calculate()).consume().midpoints
|
|
89
140
|
|
|
141
|
+
@property
|
|
142
|
+
def scan_points_second_grid(self):
|
|
143
|
+
"""A list of all the points in the second grid scan."""
|
|
144
|
+
return ScanPath(self.grid_2_spec.calculate()).consume().midpoints
|
|
90
145
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
those parameters at some point (e.g. through optical pin detection)."""
|
|
146
|
+
@property
|
|
147
|
+
def num_images(self) -> int:
|
|
148
|
+
return len(self.scan_points["sam_x"])
|
|
@@ -18,9 +18,7 @@ from mx_bluesky.common.device_setup_plans.read_hardware_for_setup import (
|
|
|
18
18
|
read_hardware_for_zocalo,
|
|
19
19
|
)
|
|
20
20
|
from mx_bluesky.common.parameters.constants import (
|
|
21
|
-
EnvironmentConstants,
|
|
22
21
|
PlanNameConstants,
|
|
23
|
-
TriggerConstants,
|
|
24
22
|
)
|
|
25
23
|
from mx_bluesky.common.utils.tracing import TRACER
|
|
26
24
|
|
|
@@ -59,7 +57,7 @@ def _wait_for_zocalo_to_stage_then_do_fgs(
|
|
|
59
57
|
yield from bps.complete(grid_scan_device, wait=True)
|
|
60
58
|
# Remove this logging statement once metrics have been added
|
|
61
59
|
LOGGER.info(
|
|
62
|
-
f"Grid scan motion program took {round(time()-gridscan_start_time,2)} to complete"
|
|
60
|
+
f"Grid scan motion program took {round(time() - gridscan_start_time, 2)} to complete"
|
|
63
61
|
)
|
|
64
62
|
|
|
65
63
|
|
|
@@ -70,7 +68,6 @@ def kickoff_and_complete_gridscan(
|
|
|
70
68
|
scan_points: list[AxesPoints[Axis]],
|
|
71
69
|
scan_start_indices: list[int],
|
|
72
70
|
plan_during_collection: Callable[[], MsgGenerator] | None = None,
|
|
73
|
-
zocalo_environment: str = EnvironmentConstants.ZOCALO_ENV,
|
|
74
71
|
):
|
|
75
72
|
"""Triggers a grid scan motion program and waits for completion, accounting for synchrotron topup.
|
|
76
73
|
If the RunEngine is subscribed to ZocaloCallback, this plan will also trigger Zocalo.
|
|
@@ -86,12 +83,11 @@ def kickoff_and_complete_gridscan(
|
|
|
86
83
|
scan_start_indices (list[int]): Contains the first index of each grid scan
|
|
87
84
|
plan_during_collection (Optional, MsgGenerator): Generic plan called in between kickoff and completion,
|
|
88
85
|
eg waiting on zocalo.
|
|
89
|
-
zocalo_environment (Optional, str) Used for zocalo connection
|
|
90
86
|
"""
|
|
91
87
|
|
|
92
|
-
assert len(scan_points) == len(
|
|
93
|
-
scan_start_indices
|
|
94
|
-
)
|
|
88
|
+
assert len(scan_points) == len(scan_start_indices), (
|
|
89
|
+
"scan_points and scan_start_indices must be lists of the same length!"
|
|
90
|
+
)
|
|
95
91
|
|
|
96
92
|
plan_name = PlanNameConstants.DO_FGS
|
|
97
93
|
|
|
@@ -100,10 +96,8 @@ def kickoff_and_complete_gridscan(
|
|
|
100
96
|
@bpp.run_decorator(
|
|
101
97
|
md={
|
|
102
98
|
"subplan_name": plan_name,
|
|
103
|
-
TriggerConstants.ZOCALO: plan_name,
|
|
104
99
|
"scan_points": scan_points,
|
|
105
100
|
"scan_start_indices": scan_start_indices,
|
|
106
|
-
"zocalo_environment": zocalo_environment,
|
|
107
101
|
}
|
|
108
102
|
)
|
|
109
103
|
@bpp.contingency_decorator(
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import re
|
|
1
2
|
from collections.abc import Callable, Generator
|
|
2
3
|
from typing import TypeVar
|
|
3
4
|
|
|
@@ -13,10 +14,23 @@ class WarningException(Exception):
|
|
|
13
14
|
pass
|
|
14
15
|
|
|
15
16
|
|
|
17
|
+
class ISPyBDepositionNotMade(Exception):
|
|
18
|
+
"""Raised when the ISPyB or Zocalo callbacks can't access ISPyB deposition numbers."""
|
|
19
|
+
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
16
23
|
class SampleException(WarningException):
|
|
17
24
|
"""An exception which identifies an issue relating to the sample."""
|
|
18
25
|
|
|
19
|
-
|
|
26
|
+
def __str__(self):
|
|
27
|
+
class_name = type(self).__name__
|
|
28
|
+
return f"[{class_name}]: {super().__str__()}"
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def type_and_message_from_reason(cls, reason: str) -> tuple[str, str]:
|
|
32
|
+
match = re.match(r"\[(\S*)?]: (.*)", reason)
|
|
33
|
+
return (match.group(1), match.group(2)) if match else (None, None)
|
|
20
34
|
|
|
21
35
|
|
|
22
36
|
T = TypeVar("T")
|
mx_bluesky/common/utils/log.py
CHANGED
|
@@ -12,7 +12,17 @@ from dodal.log import (
|
|
|
12
12
|
)
|
|
13
13
|
from dodal.log import LOGGER as dodal_logger
|
|
14
14
|
|
|
15
|
-
LOGGER = logging.getLogger("
|
|
15
|
+
LOGGER = logging.getLogger("MX-Bluesky")
|
|
16
|
+
LOGGER.setLevel("DEBUG")
|
|
17
|
+
LOGGER.parent = dodal_logger
|
|
18
|
+
|
|
19
|
+
ISPYB_ZOCALO_CALLBACK_LOGGER = logging.getLogger("ISPyB and Zocalo callbacks")
|
|
20
|
+
ISPYB_ZOCALO_CALLBACK_LOGGER.setLevel(logging.DEBUG)
|
|
21
|
+
|
|
22
|
+
NEXUS_LOGGER = logging.getLogger("NeXus callbacks")
|
|
23
|
+
NEXUS_LOGGER.setLevel(logging.DEBUG)
|
|
24
|
+
|
|
25
|
+
ALL_LOGGERS = [LOGGER, ISPYB_ZOCALO_CALLBACK_LOGGER, NEXUS_LOGGER]
|
|
16
26
|
|
|
17
27
|
__logger_handlers: DodalLogHandlers | None = None
|
|
18
28
|
|
|
@@ -76,18 +86,18 @@ def do_default_logging_setup(
|
|
|
76
86
|
|
|
77
87
|
|
|
78
88
|
def _get_debug_handler() -> CircularMemoryHandler:
|
|
79
|
-
assert (
|
|
80
|
-
|
|
81
|
-
)
|
|
89
|
+
assert __logger_handlers is not None, (
|
|
90
|
+
"You can only use this after running the default logging setup"
|
|
91
|
+
)
|
|
82
92
|
return __logger_handlers["debug_memory_handler"]
|
|
83
93
|
|
|
84
94
|
|
|
85
95
|
def flush_debug_handler() -> str:
|
|
86
96
|
"""Writes the contents of the circular debug log buffer to disk and returns the written filename"""
|
|
87
97
|
handler = _get_debug_handler()
|
|
88
|
-
assert isinstance(
|
|
89
|
-
handler
|
|
90
|
-
)
|
|
98
|
+
assert isinstance(handler.target, TimedRotatingFileHandler), (
|
|
99
|
+
"Circular memory handler doesn't have an appropriate fileHandler target"
|
|
100
|
+
)
|
|
91
101
|
handler.flush()
|
|
92
102
|
return handler.target.baseFilename
|
|
93
103
|
|