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
dodal/devices/undulator.py
CHANGED
|
@@ -2,7 +2,7 @@ import os
|
|
|
2
2
|
|
|
3
3
|
import numpy as np
|
|
4
4
|
from bluesky.protocols import Movable
|
|
5
|
-
from numpy import
|
|
5
|
+
from numpy import ndarray
|
|
6
6
|
from ophyd_async.core import (
|
|
7
7
|
AsyncStatus,
|
|
8
8
|
StandardReadable,
|
|
@@ -38,12 +38,12 @@ class UndulatorGapAccess(StrictEnum):
|
|
|
38
38
|
DISABLED = "DISABLED"
|
|
39
39
|
|
|
40
40
|
|
|
41
|
-
def
|
|
41
|
+
def _get_gap_for_energy(
|
|
42
42
|
dcm_energy_ev: float, energy_to_distance_table: ndarray
|
|
43
43
|
) -> float:
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
return np.interp(
|
|
45
|
+
dcm_energy_ev, energy_to_distance_table[:, 0], energy_to_distance_table[:, 1]
|
|
46
|
+
)
|
|
47
47
|
|
|
48
48
|
|
|
49
49
|
class Undulator(StandardReadable, Movable[float]):
|
|
@@ -115,8 +115,10 @@ class Undulator(StandardReadable, Movable[float]):
|
|
|
115
115
|
|
|
116
116
|
async def _set_undulator_gap(self, energy_kev: float) -> None:
|
|
117
117
|
await self.raise_if_not_enabled()
|
|
118
|
-
LOGGER.info(f"Setting undulator gap to {energy_kev:.2f} kev")
|
|
119
118
|
target_gap = await self._get_gap_to_match_energy(energy_kev)
|
|
119
|
+
LOGGER.info(
|
|
120
|
+
f"Setting undulator gap to {target_gap:.3f}mm based on {energy_kev:.2f}kev"
|
|
121
|
+
)
|
|
120
122
|
|
|
121
123
|
# Check if undulator gap is close enough to the value from the DCM
|
|
122
124
|
current_gap = await self.current_gap.get_value()
|
|
@@ -152,7 +154,7 @@ class Undulator(StandardReadable, Movable[float]):
|
|
|
152
154
|
)
|
|
153
155
|
|
|
154
156
|
# Use the lookup table to get the undulator gap associated with this dcm energy
|
|
155
|
-
return
|
|
157
|
+
return _get_gap_for_energy(
|
|
156
158
|
energy_kev * 1000,
|
|
157
159
|
energy_to_distance_table,
|
|
158
160
|
)
|
|
@@ -7,14 +7,13 @@ from collections.abc import Callable, Generator
|
|
|
7
7
|
|
|
8
8
|
from bluesky import plan_stubs as bps
|
|
9
9
|
from bluesky.utils import Msg
|
|
10
|
-
from ophyd.epics_motor import EpicsMotor
|
|
11
10
|
from ophyd_async.epics.motor import Motor
|
|
12
11
|
|
|
13
12
|
from dodal.log import LOGGER
|
|
14
13
|
|
|
15
14
|
|
|
16
15
|
def lookup_table_adjuster(
|
|
17
|
-
lookup_table: Callable[[float], float], output_device:
|
|
16
|
+
lookup_table: Callable[[float], float], output_device: Motor, input
|
|
18
17
|
):
|
|
19
18
|
"""Returns a callable that adjusts a value according to a lookup table"""
|
|
20
19
|
|
|
@@ -64,3 +64,41 @@ def linear_interpolation_lut(
|
|
|
64
64
|
return float(interp(s, s_values, t_values))
|
|
65
65
|
|
|
66
66
|
return s_to_t2
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def linear_extrapolation_lut(
|
|
70
|
+
s_values: Sequence[float], t_values: Sequence[float]
|
|
71
|
+
) -> Callable[[float], float]:
|
|
72
|
+
"""
|
|
73
|
+
Return a callable that implements f(s) = t according to the conversion table data
|
|
74
|
+
supplied, with linear extrapolation outside that range. Inside the range of the table,
|
|
75
|
+
the function is equivalent to that returned by linear_interpolation_lut
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
s_values: Values of the independent axis
|
|
79
|
+
t_values: Values of the dependent axis
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
A callable that returns t for the given s
|
|
83
|
+
"""
|
|
84
|
+
assert len(s_values) == len(t_values), (
|
|
85
|
+
"Lookup table does not have the same number of values for each axis"
|
|
86
|
+
)
|
|
87
|
+
assert len(s_values) > 1, "Need at least 2 points in the lookup table"
|
|
88
|
+
interp = linear_interpolation_lut(s_values, t_values)
|
|
89
|
+
s_min = s_values[0]
|
|
90
|
+
s_max = s_values[-1]
|
|
91
|
+
|
|
92
|
+
def s_to_t(s: float) -> float:
|
|
93
|
+
if s < s_min:
|
|
94
|
+
return t_values[0] + (s - s_min) * (t_values[1] - t_values[0]) / (
|
|
95
|
+
s_values[1] - s_values[0]
|
|
96
|
+
)
|
|
97
|
+
elif s > s_max:
|
|
98
|
+
return t_values[-1] + (s - s_max) * (t_values[-1] - t_values[-2]) / (
|
|
99
|
+
s_values[-1] - s_values[-2]
|
|
100
|
+
)
|
|
101
|
+
else:
|
|
102
|
+
return interp(s)
|
|
103
|
+
|
|
104
|
+
return s_to_t
|
dodal/devices/util/test_utils.py
CHANGED
|
@@ -11,6 +11,7 @@ def patch_motor(motor: Motor, initial_position=0):
|
|
|
11
11
|
set_mock_value(motor.deadband, 0.001)
|
|
12
12
|
set_mock_value(motor.motor_done_move, 1)
|
|
13
13
|
set_mock_value(motor.velocity, 3)
|
|
14
|
+
set_mock_value(motor.max_velocity, 5)
|
|
14
15
|
return callback_on_mock_put(
|
|
15
16
|
motor.user_setpoint,
|
|
16
17
|
lambda pos, *args, **kwargs: set_mock_value(motor.user_readback, pos),
|
|
File without changes
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
from bluesky import plan_stubs as bps
|
|
2
|
+
|
|
3
|
+
from dodal.devices.electron_analyser.abstract_analyser_io import (
|
|
4
|
+
AbstractAnalyserDriverIO,
|
|
5
|
+
)
|
|
6
|
+
from dodal.devices.electron_analyser.abstract_region import (
|
|
7
|
+
AbstractBaseRegion,
|
|
8
|
+
)
|
|
9
|
+
from dodal.devices.electron_analyser.specs_analyser_io import (
|
|
10
|
+
SpecsAnalyserDriverIO,
|
|
11
|
+
)
|
|
12
|
+
from dodal.devices.electron_analyser.specs_region import SpecsRegion
|
|
13
|
+
from dodal.devices.electron_analyser.vgscienta_analyser_io import (
|
|
14
|
+
VGScientaAnalyserDriverIO,
|
|
15
|
+
)
|
|
16
|
+
from dodal.devices.electron_analyser.vgscienta_region import (
|
|
17
|
+
VGScientaRegion,
|
|
18
|
+
)
|
|
19
|
+
from dodal.log import LOGGER
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def configure_analyser(
|
|
23
|
+
analyser: AbstractAnalyserDriverIO,
|
|
24
|
+
region: AbstractBaseRegion,
|
|
25
|
+
excitation_energy: float,
|
|
26
|
+
):
|
|
27
|
+
LOGGER.info(f'Configuring analyser with region "{region.name}"')
|
|
28
|
+
low_energy = region.to_kinetic_energy(region.low_energy, excitation_energy)
|
|
29
|
+
high_energy = region.to_kinetic_energy(region.high_energy, excitation_energy)
|
|
30
|
+
pass_energy_type = analyser.pass_energy_type
|
|
31
|
+
pass_energy = pass_energy_type(region.pass_energy)
|
|
32
|
+
# Set detector settings, wait for them all to have completed
|
|
33
|
+
# fmt: off
|
|
34
|
+
yield from bps.mv(
|
|
35
|
+
analyser.low_energy, low_energy,
|
|
36
|
+
analyser.high_energy, high_energy,
|
|
37
|
+
analyser.slices, region.slices,
|
|
38
|
+
analyser.lens_mode, region.lens_mode,
|
|
39
|
+
analyser.pass_energy, pass_energy,
|
|
40
|
+
analyser.iterations, region.iterations,
|
|
41
|
+
analyser.acquisition_mode, region.acquisition_mode,
|
|
42
|
+
)
|
|
43
|
+
# fmt: on
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def configure_specs(
|
|
47
|
+
analyser: SpecsAnalyserDriverIO, region: SpecsRegion, excitation_energy: float
|
|
48
|
+
):
|
|
49
|
+
yield from configure_analyser(analyser, region, excitation_energy)
|
|
50
|
+
# fmt: off
|
|
51
|
+
yield from bps.mv(
|
|
52
|
+
analyser.values, region.values,
|
|
53
|
+
analyser.psu_mode, region.psu_mode,
|
|
54
|
+
)
|
|
55
|
+
# fmt: on
|
|
56
|
+
if region.acquisition_mode == "Fixed Transmission":
|
|
57
|
+
yield from bps.mv(analyser.centre_energy, region.centre_energy)
|
|
58
|
+
|
|
59
|
+
if region.acquisition_mode == "Fixed Energy":
|
|
60
|
+
yield from bps.mv(analyser.energy_step, region.energy_step)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def configure_vgscienta(
|
|
64
|
+
analyser: VGScientaAnalyserDriverIO, region: VGScientaRegion, excitation_energy
|
|
65
|
+
):
|
|
66
|
+
yield from configure_analyser(analyser, region, excitation_energy)
|
|
67
|
+
centre_energy = region.to_kinetic_energy(region.fix_energy, excitation_energy)
|
|
68
|
+
|
|
69
|
+
# fmt: off
|
|
70
|
+
yield from bps.mv(
|
|
71
|
+
analyser.centre_energy, centre_energy,
|
|
72
|
+
analyser.energy_step, region.energy_step,
|
|
73
|
+
analyser.first_x_channel, region.first_x_channel,
|
|
74
|
+
analyser.first_y_channel, region.first_y_channel,
|
|
75
|
+
analyser.x_channel_size, region.x_channel_size(),
|
|
76
|
+
analyser.y_channel_size, region.y_channel_size(),
|
|
77
|
+
analyser.detector_mode, region.detector_mode,
|
|
78
|
+
analyser.image_mode, "Single",
|
|
79
|
+
)
|
|
80
|
+
# fmt: on
|
dodal/plan_stubs/motor_utils.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import uuid
|
|
2
2
|
from collections.abc import Generator
|
|
3
|
-
from typing import Any
|
|
3
|
+
from typing import Any
|
|
4
4
|
|
|
5
5
|
from bluesky import plan_stubs as bps
|
|
6
6
|
from bluesky.preprocessors import finalize_wrapper, pchain
|
|
@@ -8,14 +8,14 @@ from bluesky.utils import Msg, MsgGenerator, make_decorator
|
|
|
8
8
|
from ophyd_async.core import Device
|
|
9
9
|
from ophyd_async.epics.motor import Motor
|
|
10
10
|
|
|
11
|
-
from dodal.utils import MovableReadable
|
|
12
|
-
|
|
13
|
-
MovableReadableDevice = TypeVar("MovableReadableDevice", bound=MovableReadable)
|
|
14
|
-
|
|
15
11
|
|
|
16
12
|
class MoveTooLarge(Exception):
|
|
17
13
|
def __init__(
|
|
18
|
-
self,
|
|
14
|
+
self,
|
|
15
|
+
axis: Motor,
|
|
16
|
+
maximum_move: float,
|
|
17
|
+
position: float,
|
|
18
|
+
*args: object,
|
|
19
19
|
) -> None:
|
|
20
20
|
self.axis = axis
|
|
21
21
|
self.maximum_move = maximum_move
|
|
@@ -24,10 +24,10 @@ class MoveTooLarge(Exception):
|
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
def check_and_cache_values(
|
|
27
|
-
devices_and_positions: dict[
|
|
27
|
+
devices_and_positions: dict[Motor, float],
|
|
28
28
|
smallest_move: float,
|
|
29
29
|
maximum_move: float,
|
|
30
|
-
) -> Generator[Msg, Any, dict[
|
|
30
|
+
) -> Generator[Msg, Any, dict[Motor, float]]:
|
|
31
31
|
"""Caches the positions of all Motors on specified device if they are within
|
|
32
32
|
smallest_move of home_position. Throws MoveTooLarge if they are outside maximum_move
|
|
33
33
|
of the home_position
|
|
@@ -51,9 +51,7 @@ def home_and_reset_wrapper(
|
|
|
51
51
|
wait_for_all: bool = True,
|
|
52
52
|
) -> MsgGenerator:
|
|
53
53
|
home_positions = {
|
|
54
|
-
|
|
55
|
-
for _, axis in device.children()
|
|
56
|
-
if isinstance(axis, Motor)
|
|
54
|
+
axis: 0.0 for _, axis in device.children() if isinstance(axis, Motor)
|
|
57
55
|
}
|
|
58
56
|
return move_and_reset_wrapper(
|
|
59
57
|
plan, home_positions, smallest_move, maximum_move, group, wait_for_all
|
|
@@ -62,7 +60,7 @@ def home_and_reset_wrapper(
|
|
|
62
60
|
|
|
63
61
|
def move_and_reset_wrapper(
|
|
64
62
|
plan: MsgGenerator,
|
|
65
|
-
device_and_positions: dict[
|
|
63
|
+
device_and_positions: dict[Motor, float],
|
|
66
64
|
smallest_move: float,
|
|
67
65
|
maximum_move: float,
|
|
68
66
|
group: str | None = None,
|
dodal/utils.py
CHANGED
|
@@ -15,11 +15,9 @@ from types import ModuleType
|
|
|
15
15
|
from typing import (
|
|
16
16
|
Any,
|
|
17
17
|
Generic,
|
|
18
|
-
Protocol,
|
|
19
18
|
TypeAlias,
|
|
20
19
|
TypeGuard,
|
|
21
20
|
TypeVar,
|
|
22
|
-
runtime_checkable,
|
|
23
21
|
)
|
|
24
22
|
|
|
25
23
|
from bluesky.protocols import (
|
|
@@ -62,11 +60,6 @@ BLUESKY_PROTOCOLS = [
|
|
|
62
60
|
Triggerable,
|
|
63
61
|
]
|
|
64
62
|
|
|
65
|
-
|
|
66
|
-
@runtime_checkable
|
|
67
|
-
class MovableReadable(Movable, Readable, Protocol): ...
|
|
68
|
-
|
|
69
|
-
|
|
70
63
|
AnyDevice: TypeAlias = OphydV1Device | OphydV2Device
|
|
71
64
|
V1DeviceFactory: TypeAlias = Callable[..., OphydV1Device]
|
|
72
65
|
V2DeviceFactory: TypeAlias = Callable[..., OphydV2Device]
|
dodal/devices/i13_1/merlin_io.py
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
from ophyd_async.core import StrictEnum
|
|
2
|
-
from ophyd_async.epics import adcore
|
|
3
|
-
from ophyd_async.epics.core import epics_signal_rw_rbv
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class MerlinImageMode(StrictEnum):
|
|
7
|
-
SINGLE = "Single"
|
|
8
|
-
MULTIPLE = "Multiple"
|
|
9
|
-
CONTINUOUS = "Continuous"
|
|
10
|
-
THRESHOLD = "Threshold"
|
|
11
|
-
BACKGROUND = "Background"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class MerlinDriverIO(adcore.ADBaseIO):
|
|
15
|
-
def __init__(self, prefix: str, name: str = "") -> None:
|
|
16
|
-
super().__init__(prefix, name)
|
|
17
|
-
self.image_mode = epics_signal_rw_rbv(MerlinImageMode, prefix + "ImageMode")
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"XRatio": 0.485,
|
|
3
|
-
"YRatio": 0.353,
|
|
4
|
-
"tolerance": 1.0,
|
|
5
|
-
"1.0": {
|
|
6
|
-
"position": 1.0,
|
|
7
|
-
"micronsPerXPixel": 2.309,
|
|
8
|
-
"micronsPerYPixel": 3.266
|
|
9
|
-
},
|
|
10
|
-
"2.0": {
|
|
11
|
-
"position": 2.0,
|
|
12
|
-
"micronsPerXPixel": 1.775,
|
|
13
|
-
"micronsPerYPixel": 2.493
|
|
14
|
-
},
|
|
15
|
-
"3.0": {
|
|
16
|
-
"position": 3.0,
|
|
17
|
-
"micronsPerXPixel": 1.334,
|
|
18
|
-
"micronsPerYPixel": 1.856
|
|
19
|
-
},
|
|
20
|
-
"4.0": {
|
|
21
|
-
"position": 4.0,
|
|
22
|
-
"micronsPerXPixel": 1.037,
|
|
23
|
-
"micronsPerYPixel": 1.425
|
|
24
|
-
},
|
|
25
|
-
"5.0": {
|
|
26
|
-
"position": 5.0,
|
|
27
|
-
"micronsPerXPixel": 0.798,
|
|
28
|
-
"micronsPerYPixel": 1.091
|
|
29
|
-
},
|
|
30
|
-
"6.0": {
|
|
31
|
-
"position": 6.0,
|
|
32
|
-
"micronsPerXPixel": 0.627,
|
|
33
|
-
"micronsPerYPixel": 0.870
|
|
34
|
-
},
|
|
35
|
-
"7.0": {
|
|
36
|
-
"position": 7.0,
|
|
37
|
-
"micronsPerXPixel": 0.487,
|
|
38
|
-
"micronsPerYPixel": 0.666
|
|
39
|
-
},
|
|
40
|
-
"8.0": {
|
|
41
|
-
"position": 8.0,
|
|
42
|
-
"micronsPerXPixel": 0.379,
|
|
43
|
-
"micronsPerYPixel": 0.516
|
|
44
|
-
},
|
|
45
|
-
"9.0": {
|
|
46
|
-
"position": 9.0,
|
|
47
|
-
"micronsPerXPixel": 0.289,
|
|
48
|
-
"micronsPerYPixel": 0.405
|
|
49
|
-
},
|
|
50
|
-
"10.0": {
|
|
51
|
-
"position": 10.0,
|
|
52
|
-
"micronsPerXPixel": 0.227,
|
|
53
|
-
"micronsPerYPixel": 0.314
|
|
54
|
-
}
|
|
55
|
-
}
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
from ophyd_async.core import Reference, SignalR
|
|
2
|
-
from PIL import Image, ImageDraw
|
|
3
|
-
|
|
4
|
-
from dodal.devices.areadetector.plugins.MJPG import MJPG
|
|
5
|
-
|
|
6
|
-
CROSSHAIR_LENGTH_PX = 20
|
|
7
|
-
CROSSHAIR_OUTLINE_COLOUR = "Black"
|
|
8
|
-
CROSSHAIR_FILL_COLOUR = "White"
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def draw_crosshair(image: Image.Image, beam_x: int, beam_y: int):
|
|
12
|
-
draw = ImageDraw.Draw(image)
|
|
13
|
-
OUTLINE_WIDTH = 1
|
|
14
|
-
HALF_LEN = CROSSHAIR_LENGTH_PX / 2
|
|
15
|
-
draw.rectangle(
|
|
16
|
-
[
|
|
17
|
-
beam_x - OUTLINE_WIDTH,
|
|
18
|
-
beam_y - HALF_LEN - OUTLINE_WIDTH,
|
|
19
|
-
beam_x + OUTLINE_WIDTH,
|
|
20
|
-
beam_y + HALF_LEN + OUTLINE_WIDTH,
|
|
21
|
-
],
|
|
22
|
-
fill=CROSSHAIR_OUTLINE_COLOUR,
|
|
23
|
-
)
|
|
24
|
-
draw.rectangle(
|
|
25
|
-
[
|
|
26
|
-
beam_x - HALF_LEN - OUTLINE_WIDTH,
|
|
27
|
-
beam_y - OUTLINE_WIDTH,
|
|
28
|
-
beam_x + HALF_LEN + OUTLINE_WIDTH,
|
|
29
|
-
beam_y + OUTLINE_WIDTH,
|
|
30
|
-
],
|
|
31
|
-
fill=CROSSHAIR_OUTLINE_COLOUR,
|
|
32
|
-
)
|
|
33
|
-
draw.line(
|
|
34
|
-
((beam_x, beam_y - HALF_LEN), (beam_x, beam_y + HALF_LEN)),
|
|
35
|
-
fill=CROSSHAIR_FILL_COLOUR,
|
|
36
|
-
)
|
|
37
|
-
draw.line(
|
|
38
|
-
((beam_x - HALF_LEN, beam_y), (beam_x + HALF_LEN, beam_y)),
|
|
39
|
-
fill=CROSSHAIR_FILL_COLOUR,
|
|
40
|
-
)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
class SnapshotWithBeamCentre(MJPG):
|
|
44
|
-
"""A child of MJPG which, when triggered, draws an outlined crosshair at the beam
|
|
45
|
-
centre in the image and saves the image to disk."""
|
|
46
|
-
|
|
47
|
-
def __init__(
|
|
48
|
-
self,
|
|
49
|
-
prefix: str,
|
|
50
|
-
beam_x_signal: SignalR,
|
|
51
|
-
beam_y_signal: SignalR,
|
|
52
|
-
name: str = "",
|
|
53
|
-
) -> None:
|
|
54
|
-
with self.add_children_as_readables():
|
|
55
|
-
self._beam_centre_i_ref = Reference(beam_x_signal)
|
|
56
|
-
self._beam_centre_j_ref = Reference(beam_y_signal)
|
|
57
|
-
super().__init__(prefix, name)
|
|
58
|
-
|
|
59
|
-
async def post_processing(self, image: Image.Image):
|
|
60
|
-
beam_x = await self._beam_centre_i_ref().get_value()
|
|
61
|
-
beam_y = await self._beam_centre_j_ref().get_value()
|
|
62
|
-
draw_crosshair(image, beam_x, beam_y)
|
|
63
|
-
|
|
64
|
-
await self._save_image(image)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|