mx-bluesky 1.5.6__py3-none-any.whl → 1.5.8__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/i02_1/parameters/__init__.py +0 -0
- mx_bluesky/beamlines/i02_1/parameters/gridscan.py +35 -0
- mx_bluesky/beamlines/i04/experiment_plans/i04_grid_detect_then_xray_centre_plan.py +6 -3
- mx_bluesky/beamlines/i04/redis_to_murko_forwarder.py +3 -1
- mx_bluesky/beamlines/i04/thawing_plan.py +15 -8
- mx_bluesky/beamlines/i24/jungfrau_commissioning/do_external_acquisition.py +44 -0
- mx_bluesky/beamlines/i24/jungfrau_commissioning/do_internal_acquisition.py +46 -0
- mx_bluesky/beamlines/i24/jungfrau_commissioning/plan_utils.py +73 -0
- mx_bluesky/common/device_setup_plans/robot_load_unload.py +2 -1
- mx_bluesky/common/experiment_plans/common_flyscan_xray_centre_plan.py +4 -3
- mx_bluesky/common/experiment_plans/inner_plans/do_fgs.py +7 -8
- mx_bluesky/common/external_interaction/callbacks/common/ispyb_callback_base.py +6 -6
- mx_bluesky/common/external_interaction/callbacks/common/zocalo_callback.py +30 -22
- mx_bluesky/common/external_interaction/callbacks/xray_centre/ispyb_callback.py +73 -15
- mx_bluesky/common/external_interaction/callbacks/xray_centre/ispyb_mapping.py +0 -20
- mx_bluesky/common/parameters/device_composites.py +2 -2
- mx_bluesky/common/parameters/gridscan.py +67 -49
- mx_bluesky/hyperion/device_setup_plans/smargon.py +13 -8
- mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +2 -2
- mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +10 -2
- mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py +37 -1
- mx_bluesky/hyperion/parameters/device_composites.py +2 -2
- mx_bluesky/hyperion/parameters/gridscan.py +3 -3
- {mx_bluesky-1.5.6.dist-info → mx_bluesky-1.5.8.dist-info}/METADATA +2 -2
- {mx_bluesky-1.5.6.dist-info → mx_bluesky-1.5.8.dist-info}/RECORD +30 -25
- {mx_bluesky-1.5.6.dist-info → mx_bluesky-1.5.8.dist-info}/WHEEL +0 -0
- {mx_bluesky-1.5.6.dist-info → mx_bluesky-1.5.8.dist-info}/entry_points.txt +0 -0
- {mx_bluesky-1.5.6.dist-info → mx_bluesky-1.5.8.dist-info}/licenses/LICENSE +0 -0
- {mx_bluesky-1.5.6.dist-info → mx_bluesky-1.5.8.dist-info}/top_level.txt +0 -0
mx_bluesky/_version.py
CHANGED
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '1.5.
|
|
32
|
-
__version_tuple__ = version_tuple = (1, 5,
|
|
31
|
+
__version__ = version = '1.5.8'
|
|
32
|
+
__version_tuple__ = version_tuple = (1, 5, 8)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
|
File without changes
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from dodal.devices.i02_1.fast_grid_scan import ZebraGridScanParamsTwoD
|
|
2
|
+
from scanspec.specs import Product
|
|
3
|
+
|
|
4
|
+
from mx_bluesky.common.parameters.components import SplitScan, WithOptionalEnergyChange
|
|
5
|
+
from mx_bluesky.common.parameters.gridscan import SpecifiedGrid
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SpecifiedTwoDGridScan(
|
|
9
|
+
SpecifiedGrid[ZebraGridScanParamsTwoD],
|
|
10
|
+
SplitScan,
|
|
11
|
+
WithOptionalEnergyChange,
|
|
12
|
+
):
|
|
13
|
+
"""Parameters representing a so-called 2D grid scan, which consists of doing a
|
|
14
|
+
gridscan in X and Y."""
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def scan_spec(self) -> Product[str]:
|
|
18
|
+
"""A fully specified ScanSpec object representing the grid, with x, y, z and
|
|
19
|
+
omega positions."""
|
|
20
|
+
return self.grid_1_spec
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def FGS_params(self) -> ZebraGridScanParamsTwoD:
|
|
24
|
+
return ZebraGridScanParamsTwoD(
|
|
25
|
+
x_steps=self.x_steps,
|
|
26
|
+
y_steps=self.y_steps,
|
|
27
|
+
x_step_size_mm=self.x_step_size_um / 1000,
|
|
28
|
+
y_step_size_mm=self.y_step_size_um / 1000,
|
|
29
|
+
x_start_mm=self.x_start_um / 1000,
|
|
30
|
+
y1_start_mm=self.y_start_um / 1000,
|
|
31
|
+
z1_start_mm=self.z_start_um / 1000,
|
|
32
|
+
set_stub_offsets=self._set_stub_offsets,
|
|
33
|
+
transmission_fraction=0.5,
|
|
34
|
+
dwell_time_ms=self.exposure_time_s,
|
|
35
|
+
)
|
|
@@ -13,7 +13,7 @@ from dodal.devices.common_dcm import BaseDCM
|
|
|
13
13
|
from dodal.devices.detector.detector_motion import DetectorMotion
|
|
14
14
|
from dodal.devices.eiger import EigerDetector
|
|
15
15
|
from dodal.devices.fast_grid_scan import (
|
|
16
|
-
|
|
16
|
+
ZebraFastGridScanThreeD,
|
|
17
17
|
set_fast_grid_scan_params,
|
|
18
18
|
)
|
|
19
19
|
from dodal.devices.flux import Flux
|
|
@@ -48,6 +48,7 @@ from mx_bluesky.common.external_interaction.callbacks.common.zocalo_callback imp
|
|
|
48
48
|
)
|
|
49
49
|
from mx_bluesky.common.external_interaction.callbacks.xray_centre.ispyb_callback import (
|
|
50
50
|
GridscanISPyBCallback,
|
|
51
|
+
generate_start_info_from_omega_map,
|
|
51
52
|
)
|
|
52
53
|
from mx_bluesky.common.external_interaction.callbacks.xray_centre.nexus_callback import (
|
|
53
54
|
GridscanNexusFileCallback,
|
|
@@ -80,7 +81,7 @@ def i04_grid_detect_then_xray_centre(
|
|
|
80
81
|
backlight: Backlight = inject("backlight"),
|
|
81
82
|
beamstop: Beamstop = inject("beamstop"),
|
|
82
83
|
dcm: BaseDCM = inject("dcm"),
|
|
83
|
-
zebra_fast_grid_scan:
|
|
84
|
+
zebra_fast_grid_scan: ZebraFastGridScanThreeD = inject("zebra_fast_grid_scan"),
|
|
84
85
|
flux: Flux = inject("flux"),
|
|
85
86
|
oav: OAV = inject("oav"),
|
|
86
87
|
pin_tip_detection: PinTipDetection = inject("pin_tip_detection"),
|
|
@@ -198,7 +199,9 @@ def create_gridscan_callbacks() -> tuple[
|
|
|
198
199
|
GridscanISPyBCallback(
|
|
199
200
|
param_type=GridCommon,
|
|
200
201
|
emit=ZocaloCallback(
|
|
201
|
-
PlanNameConstants.DO_FGS,
|
|
202
|
+
PlanNameConstants.DO_FGS,
|
|
203
|
+
EnvironmentConstants.ZOCALO_ENV,
|
|
204
|
+
generate_start_info_from_omega_map,
|
|
202
205
|
),
|
|
203
206
|
),
|
|
204
207
|
)
|
|
@@ -7,7 +7,7 @@ from typing import TypedDict
|
|
|
7
7
|
import numpy as np
|
|
8
8
|
import zmq
|
|
9
9
|
from dodal.devices.i04.constants import RedisConstants
|
|
10
|
-
from dodal.devices.i04.murko_results import
|
|
10
|
+
from dodal.devices.i04.murko_results import MurkoResult
|
|
11
11
|
from numpy.typing import NDArray
|
|
12
12
|
from PIL import Image
|
|
13
13
|
from redis import StrictRedis
|
|
@@ -16,6 +16,8 @@ from mx_bluesky.common.utils.log import LOGGER
|
|
|
16
16
|
|
|
17
17
|
MURKO_ADDRESS = "tcp://i04-murko-prod.diamond.ac.uk:8008"
|
|
18
18
|
|
|
19
|
+
FullMurkoResults = dict[str, list[MurkoResult]]
|
|
20
|
+
|
|
19
21
|
|
|
20
22
|
class MurkoRequest(TypedDict):
|
|
21
23
|
"""See https://github.com/MartinSavko/murko#usage for more information."""
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
from collections.abc import Callable
|
|
2
|
+
from functools import partial
|
|
2
3
|
|
|
3
4
|
import bluesky.plan_stubs as bps
|
|
4
5
|
import bluesky.preprocessors as bpp
|
|
5
|
-
from bluesky.preprocessors import run_decorator,
|
|
6
|
+
from bluesky.preprocessors import run_decorator, subs_decorator
|
|
6
7
|
from bluesky.utils import MsgGenerator
|
|
7
8
|
from dodal.common import inject
|
|
8
9
|
from dodal.devices.i04.constants import RedisConstants
|
|
@@ -42,7 +43,7 @@ def thaw_and_stream_to_redis(
|
|
|
42
43
|
robot: BartRobot = inject("robot"),
|
|
43
44
|
thawer: Thawer = inject("thawer"),
|
|
44
45
|
smargon: Smargon = inject("smargon"),
|
|
45
|
-
oav: OAV = inject("
|
|
46
|
+
oav: OAV = inject("oav_full_screen"),
|
|
46
47
|
oav_to_redis_forwarder: OAVToRedisForwarder = inject("oav_to_redis_forwarder"),
|
|
47
48
|
) -> MsgGenerator:
|
|
48
49
|
"""Turns on the thawer and rotates the sample by {rotation} degrees to thaw it, then
|
|
@@ -84,7 +85,7 @@ def thaw_and_murko_centre(
|
|
|
84
85
|
robot: BartRobot = inject("robot"),
|
|
85
86
|
thawer: Thawer = inject("thawer"),
|
|
86
87
|
smargon: Smargon = inject("smargon"),
|
|
87
|
-
oav: OAV = inject("
|
|
88
|
+
oav: OAV = inject("oav_full_screen"),
|
|
88
89
|
murko_results: MurkoResultsDevice = inject("murko_results"),
|
|
89
90
|
oav_to_redis_forwarder: OAVToRedisForwarder = inject("oav_to_redis_forwarder"),
|
|
90
91
|
) -> MsgGenerator:
|
|
@@ -109,14 +110,14 @@ def thaw_and_murko_centre(
|
|
|
109
110
|
defaults are always correct
|
|
110
111
|
"""
|
|
111
112
|
|
|
113
|
+
MURKO_RESULTS_GROUP = "get_results"
|
|
114
|
+
|
|
112
115
|
def centre_then_switch_forwarder_to_ROI() -> MsgGenerator:
|
|
113
116
|
yield from bps.complete(oav_to_redis_forwarder, wait=True)
|
|
114
117
|
|
|
115
|
-
yield from bps.trigger(murko_results, group="get_results")
|
|
116
|
-
|
|
117
118
|
yield from bps.mv(oav_to_redis_forwarder.selected_source, Source.ROI.value)
|
|
118
119
|
|
|
119
|
-
yield from bps.wait(
|
|
120
|
+
yield from bps.wait(MURKO_RESULTS_GROUP)
|
|
120
121
|
x_predict = yield from bps.rd(murko_results.x_mm)
|
|
121
122
|
y_predict = yield from bps.rd(murko_results.y_mm)
|
|
122
123
|
z_predict = yield from bps.rd(murko_results.z_mm)
|
|
@@ -127,7 +128,13 @@ def thaw_and_murko_centre(
|
|
|
127
128
|
|
|
128
129
|
yield from bps.kickoff(oav_to_redis_forwarder, wait=True)
|
|
129
130
|
|
|
130
|
-
yield from
|
|
131
|
+
sample_id = yield from bps.rd(robot.sample_id)
|
|
132
|
+
yield from bps.mv(murko_results.sample_id, str(sample_id))
|
|
133
|
+
|
|
134
|
+
yield from bps.stage(murko_results, wait=True)
|
|
135
|
+
yield from bps.trigger(murko_results, group=MURKO_RESULTS_GROUP)
|
|
136
|
+
|
|
137
|
+
yield from bpp.contingency_wrapper(
|
|
131
138
|
_thaw_and_stream_to_redis(
|
|
132
139
|
time_to_thaw,
|
|
133
140
|
rotation,
|
|
@@ -138,7 +145,7 @@ def thaw_and_murko_centre(
|
|
|
138
145
|
oav_to_redis_forwarder,
|
|
139
146
|
centre_then_switch_forwarder_to_ROI,
|
|
140
147
|
),
|
|
141
|
-
|
|
148
|
+
final_plan=partial(bps.unstage, murko_results, wait=True),
|
|
142
149
|
)
|
|
143
150
|
|
|
144
151
|
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from bluesky.utils import MsgGenerator
|
|
2
|
+
from dodal.common import inject
|
|
3
|
+
from dodal.devices.i24.commissioning_jungfrau import CommissioningJungfrau
|
|
4
|
+
from ophyd_async.core import (
|
|
5
|
+
WatchableAsyncStatus,
|
|
6
|
+
)
|
|
7
|
+
from ophyd_async.fastcs.jungfrau import (
|
|
8
|
+
create_jungfrau_external_triggering_info,
|
|
9
|
+
)
|
|
10
|
+
from pydantic import PositiveInt
|
|
11
|
+
|
|
12
|
+
from mx_bluesky.beamlines.i24.jungfrau_commissioning.plan_utils import (
|
|
13
|
+
fly_jungfrau,
|
|
14
|
+
override_file_path,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def do_external_acquisition(
|
|
19
|
+
exp_time_s: float,
|
|
20
|
+
total_triggers: PositiveInt = 1,
|
|
21
|
+
output_file_name: str | None = None,
|
|
22
|
+
wait: bool = False,
|
|
23
|
+
jungfrau: CommissioningJungfrau = inject("commissioning_jungfrau"),
|
|
24
|
+
) -> MsgGenerator[WatchableAsyncStatus]:
|
|
25
|
+
"""
|
|
26
|
+
Kickoff external triggering on the Jungfrau, and optionally wait for completion.
|
|
27
|
+
|
|
28
|
+
Must be used within an open Bluesky run.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
exp_time_s: Length of detector exposure for each frame.
|
|
32
|
+
total_triggers: Number of external triggers recieved before acquisition is marked as complete.
|
|
33
|
+
jungfrau: Jungfrau device
|
|
34
|
+
output_file_name: Absolute path of the detector file output, including file name. If None, then use the PathProvider
|
|
35
|
+
set during jungfrau device instantiation
|
|
36
|
+
wait: Optionally block until data collection is complete.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
if output_file_name:
|
|
40
|
+
override_file_path(jungfrau, output_file_name)
|
|
41
|
+
|
|
42
|
+
trigger_info = create_jungfrau_external_triggering_info(total_triggers, exp_time_s)
|
|
43
|
+
status = yield from fly_jungfrau(jungfrau, trigger_info, wait)
|
|
44
|
+
return status
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from bluesky.utils import MsgGenerator
|
|
2
|
+
from dodal.beamlines.i24 import CommissioningJungfrau
|
|
3
|
+
from dodal.common import inject
|
|
4
|
+
from ophyd_async.core import (
|
|
5
|
+
WatchableAsyncStatus,
|
|
6
|
+
)
|
|
7
|
+
from ophyd_async.fastcs.jungfrau import (
|
|
8
|
+
create_jungfrau_internal_triggering_info,
|
|
9
|
+
)
|
|
10
|
+
from pydantic import PositiveInt
|
|
11
|
+
|
|
12
|
+
from mx_bluesky.beamlines.i24.jungfrau_commissioning.plan_utils import (
|
|
13
|
+
fly_jungfrau,
|
|
14
|
+
override_file_path,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def do_internal_acquisition(
|
|
19
|
+
exp_time_s: float,
|
|
20
|
+
total_frames: PositiveInt = 1,
|
|
21
|
+
jungfrau: CommissioningJungfrau = inject("jungfrau"),
|
|
22
|
+
path_of_output_file: str | None = None,
|
|
23
|
+
wait: bool = False,
|
|
24
|
+
) -> MsgGenerator[WatchableAsyncStatus]:
|
|
25
|
+
"""
|
|
26
|
+
Kickoff internal triggering on the Jungfrau, and optionally wait for completion. Frames
|
|
27
|
+
per trigger will trigger as rapidly as possible according to the Jungfrau deadtime.
|
|
28
|
+
|
|
29
|
+
Must be used within an open Bluesky run.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
exp_time_s: Length of detector exposure for each frame.
|
|
33
|
+
total_frames: Number of frames taken after being internally triggered.
|
|
34
|
+
period_between_frames_s: Time between each detector frame, including deadtime. Not needed if frames_per_triggers is 1.
|
|
35
|
+
jungfrau: Jungfrau device
|
|
36
|
+
path_of_output_file: Absolute path of the detector file output, including file name. If None, then use the PathProvider
|
|
37
|
+
set during jungfrau device instantiation
|
|
38
|
+
wait: Optionally block until data collection is complete.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
if path_of_output_file:
|
|
42
|
+
override_file_path(jungfrau, path_of_output_file)
|
|
43
|
+
|
|
44
|
+
trigger_info = create_jungfrau_internal_triggering_info(total_frames, exp_time_s)
|
|
45
|
+
status = yield from fly_jungfrau(jungfrau, trigger_info, wait)
|
|
46
|
+
return status
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from pathlib import PurePath
|
|
2
|
+
from typing import cast
|
|
3
|
+
|
|
4
|
+
import bluesky.plan_stubs as bps
|
|
5
|
+
import bluesky.preprocessors as bpp
|
|
6
|
+
from bluesky.utils import MsgGenerator
|
|
7
|
+
from dodal.common.watcher_utils import log_on_percentage_complete
|
|
8
|
+
from dodal.devices.i24.commissioning_jungfrau import CommissioningJungfrau
|
|
9
|
+
from ophyd_async.core import (
|
|
10
|
+
AutoIncrementingPathProvider,
|
|
11
|
+
StaticFilenameProvider,
|
|
12
|
+
TriggerInfo,
|
|
13
|
+
WatchableAsyncStatus,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
from mx_bluesky.common.utils.log import LOGGER
|
|
17
|
+
|
|
18
|
+
JF_COMPLETE_GROUP = "JF complete"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def fly_jungfrau(
|
|
22
|
+
jungfrau: CommissioningJungfrau, trigger_info: TriggerInfo, wait: bool = False
|
|
23
|
+
) -> MsgGenerator[WatchableAsyncStatus]:
|
|
24
|
+
"""Stage, prepare, and kickoff Jungfrau with a configured TriggerInfo. Optionally wait
|
|
25
|
+
for completion.
|
|
26
|
+
|
|
27
|
+
Note that this plan doesn't include unstaging of the Jungfrau, and a run must be open
|
|
28
|
+
before this plan is called.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
jungfrau: Jungfrau device.
|
|
32
|
+
trigger_info: TriggerInfo which should be acquired using jungfrau util functions create_jungfrau_internal_triggering_info
|
|
33
|
+
or create_jungfrau_external_triggering_info.
|
|
34
|
+
wait: Optionally block until data collection is complete.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
@bpp.contingency_decorator(
|
|
38
|
+
except_plan=lambda _: (yield from bps.unstage(jungfrau, wait=True))
|
|
39
|
+
)
|
|
40
|
+
def _fly_with_unstage_contingency():
|
|
41
|
+
yield from bps.stage(jungfrau)
|
|
42
|
+
LOGGER.info("Setting up detector...")
|
|
43
|
+
yield from bps.prepare(jungfrau, trigger_info, wait=True)
|
|
44
|
+
LOGGER.info("Detector prepared. Starting acquisition")
|
|
45
|
+
yield from bps.kickoff(jungfrau, wait=True)
|
|
46
|
+
LOGGER.info("Waiting for acquisition to complete...")
|
|
47
|
+
status = yield from bps.complete(jungfrau, group=JF_COMPLETE_GROUP)
|
|
48
|
+
|
|
49
|
+
# StandardDetector.complete converts regular status to watchable status,
|
|
50
|
+
# but bluesky plan stubs can't see this currently
|
|
51
|
+
status = cast(WatchableAsyncStatus, status)
|
|
52
|
+
log_on_percentage_complete(
|
|
53
|
+
status, "Jungfrau data collection triggers recieved", 10
|
|
54
|
+
)
|
|
55
|
+
if wait:
|
|
56
|
+
yield from bps.wait(JF_COMPLETE_GROUP)
|
|
57
|
+
return status
|
|
58
|
+
|
|
59
|
+
return (yield from _fly_with_unstage_contingency())
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def override_file_path(jungfrau: CommissioningJungfrau, path_of_output_file: str):
|
|
63
|
+
"""While we should generally use device instantiation to set the path,
|
|
64
|
+
during commissioning, it is useful to be able to explicitly set the filename
|
|
65
|
+
and path.
|
|
66
|
+
|
|
67
|
+
This function must be called before the Jungfrau is prepared.
|
|
68
|
+
"""
|
|
69
|
+
_file_path = PurePath(path_of_output_file)
|
|
70
|
+
_new_filename_provider = StaticFilenameProvider(_file_path.name)
|
|
71
|
+
jungfrau._writer._path_info = AutoIncrementingPathProvider( # noqa: SLF001
|
|
72
|
+
_new_filename_provider, _file_path.parent
|
|
73
|
+
)
|
|
@@ -12,6 +12,8 @@ from dodal.plan_stubs.motor_utils import MoveTooLarge, home_and_reset_wrapper
|
|
|
12
12
|
from mx_bluesky.common.utils.log import LOGGER
|
|
13
13
|
from mx_bluesky.hyperion.parameters.constants import CONST
|
|
14
14
|
|
|
15
|
+
SLEEP_PER_CHECK = 0.1
|
|
16
|
+
|
|
15
17
|
|
|
16
18
|
def wait_for_smargon_not_disabled(smargon: Smargon, timeout=60):
|
|
17
19
|
"""Waits for the smargon disabled flag to go low. The robot hardware is responsible
|
|
@@ -19,7 +21,6 @@ def wait_for_smargon_not_disabled(smargon: Smargon, timeout=60):
|
|
|
19
21
|
connection between the robot and the smargon.
|
|
20
22
|
"""
|
|
21
23
|
LOGGER.info("Waiting for smargon enabled")
|
|
22
|
-
SLEEP_PER_CHECK = 0.1
|
|
23
24
|
times_to_check = int(timeout / SLEEP_PER_CHECK)
|
|
24
25
|
for _ in range(times_to_check):
|
|
25
26
|
smargon_disabled = yield from bps.rd(smargon.disabled)
|
|
@@ -12,6 +12,7 @@ from bluesky.utils import MsgGenerator
|
|
|
12
12
|
from dodal.common.beamlines.commissioning_mode import read_commissioning_mode
|
|
13
13
|
from dodal.devices.fast_grid_scan import (
|
|
14
14
|
FastGridScanCommon,
|
|
15
|
+
FastGridScanThreeD,
|
|
15
16
|
)
|
|
16
17
|
from dodal.devices.zocalo import ZocaloResults
|
|
17
18
|
from dodal.devices.zocalo.zocalo_results import (
|
|
@@ -283,13 +284,13 @@ def run_gridscan(
|
|
|
283
284
|
fgs_composite.eiger,
|
|
284
285
|
fgs_composite.synchrotron,
|
|
285
286
|
[parameters.scan_points_first_grid, parameters.scan_points_second_grid],
|
|
286
|
-
parameters.scan_indices,
|
|
287
287
|
plan_during_collection=beamline_specific.read_during_collection_plan,
|
|
288
288
|
)
|
|
289
289
|
|
|
290
|
-
# GDA's gridscans requires Z steps to be at 0, so make sure we leave this device
|
|
290
|
+
# GDA's 3D gridscans requires Z steps to be at 0, so make sure we leave this device
|
|
291
291
|
# in a GDA-happy state.
|
|
292
|
-
|
|
292
|
+
if isinstance(beamline_specific.fgs_motors, FastGridScanThreeD):
|
|
293
|
+
yield from bps.abs_set(beamline_specific.fgs_motors.z_steps, 0, wait=False)
|
|
293
294
|
|
|
294
295
|
|
|
295
296
|
def wait_for_gridscan_valid(fgs_motors: FastGridScanCommon, timeout=0.5):
|
|
@@ -17,6 +17,9 @@ from scanspec.core import AxesPoints, Axis
|
|
|
17
17
|
from mx_bluesky.common.experiment_plans.inner_plans.read_hardware import (
|
|
18
18
|
read_hardware_for_zocalo,
|
|
19
19
|
)
|
|
20
|
+
from mx_bluesky.common.external_interaction.callbacks.xray_centre.ispyb_callback import (
|
|
21
|
+
GridscanPlane,
|
|
22
|
+
)
|
|
20
23
|
from mx_bluesky.common.parameters.constants import (
|
|
21
24
|
PlanNameConstants,
|
|
22
25
|
)
|
|
@@ -66,7 +69,6 @@ def kickoff_and_complete_gridscan(
|
|
|
66
69
|
detector: EigerDetector, # Once Eiger inherits from StandardDetector, use that type instead
|
|
67
70
|
synchrotron: Synchrotron,
|
|
68
71
|
scan_points: list[AxesPoints[Axis]],
|
|
69
|
-
scan_start_indices: list[int],
|
|
70
72
|
plan_during_collection: Callable[[], MsgGenerator] | None = None,
|
|
71
73
|
):
|
|
72
74
|
"""Triggers a grid scan motion program and waits for completion, accounting for synchrotron topup.
|
|
@@ -80,15 +82,10 @@ def kickoff_and_complete_gridscan(
|
|
|
80
82
|
synchrotron (Synchrotron): Synchrotron device
|
|
81
83
|
scan_points (list[AxesPoints[Axis]]): Each element in the list contains all the grid points for that grid scan.
|
|
82
84
|
Two elements in this list indicates that two grid scans will be done, eg for Hyperion's 3D grid scans.
|
|
83
|
-
scan_start_indices (list[int]): Contains the first index of each grid scan
|
|
84
85
|
plan_during_collection (Optional, MsgGenerator): Generic plan called in between kickoff and completion,
|
|
85
86
|
eg waiting on zocalo.
|
|
86
87
|
"""
|
|
87
88
|
|
|
88
|
-
assert len(scan_points) == len(scan_start_indices), (
|
|
89
|
-
"scan_points and scan_start_indices must be lists of the same length!"
|
|
90
|
-
)
|
|
91
|
-
|
|
92
89
|
plan_name = PlanNameConstants.DO_FGS
|
|
93
90
|
|
|
94
91
|
@TRACER.start_as_current_span(plan_name)
|
|
@@ -96,8 +93,10 @@ def kickoff_and_complete_gridscan(
|
|
|
96
93
|
@bpp.run_decorator(
|
|
97
94
|
md={
|
|
98
95
|
"subplan_name": plan_name,
|
|
99
|
-
"
|
|
100
|
-
|
|
96
|
+
"omega_to_scan_spec": {
|
|
97
|
+
GridscanPlane.OMEGA_XY: scan_points[0],
|
|
98
|
+
GridscanPlane.OMEGA_XZ: scan_points[1],
|
|
99
|
+
},
|
|
101
100
|
}
|
|
102
101
|
)
|
|
103
102
|
@bpp.contingency_decorator(
|
|
@@ -86,11 +86,11 @@ class BaseISPyBCallback(PlanReactiveCallback):
|
|
|
86
86
|
|
|
87
87
|
def activity_gated_start(self, doc: RunStart):
|
|
88
88
|
self._oav_snapshot_event_idx = 0
|
|
89
|
-
return self.
|
|
89
|
+
return self.tag_doc(doc)
|
|
90
90
|
|
|
91
91
|
def activity_gated_descriptor(self, doc: EventDescriptor):
|
|
92
92
|
self.descriptors[doc["uid"]] = doc
|
|
93
|
-
return self.
|
|
93
|
+
return self.tag_doc(doc)
|
|
94
94
|
|
|
95
95
|
def activity_gated_event(self, doc: Event) -> Event:
|
|
96
96
|
"""Subclasses should extend this to add a call to set_dcig_tag from
|
|
@@ -112,10 +112,10 @@ class BaseISPyBCallback(PlanReactiveCallback):
|
|
|
112
112
|
case DocDescriptorNames.HARDWARE_READ_DURING:
|
|
113
113
|
scan_data_infos = self._handle_ispyb_transmission_flux_read(doc)
|
|
114
114
|
case _:
|
|
115
|
-
return self.
|
|
115
|
+
return self.tag_doc(doc)
|
|
116
116
|
self.ispyb_ids = self.ispyb.update_deposition(self.ispyb_ids, scan_data_infos)
|
|
117
117
|
ISPYB_ZOCALO_CALLBACK_LOGGER.info(f"Received ISPYB IDs: {self.ispyb_ids}")
|
|
118
|
-
return self.
|
|
118
|
+
return self.tag_doc(doc)
|
|
119
119
|
|
|
120
120
|
def _handle_ispyb_hardware_read(self, doc) -> Sequence[ScanDataInfo]:
|
|
121
121
|
assert self.params, "Event handled before activity_gated_start received params"
|
|
@@ -203,7 +203,7 @@ class BaseISPyBCallback(PlanReactiveCallback):
|
|
|
203
203
|
ISPYB_ZOCALO_CALLBACK_LOGGER.warning(
|
|
204
204
|
f"Failed to finalise ISPyB deposition on stop document: {format_doc_for_log(doc)} with exception: {e}"
|
|
205
205
|
)
|
|
206
|
-
return self.
|
|
206
|
+
return self.tag_doc(doc)
|
|
207
207
|
|
|
208
208
|
def _append_to_comment(self, id: int, comment: str) -> None:
|
|
209
209
|
assert self.ispyb is not None
|
|
@@ -218,7 +218,7 @@ class BaseISPyBCallback(PlanReactiveCallback):
|
|
|
218
218
|
for id in self.ispyb_ids.data_collection_ids:
|
|
219
219
|
self._append_to_comment(id, comment)
|
|
220
220
|
|
|
221
|
-
def
|
|
221
|
+
def tag_doc(self, doc: D) -> D:
|
|
222
222
|
assert isinstance(doc, dict)
|
|
223
223
|
if self.ispyb_ids:
|
|
224
224
|
doc["ispyb_dcids"] = self.ispyb_ids.data_collection_ids
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from collections.abc import Callable, Generator
|
|
3
4
|
from typing import TYPE_CHECKING
|
|
4
5
|
|
|
5
6
|
from bluesky.callbacks import CallbackBase
|
|
@@ -10,12 +11,14 @@ from mx_bluesky.common.parameters.constants import (
|
|
|
10
11
|
)
|
|
11
12
|
from mx_bluesky.common.utils.exceptions import ISPyBDepositionNotMade
|
|
12
13
|
from mx_bluesky.common.utils.log import ISPYB_ZOCALO_CALLBACK_LOGGER
|
|
13
|
-
from mx_bluesky.common.utils.utils import number_of_frames_from_scan_spec
|
|
14
14
|
|
|
15
15
|
if TYPE_CHECKING:
|
|
16
16
|
from event_model.documents import Event, EventDescriptor, RunStart, RunStop
|
|
17
17
|
|
|
18
18
|
|
|
19
|
+
ZocaloInfoGenerator = Generator[list[ZocaloStartInfo], dict, None]
|
|
20
|
+
|
|
21
|
+
|
|
19
22
|
class ZocaloCallback(CallbackBase):
|
|
20
23
|
"""Callback class to handle the triggering of Zocalo processing.
|
|
21
24
|
Will start listening for collections when {triggering_plan} has been started.
|
|
@@ -26,40 +29,45 @@ class ZocaloCallback(CallbackBase):
|
|
|
26
29
|
|
|
27
30
|
Shouldn't be subscribed directly to the RunEngine, instead should be passed to the
|
|
28
31
|
`emit` argument of an ISPyB callback which appends DCIDs to the relevant start doc.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
triggering_plan: Name of the bluesky sub-plan inside of which we generate information
|
|
35
|
+
to be submitted to zocalo; this is identified by the 'subplan_name' entry in the
|
|
36
|
+
run start metadata.
|
|
37
|
+
zocalo_environment: Name of the zocalo environment we use to connect to zocalo
|
|
38
|
+
start_info_generator_factory: A factory method which returns a Generator,
|
|
39
|
+
the generator is sent the ZOCALO_HW_READ event document and in return yields
|
|
40
|
+
one or more ZocaloStartInfo which will each be submitted to zocalo as a job.
|
|
29
41
|
"""
|
|
30
42
|
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
triggering_plan: str,
|
|
46
|
+
zocalo_environment: str,
|
|
47
|
+
start_info_generator_factory: Callable[[], ZocaloInfoGenerator],
|
|
48
|
+
):
|
|
49
|
+
super().__init__()
|
|
50
|
+
self._info_generator_factory = start_info_generator_factory
|
|
51
|
+
self.triggering_plan = triggering_plan
|
|
52
|
+
self.zocalo_interactor = ZocaloTrigger(zocalo_environment)
|
|
53
|
+
self._reset_state()
|
|
54
|
+
|
|
31
55
|
def _reset_state(self):
|
|
32
56
|
self.run_uid: str | None = None
|
|
33
57
|
self.zocalo_info: list[ZocaloStartInfo] = []
|
|
34
58
|
self._started_zocalo_collections: list[ZocaloStartInfo] = []
|
|
35
59
|
self.descriptors: dict[str, EventDescriptor] = {}
|
|
36
|
-
self.
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
super().__init__()
|
|
40
|
-
self.triggering_plan = triggering_plan
|
|
41
|
-
self.zocalo_interactor = ZocaloTrigger(zocalo_environment)
|
|
42
|
-
self._reset_state()
|
|
60
|
+
self._info_generator = self._info_generator_factory()
|
|
61
|
+
# Prime the generator
|
|
62
|
+
next(self._info_generator)
|
|
43
63
|
|
|
44
64
|
def start(self, doc: RunStart):
|
|
45
65
|
ISPYB_ZOCALO_CALLBACK_LOGGER.info("Zocalo handler received start document.")
|
|
46
66
|
if self.triggering_plan and doc.get("subplan_name") == self.triggering_plan:
|
|
47
67
|
self.run_uid = doc.get("uid")
|
|
48
68
|
if self.run_uid:
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
and isinstance(ispyb_ids := doc.get("ispyb_dcids"), tuple)
|
|
52
|
-
and len(ispyb_ids) > 0
|
|
53
|
-
):
|
|
54
|
-
ISPYB_ZOCALO_CALLBACK_LOGGER.info(f"Zocalo triggering for {ispyb_ids}")
|
|
55
|
-
ids_and_shape = list(zip(ispyb_ids, scan_points, strict=False))
|
|
56
|
-
for idx, id_and_shape in enumerate(ids_and_shape):
|
|
57
|
-
id, shape = id_and_shape
|
|
58
|
-
num_frames = number_of_frames_from_scan_spec(shape)
|
|
59
|
-
self.zocalo_info.append(
|
|
60
|
-
ZocaloStartInfo(id, None, self.start_frame, num_frames, idx)
|
|
61
|
-
)
|
|
62
|
-
self.start_frame += num_frames
|
|
69
|
+
zocalo_infos = self._info_generator.send(doc) # type: ignore
|
|
70
|
+
self.zocalo_info.extend(zocalo_infos)
|
|
63
71
|
|
|
64
72
|
def descriptor(self, doc: EventDescriptor):
|
|
65
73
|
self.descriptors[doc["uid"]] = doc
|