Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
0c29c8f
Add Mbs driver and region
oliwenmandiamond May 12, 2026
4a6ac47
Fix Mbs AcquisitionMode enums
oliwenmandiamond May 13, 2026
c35e8dd
Make PSU mode read only by default, writable only for specs
oliwenmandiamond May 13, 2026
2784921
Remove psu from driver, make it optionally a str type, add static cla…
oliwenmandiamond May 13, 2026
2c4a538
Update doc string for abstract driver
oliwenmandiamond May 13, 2026
b89996d
Remove Mbs PSU enum
oliwenmandiamond May 13, 2026
6cb19d8
Rename mbs_driver to mbs_driver_io and add __init__.py imports.
oliwenmandiamond May 13, 2026
ee98ea1
Add Mbs to i05-1
oliwenmandiamond May 13, 2026
6a29c43
Fix tests
oliwenmandiamond May 14, 2026
0403a03
Add load from xml for mbs
oliwenmandiamond May 14, 2026
79280eb
Add test for mbs region from_xml
oliwenmandiamond May 15, 2026
fd10816
Update data_util to be more generic to allow use with xml
oliwenmandiamond May 15, 2026
60651d1
Convert mbs energy to eV
oliwenmandiamond May 15, 2026
13a5c82
Add dynamic region testing
oliwenmandiamond May 18, 2026
761f5cb
Update tests to use regions from sequence directly
oliwenmandiamond May 19, 2026
78e01ab
Merge branch 'main' into add_mbs_analyser
oliwenmandiamond May 19, 2026
48e7345
Convert remaining tests to use new pair method
oliwenmandiamond May 19, 2026
e403e09
Add additional test for mbs sequence
oliwenmandiamond May 19, 2026
1fd6f51
Add tests for mbs driver
oliwenmandiamond May 19, 2026
7270eda
Merge branch 'main' into add_mbs_analyser
oliwenmandiamond May 19, 2026
199d5d9
Added remaining mbs pvs
oliwenmandiamond May 19, 2026
80e89a3
Remove spectrum and image pvs until we figure out how to use with sta…
oliwenmandiamond May 20, 2026
c616463
Update data util to use callable rather than protocol
oliwenmandiamond May 20, 2026
2e59d79
Add MbsDetector, remove unneeded EnergySource logic
oliwenmandiamond May 22, 2026
2c3832b
Fix tests
oliwenmandiamond May 22, 2026
14faa96
Add Mbs detector test
oliwenmandiamond May 22, 2026
a869e94
Add frozen to dataclass
oliwenmandiamond May 22, 2026
43db25a
Merge branch 'add_mbs_analyser' into mbs_detector
oliwenmandiamond May 22, 2026
620e378
Merge branch 'main' into add_mbs_analyser
oliwenmandiamond May 22, 2026
c784668
Fix tests
oliwenmandiamond May 22, 2026
4c76c23
Move slices from BaseRegion to classes that use as Mbs doesn't use it.
oliwenmandiamond May 22, 2026
b434f3c
Merge branch 'add_mbs_analyser' of ssh://github.com/DiamondLightSourc…
oliwenmandiamond May 22, 2026
42e2b99
Merge branch 'add_mbs_analyser' into mbs_detector
oliwenmandiamond May 22, 2026
1ad7bfe
Merge branch 'main' into add_mbs_analyser
oliwenmandiamond May 26, 2026
d2ab778
Remove unused function
oliwenmandiamond May 26, 2026
24efdfd
Merge branch 'add_mbs_analyser' of ssh://github.com/DiamondLightSourc…
oliwenmandiamond May 26, 2026
eb7d117
Merge branch 'add_mbs_analyser' into mbs_detector
oliwenmandiamond May 26, 2026
7f41c80
Merge branch 'main' into mbs_detector
oliwenmandiamond May 26, 2026
a1f7aaa
Merge branch 'mbs_detector' of ssh://github.com/DiamondLightSource/do…
oliwenmandiamond May 26, 2026
ffa508e
Merge branch 'main' into mbs_detector
oliwenmandiamond May 29, 2026
d0430af
Merge branch 'main' into mbs_detector
oliwenmandiamond May 29, 2026
153fde4
Merge branch 'mbs_detector' of ssh://github.com/DiamondLightSource/do…
oliwenmandiamond May 29, 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
10 changes: 2 additions & 8 deletions src/dodal/beamlines/b07.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
LensMode,
)
from dodal.devices.beamlines.b07_shared import PsuMode
from dodal.devices.electron_analyser.base import EnergySource
from dodal.devices.electron_analyser.specs import SpecsDetector
from dodal.devices.hutch_shutter import (
EXP_SHUTTER_2_INFIX,
Expand Down Expand Up @@ -48,20 +47,15 @@ def pgm() -> PlaneGratingMonochromator:
)


@devices.factory()
def energy_source(pgm: PlaneGratingMonochromator) -> EnergySource:
return EnergySource(pgm.energy.user_readback)


# CAM:IMAGE will fail to connect outside the beamline network,
# see https://github.com/DiamondLightSource/dodal/issues/1852
@devices.factory()
def analyser(energy_source: EnergySource) -> SpecsDetector[LensMode, PsuMode]:
def analyser(pgm: PlaneGratingMonochromator) -> SpecsDetector[LensMode, PsuMode]:
return SpecsDetector[LensMode, PsuMode](
prefix=f"{B_PREFIX.beamline_prefix}-EA-DET-01:CAM:",
lens_mode_type=LensMode,
psu_mode_type=PsuMode,
energy_source=energy_source,
energy_source=pgm.energy.user_readback,
)


Expand Down
10 changes: 2 additions & 8 deletions src/dodal/beamlines/b07_1.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
LensMode,
)
from dodal.devices.beamlines.b07_shared import PsuMode
from dodal.devices.electron_analyser.base import EnergySource
from dodal.devices.electron_analyser.specs import SpecsDetector
from dodal.devices.hutch_shutter import HutchShutter
from dodal.devices.motors import XYZAzimuthPolarStage
Expand Down Expand Up @@ -42,20 +41,15 @@ def ccmc() -> ChannelCutMonochromator:
return ChannelCutMonochromator(prefix=f"{C_PREFIX.beamline_prefix}-OP-CCM-01:")


@devices.factory()
def energy_source(pgm: PlaneGratingMonochromator) -> EnergySource:
return EnergySource(pgm.energy.user_readback)


# CAM:IMAGE will fail to connect outside the beamline network,
# see https://github.com/DiamondLightSource/dodal/issues/1852
@devices.factory()
def analyser(energy_source: EnergySource) -> SpecsDetector[LensMode, PsuMode]:
def analyser(pgm: PlaneGratingMonochromator) -> SpecsDetector[LensMode, PsuMode]:
return SpecsDetector[LensMode, PsuMode](
prefix=f"{C_PREFIX.beamline_prefix}-EA-DET-01:CAM:",
lens_mode_type=LensMode,
psu_mode_type=PsuMode,
energy_source=energy_source,
energy_source=pgm.energy.user_readback,
)


Expand Down
8 changes: 5 additions & 3 deletions src/dodal/beamlines/i05.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
from dodal.devices.beamlines.i05 import I05Goniometer
from dodal.devices.beamlines.i05_shared import LensMode, M4M5Mirror, PassEnergy
from dodal.devices.common_mirror import XYZSwitchingMirror
from dodal.devices.electron_analyser.mbs import MbsAnalyserDriverIO
from dodal.devices.electron_analyser.mbs import MbsDetector
from dodal.devices.hutch_shutter import HutchShutter
from dodal.devices.pgm import PlaneGratingMonochromator
from dodal.devices.temperture_controller import Lakeshore336
from dodal.log import set_beamline as set_log_beamline
from dodal.utils import BeamlinePrefix, get_beamline_name
Expand Down Expand Up @@ -50,9 +51,10 @@ def sa() -> I05Goniometer:


@devices.factory
def analyser_driver() -> MbsAnalyserDriverIO:
return MbsAnalyserDriverIO[LensMode, PassEnergy](
def analyser(pgm: PlaneGratingMonochromator) -> MbsDetector[LensMode, PassEnergy]:
return MbsDetector[LensMode, PassEnergy](
prefix=f"{PREFIX.beamline_prefix}-EA-DET-02:CAM:",
lens_mode_type=LensMode,
pass_energy_type=PassEnergy,
energy_source=pgm.energy.user_readback,
)
8 changes: 5 additions & 3 deletions src/dodal/beamlines/i05_1.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
from dodal.devices.beamlines.i05_1 import XYZAzimuthPolarDefocusStage
from dodal.devices.beamlines.i05_shared import LensMode, Mj7j8Mirror, PassEnergy
from dodal.devices.common_mirror import XYZPiezoSwitchingMirror
from dodal.devices.electron_analyser.mbs import MbsAnalyserDriverIO
from dodal.devices.electron_analyser.mbs import MbsDetector
from dodal.devices.hutch_shutter import HutchShutter
from dodal.devices.pgm import PlaneGratingMonochromator
from dodal.log import set_beamline as set_log_beamline
from dodal.utils import BeamlinePrefix, get_beamline_name

Expand Down Expand Up @@ -39,9 +40,10 @@ def sm() -> XYZAzimuthPolarDefocusStage:


@devices.factory
def analyser_driver() -> MbsAnalyserDriverIO[LensMode, PassEnergy]:
return MbsAnalyserDriverIO[LensMode, PassEnergy](
def analyser(pgm: PlaneGratingMonochromator) -> MbsDetector[LensMode, PassEnergy]:
return MbsDetector[LensMode, PassEnergy](
prefix=f"{PREFIX.beamline_prefix}-EA-DET-04:CAM:",
lens_mode_type=LensMode,
pass_energy_type=PassEnergy,
energy_source=pgm.energy.user_readback,
)
2 changes: 1 addition & 1 deletion src/dodal/beamlines/i09.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ def ew4000(
lens_mode_type=LensMode,
psu_mode_type=PsuMode,
pass_energy_type=PassEnergy,
energy_source=dual_energy_source,
energy_source=dual_energy_source.energy,
shutter=dual_fast_shutter,
source_selector=source_selector,
)
Expand Down
12 changes: 4 additions & 8 deletions src/dodal/beamlines/i09_1.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from dodal.device_manager import DeviceManager
from dodal.devices.beamlines.i09_1 import LensMode, PsuMode
from dodal.devices.common_dcm import DoubleCrystalMonochromatorWithDSpacing
from dodal.devices.electron_analyser.base import EnergySource
from dodal.devices.electron_analyser.specs import SpecsDetector
from dodal.devices.motors import XYZAzimuthTiltPolarStage
from dodal.devices.synchrotron import Synchrotron
Expand All @@ -25,20 +24,17 @@ def synchrotron() -> Synchrotron:
return Synchrotron()


@devices.factory()
def energy_source(dcm: DoubleCrystalMonochromatorWithDSpacing) -> EnergySource:
return EnergySource(dcm.energy_in_eV)


# CAM:IMAGE will fail to connect outside the beamline network,
# see https://github.com/DiamondLightSource/dodal/issues/1852
@devices.factory()
def analyser(energy_source: EnergySource) -> SpecsDetector[LensMode, PsuMode]:
def analyser(
dcm: DoubleCrystalMonochromatorWithDSpacing,
) -> SpecsDetector[LensMode, PsuMode]:
return SpecsDetector[LensMode, PsuMode](
prefix=f"{PREFIX.beamline_prefix}-EA-DET-02:CAM:",
lens_mode_type=LensMode,
psu_mode_type=PsuMode,
energy_source=energy_source,
energy_source=dcm.energy_in_eV,
)


Expand Down
2 changes: 1 addition & 1 deletion src/dodal/beamlines/p60.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,5 +65,5 @@ def r4000(
lens_mode_type=LensMode,
psu_mode_type=PsuMode,
pass_energy_type=PassEnergy,
energy_source=energy_source,
energy_source=energy_source.energy,
)
4 changes: 1 addition & 3 deletions src/dodal/devices/electron_analyser/base/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
TLensMode,
)
from .base_util import to_binding_energy, to_kinetic_energy
from .energy_sources import AbstractEnergySource, DualEnergySource, EnergySource
from .energy_sources import DualEnergySource

__all__ = [
"ElectronAnalyserDetector",
Expand All @@ -35,7 +35,5 @@
"TLensMode",
"to_binding_energy",
"to_kinetic_energy",
"AbstractEnergySource",
"DualEnergySource",
"EnergySource",
]
10 changes: 7 additions & 3 deletions src/dodal/devices/electron_analyser/base/base_detector.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import asyncio
from collections.abc import Sequence
from typing import Generic

import numpy as np
Expand All @@ -8,6 +9,7 @@
AsyncStatus,
DetectorAcquireLogic,
DetectorTriggerLogic,
SignalR,
StandardDetector,
derived_signal_r,
)
Expand Down Expand Up @@ -63,19 +65,21 @@ def __init__(
acquire_logic: DetectorAcquireLogic,
trigger_logic: DetectorTriggerLogic,
region_logic: RegionLogic,
config_sigs: Sequence[SignalR] = (),
name: str = "",
):
self._region_logic = region_logic

self.binding_energy_axis = derived_signal_r(
self._calculate_binding_energy_axis,
"eV",
energy_axis=region_logic.driver.energy_axis,
excitation_energy=region_logic.energy_source.energy,
excitation_energy=region_logic.energy_source,
energy_mode=region_logic.driver.energy_mode,
)
self._region_logic = region_logic
# ToDo - Add data logic
self.add_detector_logics(acquire_logic, trigger_logic)
self.add_config_signals(self.binding_energy_axis)
self.add_config_signals(self.binding_energy_axis, *config_sigs)

self.sequence = SequenceHolder()
super().__init__(name)
Expand Down
5 changes: 2 additions & 3 deletions src/dodal/devices/electron_analyser/base/detector_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
TAbstractAnalyserDriverIO,
)
from dodal.devices.electron_analyser.base.base_region import BaseRegion
from dodal.devices.electron_analyser.base.energy_sources import AbstractEnergySource
from dodal.devices.fast_shutter import GenericFastShutter
from dodal.devices.selectable_source import SourceSelector

Expand Down Expand Up @@ -78,7 +77,7 @@ class RegionLogic:
def __init__(
self,
driver: AbstractAnalyserDriverIO,
energy_source: AbstractEnergySource,
energy_source: SignalR[float],
source_selector: SourceSelector | None = None,
):
self.driver = driver
Expand All @@ -90,6 +89,6 @@ async def setup_with_region(self, region: BaseRegion) -> None:
if self.source_selector is not None:
await self.source_selector.set(region.excitation_energy_source)

excitation_energy = await self.energy_source.energy.get_value()
excitation_energy = await self.energy_source.get_value()
epics_region = region.prepare_for_epics(excitation_energy)
await self.driver.set(epics_region)
64 changes: 15 additions & 49 deletions src/dodal/devices/electron_analyser/base/energy_sources.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from abc import abstractmethod

from ophyd_async.core import (
Reference,
SignalR,
Expand All @@ -13,46 +11,15 @@
from dodal.devices.selectable_source import SelectedSource, get_obj_from_selected_source


class AbstractEnergySource(StandardReadable):
"""Abstract device that wraps an energy source signal and provides common interface
via a energy signal.
"""

@property
@abstractmethod
def energy(self) -> SignalR[float]:
"""Signal to provide the excitation energy value in eV."""


class EnergySource(AbstractEnergySource):
"""Wraps a signal that relates to energy and provides common interface via energy
signal. It provides the name of the wrapped signal as a child signal in the
read_configuration via wrapped_device_name and adds the signal as a readable.
"""

def __init__(self, source: SignalR[float], name: str = "") -> None:
self.add_readables([source])
with self.add_children_as_readables(StandardReadableFormat.CONFIG_SIGNAL):
self.wrapped_device_name, _ = soft_signal_r_and_setter(
str, initial_value=source.name
)
self._source_ref = Reference(source)
super().__init__(name)

@property
def energy(self) -> SignalR[float]:
return self._source_ref()


def get_float_from_selected_source(
selected: SelectedSource, s1: float, s2: float
) -> float:
"""Wrapper function to provide type hints for derived signal."""
return get_obj_from_selected_source(selected, s1, s2)


class DualEnergySource(AbstractEnergySource):
"""Holds two EnergySource devices and provides a signal to read energy depending on
class DualEnergySource(StandardReadable):
"""Provides a signal to read energy depending on
which source is selected. The energy is the one that corrosponds to the
selected_source signal. For example, selected_source is source1 if selected_source
is at SelectedSource.SOURCE1 and vise versa for source2 and
Expand All @@ -73,21 +40,20 @@ def __init__(
name: str = "",
):
self.selected_source_ref = Reference(selected_source)
self.source1_ref = Reference(source1)
self.source2_ref = Reference(source2)
with self.add_children_as_readables():
self.source1 = EnergySource(source1)
self.source2 = EnergySource(source2)
self.energy = derived_signal_r(
get_float_from_selected_source,
"eV",
selected=selected_source,
s1=source1,
s2=source2,
)

self._selected_energy = derived_signal_r(
get_float_from_selected_source,
"eV",
selected=self.selected_source_ref(),
s1=self.source1.energy,
s2=self.source2.energy,
)
self.add_readables([selected_source])
with self.add_children_as_readables(StandardReadableFormat.CONFIG_SIGNAL):
self.source1, _ = soft_signal_r_and_setter(str, initial_value=source1.name)
self.source2, _ = soft_signal_r_and_setter(str, initial_value=source2.name)
self.add_readables([selected_source, source1, source2])

super().__init__(name)

@property
def energy(self) -> SignalR[float]:
return self._selected_energy
9 changes: 8 additions & 1 deletion src/dodal/devices/electron_analyser/mbs/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
from .mbs_detector import MbsDetector
from .mbs_driver_io import MbsAnalyserDriverIO
from .mbs_enums import AcquisitionMode
from .mbs_region import MbsRegion, MbsSequence

__all__ = ["MbsAnalyserDriverIO", "AcquisitionMode", "MbsRegion", "MbsSequence"]
__all__ = [
"MbsDetector",
"MbsAnalyserDriverIO",
"AcquisitionMode",
"MbsRegion",
"MbsSequence",
]
Loading
Loading