Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add user defined exif_data when capturing files #786

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 30 additions & 13 deletions picamera2/picamera2.py
Original file line number Diff line number Diff line change
Expand Up @@ -1316,14 +1316,14 @@ def drop_frames(self, num_frames, wait=None, signal_function=None):
functions = [partial(self.set_frame_drops_, num_frames), self.drop_frames_]
return self.dispatch_functions(functions, wait, signal_function, immediate=True)

def capture_file_(self, file_output, name: str, format=None) -> dict:
def capture_file_(self, file_output, name: str, format=None, exif_data=None) -> dict:
if not self.completed_requests:
return (False, None)
request = self.completed_requests.pop(0)
if name == "raw" and formats.is_raw(self.camera_config["raw"]["format"]):
request.save_dng(file_output)
else:
request.save(name, file_output, format=format)
request.save(name, file_output, format=format, exif_data=exif_data)

result = request.get_metadata()
request.release()
Expand All @@ -1335,12 +1335,17 @@ def capture_file(
name: str = "main",
format=None,
wait=None,
signal_function=None) -> dict:
signal_function=None,
exif_data=None) -> dict:
"""Capture an image to a file in the current camera mode.

Return the metadata for the frame captured.

exif_data - dictionary containing user defined exif data (based on `piexif`). This will
overwrite existing exif information generated by picamera2.
"""
functions = [partial(self.capture_file_, file_output, name, format=format)]
functions = [partial(self.capture_file_, file_output, name, format=format,
exif_data=exif_data)]
return self.dispatch_functions(functions, wait, signal_function)

def switch_mode_(self, camera_config):
Expand All @@ -1361,23 +1366,27 @@ def switch_mode_and_drop_frames(self, camera_config, num_frames, wait=None, sign
return self.dispatch_functions(functions, wait, signal_function, immediate=True)

def switch_mode_and_capture_file(self, camera_config, file_output, name="main", format=None,
wait=None, signal_function=None, delay=0):
wait=None, signal_function=None, delay=0, exif_data=None):
"""Switch the camera into a new (capture) mode, capture an image to file.

Then return back to the initial camera mode.

exif_data - dictionary containing user defined exif data (based on `piexif`). This will
overwrite existing exif information generated by picamera2.
"""
preview_config = self.camera_config

def capture_and_switch_back_(self, file_output, preview_config, format):
done, result = self.capture_file_(file_output, name, format=format)
def capture_and_switch_back_(self, file_output, preview_config, format, exif_data=exif_data):
done, result = self.capture_file_(file_output, name, format=format, exif_data=exif_data)
if not done:
return (False, None)
self.switch_mode_(preview_config)
return (True, result)

functions = [partial(self.switch_mode_, camera_config),
partial(self.set_frame_drops_, delay), self.drop_frames_,
partial(capture_and_switch_back_, self, file_output, preview_config, format)]
partial(capture_and_switch_back_, self, file_output, preview_config, format,
exif_data=exif_data)]
return self.dispatch_functions(functions, wait, signal_function, immediate=True)

def switch_mode_and_capture_request(self, camera_config, wait=None, signal_function=None, delay=0):
Expand Down Expand Up @@ -1739,7 +1748,7 @@ def set_overlay(self, overlay) -> None:
def start_and_capture_files(self, name: str = "image{:03d}.jpg",
initial_delay=1, preview_mode="preview",
capture_mode="still", num_files=1, delay=1,
show_preview=True):
show_preview=True, exif_data=None):
"""This function makes capturing multiple images more convenient.

Should only be used in command line line applications (not from a Qt application, for example).
Expand Down Expand Up @@ -1767,6 +1776,9 @@ def start_and_capture_files(self, name: str = "image{:03d}.jpg",
with delay zero, then there may be no images shown. This parameter only has any
effect if a preview is not already running. If it is, it would have to be stopped first
(with the stop_preview method).

exif_data - dictionary containing user defined exif data (based on `piexif`). This will
overwrite existing exif information generated by picamera2.
"""
if self.started:
self.stop()
Expand All @@ -1776,7 +1788,7 @@ def start_and_capture_files(self, name: str = "image{:03d}.jpg",
self.start(show_preview=show_preview)
for i in range(num_files):
time.sleep(initial_delay if i == 0 else delay)
self.switch_mode_and_capture_file(capture_mode, name.format(i))
self.switch_mode_and_capture_file(capture_mode, name.format(i), exif_data=exif_data)
else:
# No preview between captures, it's more efficient just to stay in capture mode.
if initial_delay:
Expand All @@ -1788,14 +1800,14 @@ def start_and_capture_files(self, name: str = "image{:03d}.jpg",
self.configure(capture_mode)
self.start(show_preview=show_preview)
for i in range(num_files):
self.capture_file(name.format(i))
self.capture_file(name.format(i), exif_data=exif_data)
if i == num_files - 1:
break
time.sleep(delay)
self.stop()

def start_and_capture_file(self, name="image.jpg", delay=1, preview_mode="preview",
capture_mode="still", show_preview=True):
capture_mode="still", show_preview=True, exif_data=None):
"""This function makes capturing a single image more convenient.

Should only be used in command line line applications (not from a Qt application, for example).
Expand All @@ -1817,9 +1829,14 @@ def start_and_capture_file(self, name="image.jpg", delay=1, preview_mode="previe
displays an image by default during the preview phase. This parameter only has any
effect if a preview is not already running. If it is, it would have to be stopped first
(with the stop_preview method).

exif_data - dictionary containing user defined exif data (based on `piexif`). This will
overwrite existing exif information generated by picamera2.
"""
self.start_and_capture_files(name=name, initial_delay=delay, preview_mode=preview_mode,
capture_mode=capture_mode, num_files=1, show_preview=show_preview)
capture_mode=capture_mode, num_files=1,
show_preview=show_preview,
exif_data=exif_data)

def start_and_record_video(self, output, encoder=None, config=None, quality=Quality.MEDIUM,
show_preview=False, duration=0, audio=False):
Expand Down
26 changes: 20 additions & 6 deletions picamera2/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,9 +163,14 @@ def make_image(self, name, width=None, height=None):
"""Make a PIL image from the named stream's buffer."""
return self.picam2.helpers.make_image(self.make_buffer(name), self.config[name], width, height)

def save(self, name, file_output, format=None):
"""Save a JPEG or PNG image of the named stream's buffer."""
return self.picam2.helpers.save(self.make_image(name), self.get_metadata(), file_output, format)
def save(self, name, file_output, format=None, exif_data=None):
"""Save a JPEG or PNG image of the named stream's buffer.

exif_data - dictionary containing user defined exif data (based on `piexif`). This will
overwrite existing exif information generated by picamera2.
"""
return self.picam2.helpers.save(self.make_image(name), self.get_metadata(), file_output,
format, exif_data)

def save_dng(self, filename, name="raw"):
"""Save a DNG RAW image of the raw stream's buffer."""
Expand Down Expand Up @@ -241,8 +246,14 @@ def make_image(self, buffer, config, width=None, height=None):
pil_img = pil_img.resize((width, height))
return pil_img

def save(self, img, metadata, file_output, format=None):
"""Save a JPEG or PNG image of the named stream's buffer."""
def save(self, img, metadata, file_output, format=None, exif_data=None):
"""Save a JPEG or PNG image of the named stream's buffer.

exif_data - dictionary containing user defined exif data (based on `piexif`). This will
overwrite existing exif information generated by picamera2.
"""
if exif_data is None:
exif_data = {}
# This is probably a hideously expensive way to do a capture.
start_time = time.monotonic()
exif = b''
Expand Down Expand Up @@ -270,7 +281,10 @@ def save(self, img, metadata, file_output, format=None):
exif_ifd = {piexif.ExifIFD.DateTimeOriginal: datetime_now,
piexif.ExifIFD.ExposureTime: (metadata["ExposureTime"], 1000000),
piexif.ExifIFD.ISOSpeedRatings: int(total_gain * 100)}
exif = piexif.dump({"0th": zero_ifd, "Exif": exif_ifd})
exif_dict = {"0th": zero_ifd, "Exif": exif_ifd}
# merge user provided exif data, overwriting the defaults
exif_dict = exif_dict | exif_data
exif = piexif.dump(exif_dict)
# compress_level=1 saves pngs much faster, and still gets most of the compression.
png_compress_level = self.picam2.options.get("compress_level", 1)
jpeg_quality = self.picam2.options.get("quality", 90)
Expand Down