21"""Simulated devices for use during development.
23This module provides a series of test devices, which mimic real
24hardware behaviour. They implement the different ABC.
34from PIL import Image, ImageDraw, ImageFont
37import microscope._utils
41_logger = logging.getLogger(__name__)
44def _theta_generator():
45 """A generator that yields values between 0 and 2*pi"""
50 th = (th + 0.01 * TWOPI) % TWOPI
54 """Generates test images, with methods for configuration via a Setting."""
68 self.
_theta = _theta_generator()
71 self.
_font = ImageFont.load_default()
73 def enable_numbering(self, enab):
76 def get_data_types(self):
82 def set_data_type(self, index):
86 """Return the names of available image generation methods"""
87 return (m.__name__
for m
in self.
_methods)
90 """Return the index of the current image generation method."""
94 """Set the image generation method."""
97 def get_image(self, width, height, dark=0, light=255, index=None):
98 """Return an image using the currently selected method."""
102 data = m(width, height, dark, light).astype(d)
105 size = tuple(d + 2
for d
in self.
_font.getsize(text))
106 img = Image.new(
"L", size)
107 ctx = ImageDraw.Draw(img)
108 ctx.text((1, 1), text, fill=light)
109 data[0 : size[1], 0 : size[0]] = np.asarray(img)
113 """Ignores dark and light - returns zeros"""
114 return np.zeros((h, w))
117 """Ignores dark and light - returns max value for current data type."""
119 if issubclass(d, np.integer):
120 value = np.iinfo(d).max
123 return value * np.ones((h, w)).astype(d)
126 """A single gradient across the whole image from top left to bottom right."""
127 xx, yy = np.meshgrid(range(w), range(h))
128 return dark + light * (xx + yy) / (xx.max() + yy.max())
132 return np.random.randint(dark, light, size=(h, w))
134 def one_gaussian(self, w, h, dark, light):
136 sigma = 0.01 * max(w, h)
137 x0 = np.random.randint(w)
138 y0 = np.random.randint(h)
139 xx, yy = np.meshgrid(range(w), range(h))
140 return dark + light * np.exp(
141 -((xx - x0) ** 2 + (yy - y0) ** 2) / (2 * sigma**2)
145 """A sawtooth gradient that rotates about 0,0."""
147 xx, yy = np.meshgrid(range(w), range(h))
148 wrap = 0.1 * max(xx.max(), yy.max())
149 return dark + light * ((np.sin(th) * xx + np.cos(th) * yy) % wrap) / (
179 "display image number",
191 lambda val: setattr(self,
"_a_setting", val),
216 def _set_error_percent(self, value):
220 def _set_gain(self, value):
223 def _purge_buffers(self):
224 """Purge buffers on both camera and PC."""
225 _logger.info(
"Purging buffers.")
227 def _create_buffers(self):
228 """Create buffers and store values needed to remove padding later."""
230 _logger.info(
"Creating buffers.")
232 def _fetch_data(self):
235 _logger.info(
"Raising exception")
237 "Exception raised in SimulatedCamera._fetch_data"
239 _logger.info(
"Sending image")
243 dark = int(32 * np.random.rand())
244 light = int(255 - 128 * np.random.rand())
248 width, height, dark, light, index=self.
_sent
254 _logger.info(
"Disabling acquisition; %d images sent.", self.
_sent)
258 def _do_disable(self):
261 def _do_enable(self):
262 _logger.info(
"Preparing for acquisition.")
268 _logger.info(
"Acquisition enabled.")
280 def _get_sensor_shape(self):
283 def soft_trigger(self):
287 def _do_trigger(self) -> None:
294 def _get_binning(self):
297 @microscope.abc.keep_acquiring
298 def _set_binning(self, binning):
304 @microscope.abc.keep_acquiring
305 def _set_roi(self, roi):
308 def _do_shutdown(self) -> None:
316 self._devices = devices.copy()
319 def devices(self) -> typing.Mapping[str, microscope.abc.Device]:
324 def __init__(self, **kwargs):
325 super().__init__(**kwargs)
328 def _do_get_position(self):
331 def _do_set_position(self, position):
332 _logger.info(
"Setting position to %s", position)
335 def _do_shutdown(self) -> None:
339class SimulatedLightSource(
343 def __init__(self, **kwargs):
344 super().__init__(**kwargs)
346 self._emission =
False
351 def _do_enable(self):
355 def _do_shutdown(self) -> None:
358 def _do_disable(self):
359 self._emission =
False
360 return self._emission
365 def _do_set_power(self, power: float) ->
None:
366 _logger.info(
"Power set to %s.", power)
369 def _do_get_power(self) -> float:
380 def __init__(self, n_actuators, **kwargs):
381 super().__init__(**kwargs)
384 def _do_shutdown(self) -> None:
388 def n_actuators(self) -> int:
391 def _do_apply_pattern(self, pattern):
395 """Method for debug purposes only.
397 This method is not part of the DeformableMirror ABC, it only
398 exists on this test device to help during development.
404 def __init__(self, limits: microscope.AxisLimits) ->
None:
417 def limits(self) -> microscope.AxisLimits:
433 """A test stage with any number of axis.
436 limits: map of test axis to be created and their limits.
438 .. code-block:: python
458 super().__init__(**kwargs)
463 def _do_shutdown(self) -> None:
470 def axes(self) -> typing.Mapping[str, microscope.abc.StageAxis]:
473 def move_by(self, delta: typing.Mapping[str, float]) ->
None:
474 for name, rpos
in delta.items():
477 def move_to(self, position: typing.Mapping[str, float]) ->
None:
478 for name, pos
in position.items():
None add_setting(self, name, dtype, get_func, set_func, values, typing.Optional[typing.Callable[[], bool]] readonly=None)
None move_to(self, float pos)
typing.Mapping[str, StageAxis] axes(self)
def gradient(self, w, h, dark, light)
def white(self, w, h, dark, light)
def one_gaussian(self, w, h, dark, light)
def black(self, w, h, dark, light)
def sawtooth(self, w, h, dark, light)
def get_image(self, width, height, dark=0, light=255, index=None)
def noise(self, w, h, dark, light)
def set_method(self, index)
def _set_gain(self, value)
def get_exposure_time(self)
def __init__(self, **kwargs)
def set_exposure_time(self, value)
def _create_buffers(self)
def _set_error_percent(self, value)
None move_by(self, float delta)
microscope.AxisLimits limits(self)
None move_to(self, float pos)
None move_by(self, typing.Mapping[str, float] delta)
None move_to(self, typing.Mapping[str, float] position)
typing.Mapping[str, microscope.abc.StageAxis] axes(self)
bool may_move_on_enable(self)