Skip to content

Commit 822ca96

Browse files
author
Ian Dobbie
committed
Added first picam implmentaion, see issue #167
1 parent 912eab4 commit 822ca96

File tree

1 file changed

+209
-0
lines changed

1 file changed

+209
-0
lines changed

microscope/cameras/picam.py

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
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

Comments
 (0)