dls-dodal 1.43.0__py3-none-any.whl → 1.45.0__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.
- {dls_dodal-1.43.0.dist-info → dls_dodal-1.45.0.dist-info}/METADATA +4 -3
- {dls_dodal-1.43.0.dist-info → dls_dodal-1.45.0.dist-info}/RECORD +66 -49
- {dls_dodal-1.43.0.dist-info → dls_dodal-1.45.0.dist-info}/WHEEL +1 -1
- dodal/_version.py +2 -2
- dodal/beamlines/__init__.py +2 -0
- dodal/beamlines/b01_1.py +8 -0
- dodal/beamlines/b07.py +27 -0
- dodal/beamlines/b07_1.py +25 -0
- dodal/beamlines/i03.py +11 -0
- dodal/beamlines/i09.py +25 -0
- dodal/beamlines/i09_1.py +25 -0
- dodal/beamlines/i10.py +19 -35
- dodal/beamlines/i13_1.py +22 -48
- dodal/beamlines/i19_1.py +17 -5
- dodal/beamlines/i19_2.py +13 -3
- dodal/beamlines/i19_optics.py +4 -2
- dodal/beamlines/i20_1.py +2 -1
- dodal/beamlines/i23.py +10 -0
- dodal/beamlines/p60.py +21 -0
- dodal/common/data_util.py +20 -0
- dodal/common/signal_utils.py +43 -4
- dodal/common/visit.py +1 -41
- dodal/devices/aperturescatterguard.py +3 -3
- dodal/devices/baton.py +17 -0
- dodal/devices/current_amplifiers/current_amplifier.py +1 -6
- dodal/devices/current_amplifiers/current_amplifier_detector.py +2 -2
- dodal/devices/current_amplifiers/femto.py +0 -5
- dodal/devices/current_amplifiers/sr570.py +0 -5
- dodal/devices/detector/det_dist_to_beam_converter.py +16 -23
- dodal/devices/detector/detector.py +2 -1
- dodal/devices/electron_analyser/__init__.py +0 -0
- dodal/devices/electron_analyser/abstract_analyser_io.py +47 -0
- dodal/devices/electron_analyser/abstract_region.py +112 -0
- dodal/devices/electron_analyser/specs_analyser_io.py +19 -0
- dodal/devices/electron_analyser/specs_region.py +26 -0
- dodal/devices/electron_analyser/vgscienta_analyser_io.py +26 -0
- dodal/devices/electron_analyser/vgscienta_region.py +90 -0
- dodal/devices/fast_grid_scan.py +2 -2
- dodal/devices/i03/beamstop.py +2 -2
- dodal/devices/i10/diagnostics.py +239 -0
- dodal/devices/i10/slits.py +93 -6
- dodal/devices/i13_1/merlin.py +1 -2
- dodal/devices/i13_1/merlin_controller.py +12 -8
- dodal/devices/i19/beamstop.py +30 -0
- dodal/devices/i19/blueapi_device.py +102 -0
- dodal/devices/i19/hutch_access.py +2 -0
- dodal/devices/i19/shutter.py +24 -40
- dodal/devices/i22/nxsas.py +1 -3
- dodal/devices/i24/focus_mirrors.py +3 -3
- dodal/devices/i24/pilatus_metadata.py +2 -2
- dodal/devices/motors.py +21 -0
- dodal/devices/oav/oav_detector.py +7 -9
- dodal/devices/oav/snapshots/snapshot.py +21 -0
- dodal/devices/oav/snapshots/snapshot_image_processing.py +74 -0
- dodal/devices/turbo_slit.py +8 -2
- dodal/devices/undulator.py +9 -7
- dodal/devices/util/adjuster_plans.py +1 -2
- dodal/devices/util/lookup_tables.py +38 -0
- dodal/devices/util/test_utils.py +1 -0
- dodal/plan_stubs/electron_analyser/__init__.py +0 -0
- dodal/plan_stubs/electron_analyser/configure_controller.py +80 -0
- dodal/plan_stubs/motor_utils.py +10 -12
- dodal/utils.py +0 -7
- dodal/devices/i13_1/merlin_io.py +0 -17
- dodal/devices/oav/microns_for_zoom_levels.json +0 -55
- dodal/devices/oav/snapshots/snapshot_with_beam_centre.py +0 -64
- dodal/devices/util/motor_utils.py +0 -6
- {dls_dodal-1.43.0.dist-info → dls_dodal-1.45.0.dist-info}/entry_points.txt +0 -0
- {dls_dodal-1.43.0.dist-info → dls_dodal-1.45.0.dist-info/licenses}/LICENSE +0 -0
- {dls_dodal-1.43.0.dist-info → dls_dodal-1.45.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import TypeVar
|
|
3
|
+
|
|
4
|
+
from ophyd_async.core import StandardReadable
|
|
5
|
+
from ophyd_async.epics.core import epics_signal_rw
|
|
6
|
+
|
|
7
|
+
from dodal.devices.electron_analyser.abstract_region import EnergyMode
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AbstractAnalyserDriverIO(ABC, StandardReadable):
|
|
11
|
+
"""
|
|
12
|
+
Generic device to configure electron analyser with new region settings.
|
|
13
|
+
Electron analysers should inherit from this class for further specialisation.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, prefix: str, name: str = "") -> None:
|
|
17
|
+
with self.add_children_as_readables():
|
|
18
|
+
self.low_energy = epics_signal_rw(float, prefix + "LOW_ENERGY")
|
|
19
|
+
self.high_energy = epics_signal_rw(float, prefix + "HIGH_ENERGY")
|
|
20
|
+
self.slices = epics_signal_rw(int, prefix + "SLICES")
|
|
21
|
+
self.lens_mode = epics_signal_rw(str, prefix + "LENS_MODE")
|
|
22
|
+
self.pass_energy = epics_signal_rw(
|
|
23
|
+
self.pass_energy_type, prefix + "PASS_ENERGY"
|
|
24
|
+
)
|
|
25
|
+
self.energy_step = epics_signal_rw(float, prefix + "STEP_SIZE")
|
|
26
|
+
self.iterations = epics_signal_rw(int, prefix + "NumExposures")
|
|
27
|
+
self.acquisition_mode = epics_signal_rw(str, prefix + "ACQ_MODE")
|
|
28
|
+
|
|
29
|
+
super().__init__(name)
|
|
30
|
+
|
|
31
|
+
def to_kinetic_energy(
|
|
32
|
+
self, value: float, excitation_energy: float, mode: EnergyMode
|
|
33
|
+
) -> float:
|
|
34
|
+
return excitation_energy - value if mode == EnergyMode.BINDING else value
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
@abstractmethod
|
|
38
|
+
def pass_energy_type(self) -> type:
|
|
39
|
+
"""
|
|
40
|
+
Return the type the pass_energy should be. Each one is unfortunately different
|
|
41
|
+
for the underlying analyser software and cannot be changed on epics side.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
TAbstractAnalyserDriverIO = TypeVar(
|
|
46
|
+
"TAbstractAnalyserDriverIO", bound=AbstractAnalyserDriverIO
|
|
47
|
+
)
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from abc import ABC
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from typing import Generic, TypeVar
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, Field, model_validator
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def java_to_python_case(java_str: str) -> str:
|
|
11
|
+
"""
|
|
12
|
+
Convert a camelCase Java-style string to a snake_case Python-style string.
|
|
13
|
+
|
|
14
|
+
:param java_str: The Java-style camelCase string.
|
|
15
|
+
:return: The Python-style snake_case string.
|
|
16
|
+
"""
|
|
17
|
+
new_value = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", java_str)
|
|
18
|
+
new_value = re.sub("([a-z0-9])([A-Z])", r"\1_\2", new_value).lower()
|
|
19
|
+
return new_value
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def switch_case_validation(data: dict, f: Callable[[str], str]) -> dict:
|
|
23
|
+
return {f(key): value for key, value in data.items()}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class JavaToPythonModel(BaseModel):
|
|
27
|
+
@model_validator(mode="before")
|
|
28
|
+
@classmethod
|
|
29
|
+
def before_validation(cls, data: dict) -> dict:
|
|
30
|
+
data = switch_case_validation(data, java_to_python_case)
|
|
31
|
+
return data
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def energy_mode_validation(data: dict) -> dict:
|
|
35
|
+
# Convert binding_energy to energy_mode to make base region more generic
|
|
36
|
+
if "binding_energy" in data:
|
|
37
|
+
is_binding_energy = data["binding_energy"]
|
|
38
|
+
del data["binding_energy"]
|
|
39
|
+
data["energy_mode"] = (
|
|
40
|
+
EnergyMode.BINDING if is_binding_energy else EnergyMode.KINETIC
|
|
41
|
+
)
|
|
42
|
+
return data
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class EnergyMode(str, Enum):
|
|
46
|
+
KINETIC = "Kinetic"
|
|
47
|
+
BINDING = "Binding"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class AbstractBaseRegion(ABC, JavaToPythonModel):
|
|
51
|
+
"""
|
|
52
|
+
Generic region model that holds the data. Specialised region models should inherit
|
|
53
|
+
this to extend functionality. All energy units are assumed to be in eV.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
name: str = "New_region"
|
|
57
|
+
enabled: bool = False
|
|
58
|
+
slices: int = 1
|
|
59
|
+
iterations: int = 1
|
|
60
|
+
# These ones we need subclasses to provide default values
|
|
61
|
+
lens_mode: str
|
|
62
|
+
pass_energy: int
|
|
63
|
+
acquisition_mode: str
|
|
64
|
+
low_energy: float
|
|
65
|
+
high_energy: float
|
|
66
|
+
step_time: float
|
|
67
|
+
energy_step: float # in eV
|
|
68
|
+
energy_mode: EnergyMode = EnergyMode.KINETIC
|
|
69
|
+
|
|
70
|
+
def is_binding_energy(self) -> bool:
|
|
71
|
+
return self.energy_mode == EnergyMode.BINDING
|
|
72
|
+
|
|
73
|
+
def is_kinetic_energy(self) -> bool:
|
|
74
|
+
return self.energy_mode == EnergyMode.KINETIC
|
|
75
|
+
|
|
76
|
+
def to_kinetic_energy(self, value: float, excitation_energy: float) -> float:
|
|
77
|
+
return value if self.is_binding_energy() else excitation_energy - value
|
|
78
|
+
|
|
79
|
+
@model_validator(mode="before")
|
|
80
|
+
@classmethod
|
|
81
|
+
def before_validation(cls, data: dict) -> dict:
|
|
82
|
+
data = switch_case_validation(data, java_to_python_case)
|
|
83
|
+
return energy_mode_validation(data)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
TAbstractBaseRegion = TypeVar("TAbstractBaseRegion", bound=AbstractBaseRegion)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class AbstractBaseSequence(ABC, JavaToPythonModel, Generic[TAbstractBaseRegion]):
|
|
90
|
+
"""
|
|
91
|
+
Generic sequence model that holds the list of region data. Specialised sequence
|
|
92
|
+
models should inherit this to extend functionality and define type of region to
|
|
93
|
+
hold.
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
version: float = 0.1 # If file format changes within prod, increment this number!
|
|
97
|
+
regions: list[TAbstractBaseRegion] = Field(default_factory=lambda: [])
|
|
98
|
+
|
|
99
|
+
def get_enabled_regions(self) -> list[TAbstractBaseRegion]:
|
|
100
|
+
return [r for r in self.regions if r.enabled]
|
|
101
|
+
|
|
102
|
+
def get_region_names(self) -> list[str]:
|
|
103
|
+
return [r.name for r in self.regions]
|
|
104
|
+
|
|
105
|
+
def get_enabled_region_names(self) -> list[str]:
|
|
106
|
+
return [r.name for r in self.get_enabled_regions()]
|
|
107
|
+
|
|
108
|
+
def get_region_by_name(self, name: str) -> TAbstractBaseRegion | None:
|
|
109
|
+
return next((region for region in self.regions if region.name == name), None)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
TAbstractBaseSequence = TypeVar("TAbstractBaseSequence", bound=AbstractBaseSequence)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from ophyd_async.epics.core import epics_signal_rw
|
|
2
|
+
|
|
3
|
+
from dodal.devices.electron_analyser.abstract_analyser_io import (
|
|
4
|
+
AbstractAnalyserDriverIO,
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SpecsAnalyserDriverIO(AbstractAnalyserDriverIO):
|
|
9
|
+
def __init__(self, prefix: str, name: str = "") -> None:
|
|
10
|
+
with self.add_children_as_readables():
|
|
11
|
+
self.psu_mode = epics_signal_rw(str, prefix + "SCAN_RANGE")
|
|
12
|
+
self.values = epics_signal_rw(int, prefix + "VALUES")
|
|
13
|
+
self.centre_energy = epics_signal_rw(float, prefix + "KINETIC_ENERGY")
|
|
14
|
+
|
|
15
|
+
super().__init__(prefix, name)
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def pass_energy_type(self) -> type:
|
|
19
|
+
return float
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from pydantic import Field
|
|
2
|
+
|
|
3
|
+
from dodal.devices.electron_analyser.abstract_region import (
|
|
4
|
+
AbstractBaseRegion,
|
|
5
|
+
AbstractBaseSequence,
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SpecsRegion(AbstractBaseRegion):
|
|
10
|
+
# Override base class with defaults
|
|
11
|
+
lens_mode: str = "SmallArea"
|
|
12
|
+
pass_energy: int = 5
|
|
13
|
+
acquisition_mode: str = "Fixed Transmission"
|
|
14
|
+
low_energy: float = Field(default=800, alias="start_energy")
|
|
15
|
+
high_energy: float = Field(default=850, alias="end_energy")
|
|
16
|
+
step_time: float = Field(default=1.0, alias="exposure_time")
|
|
17
|
+
energy_step: float = Field(default=0.1, alias="step_energy")
|
|
18
|
+
# Specific to this class
|
|
19
|
+
values: int = 1
|
|
20
|
+
centre_energy: float = 0
|
|
21
|
+
psu_mode: str = "1.5keV"
|
|
22
|
+
estimated_time_in_ms: float = 0
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class SpecsSequence(AbstractBaseSequence[SpecsRegion]):
|
|
26
|
+
regions: list[SpecsRegion] = Field(default_factory=lambda: [])
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from ophyd_async.epics.core import epics_signal_rw
|
|
2
|
+
|
|
3
|
+
from dodal.devices.electron_analyser.abstract_analyser_io import (
|
|
4
|
+
AbstractAnalyserDriverIO,
|
|
5
|
+
)
|
|
6
|
+
from dodal.devices.electron_analyser.vgscienta_region import (
|
|
7
|
+
DetectorMode,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class VGScientaAnalyserDriverIO(AbstractAnalyserDriverIO):
|
|
12
|
+
def __init__(self, prefix: str, name: str = "") -> None:
|
|
13
|
+
with self.add_children_as_readables():
|
|
14
|
+
self.centre_energy = epics_signal_rw(float, prefix + "CENTRE_ENERGY")
|
|
15
|
+
self.first_x_channel = epics_signal_rw(int, prefix + "MinX")
|
|
16
|
+
self.first_y_channel = epics_signal_rw(int, prefix + "MinY")
|
|
17
|
+
self.x_channel_size = epics_signal_rw(int, prefix + "SizeX")
|
|
18
|
+
self.y_channel_size = epics_signal_rw(int, prefix + "SizeY")
|
|
19
|
+
self.detector_mode = epics_signal_rw(DetectorMode, prefix + "DETECTOR_MODE")
|
|
20
|
+
self.image_mode = epics_signal_rw(str, prefix + "ImageMode")
|
|
21
|
+
|
|
22
|
+
super().__init__(prefix, name)
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def pass_energy_type(self) -> type:
|
|
26
|
+
return str
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import uuid
|
|
2
|
+
from enum import Enum
|
|
3
|
+
|
|
4
|
+
from ophyd_async.core import StrictEnum
|
|
5
|
+
from pydantic import Field
|
|
6
|
+
|
|
7
|
+
from dodal.devices.electron_analyser.abstract_region import (
|
|
8
|
+
AbstractBaseRegion,
|
|
9
|
+
AbstractBaseSequence,
|
|
10
|
+
JavaToPythonModel,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Status(str, Enum):
|
|
15
|
+
READY = "Ready"
|
|
16
|
+
RUNNING = "Running"
|
|
17
|
+
COMPLETED = "Completed"
|
|
18
|
+
INVALID = "Invalid"
|
|
19
|
+
ABORTED = "Aborted"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class DetectorMode(StrictEnum):
|
|
23
|
+
ADC = "ADC"
|
|
24
|
+
PULSE_COUNTING = "Pulse Counting"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class AcquisitionMode(str, Enum):
|
|
28
|
+
SWEPT = "Swept"
|
|
29
|
+
FIXED = "Fixed"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class VGScientaRegion(AbstractBaseRegion):
|
|
33
|
+
# Override defaults of base region class
|
|
34
|
+
lens_mode: str = "Angular45"
|
|
35
|
+
pass_energy: int = 5
|
|
36
|
+
acquisition_mode: str = AcquisitionMode.SWEPT
|
|
37
|
+
low_energy: float = 8.0
|
|
38
|
+
high_energy: float = 10.0
|
|
39
|
+
step_time: float = 1.0
|
|
40
|
+
energy_step: float = Field(default=200.0)
|
|
41
|
+
# Specific to this class
|
|
42
|
+
id: str = Field(default=str(uuid.uuid4()), alias="region_id")
|
|
43
|
+
excitation_energy_source: str = "source1"
|
|
44
|
+
fix_energy: float = 9.0
|
|
45
|
+
total_steps: float = 13.0
|
|
46
|
+
total_time: float = 13.0
|
|
47
|
+
exposure_time: float = 1.0
|
|
48
|
+
first_x_channel: int = 1
|
|
49
|
+
last_x_channel: int = 1000
|
|
50
|
+
first_y_channel: int = 101
|
|
51
|
+
last_y_channel: int = 800
|
|
52
|
+
detector_mode: DetectorMode = DetectorMode.ADC
|
|
53
|
+
status: Status = Status.READY
|
|
54
|
+
|
|
55
|
+
def x_channel_size(self) -> int:
|
|
56
|
+
return self.last_x_channel - self.first_x_channel + 1
|
|
57
|
+
|
|
58
|
+
def y_channel_size(self) -> int:
|
|
59
|
+
return self.last_y_channel - self.first_y_channel + 1
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class VGScientaExcitationEnergySource(JavaToPythonModel):
|
|
63
|
+
name: str = "source1"
|
|
64
|
+
device_name: str = Field(default="", alias="scannable_name")
|
|
65
|
+
value: float = 0
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class VGScientaSequence(AbstractBaseSequence[VGScientaRegion]):
|
|
69
|
+
element_set: str = Field(default="Unknown")
|
|
70
|
+
excitation_energy_sources: list[VGScientaExcitationEnergySource] = Field(
|
|
71
|
+
default_factory=lambda: []
|
|
72
|
+
)
|
|
73
|
+
regions: list[VGScientaRegion] = Field(default_factory=lambda: [])
|
|
74
|
+
|
|
75
|
+
def get_excitation_energy_source_by_region(
|
|
76
|
+
self, region: VGScientaRegion
|
|
77
|
+
) -> VGScientaExcitationEnergySource:
|
|
78
|
+
value = next(
|
|
79
|
+
(
|
|
80
|
+
e
|
|
81
|
+
for e in self.excitation_energy_sources
|
|
82
|
+
if region.excitation_energy_source == e.name
|
|
83
|
+
),
|
|
84
|
+
None,
|
|
85
|
+
)
|
|
86
|
+
if value is None:
|
|
87
|
+
raise ValueError(
|
|
88
|
+
f'Unable to find excitation energy source using region "{region.name}"'
|
|
89
|
+
)
|
|
90
|
+
return value
|
dodal/devices/fast_grid_scan.py
CHANGED
|
@@ -23,7 +23,7 @@ from ophyd_async.epics.core import (
|
|
|
23
23
|
from pydantic import field_validator
|
|
24
24
|
from pydantic.dataclasses import dataclass
|
|
25
25
|
|
|
26
|
-
from dodal.common.signal_utils import
|
|
26
|
+
from dodal.common.signal_utils import create_r_hardware_backed_soft_signal
|
|
27
27
|
from dodal.log import LOGGER
|
|
28
28
|
from dodal.parameters.experiment_parameter_base import AbstractExperimentWithBeamParams
|
|
29
29
|
|
|
@@ -203,7 +203,7 @@ class FastGridScanCommon(StandardReadable, Flyable, ABC, Generic[ParamType]):
|
|
|
203
203
|
self.stop_cmd = epics_signal_x(f"{prefix}STOP.PROC")
|
|
204
204
|
self.status = epics_signal_r(int, f"{prefix}SCAN_STATUS")
|
|
205
205
|
|
|
206
|
-
self.expected_images =
|
|
206
|
+
self.expected_images = create_r_hardware_backed_soft_signal(
|
|
207
207
|
float, self._calculate_expected_images
|
|
208
208
|
)
|
|
209
209
|
|
dodal/devices/i03/beamstop.py
CHANGED
|
@@ -5,7 +5,7 @@ from ophyd_async.core import StandardReadable, StrictEnum
|
|
|
5
5
|
from ophyd_async.epics.motor import Motor
|
|
6
6
|
|
|
7
7
|
from dodal.common.beamlines.beamline_parameters import GDABeamlineParameters
|
|
8
|
-
from dodal.common.signal_utils import
|
|
8
|
+
from dodal.common.signal_utils import create_r_hardware_backed_soft_signal
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class BeamstopPositions(StrictEnum):
|
|
@@ -53,7 +53,7 @@ class Beamstop(StandardReadable):
|
|
|
53
53
|
self.x_mm = Motor(prefix + "X")
|
|
54
54
|
self.y_mm = Motor(prefix + "Y")
|
|
55
55
|
self.z_mm = Motor(prefix + "Z")
|
|
56
|
-
self.selected_pos =
|
|
56
|
+
self.selected_pos = create_r_hardware_backed_soft_signal(
|
|
57
57
|
BeamstopPositions, self._get_selected_position
|
|
58
58
|
)
|
|
59
59
|
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
from bluesky.protocols import Movable
|
|
2
|
+
from ophyd_async.core import (
|
|
3
|
+
AsyncStatus,
|
|
4
|
+
Device,
|
|
5
|
+
StandardReadable,
|
|
6
|
+
StrictEnum,
|
|
7
|
+
)
|
|
8
|
+
from ophyd_async.core import StandardReadableFormat as Format
|
|
9
|
+
from ophyd_async.core._device import DeviceConnector
|
|
10
|
+
from ophyd_async.epics.adaravis import AravisDriverIO
|
|
11
|
+
from ophyd_async.epics.adcore import SingleTriggerDetector
|
|
12
|
+
from ophyd_async.epics.core import (
|
|
13
|
+
epics_signal_r,
|
|
14
|
+
epics_signal_rw,
|
|
15
|
+
)
|
|
16
|
+
from ophyd_async.epics.motor import Motor
|
|
17
|
+
|
|
18
|
+
from dodal.devices.current_amplifiers import (
|
|
19
|
+
CurrentAmpDet,
|
|
20
|
+
Femto3xxGainTable,
|
|
21
|
+
Femto3xxGainToCurrentTable,
|
|
22
|
+
Femto3xxRaiseTime,
|
|
23
|
+
FemtoDDPCA,
|
|
24
|
+
StruckScaler,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class D3Position(StrictEnum):
|
|
29
|
+
NOTHING = "Nothing"
|
|
30
|
+
GRID = "Grid"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class D5Position(StrictEnum):
|
|
34
|
+
CELL_IN = "Cell In"
|
|
35
|
+
CELL_OUT = "Cell Out"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class D5APosition(StrictEnum):
|
|
39
|
+
OUT_OF_THE_BEAM = "Out of the beam"
|
|
40
|
+
DIODE = "Diode"
|
|
41
|
+
BLADE = "Blade"
|
|
42
|
+
LA = "La ref"
|
|
43
|
+
GD = "Gd ref"
|
|
44
|
+
YB = "Yb ref"
|
|
45
|
+
GRID = "Grid"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class D6Position(StrictEnum):
|
|
49
|
+
DIODE_OUT = "Diode Out"
|
|
50
|
+
DIODE_IN = "Diode In"
|
|
51
|
+
AU_MESH = "Au Mesh"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class D7Position(StrictEnum):
|
|
55
|
+
OUT = "Out"
|
|
56
|
+
SHUTTER = "Shutter"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class InOutTable(StrictEnum):
|
|
60
|
+
MOVE_IN = "Move In"
|
|
61
|
+
MOVE_OUT = "Move Out"
|
|
62
|
+
RESET = "Reset"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class InOutReadBackTable(StrictEnum):
|
|
66
|
+
MOVE_IN = "Moving In"
|
|
67
|
+
MOVE_OUT = "Moving Out"
|
|
68
|
+
IN_BEAM = "In Beam"
|
|
69
|
+
FAULT = "Fault"
|
|
70
|
+
OUT_OF_BEAM = "Out of Beam"
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class Positioner(StandardReadable, Movable):
|
|
74
|
+
"""1D stage with a enum table to select positions."""
|
|
75
|
+
|
|
76
|
+
def __init__(
|
|
77
|
+
self,
|
|
78
|
+
prefix: str,
|
|
79
|
+
positioner_enum: type[StrictEnum],
|
|
80
|
+
positioner_suffix: str = "",
|
|
81
|
+
Positioner_pv_suffix: str = ":MP:SELECT",
|
|
82
|
+
name: str = "",
|
|
83
|
+
) -> None:
|
|
84
|
+
self._stage_motion = Motor(prefix=prefix + positioner_suffix)
|
|
85
|
+
with self.add_children_as_readables(Format.CONFIG_SIGNAL):
|
|
86
|
+
self.stage_position = epics_signal_rw(
|
|
87
|
+
positioner_enum,
|
|
88
|
+
read_pv=prefix + positioner_suffix + Positioner_pv_suffix,
|
|
89
|
+
)
|
|
90
|
+
super().__init__(name=name)
|
|
91
|
+
self.positioner_enum = positioner_enum
|
|
92
|
+
|
|
93
|
+
@AsyncStatus.wrap
|
|
94
|
+
async def set(self, value: StrictEnum) -> None:
|
|
95
|
+
if value in self.positioner_enum:
|
|
96
|
+
await self.stage_position.set(value=value)
|
|
97
|
+
else:
|
|
98
|
+
raise ValueError(
|
|
99
|
+
f"{value} is not an allow position. Position must be: {self.positioner_enum}"
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class I10PneumaticStage(StandardReadable):
|
|
104
|
+
"""Pneumatic stage only has two real positions in or out.
|
|
105
|
+
Use for fluorescent screen which can be insert into the x-ray beam.
|
|
106
|
+
Most often use in conjunction with a webcam to locate the x-ray beam."""
|
|
107
|
+
|
|
108
|
+
def __init__(
|
|
109
|
+
self,
|
|
110
|
+
prefix: str,
|
|
111
|
+
name: str = "",
|
|
112
|
+
) -> None:
|
|
113
|
+
with self.add_children_as_readables(Format.HINTED_SIGNAL):
|
|
114
|
+
self.stage_position_set = epics_signal_rw(
|
|
115
|
+
InOutTable,
|
|
116
|
+
read_pv=prefix + "CON",
|
|
117
|
+
)
|
|
118
|
+
self.stage_position_readback = epics_signal_r(
|
|
119
|
+
InOutReadBackTable,
|
|
120
|
+
read_pv=prefix + "STA",
|
|
121
|
+
)
|
|
122
|
+
super().__init__(name=name)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class ScreenCam(Device):
|
|
126
|
+
"""Compound device of pneumatic stage(fluorescent screen) and webcam"""
|
|
127
|
+
|
|
128
|
+
def __init__(
|
|
129
|
+
self,
|
|
130
|
+
prefix: str,
|
|
131
|
+
cam_infix="DCAM:",
|
|
132
|
+
name: str = "",
|
|
133
|
+
) -> None:
|
|
134
|
+
self.screen_stage = I10PneumaticStage(
|
|
135
|
+
prefix=prefix,
|
|
136
|
+
)
|
|
137
|
+
cam_pv = prefix + cam_infix
|
|
138
|
+
self.centroid_x = epics_signal_r(float, read_pv=f"{cam_pv}STAT:CentroidX_RBV")
|
|
139
|
+
self.centroid_y = epics_signal_r(float, read_pv=f"{cam_pv}STAT:CentroidY_RBV")
|
|
140
|
+
self.single_trigger_centroid = SingleTriggerDetector(
|
|
141
|
+
drv=AravisDriverIO(prefix=cam_pv + "CAM:"),
|
|
142
|
+
read_uncached=[
|
|
143
|
+
self.centroid_x,
|
|
144
|
+
self.centroid_y,
|
|
145
|
+
],
|
|
146
|
+
)
|
|
147
|
+
super().__init__(name=name)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class FullDiagnostic(Device):
|
|
151
|
+
"""Compound device of a diagnostic with screen, webcam and Positioner stage."""
|
|
152
|
+
|
|
153
|
+
def __init__(
|
|
154
|
+
self,
|
|
155
|
+
prefix: str,
|
|
156
|
+
positioner_enum: type[StrictEnum],
|
|
157
|
+
positioner_suffix: str = "",
|
|
158
|
+
Positioner_pv_suffix: str = ":MP:SELECT",
|
|
159
|
+
cam_infix: str = "DCAM:",
|
|
160
|
+
name: str = "",
|
|
161
|
+
) -> None:
|
|
162
|
+
self.positioner = Positioner(
|
|
163
|
+
prefix=prefix,
|
|
164
|
+
positioner_enum=positioner_enum,
|
|
165
|
+
positioner_suffix=positioner_suffix,
|
|
166
|
+
Positioner_pv_suffix=Positioner_pv_suffix,
|
|
167
|
+
)
|
|
168
|
+
self.screen = ScreenCam(
|
|
169
|
+
prefix,
|
|
170
|
+
cam_infix,
|
|
171
|
+
name,
|
|
172
|
+
)
|
|
173
|
+
super().__init__(name)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class I10Diagnostic(Device):
|
|
177
|
+
"""Collection of all the diagnostic stage on i10."""
|
|
178
|
+
|
|
179
|
+
def __init__(self, prefix, name: str = "") -> None:
|
|
180
|
+
self.d1 = ScreenCam(prefix=prefix + "PHDGN-01:")
|
|
181
|
+
self.d2 = ScreenCam(prefix=prefix + "PHDGN-02:")
|
|
182
|
+
self.d3 = FullDiagnostic(
|
|
183
|
+
prefix=prefix + "PHDGN-03:",
|
|
184
|
+
positioner_enum=D3Position,
|
|
185
|
+
positioner_suffix="DET:X",
|
|
186
|
+
)
|
|
187
|
+
self.d4 = ScreenCam(prefix=prefix + "PHDGN-04:")
|
|
188
|
+
self.d5 = Positioner(
|
|
189
|
+
prefix=prefix + "IONC-01:",
|
|
190
|
+
positioner_enum=D5Position,
|
|
191
|
+
positioner_suffix="Y",
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
self.d5A = Positioner(
|
|
195
|
+
prefix=prefix + "PHDGN-06:",
|
|
196
|
+
positioner_enum=D5APosition,
|
|
197
|
+
positioner_suffix="DET:X",
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
self.d6 = FullDiagnostic(
|
|
201
|
+
prefix=prefix + "PHDGN-05:",
|
|
202
|
+
positioner_enum=D6Position,
|
|
203
|
+
positioner_suffix="DET:X",
|
|
204
|
+
)
|
|
205
|
+
self.d7 = Positioner(
|
|
206
|
+
prefix=prefix + "PHDGN-07:",
|
|
207
|
+
positioner_enum=D7Position,
|
|
208
|
+
positioner_suffix="Y",
|
|
209
|
+
)
|
|
210
|
+
super().__init__(name)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
class I10Diagnostic5ADet(Device):
|
|
214
|
+
"""Diagnostic 5a detection with drain current and photo diode"""
|
|
215
|
+
|
|
216
|
+
def __init__(
|
|
217
|
+
self, prefix: str, name: str = "", connector: DeviceConnector | None = None
|
|
218
|
+
) -> None:
|
|
219
|
+
self.drain_current = CurrentAmpDet(
|
|
220
|
+
current_amp=FemtoDDPCA(
|
|
221
|
+
prefix=prefix + "IAMP-06:",
|
|
222
|
+
suffix="GAIN",
|
|
223
|
+
gain_table=Femto3xxGainTable,
|
|
224
|
+
gain_to_current_table=Femto3xxGainToCurrentTable,
|
|
225
|
+
raise_timetable=Femto3xxRaiseTime,
|
|
226
|
+
),
|
|
227
|
+
counter=StruckScaler(prefix=prefix + "SCLR-02:SCALER2", suffix=".S17"),
|
|
228
|
+
)
|
|
229
|
+
self.diode = CurrentAmpDet(
|
|
230
|
+
FemtoDDPCA(
|
|
231
|
+
prefix=prefix + "IAMP-05:",
|
|
232
|
+
suffix="GAIN",
|
|
233
|
+
gain_table=Femto3xxGainTable,
|
|
234
|
+
gain_to_current_table=Femto3xxGainToCurrentTable,
|
|
235
|
+
raise_timetable=Femto3xxRaiseTime,
|
|
236
|
+
),
|
|
237
|
+
counter=StruckScaler(prefix=prefix + "SCLR-02:SCALER2", suffix=".S18"),
|
|
238
|
+
)
|
|
239
|
+
super().__init__(name, connector)
|