mx-bluesky 0.3.1__py3-none-any.whl → 1.2.0__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 +3 -0
- mx_bluesky/{i04 → beamlines/i04}/thawing_plan.py +5 -4
- mx_bluesky/{i24 → beamlines/i24}/serial/blueapi_config.yaml +1 -1
- mx_bluesky/{i24 → beamlines/i24}/serial/dcid.py +2 -2
- mx_bluesky/{i24 → beamlines/i24}/serial/extruder/EX-gui-edm/DetStage.edl +3 -3
- mx_bluesky/{i24 → beamlines/i24}/serial/extruder/EX-gui-edm/DiamondExtruder-I24-py3v1.edl +7 -7
- mx_bluesky/{i24 → beamlines/i24}/serial/extruder/i24ssx_Extruder_Collect_py3v2.py +12 -9
- mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/CustomChip_py3v1.edl +3 -3
- mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/DetStage.edl +3 -3
- mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/DiamondChipI24-py3v1.edl +245 -200
- mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/MappingLite-oxford_py3v1.edl +4 -4
- mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/pumpprobe-py3v1.edl +8 -8
- mx_bluesky/beamlines/i24/serial/fixed_target/__init__.py +0 -0
- mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/i24ssx_Chip_Collect_py3v1.py +80 -70
- mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/i24ssx_Chip_Manager_py3v1.py +20 -21
- mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/i24ssx_Chip_Mapping_py3v1.py +5 -5
- mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/i24ssx_Chip_StartUp_py3v1.py +7 -4
- mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/i24ssx_moveonclick.py +59 -39
- mx_bluesky/{i24 → beamlines/i24}/serial/log.py +1 -9
- mx_bluesky/beamlines/i24/serial/parameters/__init__.py +15 -0
- mx_bluesky/{i24 → beamlines/i24}/serial/parameters/constants.py +1 -1
- mx_bluesky/{i24 → beamlines/i24}/serial/parameters/experiment_parameters.py +4 -25
- mx_bluesky/{i24 → beamlines/i24}/serial/parameters/utils.py +5 -3
- mx_bluesky/{i24 → beamlines/i24}/serial/run_serial.py +1 -1
- mx_bluesky/{i24 → beamlines/i24}/serial/setup_beamline/pv_abstract.py +1 -1
- mx_bluesky/{i24 → beamlines/i24}/serial/setup_beamline/setup_beamline.py +2 -2
- mx_bluesky/{i24 → beamlines/i24}/serial/setup_beamline/setup_detector.py +5 -5
- mx_bluesky/{i24 → beamlines/i24}/serial/write_nexus.py +6 -3
- mx_bluesky/hyperion/__init__.py +1 -0
- mx_bluesky/hyperion/__main__.py +374 -0
- mx_bluesky/hyperion/device_setup_plans/__init__.py +0 -0
- mx_bluesky/hyperion/device_setup_plans/dcm_pitch_roll_mirror_adjuster.py +134 -0
- mx_bluesky/hyperion/device_setup_plans/manipulate_sample.py +110 -0
- mx_bluesky/hyperion/device_setup_plans/position_detector.py +16 -0
- mx_bluesky/hyperion/device_setup_plans/read_hardware_for_setup.py +60 -0
- mx_bluesky/hyperion/device_setup_plans/setup_oav.py +87 -0
- mx_bluesky/hyperion/device_setup_plans/setup_panda.py +210 -0
- mx_bluesky/hyperion/device_setup_plans/setup_zebra.py +214 -0
- mx_bluesky/hyperion/device_setup_plans/smargon.py +25 -0
- mx_bluesky/hyperion/device_setup_plans/utils.py +55 -0
- mx_bluesky/hyperion/device_setup_plans/xbpm_feedback.py +93 -0
- mx_bluesky/hyperion/exceptions.py +47 -0
- mx_bluesky/hyperion/experiment_plans/__init__.py +30 -0
- mx_bluesky/hyperion/experiment_plans/experiment_registry.py +93 -0
- mx_bluesky/hyperion/experiment_plans/flyscan_xray_centre_plan.py +537 -0
- mx_bluesky/hyperion/experiment_plans/grid_detect_then_xray_centre_plan.py +209 -0
- mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py +46 -0
- mx_bluesky/hyperion/experiment_plans/oav_grid_detection_plan.py +173 -0
- mx_bluesky/hyperion/experiment_plans/oav_snapshot_plan.py +81 -0
- mx_bluesky/hyperion/experiment_plans/optimise_attenuation_plan.py +463 -0
- mx_bluesky/hyperion/experiment_plans/pin_centre_then_xray_centre_plan.py +119 -0
- mx_bluesky/hyperion/experiment_plans/pin_tip_centring_plan.py +164 -0
- mx_bluesky/hyperion/experiment_plans/robot_load_and_change_energy.py +237 -0
- mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +162 -0
- mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +436 -0
- mx_bluesky/hyperion/experiment_plans/set_energy_plan.py +60 -0
- mx_bluesky/hyperion/external_interaction/__init__.py +9 -0
- mx_bluesky/hyperion/external_interaction/callbacks/__init__.py +10 -0
- mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +148 -0
- mx_bluesky/hyperion/external_interaction/callbacks/aperture_change_callback.py +22 -0
- mx_bluesky/hyperion/external_interaction/callbacks/common/__init__.py +0 -0
- mx_bluesky/hyperion/external_interaction/callbacks/common/callback_util.py +64 -0
- mx_bluesky/hyperion/external_interaction/callbacks/common/ispyb_mapping.py +62 -0
- mx_bluesky/hyperion/external_interaction/callbacks/grid_detection_callback.py +88 -0
- mx_bluesky/hyperion/external_interaction/callbacks/ispyb_callback_base.py +203 -0
- mx_bluesky/hyperion/external_interaction/callbacks/log_uid_tag_callback.py +20 -0
- mx_bluesky/hyperion/external_interaction/callbacks/logging_callback.py +29 -0
- mx_bluesky/hyperion/external_interaction/callbacks/plan_reactive_callback.py +101 -0
- mx_bluesky/hyperion/external_interaction/callbacks/robot_load/ispyb_callback.py +86 -0
- mx_bluesky/hyperion/external_interaction/callbacks/rotation/__init__.py +0 -0
- mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py +174 -0
- mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_mapping.py +17 -0
- mx_bluesky/hyperion/external_interaction/callbacks/rotation/nexus_callback.py +102 -0
- mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/__init__.py +0 -0
- mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/ispyb_callback.py +269 -0
- mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/ispyb_mapping.py +53 -0
- mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/nexus_callback.py +95 -0
- mx_bluesky/hyperion/external_interaction/callbacks/zocalo_callback.py +92 -0
- mx_bluesky/hyperion/external_interaction/config_server.py +35 -0
- mx_bluesky/hyperion/external_interaction/exceptions.py +13 -0
- mx_bluesky/hyperion/external_interaction/ispyb/__init__.py +0 -0
- mx_bluesky/hyperion/external_interaction/ispyb/data_model.py +95 -0
- mx_bluesky/hyperion/external_interaction/ispyb/exp_eye_store.py +125 -0
- mx_bluesky/hyperion/external_interaction/ispyb/ispyb_store.py +276 -0
- mx_bluesky/hyperion/external_interaction/ispyb/ispyb_utils.py +27 -0
- mx_bluesky/hyperion/external_interaction/nexus/__init__.py +0 -0
- mx_bluesky/hyperion/external_interaction/nexus/nexus_utils.py +148 -0
- mx_bluesky/hyperion/external_interaction/nexus/write_nexus.py +114 -0
- mx_bluesky/hyperion/log.py +99 -0
- mx_bluesky/hyperion/parameters/__init__.py +2 -0
- mx_bluesky/hyperion/parameters/cli.py +68 -0
- mx_bluesky/{parameters → hyperion/parameters}/components.py +80 -26
- mx_bluesky/hyperion/parameters/constants.py +158 -0
- mx_bluesky/hyperion/parameters/gridscan.py +221 -0
- mx_bluesky/hyperion/parameters/load_centre_collect.py +50 -0
- mx_bluesky/hyperion/parameters/robot_load.py +16 -0
- mx_bluesky/hyperion/parameters/rotation.py +160 -0
- mx_bluesky/hyperion/resources/panda/panda-gridscan.yaml +964 -0
- mx_bluesky/hyperion/tracing.py +28 -0
- mx_bluesky/hyperion/utils/context.py +84 -0
- mx_bluesky/hyperion/utils/utils.py +25 -0
- mx_bluesky/hyperion/utils/validation.py +196 -0
- mx_bluesky/jupyter_example.ipynb +3 -2
- {mx_bluesky-0.3.1.dist-info → mx_bluesky-1.2.0.dist-info}/METADATA +26 -11
- mx_bluesky-1.2.0.dist-info/RECORD +140 -0
- {mx_bluesky-0.3.1.dist-info → mx_bluesky-1.2.0.dist-info}/WHEEL +1 -1
- mx_bluesky-1.2.0.dist-info/entry_points.txt +8 -0
- mx_bluesky/i04/__init__.py +0 -3
- mx_bluesky/i24/serial/parameters/__init__.py +0 -15
- mx_bluesky/parameters/__init__.py +0 -31
- mx_bluesky-0.3.1.dist-info/RECORD +0 -67
- mx_bluesky-0.3.1.dist-info/entry_points.txt +0 -4
- /mx_bluesky/{i24 → beamlines}/__init__.py +0 -0
- /mx_bluesky/{i04 → beamlines/i04}/callbacks/murko_callback.py +0 -0
- /mx_bluesky/{i24/serial/extruder → beamlines/i24}/__init__.py +0 -0
- /mx_bluesky/{i24 → beamlines/i24}/serial/__init__.py +0 -0
- /mx_bluesky/{i24 → beamlines/i24}/serial/extruder/EX-gui-edm/microdrop_alignment.edl +0 -0
- /mx_bluesky/{i24/serial/fixed_target → beamlines/i24/serial/extruder}/__init__.py +0 -0
- /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/ME14E-GeneralPurpose.edl +0 -0
- /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/PMAC_Command.edl +0 -0
- /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/Shutter_Control.edl +0 -0
- /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/microdrop_alignment.edl +0 -0
- /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/nudgechip.edl +0 -0
- /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/short1-laser.png +0 -0
- /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/short2-laser.png +0 -0
- /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/ft_utils.py +0 -0
- /mx_bluesky/{i24 → beamlines/i24}/serial/parameters/fixed_target/cs/cs_maker.json +0 -0
- /mx_bluesky/{i24 → beamlines/i24}/serial/parameters/fixed_target/cs/motor_direction.txt +0 -0
- /mx_bluesky/{i24 → beamlines/i24}/serial/parameters/fixed_target/pvar_files/minichip-oxford.pvar +0 -0
- /mx_bluesky/{i24 → beamlines/i24}/serial/parameters/fixed_target/pvar_files/oxford.pvar +0 -0
- /mx_bluesky/{i24 → beamlines/i24}/serial/run_extruder.sh +0 -0
- /mx_bluesky/{i24 → beamlines/i24}/serial/run_fixed_target.sh +0 -0
- /mx_bluesky/{i24 → beamlines/i24}/serial/run_ssx.sh +0 -0
- /mx_bluesky/{i24 → beamlines/i24}/serial/set_visit_directory.sh +0 -0
- /mx_bluesky/{i24 → beamlines/i24}/serial/setup_beamline/__init__.py +0 -0
- /mx_bluesky/{i24 → beamlines/i24}/serial/setup_beamline/ca.py +0 -0
- /mx_bluesky/{i24 → beamlines/i24}/serial/setup_beamline/pv.py +0 -0
- /mx_bluesky/{i24 → beamlines/i24}/serial/setup_beamline/setup_zebra_plans.py +0 -0
- /mx_bluesky/{i24 → beamlines/i24}/serial/start_blueapi.sh +0 -0
- {mx_bluesky-0.3.1.dist-info → mx_bluesky-1.2.0.dist-info}/LICENSE +0 -0
- {mx_bluesky-0.3.1.dist-info → mx_bluesky-1.2.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
|
|
3
|
+
from bluesky.callbacks import CallbackBase
|
|
4
|
+
|
|
5
|
+
from mx_bluesky.hyperion.external_interaction.callbacks.robot_load.ispyb_callback import (
|
|
6
|
+
RobotLoadISPyBCallback,
|
|
7
|
+
)
|
|
8
|
+
from mx_bluesky.hyperion.external_interaction.callbacks.rotation.ispyb_callback import (
|
|
9
|
+
RotationISPyBCallback,
|
|
10
|
+
)
|
|
11
|
+
from mx_bluesky.hyperion.external_interaction.callbacks.rotation.nexus_callback import (
|
|
12
|
+
RotationNexusFileCallback,
|
|
13
|
+
)
|
|
14
|
+
from mx_bluesky.hyperion.external_interaction.callbacks.xray_centre.ispyb_callback import (
|
|
15
|
+
GridscanISPyBCallback,
|
|
16
|
+
)
|
|
17
|
+
from mx_bluesky.hyperion.external_interaction.callbacks.xray_centre.nexus_callback import (
|
|
18
|
+
GridscanNexusFileCallback,
|
|
19
|
+
)
|
|
20
|
+
from mx_bluesky.hyperion.external_interaction.callbacks.zocalo_callback import (
|
|
21
|
+
ZocaloCallback,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
CallbacksFactory = Callable[[], tuple[CallbackBase, ...]]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def create_robot_load_and_centre_callbacks() -> (
|
|
28
|
+
tuple[GridscanNexusFileCallback, GridscanISPyBCallback, RobotLoadISPyBCallback]
|
|
29
|
+
):
|
|
30
|
+
return (
|
|
31
|
+
GridscanNexusFileCallback(),
|
|
32
|
+
GridscanISPyBCallback(emit=ZocaloCallback()),
|
|
33
|
+
RobotLoadISPyBCallback(),
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def create_gridscan_callbacks() -> (
|
|
38
|
+
tuple[GridscanNexusFileCallback, GridscanISPyBCallback]
|
|
39
|
+
):
|
|
40
|
+
return (GridscanNexusFileCallback(), GridscanISPyBCallback(emit=ZocaloCallback()))
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def create_rotation_callbacks() -> (
|
|
44
|
+
tuple[RotationNexusFileCallback, RotationISPyBCallback]
|
|
45
|
+
):
|
|
46
|
+
return (RotationNexusFileCallback(), RotationISPyBCallback(emit=ZocaloCallback()))
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def create_load_centre_collect_callbacks() -> (
|
|
50
|
+
tuple[
|
|
51
|
+
GridscanNexusFileCallback,
|
|
52
|
+
GridscanISPyBCallback,
|
|
53
|
+
RobotLoadISPyBCallback,
|
|
54
|
+
RotationNexusFileCallback,
|
|
55
|
+
RotationISPyBCallback,
|
|
56
|
+
]
|
|
57
|
+
):
|
|
58
|
+
return (
|
|
59
|
+
GridscanNexusFileCallback(),
|
|
60
|
+
GridscanISPyBCallback(emit=ZocaloCallback()),
|
|
61
|
+
RobotLoadISPyBCallback(),
|
|
62
|
+
RotationNexusFileCallback(),
|
|
63
|
+
RotationISPyBCallback(emit=ZocaloCallback()),
|
|
64
|
+
)
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from mx_bluesky.hyperion.external_interaction.ispyb.data_model import (
|
|
4
|
+
DataCollectionGroupInfo,
|
|
5
|
+
DataCollectionInfo,
|
|
6
|
+
)
|
|
7
|
+
from mx_bluesky.hyperion.external_interaction.ispyb.ispyb_store import (
|
|
8
|
+
EIGER_FILE_SUFFIX,
|
|
9
|
+
I03_EIGER_DETECTOR,
|
|
10
|
+
)
|
|
11
|
+
from mx_bluesky.hyperion.external_interaction.ispyb.ispyb_utils import (
|
|
12
|
+
get_current_time_string,
|
|
13
|
+
)
|
|
14
|
+
from mx_bluesky.hyperion.parameters.components import DiffractionExperimentWithSample
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def populate_data_collection_group(params: DiffractionExperimentWithSample):
|
|
18
|
+
dcg_info = DataCollectionGroupInfo(
|
|
19
|
+
visit_string=params.visit,
|
|
20
|
+
experiment_type=params.ispyb_experiment_type.value,
|
|
21
|
+
sample_id=params.sample_id,
|
|
22
|
+
)
|
|
23
|
+
return dcg_info
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def populate_remaining_data_collection_info(
|
|
27
|
+
comment,
|
|
28
|
+
data_collection_group_id,
|
|
29
|
+
data_collection_info: DataCollectionInfo,
|
|
30
|
+
params: DiffractionExperimentWithSample,
|
|
31
|
+
):
|
|
32
|
+
data_collection_info.visit_string = params.visit
|
|
33
|
+
data_collection_info.parent_id = data_collection_group_id
|
|
34
|
+
data_collection_info.sample_id = params.sample_id
|
|
35
|
+
data_collection_info.detector_id = I03_EIGER_DETECTOR
|
|
36
|
+
data_collection_info.comments = comment
|
|
37
|
+
data_collection_info.detector_distance = params.detector_params.detector_distance
|
|
38
|
+
data_collection_info.exp_time = params.detector_params.exposure_time
|
|
39
|
+
data_collection_info.imgdir = params.detector_params.directory
|
|
40
|
+
data_collection_info.imgprefix = params.detector_params.prefix
|
|
41
|
+
data_collection_info.imgsuffix = EIGER_FILE_SUFFIX
|
|
42
|
+
# Both overlap and n_passes included for backwards compatibility,
|
|
43
|
+
# planned to be removed later
|
|
44
|
+
data_collection_info.n_passes = 1
|
|
45
|
+
data_collection_info.overlap = 0
|
|
46
|
+
data_collection_info.start_image_number = 1
|
|
47
|
+
beam_position = params.detector_params.get_beam_position_mm(
|
|
48
|
+
params.detector_params.detector_distance
|
|
49
|
+
)
|
|
50
|
+
data_collection_info.xbeam = beam_position[0]
|
|
51
|
+
data_collection_info.ybeam = beam_position[1]
|
|
52
|
+
data_collection_info.start_time = get_current_time_string()
|
|
53
|
+
# temporary file template until nxs filewriting is integrated and we can use
|
|
54
|
+
# that file name
|
|
55
|
+
data_collection_info.file_template = f"{params.detector_params.prefix}_{data_collection_info.data_collection_number}_master.h5"
|
|
56
|
+
return data_collection_info
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def get_proposal_and_session_from_visit_string(visit_string: str) -> tuple[str, int]:
|
|
60
|
+
visit_parts = visit_string.split("-")
|
|
61
|
+
assert len(visit_parts) == 2, f"Unexpected visit string {visit_string}"
|
|
62
|
+
return visit_parts[0], int(visit_parts[1])
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
from typing import TypedDict
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from bluesky.callbacks import CallbackBase
|
|
5
|
+
from dodal.devices.oav.oav_detector import OAVConfigParams
|
|
6
|
+
from dodal.devices.oav.utils import calculate_x_y_z_of_pixel
|
|
7
|
+
from event_model.documents import Event
|
|
8
|
+
|
|
9
|
+
from mx_bluesky.hyperion.log import LOGGER
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class GridParamUpdate(TypedDict):
|
|
13
|
+
x_start_um: float
|
|
14
|
+
y_start_um: float
|
|
15
|
+
y2_start_um: float
|
|
16
|
+
z_start_um: float
|
|
17
|
+
z2_start_um: float
|
|
18
|
+
x_steps: int
|
|
19
|
+
y_steps: int
|
|
20
|
+
z_steps: int
|
|
21
|
+
x_step_size_um: float
|
|
22
|
+
y_step_size_um: float
|
|
23
|
+
z_step_size_um: float
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class GridDetectionCallback(CallbackBase):
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
oav_params: OAVConfigParams,
|
|
30
|
+
*args,
|
|
31
|
+
) -> None:
|
|
32
|
+
super().__init__(*args)
|
|
33
|
+
self.oav_params = oav_params
|
|
34
|
+
self.start_positions: list = []
|
|
35
|
+
self.box_numbers: list = []
|
|
36
|
+
|
|
37
|
+
def event(self, doc: Event):
|
|
38
|
+
data = doc.get("data")
|
|
39
|
+
top_left_x_px = data["oav_grid_snapshot_top_left_x"]
|
|
40
|
+
box_width_px = data["oav_grid_snapshot_box_width"]
|
|
41
|
+
x_of_centre_of_first_box_px = top_left_x_px + box_width_px / 2
|
|
42
|
+
|
|
43
|
+
top_left_y_px = data["oav_grid_snapshot_top_left_y"]
|
|
44
|
+
y_of_centre_of_first_box_px = top_left_y_px + box_width_px / 2
|
|
45
|
+
|
|
46
|
+
smargon_omega = data["smargon-omega"]
|
|
47
|
+
current_xyz = np.array(
|
|
48
|
+
[data["smargon-x"], data["smargon-y"], data["smargon-z"]]
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
centre_of_first_box = (
|
|
52
|
+
x_of_centre_of_first_box_px,
|
|
53
|
+
y_of_centre_of_first_box_px,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
position_grid_start = calculate_x_y_z_of_pixel(
|
|
57
|
+
current_xyz, smargon_omega, centre_of_first_box, self.oav_params
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
LOGGER.info(f"Calculated start position {position_grid_start}")
|
|
61
|
+
|
|
62
|
+
self.start_positions.append(position_grid_start)
|
|
63
|
+
self.box_numbers.append(
|
|
64
|
+
(
|
|
65
|
+
data["oav_grid_snapshot_num_boxes_x"],
|
|
66
|
+
data["oav_grid_snapshot_num_boxes_y"],
|
|
67
|
+
)
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
self.x_step_size_mm = box_width_px * self.oav_params.micronsPerXPixel / 1000
|
|
71
|
+
self.y_step_size_mm = box_width_px * self.oav_params.micronsPerYPixel / 1000
|
|
72
|
+
self.z_step_size_mm = box_width_px * self.oav_params.micronsPerYPixel / 1000
|
|
73
|
+
return doc
|
|
74
|
+
|
|
75
|
+
def get_grid_parameters(self) -> GridParamUpdate:
|
|
76
|
+
return {
|
|
77
|
+
"x_start_um": self.start_positions[0][0],
|
|
78
|
+
"y_start_um": self.start_positions[0][1],
|
|
79
|
+
"y2_start_um": self.start_positions[0][1],
|
|
80
|
+
"z_start_um": self.start_positions[1][2],
|
|
81
|
+
"z2_start_um": self.start_positions[1][2],
|
|
82
|
+
"x_steps": self.box_numbers[0][0],
|
|
83
|
+
"y_steps": self.box_numbers[0][1],
|
|
84
|
+
"z_steps": self.box_numbers[1][1],
|
|
85
|
+
"x_step_size_um": self.x_step_size_mm,
|
|
86
|
+
"y_step_size_um": self.y_step_size_mm,
|
|
87
|
+
"z_step_size_um": self.z_step_size_mm,
|
|
88
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from abc import abstractmethod
|
|
4
|
+
from collections.abc import Callable, Sequence
|
|
5
|
+
from typing import TYPE_CHECKING, Any, TypeVar, cast
|
|
6
|
+
|
|
7
|
+
from dodal.beamline_specific_utils.i03 import beam_size_from_aperture
|
|
8
|
+
from dodal.devices.detector.det_resolution import resolution
|
|
9
|
+
from dodal.devices.synchrotron import SynchrotronMode
|
|
10
|
+
|
|
11
|
+
from mx_bluesky.hyperion.external_interaction.callbacks.plan_reactive_callback import (
|
|
12
|
+
PlanReactiveCallback,
|
|
13
|
+
)
|
|
14
|
+
from mx_bluesky.hyperion.external_interaction.ispyb.data_model import (
|
|
15
|
+
DataCollectionInfo,
|
|
16
|
+
DataCollectionPositionInfo,
|
|
17
|
+
ScanDataInfo,
|
|
18
|
+
)
|
|
19
|
+
from mx_bluesky.hyperion.external_interaction.ispyb.ispyb_store import (
|
|
20
|
+
IspybIds,
|
|
21
|
+
StoreInIspyb,
|
|
22
|
+
)
|
|
23
|
+
from mx_bluesky.hyperion.external_interaction.ispyb.ispyb_utils import get_ispyb_config
|
|
24
|
+
from mx_bluesky.hyperion.log import ISPYB_LOGGER, set_dcgid_tag
|
|
25
|
+
from mx_bluesky.hyperion.parameters.components import DiffractionExperimentWithSample
|
|
26
|
+
from mx_bluesky.hyperion.parameters.constants import CONST
|
|
27
|
+
from mx_bluesky.hyperion.utils.utils import convert_eV_to_angstrom
|
|
28
|
+
|
|
29
|
+
from .logging_callback import format_doc_for_log
|
|
30
|
+
|
|
31
|
+
D = TypeVar("D")
|
|
32
|
+
if TYPE_CHECKING:
|
|
33
|
+
from event_model.documents import Event, EventDescriptor, RunStart, RunStop
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class BaseISPyBCallback(PlanReactiveCallback):
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
*,
|
|
40
|
+
emit: Callable[..., Any] | None = None,
|
|
41
|
+
) -> None:
|
|
42
|
+
"""Subclasses should run super().__init__() with parameters, then set
|
|
43
|
+
self.ispyb to the type of ispyb relevant to the experiment and define the type
|
|
44
|
+
for self.ispyb_ids."""
|
|
45
|
+
ISPYB_LOGGER.debug("Initialising ISPyB callback")
|
|
46
|
+
super().__init__(log=ISPYB_LOGGER, emit=emit)
|
|
47
|
+
self._oav_snapshot_event_idx: int = 0
|
|
48
|
+
self.params: DiffractionExperimentWithSample | None = None
|
|
49
|
+
self.ispyb: StoreInIspyb
|
|
50
|
+
self.descriptors: dict[str, EventDescriptor] = {}
|
|
51
|
+
self.ispyb_config = get_ispyb_config()
|
|
52
|
+
if (
|
|
53
|
+
self.ispyb_config == CONST.SIM.ISPYB_CONFIG
|
|
54
|
+
or self.ispyb_config == CONST.SIM.DEV_ISPYB_DATABASE_CFG
|
|
55
|
+
):
|
|
56
|
+
ISPYB_LOGGER.warning(
|
|
57
|
+
f"{self.__class__} using dev ISPyB config: {self.ispyb_config}. If you"
|
|
58
|
+
"want to use the real database, please set the ISPYB_CONFIG_PATH "
|
|
59
|
+
"environment variable."
|
|
60
|
+
)
|
|
61
|
+
self.uid_to_finalize_on: str | None = None
|
|
62
|
+
self.ispyb_ids: IspybIds = IspybIds()
|
|
63
|
+
self.log = ISPYB_LOGGER
|
|
64
|
+
|
|
65
|
+
def activity_gated_start(self, doc: RunStart):
|
|
66
|
+
self._oav_snapshot_event_idx = 0
|
|
67
|
+
return self._tag_doc(doc)
|
|
68
|
+
|
|
69
|
+
def activity_gated_descriptor(self, doc: EventDescriptor):
|
|
70
|
+
self.descriptors[doc["uid"]] = doc
|
|
71
|
+
return self._tag_doc(doc)
|
|
72
|
+
|
|
73
|
+
def activity_gated_event(self, doc: Event) -> Event:
|
|
74
|
+
"""Subclasses should extend this to add a call to set_dcig_tag from
|
|
75
|
+
hyperion.log"""
|
|
76
|
+
ISPYB_LOGGER.debug("ISPyB handler received event document.")
|
|
77
|
+
assert self.ispyb is not None, "ISPyB deposition wasn't initialised!"
|
|
78
|
+
assert self.params is not None, "ISPyB handler didn't receive parameters!"
|
|
79
|
+
|
|
80
|
+
event_descriptor = self.descriptors.get(doc["descriptor"])
|
|
81
|
+
if event_descriptor is None:
|
|
82
|
+
ISPYB_LOGGER.warning(
|
|
83
|
+
f"Ispyb handler {self} received event doc {format_doc_for_log(doc)} and "
|
|
84
|
+
"has no corresponding descriptor record"
|
|
85
|
+
)
|
|
86
|
+
return doc
|
|
87
|
+
match event_descriptor.get("name"):
|
|
88
|
+
case CONST.DESCRIPTORS.HARDWARE_READ_PRE:
|
|
89
|
+
scan_data_infos = self._handle_ispyb_hardware_read(doc)
|
|
90
|
+
case CONST.DESCRIPTORS.HARDWARE_READ_DURING:
|
|
91
|
+
scan_data_infos = self._handle_ispyb_transmission_flux_read(doc)
|
|
92
|
+
case _:
|
|
93
|
+
return self._tag_doc(doc)
|
|
94
|
+
self.ispyb_ids = self.ispyb.update_deposition(self.ispyb_ids, scan_data_infos)
|
|
95
|
+
ISPYB_LOGGER.info(f"Received ISPYB IDs: {self.ispyb_ids}")
|
|
96
|
+
return self._tag_doc(doc)
|
|
97
|
+
|
|
98
|
+
def _handle_ispyb_hardware_read(self, doc) -> Sequence[ScanDataInfo]:
|
|
99
|
+
assert self.params, "Event handled before activity_gated_start received params"
|
|
100
|
+
ISPYB_LOGGER.info("ISPyB handler received event from read hardware")
|
|
101
|
+
assert isinstance(
|
|
102
|
+
synchrotron_mode := doc["data"]["synchrotron-synchrotron_mode"],
|
|
103
|
+
SynchrotronMode,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
hwscan_data_collection_info = DataCollectionInfo(
|
|
107
|
+
undulator_gap1=doc["data"]["undulator-current_gap"],
|
|
108
|
+
synchrotron_mode=synchrotron_mode.value,
|
|
109
|
+
slitgap_horizontal=doc["data"]["s4_slit_gaps_xgap"],
|
|
110
|
+
slitgap_vertical=doc["data"]["s4_slit_gaps_ygap"],
|
|
111
|
+
)
|
|
112
|
+
hwscan_position_info = DataCollectionPositionInfo(
|
|
113
|
+
pos_x=float(doc["data"]["smargon-x"]),
|
|
114
|
+
pos_y=float(doc["data"]["smargon-y"]),
|
|
115
|
+
pos_z=float(doc["data"]["smargon-z"]),
|
|
116
|
+
)
|
|
117
|
+
scan_data_infos = self.populate_info_for_update(
|
|
118
|
+
hwscan_data_collection_info, hwscan_position_info, self.params
|
|
119
|
+
)
|
|
120
|
+
ISPYB_LOGGER.info("Updating ispyb data collection after hardware read.")
|
|
121
|
+
return scan_data_infos
|
|
122
|
+
|
|
123
|
+
def _handle_ispyb_transmission_flux_read(self, doc) -> Sequence[ScanDataInfo]:
|
|
124
|
+
assert self.params
|
|
125
|
+
aperture = doc["data"]["aperture_scatterguard-selected_aperture"]
|
|
126
|
+
aperture_radius = doc["data"]["aperture_scatterguard-radius"]
|
|
127
|
+
beamsize = beam_size_from_aperture(aperture_radius)
|
|
128
|
+
beamsize_x_mm = beamsize.x_um / 1000 if beamsize.x_um else None
|
|
129
|
+
beamsize_y_mm = beamsize.y_um / 1000 if beamsize.y_um else None
|
|
130
|
+
hwscan_data_collection_info = DataCollectionInfo(
|
|
131
|
+
beamsize_at_samplex=beamsize_x_mm,
|
|
132
|
+
beamsize_at_sampley=beamsize_y_mm,
|
|
133
|
+
focal_spot_size_at_samplex=beamsize_x_mm,
|
|
134
|
+
focal_spot_size_at_sampley=beamsize_y_mm,
|
|
135
|
+
flux=doc["data"]["flux_flux_reading"],
|
|
136
|
+
)
|
|
137
|
+
if transmission := doc["data"]["attenuator-actual_transmission"]:
|
|
138
|
+
# Ispyb wants the transmission in a percentage, we use fractions
|
|
139
|
+
hwscan_data_collection_info.transmission = transmission * 100
|
|
140
|
+
event_energy = doc["data"]["dcm-energy_in_kev"]
|
|
141
|
+
if event_energy:
|
|
142
|
+
energy_ev = event_energy * 1000
|
|
143
|
+
wavelength_angstroms = convert_eV_to_angstrom(energy_ev)
|
|
144
|
+
hwscan_data_collection_info.wavelength = wavelength_angstroms
|
|
145
|
+
hwscan_data_collection_info.resolution = resolution(
|
|
146
|
+
self.params.detector_params,
|
|
147
|
+
wavelength_angstroms,
|
|
148
|
+
self.params.detector_params.detector_distance,
|
|
149
|
+
)
|
|
150
|
+
scan_data_infos = self.populate_info_for_update(
|
|
151
|
+
hwscan_data_collection_info, None, self.params
|
|
152
|
+
)
|
|
153
|
+
ISPYB_LOGGER.info("Updating ispyb data collection after flux read.")
|
|
154
|
+
self.append_to_comment(f"Aperture: {aperture}. ")
|
|
155
|
+
return scan_data_infos
|
|
156
|
+
|
|
157
|
+
@abstractmethod
|
|
158
|
+
def populate_info_for_update(
|
|
159
|
+
self,
|
|
160
|
+
event_sourced_data_collection_info: DataCollectionInfo,
|
|
161
|
+
event_sourced_position_info: DataCollectionPositionInfo | None,
|
|
162
|
+
params: DiffractionExperimentWithSample,
|
|
163
|
+
) -> Sequence[ScanDataInfo]:
|
|
164
|
+
pass
|
|
165
|
+
|
|
166
|
+
def activity_gated_stop(self, doc: RunStop) -> RunStop:
|
|
167
|
+
"""Subclasses must check that they are recieving a stop document for the correct
|
|
168
|
+
uid to use this method!"""
|
|
169
|
+
assert (
|
|
170
|
+
self.ispyb is not None
|
|
171
|
+
), "ISPyB handler received stop document, but deposition object doesn't exist!"
|
|
172
|
+
ISPYB_LOGGER.debug("ISPyB handler received stop document.")
|
|
173
|
+
exit_status = (
|
|
174
|
+
doc.get("exit_status") or "Exit status not available in stop document!"
|
|
175
|
+
)
|
|
176
|
+
reason = doc.get("reason") or ""
|
|
177
|
+
set_dcgid_tag(None)
|
|
178
|
+
try:
|
|
179
|
+
self.ispyb.end_deposition(self.ispyb_ids, exit_status, reason)
|
|
180
|
+
except Exception as e:
|
|
181
|
+
ISPYB_LOGGER.warning(
|
|
182
|
+
f"Failed to finalise ISPyB deposition on stop document: {format_doc_for_log(doc)} with exception: {e}"
|
|
183
|
+
)
|
|
184
|
+
return self._tag_doc(doc)
|
|
185
|
+
|
|
186
|
+
def _append_to_comment(self, id: int, comment: str) -> None:
|
|
187
|
+
assert self.ispyb is not None
|
|
188
|
+
try:
|
|
189
|
+
self.ispyb.append_to_comment(id, comment)
|
|
190
|
+
except TypeError:
|
|
191
|
+
ISPYB_LOGGER.warning(
|
|
192
|
+
"ISPyB deposition not initialised, can't update comment."
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
def append_to_comment(self, comment: str):
|
|
196
|
+
for id in self.ispyb_ids.data_collection_ids:
|
|
197
|
+
self._append_to_comment(id, comment)
|
|
198
|
+
|
|
199
|
+
def _tag_doc(self, doc: D) -> D:
|
|
200
|
+
assert isinstance(doc, dict)
|
|
201
|
+
if self.ispyb_ids:
|
|
202
|
+
doc["ispyb_dcids"] = self.ispyb_ids.data_collection_ids
|
|
203
|
+
return cast(D, doc)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from bluesky.callbacks import CallbackBase
|
|
2
|
+
from event_model import RunStart, RunStop
|
|
3
|
+
|
|
4
|
+
from mx_bluesky.hyperion.log import set_uid_tag
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class LogUidTaggingCallback(CallbackBase):
|
|
8
|
+
def __init__(self) -> None:
|
|
9
|
+
"""Sets the logging filter to add the outermost run uid to graylog messages"""
|
|
10
|
+
self.run_uid = None
|
|
11
|
+
|
|
12
|
+
def start(self, doc: RunStart):
|
|
13
|
+
if self.run_uid is None:
|
|
14
|
+
self.run_uid = doc.get("uid")
|
|
15
|
+
set_uid_tag(self.run_uid)
|
|
16
|
+
|
|
17
|
+
def stop(self, doc: RunStop):
|
|
18
|
+
if doc.get("run_start") == self.run_uid:
|
|
19
|
+
self.run_uid = None
|
|
20
|
+
set_uid_tag(None)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from bluesky.callbacks import CallbackBase
|
|
4
|
+
|
|
5
|
+
from mx_bluesky.hyperion.log import LOGGER
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class _BestEffortEncoder(json.JSONEncoder):
|
|
9
|
+
def default(self, o):
|
|
10
|
+
return repr(o)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def format_doc_for_log(doc):
|
|
14
|
+
return json.dumps(doc, indent=2, cls=_BestEffortEncoder)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class VerbosePlanExecutionLoggingCallback(CallbackBase):
|
|
18
|
+
def start(self, doc):
|
|
19
|
+
LOGGER.info(f"START: {format_doc_for_log(doc)}")
|
|
20
|
+
|
|
21
|
+
def descriptor(self, doc):
|
|
22
|
+
LOGGER.info(f"DESCRIPTOR: {format_doc_for_log(doc)}")
|
|
23
|
+
|
|
24
|
+
def event(self, doc):
|
|
25
|
+
LOGGER.info(f"EVENT: {format_doc_for_log(doc)}")
|
|
26
|
+
return doc
|
|
27
|
+
|
|
28
|
+
def stop(self, doc):
|
|
29
|
+
LOGGER.info(f"STOP: {format_doc_for_log(doc)}")
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from logging import Logger
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
6
|
+
|
|
7
|
+
from bluesky.callbacks import CallbackBase
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from event_model.documents import Event, EventDescriptor, RunStart, RunStop
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class PlanReactiveCallback(CallbackBase):
|
|
14
|
+
log: Logger # type: ignore # this is initialised to None and not annotated in the superclass
|
|
15
|
+
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
log: Logger,
|
|
19
|
+
*,
|
|
20
|
+
emit: Callable[..., Any] | None = None,
|
|
21
|
+
) -> None:
|
|
22
|
+
"""A callback base class which can be left permanently subscribed to a plan, and
|
|
23
|
+
will 'activate' and 'deactivate' at the start and end of a plan which provides
|
|
24
|
+
metadata to trigger this.
|
|
25
|
+
The run_decorator of the plan should include in its metadata dictionary the key
|
|
26
|
+
'activate callbacks', with a list of strings of the callback class(es) to
|
|
27
|
+
activate or deactivate. On a recieving a start doc which specifies this, this
|
|
28
|
+
class will be activated, and on recieving the stop document for the
|
|
29
|
+
corresponding uid it will deactivate. The ordinary 'start', 'descriptor',
|
|
30
|
+
'event' and 'stop' methods will be triggered as normal, and will in turn trigger
|
|
31
|
+
'activity_gated_' methods - to preserve this functionality, subclasses which
|
|
32
|
+
override 'start' etc. should include a call to super().start(...) etc.
|
|
33
|
+
The logic of how activation is triggered will change to a more readable, version
|
|
34
|
+
in the future (https://github.com/DiamondLightSource/hyperion/issues/964)."""
|
|
35
|
+
|
|
36
|
+
super().__init__(emit=emit)
|
|
37
|
+
self.emit_cb = emit # to avoid GC; base class only holds a WeakRef
|
|
38
|
+
self.active = False
|
|
39
|
+
self.activity_uid = 0
|
|
40
|
+
self.log = log
|
|
41
|
+
|
|
42
|
+
def _run_activity_gated(self, name: str, func, doc, override=False):
|
|
43
|
+
# Runs `func` if self.active is True or overide is true. Override can be used
|
|
44
|
+
# to run the function even after setting self.active to False, i.e. in the last
|
|
45
|
+
# handler of a run.
|
|
46
|
+
|
|
47
|
+
running_gated_function = override or self.active
|
|
48
|
+
if not running_gated_function:
|
|
49
|
+
return doc
|
|
50
|
+
try:
|
|
51
|
+
return self.emit(name, func(doc))
|
|
52
|
+
except Exception as e:
|
|
53
|
+
self.log.exception(e)
|
|
54
|
+
raise
|
|
55
|
+
|
|
56
|
+
def start(self, doc: RunStart) -> RunStart | None:
|
|
57
|
+
callbacks_to_activate = doc.get("activate_callbacks")
|
|
58
|
+
if callbacks_to_activate and not self.active:
|
|
59
|
+
activate = type(self).__name__ in callbacks_to_activate
|
|
60
|
+
self.active = activate
|
|
61
|
+
self.log.info(
|
|
62
|
+
f"{'' if activate else 'not'} activating {type(self).__name__}"
|
|
63
|
+
)
|
|
64
|
+
self.activity_uid = doc.get("uid")
|
|
65
|
+
return self._run_activity_gated("start", self.activity_gated_start, doc)
|
|
66
|
+
|
|
67
|
+
def descriptor(self, doc: EventDescriptor) -> EventDescriptor | None:
|
|
68
|
+
return self._run_activity_gated(
|
|
69
|
+
"descriptor", self.activity_gated_descriptor, doc
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
def event(self, doc: Event) -> Event | None: # type: ignore
|
|
73
|
+
return self._run_activity_gated("event", self.activity_gated_event, doc)
|
|
74
|
+
|
|
75
|
+
def stop(self, doc: RunStop) -> RunStop | None:
|
|
76
|
+
do_stop = self.active
|
|
77
|
+
if doc.get("run_start") == self.activity_uid:
|
|
78
|
+
self.active = False
|
|
79
|
+
self.activity_uid = 0
|
|
80
|
+
return (
|
|
81
|
+
self._run_activity_gated(
|
|
82
|
+
"stop", self.activity_gated_stop, doc, override=True
|
|
83
|
+
)
|
|
84
|
+
if do_stop
|
|
85
|
+
else doc
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
def activity_gated_start(self, doc: RunStart) -> RunStart | None:
|
|
89
|
+
return doc
|
|
90
|
+
|
|
91
|
+
def activity_gated_descriptor(self, doc: EventDescriptor) -> EventDescriptor | None:
|
|
92
|
+
return doc
|
|
93
|
+
|
|
94
|
+
def activity_gated_event(self, doc: Event) -> Event | None:
|
|
95
|
+
return doc
|
|
96
|
+
|
|
97
|
+
def activity_gated_stop(self, doc: RunStop) -> RunStop | None:
|
|
98
|
+
return doc
|
|
99
|
+
|
|
100
|
+
def __repr__(self) -> str:
|
|
101
|
+
return f"<{self.__class__.__name__} with id: {hex(id(self))} - active: {self.active}>"
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from event_model.documents import EventDescriptor
|
|
6
|
+
|
|
7
|
+
from mx_bluesky.hyperion.external_interaction.callbacks.common.ispyb_mapping import (
|
|
8
|
+
get_proposal_and_session_from_visit_string,
|
|
9
|
+
)
|
|
10
|
+
from mx_bluesky.hyperion.external_interaction.callbacks.plan_reactive_callback import (
|
|
11
|
+
PlanReactiveCallback,
|
|
12
|
+
)
|
|
13
|
+
from mx_bluesky.hyperion.external_interaction.ispyb.exp_eye_store import (
|
|
14
|
+
ExpeyeInteraction,
|
|
15
|
+
RobotActionID,
|
|
16
|
+
)
|
|
17
|
+
from mx_bluesky.hyperion.log import ISPYB_LOGGER
|
|
18
|
+
from mx_bluesky.hyperion.parameters.constants import CONST
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from event_model.documents import Event, EventDescriptor, RunStart, RunStop
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class RobotLoadISPyBCallback(PlanReactiveCallback):
|
|
25
|
+
def __init__(self) -> None:
|
|
26
|
+
ISPYB_LOGGER.debug("Initialising ISPyB Robot Load Callback")
|
|
27
|
+
super().__init__(log=ISPYB_LOGGER)
|
|
28
|
+
self.run_uid: str | None = None
|
|
29
|
+
self.descriptors: dict[str, EventDescriptor] = {}
|
|
30
|
+
self.action_id: RobotActionID | None = None
|
|
31
|
+
self.expeye = ExpeyeInteraction()
|
|
32
|
+
|
|
33
|
+
def activity_gated_start(self, doc: RunStart):
|
|
34
|
+
ISPYB_LOGGER.debug("ISPyB robot load callback received start document.")
|
|
35
|
+
if doc.get("subplan_name") == CONST.PLAN.ROBOT_LOAD:
|
|
36
|
+
ISPYB_LOGGER.debug(f"ISPyB robot load callback received: {doc}")
|
|
37
|
+
self.run_uid = doc.get("uid")
|
|
38
|
+
assert isinstance(metadata := doc.get("metadata"), dict)
|
|
39
|
+
proposal, session = get_proposal_and_session_from_visit_string(
|
|
40
|
+
metadata["visit"]
|
|
41
|
+
)
|
|
42
|
+
self.action_id = self.expeye.start_load(
|
|
43
|
+
proposal,
|
|
44
|
+
session,
|
|
45
|
+
metadata["sample_id"],
|
|
46
|
+
metadata["sample_puck"],
|
|
47
|
+
metadata["sample_pin"],
|
|
48
|
+
)
|
|
49
|
+
return super().activity_gated_start(doc)
|
|
50
|
+
|
|
51
|
+
def activity_gated_descriptor(self, doc: EventDescriptor) -> EventDescriptor | None:
|
|
52
|
+
self.descriptors[doc["uid"]] = doc
|
|
53
|
+
return super().activity_gated_descriptor(doc)
|
|
54
|
+
|
|
55
|
+
def activity_gated_event(self, doc: Event) -> Event | None:
|
|
56
|
+
event_descriptor = self.descriptors.get(doc["descriptor"])
|
|
57
|
+
if (
|
|
58
|
+
event_descriptor
|
|
59
|
+
and event_descriptor.get("name") == CONST.DESCRIPTORS.ROBOT_LOAD
|
|
60
|
+
):
|
|
61
|
+
assert (
|
|
62
|
+
self.action_id is not None
|
|
63
|
+
), "ISPyB Robot load callback event called unexpectedly"
|
|
64
|
+
barcode = doc["data"]["robot-barcode"]
|
|
65
|
+
oav_snapshot = doc["data"]["oav_snapshot_last_saved_path"]
|
|
66
|
+
webcam_snapshot = doc["data"]["webcam-last_saved_path"]
|
|
67
|
+
# I03 uses webcam/oav snapshots in place of before/after snapshots
|
|
68
|
+
self.expeye.update_barcode_and_snapshots(
|
|
69
|
+
self.action_id, barcode, webcam_snapshot, oav_snapshot
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
return super().activity_gated_event(doc)
|
|
73
|
+
|
|
74
|
+
def activity_gated_stop(self, doc: RunStop) -> RunStop | None:
|
|
75
|
+
ISPYB_LOGGER.debug("ISPyB robot load callback received stop document.")
|
|
76
|
+
if doc.get("run_start") == self.run_uid:
|
|
77
|
+
assert (
|
|
78
|
+
self.action_id is not None
|
|
79
|
+
), "ISPyB Robot load callback stop called unexpectedly"
|
|
80
|
+
exit_status = (
|
|
81
|
+
doc.get("exit_status") or "Exit status not available in stop document!"
|
|
82
|
+
)
|
|
83
|
+
reason = doc.get("reason") or "OK"
|
|
84
|
+
self.expeye.end_load(self.action_id, exit_status, reason)
|
|
85
|
+
self.action_id = None
|
|
86
|
+
return super().activity_gated_stop(doc)
|
|
File without changes
|