Idle functionality for camera added. Some Photobooth config added to settings file and dialog.
This commit is contained in:
@@ -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:
|
||||||
|
|||||||
@@ -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')
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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()])
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user