Skip to content

Commit

Permalink
Add user defined exif_data when capturing files
Browse files Browse the repository at this point in the history
When capturing to EXIF-capable files, the user can provide the
dictionary `exif_data` which is merged with the auto-generated EXIF-data
before writing into the image file.

Changes affect the `capture_file_` method and all derivatives.

Signed-off-by: Simon Lenz <[email protected]>
  • Loading branch information
Amudtogal authored and Simon Lenz committed Oct 5, 2023
1 parent 0759b0e commit 62efbf8
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 19 deletions.
43 changes: 30 additions & 13 deletions picamera2/picamera2.py
Original file line number Diff line number Diff line change
Expand Up @@ -1202,14 +1202,14 @@ def dispatch_functions(self, functions, wait, signal_function=None, immediate=Fa
self._run_process_requests()
return job.get_result() if wait else job

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 @@ -1221,12 +1221,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 @@ -1241,22 +1246,26 @@ def switch_mode(self, camera_config, wait=None, signal_function=None):
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):
wait=None, signal_function=None, 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(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):
Expand Down Expand Up @@ -1620,7 +1629,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 @@ -1648,6 +1657,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 @@ -1657,7 +1669,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 @@ -1669,14 +1681,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=exif_data):
"""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 @@ -1698,9 +1710,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 @@ -162,9 +162,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 @@ -240,8 +245,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 @@ -269,7 +280,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

0 comments on commit 62efbf8

Please sign in to comment.