|
| 1 | +#!/usr/bin/env python |
| 2 | +# -*- coding: utf-8 -*- |
| 3 | + |
| 4 | +## Copyright (C) 2016-2017 Mick Phillips <mick.phillips@gmail.com> |
| 5 | +## Copyright (C) 2019 Ian Dobbie <ian.dobbie@bioch.ox.ac.uk> |
| 6 | +## |
| 7 | +## This file is part of Microscope. |
| 8 | +## |
| 9 | +## Microscope is free software: you can redistribute it and/or modify |
| 10 | +## it under the terms of the GNU General Public License as published by |
| 11 | +## the Free Software Foundation, either version 3 of the License, or |
| 12 | +## (at your option) any later version. |
| 13 | +## |
| 14 | +## Microscope is distributed in the hope that it will be useful, |
| 15 | +## but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 16 | +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 17 | +## GNU General Public License for more details. |
| 18 | +## |
| 19 | +## You should have received a copy of the GNU General Public License |
| 20 | +## along with Microscope. If not, see <http://www.gnu.org/licenses/>. |
| 21 | + |
| 22 | +import time |
| 23 | + |
| 24 | +import Pyro4 |
| 25 | +import numpy as np |
| 26 | + |
| 27 | +from microscope import devices |
| 28 | +from microscope.devices import keep_acquiring, Binning, Roi |
| 29 | + |
| 30 | +#import raspberry pi specific modules |
| 31 | +import picamera |
| 32 | +import picamera.array |
| 33 | +from io import BytesIO |
| 34 | +#to allow hardware trigger. |
| 35 | +import RPi.GPIO as GPIO |
| 36 | +GPIO_Trigger=21 |
| 37 | + |
| 38 | + |
| 39 | +# Trigger mode to type. |
| 40 | +TRIGGER_MODES = { |
| 41 | + 'internal': None, |
| 42 | + 'external': devices.TRIGGER_BEFORE, |
| 43 | + 'external start': None, |
| 44 | + 'external exposure': devices.TRIGGER_DURATION, |
| 45 | + 'software': devices.TRIGGER_SOFT, |
| 46 | +} |
| 47 | + |
| 48 | + |
| 49 | +@Pyro4.expose |
| 50 | +@Pyro4.behavior('single') |
| 51 | +class PiCamera(devices.CameraDevice): |
| 52 | + def __init__(self, *args, **kwargs): |
| 53 | + super(PiCamera, self).__init__(**kwargs) |
| 54 | +#example parameter to allow setting. |
| 55 | +# self.add_setting('_error_percent', 'int', |
| 56 | +# lambda: self._error_percent, |
| 57 | +# self._set_error_percent, |
| 58 | +# lambda: (0, 100)) |
| 59 | + self._acquiring = False |
| 60 | + self._exposure_time = 0.1 |
| 61 | + self._triggered = False |
| 62 | + self.camera = None |
| 63 | + # Region of interest. |
| 64 | + self.roi = Roi(None, None, None, None) |
| 65 | + # Cycle time |
| 66 | + self.exposure_time = 0.001 # in seconds |
| 67 | + self.cycle_time = self.exposure_time |
| 68 | + #initialise in soft trigger mode |
| 69 | + self.trigger=devices.TRIGGER_SOFT |
| 70 | + #setup hardware triggerline |
| 71 | + GPIO.setmode(GPIO.BCM) |
| 72 | + GPIO.setup(GPIO_Trigger.GPIO.IN) |
| 73 | + |
| 74 | + #when a rasing edge is detected on port GPIO_Trigger, |
| 75 | + #regardless of whatever else is happening in the program, the |
| 76 | + #function self._HW_trigger will be run |
| 77 | + GPIO.add_event_detect(GPIO_Trigger, GPIO.RAISING, |
| 78 | + callback=self._HW_trigger, bouncetime=10) |
| 79 | + |
| 80 | + |
| 81 | + |
| 82 | + def _HW_trigger(self): |
| 83 | + '''Function called by GPIO interupt, needs to trigger image capture''' |
| 84 | + print ('PiCam HW trigger') |
| 85 | + |
| 86 | + |
| 87 | + def _fetch_data(self): |
| 88 | + if self._acquiring and self._triggered: |
| 89 | + with picamera.array.PiYUVArray(self.camera) as output: |
| 90 | + self.camera.capture(output, format='yuv', use_video_port = False) |
| 91 | + #just return intensity values |
| 92 | + self._logger.info('Sending image') |
| 93 | + self._triggered = False |
| 94 | + return(output.array[:,:,0]) |
| 95 | + |
| 96 | + def initialize(self): |
| 97 | + """Initialise the Pi Camera camera. |
| 98 | + Open the connection, connect properties and populate settings dict. |
| 99 | + """ |
| 100 | + if not self.camera: |
| 101 | + try: |
| 102 | + #initialise camera in still image mode. |
| 103 | + self.camera = picamera.PiCamera(sensor_mode=2) |
| 104 | + except: |
| 105 | + raise Exception("Problem opening camera.") |
| 106 | + self._logger.info('Initializing camera.') |
| 107 | + #create img buffer to hold images. |
| 108 | + #disable camera LED by default |
| 109 | + self.setLED(False) |
| 110 | + self._get_sensor_shape() |
| 111 | + |
| 112 | + def make_safe(self): |
| 113 | + if self._acquiring: |
| 114 | + self.abort() |
| 115 | + |
| 116 | + def _on_disable(self): |
| 117 | + self.abort() |
| 118 | + |
| 119 | + def _on_enable(self): |
| 120 | + self._logger.info("Preparing for acquisition.") |
| 121 | + if self._acquiring: |
| 122 | + self.abort() |
| 123 | + self._acquiring = True |
| 124 | + #actually start camera |
| 125 | + self._logger.info("Acquisition enabled.") |
| 126 | + return True |
| 127 | + |
| 128 | + def abort(self): |
| 129 | + self._logger.info('Disabling acquisition.') |
| 130 | + if self._acquiring: |
| 131 | + self._acquiring = False |
| 132 | + |
| 133 | + |
| 134 | + def set_trigger_type(self,trigger): |
| 135 | + if (trigger == devices.TRIGGER_SOFT): |
| 136 | + GPIO.remove_event_detect(GPIO_Trigger) |
| 137 | + self.trigger=devices.TRIGGER_SOFT |
| 138 | + elif (trigger == devices.TRIGGER_BEFORE): |
| 139 | + GPIO.add_event_detect(GPIO_Trigger,RISING, |
| 140 | + self.HWtrigger,self.exposure_time) |
| 141 | + self.trigger=devices.TRIGGER_BEFORE |
| 142 | + |
| 143 | + def get_trigger_type(self): |
| 144 | + return self.trigger |
| 145 | + |
| 146 | + def _get_roi(self): |
| 147 | + """Return the current ROI (left, top, width, height).""" |
| 148 | + return self.roi |
| 149 | + |
| 150 | + def _set_binning(self, h_bin, v_bin): |
| 151 | + return True |
| 152 | + |
| 153 | + def _get_binning(self): |
| 154 | + return(Binning(1,1)) |
| 155 | + |
| 156 | + @keep_acquiring |
| 157 | + def _set_roi(self, left, top, width, height): |
| 158 | + """Set the ROI to (left, tip, width, height).""" |
| 159 | + self.roi = Roi(left, top, width, height) |
| 160 | + |
| 161 | + |
| 162 | + #set camera LED status, off is best for microscopy. |
| 163 | + def setLED(self, state=False): |
| 164 | + print ('self.camera.led(state)') |
| 165 | + |
| 166 | + |
| 167 | + def set_exposure_time(self, value): |
| 168 | + #exposure times are set in us. |
| 169 | + self.camera.shutter_speed=(int(value*1.0E6)) |
| 170 | + |
| 171 | + |
| 172 | + def get_exposure_time(self): |
| 173 | + #exposure times are in us, so multiple by 1E-6 to get seconds. |
| 174 | + return (self.camera.exposure_speed*1.0E-6) |
| 175 | + |
| 176 | + |
| 177 | + def get_cycle_time(self): |
| 178 | + #fudge to make it work initially |
| 179 | + #exposure times are in us, so multiple by 1E-6 to get seconds. |
| 180 | + return (self.camera.exposure_speed*1.0E-6+.1) |
| 181 | + |
| 182 | + |
| 183 | + def _get_sensor_shape(self): |
| 184 | + res=self.camera.resolution |
| 185 | + self._set_roi(0,0,res[0],res[1]) |
| 186 | + return (res) |
| 187 | + |
| 188 | + def soft_trigger(self): |
| 189 | + self._logger.info('Trigger received; self._acquiring is %s.' |
| 190 | + % self._acquiring) |
| 191 | + if self._acquiring: |
| 192 | + self._triggered = True |
| 193 | + |
| 194 | + |
| 195 | + def HWtrigger(self, pin): |
| 196 | + self._logger.info('HWTrigger received') |
| 197 | + |
| 198 | + |
| 199 | +#ongoing implemetation notes |
| 200 | + |
| 201 | +#should be able to use rotation and hflip to set specific output image |
| 202 | +# rotations |
| 203 | + |
| 204 | +#roi's can be set with the zoom function, default is (0,0,1,1) meaning all the data. |
| 205 | + |
| 206 | +#Need to setup a buffer for harware triggered data aquisition so we can |
| 207 | +#call the acquisition and then download the data at our leasure |
| 208 | + |
| 209 | + |
0 commit comments