Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/user/hyperion/advanced/concurrent_operations.puml
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ activate RE #ffeeee
RE -> RE++ : start_preparing_data_collection_then_do_plan
RE -> eiger ++ : arm the eiger
RE -> GridscanISPyB ++ : send parameters to grdscan callback
RE -> RE ++ #ffdddd : pin_centre_then_flyscan_plan
RE -> RE ++ #ffdddd : pin_centre_then_xray_centre_plan
RE -> RE: setup_beamline_for_oav
RE -> RE: pin_tip_centre_plan
...do pin tip centring...
Expand Down Expand Up @@ -121,7 +121,7 @@ snapshots later
RE -> zebra : tidy zebra
deactivate RE /'common_flyscan_xray_centre'/
deactivate RE /' detect_grid_and_do_gridscan'/
return /' pin_centre_then_flyscan_plan'/
return /' pin_centre_then_xray_centre_plan'/
deactivate GridscanISPyB
deactivate RE /' start_preparing_data_collection_then_do_plan '/
deactivate RE /'robot_load_then_xray_centre'/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,19 @@
setup_zebra_for_gridscan,
tidy_up_zebra_after_gridscan,
)
from mx_bluesky.common.experiment_plans.change_aperture_then_move_plan import (
get_results_then_change_aperture_and_move_to_xtal,
)
from mx_bluesky.common.experiment_plans.common_flyscan_xray_centre_plan import (
BeamlineSpecificFGSFeatures,
construct_beamline_specific_fast_gridscan_features,
)
from mx_bluesky.common.experiment_plans.common_grid_detect_then_xray_centre_plan import (
grid_detect_then_xray_centre,
)
from mx_bluesky.common.experiment_plans.inner_plans.xrc_results_utils import (
zocalo_stage_decorator,
)
from mx_bluesky.common.experiment_plans.oav_snapshot_plan import (
setup_beamline_for_oav,
)
Expand Down Expand Up @@ -85,6 +91,7 @@
from mx_bluesky.common.utils.utils import (
fix_transmission_and_exposure_time_for_current_wavelength,
)
from mx_bluesky.common.utils.xrc_result import XRayCentreEventHandler

DEFAULT_XRC_BEAMSIZE_MICRONS = 20

Expand Down Expand Up @@ -154,10 +161,10 @@ def i04_default_grid_detect_and_xray_centre(
composite = GridDetectThenXRayCentreComposite(
eiger,
synchrotron,
zocalo,
smargon,
aperture_scatterguard,
attenuator,
zocalo,
backlight,
beamstop,
beamsize,
Expand Down Expand Up @@ -194,12 +201,14 @@ def tidy_beamline():
composite.detector_motion,
)

@zocalo_stage_decorator(composite.zocalo)
@bpp.finalize_decorator(tidy_beamline)
def _inner_grid_detect_then_xrc():
# These callbacks let us talk to ISPyB and Nexgen. They aren't included in the common plan because
# Hyperion handles its callbacks differently to BlueAPI-managed plans, see
# https://github.com/DiamondLightSource/mx-bluesky/issues/1117
callbacks = create_gridscan_callbacks()
flyscan_event_handler = XRayCentreEventHandler()
callbacks = *create_gridscan_callbacks(), flyscan_event_handler

@bpp.subs_decorator(callbacks)
@verify_undulator_gap_before_run_decorator(composite)
Expand All @@ -219,6 +228,11 @@ def grid_detect_then_xray_centre_with_callbacks():

try:
yield from grid_detect_then_xray_centre_with_callbacks()
yield from get_results_then_change_aperture_and_move_to_xtal(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should: Were we previously changing aperture based on xtal size for i04? I didn't think this was something they wanted

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, you're right

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

composite,
grid_common_params.specified_grid_params,
flyscan_event_handler,
)
except CrystalNotFoundError:
yield from bps.mv(
smargon.x, initial_x, smargon.y, initial_y, smargon.z, initial_z
Expand Down Expand Up @@ -316,7 +330,6 @@ def construct_i04_specific_features(
fgs_motors,
signals_to_read_pre_flyscan,
signals_to_read_during_collection,
get_xrc_results_from_zocalo=True,
)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,32 @@
from dodal.devices.smargon import Smargon, StubPosition

from mx_bluesky.common.device_setup_plans.manipulate_sample import move_x_y_z
from mx_bluesky.common.experiment_plans.inner_plans.xrc_results_utils import (
fetch_xrc_results_from_zocalo,
)
from mx_bluesky.common.parameters.constants import PlanGroupCheckpointConstants
from mx_bluesky.common.parameters.device_composites import (
GridDetectThenXRayCentreComposite,
)
from mx_bluesky.common.parameters.gridscan import SpecifiedThreeDGridScan
from mx_bluesky.common.utils.log import LOGGER
from mx_bluesky.common.utils.tracing import TRACER
from mx_bluesky.common.xrc_result import XRayCentreResult
from mx_bluesky.common.utils.xrc_result import XRayCentreEventHandler, XRayCentreResult


def get_results_then_change_aperture_and_move_to_xtal(
composite: GridDetectThenXRayCentreComposite,
parameters: SpecifiedThreeDGridScan,
flyscan_event_handler: XRayCentreEventHandler,
):
yield from fetch_xrc_results_from_zocalo(composite.zocalo, parameters)
flyscan_results = flyscan_event_handler.xray_centre_results
assert flyscan_results, (
"Flyscan result event not received or no crystal found and exception not raised"
)
yield from change_aperture_then_move_to_xtal(
flyscan_results[0], composite.smargon, composite.aperture_scatterguard
)


def change_aperture_then_move_to_xtal(
Expand All @@ -23,7 +45,7 @@ def change_aperture_then_move_to_xtal(
bounding_box_size = numpy.abs(
best_hit.bounding_box_mm[1] - best_hit.bounding_box_mm[0]
)
yield from set_aperture_for_bbox_mm(
yield from _set_aperture_for_bbox_mm(
aperture_scatterguard,
bounding_box_size,
)
Expand All @@ -41,7 +63,7 @@ def change_aperture_then_move_to_xtal(
yield from bps.mv(smargon.stub_offsets, StubPosition.CURRENT_AS_CENTER)


def set_aperture_for_bbox_mm(
def _set_aperture_for_bbox_mm(
aperture_device: ApertureScatterguard,
bbox_size_mm: list[float] | numpy.ndarray,
group=PlanGroupCheckpointConstants.GRID_READY_FOR_DC,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,48 +1,38 @@
from __future__ import annotations

import dataclasses
from collections.abc import Callable, Sequence
from collections.abc import Callable
from functools import partial

import bluesky.plan_stubs as bps
import bluesky.preprocessors as bpp
import numpy as np
from bluesky.protocols import Readable
from bluesky.utils import FailedStatus, MsgGenerator
from dodal.common.beamlines.commissioning_mode import read_commissioning_mode
from dodal.devices.eiger import EigerDetector
from dodal.devices.fast_grid_scan import (
FastGridScanCommon,
FastGridScanThreeD,
GridScanInvalidError,
)
from dodal.devices.zocalo import ZocaloResults
from dodal.devices.zocalo.zocalo_results import (
XrcResult,
get_full_processing_results,
)

from mx_bluesky.common.experiment_plans.inner_plans.do_fgs import (
ZOCALO_STAGE_GROUP,
kickoff_and_complete_gridscan,
)
from mx_bluesky.common.experiment_plans.inner_plans.read_hardware import (
read_hardware_plan,
)
from mx_bluesky.common.parameters.constants import (
DocDescriptorNames,
GridscanParamConstants,
PlanGroupCheckpointConstants,
PlanNameConstants,
)
from mx_bluesky.common.parameters.device_composites import FlyScanEssentialDevices
from mx_bluesky.common.parameters.gridscan import SpecifiedThreeDGridScan
from mx_bluesky.common.utils.exceptions import (
CrystalNotFoundError,
SampleError,
)
from mx_bluesky.common.utils.log import LOGGER
from mx_bluesky.common.utils.tracing import TRACER
from mx_bluesky.common.xrc_result import XRayCentreResult


@dataclasses.dataclass
Expand All @@ -55,26 +45,19 @@ class BeamlineSpecificFGSFeatures:
..., MsgGenerator
] # Eventually replace with https://github.com/DiamondLightSource/mx-bluesky/issues/819
read_during_collection_plan: Callable[..., MsgGenerator]
get_xrc_results_from_zocalo: bool


def generic_tidy(xrc_composite: FlyScanEssentialDevices, wait=True) -> MsgGenerator:
"""Tidy Zocalo and turn off Eiger dev/shm. Ran after the beamline-specific tidy plan"""

LOGGER.info("Tidying up Zocalo")
group = "generic_tidy"
# make sure we don't consume any other results
yield from bps.unstage(xrc_composite.zocalo, group=group)
def tidy_eiger(eiger: EigerDetector) -> MsgGenerator:
"""Turn off Eiger dev/shm. Ran after the beamline-specific tidy plan"""

# Turn off dev/shm streaming to avoid filling disk, see https://github.com/DiamondLightSource/hyperion/issues/1395
LOGGER.info("Turning off Eiger dev/shm streaming")
# Fix types in ophyd-async (https://github.com/DiamondLightSource/mx-bluesky/issues/855)
yield from bps.abs_set(
xrc_composite.eiger.odin.fan.dev_shm_enable, # type: ignore
eiger.odin.fan.dev_shm_enable, # type: ignore
0,
group=group,
wait=True,
)
yield from bps.wait(group)


def construct_beamline_specific_fast_gridscan_features(
Expand All @@ -84,7 +67,6 @@ def construct_beamline_specific_fast_gridscan_features(
fgs_motors: FastGridScanCommon,
signals_to_read_pre_flyscan: list[Readable],
signals_to_read_during_collection: list[Readable],
get_xrc_results_from_zocalo: bool = False,
) -> BeamlineSpecificFGSFeatures:
"""Construct the class needed to do beamline-specific parts of the XRC FGS

Expand All @@ -104,9 +86,6 @@ def construct_beamline_specific_fast_gridscan_features(

signals_to_read_during_collection (Callable): Signals which will be read and saved as a bluesky event
document whilst the gridscan motion is in progress

get_xrc_results_from_zocalo (bool): If true, fetch grid scan results from zocalo after completion, as well as
update the ispyb comment field with information about the results. See _fetch_xrc_results_from_zocalo
"""
read_pre_flyscan_plan = partial(
read_hardware_plan,
Expand All @@ -127,7 +106,6 @@ def construct_beamline_specific_fast_gridscan_features(
fgs_motors,
read_pre_flyscan_plan,
read_during_collection_plan,
get_xrc_results_from_zocalo,
)


Expand Down Expand Up @@ -159,7 +137,7 @@ def common_flyscan_xray_centre(

def _overall_tidy():
yield from beamline_specific.tidy_plan()
yield from generic_tidy(composite)
yield from tidy_eiger(composite.eiger)

def _decorated_flyscan():
@bpp.set_run_key_decorator(PlanNameConstants.GRIDSCAN_OUTER)
Expand All @@ -181,81 +159,16 @@ def run_gridscan_and_tidy(
yield from beamline_specific.setup_trigger_plan(fgs_composite, parameters)

LOGGER.info("Starting grid scan")
yield from bps.stage(
fgs_composite.zocalo, group=ZOCALO_STAGE_GROUP
) # connect to zocalo and make sure the queue is clear
yield from run_gridscan(fgs_composite, params, beamline_specific)

LOGGER.info("Grid scan finished")

if beamline_specific.get_xrc_results_from_zocalo:
yield from _fetch_xrc_results_from_zocalo(composite.zocalo, parameters)

yield from run_gridscan_and_tidy(composite, parameters, beamline_specific)

composite.eiger.set_detector_parameters(parameters.detector_params)
yield from _decorated_flyscan()


def _fetch_xrc_results_from_zocalo(
zocalo_results: ZocaloResults,
parameters: SpecifiedThreeDGridScan,
) -> MsgGenerator:
"""
Get XRC results from the ZocaloResults device which was staged during a grid scan,
and store them in XRayCentreEventHandler.xray_centre_results by firing an event.

The RunEngine must be subscribed to XRayCentreEventHandler for this plan to work.
"""

LOGGER.info("Getting X-ray center Zocalo results...")

yield from bps.trigger(zocalo_results, wait=True)
LOGGER.info("Zocalo triggered and read, interpreting results.")
xrc_results = yield from get_full_processing_results(zocalo_results)
LOGGER.info(f"Got xray centres, top 5: {xrc_results[:5]}")
filtered_results = [
result
for result in xrc_results
if result["total_count"]
>= GridscanParamConstants.ZOCALO_MIN_TOTAL_COUNT_THRESHOLD
]
discarded_count = len(xrc_results) - len(filtered_results)
if discarded_count > 0:
LOGGER.info(f"Removed {discarded_count} results because below threshold")
if filtered_results:
flyscan_results = [
_xrc_result_in_boxes_to_result_in_mm(xr, parameters)
for xr in filtered_results
]
else:
commissioning_mode = yield from read_commissioning_mode()
if commissioning_mode:
LOGGER.info("Commissioning mode enabled, returning dummy result")
flyscan_results = [_generate_dummy_xrc_result(parameters)]
else:
LOGGER.warning("No X-ray centre received")
raise CrystalNotFoundError()
yield from _fire_xray_centre_result_event(flyscan_results)


def _generate_dummy_xrc_result(params: SpecifiedThreeDGridScan) -> XRayCentreResult:
com = [params.x_steps / 2, params.y_steps / 2, params.z_steps / 2]
max_voxel = [round(p) for p in com]
return _xrc_result_in_boxes_to_result_in_mm(
XrcResult(
centre_of_mass=com,
max_voxel=max_voxel,
bounding_box=[max_voxel, [p + 1 for p in max_voxel]],
n_voxels=1,
max_count=10000,
total_count=100000,
sample_id=params.sample_id,
),
params,
)


@bpp.set_run_key_decorator(PlanNameConstants.GRIDSCAN_MAIN)
@bpp.run_decorator(md={"subplan_name": PlanNameConstants.GRIDSCAN_MAIN})
def run_gridscan(
Expand Down Expand Up @@ -298,49 +211,3 @@ def run_gridscan(
# in a GDA-happy state.
if isinstance(beamline_specific.fgs_motors, FastGridScanThreeD):
yield from bps.abs_set(beamline_specific.fgs_motors.z_steps, 0, wait=False)


def _xrc_result_in_boxes_to_result_in_mm(
xrc_result: XrcResult, parameters: SpecifiedThreeDGridScan
) -> XRayCentreResult:
fgs_params = parameters.fast_gridscan_params
xray_centre = fgs_params.grid_position_to_motor_position(
np.array(xrc_result["centre_of_mass"])
)
# A correction is applied to the bounding box to map discrete grid coordinates to
# the corners of the box in motor-space; we do not apply this correction
# to the xray-centre as it is already in continuous space and the conversion has
# been performed already
# In other words, xrc_result["bounding_box"] contains the position of the box centre,
# so we subtract half a box to get the corner of the box
return XRayCentreResult(
centre_of_mass_mm=xray_centre,
bounding_box_mm=(
fgs_params.grid_position_to_motor_position(
np.array(xrc_result["bounding_box"][0]) - 0.5
),
fgs_params.grid_position_to_motor_position(
np.array(xrc_result["bounding_box"][1]) - 0.5
),
),
max_count=xrc_result["max_count"],
total_count=xrc_result["total_count"],
sample_id=xrc_result["sample_id"],
)


def _fire_xray_centre_result_event(results: Sequence[XRayCentreResult]):
def empty_plan():
return iter([])

yield from bpp.set_run_key_wrapper(
bpp.run_wrapper(
empty_plan(),
md={
PlanNameConstants.FLYSCAN_RESULTS: [
dataclasses.asdict(r) for r in results
]
},
),
PlanNameConstants.FLYSCAN_RESULTS,
)
Loading