mx-bluesky 1.5.15__py3-none-any.whl → 1.5.16__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/Getting started.ipynb +1 -0
- mx_bluesky/_version.py +2 -2
- mx_bluesky/beamlines/i04/callbacks/murko_callback.py +18 -0
- mx_bluesky/beamlines/i04/experiment_plans/i04_grid_detect_then_xray_centre_plan.py +3 -2
- mx_bluesky/beamlines/i04/thawing_plan.py +1 -0
- mx_bluesky/beamlines/i24/jungfrau_commissioning/__init__.py +13 -0
- mx_bluesky/beamlines/i24/jungfrau_commissioning/callbacks/__init__.py +0 -0
- mx_bluesky/beamlines/i24/jungfrau_commissioning/callbacks/metadata_writer.py +86 -0
- mx_bluesky/beamlines/i24/jungfrau_commissioning/composites.py +35 -0
- mx_bluesky/beamlines/i24/jungfrau_commissioning/experiment_plans/do_darks.py +18 -19
- mx_bluesky/beamlines/i24/jungfrau_commissioning/experiment_plans/rotation_scan_plan.py +292 -0
- mx_bluesky/beamlines/i24/jungfrau_commissioning/plan_stubs/do_external_acquisition.py +3 -8
- mx_bluesky/beamlines/i24/jungfrau_commissioning/plan_stubs/do_internal_acquisition.py +4 -5
- mx_bluesky/beamlines/i24/jungfrau_commissioning/plan_stubs/plan_utils.py +14 -18
- mx_bluesky/beamlines/i24/parameters/__init__.py +0 -0
- mx_bluesky/beamlines/i24/parameters/constants.py +9 -0
- mx_bluesky/beamlines/i24/serial/extruder/i24ssx_extruder_collect_py3v2.py +1 -1
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_moveonclick.py +2 -2
- mx_bluesky/common/device_setup_plans/robot_load_unload.py +2 -24
- mx_bluesky/common/device_setup_plans/setup_zebra_and_shutter.py +5 -2
- mx_bluesky/common/experiment_plans/common_flyscan_xray_centre_plan.py +1 -1
- mx_bluesky/common/experiment_plans/inner_plans/read_hardware.py +1 -0
- mx_bluesky/common/experiment_plans/rotation/__init__.py +0 -0
- mx_bluesky/common/experiment_plans/rotation/rotation_utils.py +127 -0
- mx_bluesky/common/external_interaction/callbacks/common/ispyb_callback_base.py +13 -2
- mx_bluesky/common/external_interaction/callbacks/common/ispyb_mapping.py +0 -2
- mx_bluesky/common/external_interaction/ispyb/data_model.py +1 -1
- mx_bluesky/common/external_interaction/ispyb/exp_eye_store.py +1 -1
- mx_bluesky/common/parameters/components.py +17 -7
- mx_bluesky/common/parameters/constants.py +6 -0
- mx_bluesky/{hyperion → common}/parameters/rotation.py +10 -8
- mx_bluesky/common/preprocessors/preprocessors.py +98 -36
- mx_bluesky/hyperion/__main__.py +55 -22
- mx_bluesky/hyperion/baton_handler.py +24 -64
- mx_bluesky/hyperion/blueapi_config.yaml +17 -0
- mx_bluesky/hyperion/blueapi_dev_config.yaml +16 -0
- mx_bluesky/hyperion/blueapi_plans/__init__.py +96 -0
- mx_bluesky/hyperion/device_setup_plans/dcm_pitch_roll_mirror_adjuster.py +8 -6
- mx_bluesky/hyperion/device_setup_plans/setup_panda.py +1 -1
- mx_bluesky/hyperion/experiment_plans/experiment_registry.py +3 -1
- mx_bluesky/hyperion/experiment_plans/hyperion_flyscan_xray_centre_plan.py +1 -0
- mx_bluesky/hyperion/experiment_plans/hyperion_grid_detect_then_xray_centre_plan.py +2 -2
- mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py +3 -1
- mx_bluesky/hyperion/experiment_plans/pin_centre_then_xray_centre_plan.py +17 -6
- mx_bluesky/hyperion/experiment_plans/robot_load_and_change_energy.py +0 -3
- mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +12 -126
- mx_bluesky/hyperion/experiment_plans/set_energy_plan.py +2 -2
- mx_bluesky/hyperion/external_interaction/agamemnon.py +3 -8
- mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +121 -47
- mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py +3 -1
- mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_mapping.py +3 -1
- mx_bluesky/hyperion/external_interaction/callbacks/rotation/nexus_callback.py +6 -3
- mx_bluesky/hyperion/external_interaction/callbacks/stomp/__init__.py +0 -0
- mx_bluesky/hyperion/external_interaction/callbacks/stomp/dispatcher.py +33 -0
- mx_bluesky/hyperion/in_process_runner.py +132 -0
- mx_bluesky/hyperion/parameters/cli.py +43 -4
- mx_bluesky/hyperion/parameters/components.py +13 -0
- mx_bluesky/hyperion/parameters/constants.py +2 -9
- mx_bluesky/hyperion/parameters/load_centre_collect.py +3 -1
- mx_bluesky/hyperion/plan_runner.py +45 -66
- mx_bluesky/hyperion/plan_runner_api.py +3 -4
- mx_bluesky/hyperion/supervisor/__init__.py +3 -0
- mx_bluesky/hyperion/supervisor/_supervisor.py +116 -0
- mx_bluesky/hyperion/supervisor/client_config.yaml +6 -0
- mx_bluesky/hyperion/supervisor/supervisor_config.yaml +10 -0
- mx_bluesky/hyperion/supervisor/supervisor_dev_config.yaml +9 -0
- {mx_bluesky-1.5.15.dist-info → mx_bluesky-1.5.16.dist-info}/METADATA +3 -31
- {mx_bluesky-1.5.15.dist-info → mx_bluesky-1.5.16.dist-info}/RECORD +72 -52
- {mx_bluesky-1.5.15.dist-info → mx_bluesky-1.5.16.dist-info}/WHEEL +0 -0
- {mx_bluesky-1.5.15.dist-info → mx_bluesky-1.5.16.dist-info}/entry_points.txt +0 -0
- {mx_bluesky-1.5.15.dist-info → mx_bluesky-1.5.16.dist-info}/licenses/LICENSE +0 -0
- {mx_bluesky-1.5.15.dist-info → mx_bluesky-1.5.16.dist-info}/top_level.txt +0 -0
mx_bluesky/Getting started.ipynb
CHANGED
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.16'
|
|
32
|
+
__version_tuple__ = version_tuple = (1, 5, 16)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
|
@@ -7,6 +7,7 @@ from bluesky.callbacks import CallbackBase
|
|
|
7
7
|
from dodal.log import LOGGER
|
|
8
8
|
from event_model.documents import Event, RunStart, RunStop
|
|
9
9
|
from redis import StrictRedis
|
|
10
|
+
from redis.exceptions import ConnectionError
|
|
10
11
|
|
|
11
12
|
FORWARDING_COMPLETE_MESSAGE = "image_forwarding_complete"
|
|
12
13
|
|
|
@@ -57,7 +58,20 @@ class MurkoCallback(CallbackBase):
|
|
|
57
58
|
self.last_uuid = None
|
|
58
59
|
self.previous_omegas: list[OmegaReading] = []
|
|
59
60
|
|
|
61
|
+
def _check_redis_connection(self):
|
|
62
|
+
try:
|
|
63
|
+
self.redis_client.ping()
|
|
64
|
+
return True
|
|
65
|
+
except ConnectionError:
|
|
66
|
+
LOGGER.warning(
|
|
67
|
+
f"Failed to connect to redis: {self.redis_client}. Murko callback will not run"
|
|
68
|
+
)
|
|
69
|
+
return False
|
|
70
|
+
|
|
60
71
|
def start(self, doc: RunStart) -> RunStart | None:
|
|
72
|
+
self.redis_connected = self._check_redis_connection()
|
|
73
|
+
if not self.redis_connected:
|
|
74
|
+
return doc
|
|
61
75
|
self.murko_metadata: dict = {"sample_id": doc.get("sample_id")}
|
|
62
76
|
self.last_uuid = None
|
|
63
77
|
self.previous_omegas = []
|
|
@@ -67,6 +81,8 @@ class MurkoCallback(CallbackBase):
|
|
|
67
81
|
return doc
|
|
68
82
|
|
|
69
83
|
def event(self, doc: Event) -> Event:
|
|
84
|
+
if not self.redis_connected:
|
|
85
|
+
return doc
|
|
70
86
|
data = doc["data"]
|
|
71
87
|
for prefix in ("oav", "oav_full_screen"):
|
|
72
88
|
if f"{prefix}-beam_centre_j" in data:
|
|
@@ -114,6 +130,8 @@ class MurkoCallback(CallbackBase):
|
|
|
114
130
|
self.redis_client.publish("murko", json.dumps(metadata))
|
|
115
131
|
|
|
116
132
|
def stop(self, doc: RunStop) -> RunStop | None:
|
|
133
|
+
if not self.redis_connected:
|
|
134
|
+
return doc
|
|
117
135
|
LOGGER.info(f"Finished streaming {self.murko_metadata['sample_id']} to murko")
|
|
118
136
|
LOGGER.info(
|
|
119
137
|
f"Publishing forwarding complete message: {FORWARDING_COMPLETE_MESSAGE}"
|
|
@@ -78,7 +78,7 @@ from mx_bluesky.common.parameters.gridscan import (
|
|
|
78
78
|
SpecifiedThreeDGridScan,
|
|
79
79
|
)
|
|
80
80
|
from mx_bluesky.common.preprocessors.preprocessors import (
|
|
81
|
-
|
|
81
|
+
set_transmission_and_trigger_xbpm_feedback_before_collection_decorator,
|
|
82
82
|
)
|
|
83
83
|
from mx_bluesky.common.utils.exceptions import CrystalNotFoundError
|
|
84
84
|
from mx_bluesky.common.utils.log import LOGGER
|
|
@@ -203,7 +203,7 @@ def i04_default_grid_detect_and_xray_centre(
|
|
|
203
203
|
|
|
204
204
|
@bpp.subs_decorator(callbacks)
|
|
205
205
|
@verify_undulator_gap_before_run_decorator(composite)
|
|
206
|
-
@
|
|
206
|
+
@set_transmission_and_trigger_xbpm_feedback_before_collection_decorator(
|
|
207
207
|
composite,
|
|
208
208
|
grid_common_params.transmission_frac,
|
|
209
209
|
PlanNameConstants.GRIDSCAN_OUTER,
|
|
@@ -291,6 +291,7 @@ def construct_i04_specific_features(
|
|
|
291
291
|
xrc_composite.eiger.bit_depth,
|
|
292
292
|
xrc_composite.beamsize,
|
|
293
293
|
xrc_composite.eiger.cam.roi_mode,
|
|
294
|
+
xrc_composite.eiger.ispyb_detector_id,
|
|
294
295
|
]
|
|
295
296
|
|
|
296
297
|
tidy_plan = partial(
|
|
@@ -88,6 +88,7 @@ def thaw_and_murko_centre(
|
|
|
88
88
|
initial_zoom_level = yield from bps.rd(oav_fs.zoom_controller.level)
|
|
89
89
|
initial_velocity = yield from bps.rd(smargon.omega.velocity)
|
|
90
90
|
new_velocity = abs(rotation / time_to_thaw) * 2.0
|
|
91
|
+
|
|
91
92
|
murko_callback = MurkoCallback(
|
|
92
93
|
RedisConstants.REDIS_HOST,
|
|
93
94
|
RedisConstants.REDIS_PASSWORD,
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from mx_bluesky.beamlines.i24.jungfrau_commissioning.experiment_plans.do_darks import (
|
|
2
|
+
do_non_pedestal_darks,
|
|
3
|
+
do_pedestal_darks,
|
|
4
|
+
)
|
|
5
|
+
from mx_bluesky.beamlines.i24.jungfrau_commissioning.experiment_plans.rotation_scan_plan import (
|
|
6
|
+
single_rotation_plan,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"do_pedestal_darks",
|
|
11
|
+
"do_non_pedestal_darks",
|
|
12
|
+
"single_rotation_plan",
|
|
13
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from bluesky.callbacks import CallbackBase
|
|
5
|
+
|
|
6
|
+
from mx_bluesky.beamlines.i24.parameters.constants import PlanNameConstants
|
|
7
|
+
from mx_bluesky.common.parameters.rotation import SingleRotationScan
|
|
8
|
+
from mx_bluesky.common.utils.log import LOGGER
|
|
9
|
+
|
|
10
|
+
READING_DUMP_FILENAME = "collection_info.json"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class JsonMetadataWriter(CallbackBase):
|
|
14
|
+
"""Callback class to handle the creation of metadata json files for commissioning.
|
|
15
|
+
|
|
16
|
+
To use, subscribe the Bluesky RunEngine to an instance of this class.
|
|
17
|
+
E.g.:
|
|
18
|
+
metadata_writer_callback = JsonMetadataWriter(parameters)
|
|
19
|
+
RE.subscribe(metadata_writer_callback)
|
|
20
|
+
Or decorate a plan using bluesky.preprocessors.subs_decorator.
|
|
21
|
+
|
|
22
|
+
See: https://blueskyproject.io/bluesky/callbacks.html#ways-to-invoke-callbacks
|
|
23
|
+
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self):
|
|
27
|
+
self.wavelength_in_a = None
|
|
28
|
+
self.energy_in_kev = None
|
|
29
|
+
self.detector_distance_mm = None
|
|
30
|
+
self.final_path: Path | None = None
|
|
31
|
+
self.descriptors: dict[str, dict] = {}
|
|
32
|
+
self.transmission: float | None = None
|
|
33
|
+
self.parameters: SingleRotationScan | None = None
|
|
34
|
+
|
|
35
|
+
super().__init__()
|
|
36
|
+
|
|
37
|
+
def start(self, doc: dict): # type: ignore
|
|
38
|
+
if doc.get("subplan_name") == PlanNameConstants.ROTATION_MAIN:
|
|
39
|
+
json_params = doc.get("rotation_scan_params")
|
|
40
|
+
assert json_params is not None
|
|
41
|
+
LOGGER.info(
|
|
42
|
+
f"Metadata writer recieved start document with experiment parameters {json_params}"
|
|
43
|
+
)
|
|
44
|
+
self.parameters = SingleRotationScan(**json.loads(json_params))
|
|
45
|
+
self.run_start_uid = doc.get("uid")
|
|
46
|
+
|
|
47
|
+
def descriptor(self, doc: dict): # type: ignore
|
|
48
|
+
self.descriptors[doc["uid"]] = doc
|
|
49
|
+
|
|
50
|
+
def event(self, doc: dict): # type: ignore
|
|
51
|
+
event_descriptor = self.descriptors[doc["descriptor"]]
|
|
52
|
+
|
|
53
|
+
if event_descriptor.get("name") == PlanNameConstants.ROTATION_DEVICE_READ:
|
|
54
|
+
assert self.parameters is not None
|
|
55
|
+
data = doc.get("data")
|
|
56
|
+
assert data is not None
|
|
57
|
+
self.wavelength_in_a = data.get("dcm-wavelength_in_a")
|
|
58
|
+
self.energy_in_kev = data.get("dcm-energy_in_keV")
|
|
59
|
+
self.detector_distance_mm = data.get("detector_motion-z")
|
|
60
|
+
assert data.get("detector-_writer-file_path"), (
|
|
61
|
+
"No detector writer path was found"
|
|
62
|
+
)
|
|
63
|
+
self.final_path = Path(data.get("detector-_writer-file_path"))
|
|
64
|
+
|
|
65
|
+
LOGGER.info(
|
|
66
|
+
f"Metadata writer received parameters, energy_in_kev: {self.energy_in_kev}, wavelength: {self.wavelength_in_a}, det_distance_mm: {self.detector_distance_mm}, file path: {self.final_path}"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
def stop(self, doc: dict): # type: ignore
|
|
70
|
+
assert self.parameters is not None
|
|
71
|
+
if (
|
|
72
|
+
self.run_start_uid is not None
|
|
73
|
+
and doc.get("run_start") == self.run_start_uid
|
|
74
|
+
and self.final_path
|
|
75
|
+
):
|
|
76
|
+
with open(self.final_path / READING_DUMP_FILENAME, "w") as f:
|
|
77
|
+
f.write(
|
|
78
|
+
json.dumps(
|
|
79
|
+
{
|
|
80
|
+
"wavelength_in_a": self.wavelength_in_a,
|
|
81
|
+
"energy_kev": self.energy_in_kev,
|
|
82
|
+
"angular_increment_deg": self.parameters.rotation_increment_deg,
|
|
83
|
+
"detector_distance_mm": self.detector_distance_mm,
|
|
84
|
+
}
|
|
85
|
+
)
|
|
86
|
+
)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import pydantic
|
|
4
|
+
from dodal.devices.attenuator.attenuator import EnumFilterAttenuator
|
|
5
|
+
from dodal.devices.hutch_shutter import HutchShutter
|
|
6
|
+
from dodal.devices.i24.aperture import Aperture
|
|
7
|
+
from dodal.devices.i24.beamstop import Beamstop
|
|
8
|
+
from dodal.devices.i24.commissioning_jungfrau import CommissioningJungfrau
|
|
9
|
+
from dodal.devices.i24.dcm import DCM
|
|
10
|
+
from dodal.devices.i24.dual_backlight import DualBacklight
|
|
11
|
+
from dodal.devices.i24.vgonio import VerticalGoniometer
|
|
12
|
+
from dodal.devices.motors import YZStage
|
|
13
|
+
from dodal.devices.synchrotron import Synchrotron
|
|
14
|
+
from dodal.devices.xbpm_feedback import XBPMFeedback
|
|
15
|
+
from dodal.devices.zebra.zebra import Zebra
|
|
16
|
+
from dodal.devices.zebra.zebra_controlled_shutter import ZebraShutter
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@pydantic.dataclasses.dataclass(config={"arbitrary_types_allowed": True})
|
|
20
|
+
class RotationScanComposite:
|
|
21
|
+
"""All devices which are directly or indirectly required by this plan"""
|
|
22
|
+
|
|
23
|
+
aperture: Aperture
|
|
24
|
+
attenuator: EnumFilterAttenuator
|
|
25
|
+
jungfrau: CommissioningJungfrau
|
|
26
|
+
gonio: VerticalGoniometer
|
|
27
|
+
synchrotron: Synchrotron
|
|
28
|
+
sample_shutter: ZebraShutter
|
|
29
|
+
zebra: Zebra
|
|
30
|
+
xbpm_feedback: XBPMFeedback
|
|
31
|
+
hutch_shutter: HutchShutter
|
|
32
|
+
beamstop: Beamstop
|
|
33
|
+
det_stage: YZStage
|
|
34
|
+
backlight: DualBacklight
|
|
35
|
+
dcm: DCM
|
|
@@ -13,7 +13,6 @@ from pydantic import PositiveInt
|
|
|
13
13
|
|
|
14
14
|
from mx_bluesky.beamlines.i24.jungfrau_commissioning.plan_stubs.plan_utils import (
|
|
15
15
|
fly_jungfrau,
|
|
16
|
-
override_file_path,
|
|
17
16
|
)
|
|
18
17
|
from mx_bluesky.common.utils.log import LOGGER
|
|
19
18
|
|
|
@@ -25,8 +24,8 @@ def do_pedestal_darks(
|
|
|
25
24
|
exp_time_s: float = 0.001,
|
|
26
25
|
pedestal_frames: PositiveInt = 20,
|
|
27
26
|
pedestal_loops: PositiveInt = 200,
|
|
27
|
+
filename: str = "pedestal_darks",
|
|
28
28
|
jungfrau: CommissioningJungfrau = inject("jungfrau"),
|
|
29
|
-
path_of_output_file: str | None = None,
|
|
30
29
|
) -> MsgGenerator:
|
|
31
30
|
"""Acquire darks in pedestal mode, using dynamic gain mode. This calibrates the offsets
|
|
32
31
|
for the jungfrau, and must be performed before acquiring real data in dynamic gain mode.
|
|
@@ -46,18 +45,19 @@ def do_pedestal_darks(
|
|
|
46
45
|
exp_time_s: Length of detector exposure for each frame.
|
|
47
46
|
pedestal_frames: Number of frames acquired per pedestal loop.
|
|
48
47
|
pedestal_loops: Number of times to acquire a set of pedestal_frames
|
|
48
|
+
filename: Name of output file
|
|
49
49
|
jungfrau: Jungfrau device
|
|
50
|
-
path_of_output_file: Absolute path of the detector file output, including file name. If None, then use the PathProvider
|
|
51
|
-
set during Jungfrau device instantiation
|
|
52
50
|
"""
|
|
53
51
|
|
|
54
52
|
@bpp.set_run_key_decorator(PEDESTAL_DARKS_RUN)
|
|
55
|
-
@bpp.run_decorator(
|
|
53
|
+
@bpp.run_decorator(
|
|
54
|
+
md={
|
|
55
|
+
"subplan_name": PEDESTAL_DARKS_RUN,
|
|
56
|
+
"detector_file_template": filename,
|
|
57
|
+
}
|
|
58
|
+
)
|
|
56
59
|
@bpp.stage_decorator([jungfrau])
|
|
57
60
|
def _do_decorated_plan():
|
|
58
|
-
if path_of_output_file:
|
|
59
|
-
override_file_path(jungfrau, path_of_output_file)
|
|
60
|
-
|
|
61
61
|
trigger_info = create_jungfrau_pedestal_triggering_info(
|
|
62
62
|
exp_time_s, pedestal_frames, pedestal_loops
|
|
63
63
|
)
|
|
@@ -67,12 +67,11 @@ def do_pedestal_darks(
|
|
|
67
67
|
yield from bps.mv(
|
|
68
68
|
jungfrau.drv.acquisition_type,
|
|
69
69
|
AcquisitionType.PEDESTAL,
|
|
70
|
-
jungfrau.drv.gain_mode,
|
|
71
|
-
GainMode.DYNAMIC,
|
|
72
70
|
)
|
|
73
71
|
yield from fly_jungfrau(
|
|
74
72
|
jungfrau,
|
|
75
73
|
trigger_info,
|
|
74
|
+
GainMode.DYNAMIC,
|
|
76
75
|
wait=True,
|
|
77
76
|
log_on_percentage_prefix="Jungfrau pedestal dynamic gain mode darks triggers received",
|
|
78
77
|
)
|
|
@@ -84,8 +83,8 @@ def do_non_pedestal_darks(
|
|
|
84
83
|
gain_mode: GainMode,
|
|
85
84
|
exp_time_s: float = 0.001,
|
|
86
85
|
total_triggers: PositiveInt = 1000,
|
|
86
|
+
filename: str = "darks",
|
|
87
87
|
jungfrau: CommissioningJungfrau = inject("jungfrau"),
|
|
88
|
-
path_of_output_file: str | None = None,
|
|
89
88
|
) -> MsgGenerator:
|
|
90
89
|
"""Internally take a set of images at a given gain mode.
|
|
91
90
|
|
|
@@ -96,26 +95,26 @@ def do_non_pedestal_darks(
|
|
|
96
95
|
exp_time_s: Length of detector exposure for each trigger.
|
|
97
96
|
total_triggers: Total triggers for the dark scan.
|
|
98
97
|
jungfrau: Jungfrau device
|
|
99
|
-
|
|
100
|
-
set during Jungfrau device instantiation
|
|
98
|
+
filename: Name of output file
|
|
101
99
|
"""
|
|
102
100
|
|
|
103
101
|
@bpp.set_run_key_decorator(STANDARD_DARKS_RUN)
|
|
104
|
-
@bpp.run_decorator(
|
|
102
|
+
@bpp.run_decorator(
|
|
103
|
+
md={
|
|
104
|
+
"subplan_name": STANDARD_DARKS_RUN,
|
|
105
|
+
"detector_file_template": filename,
|
|
106
|
+
}
|
|
107
|
+
)
|
|
105
108
|
@bpp.stage_decorator([jungfrau])
|
|
106
109
|
def _do_decorated_plan():
|
|
107
|
-
if path_of_output_file:
|
|
108
|
-
override_file_path(jungfrau, path_of_output_file)
|
|
109
|
-
|
|
110
110
|
trigger_info = create_jungfrau_internal_triggering_info(
|
|
111
111
|
total_triggers, exp_time_s
|
|
112
112
|
)
|
|
113
113
|
|
|
114
|
-
yield from bps.mv(jungfrau.drv.gain_mode, gain_mode)
|
|
115
|
-
|
|
116
114
|
yield from fly_jungfrau(
|
|
117
115
|
jungfrau,
|
|
118
116
|
trigger_info,
|
|
117
|
+
gain_mode,
|
|
119
118
|
wait=True,
|
|
120
119
|
log_on_percentage_prefix=f"Jungfrau {gain_mode} gain mode darks triggers received",
|
|
121
120
|
)
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
from functools import partial
|
|
2
|
+
|
|
3
|
+
import bluesky.plan_stubs as bps
|
|
4
|
+
import bluesky.preprocessors as bpp
|
|
5
|
+
from bluesky.preprocessors import run_decorator
|
|
6
|
+
from bluesky.utils import MsgGenerator
|
|
7
|
+
from dodal.devices.hutch_shutter import ShutterState
|
|
8
|
+
from dodal.devices.i24.aperture import AperturePositions
|
|
9
|
+
from dodal.devices.i24.beamstop import BeamstopPositions
|
|
10
|
+
from dodal.devices.i24.commissioning_jungfrau import CommissioningJungfrau
|
|
11
|
+
from dodal.devices.i24.dual_backlight import BacklightPositions
|
|
12
|
+
from dodal.devices.zebra.zebra import ArmDemand, I24Axes, Zebra
|
|
13
|
+
from dodal.devices.zebra.zebra_controlled_shutter import ZebraShutter
|
|
14
|
+
from ophyd_async.fastcs.jungfrau import (
|
|
15
|
+
GainMode,
|
|
16
|
+
create_jungfrau_external_triggering_info,
|
|
17
|
+
)
|
|
18
|
+
from pydantic import BaseModel, field_validator
|
|
19
|
+
|
|
20
|
+
from mx_bluesky.beamlines.i24.jungfrau_commissioning.callbacks.metadata_writer import (
|
|
21
|
+
JsonMetadataWriter,
|
|
22
|
+
)
|
|
23
|
+
from mx_bluesky.beamlines.i24.jungfrau_commissioning.composites import (
|
|
24
|
+
RotationScanComposite,
|
|
25
|
+
)
|
|
26
|
+
from mx_bluesky.beamlines.i24.jungfrau_commissioning.plan_stubs.plan_utils import (
|
|
27
|
+
JF_COMPLETE_GROUP,
|
|
28
|
+
fly_jungfrau,
|
|
29
|
+
)
|
|
30
|
+
from mx_bluesky.beamlines.i24.parameters.constants import (
|
|
31
|
+
PlanNameConstants,
|
|
32
|
+
)
|
|
33
|
+
from mx_bluesky.common.device_setup_plans.setup_zebra_and_shutter import (
|
|
34
|
+
setup_zebra_for_rotation,
|
|
35
|
+
tidy_up_zebra_after_rotation_scan,
|
|
36
|
+
)
|
|
37
|
+
from mx_bluesky.common.experiment_plans.inner_plans.read_hardware import (
|
|
38
|
+
read_hardware_plan,
|
|
39
|
+
)
|
|
40
|
+
from mx_bluesky.common.experiment_plans.rotation.rotation_utils import (
|
|
41
|
+
RotationMotionProfile,
|
|
42
|
+
calculate_motion_profile,
|
|
43
|
+
)
|
|
44
|
+
from mx_bluesky.common.parameters.components import PARAMETER_VERSION
|
|
45
|
+
from mx_bluesky.common.parameters.constants import (
|
|
46
|
+
USE_NUMTRACKER,
|
|
47
|
+
PlanGroupCheckpointConstants,
|
|
48
|
+
)
|
|
49
|
+
from mx_bluesky.common.parameters.rotation import (
|
|
50
|
+
SingleRotationScan,
|
|
51
|
+
)
|
|
52
|
+
from mx_bluesky.common.utils.log import LOGGER
|
|
53
|
+
|
|
54
|
+
READING_DUMP_FILENAME = "collection_info.json"
|
|
55
|
+
|
|
56
|
+
# Should be read from config file, see
|
|
57
|
+
# https://github.com/DiamondLightSource/mx-bluesky/issues/1502
|
|
58
|
+
JF_DET_STAGE_Y_POSITION_MM = 730
|
|
59
|
+
DEFAULT_DETECTOR_DISTANCE_MM = 200
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class ExternalRotationScanParams(BaseModel):
|
|
63
|
+
transmission_fractions: list[float]
|
|
64
|
+
exposure_time_s: float
|
|
65
|
+
omega_start_deg: float = 0
|
|
66
|
+
rotation_increment_per_image_deg: float = 0.1
|
|
67
|
+
filename: str = "rotations"
|
|
68
|
+
detector_distance_mm: float = DEFAULT_DETECTOR_DISTANCE_MM
|
|
69
|
+
sample_id: int
|
|
70
|
+
|
|
71
|
+
@field_validator("transmission_fractions")
|
|
72
|
+
@classmethod
|
|
73
|
+
def validate_transmission_fractions(cls, values):
|
|
74
|
+
for v in values:
|
|
75
|
+
if not 0 <= v <= 1:
|
|
76
|
+
raise ValueError(
|
|
77
|
+
f"All transmission fractions must be between 0 and 1; got {v}"
|
|
78
|
+
)
|
|
79
|
+
return values
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _get_internal_rotation_params(
|
|
83
|
+
entry_params: ExternalRotationScanParams, transmission: float
|
|
84
|
+
) -> SingleRotationScan:
|
|
85
|
+
return SingleRotationScan(
|
|
86
|
+
sample_id=entry_params.sample_id,
|
|
87
|
+
visit=USE_NUMTRACKER, # See https://github.com/DiamondLightSource/mx-bluesky/issues/1527
|
|
88
|
+
parameter_model_version=PARAMETER_VERSION,
|
|
89
|
+
file_name=entry_params.filename,
|
|
90
|
+
transmission_frac=transmission,
|
|
91
|
+
exposure_time_s=entry_params.exposure_time_s,
|
|
92
|
+
storage_directory=USE_NUMTRACKER,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class HutchClosedError(Exception): ...
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def rotation_scan_plan(
|
|
100
|
+
composite: RotationScanComposite, params: ExternalRotationScanParams
|
|
101
|
+
) -> MsgGenerator:
|
|
102
|
+
"""BlueAPI entry point for i24 JF rotation scans"""
|
|
103
|
+
|
|
104
|
+
for transmission in params.transmission_fractions:
|
|
105
|
+
rotation_params = _get_internal_rotation_params(params, transmission)
|
|
106
|
+
yield from single_rotation_plan(composite, rotation_params)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def set_up_beamline_for_rotation(
|
|
110
|
+
composite: RotationScanComposite,
|
|
111
|
+
det_z_mm: float,
|
|
112
|
+
transmission_frac: float,
|
|
113
|
+
):
|
|
114
|
+
"""Check hutch is open, then, in parallel, move backlight in,
|
|
115
|
+
move aperture in, move beamstop out and move det stages in. Wait for this parallel
|
|
116
|
+
move to finish."""
|
|
117
|
+
|
|
118
|
+
hutch_shutter_state: ShutterState = yield from bps.rd(
|
|
119
|
+
composite.hutch_shutter.status
|
|
120
|
+
)
|
|
121
|
+
LOGGER.info(f"Hutch shutter: {hutch_shutter_state}")
|
|
122
|
+
if hutch_shutter_state != ShutterState.OPEN:
|
|
123
|
+
LOGGER.error(f"Hutch shutter is not open! State is {hutch_shutter_state}")
|
|
124
|
+
raise HutchClosedError(
|
|
125
|
+
f"Hutch shutter is not open! State is {hutch_shutter_state}"
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
LOGGER.info(
|
|
129
|
+
"Making sure aperture and beamstop are in, detector stages are in position, backlight is out, and transmission is set..."
|
|
130
|
+
)
|
|
131
|
+
yield from bps.mv(
|
|
132
|
+
composite.aperture.position,
|
|
133
|
+
AperturePositions.IN,
|
|
134
|
+
composite.beamstop.pos_select,
|
|
135
|
+
BeamstopPositions.DATA_COLLECTION,
|
|
136
|
+
composite.det_stage.y,
|
|
137
|
+
JF_DET_STAGE_Y_POSITION_MM,
|
|
138
|
+
composite.backlight.backlight_position,
|
|
139
|
+
BacklightPositions.OUT,
|
|
140
|
+
composite.det_stage.z,
|
|
141
|
+
det_z_mm,
|
|
142
|
+
composite.attenuator,
|
|
143
|
+
transmission_frac,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def single_rotation_plan(
|
|
148
|
+
composite: RotationScanComposite,
|
|
149
|
+
params: SingleRotationScan,
|
|
150
|
+
):
|
|
151
|
+
"""A stub plan to collect diffraction images from a sample continuously rotating
|
|
152
|
+
about a fixed axis - for now this axis is limited to omega.
|
|
153
|
+
Needs additional setup of the sample environment and a wrapper to clean up."""
|
|
154
|
+
|
|
155
|
+
@bpp.set_run_key_decorator(PlanNameConstants.SINGLE_ROTATION_SCAN)
|
|
156
|
+
@run_decorator()
|
|
157
|
+
def _plan_in_run_decorator():
|
|
158
|
+
if not params.detector_distance_mm:
|
|
159
|
+
LOGGER.info(
|
|
160
|
+
f"Using default detector distance of {DEFAULT_DETECTOR_DISTANCE_MM} mm"
|
|
161
|
+
)
|
|
162
|
+
params.detector_distance_mm = DEFAULT_DETECTOR_DISTANCE_MM
|
|
163
|
+
|
|
164
|
+
yield from set_up_beamline_for_rotation(
|
|
165
|
+
composite, params.detector_distance_mm, params.transmission_frac
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
# This value isn't actually used, see https://github.com/DiamondLightSource/mx-bluesky/issues/1224
|
|
169
|
+
_motor_time_to_speed = 1
|
|
170
|
+
_max_velocity_deg_s = yield from bps.rd(composite.gonio.omega.max_velocity)
|
|
171
|
+
|
|
172
|
+
motion_values = calculate_motion_profile(
|
|
173
|
+
params, _motor_time_to_speed, _max_velocity_deg_s
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# Callback which intercepts read documents and writes to json file,
|
|
177
|
+
# used for saving device metadata
|
|
178
|
+
metadata_writer = JsonMetadataWriter()
|
|
179
|
+
|
|
180
|
+
@bpp.subs_decorator([metadata_writer])
|
|
181
|
+
@bpp.set_run_key_decorator(PlanNameConstants.ROTATION_MAIN)
|
|
182
|
+
@bpp.run_decorator(
|
|
183
|
+
md={
|
|
184
|
+
"subplan_name": PlanNameConstants.ROTATION_MAIN,
|
|
185
|
+
"scan_points": [params.scan_points],
|
|
186
|
+
"rotation_scan_params": params.model_dump_json(),
|
|
187
|
+
"detector_file_template": params.file_name,
|
|
188
|
+
}
|
|
189
|
+
)
|
|
190
|
+
def _rotation_scan_plan(
|
|
191
|
+
motion_values: RotationMotionProfile,
|
|
192
|
+
composite: RotationScanComposite,
|
|
193
|
+
):
|
|
194
|
+
_jf_trigger_info = create_jungfrau_external_triggering_info(
|
|
195
|
+
params.num_images, params.detector_params.exposure_time_s
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
axis = composite.gonio.omega
|
|
199
|
+
|
|
200
|
+
# can move to start as fast as possible
|
|
201
|
+
yield from bps.abs_set(
|
|
202
|
+
axis.velocity, motion_values.max_velocity_deg_s, wait=True
|
|
203
|
+
)
|
|
204
|
+
LOGGER.info(f"Moving omega to start value, {motion_values.start_scan_deg=}")
|
|
205
|
+
yield from bps.abs_set(
|
|
206
|
+
axis,
|
|
207
|
+
motion_values.start_motion_deg,
|
|
208
|
+
group=PlanGroupCheckpointConstants.ROTATION_READY_FOR_DC,
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
yield from setup_zebra_for_rotation(
|
|
212
|
+
composite.zebra,
|
|
213
|
+
composite.sample_shutter,
|
|
214
|
+
axis=I24Axes.OMEGA,
|
|
215
|
+
start_angle=motion_values.start_scan_deg,
|
|
216
|
+
scan_width=motion_values.scan_width_deg,
|
|
217
|
+
direction=motion_values.direction,
|
|
218
|
+
shutter_opening_deg=motion_values.shutter_opening_deg,
|
|
219
|
+
shutter_opening_s=motion_values.shutter_time_s,
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
yield from bps.wait(PlanGroupCheckpointConstants.ROTATION_READY_FOR_DC)
|
|
223
|
+
|
|
224
|
+
# Get ready for the actual scan
|
|
225
|
+
yield from bps.abs_set(
|
|
226
|
+
axis.velocity, motion_values.speed_for_rotation_deg_s, wait=True
|
|
227
|
+
)
|
|
228
|
+
yield from bps.abs_set(composite.zebra.pc.arm, ArmDemand.ARM, wait=True)
|
|
229
|
+
|
|
230
|
+
# Should check topup gate here, but not yet implemented,
|
|
231
|
+
# see https://github.com/DiamondLightSource/mx-bluesky/issues/1501
|
|
232
|
+
|
|
233
|
+
# Read hardware after preparing jungfrau so that device metadata output from callback is correct
|
|
234
|
+
# Whilst metadata is being written in bluesky we need to access the private writer here
|
|
235
|
+
read_hardware_partial = partial(
|
|
236
|
+
read_hardware_plan,
|
|
237
|
+
[
|
|
238
|
+
composite.dcm.energy_in_keV,
|
|
239
|
+
composite.dcm.wavelength_in_a,
|
|
240
|
+
composite.det_stage.z,
|
|
241
|
+
composite.jungfrau._writer.file_path, # noqa: SLF001 N
|
|
242
|
+
],
|
|
243
|
+
PlanNameConstants.ROTATION_DEVICE_READ,
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
yield from fly_jungfrau(
|
|
247
|
+
composite.jungfrau,
|
|
248
|
+
_jf_trigger_info,
|
|
249
|
+
GainMode.DYNAMIC,
|
|
250
|
+
wait=False,
|
|
251
|
+
log_on_percentage_prefix="Jungfrau rotation scan triggers received",
|
|
252
|
+
read_hardware_after_prepare_plan=read_hardware_partial,
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
LOGGER.info("Executing rotation scan")
|
|
256
|
+
yield from bps.rel_set(
|
|
257
|
+
axis,
|
|
258
|
+
motion_values.distance_to_move_deg,
|
|
259
|
+
wait=False,
|
|
260
|
+
group=JF_COMPLETE_GROUP,
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
LOGGER.info(
|
|
264
|
+
"Waiting for omega to finish moving and for Jungfrau to receive correct number of triggers"
|
|
265
|
+
)
|
|
266
|
+
yield from bps.wait(group=JF_COMPLETE_GROUP)
|
|
267
|
+
|
|
268
|
+
yield from bpp.finalize_wrapper(
|
|
269
|
+
_rotation_scan_plan(motion_values, composite),
|
|
270
|
+
final_plan=partial(
|
|
271
|
+
_cleanup_plan,
|
|
272
|
+
composite.zebra,
|
|
273
|
+
composite.jungfrau,
|
|
274
|
+
composite.sample_shutter,
|
|
275
|
+
),
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
yield from _plan_in_run_decorator()
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def _cleanup_plan(
|
|
282
|
+
zebra: Zebra,
|
|
283
|
+
jf: CommissioningJungfrau,
|
|
284
|
+
zebra_shutter: ZebraShutter,
|
|
285
|
+
group="rotation cleanup",
|
|
286
|
+
):
|
|
287
|
+
LOGGER.info("Tidying up Zebra and Jungfrau...")
|
|
288
|
+
yield from bps.unstage(jf, group=group)
|
|
289
|
+
yield from tidy_up_zebra_after_rotation_scan(
|
|
290
|
+
zebra, zebra_shutter, group=group, wait=False
|
|
291
|
+
)
|
|
292
|
+
yield from bps.wait(group=group)
|
|
@@ -5,20 +5,20 @@ from ophyd_async.core import (
|
|
|
5
5
|
WatchableAsyncStatus,
|
|
6
6
|
)
|
|
7
7
|
from ophyd_async.fastcs.jungfrau import (
|
|
8
|
+
GainMode,
|
|
8
9
|
create_jungfrau_external_triggering_info,
|
|
9
10
|
)
|
|
10
11
|
from pydantic import PositiveInt
|
|
11
12
|
|
|
12
13
|
from mx_bluesky.beamlines.i24.jungfrau_commissioning.plan_stubs.plan_utils import (
|
|
13
14
|
fly_jungfrau,
|
|
14
|
-
override_file_path,
|
|
15
15
|
)
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
def do_external_acquisition(
|
|
19
19
|
exp_time_s: float,
|
|
20
|
+
gain_mode: GainMode,
|
|
20
21
|
total_triggers: PositiveInt = 1,
|
|
21
|
-
output_file_path: str | None = None,
|
|
22
22
|
wait: bool = False,
|
|
23
23
|
jungfrau: CommissioningJungfrau = inject("commissioning_jungfrau"),
|
|
24
24
|
) -> MsgGenerator[WatchableAsyncStatus]:
|
|
@@ -32,14 +32,9 @@ def do_external_acquisition(
|
|
|
32
32
|
exp_time_s: Length of detector exposure for each frame.
|
|
33
33
|
total_triggers: Number of external triggers received before acquisition is marked as complete.
|
|
34
34
|
jungfrau: Jungfrau device
|
|
35
|
-
output_file_name: Absolute path of the detector file output, including file name. If None, then use the PathProvider
|
|
36
|
-
set during jungfrau device instantiation
|
|
37
35
|
wait: Optionally block until data collection is complete.
|
|
38
36
|
"""
|
|
39
37
|
|
|
40
|
-
if output_file_path:
|
|
41
|
-
override_file_path(jungfrau, output_file_path)
|
|
42
|
-
|
|
43
38
|
trigger_info = create_jungfrau_external_triggering_info(total_triggers, exp_time_s)
|
|
44
|
-
status = yield from fly_jungfrau(jungfrau, trigger_info, wait=wait)
|
|
39
|
+
status = yield from fly_jungfrau(jungfrau, trigger_info, gain_mode, wait=wait)
|
|
45
40
|
return status
|