From 8b2cc276477c9d5798f1587602e7e325367738fb Mon Sep 17 00:00:00 2001 From: Balthasar Reuter Date: Thu, 10 May 2018 01:04:20 +0200 Subject: [PATCH] Timings moved to GUI. Thread infrastructure for photobooth thread improved --- photobooth/Photobooth.py | 176 ++++++++++++++----------------------- photobooth/gui/PyQt5Gui.py | 82 +++++++++++++++-- photobooth/gui/__init__.py | 2 +- photobooth/main.py | 19 ++-- 4 files changed, 154 insertions(+), 125 deletions(-) diff --git a/photobooth/Photobooth.py b/photobooth/Photobooth.py index 47ed253..a77ccdf 100644 --- a/photobooth/Photobooth.py +++ b/photobooth/Photobooth.py @@ -10,13 +10,23 @@ from .PictureDimensions import PictureDimensions from . import gui + +class TeardownException(Exception): + + def __init__(self): + + super().__init__() + + class Photobooth: - def __init__(self, config, camera): + def __init__(self, config, camera, send, recv): + + self._send = send + self._recv = recv self.initCamera(config, camera) self.initGpio(config) - self.initTimings(config) self.triggerOff() @@ -55,19 +65,44 @@ class Photobooth: self._lampOff = lambda : None - def initTimings(self, config): - - self._greeter_time = config.getInt('Photobooth', 'greeter_time') - self._countdown_time = config.getInt('Photobooth', 'countdown_time') - self._display_time = config.getInt('Photobooth', 'display_time') - - def teardown(self): + print('Camera teardown') self.triggerOff() self.setCameraIdle() + def recvEvent(self, expected): + + event = self._recv.recv() + + try: + event_idx = expected.index(str(event)) + except ValueError: + print('Photobooth: Unknown event received: ' + str(event)) + raise ValueError('Unknown event received', str(event)) + + return event_idx + + + def recvAck(self): + + events = ['ack', 'cancel', 'teardown'] + + if self.recvEvent(events) != 0: + print('Teardown of Photobooth requested') + raise TeardownException() + + + def recvTriggered(self): + + events = ['triggered', 'teardown'] + + if self.recvEvent(events) != 0: + print('Teardown of Photobooth requested') + raise TeardownException() + + @property def getNextFilename(self): @@ -80,24 +115,6 @@ class Photobooth: return self._show_counter - @property - def greeterTime(self): - - return self._greeter_time - - - @property - def countdownTime(self): - - return self._countdown_time - - - @property - def displayTime(self): - - return self._display_time - - def initRun(self): self.setCameraIdle() @@ -105,45 +122,26 @@ class Photobooth: self.triggerOn() - def run(self, send, recv): + def run(self): - self._send = send - self._recv = recv self.initRun() - while True: - try: - event = self._recv.recv() + try: + while True: + try: + self.recvTriggered() + except EOFError: + return 0 - if str(event) == 'start': - print('Camera already started') - self.initRun() - continue - elif str(event) == 'teardown': - self.teardown() - return -1 - elif str(event) != 'triggered': - print('Unknown event received: ' + str(event)) - raise RuntimeError('Unknown event received', str(event)) - except EOFError: - return 1 - else: try: self.trigger() except RuntimeError as e: print('Camera error: ' + str(e)) self._send.send( gui.ErrorState('Camera error', str(e)) ) - event = self._recv.recv() - if str(event) == 'cancel': - self.teardown() - return 1 - elif str(event) == 'ack': - pass - else: - print('Unknown event received: ' + str(event)) - raise RuntimeError('Unknown event received', str(event)) + self.recvAck() - return 0 + except TeardownException: + return -1 def setCameraActive(self): @@ -159,46 +157,20 @@ class Photobooth: def showCounterPreview(self): - tic, toc = time(), 0 - self._send.send(gui.CountdownState()) while not self._recv.poll(): - toc = time() - tic - self._send.send( gui.PreviewState( - message = str(self.countdownTime - int(toc)), - picture = ImageOps.mirror(self._cap.getPreview()) ) ) + self._send.send( + gui.PreviewState(picture = ImageOps.mirror(self._cap.getPreview())) ) - event = self._recv.recv() - if str(event) == 'cancel': - self.teardown() - return 1 - elif str(event) == 'ack': - pass - else: - print('Unknown event received: ' + str(event)) - raise RuntimeError('Unknown event received', str(event)) + self.recvAck() def showCounterNoPreview(self): self._send.send(gui.CountdownState()) - - for i in range(self.countdownTime): - self._send.send( gui.PreviewState( - message = str(self.countdownTime - i), - picture = Image.new('RGB', (1,1), 'white') ) ) - sleep(1) - - event = self._recv.recv() - if str(event) == 'cancel': - self.teardown() - return 1 - elif str(event) == 'ack': - pass - else: - print('Unknown event received: ' + str(event)) - raise RuntimeError('Unknown event received', str(event)) + self.recvAck() + print('ack received') def showPose(self): @@ -213,6 +185,11 @@ class Photobooth: return self._cap.getPicture() + def capturePictures(self): + + return [ self.captureSinglePicture() for _ in range(self._pic_dims.totalNumPictures) ] + + def assemblePictures(self, pictures): output_image = Image.new('RGB', self._pic_dims.outputSize, (255, 255, 255)) @@ -224,26 +201,13 @@ class Photobooth: return output_image - def capturePictures(self): - - return [ self.captureSinglePicture() for _ in range(self._pic_dims.totalNumPictures) ] - - def trigger(self): self._send.send(gui.GreeterState()) self.triggerOff() self.setCameraActive() - event = self._recv.recv() - if str(event) == 'cancel': - self.teardown() - return 1 - elif str(event) == 'ack': - pass - else: - print('Unknown event received: ' + str(event)) - raise RuntimeError('Unknown event received', str(event)) + self.recvAck() pics = self.capturePictures() self._send.send(gui.AssembleState()) @@ -254,15 +218,7 @@ class Photobooth: self.setCameraIdle() - event = self._recv.recv() - if str(event) == 'cancel': - self.teardown() - return 1 - elif str(event) == 'ack': - pass - else: - print('Unknown event received: ' + str(event)) - raise RuntimeError('Unknown event received', str(event)) + self.recvAck() self._send.send(gui.IdleState()) self.triggerOn() diff --git a/photobooth/gui/PyQt5Gui.py b/photobooth/gui/PyQt5Gui.py index 2494a7e..dfe05e5 100644 --- a/photobooth/gui/PyQt5Gui.py +++ b/photobooth/gui/PyQt5Gui.py @@ -61,7 +61,8 @@ class PyQt5Gui(Gui): def handleKeypressEvent(self, event): if event.key() == Qt.Key_Escape: - self.showStart() + # self.showStart() + self.handleState(TeardownState()) elif event.key() == Qt.Key_Space: # self._transport.send('triggered') self.handleState(TriggerState()) @@ -70,7 +71,8 @@ class PyQt5Gui(Gui): def handleKeypressEventNoTrigger(self, event): if event.key() == Qt.Key_Escape: - self.showStart() + # self.showStart() + self.handleState(TeardownState()) def handleState(self, state): @@ -80,7 +82,7 @@ class PyQt5Gui(Gui): if isinstance(state, IdleState): self.showIdle() - + elif isinstance(state, TriggerState): self._transport.send('triggered') @@ -95,11 +97,11 @@ class PyQt5Gui(Gui): QTimer.singleShot(cfg.getInt('Photobooth', 'greeter_time') * 1000, lambda : self._transport.send('ack')) elif isinstance(state, CountdownState): - QTimer.singleShot(cfg.getInt('Photobooth', 'countdown_time') * 1000, lambda : self._transport.send('ack')) + self._p.setCentralWidget(PyQt5CountdownMessage(cfg.getInt('Photobooth', 'countdown_time'), lambda : self._transport.send('ack'))) elif isinstance(state, PreviewState): - img = ImageQt.ImageQt(state.picture) - self._p.setCentralWidget(PyQt5PictureMessage(state.message, img)) + self._p.centralWidget().picture = ImageQt.ImageQt(state.picture) + self._p.centralWidget().update() elif isinstance(state, PoseState): self._p.setCentralWidget(PyQt5PictureMessage('Pose!')) @@ -631,6 +633,74 @@ class PyQt5WaitMessage(QFrame): self.update() +class PyQt5CountdownMessage(QFrame): + + def __init__(self, time, action): + + super().__init__() + + self._counter = time + self._action = action + self._picture = None + + self.initFrame() + + + def initFrame(self): + + self.setStyleSheet('background-color: white;') + + + @property + def counter(self): + + return self._counter + + + @property + def picture(self): + + return self._picture + + + @picture.setter + def picture(self, pic): + + if not isinstance(pic, QImage): + raise ValueError('picture must be a QImage') + + self._picture = pic + + + def paintEvent(self, event): + + painter = QPainter(self) + + if self._picture != None: + pix = QPixmap.fromImage(self._picture) + pix = pix.scaled(self.rect().size(), Qt.KeepAspectRatio, Qt.FastTransformation) + origin = ( (self.rect().width() - pix.width()) // 2, + (self.rect().height() - pix.height()) // 2 ) + painter.drawPixmap(QPoint(*origin), pix) + + painter.drawText(event.rect(), Qt.AlignCenter, str(self.counter)) + painter.end() + + + def showEvent(self, event): + + self._timer = self.startTimer(1000) + + + def timerEvent(self, event): + + self._counter -= 1 + self.update() + + if self._counter == 0: + self.killTimer(self._timer) + self._action() + class PyQt5PictureMessage(QFrame): diff --git a/photobooth/gui/__init__.py b/photobooth/gui/__init__.py index 7b2da02..9bbdc19 100644 --- a/photobooth/gui/__init__.py +++ b/photobooth/gui/__init__.py @@ -150,7 +150,7 @@ class CountdownState(GuiState): super().__init__(**kwargs) -class PreviewState(MessageState, PictureState): +class PreviewState(PictureState): def __init__(self, **kwargs): diff --git a/photobooth/main.py b/photobooth/main.py index 225c4e8..c1bad3b 100644 --- a/photobooth/main.py +++ b/photobooth/main.py @@ -39,8 +39,8 @@ def start_photobooth(config, send, recv): Camera = lookup_and_import(camera.modules, config.get('Camera', 'module'), 'camera') with Camera() as cap: - photobooth = Photobooth(config, cap) - return photobooth.run(send, recv) + photobooth = Photobooth(config, cap, send, recv) + return photobooth.run() except BaseException as e: send.send( gui.ErrorState('Camera error', str(e)) ) @@ -58,13 +58,13 @@ def main_photobooth(config, send, recv): event = recv.recv() if str(event) != 'start': - print('Unknown event received: ' + str(event)) - raise RuntimeError('Unknown event received', str(event)) + continue - exit_status = start_photobooth(config, send, recv) + status_code = start_photobooth(config, send, recv) + print('Camera exit') - if exit_status != -1: - return exit_status + if status_code != -1: + return status_code def run(argv): @@ -80,7 +80,10 @@ def run(argv): photobooth.start() Gui = lookup_and_import(gui.modules, config.get('Gui', 'module'), 'gui') - return Gui(argv, config).run(event_send, gui_recv) + status_code = Gui(argv, config).run(event_send, gui_recv) + + photobooth.join(1) + return status_code def main(argv):