mx-bluesky 1.5.0__py3-none-any.whl → 1.5.2__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/__init__.py +4 -1
- mx_bluesky/beamlines/i04/callbacks/murko_callback.py +56 -1
- mx_bluesky/beamlines/i04/experiment_plans/__init__.py +0 -0
- mx_bluesky/beamlines/i04/experiment_plans/i04_grid_detect_then_xray_centre_plan.py +259 -0
- mx_bluesky/beamlines/i24/serial/extruder/i24ssx_Extruder_Collect_py3v2.py +8 -8
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Collect_py3v1.py +8 -6
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Manager_py3v1.py +4 -4
- mx_bluesky/beamlines/i24/serial/setup_beamline/setup_beamline.py +2 -2
- mx_bluesky/beamlines/i24/serial/setup_beamline/setup_detector.py +5 -5
- mx_bluesky/beamlines/i24/serial/web_gui_plans/general_plans.py +3 -3
- mx_bluesky/common/device_setup_plans/robot_load_unload.py +123 -0
- mx_bluesky/common/experiment_plans/change_aperture_then_move_plan.py +5 -1
- mx_bluesky/common/experiment_plans/common_flyscan_xray_centre_plan.py +27 -3
- mx_bluesky/common/experiment_plans/common_grid_detect_then_xray_centre_plan.py +1 -0
- mx_bluesky/common/experiment_plans/inner_plans/do_fgs.py +3 -1
- mx_bluesky/common/external_interaction/ispyb/exp_eye_store.py +26 -24
- mx_bluesky/common/external_interaction/ispyb/ispyb_store.py +11 -7
- mx_bluesky/common/external_interaction/nexus/write_nexus.py +2 -2
- mx_bluesky/common/parameters/__init__.py +0 -0
- mx_bluesky/common/parameters/components.py +7 -2
- mx_bluesky/common/parameters/constants.py +5 -3
- mx_bluesky/common/parameters/device_composites.py +1 -1
- mx_bluesky/common/parameters/gridscan.py +1 -0
- mx_bluesky/common/xrc_result.py +25 -2
- mx_bluesky/hyperion/__main__.py +1 -1
- mx_bluesky/hyperion/baton_handler.py +36 -4
- mx_bluesky/hyperion/device_setup_plans/setup_zebra.py +4 -93
- mx_bluesky/hyperion/experiment_plans/hyperion_flyscan_xray_centre_plan.py +19 -31
- mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py +26 -8
- mx_bluesky/hyperion/experiment_plans/robot_load_and_change_energy.py +21 -75
- mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +2 -2
- mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +14 -9
- mx_bluesky/hyperion/external_interaction/agamemnon.py +4 -4
- mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +1 -1
- mx_bluesky/hyperion/external_interaction/callbacks/{robot_load → robot_actions}/ispyb_callback.py +28 -19
- mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py +1 -1
- mx_bluesky/hyperion/external_interaction/callbacks/snapshot_callback.py +3 -0
- mx_bluesky/hyperion/external_interaction/config_server.py +0 -11
- mx_bluesky/hyperion/parameters/constants.py +2 -7
- mx_bluesky/hyperion/parameters/gridscan.py +2 -6
- mx_bluesky/hyperion/parameters/load_centre_collect.py +15 -0
- mx_bluesky/hyperion/parameters/rotation.py +7 -3
- mx_bluesky/hyperion/utils/context.py +19 -5
- mx_bluesky/phase1_zebra/__init__.py +1 -0
- mx_bluesky/phase1_zebra/device_setup_plans/__init__.py +0 -0
- mx_bluesky/phase1_zebra/device_setup_plans/setup_zebra.py +112 -0
- {mx_bluesky-1.5.0.dist-info → mx_bluesky-1.5.2.dist-info}/METADATA +5 -4
- {mx_bluesky-1.5.0.dist-info → mx_bluesky-1.5.2.dist-info}/RECORD +55 -49
- mx_bluesky/hyperion/utils/validation.py +0 -196
- /mx_bluesky/common/experiment_plans/{read_hardware.py → inner_plans/read_hardware.py} +0 -0
- /mx_bluesky/common/experiment_plans/{write_sample_status.py → inner_plans/write_sample_status.py} +0 -0
- {mx_bluesky-1.5.0.dist-info → mx_bluesky-1.5.2.dist-info}/WHEEL +0 -0
- {mx_bluesky-1.5.0.dist-info → mx_bluesky-1.5.2.dist-info}/entry_points.txt +0 -0
- {mx_bluesky-1.5.0.dist-info → mx_bluesky-1.5.2.dist-info}/licenses/LICENSE +0 -0
- {mx_bluesky-1.5.0.dist-info → mx_bluesky-1.5.2.dist-info}/top_level.txt +0 -0
mx_bluesky/hyperion/external_interaction/callbacks/{robot_load → robot_actions}/ispyb_callback.py
RENAMED
|
@@ -12,6 +12,7 @@ from mx_bluesky.common.external_interaction.ispyb.exp_eye_store import (
|
|
|
12
12
|
BLSampleStatus,
|
|
13
13
|
ExpeyeInteraction,
|
|
14
14
|
RobotActionID,
|
|
15
|
+
create_update_data_from_event_doc,
|
|
15
16
|
)
|
|
16
17
|
from mx_bluesky.common.utils.log import ISPYB_ZOCALO_CALLBACK_LOGGER
|
|
17
18
|
from mx_bluesky.hyperion.parameters.constants import CONST
|
|
@@ -20,11 +21,21 @@ if TYPE_CHECKING:
|
|
|
20
21
|
from event_model.documents import Event, EventDescriptor, RunStart, RunStop
|
|
21
22
|
|
|
22
23
|
|
|
24
|
+
robot_update_mapping = {
|
|
25
|
+
"robot-barcode": "sampleBarcode",
|
|
26
|
+
"robot-current_pin": "containerLocation",
|
|
27
|
+
"robot-current_puck": "dewarLocation",
|
|
28
|
+
# I03 uses webcam/oav snapshots in place of before/after snapshots
|
|
29
|
+
"webcam-last_saved_path": "xtalSnapshotBefore",
|
|
30
|
+
"oav-snapshot-last_saved_path": "xtalSnapshotAfter",
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
23
34
|
class RobotLoadISPyBCallback(PlanReactiveCallback):
|
|
24
35
|
def __init__(self) -> None:
|
|
25
36
|
ISPYB_ZOCALO_CALLBACK_LOGGER.debug("Initialising ISPyB Robot Load Callback")
|
|
26
37
|
super().__init__(log=ISPYB_ZOCALO_CALLBACK_LOGGER)
|
|
27
|
-
self.
|
|
38
|
+
self._sample_id: int | None = None
|
|
28
39
|
|
|
29
40
|
self.run_uid: str | None = None
|
|
30
41
|
self.descriptors: dict[str, EventDescriptor] = {}
|
|
@@ -35,22 +46,24 @@ class RobotLoadISPyBCallback(PlanReactiveCallback):
|
|
|
35
46
|
ISPYB_ZOCALO_CALLBACK_LOGGER.debug(
|
|
36
47
|
"ISPyB robot load callback received start document."
|
|
37
48
|
)
|
|
38
|
-
|
|
49
|
+
subplan = doc.get("subplan_name")
|
|
50
|
+
if subplan == CONST.PLAN.ROBOT_LOAD or subplan == CONST.PLAN.ROBOT_UNLOAD:
|
|
39
51
|
ISPYB_ZOCALO_CALLBACK_LOGGER.debug(
|
|
40
52
|
f"ISPyB robot load callback received: {doc}"
|
|
41
53
|
)
|
|
42
54
|
self.run_uid = doc.get("uid")
|
|
43
|
-
|
|
44
|
-
assert isinstance(
|
|
55
|
+
metadata = doc.get("metadata")
|
|
56
|
+
assert isinstance(metadata, dict)
|
|
57
|
+
self._sample_id = metadata["sample_id"]
|
|
58
|
+
assert isinstance(self._sample_id, int)
|
|
45
59
|
proposal, session = get_proposal_and_session_from_visit_string(
|
|
46
|
-
|
|
60
|
+
metadata["visit"]
|
|
47
61
|
)
|
|
48
|
-
self.action_id = self.expeye.
|
|
62
|
+
self.action_id = self.expeye.start_robot_action(
|
|
63
|
+
"LOAD" if subplan == CONST.PLAN.ROBOT_LOAD else "UNLOAD",
|
|
49
64
|
proposal,
|
|
50
65
|
session,
|
|
51
|
-
self.
|
|
52
|
-
self._metadata["sample_puck"],
|
|
53
|
-
self._metadata["sample_pin"],
|
|
66
|
+
self._sample_id,
|
|
54
67
|
)
|
|
55
68
|
return super().activity_gated_start(doc)
|
|
56
69
|
|
|
@@ -62,18 +75,14 @@ class RobotLoadISPyBCallback(PlanReactiveCallback):
|
|
|
62
75
|
event_descriptor = self.descriptors.get(doc["descriptor"])
|
|
63
76
|
if (
|
|
64
77
|
event_descriptor
|
|
65
|
-
and event_descriptor.get("name") == CONST.DESCRIPTORS.
|
|
78
|
+
and event_descriptor.get("name") == CONST.DESCRIPTORS.ROBOT_UPDATE
|
|
66
79
|
):
|
|
67
80
|
assert self.action_id is not None, (
|
|
68
81
|
"ISPyB Robot load callback event called unexpectedly"
|
|
69
82
|
)
|
|
70
|
-
barcode = doc["data"]["robot-barcode"]
|
|
71
|
-
oav_snapshot = doc["data"]["oav-snapshot-last_saved_path"]
|
|
72
|
-
webcam_snapshot = doc["data"]["webcam-last_saved_path"]
|
|
73
83
|
# I03 uses webcam/oav snapshots in place of before/after snapshots
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
)
|
|
84
|
+
update_data = create_update_data_from_event_doc(robot_update_mapping, doc)
|
|
85
|
+
self.expeye.update_robot_action(self.action_id, update_data)
|
|
77
86
|
|
|
78
87
|
return super().activity_gated_event(doc)
|
|
79
88
|
|
|
@@ -87,12 +96,12 @@ class RobotLoadISPyBCallback(PlanReactiveCallback):
|
|
|
87
96
|
)
|
|
88
97
|
exit_status = doc.get("exit_status")
|
|
89
98
|
assert exit_status, "Exit status not available in stop document!"
|
|
90
|
-
assert self.
|
|
99
|
+
assert self._sample_id is not None, "Stop called before start"
|
|
91
100
|
reason = doc.get("reason") or "OK"
|
|
92
101
|
|
|
93
|
-
self.expeye.
|
|
102
|
+
self.expeye.end_robot_action(self.action_id, exit_status, reason)
|
|
94
103
|
self.expeye.update_sample_status(
|
|
95
|
-
self.
|
|
104
|
+
self._sample_id,
|
|
96
105
|
BLSampleStatus.LOADED
|
|
97
106
|
if exit_status == "success"
|
|
98
107
|
else BLSampleStatus.ERROR_BEAMLINE,
|
|
@@ -54,6 +54,7 @@ class RotationISPyBCallback(BaseISPyBCallback):
|
|
|
54
54
|
super().__init__(emit=emit)
|
|
55
55
|
self.last_sample_id: int | None = None
|
|
56
56
|
self.ispyb_ids: IspybIds = IspybIds()
|
|
57
|
+
self.ispyb = StoreInIspyb(self.ispyb_config)
|
|
57
58
|
|
|
58
59
|
def activity_gated_start(self, doc: RunStart):
|
|
59
60
|
if doc.get("subplan_name") == CONST.PLAN.ROTATION_OUTER:
|
|
@@ -82,7 +83,6 @@ class RotationISPyBCallback(BaseISPyBCallback):
|
|
|
82
83
|
f"Collection is {self.params.ispyb_experiment_type} - storing sampleID to bundle images"
|
|
83
84
|
)
|
|
84
85
|
self.last_sample_id = self.params.sample_id
|
|
85
|
-
self.ispyb = StoreInIspyb(self.ispyb_config)
|
|
86
86
|
ISPYB_ZOCALO_CALLBACK_LOGGER.info("Beginning ispyb deposition")
|
|
87
87
|
data_collection_group_info = populate_data_collection_group(self.params)
|
|
88
88
|
data_collection_info = populate_data_collection_info_for_rotation(
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import dataclasses
|
|
2
|
+
import os
|
|
2
3
|
import re
|
|
3
4
|
from collections.abc import Iterator
|
|
4
5
|
from datetime import datetime
|
|
@@ -172,6 +173,8 @@ class BeamDrawingCallback(PlanReactiveCallback):
|
|
|
172
173
|
f"Generating snapshot at {current_sample_pos_mm} from base snapshot {snapshot_info}"
|
|
173
174
|
)
|
|
174
175
|
output_snapshot_directory = data["oav-snapshot-directory"]
|
|
176
|
+
if not os.path.exists(output_snapshot_directory):
|
|
177
|
+
os.mkdir(output_snapshot_directory)
|
|
175
178
|
base_file_stem = Path(snapshot_info.snapshot_path).stem
|
|
176
179
|
output_snapshot_filename = _snapshot_filename(base_file_stem)
|
|
177
180
|
output_snapshot_path = (
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from functools import cache
|
|
2
2
|
|
|
3
3
|
from daq_config_server.client import ConfigServer
|
|
4
|
-
from pydantic import model_validator
|
|
5
4
|
|
|
6
5
|
from mx_bluesky.common.external_interaction.config_server import FeatureFlags
|
|
7
6
|
from mx_bluesky.common.utils.log import LOGGER
|
|
@@ -14,8 +13,6 @@ class HyperionFeatureFlags(FeatureFlags):
|
|
|
14
13
|
|
|
15
14
|
Attributes:
|
|
16
15
|
use_panda_for_gridscan: If True then the PandA is used for gridscans, otherwise the zebra is used
|
|
17
|
-
compare_cpu_and_gpu_zocalo: If True then GPU result processing is enabled
|
|
18
|
-
alongside CPU and the results are compared. The CPU result is still take.n
|
|
19
16
|
use_gpu_results: If True then GPU result processing is enabled
|
|
20
17
|
and the GPU result is taken.
|
|
21
18
|
set_stub_offsets: If True then set the stub offsets after moving to the crystal (ignored for
|
|
@@ -31,15 +28,7 @@ class HyperionFeatureFlags(FeatureFlags):
|
|
|
31
28
|
def get_config_server() -> ConfigServer:
|
|
32
29
|
return ConfigServer(CONST.CONFIG_SERVER_URL, LOGGER)
|
|
33
30
|
|
|
34
|
-
@model_validator(mode="after")
|
|
35
|
-
def use_gpu_and_compare_cannot_both_be_true(self):
|
|
36
|
-
assert not (self.use_gpu_results and self.compare_cpu_and_gpu_zocalo), (
|
|
37
|
-
"Cannot both use GPU results and compare them to CPU"
|
|
38
|
-
)
|
|
39
|
-
return self
|
|
40
|
-
|
|
41
31
|
use_panda_for_gridscan: bool = CONST.I03.USE_PANDA_FOR_GRIDSCAN
|
|
42
|
-
compare_cpu_and_gpu_zocalo: bool = CONST.I03.COMPARE_CPU_AND_GPU_ZOCALO
|
|
43
32
|
use_gpu_results: bool = CONST.I03.USE_GPU_RESULTS
|
|
44
33
|
set_stub_offsets: bool = CONST.I03.SET_STUB_OFFSETS
|
|
45
34
|
omega_flip: bool = CONST.I03.OMEGA_FLIP
|
|
@@ -29,17 +29,12 @@ class I03Constants:
|
|
|
29
29
|
OMEGA_FLIP = True
|
|
30
30
|
ALTERNATE_ROTATION_DIRECTION = True
|
|
31
31
|
|
|
32
|
-
# Turns on GPU processing for zocalo and logs a comparison between GPU and CPU-
|
|
33
|
-
# processed results.
|
|
34
|
-
COMPARE_CPU_AND_GPU_ZOCALO = False
|
|
35
|
-
|
|
36
32
|
# Turns on GPU processing for zocalo and uses the results that come back
|
|
37
|
-
USE_GPU_RESULTS =
|
|
33
|
+
USE_GPU_RESULTS = True
|
|
38
34
|
|
|
39
35
|
|
|
40
36
|
@dataclass(frozen=True)
|
|
41
37
|
class HyperionConstants:
|
|
42
|
-
DESCRIPTORS = DocDescriptorNames()
|
|
43
38
|
ZOCALO_ENV = EnvironmentConstants.ZOCALO_ENV
|
|
44
39
|
HARDWARE = HardwareConstants()
|
|
45
40
|
I03 = I03Constants()
|
|
@@ -53,7 +48,7 @@ class HyperionConstants:
|
|
|
53
48
|
if TEST_MODE
|
|
54
49
|
else "https://daq-config.diamond.ac.uk/api"
|
|
55
50
|
)
|
|
56
|
-
GRAYLOG_PORT = 12232
|
|
51
|
+
GRAYLOG_PORT = 12232 # Hyperion stream
|
|
57
52
|
PARAMETER_SCHEMA_DIRECTORY = "src/hyperion/parameters/schemas/"
|
|
58
53
|
LOG_FILE_NAME = "hyperion.log"
|
|
59
54
|
DEVICE_SETTINGS_CONSTANTS = DeviceSettingsConstants()
|
|
@@ -20,9 +20,7 @@ class GridCommonWithHyperionDetectorParams(GridCommon, WithHyperionUDCFeatures):
|
|
|
20
20
|
@property
|
|
21
21
|
def detector_params(self):
|
|
22
22
|
params = super().detector_params
|
|
23
|
-
params.enable_dev_shm =
|
|
24
|
-
self.features.compare_cpu_and_gpu_zocalo or self.features.use_gpu_results
|
|
25
|
-
)
|
|
23
|
+
params.enable_dev_shm = self.features.use_gpu_results
|
|
26
24
|
return params
|
|
27
25
|
|
|
28
26
|
|
|
@@ -35,9 +33,7 @@ class HyperionSpecifiedThreeDGridScan(WithHyperionUDCFeatures, SpecifiedThreeDGr
|
|
|
35
33
|
@property
|
|
36
34
|
def detector_params(self):
|
|
37
35
|
params = super().detector_params
|
|
38
|
-
params.enable_dev_shm =
|
|
39
|
-
self.features.compare_cpu_and_gpu_zocalo or self.features.use_gpu_results
|
|
40
|
-
)
|
|
36
|
+
params.enable_dev_shm = self.features.use_gpu_results
|
|
41
37
|
return params
|
|
42
38
|
|
|
43
39
|
# Relative to common grid scan, stub offsets are defined by config server
|
|
@@ -50,6 +50,12 @@ class LoadCentreCollect(
|
|
|
50
50
|
f"Unexpected fields found in LoadCentreCollect {disallowed_keys}"
|
|
51
51
|
)
|
|
52
52
|
|
|
53
|
+
assert "features" not in values["robot_load_then_centre"], (
|
|
54
|
+
"Features flags must be specified at top-level in LoadCentreCollect"
|
|
55
|
+
)
|
|
56
|
+
assert "features" not in values["multi_rotation_scan"], (
|
|
57
|
+
"Features flags must be specified at top-level in LoadCentreCollect"
|
|
58
|
+
)
|
|
53
59
|
keys_from_outer_load_centre_collect = (
|
|
54
60
|
MxBlueskyParameters.model_fields.keys()
|
|
55
61
|
| WithSample.model_fields.keys()
|
|
@@ -70,6 +76,9 @@ class LoadCentreCollect(
|
|
|
70
76
|
f"Unexpected keys in multi_rotation_scan: {', '.join(duplicated_multi_rotation_scan_keys)}"
|
|
71
77
|
)
|
|
72
78
|
|
|
79
|
+
for rotation in values["multi_rotation_scan"]["rotation_scans"]:
|
|
80
|
+
rotation["sample_id"] = values["sample_id"]
|
|
81
|
+
|
|
73
82
|
new_robot_load_then_centre_params = construct_from_values(
|
|
74
83
|
values, values["robot_load_then_centre"], RobotLoadThenCentre
|
|
75
84
|
)
|
|
@@ -80,6 +89,12 @@ class LoadCentreCollect(
|
|
|
80
89
|
values["robot_load_then_centre"] = new_robot_load_then_centre_params
|
|
81
90
|
return values
|
|
82
91
|
|
|
92
|
+
@model_validator(mode="after")
|
|
93
|
+
def _ensure_features_are_internally_consistent(self) -> Self:
|
|
94
|
+
self.robot_load_then_centre.features = self.features
|
|
95
|
+
self.multi_rotation_scan.features = self.features
|
|
96
|
+
return self
|
|
97
|
+
|
|
83
98
|
@model_validator(mode="after")
|
|
84
99
|
def _check_rotation_start_xyz_is_not_specified(self) -> Self:
|
|
85
100
|
for scan in self.multi_rotation_scan.single_rotation_scans:
|
|
@@ -18,12 +18,14 @@ from scanspec.core import Path as ScanPath
|
|
|
18
18
|
from scanspec.specs import Line
|
|
19
19
|
|
|
20
20
|
from mx_bluesky.common.parameters.components import (
|
|
21
|
+
DiffractionExperiment,
|
|
21
22
|
DiffractionExperimentWithSample,
|
|
22
23
|
IspybExperimentType,
|
|
23
24
|
OptionalGonioAngleStarts,
|
|
24
25
|
OptionalXyzStarts,
|
|
25
26
|
RotationAxis,
|
|
26
27
|
SplitScan,
|
|
28
|
+
WithSample,
|
|
27
29
|
WithScan,
|
|
28
30
|
)
|
|
29
31
|
from mx_bluesky.hyperion.parameters.components import WithHyperionUDCFeatures
|
|
@@ -33,7 +35,7 @@ from mx_bluesky.hyperion.parameters.constants import (
|
|
|
33
35
|
)
|
|
34
36
|
|
|
35
37
|
|
|
36
|
-
class RotationScanPerSweep(OptionalGonioAngleStarts, OptionalXyzStarts):
|
|
38
|
+
class RotationScanPerSweep(OptionalGonioAngleStarts, OptionalXyzStarts, WithSample):
|
|
37
39
|
"""
|
|
38
40
|
Describes a rotation scan about the specified axis.
|
|
39
41
|
|
|
@@ -54,7 +56,7 @@ class RotationScanPerSweep(OptionalGonioAngleStarts, OptionalXyzStarts):
|
|
|
54
56
|
nexus_vds_start_img: int = Field(default=0, ge=0)
|
|
55
57
|
|
|
56
58
|
|
|
57
|
-
class RotationExperiment(
|
|
59
|
+
class RotationExperiment(DiffractionExperiment, WithHyperionUDCFeatures):
|
|
58
60
|
shutter_opening_time_s: float = Field(default=CONST.I03.SHUTTER_TIME_S)
|
|
59
61
|
rotation_increment_deg: float = Field(default=0.1, gt=0)
|
|
60
62
|
ispyb_experiment_type: IspybExperimentType = Field(
|
|
@@ -105,7 +107,9 @@ class RotationExperiment(DiffractionExperimentWithSample, WithHyperionUDCFeature
|
|
|
105
107
|
return aperture_position
|
|
106
108
|
|
|
107
109
|
|
|
108
|
-
class SingleRotationScan(
|
|
110
|
+
class SingleRotationScan(
|
|
111
|
+
WithScan, RotationScanPerSweep, RotationExperiment, DiffractionExperimentWithSample
|
|
112
|
+
):
|
|
109
113
|
@property
|
|
110
114
|
def detector_params(self):
|
|
111
115
|
return self._detector_params(self.omega_start_deg)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from blueapi.core import BlueskyContext
|
|
2
|
-
from dodal.
|
|
2
|
+
from dodal.common.beamlines.beamline_utils import clear_devices
|
|
3
|
+
from dodal.utils import collect_factories, get_beamline_based_on_environment_variable
|
|
3
4
|
|
|
4
5
|
import mx_bluesky.hyperion.experiment_plans as hyperion_plans
|
|
5
6
|
from mx_bluesky.common.utils.log import LOGGER
|
|
@@ -9,11 +10,24 @@ def setup_context(dev_mode: bool = False) -> BlueskyContext:
|
|
|
9
10
|
context = BlueskyContext()
|
|
10
11
|
context.with_plan_module(hyperion_plans)
|
|
11
12
|
|
|
12
|
-
context
|
|
13
|
-
get_beamline_based_on_environment_variable(),
|
|
14
|
-
mock=dev_mode,
|
|
15
|
-
)
|
|
13
|
+
setup_devices(context, dev_mode)
|
|
16
14
|
|
|
17
15
|
LOGGER.info(f"Plans found in context: {context.plan_functions.keys()}")
|
|
18
16
|
|
|
19
17
|
return context
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def clear_all_device_caches(context: BlueskyContext):
|
|
21
|
+
context.unregister_all_devices()
|
|
22
|
+
clear_devices()
|
|
23
|
+
|
|
24
|
+
for f in collect_factories(get_beamline_based_on_environment_variable()).values():
|
|
25
|
+
if hasattr(f, "cache_clear"):
|
|
26
|
+
f.cache_clear() # type: ignore
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def setup_devices(context: BlueskyContext, dev_mode: bool):
|
|
30
|
+
context.with_dodal_module(
|
|
31
|
+
get_beamline_based_on_environment_variable(),
|
|
32
|
+
mock=dev_mode,
|
|
33
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""The zebra on i03 and i04 (phase1 beamlines) are configured in a specific way which are compatible to the plans in this module"""
|
|
File without changes
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
from typing import Protocol, runtime_checkable
|
|
2
|
+
|
|
3
|
+
import bluesky.plan_stubs as bps
|
|
4
|
+
from bluesky.utils import MsgGenerator
|
|
5
|
+
from dodal.devices.zebra.zebra import (
|
|
6
|
+
Zebra,
|
|
7
|
+
)
|
|
8
|
+
from dodal.devices.zebra.zebra_controlled_shutter import (
|
|
9
|
+
ZebraShutter,
|
|
10
|
+
ZebraShutterControl,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
from mx_bluesky.common.parameters.constants import ZEBRA_STATUS_TIMEOUT
|
|
14
|
+
from mx_bluesky.common.utils.log import LOGGER
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@runtime_checkable
|
|
18
|
+
class GridscanSetupDevices(Protocol):
|
|
19
|
+
zebra: Zebra
|
|
20
|
+
sample_shutter: ZebraShutter
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def setup_zebra_for_gridscan(
|
|
24
|
+
composite: GridscanSetupDevices, # XRC gridscan's generic trigger setup expects a composite rather than individual devices
|
|
25
|
+
group="setup_zebra_for_gridscan",
|
|
26
|
+
wait=True,
|
|
27
|
+
) -> MsgGenerator:
|
|
28
|
+
zebra = composite.zebra
|
|
29
|
+
# Set shutter to automatic and to trigger via motion controller GPIO signal (IN4_TTL)
|
|
30
|
+
yield from configure_zebra_and_shutter_for_auto_shutter(
|
|
31
|
+
zebra, composite.sample_shutter, zebra.mapping.sources.IN4_TTL, group=group
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
yield from bps.abs_set(
|
|
35
|
+
zebra.output.out_pvs[zebra.mapping.outputs.TTL_DETECTOR],
|
|
36
|
+
zebra.mapping.sources.IN3_TTL,
|
|
37
|
+
group=group,
|
|
38
|
+
)
|
|
39
|
+
yield from bps.abs_set(
|
|
40
|
+
zebra.output.out_pvs[zebra.mapping.outputs.TTL_XSPRESS3],
|
|
41
|
+
zebra.mapping.sources.DISCONNECT,
|
|
42
|
+
group=group,
|
|
43
|
+
)
|
|
44
|
+
yield from bps.abs_set(
|
|
45
|
+
zebra.output.pulse_1.input, zebra.mapping.sources.DISCONNECT, group=group
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
if wait:
|
|
49
|
+
yield from bps.wait(group, timeout=ZEBRA_STATUS_TIMEOUT)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def set_shutter_auto_input(zebra: Zebra, input: int, group="set_shutter_trigger"):
|
|
53
|
+
"""Set the signal that controls the shutter. We use the second input to the
|
|
54
|
+
Zebra's AND2 gate for this input. ZebraShutter control mode must be in auto for this input to take control
|
|
55
|
+
|
|
56
|
+
For more details see the ZebraShutter device."""
|
|
57
|
+
auto_gate = zebra.mapping.AND_GATE_FOR_AUTO_SHUTTER
|
|
58
|
+
auto_shutter_control = zebra.logic_gates.and_gates[auto_gate]
|
|
59
|
+
yield from bps.abs_set(auto_shutter_control.sources[2], input, group)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def configure_zebra_and_shutter_for_auto_shutter(
|
|
63
|
+
zebra: Zebra, zebra_shutter: ZebraShutter, input: int, group="use_automatic_shutter"
|
|
64
|
+
):
|
|
65
|
+
"""Set the shutter to auto mode, and configure the zebra to trigger the shutter on
|
|
66
|
+
an input source. For the input, use one of the source constants in zebra.py
|
|
67
|
+
|
|
68
|
+
When the shutter is in auto/manual, logic in EPICS sets the Zebra's
|
|
69
|
+
SOFT_IN1 to low/high respectively. The Zebra's AND2 gate should be used to control the shutter while in auto mode.
|
|
70
|
+
To do this, we need (AND2 = SOFT_IN1 AND input), where input is the zebra signal we want to control the shutter when in auto mode.
|
|
71
|
+
"""
|
|
72
|
+
# See https://github.com/DiamondLightSource/dodal/issues/813 for better typing here.
|
|
73
|
+
|
|
74
|
+
# Set shutter to auto mode
|
|
75
|
+
yield from bps.abs_set(
|
|
76
|
+
zebra_shutter.control_mode, ZebraShutterControl.AUTO, group=group
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
auto_gate = zebra.mapping.AND_GATE_FOR_AUTO_SHUTTER
|
|
80
|
+
|
|
81
|
+
# Set first input of AND2 gate to SOFT_IN1, which is high when shutter is in auto mode
|
|
82
|
+
# Note the Zebra should ALWAYS be setup this way. See https://github.com/DiamondLightSource/mx-bluesky/issues/551
|
|
83
|
+
yield from bps.abs_set(
|
|
84
|
+
zebra.logic_gates.and_gates[auto_gate].sources[1],
|
|
85
|
+
zebra.mapping.sources.SOFT_IN1,
|
|
86
|
+
group=group,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# Set the second input of AND2 gate to the requested zebra input source
|
|
90
|
+
yield from set_shutter_auto_input(zebra, input, group=group)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def tidy_up_zebra_after_gridscan(
|
|
94
|
+
zebra: Zebra,
|
|
95
|
+
zebra_shutter: ZebraShutter,
|
|
96
|
+
group="tidy_up_zebra_after_gridscan",
|
|
97
|
+
wait=True,
|
|
98
|
+
) -> MsgGenerator:
|
|
99
|
+
LOGGER.info("Tidying up Zebra")
|
|
100
|
+
|
|
101
|
+
yield from bps.abs_set(
|
|
102
|
+
zebra.output.out_pvs[zebra.mapping.outputs.TTL_DETECTOR],
|
|
103
|
+
zebra.mapping.sources.PC_PULSE,
|
|
104
|
+
group=group,
|
|
105
|
+
)
|
|
106
|
+
yield from bps.abs_set(
|
|
107
|
+
zebra_shutter.control_mode, ZebraShutterControl.MANUAL, group=group
|
|
108
|
+
)
|
|
109
|
+
yield from set_shutter_auto_input(zebra, zebra.mapping.sources.PC_GATE, group=group)
|
|
110
|
+
|
|
111
|
+
if wait:
|
|
112
|
+
yield from bps.wait(group, timeout=ZEBRA_STATUS_TIMEOUT)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mx-bluesky
|
|
3
|
-
Version: 1.5.
|
|
3
|
+
Version: 1.5.2
|
|
4
4
|
Summary: Bluesky tools for MX Beamlines at DLS
|
|
5
5
|
Author-email: Dominic Oram <dominic.oram@diamond.ac.uk>
|
|
6
6
|
License: Apache License
|
|
@@ -221,7 +221,7 @@ Requires-Dist: ispyb
|
|
|
221
221
|
Requires-Dist: jupyterlab
|
|
222
222
|
Requires-Dist: matplotlib
|
|
223
223
|
Requires-Dist: nexgen>=0.11.0
|
|
224
|
-
Requires-Dist: numpy
|
|
224
|
+
Requires-Dist: numpy==2.2.6
|
|
225
225
|
Requires-Dist: opencv-python
|
|
226
226
|
Requires-Dist: opentelemetry-distro
|
|
227
227
|
Requires-Dist: opentelemetry-exporter-otlp
|
|
@@ -233,13 +233,14 @@ Requires-Dist: requests
|
|
|
233
233
|
Requires-Dist: scanspec
|
|
234
234
|
Requires-Dist: scipy
|
|
235
235
|
Requires-Dist: semver
|
|
236
|
+
Requires-Dist: deepdiff
|
|
236
237
|
Requires-Dist: matplotlib
|
|
237
|
-
Requires-Dist: blueapi>=0.
|
|
238
|
+
Requires-Dist: blueapi>=0.15.0
|
|
238
239
|
Requires-Dist: daq-config-server==0.1.1
|
|
239
240
|
Requires-Dist: ophyd>=1.10.5
|
|
240
241
|
Requires-Dist: ophyd-async>=0.10.0a2
|
|
241
242
|
Requires-Dist: bluesky>=1.13.1
|
|
242
|
-
Requires-Dist: dls-dodal==1.
|
|
243
|
+
Requires-Dist: dls-dodal==1.52.0
|
|
243
244
|
Provides-Extra: dev
|
|
244
245
|
Requires-Dist: black; extra == "dev"
|
|
245
246
|
Requires-Dist: build; extra == "dev"
|