BioImager  3.9.1
A .NET microscopy imaging library. Supports various microscopes by using imported libraries & GUI automation. Supported libraries include PriorĀ® & ZeissĀ® & all devices supported by Micromanager 2.0 and python-microscope.
Loading...
Searching...
No Matches
alpao.py
1#!/usr/bin/env python3
2
3## Copyright (C) 2020 David Miguel Susano Pinto <carandraug@gmail.com>
4##
5## This file is part of Microscope.
6##
7## Microscope is free software: you can redistribute it and/or modify
8## it under the terms of the GNU General Public License as published by
9## the Free Software Foundation, either version 3 of the License, or
10## (at your option) any later version.
11##
12## Microscope is distributed in the hope that it will be useful,
13## but WITHOUT ANY WARRANTY; without even the implied warranty of
14## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15## GNU General Public License for more details.
16##
17## You should have received a copy of the GNU General Public License
18## along with Microscope. If not, see <http://www.gnu.org/licenses/>.
19
20import ctypes
21import warnings
22
23import numpy
24
25import microscope
26import microscope.abc
27
28
29try:
30 import microscope._wrappers.asdk as asdk
31except Exception as e:
32 raise microscope.LibraryLoadError(e) from e
33
34
36 """Alpao deformable mirror.
37
38 The Alpao mirrors support hardware triggers modes
39 `TriggerMode.ONCE` and `TriggerMode.START`. By default, they will
40 be set for software triggering, and trigger once.
41
42 Args:
43 serial_number: the serial number of the deformable mirror,
44 something like `"BIL103"`.
45 """
46
47 _TriggerType_to_asdkTriggerIn = {
48 microscope.TriggerType.SOFTWARE: 0,
49 microscope.TriggerType.RISING_EDGE: 1,
50 microscope.TriggerType.FALLING_EDGE: 2,
51 }
52
53 _supported_TriggerModes = [
54 microscope.TriggerMode.ONCE,
55 microscope.TriggerMode.START,
56 ]
57
58 @staticmethod
59 def _normalize_patterns(patterns: numpy.ndarray) -> numpy.ndarray:
60 """
61 Alpao SDK expects values in the [-1 1] range, so we normalize
62 them from the [0 1] range we expect in our interface.
63 """
64 patterns = (patterns * 2) - 1
65 return patterns
66
67 def _find_error_str(self) -> str:
68 """Get an error string from the Alpao SDK error stack.
69
70 Returns:
71 A string with error message. An empty string if there was
72 no error on the stack.
73 """
74 err_msg_buffer_len = 64
75 err_msg_buffer = ctypes.create_string_buffer(err_msg_buffer_len)
76
77 err = ctypes.pointer(asdk.UInt(0))
78 status = asdk.GetLastError(err, err_msg_buffer, err_msg_buffer_len)
79 if status == asdk.SUCCESS:
80 msg = err_msg_buffer.value
81 if len(msg) > err_msg_buffer_len:
82 msg = msg + b"..."
83 msg += b" (error code %i)" % (err.contents.value)
84 return msg.decode()
85 else:
86 return ""
87
88 def _raise_if_error(
89 self, status: int, exception_cls=microscope.DeviceError
90 ) -> None:
91 if status != asdk.SUCCESS:
92 msg = self._find_error_str()
93 if msg:
94 raise exception_cls(msg)
95
96 def __init__(self, serial_number: str, **kwargs) -> None:
97 super().__init__(**kwargs)
98 self._dm = asdk.Init(serial_number.encode())
99 if not self._dm:
101 "Failed to initialise connection: don't know why"
102 )
103 # In theory, asdkInit should return a NULL pointer in case of
104 # failure and that should be enough to check. However, at least
105 # in the case of a missing configuration file it still returns a
106 # DM pointer so we still need to check for errors on the stack.
107 self._raise_if_error(asdk.FAILURE)
108
109 value = asdk.Scalar_p(asdk.Scalar())
110 status = asdk.Get(self._dm, b"NbOfActuator", value)
111 self._raise_if_error(status)
112 self._n_actuators = int(value.contents.value)
113 self._trigger_type = microscope.TriggerType.SOFTWARE
114 self._trigger_mode = microscope.TriggerMode.ONCE
115
116 @property
117 def n_actuators(self) -> int:
118 return self._n_actuators
119
120 @property
121 def trigger_mode(self) -> microscope.TriggerMode:
122 return self._trigger_mode
123
124 @property
125 def trigger_type(self) -> microscope.TriggerType:
126 return self._trigger_type
127
128 def _do_apply_pattern(self, pattern: numpy.ndarray) -> None:
129 pattern = self._normalize_patterns(pattern)
130 data_pointer = pattern.ctypes.data_as(asdk.Scalar_p)
131 status = asdk.Send(self._dm, data_pointer)
132 self._raise_if_error(status)
133
134 def set_trigger(self, ttype, tmode):
135 if tmode not in self._supported_TriggerModes:
137 "unsupported trigger of mode '%s' for Alpao Mirrors"
138 % tmode.name
139 )
140 elif (
141 ttype == microscope.TriggerType.SOFTWARE
142 and tmode != microscope.TriggerMode.ONCE
143 ):
145 "trigger mode '%s' only supports trigger type ONCE"
146 % tmode.name
147 )
148 self._trigger_mode = tmode
149
150 try:
151 value = self._TriggerType_to_asdkTriggerIn[ttype]
152 except KeyError:
154 "unsupported trigger of type '%s' for Alpao Mirrors"
155 % ttype.name
156 )
157 status = asdk.Set(self._dm, b"TriggerIn", value)
158 self._raise_if_error(status)
159 self._trigger_type = ttype
160
161 def queue_patterns(self, patterns: numpy.ndarray) -> None:
162 if self._trigger_type == microscope.TriggerType.SOFTWARE:
163 super().queue_patterns(patterns)
164 return
165
166 self._validate_patterns(patterns)
167 patterns = self._normalize_patterns(patterns)
168 patterns = numpy.atleast_2d(patterns)
169 n_patterns: int = patterns.shape[0]
170
171 # The Alpao SDK seems to only support the trigger mode start. It
172 # still has option called nRepeats that we can't really figure
173 # what is meant to do. When set to 1, the mode is start. What
174 # we want it is to have trigger mode once which was not
175 # supported. We have received a modified version where if
176 # nRepeats is set to same number of patterns, does trigger mode
177 # once (not documented on Alpao SDK).
178 if self._trigger_mode == microscope.TriggerMode.ONCE:
179 n_repeats = n_patterns
180 elif self._trigger_mode == microscope.TriggerMode.START:
181 n_repeats = 1
182 else:
183 # We should not get here in the first place since
184 # set_trigger filters unsupported modes.
186 "trigger type '%s' and trigger mode '%s' is not supported"
187 % (self._trigger_type.name, self._trigger_mode.name)
188 )
189
190 data_pointer = patterns.ctypes.data_as(asdk.Scalar_p)
191
192 # We don't know if the previous queue of pattern ran until the
193 # end, so we need to clear it before sending (see issue #50)
194 status = asdk.Stop(self._dm)
195 self._raise_if_error(status)
196
197 status = asdk.SendPattern(
198 self._dm, data_pointer, n_patterns, n_repeats
199 )
200 self._raise_if_error(status)
201
202 def _do_shutdown(self) -> None:
203 status = asdk.Release(self._dm)
204 if status != asdk.SUCCESS:
205 msg = self._find_error_str()
206 warnings.warn(msg)
None _validate_patterns(self, numpy.ndarray patterns)
Definition: abc.py:1086
numpy.ndarray _normalize_patterns(numpy.ndarray patterns)
Definition: alpao.py:59
None _raise_if_error(self, int status, exception_cls=microscope.DeviceError)
Definition: alpao.py:90
def set_trigger(self, ttype, tmode)
Definition: alpao.py:134
None queue_patterns(self, numpy.ndarray patterns)
Definition: alpao.py:161