Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

949 make ophyd devices for the diagonstics for i10 #960

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
54 changes: 19 additions & 35 deletions src/dodal/beamlines/i10.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
UndulatorPhaseAxes,
)
from dodal.devices.current_amplifiers import CurrentAmpDet
from dodal.devices.i10.diagnostics import I10Diagnostic, I10Diagnotic5ADet
from dodal.devices.i10.i10_apple2 import (
I10Apple2,
I10Apple2PGM,
Expand All @@ -24,10 +25,9 @@
PinHole,
)
from dodal.devices.i10.rasor.rasor_scaler_cards import RasorScalerCard1
from dodal.devices.i10.slits import I10PrimarySlits, I10Slits
from dodal.devices.i10.slits import I10Slits, I10SlitsDrainCurrent
from dodal.devices.motors import XYZPositioner
from dodal.devices.pgm import PGM
from dodal.devices.slits import MinimalSlits
from dodal.log import set_beamline as set_log_beamline
from dodal.utils import BeamlinePrefix, get_beamline_name

Expand Down Expand Up @@ -272,6 +272,9 @@ def idd_la_angle(
)


"""Mirrors"""


@device_factory()
def first_mirror() -> PiezoMirror:
return PiezoMirror(prefix=f"{PREFIX.beamline_prefix}-OP-COL-01:")
Expand All @@ -283,58 +286,39 @@ def switching_mirror() -> PiezoMirror:


@device_factory()
def slit_1() -> I10PrimarySlits:
return I10PrimarySlits(
prefix=f"{PREFIX.beamline_prefix}-AL-SLITS-01:",
)
def focusing_mirror() -> PiezoMirror:
return PiezoMirror(prefix=f"{PREFIX.beamline_prefix}-OP-FOCS-01:")


@device_factory()
def slit_2() -> I10Slits:
return I10Slits(
prefix=f"{PREFIX.beamline_prefix}-AL-SLITS-02:",
)
"""Optic slits"""


@device_factory()
def slit_3() -> I10Slits:
return I10Slits(
prefix=f"{PREFIX.beamline_prefix}-AL-SLITS-03:",
)


"""Rasor devices"""
def slits() -> I10Slits:
return I10Slits(prefix=f"{PREFIX.beamline_prefix}-AL-SLITS-")


@device_factory()
def focusing_mirror() -> PiezoMirror:
return PiezoMirror(prefix=f"{PREFIX.beamline_prefix}-OP-FOCS-01:")
def slits_current() -> I10SlitsDrainCurrent:
return I10SlitsDrainCurrent(prefix=f"{PREFIX.beamline_prefix}-")


@device_factory()
def slit_4() -> MinimalSlits:
return MinimalSlits(
prefix=f"{PREFIX.beamline_prefix}-AL-SLITS-04:",
x_gap="XSIZE",
y_gap="YSIZE",
)
"""Diagnostics"""


@device_factory()
def slit_5() -> I10Slits:
return I10Slits(
prefix=f"{PREFIX.beamline_prefix}-AL-SLITS-05:",
def diagnostics() -> I10Diagnostic:
return I10Diagnostic(
prefix=f"{PREFIX.beamline_prefix}-DI-",
)


@device_factory()
def slit_6() -> I10Slits:
return I10Slits(
prefix=f"{PREFIX.beamline_prefix}-AL-SLITS-06:",
)
def d5a_det() -> I10Diagnotic5ADet:
return I10Diagnotic5ADet(prefix=f"{PREFIX.beamline_prefix}-DI-")


"Rasor devices"
"""Rasor devices"""


@device_factory()
Expand Down
260 changes: 260 additions & 0 deletions src/dodal/devices/i10/diagnostics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
from bluesky.protocols import Movable
from ophyd_async.core import (
AsyncStatus,
Device,
StandardReadable,
StrictEnum,
)
from ophyd_async.core import StandardReadableFormat as Format
from ophyd_async.core._device import DeviceConnector
from ophyd_async.epics.adaravis import AravisDriverIO
from ophyd_async.epics.adcore import SingleTriggerDetector
from ophyd_async.epics.core import (
epics_signal_r,
epics_signal_rw,
)
from ophyd_async.epics.motor import Motor

from dodal.devices.current_amplifiers import (
CurrentAmpDet,
Femto3xxGainTable,
Femto3xxGainToCurrentTable,
Femto3xxRaiseTime,
FemtoDDPCA,
StruckScaler,
)


class D3DropDown(StrictEnum):
NOTHING = "Nothing"
GRID = "Grid"


class D5DropDown(StrictEnum):
CELL_IN = "Cell In"
CELL_OUT = "Cell Out"


class D5ADropDown(StrictEnum):
OUT_OF_THE_BEAM = "Out of the beam"
DIODE = "Diode"
BLADE = "Blade"
LA = "La ref"
GD = "Gd ref"
YB = "Yb ref"
GRID = "Grid"


class D6DropDown(StrictEnum):
DIODE_OUT = "Diode Out"
DIODE_IN = "Diode In"
AU_MESH = "Au Mesh"


class D7DropDown(StrictEnum):
OUT = "Out"
SHUTTER = "Shutter"


class InOutTable(StrictEnum):
MOVE_IN = "Move In"
MOVE_OUT = "Move Out"
RESET = "Reset"


class InOutReadBackTable(StrictEnum):
MOVE_IN = "Moving In"
MOVE_OUT = "Moving Out"
IN_BEAM = "In Beam"
FAULT = "Fault"
OUT_OF_BEAM = "Out of Beam"


class I10WebCamIODataType(StrictEnum):
UINT8 = "UInt8"
INT16 = "UInt16"


class DropDownStage(StandardReadable, Movable):
"""1D stage with a enum table to select positions."""

def __init__(
self,
prefix: str,
positioner_enum: type[StrictEnum],
positioner_suffix: str = "",
dropdown_pv_suffix: str = ":MP:SELECT",
name: str = "",
) -> None:
self._stage_motion = Motor(prefix=prefix + positioner_suffix)
with self.add_children_as_readables(Format.CONFIG_SIGNAL):
self.stage_drop_down = epics_signal_rw(
positioner_enum, read_pv=prefix + positioner_suffix + dropdown_pv_suffix
)
super().__init__(name=name)
self.positioner_enum = positioner_enum

@AsyncStatus.wrap
async def set(self, value: StrictEnum) -> None:
if value in self.positioner_enum:
await self.stage_drop_down.set(value=value)

Check warning on line 100 in src/dodal/devices/i10/diagnostics.py

View check run for this annotation

Codecov / codecov/patch

src/dodal/devices/i10/diagnostics.py#L99-L100

Added lines #L99 - L100 were not covered by tests
else:
raise ValueError(

Check warning on line 102 in src/dodal/devices/i10/diagnostics.py

View check run for this annotation

Codecov / codecov/patch

src/dodal/devices/i10/diagnostics.py#L102

Added line #L102 was not covered by tests
f"{value} is not an allow position. Position must be: {self.positioner_enum}"
)


class I10PneumaticStage(StandardReadable):
"""Pneumatic stage only has two real positions in or out.
Use for fluorescent screen which can be insert into the x-ray beam.
Most often use in conjunction with a webcam to locate the x-ray beam."""

def __init__(
self,
prefix: str,
name: str = "",
) -> None:
with self.add_children_as_readables(Format.HINTED_SIGNAL):
self.stage_drop_down_set = epics_signal_rw(
InOutTable,
read_pv=prefix + "CON",
)
self.stage_drop_down_readback = epics_signal_r(
InOutReadBackTable,
read_pv=prefix + "STA",
)
super().__init__(name=name)


class I10AravisDriverIO(AravisDriverIO):
"""This is the standard webcam that can be found in ophyd_async
with four extra centroid signal. There is also a change in data_type due to it being
an older/different model"""
Comment on lines +130 to +132
Copy link
Contributor

Choose a reason for hiding this comment

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

I would like @coretl's opinion on this approach, whether we should make a configurable "super detector" in ophyd-async or accept variations like these

Copy link
Collaborator

Choose a reason for hiding this comment

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

This should be rolled into ophyd-async AravisDetector. Since bluesky/ophyd-async#606 this should now be possible. We pass stats as a plugin to the init. I'm unsure why the datatype change was needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The issue for datatype is without changing it I with get the follow error as datatype is strict enum drv: NotConnected: data_type: TypeError: BL10I-DI-PHDGN-01:DCAM:CAM:DataType_RBV has choices ('UInt8', 'UInt16'), but <enum 'ADBaseDataType'> requested ['Int8', 'UInt8', 'Int16', 'UInt16', 'Int32', 'UInt32', 'Int64', 'UInt64', 'Float32', 'Float64'] to be strictly equal to them.


def __init__(
self,
prefix: str,
cam_infix: str = "CAM:",
name: str = "",
) -> None:
super().__init__(prefix + cam_infix, name)

# data type correction for i10 model.
self.data_type = epics_signal_r(
I10WebCamIODataType, prefix + cam_infix + "DataType_RBV"
)


class ScreenCam(Device):
"""Compound device of pneumatic stage(fluorescent screen) and webcam"""

def __init__(
self,
prefix: str,
cam_infix="DCAM:",
name: str = "",
) -> None:
self.screen_stage = I10PneumaticStage(
prefix=prefix,
)
cam_pv = prefix = prefix + cam_infix
self.single_trigger_centroid = SingleTriggerDetector(
drv=I10AravisDriverIO(prefix=cam_pv),
read_uncached=[
epics_signal_r(float, read_pv=f"{cam_pv}CentroidX_RBV"),
epics_signal_r(float, read_pv=f"{cam_pv}CentroidY_RBV"),
],
)
super().__init__(name=name)


class FullDiagnostic(Device):
"""Compound device of a diagnostic with screen, webcam and dropdown stage."""

def __init__(
self,
prefix: str,
positioner_enum: type[StrictEnum],
positioner_suffix: str = "",
dropdown_pv_suffix: str = ":MP:SELECT",
cam_infix: str = "DCAM:",
name: str = "",
) -> None:
self.positioner = DropDownStage(
prefix=prefix,
positioner_enum=positioner_enum,
positioner_suffix=positioner_suffix,
dropdown_pv_suffix=dropdown_pv_suffix,
)
self.screen = ScreenCam(
prefix,
cam_infix,
name,
)
super().__init__(name)


class I10Diagnostic(Device):
"""Collection of all the diagnostic stage on i10."""

def __init__(self, prefix, name: str = "") -> None:
self.d1 = ScreenCam(prefix=prefix + "PHDGN-01:")
self.d2 = ScreenCam(prefix=prefix + "PHDGN-02:")
self.d3 = FullDiagnostic(
prefix=prefix + "PHDGN-03:",
positioner_enum=D3DropDown,
positioner_suffix="DET:X",
)
self.d4 = ScreenCam(prefix=prefix + "PHDGN-04:")
self.d5 = DropDownStage(
prefix=prefix + "IONC-01:",
positioner_enum=D5DropDown,
positioner_suffix="Y",
)

self.d5A = DropDownStage(
prefix=prefix + "PHDGN-06:",
positioner_enum=D5ADropDown,
positioner_suffix="DET:X",
)

self.d6 = FullDiagnostic(
prefix=prefix + "PHDGN-05:",
positioner_enum=D6DropDown,
positioner_suffix="DET:X",
)
self.d7 = DropDownStage(
prefix=prefix + "PHDGN-07:",
positioner_enum=D7DropDown,
positioner_suffix="Y",
)
super().__init__(name)


class I10Diagnotic5ADet(Device):
"""Diagnotic 5a detection with drain current and photo diode"""

def __init__(
self, prefix: str, name: str = "", connector: DeviceConnector | None = None
) -> None:
self.drain_current = CurrentAmpDet(
current_amp=FemtoDDPCA(
prefix=prefix + "IAMP-06:",
suffix="GAIN",
gain_table=Femto3xxGainTable,
gain_to_current_table=Femto3xxGainToCurrentTable,
raise_timetable=Femto3xxRaiseTime,
),
counter=StruckScaler(prefix=prefix + "SCLR-02:SCALER2", suffix=".S17"),
)
self.diode = CurrentAmpDet(
FemtoDDPCA(
prefix=prefix + "IAMP-05:",
suffix="GAIN",
gain_table=Femto3xxGainTable,
gain_to_current_table=Femto3xxGainToCurrentTable,
raise_timetable=Femto3xxRaiseTime,
),
counter=StruckScaler(prefix=prefix + "SCLR-02:SCALER2", suffix=".S18"),
)
super().__init__(name, connector)
Loading
Loading