From fb62d2fdb286ba33301b653b516c1c60d11241ad Mon Sep 17 00:00:00 2001 From: Balthasar Reuter Date: Sat, 31 Mar 2018 00:02:57 +0200 Subject: [PATCH] Added saving of assembled pictures and more robust error handling --- photobooth/Gui.py | 39 +++++++++++++++++++++-- photobooth/Photobooth.py | 44 +++++++++++++++++++++----- photobooth/PictureList.py | 66 +++++++++++++++++++++++++++++++++++++++ photobooth/PyQt5Gui.py | 9 ++++++ 4 files changed, 149 insertions(+), 9 deletions(-) create mode 100644 photobooth/PictureList.py diff --git a/photobooth/Gui.py b/photobooth/Gui.py index b33fc74..2307778 100644 --- a/photobooth/Gui.py +++ b/photobooth/Gui.py @@ -21,6 +21,41 @@ class GuiState: assert not kwargs + +class ErrorState(GuiState): + + def __init__(self, title, message, **kwargs): + + super().__init__(**kwargs) + + self.title = title + self.message = message + + + @property + def title(self): + + return self._title + + + @title.setter + def title(self, title): + + self._title = title + + + @property + def message(self): + + return self._message + + + @message.setter + def message(self, message): + + self._message = message + + class IdleState(GuiState): def __init__(self, **kwargs): @@ -35,7 +70,7 @@ class PictureState(GuiState): super().__init__(**kwargs) - self._pic = picture + self.picture = picture @property @@ -56,7 +91,7 @@ class MessageState(GuiState): super().__init__(**kwargs) - self._msg = message + self.message = message @property diff --git a/photobooth/Photobooth.py b/photobooth/Photobooth.py index a5a0149..e04d9f2 100644 --- a/photobooth/Photobooth.py +++ b/photobooth/Photobooth.py @@ -2,21 +2,26 @@ # -*- coding: utf-8 -*- from Config import Config + +from PictureList import PictureList + import Gui from PyQt5Gui import PyQt5Gui + from CameraOpenCV import CameraOpenCV as Camera from PIL import Image from multiprocessing import Pipe, Process -from time import time, sleep +from time import time, sleep, localtime, strftime output_size = (1920, 1080) min_distance = (10, 10) num_pictures = (2, 2) pose_time = 2 +picture_basename = strftime('%Y-%m-%d/photobooth', localtime()) class Photobooth: @@ -25,6 +30,20 @@ class Photobooth: self._cap = Camera() + @property + def getNextFilename(self): + + return self._get_next_filename + + @getNextFilename.setter + def getNextFilename(self, func): + + if not callable(func): + raise ValueError('getNextFilename must be callable') + + self._get_next_filename = func + + def run(self, send, recv): self._send = send @@ -35,7 +54,11 @@ class Photobooth: except EOFError: return 1 else: - self.trigger() + try: + self.trigger() + except RuntimeError as e: + print('Camera error: ' + str(e)) + self._send.send( Gui.ErrorState('Camera error', str(e)) ) return 0 @@ -71,10 +94,10 @@ class Photobooth: ( output_size[i] - (num_pictures[i] + 1) * min_distance[i] ) / ( num_pictures[i] * picture_size[i]) ) for i in range(2) ) ) - output_picture_size = tuple( int(picture_size[0] * resize_factor) + output_picture_size = tuple( int(picture_size[i] * resize_factor) for i in range(2) ) - output_picture_dist = tuple( int( ( output_size[i] - num_pictures[i] * - output_picture_size[i] ) / (num_pictures[i] + 1) ) + output_picture_dist = tuple( ( output_size[i] - num_pictures[i] * + output_picture_size[i] ) // (num_pictures[i] + 1) for i in range(2) ) output_image = Image.new('RGB', output_size, (255, 255, 255)) @@ -93,7 +116,8 @@ class Photobooth: def capturePictures(self): - pictures = [self.captureSinglePicture() for i in range(2) for _ in range(num_pictures[i])] + pictures = [self.captureSinglePicture() + for i in range(2) for _ in range(num_pictures[i])] return self.assemblePictures(pictures) @@ -103,7 +127,9 @@ class Photobooth: sleep(2) - self._send.send(Gui.PictureState(self.capturePictures())) + img = self.capturePictures() + img.save(self.getNextFilename(), 'JPEG') + self._send.send(Gui.PictureState(img)) sleep(5) @@ -112,7 +138,11 @@ class Photobooth: def main_photobooth(send, recv): + picture_list = PictureList(picture_basename) + photobooth = Photobooth() + photobooth.getNextFilename = picture_list.getNext + return photobooth.run(send, recv) diff --git a/photobooth/PictureList.py b/photobooth/PictureList.py new file mode 100644 index 0000000..b9ef563 --- /dev/null +++ b/photobooth/PictureList.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import os + +from glob import glob + + +class PictureList: + """A simple helper class. + + It provides the filenames for the assembled pictures and keeps count + of taken and previously existing pictures. + """ + + def __init__(self, basename): + """Initialize filenames to the given basename and search for + existing files. Set the counter accordingly. + """ + + # Set basename and suffix + self.basename = basename + self.suffix = '.jpg' + self.count_width = 5 + + # Ensure directory exists + dirname = os.path.dirname(self.basename) + if not os.path.exists(dirname): + os.makedirs(dirname) + + self.findExistingFiles() + + + def findExistingFiles(self): + + # Find existing files + count_pattern = '[0-9]' * self.count_width + pictures = glob(self.basename + count_pattern + self.suffix) + + # Get number of latest file + if len(pictures) == 0: + self.counter = 0 + else: + pictures.sort() + last_picture = pictures[-1] + self.counter = int(last_picture[-(self.count_width+len(self.suffix)):-len(self.suffix)]) + + # Print initial infos + print('Info: Number of last existing file: ' + str(self.counter)) + print('Info: Saving assembled pictures as: ' + self.basename + (self.count_width * 'X') + '.jpg') + + + def getFilename(self, count): + + return self.basename + str(count).zfill(self.count_width) + self.suffix + + + def getLast(self): + + return self.getFilename(self.counter) + + + def getNext(self): + + self.counter += 1 + return self.getFilename(self.counter) diff --git a/photobooth/PyQt5Gui.py b/photobooth/PyQt5Gui.py index 506e39c..e02c97d 100644 --- a/photobooth/PyQt5Gui.py +++ b/photobooth/PyQt5Gui.py @@ -67,6 +67,8 @@ class PyQt5Gui(Gui.Gui): # img = QImage(state.picture, state.picture.shape[1], state.picture.shape[0], QImage.Format_RGB888) img = ImageQt.ImageQt(state.picture) self._p.setCentralWidget(PyQt5PictureMessage('', img)) + elif isinstance(state, Gui.ErrorState): + self.showError(state.title, state.message) else: raise ValueError('Unknown state') @@ -89,6 +91,13 @@ class PyQt5Gui(Gui.Gui): self._p.setCentralWidget(PyQt5PictureMessage('Hit the button!', 'homer.jpg')) + def showError(self, title, message): + + if QMessageBox.warning(self._p, title,message, QMessageBox.Ok, + QMessageBox.Ok) == QMessageBox.Ok: + self.showIdle() + + class PyQt5Receiver(QThread): notify = pyqtSignal(object)