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
picam.py
1#!/usr/bin/env python3
2
3## Copyright (C) 2016-2017 Mick Phillips <mick.phillips@gmail.com>
4## Copyright (C) 2019 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
21import enum
22import logging
23import queue
24import time
25from io import BytesIO
26
27import numpy as np
28import picamera
29import picamera.array
30import RPi.GPIO as GPIO
31
32import microscope.abc
33from microscope import ROI, Binning, TriggerMode, TriggerType
34from microscope.abc import keep_acquiring
35
36
37GPIO_Trigger = 21
38GPIO_CAMLED = 5
39
40_logger = logging.getLogger(__name__)
41
42
43# Trigger types.
44@enum.unique
45class TrgSourceMap(enum.Enum):
46 SOFTWARE = TriggerType.SOFTWARE
47 EDGE_RISING = TriggerType.RISING_EDGE
48
49
51 def __init__(self, *args, **kwargs):
52 super(PiCamera, self).__init__(**kwargs)
53 # example parameter to allow setting.
54 # self.add_setting('_error_percent', 'int',
55 # lambda: self._error_percent,
56 # self._set_error_percent,
57 # lambda: (0, 100))
58 self._acquiring_acquiring = False
59 self._exposure_time = 0.1
60 self._triggered = False
61 self.camera = None
62 # Region of interest.
63 self.roi = ROI(None, None, None, None)
64 # Cycle time
65 self.exposure_time = 0.001 # in seconds
66 self.cycle_time = self.exposure_time
67 # initialise in soft trigger mode
68 self._trigger_type = TriggerType.SOFTWARE
69 # setup hardware triggerline
70 GPIO.setmode(GPIO.BCM)
71 # GPIO trigger line is an input
72 GPIO.setup(GPIO_Trigger, GPIO.IN)
73 # GPIO control over camera LED is an output
74 GPIO.setup(GPIO_CAMLED, GPIO.OUT)
75 # add trigger to settings
76 trg_source_names = [x.name for x in TrgSourceMap]
77 # set up queue to store images as they are acquired
78 self._queue = queue.Queue()
79 self._awb_modes = picamera.PiCamera.AWB_MODES
80 self._iso_modes = [0, 100, 200, 320, 400, 500, 640, 800]
81
82 def _trigger_source_setter(index: int) -> None:
83 trigger_type = TrgSourceMap[trg_source_names[index]].value
85
86 self.add_setting(
87 "trig source",
88 "enum",
89 lambda: TrgSourceMap(self._trigger_type).name,
90 _trigger_source_setter,
91 trg_source_names,
92 )
93 self.add_setting(
94 "AWB",
95 "enum",
96 lambda: self._awb_modes[self.get_awb_mode()],
97 lambda awb: self.set_awb_mode(awb),
98 values=(list(self._awb_modes.keys())),
99 )
100
101 self.add_setting(
102 "ISO",
103 "enum",
104 lambda: self._iso_modes.index(self.camera.iso),
105 lambda iso: self.set_iso_mode(iso),
106 values=(self._iso_modes),
107 )
108
109 # self.add_setting(
110 # "pixel size",
111 # "float",
112 # lambda: self._pixel_size,
113 # lambda pxsz: setattr(self, "_pixel_size", pxsz),
114 # # technically should be: (nextafter(0.0, inf), nextafter(inf, 0.0))
115 # values=(0.0, float("inf")),
116 # )
118
119 def get_awb_mode(self):
120 return self.camera.awb_mode
121
122 def set_awb_mode(self, val):
123 for key, value in self._awb_modes.items():
124 if value == val:
125 self.camera.awb_mode = key
126
127 def set_iso_mode(self, val):
128 self.camera.iso = self._iso_modes[val]
129
130 def HW_trigger(self, channel):
131 """Function called by GPIO interupt, needs to trigger image capture"""
132 with picamera.array.PiYUVArray(self.camera) as output:
133 self.camera.capture(output, format="yuv", use_video_port=False)
134 self._queue.put(output.array[:, :, 0])
135
136 def _fetch_data(self):
137 if self._queue.qsize() is not 0:
138 data = self._queue.get()
139 _logger.info("Sending image")
140 return data
141 else:
142 return None
143
144 def initialize(self):
145 """Initialise the Pi Camera camera.
146 Open the connection, connect properties and populate settings dict.
147 """
148 if not self.camera:
149 try:
150 # initialise camera in still image mode.
151 self.camera = picamera.PiCamera(sensor_mode=2)
152 except:
153 raise Exception("Problem opening camera.")
154 _logger.info("Initializing camera.")
155 self.camversion = self.camera.revision
156 _logger.info("cam version " + self.camversion)
157
158 # create img buffer to hold images.
159 # disable camera LED by default
160 self.setLED(False)
161 self.set_awb_mode(0) # set auto white balance to off
163
164 def make_safe(self):
165 if self._acquiring_acquiring:
166 self.abortabort()
167
168 def _do_disable(self):
169 self.abortabort()
170
171 def _do_shutdown(self):
173 self.camera.close()
174
175 def _do_enable(self):
176 _logger.info("Preparing for acquisition.")
177 if self._acquiring_acquiring:
178 self.abortabort()
179 # actually start camera
180 if not self.camera:
182 self._acquiring_acquiring = True
183 _logger.info("Acquisition enabled.")
184 return True
185
186 def abort(self):
187 _logger.info("Disabling acquisition.")
188 if self._acquiring_acquiring:
189 self._acquiring_acquiring = False
190
192 self, ttype: TriggerType, tmode: TriggerMode
193 ) -> None:
194 if ttype == self._trigger_type:
195 return
196 elif ttype == TriggerType.SOFTWARE:
197 GPIO.remove_event_detect(GPIO_Trigger)
198 self._trigger_type = TriggerType.SOFTWARE
199 elif ttype == TriggerType.RISING_EDGE:
200 GPIO.add_event_detect(
201 GPIO_Trigger,
202 GPIO.RISING,
203 callback=self.HW_trigger,
204 bouncetime=10,
205 )
206 self._trigger_type = TriggerType.RISING_EDGE
207
208 @property
209 def trigger_mode(self) -> TriggerMode:
210 # if self._trigger_type==devices.TRIGGER_BEFORE:
211 return TriggerMode.ONCE
212
213 # else:
214 # return TriggerMode.ONCE
215
216 @property
217 def trigger_type(self) -> TriggerType:
218 return self._trigger_type
219
220 def _get_roi(self):
221 """Return the current ROI (left, top, width, height)."""
222 return self.roi
223
224 def _set_binning(self, h_bin, v_bin):
225 return True
226
227 def _get_binning(self):
228 return Binning(1, 1)
229
230 @keep_acquiring
231 def _set_roi(self, left, top, width, height):
232 """Set the ROI to (left, tip, width, height)."""
233 self.roi = ROI(left, top, width, height)
234
235 # set camera LED status, off is best for microscopy.
236 def setLED(self, state=False):
237 GPIO.output(GPIO_CAMLED, state)
238
239 def set_exposure_time(self, value):
240 # exposure times are set in us.
241 self.camera.shutter_speed = int(value * 1.0e6)
242
244 # exposure times are in us, so multiple by 1E-6 to get seconds.
245 return self.camera.exposure_speed * 1.0e-6
246
247 def get_cycle_time(self):
248 # fudge to make it work initially
249 # exposure times are in us, so multiple by 1E-6 to get seconds.
250 return self.camera.exposure_speed * 1.0e-6 + 0.1
251
252 def _get_sensor_shape(self):
253 if self.camversion == "ov5647": # picam version 1
254 self.camera.resolution = (2592, 1944)
255 # faqll back to defualt if not set above.
256 res = self.camera.resolution
257 self._set_roi_set_roi(0, 0, res[0], res[1])
258 return res
259
260 def _do_trigger(self):
261 self.soft_trigger()
262
263 def soft_trigger(self):
264 _logger.info(
265 "Trigger received; self._acquiring is %s." % self._acquiring_acquiring
266 )
267 if self._acquiring_acquiring:
268 with picamera.array.PiYUVArray(self.camera) as output:
269 self.camera.capture(output, format="yuv", use_video_port=False)
270 self._queue.put(output.array[:, :, 0])
271
272
273# ongoing implemetation notes
274
275# should be able to use rotation and hflip to set specific output image
276# rotations
277
278# roi's can be set with the zoom function, default is (0,0,1,1) meaning all the data.
279
280# Need to setup a buffer for harware triggered data aquisition so we can
281# call the acquisition and then download the data at our leasure
def _set_roi(self, microscope.ROI roi)
Definition: abc.py:970
typing.Tuple[int, int] _get_sensor_shape(self)
Definition: abc.py:916
None abort(self)
Definition: abc.py:588
None add_setting(self, name, dtype, get_func, set_func, values, typing.Optional[typing.Callable[[], bool]] readonly=None)
Definition: abc.py:407
def _do_disable(self)
Definition: abc.py:298
None initialize(self)
Definition: abc.py:339
None set_trigger(self, microscope.TriggerType ttype, microscope.TriggerMode tmode)
Definition: abc.py:249
microscope.TriggerMode trigger_mode(self)
Definition: abc.py:238
def HW_trigger(self, channel)
Definition: picam.py:130
def set_awb_mode(self, val)
Definition: picam.py:122
def set_iso_mode(self, val)
Definition: picam.py:127
TriggerMode trigger_mode(self)
Definition: picam.py:209
def __init__(self, *args, **kwargs)
Definition: picam.py:51
def set_exposure_time(self, value)
Definition: picam.py:239
None set_trigger(self, TriggerType ttype, TriggerMode tmode)
Definition: picam.py:193
def _set_roi(self, left, top, width, height)
Definition: picam.py:231
def setLED(self, state=False)
Definition: picam.py:236