Idle functionality for camera added. Some Photobooth config added to settings file and dialog.

This commit is contained in:
Balthasar Reuter
2018-03-31 21:25:18 +02:00
parent 235c795a75
commit dc57fd26b5
5 changed files with 152 additions and 31 deletions

View File

@@ -37,6 +37,22 @@ class Camera:
self._has_idle = value self._has_idle = value
def setActive(self):
if not self.hasIdle:
pass
else:
raise NotImplementedError()
def setIdle(self):
if not self.hasIdle:
raise RuntimeError('Camera does not have idle functionality')
raise NotImplementedError()
def getPreview(self): def getPreview(self):
if not self.hasPreview: if not self.hasPreview:

View File

@@ -13,11 +13,23 @@ class CameraOpenCV(Camera):
super().__init__() super().__init__()
self.hasPreview = True self.hasPreview = True
self.hasIdle = False self.hasIdle = True
self._cap = cv2.VideoCapture()
def setActive(self):
self._cap = cv2.VideoCapture(0)
if not self._cap.isOpened(): if not self._cap.isOpened():
raise RuntimeError('Camera could not be opened') self._cap.open(0)
if not self._cap.isOpened():
raise RuntimeError('Camera could not be opened')
def setIdle(self):
if self._cap.isOpened():
self._cap.release()
def getPreview(self): def getPreview(self):
@@ -27,6 +39,7 @@ class CameraOpenCV(Camera):
def getPicture(self): def getPicture(self):
self.setActive()
status, frame = self._cap.read() status, frame = self._cap.read()
if not status: if not status:
raise RuntimeError('Failed to capture picture') raise RuntimeError('Failed to capture picture')

View File

@@ -19,22 +19,32 @@ from time import time, sleep, localtime, strftime
output_size = (1920, 1080) output_size = (1920, 1080)
min_distance = (10, 10) min_distance = (10, 10)
num_pictures = (2, 2) # num_pictures = (2, 2)
pose_time = 2 pose_time = 2
picture_basename = strftime('%Y-%m-%d/photobooth', localtime()) picture_basename = strftime('%Y-%m-%d/photobooth', localtime())
class Photobooth: class Photobooth:
def __init__(self): def __init__(self, config):
self._cap = Camera() self._cap = Camera()
self._cfg = config
if ( self._cfg.getBool('Photobooth', 'show_preview')
and self._cap.hasPreview ):
self.showCounter = self.showCounterPreview
else:
self.showCounter = self.showCounterNoPreview
self.numPictures = ( self._cfg.getInt('Photobooth', 'num_pictures_x') ,
self._cfg.getInt('Photobooth', 'num_pictures_y') )
@property @property
def getNextFilename(self): def getNextFilename(self):
return self._get_next_filename return self._get_next_filename
@getNextFilename.setter @getNextFilename.setter
def getNextFilename(self, func): def getNextFilename(self, func):
@@ -44,6 +54,36 @@ class Photobooth:
self._get_next_filename = func self._get_next_filename = func
@property
def showCounter(self):
return self._show_counter
@showCounter.setter
def showCounter(self, func):
if not callable(func):
raise ValueError('showCounter must be callable')
self._show_counter = func
@property
def numPictures(self):
return self._num_pictures
@numPictures.setter
def numPictures(self, num_pictures):
if len(num_pictures) != 2:
raise ValueError('num_pictures must have two entries')
self._num_pictures = num_pictures
def run(self, send, recv): def run(self, send, recv):
self._send = send self._send = send
@@ -61,22 +101,35 @@ class Photobooth:
self._send.send( Gui.ErrorState('Camera error', str(e)) ) self._send.send( Gui.ErrorState('Camera error', str(e)) )
return 0 return 0
def setCameraActive(self):
self._cap.setActive()
def setCameraIdle(self):
if self._cap.hasIdle:
self._cap.setIdle()
def showCounter(self): def showCounterPreview(self):
if self._cap.hasPreview: tic, toc = time(), 0
tic, toc = time(), 0
while toc < pose_time: while toc < pose_time:
self._send.send( Gui.PreviewState( self._send.send( Gui.PreviewState(
message = str(pose_time - int(toc)), message = str(pose_time - int(toc)),
picture = ImageOps.mirror(self._cap.getPreview()) ) ) picture = ImageOps.mirror(self._cap.getPreview()) ) )
toc = time() - tic toc = time() - tic
else:
for i in range(pose_time):
self._send.send( Gui.PreviewState(str(i)) ) def showCounterNoPreview(self):
sleep(1)
for i in range(pose_time):
self._send.send( Gui.PreviewState(str(i)) )
sleep(1)
def captureSinglePicture(self): def captureSinglePicture(self):
@@ -88,23 +141,24 @@ class Photobooth:
def assemblePictures(self, pictures): def assemblePictures(self, pictures):
# TODO: determine sizes only once
picture_size = pictures[0].size picture_size = pictures[0].size
resize_factor = min( ( ( resize_factor = min( ( (
( output_size[i] - (num_pictures[i] + 1) * min_distance[i] ) / ( output_size[i] - (self.numPictures[i] + 1) * min_distance[i] ) /
( num_pictures[i] * picture_size[i]) ) for i in range(2) ) ) ( self.numPictures[i] * picture_size[i]) ) for i in range(2) ) )
output_picture_size = tuple( int(picture_size[i] * resize_factor) output_picture_size = tuple( int(picture_size[i] * resize_factor)
for i in range(2) ) for i in range(2) )
output_picture_dist = tuple( ( output_size[i] - num_pictures[i] * output_picture_dist = tuple( ( output_size[i] - self.numPictures[i] *
output_picture_size[i] ) // (num_pictures[i] + 1) output_picture_size[i] ) // (self.numPictures[i] + 1)
for i in range(2) ) for i in range(2) )
output_image = Image.new('RGB', output_size, (255, 255, 255)) output_image = Image.new('RGB', output_size, (255, 255, 255))
idx = 0 idx = 0
for img in pictures: for img in pictures:
pos = (idx % num_pictures[0], idx // num_pictures[0]) pos = (idx % self.numPictures[0], idx // self.numPictures[0])
img = img.resize(output_picture_size) img = img.resize(output_picture_size)
offset = tuple( (pos[i] + 1) * output_picture_dist[i] + offset = tuple( (pos[i] + 1) * output_picture_dist[i] +
pos[i] * output_picture_size[i] for i in range(2) ) pos[i] * output_picture_size[i] for i in range(2) )
@@ -117,13 +171,14 @@ class Photobooth:
def capturePictures(self): def capturePictures(self):
pictures = [self.captureSinglePicture() pictures = [self.captureSinglePicture()
for i in range(2) for _ in range(num_pictures[i])] for i in range(2) for _ in range(self.numPictures[i])]
return self.assemblePictures(pictures) return self.assemblePictures(pictures)
def trigger(self): def trigger(self):
self._send.send(Gui.PoseState()) self._send.send(Gui.PoseState())
self.setCameraActive()
sleep(2) sleep(2)
@@ -131,16 +186,18 @@ class Photobooth:
img.save(self.getNextFilename(), 'JPEG') img.save(self.getNextFilename(), 'JPEG')
self._send.send(Gui.PictureState(img)) self._send.send(Gui.PictureState(img))
self.setCameraIdle()
sleep(5) sleep(5)
self._send.send(Gui.IdleState()) self._send.send(Gui.IdleState())
def main_photobooth(send, recv): def main_photobooth(config, send, recv):
picture_list = PictureList(picture_basename) picture_list = PictureList(picture_basename)
photobooth = Photobooth() photobooth = Photobooth(config)
photobooth.getNextFilename = picture_list.getNext photobooth.getNextFilename = picture_list.getNext
return photobooth.run(send, recv) return photobooth.run(send, recv)
@@ -153,7 +210,7 @@ def main(argv):
event_recv, event_send = Pipe(duplex=False) event_recv, event_send = Pipe(duplex=False)
gui_recv, gui_send = Pipe(duplex=False) gui_recv, gui_send = Pipe(duplex=False)
photobooth = Process(target=main_photobooth, args=(gui_send, event_recv), daemon=True) photobooth = Process(target=main_photobooth, args=(config, gui_send, event_recv), daemon=True)
photobooth.start() photobooth.start()
gui = PyQt5Gui(argv, config) gui = PyQt5Gui(argv, config)

View File

@@ -58,7 +58,11 @@ class PyQt5Gui(Gui.Gui):
if isinstance(state, Gui.IdleState): if isinstance(state, Gui.IdleState):
self.showIdle() self.showIdle()
elif isinstance(state, Gui.PoseState): elif isinstance(state, Gui.PoseState):
self._p.setCentralWidget(PyQt5PictureMessage('Will capture 4 pictures!')) global cfg
num_pictures = ( cfg.getInt('Photobooth', 'num_pictures_x') *
cfg.getInt('Photobooth', 'num_pictures_y') )
self._p.setCentralWidget(
PyQt5PictureMessage('Will capture {} pictures!'.format(num_pictures)))
elif isinstance(state, Gui.PreviewState): elif isinstance(state, Gui.PreviewState):
img = ImageQt.ImageQt(state.picture) img = ImageQt.ImageQt(state.picture)
self._p.setCentralWidget(PyQt5PictureMessage(state.message, img)) self._p.setCentralWidget(PyQt5PictureMessage(state.message, img))
@@ -86,7 +90,7 @@ class PyQt5Gui(Gui.Gui):
def showIdle(self): def showIdle(self):
self._p.handleKeypressEvent = self.handleKeypressEvent self._p.handleKeypressEvent = self.handleKeypressEvent
self._p.setCentralWidget(PyQt5PictureMessage('Hit the button!'))#, 'homer.jpg')) self._p.setCentralWidget(PyQt5PictureMessage('Hit the button!'))
def showError(self, title, message): def showError(self, title, message):
@@ -254,6 +258,7 @@ class PyQt5Settings(QFrame):
grid.addWidget(self.createGuiSettings(), 0, 0) grid.addWidget(self.createGuiSettings(), 0, 0)
grid.addWidget(self.createGpioSettings(), 1, 0) grid.addWidget(self.createGpioSettings(), 1, 0)
grid.addWidget(self.createCameraSettings(), 0, 1) grid.addWidget(self.createCameraSettings(), 0, 1)
grid.addWidget(self.createPhotoboothSettings(), 1, 1)
layout = QVBoxLayout() layout = QVBoxLayout()
layout.addLayout(grid) layout.addLayout(grid)
@@ -336,6 +341,32 @@ class PyQt5Settings(QFrame):
return widget return widget
def createPhotoboothSettings(self):
global cfg
self._value_widgets['Photobooth'] = {}
self._value_widgets['Photobooth']['show_preview'] = QCheckBox('Show preview while countdown (restart required)')
if cfg.getBool('Photobooth', 'show_preview'):
self._value_widgets['Photobooth']['show_preview'].toggle()
self._value_widgets['Photobooth']['num_pictures_x'] = QLineEdit(cfg.get('Photobooth', 'num_pictures_x'))
self._value_widgets['Photobooth']['num_pictures_y'] = QLineEdit(cfg.get('Photobooth', 'num_pictures_y'))
layout = QFormLayout()
layout.addRow(self._value_widgets['Photobooth']['show_preview'])
sublayout = QHBoxLayout()
sublayout.addWidget(QLabel('Number of pictures'))
sublayout.addWidget(self._value_widgets['Photobooth']['num_pictures_x'])
sublayout.addWidget(QLabel('x'))
sublayout.addWidget(self._value_widgets['Photobooth']['num_pictures_y'])
layout.addRow(sublayout)
widget = QGroupBox('Photobooth settings')
widget.setLayout(layout)
return widget
def createButtons(self): def createButtons(self):
layout = QHBoxLayout() layout = QHBoxLayout()
@@ -374,6 +405,10 @@ class PyQt5Settings(QFrame):
cfg.set('Gpio', 'trigger_channel', self._value_widgets['Gpio']['trigger_channel'].text()) cfg.set('Gpio', 'trigger_channel', self._value_widgets['Gpio']['trigger_channel'].text())
cfg.set('Gpio', 'lamp_channel', self._value_widgets['Gpio']['lamp_channel'].text()) cfg.set('Gpio', 'lamp_channel', self._value_widgets['Gpio']['lamp_channel'].text())
cfg.set('Photobooth', 'show_preview', str(self._value_widgets['Photobooth']['show_preview'].isChecked()))
cfg.set('Photobooth', 'num_pictures_x', self._value_widgets['Photobooth']['num_pictures_x'].text())
cfg.set('Photobooth', 'num_pictures_y', self._value_widgets['Photobooth']['num_pictures_y'].text())
wrapper_idx2val = [ 'commandline', 'piggyphoto', 'gphoto2-cffi' ] wrapper_idx2val = [ 'commandline', 'piggyphoto', 'gphoto2-cffi' ]
cfg.set('Camera', 'gphoto2_wrapper', wrapper_idx2val[self._value_widgets['Camera']['gphoto2_wrapper'].currentIndex()]) cfg.set('Camera', 'gphoto2_wrapper', wrapper_idx2val[self._value_widgets['Camera']['gphoto2_wrapper'].currentIndex()])

View File

@@ -22,8 +22,8 @@ lamp_channel = 4
[Photobooth] [Photobooth]
# Show preview while posing time (True/False) # Show preview while posing time (True/False)
preview = True show_preview = True
# Number of pictures in horizontal direction # Number of pictures in horizontal direction
num_pic_x = 2 num_pictures_x = 2
# Number of pictures in vertical direction # Number of pictures in vertical direction
num_pic_y = 2 num_pictures_y = 2