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
stage_aware_camera.py
1#!/usr/bin/env python3
2
3## Copyright (C) 2020 David Miguel Susano Pinto <carandraug@gmail.com>
4## Copyright (C) 2020 Ian Dobbie <ian.dobbie@bioch.ox.ac.uk>
5##
6## This file is part of Microscope.
7##
8## Microscope is free software: you can redistribute it and/or modify
9## it under the terms of the GNU General Public License as published by
10## the Free Software Foundation, either version 3 of the License, or
11## (at your option) any later version.
12##
13## Microscope is distributed in the hope that it will be useful,
14## but WITHOUT ANY WARRANTY; without even the implied warranty of
15## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16## GNU General Public License for more details.
17##
18## You should have received a copy of the GNU General Public License
19## along with Microscope. If not, see <http://www.gnu.org/licenses/>.
20
21"""Simulation of a full setup based on a given image file.
22"""
23
24import logging
25import time
26import typing
27
28import numpy as np
29import PIL.Image
30import scipy.ndimage
31
32import microscope
33import microscope.abc
34from microscope.simulators import (
35 SimulatedCamera,
36 SimulatedFilterWheel,
37 SimulatedStage,
38)
39
40
41_logger = logging.getLogger(__name__)
42
43
45 """Simulated camera that returns subregions of image based on stage
46 position.
47
48 Instead of using this class directly, consider using the
49 :func:`simulated_setup_from_image` function which will generate
50 all the required simulated devices for a given image file.
51
52 Args:
53 image: the image from which regions will be cropped based on
54 the stage and filter wheel positions.
55 stage: stage to read coordinates from. Must have an "x",
56 "y", and "z" axis.
57 filterwheel: filter wheel to read position.
58
59 """
60
62 self,
63 image: np.ndarray,
65 filterwheel: microscope.abc.FilterWheel,
66 **kwargs,
67 ) -> None:
68 super().__init__(**kwargs)
69 self._image = image
70 self._stage = stage
71 self._filterwheel = filterwheel
72 self._pixel_size = 1.0
73
74 if not all([name in stage.axes.keys() for name in ["x", "y", "z"]]):
76 "stage for StageAwareCamera requires x, y, and z axis"
77 )
78 if image.shape[2] != self._filterwheel.n_positions:
79 raise ValueError(
80 "image has %d channels but filterwheel has %d positions"
81 )
82
83 # Empty the settings dict, most of them are for testing
84 # settings, and the rest is specific to the image generator
85 # which we don't need. We probably should have a simpler
86 # SimulatedCamera that we could subclass.
87 self._settings = {}
88
89 self.add_setting(
90 "pixel size",
91 "float",
92 lambda: self._pixel_size,
93 lambda pxsz: setattr(self, "_pixel_size", pxsz),
94 # technically should be: (nextafter(0.0, inf), nextafter(inf, 0.0))
95 values=(0.0, float("inf")),
96 )
97
98 def _fetch_data(self) -> typing.Optional[np.ndarray]:
99 if not self._acquiring_acquiring or self._triggered_triggered == 0:
100 return None
101
102 time.sleep(self._exposure_time)
103 self._triggered_triggered -= 1
104 _logger.info("Creating image")
105
106 # Use stage position to compute bounding box.
107 width = self._roi.width // self._binning.h
108 height = self._roi.height // self._binning.v
109 x = int((self._stage.position["x"] / self._pixel_size) - (width / 2))
110 y = int((self._stage.position["y"] / self._pixel_size) - (height / 2))
111
112 # Use filter wheel position to select the image channel.
113 channel = self._filterwheel.position
114
115 subsection = self._image[y : y + height, x : x + width, channel]
116
117 # Gaussian filter on abs Z position to simulate being out of
118 # focus (Z position zero is in focus).
119 blur = abs((self._stage.position["z"]) / 10.0)
120 image = scipy.ndimage.gaussian_filter(subsection, blur)
121
122 self._sent += 1
123 # Not sure this flipping is correct but it's required to make
124 # cockpit mosaic work. This is probably related to not having
125 # defined what the image origin should be (see issue #89).
126 return np.fliplr(np.flipud(image))
127
128
129def simulated_setup_from_image(
130 filepath: str, **kwargs
131) -> typing.Dict[str, microscope.abc.Device]:
132 """Create simulated devices given an image file.
133
134 To use with the `device-server`::
135
136 DEVICES = [
137 device(simulated_setup_from_image, 'localhost', 8000,
138 conf={'filepath': path_to_image_file}),
139 ]
140 """
141 PIL.Image.MAX_IMAGE_PIXELS = None
142 image = np.array(PIL.Image.open(filepath))
143 if len(image.shape) < 3:
144 raise ValueError("not an RGB image")
145
146 stage = SimulatedStage(
147 {
148 "x": microscope.AxisLimits(0, image.shape[0]),
149 "y": microscope.AxisLimits(0, image.shape[1]),
150 "z": microscope.AxisLimits(-50, 50),
151 }
152 )
153 filterwheel = SimulatedFilterWheel(positions=image.shape[2])
154 camera = StageAwareCamera(image, stage, filterwheel)
155
156 return {
157 "camera": camera,
158 "filterwheel": filterwheel,
159 "stage": stage,
160 }
None add_setting(self, name, dtype, get_func, set_func, values, typing.Optional[typing.Callable[[], bool]] readonly=None)
Definition: abc.py:407
None __init__(self, np.ndarray image, microscope.abc.Stage stage, microscope.abc.FilterWheel filterwheel, **kwargs)