mx-bluesky 1.5.15__py3-none-any.whl → 1.6.3__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/i02_1/parameters/gridscan.py +1 -1
- mx_bluesky/beamlines/i04/__init__.py +4 -0
- mx_bluesky/beamlines/i04/callbacks/murko_callback.py +18 -0
- mx_bluesky/beamlines/i04/experiment_plans/i04_grid_detect_then_xray_centre_plan.py +5 -4
- mx_bluesky/beamlines/i04/oav_centering_plans/oav_imaging.py +224 -10
- mx_bluesky/beamlines/i04/redis_to_murko_forwarder.py +5 -2
- mx_bluesky/beamlines/i04/thawing_plan.py +3 -2
- 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 +19 -20
- 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 +4 -9
- 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 +15 -19
- mx_bluesky/beamlines/i24/parameters/__init__.py +0 -0
- mx_bluesky/beamlines/i24/parameters/constants.py +9 -0
- mx_bluesky/beamlines/i24/serial/dcid.py +3 -3
- mx_bluesky/beamlines/i24/serial/extruder/i24ssx_extruder_collect_py3v2.py +7 -7
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_chip_collect_py3v1.py +7 -7
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_chip_manager_py3v1.py +3 -3
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_moveonclick.py +3 -3
- mx_bluesky/beamlines/i24/serial/setup_beamline/setup_beamline.py +5 -5
- mx_bluesky/beamlines/i24/serial/web_gui_plans/general_plans.py +7 -7
- mx_bluesky/beamlines/i24/serial/web_gui_plans/oav_plans.py +1 -1
- 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 +2 -2
- mx_bluesky/common/experiment_plans/inner_plans/read_hardware.py +1 -0
- mx_bluesky/common/experiment_plans/oav_grid_detection_plan.py +1 -1
- mx_bluesky/common/experiment_plans/pin_tip_centring_plan.py +2 -2
- 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 +9 -7
- mx_bluesky/hyperion/device_setup_plans/setup_panda.py +1 -1
- mx_bluesky/hyperion/device_setup_plans/utils.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 +2 -5
- mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +3 -3
- mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +14 -128
- mx_bluesky/hyperion/experiment_plans/set_energy_plan.py +4 -4
- mx_bluesky/hyperion/experiment_plans/udc_default_state.py +19 -6
- 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/device_composites.py +1 -1
- 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.6.3.dist-info}/METADATA +3 -31
- {mx_bluesky-1.5.15.dist-info → mx_bluesky-1.6.3.dist-info}/RECORD +88 -68
- {mx_bluesky-1.5.15.dist-info → mx_bluesky-1.6.3.dist-info}/WHEEL +1 -1
- {mx_bluesky-1.5.15.dist-info → mx_bluesky-1.6.3.dist-info}/entry_points.txt +0 -0
- {mx_bluesky-1.5.15.dist-info → mx_bluesky-1.6.3.dist-info}/licenses/LICENSE +0 -0
- {mx_bluesky-1.5.15.dist-info → mx_bluesky-1.6.3.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.
|
|
32
|
-
__version_tuple__ = version_tuple = (1,
|
|
31
|
+
__version__ = version = '1.6.3'
|
|
32
|
+
__version_tuple__ = version_tuple = (1, 6, 3)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from dodal.devices.i02_1.fast_grid_scan import ZebraGridScanParamsTwoD
|
|
1
|
+
from dodal.devices.beamlines.i02_1.fast_grid_scan import ZebraGridScanParamsTwoD
|
|
2
2
|
from scanspec.specs import Product
|
|
3
3
|
|
|
4
4
|
from mx_bluesky.common.parameters.components import SplitScan, WithOptionalEnergyChange
|
|
@@ -2,6 +2,8 @@ from mx_bluesky.beamlines.i04.experiment_plans.i04_grid_detect_then_xray_centre_
|
|
|
2
2
|
i04_default_grid_detect_and_xray_centre,
|
|
3
3
|
)
|
|
4
4
|
from mx_bluesky.beamlines.i04.oav_centering_plans.oav_imaging import (
|
|
5
|
+
find_beam_centres,
|
|
6
|
+
optimise_transmission_with_oav,
|
|
5
7
|
take_oav_image_with_scintillator_in,
|
|
6
8
|
)
|
|
7
9
|
from mx_bluesky.beamlines.i04.thawing_plan import (
|
|
@@ -16,4 +18,6 @@ __all__ = [
|
|
|
16
18
|
"i04_default_grid_detect_and_xray_centre",
|
|
17
19
|
"thaw_and_murko_centre",
|
|
18
20
|
"take_oav_image_with_scintillator_in",
|
|
21
|
+
"optimise_transmission_with_oav",
|
|
22
|
+
"find_beam_centres",
|
|
19
23
|
]
|
|
@@ -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}"
|
|
@@ -9,6 +9,8 @@ from dodal.common import inject
|
|
|
9
9
|
from dodal.devices.aperturescatterguard import ApertureScatterguard, ApertureValue
|
|
10
10
|
from dodal.devices.attenuator.attenuator import BinaryFilterAttenuator
|
|
11
11
|
from dodal.devices.backlight import Backlight
|
|
12
|
+
from dodal.devices.beamlines.i04.beamsize import Beamsize
|
|
13
|
+
from dodal.devices.beamlines.i04.transfocator import Transfocator
|
|
12
14
|
from dodal.devices.common_dcm import DoubleCrystalMonochromator
|
|
13
15
|
from dodal.devices.detector.detector_motion import DetectorMotion
|
|
14
16
|
from dodal.devices.eiger import EigerDetector
|
|
@@ -17,8 +19,6 @@ from dodal.devices.fast_grid_scan import (
|
|
|
17
19
|
set_fast_grid_scan_params,
|
|
18
20
|
)
|
|
19
21
|
from dodal.devices.flux import Flux
|
|
20
|
-
from dodal.devices.i04.beamsize import Beamsize
|
|
21
|
-
from dodal.devices.i04.transfocator import Transfocator
|
|
22
22
|
from dodal.devices.mx_phase1.beamstop import Beamstop
|
|
23
23
|
from dodal.devices.oav.oav_detector import OAV
|
|
24
24
|
from dodal.devices.oav.pin_image_recognition import PinTipDetection
|
|
@@ -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(
|
|
@@ -6,8 +6,10 @@ from bluesky.utils import MsgGenerator
|
|
|
6
6
|
from dodal.common import inject
|
|
7
7
|
from dodal.devices.attenuator.attenuator import BinaryFilterAttenuator
|
|
8
8
|
from dodal.devices.backlight import Backlight
|
|
9
|
+
from dodal.devices.beamlines.i04.beam_centre import CentreEllipseMethod
|
|
10
|
+
from dodal.devices.beamlines.i04.max_pixel import MaxPixel
|
|
9
11
|
from dodal.devices.mx_phase1.beamstop import Beamstop, BeamstopPositions
|
|
10
|
-
from dodal.devices.oav.oav_detector import OAV
|
|
12
|
+
from dodal.devices.oav.oav_detector import OAV, ZoomControllerWithBeamCentres
|
|
11
13
|
from dodal.devices.robot import BartRobot, PinMounted
|
|
12
14
|
from dodal.devices.scintillator import InOut, Scintillator
|
|
13
15
|
from dodal.devices.xbpm_feedback import XBPMFeedback
|
|
@@ -19,8 +21,9 @@ from dodal.devices.zebra.zebra_controlled_shutter import (
|
|
|
19
21
|
from ophyd_async.core import InOut as core_INOUT
|
|
20
22
|
|
|
21
23
|
from mx_bluesky.common.utils.exceptions import BeamlineStateError
|
|
24
|
+
from mx_bluesky.common.utils.log import LOGGER
|
|
22
25
|
|
|
23
|
-
|
|
26
|
+
OAV_PREPARE_BEAMLINE_FOR_SCINT_WAIT = "Wait for scint to move in"
|
|
24
27
|
|
|
25
28
|
|
|
26
29
|
def take_oav_image_with_scintillator_in(
|
|
@@ -48,21 +51,36 @@ def take_oav_image_with_scintillator_in(
|
|
|
48
51
|
defaults are always correct.
|
|
49
52
|
"""
|
|
50
53
|
|
|
54
|
+
LOGGER.info("Preparing beamline to take scintillator images...")
|
|
51
55
|
yield from _prepare_beamline_for_scintillator_images(
|
|
52
|
-
robot,
|
|
56
|
+
robot,
|
|
57
|
+
beamstop,
|
|
58
|
+
backlight,
|
|
59
|
+
scintillator,
|
|
60
|
+
xbpm_feedback,
|
|
61
|
+
shutter,
|
|
62
|
+
OAV_PREPARE_BEAMLINE_FOR_SCINT_WAIT,
|
|
63
|
+
)
|
|
64
|
+
yield from bps.abs_set(
|
|
65
|
+
attenuator, transmission, group=OAV_PREPARE_BEAMLINE_FOR_SCINT_WAIT
|
|
53
66
|
)
|
|
54
|
-
|
|
55
|
-
yield from bps.abs_set(attenuator, transmission, group=initial_wait_group)
|
|
56
67
|
|
|
57
68
|
if image_name is None:
|
|
58
69
|
image_name = f"{time.time_ns()}ATT{transmission * 100}"
|
|
70
|
+
LOGGER.info(f"Using image name {image_name}")
|
|
71
|
+
LOGGER.info("Waiting for prepare beamline plan to complete...")
|
|
72
|
+
yield from bps.wait(OAV_PREPARE_BEAMLINE_FOR_SCINT_WAIT)
|
|
59
73
|
|
|
60
|
-
|
|
74
|
+
LOGGER.info("Opening shutter...")
|
|
61
75
|
|
|
62
76
|
yield from bps.abs_set(shutter.control_mode, ZebraShutterControl.MANUAL, wait=True)
|
|
63
77
|
yield from bps.abs_set(shutter, ZebraShutterState.OPEN, wait=True)
|
|
64
78
|
|
|
65
|
-
|
|
79
|
+
LOGGER.info("Taking image...")
|
|
80
|
+
|
|
81
|
+
yield from take_and_save_oav_image(
|
|
82
|
+
file_path=image_path, file_name=image_name, oav=oav
|
|
83
|
+
)
|
|
66
84
|
|
|
67
85
|
|
|
68
86
|
def _prepare_beamline_for_scintillator_images(
|
|
@@ -71,11 +89,13 @@ def _prepare_beamline_for_scintillator_images(
|
|
|
71
89
|
backlight: Backlight,
|
|
72
90
|
scintillator: Scintillator,
|
|
73
91
|
xbpm_feedback: XBPMFeedback,
|
|
92
|
+
shutter: ZebraShutter,
|
|
74
93
|
group: str,
|
|
75
94
|
) -> MsgGenerator:
|
|
76
95
|
"""
|
|
77
|
-
Prepares the beamline for oav image by making sure the pin is
|
|
78
|
-
the beam is on (feedback check). Finally, the scintillator is moved in
|
|
96
|
+
Prepares the beamline for oav image by making sure the pin is not mounted and
|
|
97
|
+
the beam is on (feedback check). Finally, the scintillator is moved in and the
|
|
98
|
+
shutter opened.
|
|
79
99
|
"""
|
|
80
100
|
pin_mounted = yield from bps.rd(robot.gonio_pin_sensor)
|
|
81
101
|
if pin_mounted == PinMounted.PIN_MOUNTED:
|
|
@@ -90,6 +110,9 @@ def _prepare_beamline_for_scintillator_images(
|
|
|
90
110
|
yield from bps.abs_set(backlight, core_INOUT.OUT, group=group)
|
|
91
111
|
|
|
92
112
|
yield from bps.abs_set(scintillator.selected_pos, InOut.IN, group=group)
|
|
113
|
+
# Not waiting for control mode to be set before opening shutter can result in timeout error
|
|
114
|
+
yield from bps.abs_set(shutter.control_mode, ZebraShutterControl.MANUAL, wait=True)
|
|
115
|
+
yield from bps.abs_set(shutter, ZebraShutterState.OPEN, group=group)
|
|
93
116
|
|
|
94
117
|
|
|
95
118
|
def take_and_save_oav_image(
|
|
@@ -109,7 +132,198 @@ def take_and_save_oav_image(
|
|
|
109
132
|
if not os.path.exists(full_file_path):
|
|
110
133
|
yield from bps.abs_set(oav.snapshot.filename, file_name, group=group)
|
|
111
134
|
yield from bps.abs_set(oav.snapshot.directory, file_path, group=group)
|
|
112
|
-
yield from bps.wait(group)
|
|
135
|
+
yield from bps.wait(group, timeout=60)
|
|
113
136
|
yield from bps.trigger(oav.snapshot, wait=True)
|
|
114
137
|
else:
|
|
115
138
|
raise FileExistsError("OAV image file path already exists")
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _max_pixel_at_transmission(
|
|
142
|
+
max_pixel: MaxPixel,
|
|
143
|
+
attenuator: BinaryFilterAttenuator,
|
|
144
|
+
xbpm_feedback: XBPMFeedback,
|
|
145
|
+
transmission: float,
|
|
146
|
+
):
|
|
147
|
+
# Potential controls issue on XBPM device means it can mark
|
|
148
|
+
# itself as stable before it really is
|
|
149
|
+
yield from bps.trigger(xbpm_feedback, wait=True)
|
|
150
|
+
yield from bps.mv(attenuator, transmission)
|
|
151
|
+
yield from bps.trigger(max_pixel, wait=True)
|
|
152
|
+
return (yield from bps.rd(max_pixel.max_pixel_val))
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def optimise_transmission_with_oav(
|
|
156
|
+
upper_bound: float = 100,
|
|
157
|
+
lower_bound: float = 0,
|
|
158
|
+
target_brightness_fraction: float = 0.75,
|
|
159
|
+
min_transmission_change: float = 5,
|
|
160
|
+
max_iterations: int = 10,
|
|
161
|
+
max_pixel: MaxPixel = inject("max_pixel"),
|
|
162
|
+
attenuator: BinaryFilterAttenuator = inject("attenuator"),
|
|
163
|
+
xbpm_feedback: XBPMFeedback = inject("xbpm_feedback"),
|
|
164
|
+
) -> MsgGenerator:
|
|
165
|
+
"""
|
|
166
|
+
Plan to find the optimal oav transmission. First the brightest pixel at 100%
|
|
167
|
+
transmission is taken. A fraction of this (target_brightness_fraction) is taken
|
|
168
|
+
as the target - as in the optimal transmission will have it's max pixel as the set
|
|
169
|
+
target. A binary search is used to reach this.
|
|
170
|
+
Args:
|
|
171
|
+
upper_bound: Maximum transmission which will be searched. In percent.
|
|
172
|
+
lower_bound: Minimum transmission which will be searched. In percent.
|
|
173
|
+
target_brightness_fraction: Fraction of the brightest pixel at 100%
|
|
174
|
+
transmission which should be used as the target max pixel brightness.
|
|
175
|
+
min_transmission_change: If the next search point would require a transmission
|
|
176
|
+
change less than this then we stop.
|
|
177
|
+
max_iterations: Maximum amount of iterations.
|
|
178
|
+
"""
|
|
179
|
+
|
|
180
|
+
if upper_bound < lower_bound:
|
|
181
|
+
raise ValueError(
|
|
182
|
+
f"Upper bound ({upper_bound}) must be higher than lower bound {lower_bound}"
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
brightest_pixel_at_full_beam = yield from _max_pixel_at_transmission(
|
|
186
|
+
max_pixel, attenuator, xbpm_feedback, 1
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
if brightest_pixel_at_full_beam == 0:
|
|
190
|
+
raise ValueError("No beam found at full transmission")
|
|
191
|
+
|
|
192
|
+
target_pixel_brightness = brightest_pixel_at_full_beam * target_brightness_fraction
|
|
193
|
+
LOGGER.info(
|
|
194
|
+
f"Optimising until max pixel in image has a value of {target_pixel_brightness}"
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
iterations = 0
|
|
198
|
+
|
|
199
|
+
while iterations < max_iterations:
|
|
200
|
+
mid = round((upper_bound + lower_bound) / 2, 2) # limit to 2 dp
|
|
201
|
+
LOGGER.info(f"On iteration {iterations}")
|
|
202
|
+
|
|
203
|
+
current_transmission = yield from bps.rd(attenuator.actual_transmission)
|
|
204
|
+
transmission_change_percent = abs(mid - current_transmission * 100)
|
|
205
|
+
if transmission_change_percent < min_transmission_change:
|
|
206
|
+
LOGGER.info(
|
|
207
|
+
f"Next transmission change would be small ({transmission_change_percent}%) so stopping at {current_transmission}"
|
|
208
|
+
)
|
|
209
|
+
return
|
|
210
|
+
|
|
211
|
+
brightest_pixel = yield from _max_pixel_at_transmission(
|
|
212
|
+
max_pixel, attenuator, xbpm_feedback, mid / 100
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
LOGGER.info(f"Upper bound is: {upper_bound}, Lower bound is: {lower_bound}")
|
|
216
|
+
LOGGER.info(
|
|
217
|
+
f"Testing transmission {mid}, brightest pixel found {brightest_pixel}"
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
if target_pixel_brightness == brightest_pixel:
|
|
221
|
+
mid = round(mid, 0)
|
|
222
|
+
LOGGER.info(f"\nOptimal transmission found: {mid}")
|
|
223
|
+
return
|
|
224
|
+
|
|
225
|
+
# condition for too low so want to try higher
|
|
226
|
+
elif brightest_pixel < target_pixel_brightness:
|
|
227
|
+
LOGGER.info("Result: Too low \n")
|
|
228
|
+
lower_bound = mid
|
|
229
|
+
|
|
230
|
+
# condition for too high so want to try lower
|
|
231
|
+
elif brightest_pixel > target_pixel_brightness:
|
|
232
|
+
LOGGER.info("Result: Too high \n")
|
|
233
|
+
upper_bound = mid
|
|
234
|
+
iterations += 1
|
|
235
|
+
raise StopIteration("Max iterations reached")
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _get_all_zoom_levels(
|
|
239
|
+
zoom_controller: ZoomControllerWithBeamCentres,
|
|
240
|
+
) -> MsgGenerator[tuple[str]]:
|
|
241
|
+
zoom_levels = []
|
|
242
|
+
level_signals = [
|
|
243
|
+
centring_device.level_name
|
|
244
|
+
for centring_device in zoom_controller.beam_centres.values()
|
|
245
|
+
]
|
|
246
|
+
for signal in level_signals:
|
|
247
|
+
level_name = yield from bps.rd(signal)
|
|
248
|
+
if level_name:
|
|
249
|
+
zoom_levels.append(level_name)
|
|
250
|
+
return tuple(zoom_levels)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def find_beam_centres(
|
|
254
|
+
zoom_levels_to_centre: tuple[str, ...] | None = None,
|
|
255
|
+
zoom_levels_to_optimise_transmission: tuple[str, ...] = ("1.0x", "7.5x"),
|
|
256
|
+
robot: BartRobot = inject("robot"),
|
|
257
|
+
beamstop: Beamstop = inject("beamstop"),
|
|
258
|
+
backlight: Backlight = inject("backlight"),
|
|
259
|
+
scintillator: Scintillator = inject("scintillator"),
|
|
260
|
+
xbpm_feedback: XBPMFeedback = inject("xbpm_feedback"),
|
|
261
|
+
max_pixel: MaxPixel = inject("max_pixel"),
|
|
262
|
+
centre_ellipse: CentreEllipseMethod = inject("beam_centre"),
|
|
263
|
+
attenuator: BinaryFilterAttenuator = inject("attenuator"),
|
|
264
|
+
zoom_controller: ZoomControllerWithBeamCentres = inject("zoom_controller"),
|
|
265
|
+
shutter: ZebraShutter = inject("sample_shutter"),
|
|
266
|
+
) -> MsgGenerator:
|
|
267
|
+
"""
|
|
268
|
+
Finds beam centres at the zoom levels given by zoom_levels_to_centre, first
|
|
269
|
+
optimising transmission if the zoom level is in zoom_levels_to_optimise_transmission.
|
|
270
|
+
|
|
271
|
+
Note that the previous beam centre values are used to draw an ROI box around the
|
|
272
|
+
OAV image when finding updated beam centre. If the previous values are very wrong,
|
|
273
|
+
this plan may fail or give inaccurate results.
|
|
274
|
+
|
|
275
|
+
zoom_levels_to_centre: The levels to do centring at, by default runs at all known
|
|
276
|
+
zoom levels.
|
|
277
|
+
zoom_levels_to_optimise_transmission: The levels to optimise transmission at,
|
|
278
|
+
defaults to 1x and 7.5x
|
|
279
|
+
"""
|
|
280
|
+
|
|
281
|
+
all_zooms = yield from _get_all_zoom_levels(zoom_controller)
|
|
282
|
+
if zoom_levels_to_centre is None:
|
|
283
|
+
zoom_levels_to_centre = all_zooms
|
|
284
|
+
|
|
285
|
+
for zoom in [*zoom_levels_to_optimise_transmission, *zoom_levels_to_centre]:
|
|
286
|
+
if zoom not in all_zooms:
|
|
287
|
+
raise ValueError(f"Unknown zoom ({zoom}). Known zooms are {all_zooms}")
|
|
288
|
+
|
|
289
|
+
LOGGER.info("Preparing beamline for images...")
|
|
290
|
+
yield from _prepare_beamline_for_scintillator_images(
|
|
291
|
+
robot,
|
|
292
|
+
beamstop,
|
|
293
|
+
backlight,
|
|
294
|
+
scintillator,
|
|
295
|
+
xbpm_feedback,
|
|
296
|
+
shutter,
|
|
297
|
+
OAV_PREPARE_BEAMLINE_FOR_SCINT_WAIT,
|
|
298
|
+
)
|
|
299
|
+
LOGGER.info("Waiting for prepare beamline plan to complete...")
|
|
300
|
+
yield from bps.wait(OAV_PREPARE_BEAMLINE_FOR_SCINT_WAIT)
|
|
301
|
+
|
|
302
|
+
for centring_device in zoom_controller.beam_centres.values():
|
|
303
|
+
zoom_name = yield from bps.rd(centring_device.level_name)
|
|
304
|
+
if zoom_name in zoom_levels_to_centre:
|
|
305
|
+
LOGGER.info(f"Moving to zoom level {zoom_name}")
|
|
306
|
+
yield from bps.abs_set(zoom_controller, zoom_name, wait=True)
|
|
307
|
+
if zoom_name in zoom_levels_to_optimise_transmission:
|
|
308
|
+
LOGGER.info(f"Optimising transmission at zoom level {zoom_name}")
|
|
309
|
+
yield from optimise_transmission_with_oav(
|
|
310
|
+
100,
|
|
311
|
+
0,
|
|
312
|
+
max_pixel=max_pixel,
|
|
313
|
+
attenuator=attenuator,
|
|
314
|
+
xbpm_feedback=xbpm_feedback,
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
yield from bps.trigger(centre_ellipse, wait=True)
|
|
318
|
+
centre_x = yield from bps.rd(centre_ellipse.center_x_val)
|
|
319
|
+
centre_y = yield from bps.rd(centre_ellipse.center_y_val)
|
|
320
|
+
centre_x = round(centre_x)
|
|
321
|
+
centre_y = round(centre_y)
|
|
322
|
+
LOGGER.info(
|
|
323
|
+
f"Writing centre values ({centre_x}, {centre_y}) to OAV PVs at zoom level {zoom_name}"
|
|
324
|
+
)
|
|
325
|
+
yield from bps.mv(
|
|
326
|
+
centring_device.x_centre, centre_x, centring_device.y_centre, centre_y
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
LOGGER.info("Find beam centre plan completed!")
|
|
@@ -8,8 +8,11 @@ from typing import TypedDict
|
|
|
8
8
|
|
|
9
9
|
import numpy as np
|
|
10
10
|
import zmq
|
|
11
|
-
from dodal.devices.i04.constants import RedisConstants
|
|
12
|
-
from dodal.devices.i04.murko_results import
|
|
11
|
+
from dodal.devices.beamlines.i04.constants import RedisConstants
|
|
12
|
+
from dodal.devices.beamlines.i04.murko_results import (
|
|
13
|
+
RESULTS_COMPLETE_MESSAGE,
|
|
14
|
+
MurkoResult,
|
|
15
|
+
)
|
|
13
16
|
from numpy.typing import NDArray
|
|
14
17
|
from PIL import Image
|
|
15
18
|
from redis import StrictRedis
|
|
@@ -3,8 +3,8 @@ import bluesky.preprocessors as bpp
|
|
|
3
3
|
from bluesky.preprocessors import contingency_decorator, run_decorator, subs_decorator
|
|
4
4
|
from bluesky.utils import MsgGenerator
|
|
5
5
|
from dodal.common import inject
|
|
6
|
-
from dodal.devices.i04.constants import RedisConstants
|
|
7
|
-
from dodal.devices.i04.murko_results import MurkoResultsDevice
|
|
6
|
+
from dodal.devices.beamlines.i04.constants import RedisConstants
|
|
7
|
+
from dodal.devices.beamlines.i04.murko_results import MurkoResultsDevice
|
|
8
8
|
from dodal.devices.oav.oav_to_redis_forwarder import OAVToRedisForwarder, Source
|
|
9
9
|
from dodal.devices.robot import BartRobot
|
|
10
10
|
from dodal.devices.smargon import Smargon
|
|
@@ -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.beamlines.i24.aperture import Aperture
|
|
6
|
+
from dodal.devices.beamlines.i24.beamstop import Beamstop
|
|
7
|
+
from dodal.devices.beamlines.i24.commissioning_jungfrau import CommissioningJungfrau
|
|
8
|
+
from dodal.devices.beamlines.i24.dcm import DCM
|
|
9
|
+
from dodal.devices.beamlines.i24.dual_backlight import DualBacklight
|
|
10
|
+
from dodal.devices.beamlines.i24.vgonio import VerticalGoniometer
|
|
11
|
+
from dodal.devices.hutch_shutter import HutchShutter
|
|
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
|