A bit of cleanup. Failed pictures can now produce recoverable errors (e.g., failed focus) and retry up to 3 times
This commit is contained in:
21
camera.py
21
camera.py
@@ -11,7 +11,9 @@ except ImportError:
|
|||||||
|
|
||||||
class CameraException(Exception):
|
class CameraException(Exception):
|
||||||
"""Custom exception class to handle camera class errors"""
|
"""Custom exception class to handle camera class errors"""
|
||||||
pass
|
def __init__(self, message, recoverable=False):
|
||||||
|
self.message = message
|
||||||
|
self.recoverable = recoverable
|
||||||
|
|
||||||
|
|
||||||
class Camera_cv:
|
class Camera_cv:
|
||||||
@@ -35,23 +37,28 @@ class Camera_gPhoto:
|
|||||||
|
|
||||||
def __init__(self, picture_size):
|
def __init__(self, picture_size):
|
||||||
self.picture_size = picture_size
|
self.picture_size = picture_size
|
||||||
# Print the abilities of the connected camera
|
# Print the capabilities of the connected camera
|
||||||
# print(self.call_gphoto("-a", "/dev/null"))
|
try:
|
||||||
|
print(self.call_gphoto("-a", "/dev/null"))
|
||||||
|
except:
|
||||||
|
print("Warning: Can't list camera capabilities. Do you have gPhoto2 installed?")
|
||||||
|
|
||||||
def call_gphoto(self, action, filename):
|
def call_gphoto(self, action, filename):
|
||||||
# Try to run the command
|
# Try to run the command
|
||||||
try:
|
try:
|
||||||
cmd = "gphoto2 --force-overwrite --quiet " + action + " --filename " + filename
|
cmd = [ "gphoto2", "--force-overwrite", "--quiet", action, "--filename " + filename ]
|
||||||
output = subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT)
|
output = subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT)
|
||||||
if "ERROR" in output:
|
if "ERROR" in output:
|
||||||
raise subprocess.CalledProcessError(returncode=0, cmd=cmd, output=output)
|
raise subprocess.CalledProcessError(returncode=0, cmd=cmd, output=output)
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
if "Canon EOS Capture failed: 2019" in e.output:
|
if "Canon EOS Capture failed: 2019" in e.output:
|
||||||
raise CameraException("Can't focus! Move and try again!")
|
raise CameraException("Can't focus! Move a little bit!", True)
|
||||||
elif "No camera found" in e.output:
|
elif "No camera found" in e.output:
|
||||||
raise CameraException("No (supported) camera detected!")
|
raise CameraException("No (supported) camera detected!", False)
|
||||||
|
elif "command not found" in e.output:
|
||||||
|
raise CameraException("gPhoto2 not found!", False)
|
||||||
else:
|
else:
|
||||||
raise CameraException("Unknown error!\n" + '\n'.join(e.output.split('\n')[1:3]))
|
raise CameraException("Unknown error!\n" + '\n'.join(e.output.split('\n')[1:3]), False)
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def take_picture(self, filename="/tmp/picture.jpg"):
|
def take_picture(self, filename="/tmp/picture.jpg"):
|
||||||
|
|||||||
@@ -49,19 +49,31 @@ display_time = 10
|
|||||||
###############
|
###############
|
||||||
|
|
||||||
class PictureList:
|
class PictureList:
|
||||||
"""Class to manage images and count them"""
|
"""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):
|
def __init__(self, basename):
|
||||||
|
"""Initialize filenames to the given basename and search for
|
||||||
|
existing files. Set the counter accordingly.
|
||||||
|
"""
|
||||||
|
|
||||||
# Set basename and suffix
|
# Set basename and suffix
|
||||||
self.basename = basename
|
self.basename = basename
|
||||||
self.suffix = ".jpg"
|
self.suffix = ".jpg"
|
||||||
self.count_width = 5
|
self.count_width = 5
|
||||||
|
|
||||||
# Ensure directory exists
|
# Ensure directory exists
|
||||||
dirname = os.path.dirname(self.basename)
|
dirname = os.path.dirname(self.basename)
|
||||||
if not os.path.exists(dirname):
|
if not os.path.exists(dirname):
|
||||||
os.makedirs(dirname)
|
os.makedirs(dirname)
|
||||||
|
|
||||||
# Find existing files
|
# Find existing files
|
||||||
count_pattern = "[0-9]" * self.count_width
|
count_pattern = "[0-9]" * self.count_width
|
||||||
pictures = glob(self.basename + count_pattern + self.suffix)
|
pictures = glob(self.basename + count_pattern + self.suffix)
|
||||||
|
|
||||||
# Get number of latest file
|
# Get number of latest file
|
||||||
if len(pictures) == 0:
|
if len(pictures) == 0:
|
||||||
self.counter = 0
|
self.counter = 0
|
||||||
@@ -69,8 +81,10 @@ class PictureList:
|
|||||||
pictures.sort()
|
pictures.sort()
|
||||||
last_picture = pictures[-1]
|
last_picture = pictures[-1]
|
||||||
self.counter = int(last_picture[-(self.count_width+len(self.suffix)):-len(self.suffix)])
|
self.counter = int(last_picture[-(self.count_width+len(self.suffix)):-len(self.suffix)])
|
||||||
print("Number of last existing file: " + str(self.counter) + "(" + str(len(pictures)) + ")")
|
|
||||||
print("Saving as: " + self.basename)
|
# Print initial infos
|
||||||
|
print("Info: Number of last existing file: " + str(self.counter))
|
||||||
|
print("Info: Saving assembled pictures as: " + self.basename + "XXXXX.jpg")
|
||||||
|
|
||||||
def get(self, count):
|
def get(self, count):
|
||||||
return self.basename + str(count).zfill(self.count_width) + self.suffix
|
return self.basename + str(count).zfill(self.count_width) + self.suffix
|
||||||
@@ -84,6 +98,11 @@ class PictureList:
|
|||||||
|
|
||||||
|
|
||||||
class Photobooth:
|
class Photobooth:
|
||||||
|
"""The main class.
|
||||||
|
|
||||||
|
It contains all the logic for the photobooth.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, display_size, picture_basename, picture_size, pose_time, display_time,
|
def __init__(self, display_size, picture_basename, picture_size, pose_time, display_time,
|
||||||
trigger_channel, shutdown_channel, lamp_channel):
|
trigger_channel, shutdown_channel, lamp_channel):
|
||||||
self.display = GuiModule('Photobooth', display_size)
|
self.display = GuiModule('Photobooth', display_size)
|
||||||
@@ -277,16 +296,34 @@ class Photobooth:
|
|||||||
# Take pictures
|
# Take pictures
|
||||||
filenames = [i for i in range(4)]
|
filenames = [i for i in range(4)]
|
||||||
for x in range(4):
|
for x in range(4):
|
||||||
self.display.clear()
|
# Try each picture up to 3 times
|
||||||
self.display.show_message("S M I L E !!!\n\n" + str(x+1) + " of 4")
|
for attempt in range(3):
|
||||||
self.display.apply()
|
self.display.clear()
|
||||||
|
self.display.show_message("S M I L E !!!\n\n" + str(x+1) + " of 4")
|
||||||
|
self.display.apply()
|
||||||
|
|
||||||
tic = clock()
|
tic = clock()
|
||||||
filenames[x] = self.camera.take_picture("/tmp/photobooth_%02d.jpg" % x)
|
|
||||||
toc = clock() - tic
|
|
||||||
|
|
||||||
if toc < 1.0:
|
try:
|
||||||
sleep(1.0 - toc)
|
filenames[x] = self.camera.take_picture("/tmp/photobooth_%02d.jpg" % x)
|
||||||
|
break
|
||||||
|
except CameraException as e:
|
||||||
|
# On recoverable errors: display message and retry
|
||||||
|
if e.recoverable:
|
||||||
|
if attempt < 3:
|
||||||
|
self.display.clear()
|
||||||
|
self.display.show_message(e.message)
|
||||||
|
self.display.apply()
|
||||||
|
sleep(1)
|
||||||
|
else:
|
||||||
|
raise CameraException("Giving up! Please start again!", False)
|
||||||
|
else:
|
||||||
|
raise e
|
||||||
|
|
||||||
|
# Sleep for a little bit if necessary
|
||||||
|
toc = clock() - tic
|
||||||
|
if toc < 1.0:
|
||||||
|
sleep(1.0 - toc)
|
||||||
|
|
||||||
# Show 'Wait'
|
# Show 'Wait'
|
||||||
self.display.clear()
|
self.display.clear()
|
||||||
|
|||||||
Reference in New Issue
Block a user