Skip to content

Commit b99d23e

Browse files
author
Ian Dobbie
committed
working picam version updated to lates main branch
1 parent cded49e commit b99d23e

File tree

1 file changed

+144
-102
lines changed

1 file changed

+144
-102
lines changed

microscope/cameras/picam.py

Lines changed: 144 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
#!/usr/bin/env python3
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
23

34
## Copyright (C) 2016-2017 Mick Phillips <mick.phillips@gmail.com>
45
## Copyright (C) 2019 Ian Dobbie <ian.dobbie@bioch.ox.ac.uk>
@@ -19,131 +20,161 @@
1920
## along with Microscope. If not, see <http://www.gnu.org/licenses/>.
2021

2122
import time
22-
from io import BytesIO
2323

24-
import numpy as np
2524
import Pyro4
25+
import numpy as np
26+
import logging
27+
import enum
28+
import microscope
29+
import queue
30+
from microscope import devices
31+
from microscope.devices import keep_acquiring, Binning, ROI
2632

27-
# import raspberry pi specific modules
33+
#import raspberry pi specific modules
2834
import picamera
2935
import picamera.array
30-
# to allow hardware trigger.
36+
from io import BytesIO
37+
#to allow hardware trigger.
3138
import RPi.GPIO as GPIO
39+
GPIO_Trigger=21
40+
GPIO_CAMLED=5
3241

33-
from microscope import devices
34-
from microscope.devices import Binning, Roi, keep_acquiring
35-
36-
37-
GPIO_Trigger = 21
38-
39-
40-
# Trigger mode to type.
41-
TRIGGER_MODES = {
42-
"internal": None,
43-
"external": devices.TRIGGER_BEFORE,
44-
"external start": None,
45-
"external exposure": devices.TRIGGER_DURATION,
46-
"software": devices.TRIGGER_SOFT,
47-
}
42+
_logger = logging.getLogger(__name__)
4843

44+
# Trigger types.
45+
@enum.unique
46+
class TrgSourceMap(enum.Enum):
47+
SOFTWARE = microscope.TriggerType.SOFTWARE
48+
EDGE_RISING = microscope.TriggerType.RISING_EDGE
4949

5050
@Pyro4.expose
51-
@Pyro4.behavior("single")
52-
class PiCamera(devices.CameraDevice):
51+
@Pyro4.behavior('single')
52+
class PiCamera(microscope.abc.Camera):
5353
def __init__(self, *args, **kwargs):
5454
super(PiCamera, self).__init__(**kwargs)
55-
# example parameter to allow setting.
56-
# self.add_setting('_error_percent', 'int',
57-
# lambda: self._error_percent,
58-
# self._set_error_percent,
59-
# lambda: (0, 100))
55+
#example parameter to allow setting.
56+
# self.add_setting('_error_percent', 'int',
57+
# lambda: self._error_percent,
58+
# self._set_error_percent,
59+
# lambda: (0, 100))
6060
self._acquiring = False
6161
self._exposure_time = 0.1
6262
self._triggered = False
6363
self.camera = None
6464
# Region of interest.
65-
self.roi = Roi(None, None, None, None)
65+
self.roi = ROI(None, None, None, None)
6666
# Cycle time
67-
self.exposure_time = 0.001 # in seconds
67+
self.exposure_time = 0.001 # in seconds
6868
self.cycle_time = self.exposure_time
69-
# initialise in soft trigger mode
70-
self.trigger = devices.TRIGGER_SOFT
71-
# setup hardware triggerline
69+
#initialise in soft trigger mode
70+
self._trigger_type=microscope.TriggerType.SOFTWARE
71+
#setup hardware triggerline
7272
GPIO.setmode(GPIO.BCM)
73-
GPIO.setup(GPIO_Trigger.GPIO.IN)
74-
75-
# when a rasing edge is detected on port GPIO_Trigger,
76-
# regardless of whatever else is happening in the program, the
77-
# function self._HW_trigger will be run
78-
GPIO.add_event_detect(
79-
GPIO_Trigger,
80-
GPIO.RAISING,
81-
callback=self._HW_trigger,
82-
bouncetime=10,
73+
#GPIO trigger line is an input
74+
GPIO.setup(GPIO_Trigger,GPIO.IN)
75+
#GPIO control over camera LED is an output
76+
GPIO.setup(GPIO_CAMLED,GPIO.OUT)
77+
#add trigger to settings
78+
trg_source_names = [x.name for x in TrgSourceMap]
79+
#set up queue to store images as they are acquired
80+
self._queue = queue.Queue()
81+
82+
def _trigger_source_setter(index: int) -> None:
83+
trigger_type = TrgSourceMap[trg_source_names[index]].value
84+
self.set_trigger(trigger_type, self.trigger_mode)
85+
86+
self.add_setting(
87+
"trigger source",
88+
"enum",
89+
lambda: TrgSourceMap(self._trigger_type).name,
90+
_trigger_source_setter,
91+
trg_source_names,
8392
)
93+
self.initialize()
8494

85-
def _HW_trigger(self):
86-
"""Function called by GPIO interupt, needs to trigger image capture"""
87-
print("PiCam HW trigger")
95+
96+
def HW_trigger(self,channel):
97+
'''Function called by GPIO interupt, needs to trigger image capture'''
98+
with picamera.array.PiYUVArray(self.camera) as output:
99+
self.camera.capture(output, format='yuv', use_video_port = False)
100+
self._queue.put(output.array[:,:,0])
88101

89102
def _fetch_data(self):
90-
if self._acquiring and self._triggered:
91-
with picamera.array.PiYUVArray(self.camera) as output:
92-
self.camera.capture(output, format="yuv", use_video_port=False)
93-
# just return intensity values
94-
self._logger.info("Sending image")
95-
self._triggered = False
96-
return output.array[:, :, 0]
103+
if self._queue.qsize() is not 0:
104+
data=self._queue.get()
105+
_logger.info('Sending image')
106+
return data
107+
else:
108+
return None
97109

98110
def initialize(self):
99111
"""Initialise the Pi Camera camera.
100112
Open the connection, connect properties and populate settings dict.
101113
"""
102114
if not self.camera:
103115
try:
104-
# initialise camera in still image mode.
105-
self.camera = picamera.PiCamera(sensor_mode=2)
116+
#initialise camera in still image mode.
117+
self.camera = picamera.PiCamera(sensor_mode=2)
106118
except:
107119
raise Exception("Problem opening camera.")
108-
self._logger.info("Initializing camera.")
109-
# create img buffer to hold images.
110-
# disable camera LED by default
120+
_logger.info('Initializing camera.')
121+
#create img buffer to hold images.
122+
#disable camera LED by default
111123
self.setLED(False)
112124
self._get_sensor_shape()
113-
125+
114126
def make_safe(self):
115127
if self._acquiring:
116128
self.abort()
117-
118-
def _on_disable(self):
129+
130+
def _do_disable(self):
119131
self.abort()
120132

121-
def _on_enable(self):
122-
self._logger.info("Preparing for acquisition.")
133+
def _do_shutdown(self):
134+
self._do_disable()
135+
self.camera.close()
136+
137+
def _do_enable(self):
138+
_logger.info("Preparing for acquisition.")
123139
if self._acquiring:
124140
self.abort()
141+
#actually start camera
142+
if not self.camera:
143+
self.initialize()
125144
self._acquiring = True
126-
# actually start camera
127-
self._logger.info("Acquisition enabled.")
145+
_logger.info("Acquisition enabled.")
128146
return True
129147

130148
def abort(self):
131-
self._logger.info("Disabling acquisition.")
149+
_logger.info('Disabling acquisition.')
132150
if self._acquiring:
133151
self._acquiring = False
152+
134153

135-
def set_trigger_type(self, trigger):
136-
if trigger == devices.TRIGGER_SOFT:
154+
def set_trigger(self,ttype: microscope.TriggerType,
155+
tmode: microscope.TriggerMode) -> None:
156+
if ttype == self._trigger_type:
157+
return
158+
elif (ttype == microscope.TriggerType.SOFTWARE):
137159
GPIO.remove_event_detect(GPIO_Trigger)
138-
self.trigger = devices.TRIGGER_SOFT
139-
elif trigger == devices.TRIGGER_BEFORE:
140-
GPIO.add_event_detect(
141-
GPIO_Trigger, RISING, self.HWtrigger, self.exposure_time
142-
)
143-
self.trigger = devices.TRIGGER_BEFORE
160+
self._trigger_type=microscope.TriggerType.SOFTWARE
161+
elif (ttype == microscope.TriggerType.RISING_EDGE):
162+
GPIO.add_event_detect(GPIO_Trigger,GPIO.RISING,
163+
callback=self.HW_trigger,
164+
bouncetime=10)
165+
self._trigger_type=microscope.TriggerType.RISING_EDGE
144166

145-
def get_trigger_type(self):
146-
return self.trigger
167+
168+
@property
169+
def trigger_mode(self) -> microscope.TriggerMode:
170+
# if self._trigger_type==devices.TRIGGER_BEFORE:
171+
return microscope.TriggerMode.ONCE
172+
# else:
173+
# return microscope.TriggerMode.ONCE
174+
175+
@property
176+
def trigger_type(self) -> microscope.TriggerType:
177+
return self._trigger_type
147178

148179
def _get_roi(self):
149180
"""Return the current ROI (left, top, width, height)."""
@@ -153,52 +184,63 @@ def _set_binning(self, h_bin, v_bin):
153184
return True
154185

155186
def _get_binning(self):
156-
return Binning(1, 1)
187+
return(Binning(1,1))
157188

158189
@keep_acquiring
159190
def _set_roi(self, left, top, width, height):
160191
"""Set the ROI to (left, tip, width, height)."""
161-
self.roi = Roi(left, top, width, height)
162-
163-
# set camera LED status, off is best for microscopy.
192+
self.roi = ROI(left, top, width, height)
193+
194+
195+
#set camera LED status, off is best for microscopy.
164196
def setLED(self, state=False):
165-
print("self.camera.led(state)")
197+
GPIO.output(GPIO_CAMLED, state)
166198

167199
def set_exposure_time(self, value):
168-
# exposure times are set in us.
169-
self.camera.shutter_speed = int(value * 1.0e6)
200+
#exposure times are set in us.
201+
self.camera.shutter_speed=(int(value*1.0E6))
202+
170203

171204
def get_exposure_time(self):
172-
# exposure times are in us, so multiple by 1E-6 to get seconds.
173-
return self.camera.exposure_speed * 1.0e-6
205+
#exposure times are in us, so multiple by 1E-6 to get seconds.
206+
return (self.camera.exposure_speed*1.0E-6)
207+
174208

175209
def get_cycle_time(self):
176-
# fudge to make it work initially
177-
# exposure times are in us, so multiple by 1E-6 to get seconds.
178-
return self.camera.exposure_speed * 1.0e-6 + 0.1
210+
#fudge to make it work initially
211+
#exposure times are in us, so multiple by 1E-6 to get seconds.
212+
return (self.camera.exposure_speed*1.0E-6+.1)
179213

214+
180215
def _get_sensor_shape(self):
181-
res = self.camera.resolution
182-
self._set_roi(0, 0, res[0], res[1])
183-
return res
184-
216+
res=self.camera.resolution
217+
self._set_roi(0,0,res[0],res[1])
218+
return (res)
219+
def _do_trigger(self):
220+
self.soft_trigger()
221+
185222
def soft_trigger(self):
186-
self._logger.info(
187-
"Trigger received; self._acquiring is %s." % self._acquiring
188-
)
223+
_logger.info('Trigger received; self._acquiring is %s.'
224+
% self._acquiring)
189225
if self._acquiring:
190-
self._triggered = True
226+
with picamera.array.PiYUVArray(self.camera) as output:
227+
self.camera.capture(output, format='yuv', use_video_port = False)
228+
self._queue.put(output.array[:,:,0])
229+
191230

192-
def HWtrigger(self, pin):
193-
self._logger.info("HWTrigger received")
194231

232+
def HWtrigger(self, pin):
233+
_logger.info('HWTrigger received')
195234

196-
# ongoing implemetation notes
235+
236+
#ongoing implemetation notes
197237

198-
# should be able to use rotation and hflip to set specific output image
238+
#should be able to use rotation and hflip to set specific output image
199239
# rotations
200240

201-
# roi's can be set with the zoom function, default is (0,0,1,1) meaning all the data.
241+
#roi's can be set with the zoom function, default is (0,0,1,1) meaning all the data.
242+
243+
#Need to setup a buffer for harware triggered data aquisition so we can
244+
#call the acquisition and then download the data at our leasure
245+
202246

203-
# Need to setup a buffer for harware triggered data aquisition so we can
204-
# call the acquisition and then download the data at our leasure

0 commit comments

Comments
 (0)