-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy patheyeblinkCamera.py
More file actions
254 lines (230 loc) · 9.55 KB
/
eyeblinkCamera.py
File metadata and controls
254 lines (230 loc) · 9.55 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
# Web streaming and save split under RPi.GPIO control
# Todo: instantiate as an object, test that it runs as expected
# put under RPi.GPIO controls - add interrupts?
# fold into the eyeblink code
# share filenames
# share timestamps
# dynamically create save folders, file names
# dynamically mv files using bash during ITIs
# clean up libraries that aren't being used
#
import io
import picamera
from picamera import mmal, mmalobj as mo
import logging
import socketserver
from threading import Condition,Thread
from http import server
import datetime as dt
import RPi.GPIO as GPIO
import time
#import cv2
import pickle
import csv
import os
#Setting the camera-specific variables
#Breaking into the stream, clocks
global camera
camera = []
global streamOn
streamOn = False
global firstFrame_idx
firstFrame_idx = 0
GPUtimer = mo.MMALCamera()
global outdata
global trialNum
trialNum = 0
global justOff
justOff = False
#Setting up the GPIO interface
GPIO.setwarnings(False)
GPIO.cleanup()
GPIO.setmode(GPIO.BCM)
on_pin = 27
GPIO.setup(on_pin,GPIO.IN)
led_pin = 4
GPIO.setup(led_pin,GPIO.OUT)
GPIO.output(led_pin, GPIO.LOW)
PAGE="""\
<html>
<head>
<title>Eyeblinlk picamera</title>
</head>
<body>
<center><h1>Eyeblink picamera</h1></center>
<center><img src="stream.mjpg" width="640" height="480"></center>
</body>
</html>
"""
class masterStream:
def __init__(self):
thread = Thread(target = self.startCamera(),args=())
thread.daemon = True
thread.run()
def get_millis():
millis = (round(GPUtimer.control.params[mmal.MMAL_PARAMETER_SYSTEM_TIME]/1000))
return millis
def interrupt_on(on_pin):
#What we do when low-level interrupt pin goes high
global streamOn
print('streamOn = '+ str(streamOn))
if streamOn:
global camera
global trial_start
global d
global firstFrame_idx
global outdata
#Get time and frame at on TTL receipt
trial_start = masterStream.get_millis()
firstFrame_idx = camera.frame.index
#Take down idle banner and initialize data outputs
camera.annotate_text = ''
#Can keep metadata as tuples (stamp,frameNum,trial)
d = []
#Write bytes directly from stream to this object
outdata = []
print('Start TTL received at '+str(camera.frame.timestamp/1000 - trial_start) +'\n relative to first frame grab')
class StreamingOutput(object):
def __init__(self):
self.frame = None
self.buffer = io.BytesIO()
self.condition = Condition()
def write(self, buf):
if buf.startswith(b'\xff\xd8'):
# New frame, copy the existing buffer's content and notify all
# clients it's available
self.buffer.truncate()
with self.condition:
self.frame = self.buffer.getvalue()
self.condition.notify_all()
self.buffer.seek(0)
return self.buffer.write(buf)
class StreamingHandler(server.BaseHTTPRequestHandler):
def do_GET(self):
if self.path == '/':
self.send_response(301)
self.send_header('Location', '/index.html')
self.end_headers()
elif self.path == '/index.html':
content = PAGE.encode('utf-8')
self.send_response(200)
self.send_header('Content-Type', 'text/html')
self.send_header('Content-Length', len(content))
self.end_headers()
self.wfile.write(content)
elif self.path == '/stream.mjpg':
self.send_response(200)
self.send_header('Age', 0)
self.send_header('Cache-Control', 'no-cache, private')
self.send_header('Pragma', 'no-cache')
self.send_header('Content-Type', 'multipart/x-mixed-replace; boundary=FRAME')
self.end_headers()
try:
global streamOn
streamOn = True
while True:
global output
with output.condition:
output.condition.wait()
frame = output.frame
self.wfile.write(b'--FRAME\r\n')
self.send_header('Content-Type', 'image/mjpg')
self.send_header('Content-Length', len(frame))
self.end_headers()
self.wfile.write(frame)
self.wfile.write(b'\r\n')
global camera
if GPIO.input(on_pin) and 'd' in globals():
global trial_start
global firstFrame_idx
global d
global outdata
global trialNum
frame_millis = int(round(camera.frame.timestamp/1000)) - trial_start
frame_idx = camera.frame.index - firstFrame_idx
#python lib of tuples with (timestamp,frameNum,trial)
d.append((frame_millis,frame_idx,trialNum))
#Pickling the frame data, save and puch during ITI
outdata.append(frame)
global justOff
justOff = True
elif ~GPIO.input(on_pin) and justOff:
t = time.time()
camera.annotate_text_size = 12
camera.annotate_text = 'not recording'
#Also do initialization of data paths
savePathInit = 'data/'
dateStr = time.strftime("%Y%m%d")
savePath = savePathInit + dateStr + '/'
if not os.path.exists(savePath):
os.mkdir(savePath)
trialNum += 1
dateStr = time.strftime("%Y%m%d")
timeStr = time.strftime("%H%M%S")
datetimeStr = dateStr + '_' + timeStr
fName = savePath + datetimeStr + 'cam_t' + str(trialNum)
#Contains metadata
csvfName = fName + '.csv'
#Contains stream captures of mjpeg encoded image data
bytefName = fName + '.data'
#csv save the metadata
with open(csvfName,'w',newline = '') as outMetadata:
wr = csv.writer(outMetadata)
wr.writerows(d)
print('Wrote '+csvfName)
#pickle the image data
fByte = open(bytefName,'wb')
pickle.dump(outdata,fByte)
print('Wrote '+ bytefName)
elapsed = time.time() - t
print('Writing took '+str(elapsed))
justOff = False
except KeyboardInterrupt:
print('Keyboard interrupt ended stream')
camera.close()
server.server_close()
GPIO.cleanup()
pass
except Exception as e:
camera.close()
GPIO.cleanup()
logging.warning(
'Removed streaming client %s: %s',
self.client_address, str(e))
else:
self.send_error(404)
self.end_headers()
class StreamingServer(socketserver.ThreadingMixIn, server.HTTPServer):
allow_reuse_address = True
daemon_threads = True
def startCamera(self):
global camera
with picamera.PiCamera(resolution='160x120', framerate=150) as camera:
global output
output = masterStream.StreamingOutput()
#Uncomment the next line to change your Pi's Camera rotation (in degrees)
#camera.rotation = 90
camera.clock_mode = 'raw'
camera.start_recording(output, format='mjpeg')
camera.annotate_background = picamera.Color('black')
camera.annotate_text_size = 14
camera.annotate_text = dt.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
#Setting up the low level interrupts with optional LED vis
GPIO.add_event_detect(on_pin,GPIO.RISING,callback=masterStream.interrupt_on)
try:
global address
address = ('', 8000)
server = masterStream.StreamingServer(address, masterStream.StreamingHandler)
server.serve_forever()
except KeyboardInterrupt:
print('Keyboard interrupt ended stream at address',address)
camera.close()
server.server_close()
GPIO.cleanup()
pass
finally:
camera.close()
server.server_close()
GPIO.cleanup()
if __name__ == "__main__":
master = masterStream()