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>
1920## along with Microscope. If not, see <http://www.gnu.org/licenses/>.
2021
2122import time
22- from io import BytesIO
2323
24- import numpy as np
2524import 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
2834import picamera
2935import picamera .array
30- # to allow hardware trigger.
36+ from io import BytesIO
37+ #to allow hardware trigger.
3138import 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