Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
a94ab98
WIP begin making callbacks generic
olliesilvester Feb 9, 2026
5a2c51f
wip
olliesilvester Feb 11, 2026
0a84a71
wip
olliesilvester Feb 11, 2026
40fa5c4
wip
olliesilvester Feb 11, 2026
0ff8cc8
Merge remote-tracking branch 'origin/main' into 364_make_nexus_callba…
olliesilvester Feb 12, 2026
0fa10d9
Some fixes
olliesilvester Feb 12, 2026
757ac75
Partially fix nexus tests
olliesilvester Feb 12, 2026
145a648
Fix nexus tests
olliesilvester Feb 16, 2026
e63677e
fix test
olliesilvester Feb 16, 2026
c2a232d
fix typing
olliesilvester Feb 16, 2026
860562b
Address some todos and add validator
olliesilvester Feb 16, 2026
0070405
Merge branch 'main' into 364_make_nexus_callbacks_generic
olliesilvester Feb 17, 2026
ab8fd89
Merge branch 'main' into 364_make_nexus_callbacks_generic
olliesilvester Feb 18, 2026
7c037bb
Fixes from merge
olliesilvester Feb 18, 2026
bf9449f
Add test
olliesilvester Feb 18, 2026
16885da
Remove SingleGrid class
olliesilvester Feb 18, 2026
d856a8c
don't implement dummy mode xrc results for 2d grids
olliesilvester Feb 18, 2026
a31185d
Link to issue in comments
olliesilvester Feb 18, 2026
20d1d60
Merge remote-tracking branch 'origin/main' into 364_make_nexus_callba…
olliesilvester Mar 2, 2026
668c8e0
Keep up to date
olliesilvester Mar 2, 2026
0e68e2b
Add vmxm FGS entry point
olliesilvester Feb 18, 2026
945cb4c
Fixes and tests
olliesilvester Feb 18, 2026
4a93d73
Typo
olliesilvester Feb 18, 2026
6198594
make codecov happy
olliesilvester Feb 18, 2026
638a8c1
improve tests
olliesilvester Feb 19, 2026
0e03d9b
Merge remote-tracking branch 'origin/main' into 364_make_nexus_callba…
rtuck99 Apr 23, 2026
f388814
Merge remote-tracking branch 'origin/main' into 364_make_nexus_callba…
rtuck99 May 5, 2026
28cff71
Update uv.lock
rtuck99 May 5, 2026
176d60b
Tidy gridscan plantUML
rtuck99 May 5, 2026
f36a3f3
Merge branch '364_make_nexus_callbacks_generic' into add_vmxm_gridsca…
rtuck99 May 5, 2026
e5dcb4e
Make ruff happy
rtuck99 May 13, 2026
7ede7d8
Update uv.lock
rtuck99 May 13, 2026
db8fd0f
Merge remote-tracking branch 'origin/main' into add_vmxm_gridscan_plan
rtuck99 May 13, 2026
a6e89af
Rename composite as suggested
rtuck99 May 13, 2026
eeb623c
Merge remote-tracking branch 'origin/main' into add_vmxm_gridscan_plan
rtuck99 May 13, 2026
d0a8f0c
Unpin dodal
rtuck99 May 13, 2026
dfc3863
Add todo
rtuck99 May 13, 2026
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
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ dependencies = [
"ophyd >= 1.10.5",
"ophyd-async >= 0.16.0",
"bluesky >= 1.14.6",
"dls-dodal @ git+https://github.com/DiamondLightSource/dodal.git@2024_decouple_interlock_from_shutter",
"dls-dodal @ git+https://github.com/DiamondLightSource/dodal.git@main",
]


Expand Down
Empty file.
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import bluesky.plan_stubs as bps
from dodal.devices.zebra.zebra import Zebra

from mx_bluesky.common.parameters.constants import PlanGroupCheckpointConstants

ZEBRA_STATUS_TIMEOUT = 30


# Control Eiger from motion controller. Fast shutter is configured in GDA
def setup_zebra_for_gridscan(
zebra: Zebra,
ttl_detector: int | None = None,
group=PlanGroupCheckpointConstants.SETUP_ZEBRA_FOR_GRIDSCAN,
wait=True,
):
"""
Assumes that the motion controller, as part of its gridscan PLC, will send triggers as required to the zebra's
IN1_TTL to control the detector. The fast shutter is configured in GDA, don't need to touch it in Bluesky for now.
"""
ttl_detector = ttl_detector or zebra.mapping.outputs.TTL_EIGER
yield from bps.abs_set(
zebra.output.out_pvs[ttl_detector],
zebra.mapping.sources.IN1_TTL,
)
if wait:
yield from bps.wait(group, timeout=ZEBRA_STATUS_TIMEOUT)


def tidy_up_zebra_after_gridscan(
zebra: Zebra,
ttl_detector: int | None = None,
group=PlanGroupCheckpointConstants.TIDY_ZEBRA_AFTER_GRIDSCAN,
wait=False,
):
"""Revert zebra to state expected by GDA"""
ttl_detector = ttl_detector or zebra.mapping.outputs.TTL_EIGER

yield from bps.abs_set(
zebra.output.out_pvs[ttl_detector],
zebra.mapping.sources.OR1,
group=group,
)

if wait:
yield from bps.wait(group)
138 changes: 138 additions & 0 deletions src/mx_bluesky/beamlines/i02_1/i02_1_gridscan_plan.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
from functools import partial

import bluesky.preprocessors as bpp
import pydantic
from bluesky.utils import MsgGenerator
from dodal.beamlines.i02_1 import ZebraFastGridScanTwoD
from dodal.common import inject
from dodal.devices.attenuator.attenuator import ReadOnlyAttenuator
from dodal.devices.common_dcm import DoubleCrystalMonochromatorBase
from dodal.devices.fast_grid_scan import (
set_fast_grid_scan_params as set_flyscan_params_plan,
)
from dodal.devices.flux import Flux
from dodal.devices.s4_slit_gaps import S4SlitGaps
from dodal.devices.undulator import BaseUndulator
from dodal.devices.zebra.zebra import Zebra

from mx_bluesky.beamlines.i02_1.device_setup_plans.setup_zebra import (
setup_zebra_for_gridscan,
tidy_up_zebra_after_gridscan,
)
from mx_bluesky.beamlines.i02_1.parameters.gridscan import SpecifiedTwoDGridScan
from mx_bluesky.common.experiment_plans.common_flyscan_xray_centre_plan import (
BeamlineSpecificFGSFeatures,
common_flyscan_xray_centre,
construct_beamline_specific_fast_gridscan_features,
)
from mx_bluesky.common.external_interaction.callbacks.common.zocalo_callback import (
ZocaloCallback,
)
from mx_bluesky.common.external_interaction.callbacks.xray_centre.ispyb_callback import (
GridscanISPyBCallback,
generate_start_info_from_omega_map,
)
from mx_bluesky.common.external_interaction.callbacks.xray_centre.nexus_callback import (
GridscanNexusFileCallback,
)
from mx_bluesky.common.parameters.constants import (
EnvironmentConstants,
PlanNameConstants,
)
from mx_bluesky.common.parameters.device_composites import (
FlyScanEssentialDevices,
GonioWithOmegaType,
)
from mx_bluesky.common.parameters.gridscan import GenericGrid
from mx_bluesky.common.utils.log import LOGGER


def create_gridscan_callbacks() -> tuple[
GridscanNexusFileCallback, GridscanISPyBCallback
]:
return (
GridscanNexusFileCallback(param_type=SpecifiedTwoDGridScan),
GridscanISPyBCallback(
param_type=GenericGrid,
emit=ZocaloCallback(
PlanNameConstants.DO_FGS,
EnvironmentConstants.ZOCALO_ENV,
generate_start_info_from_omega_map,
),
),
)


@pydantic.dataclasses.dataclass(config={"arbitrary_types_allowed": True})
class I021FlyScanXRayCentreComposite(FlyScanEssentialDevices[GonioWithOmegaType]):
"""All devices which are directly or indirectly required by this plan"""

zebra: Zebra
zebra_fast_grid_scan: ZebraFastGridScanTwoD
dcm: DoubleCrystalMonochromatorBase
attenuator: ReadOnlyAttenuator
flux: Flux
undulator: BaseUndulator
s4_slit_gaps: S4SlitGaps


def construct_i02_1_specific_features(
fgs_composite: I021FlyScanXRayCentreComposite,
parameters: SpecifiedTwoDGridScan,
) -> BeamlineSpecificFGSFeatures:
signals_to_read_pre_flyscan = [
fgs_composite.synchrotron.synchrotron_mode,
fgs_composite.gonio,
fgs_composite.dcm.energy_in_keV,
fgs_composite.undulator.current_gap,
fgs_composite.s4_slit_gaps,
]

signals_to_read_during_collection = [
fgs_composite.attenuator.actual_transmission,
fgs_composite.flux.flux_reading,
fgs_composite.dcm.energy_in_keV,
fgs_composite.eiger.bit_depth,
fgs_composite.eiger.cam.roi_mode,
fgs_composite.eiger.ispyb_detector_id,
]

return construct_beamline_specific_fast_gridscan_features(
partial(_zebra_triggering_setup),
partial(_tidy_plan, fgs_composite, group="flyscan_zebra_tidy", wait=True),
partial(
set_flyscan_params_plan,
fgs_composite.zebra_fast_grid_scan,
parameters.fast_gridscan_params,
),
fgs_composite.zebra_fast_grid_scan,
signals_to_read_pre_flyscan,
signals_to_read_during_collection, # type: ignore # See : https://github.com/bluesky/bluesky/issues/1809
)


def _zebra_triggering_setup(fgs_composite: I021FlyScanXRayCentreComposite, _):
yield from setup_zebra_for_gridscan(fgs_composite.zebra)


def _tidy_plan(
fgs_composite: I021FlyScanXRayCentreComposite, group, wait=True
) -> MsgGenerator:
LOGGER.info("Tidying up Zebra")
yield from tidy_up_zebra_after_gridscan(fgs_composite.zebra)


def i02_1_gridscan_plan(
parameters: SpecifiedTwoDGridScan,
composite: I021FlyScanXRayCentreComposite = inject(""),
) -> MsgGenerator:
"""BlueAPI entry point for i02-1 grid scans"""

beamline_specific = construct_i02_1_specific_features(composite, parameters)
callbacks = create_gridscan_callbacks()

@bpp.subs_decorator(callbacks)
def decorated_flyscan_plan():
yield from common_flyscan_xray_centre(composite, parameters, beamline_specific)

yield from decorated_flyscan_plan()
15 changes: 14 additions & 1 deletion src/mx_bluesky/beamlines/i02_1/parameters/gridscan.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from dodal.devices.beamlines.i02_1.fast_grid_scan import ZebraGridScanParamsTwoD
from pydantic import model_validator

from mx_bluesky.common.parameters.components import SplitScan, WithOptionalEnergyChange
from mx_bluesky.common.parameters.gridscan import SpecifiedGrids
Expand All @@ -24,5 +25,17 @@ def fast_gridscan_params(self) -> ZebraGridScanParamsTwoD:
z1_start_mm=self.z_starts_um[0] / 1000,
set_stub_offsets=self._set_stub_offsets,
transmission_fraction=0.5,
dwell_time_ms=self.exposure_time_s,
dwell_time_ms=self.exposure_time_s * 1000,
)

@model_validator(mode="after")
def validate_y_axes(self):
_err_str = "must be length 1 for 2D scans"
if len(self.y_steps) != 1:
raise ValueError(f"{self.y_steps=} {_err_str}")
if len(self.y_step_sizes_um) != 1:
raise ValueError(f"{self.y_step_sizes_um=} {_err_str}")
if len(self.omega_starts_deg) != 1:
raise ValueError(f"{self.omega_starts_deg=} {_err_str}")

return self
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@
FlyScanEssentialDevices,
GonioWithOmegaType,
)
from mx_bluesky.common.parameters.gridscan import SpecifiedThreeDGridScan
from mx_bluesky.common.parameters.gridscan import (
SpecifiedGrids,
)
from mx_bluesky.common.utils.exceptions import (
SampleError,
)
Expand Down Expand Up @@ -114,7 +116,7 @@ def construct_beamline_specific_fast_gridscan_features(

def common_flyscan_xray_centre(
composite: FlyScanEssentialDevices[GonioWithOmegaType],
parameters: SpecifiedThreeDGridScan,
parameters: SpecifiedGrids,
beamline_specific: BeamlineSpecificFGSFeatures,
) -> MsgGenerator:
"""Main entry point of the MX-Bluesky x-ray centering flyscan
Expand Down Expand Up @@ -156,7 +158,7 @@ def _decorated_flyscan():
@bpp.finalize_decorator(lambda: _overall_tidy())
def run_gridscan_and_tidy(
fgs_composite: FlyScanEssentialDevices[GonioWithOmegaType],
params: SpecifiedThreeDGridScan,
params: SpecifiedGrids,
beamline_specific: BeamlineSpecificFGSFeatures,
) -> MsgGenerator:
yield from beamline_specific.setup_trigger_plan(fgs_composite, parameters)
Expand All @@ -174,12 +176,15 @@ def run_gridscan_and_tidy(

def run_gridscan(
fgs_composite: FlyScanEssentialDevices[GonioWithOmegaType],
parameters: SpecifiedThreeDGridScan,
parameters: SpecifiedGrids,
beamline_specific: BeamlineSpecificFGSFeatures,
):
# Currently gridscan only works for omega 0, see https://github.com/DiamondLightSource/mx-bluesky/issues/410
with TRACER.start_span("moving_omega_to_0"):
yield from bps.abs_set(fgs_composite.gonio.wrapped_omega.phase, 0)
yield from bps.abs_set(
fgs_composite.gonio.wrapped_omega.phase,
parameters.omega_starts_deg[0],
wait=True,
)

with TRACER.start_span("ispyb_hardware_readings"):
yield from beamline_specific.read_pre_flyscan_plan()
Expand Down
2 changes: 2 additions & 0 deletions src/mx_bluesky/common/parameters/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,8 @@ class PlanGroupCheckpointConstants:
READY_FOR_OAV = "ready_for_oav"
PREPARE_APERTURE = "prepare_aperture"
SETUP_ZEBRA_FOR_ROTATION = "setup_zebra_for_rotation"
SETUP_ZEBRA_FOR_GRIDSCAN = "setup_zebra_for_gridscan"
TIDY_ZEBRA_AFTER_GRIDSCAN = "tidy_zebra_after_gridscan"


# Eventually replace below with https://github.com/DiamondLightSource/mx-bluesky/issues/798
Expand Down
8 changes: 6 additions & 2 deletions src/mx_bluesky/common/parameters/gridscan.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from typing import Annotated, Generic, TypeVar

from dodal.devices.aperturescatterguard import ApertureValue
from dodal.devices.detector.det_dim_constants import EIGER2_X_9M_SIZE, EIGER2_X_16M_SIZE
from dodal.devices.detector.det_dim_constants import EIGER2_X_4M_SIZE, EIGER2_X_16M_SIZE
from dodal.devices.detector.detector import DetectorParams
from dodal.devices.fast_grid_scan import (
GridScanParamsCommon,
Expand All @@ -31,9 +31,10 @@
HardwareConstants,
)

# TODO Remove this https://github.com/DiamondLightSource/mx-bluesky/issues/1748
DETECTOR_SIZE_PER_BEAMLINE = {
"test": EIGER2_X_16M_SIZE,
"i02-1": EIGER2_X_9M_SIZE,
"i02-1": EIGER2_X_4M_SIZE,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

"dev": EIGER2_X_16M_SIZE,
"i03": EIGER2_X_16M_SIZE,
"i04": EIGER2_X_16M_SIZE,
Expand Down Expand Up @@ -250,6 +251,9 @@ def validate_y_and_z_axes(self):
raise ValueError(f"{self.y_steps=} {_err_str}")
if len(self.y_step_sizes_um) != 2:
raise ValueError(f"{self.y_step_sizes_um=} {_err_str}")
if len(self.omega_starts_deg) != 2:
raise ValueError(f"{self.omega_starts_deg=} {_err_str}")

return self

@property
Expand Down
Empty file.
Loading
Loading