Capture metadata with more info, including default time tags

This commit is contained in:
jaknapper 2025-10-21 18:59:46 +01:00 committed by Julian Stirling
parent f8ee71b9c0
commit 057376a799

View file

@ -288,10 +288,12 @@ class BaseCamera(lt.Thing):
img = self.capture_image(stream_name, wait) img = self.capture_image(stream_name, wait)
capture_metadata = self._capture_metadata(metadata_getter())
self._save_capture( self._save_capture(
jpeg_path=jpeg_path, jpeg_path=jpeg_path,
image=img, image=img,
metadata=metadata_getter(), metadata=capture_metadata,
logger=logger, logger=logger,
) )
@ -388,7 +390,7 @@ class BaseCamera(lt.Thing):
:param save_resolution: can be set to resize the image before saving. By :param save_resolution: can be set to resize the image before saving. By
default this is None meaning that the image is saved at original resolution. default this is None meaning that the image is saved at original resolution.
""" """
image, metadata = self._robust_image_capture( image, capture_metadata = self._robust_image_capture(
metadata_getter, metadata_getter,
logger=logger, logger=logger,
) )
@ -396,7 +398,7 @@ class BaseCamera(lt.Thing):
self._save_capture( self._save_capture(
jpeg_path, jpeg_path,
image, image,
metadata, capture_metadata,
logger, logger,
save_resolution, save_resolution,
) )
@ -476,14 +478,63 @@ class BaseCamera(lt.Thing):
for capture_attempts in range(5): for capture_attempts in range(5):
try: try:
metadata = metadata_getter() metadata = metadata_getter()
capture_metadata = self._capture_metadata(metadata)
image = self.capture_image(stream_name="main", wait=5) image = self.capture_image(stream_name="main", wait=5)
return image, metadata return image, capture_metadata
except TimeoutError: except TimeoutError:
logger.warning( logger.warning(
f"Attempt {capture_attempts + 1} to capture image timed out. Do you have enough RAM?" f"Attempt {capture_attempts + 1} to capture image timed out. Do you have enough RAM?"
) )
raise CaptureError("An error occurred while capturing after 5 attempts") raise CaptureError("An error occurred while capturing after 5 attempts")
def _capture_metadata(
self,
metadata: dict,
) -> None:
"""Return the metadata for a capture, from the thing states, time and known names."""
return {
"capture_time": datetime.now().timestamp(),
"make": "OpenFlexure",
"model": "OpenFlexure Microscope",
"things_states": metadata,
}
def _add_metadata_to_capture(self, jpeg_path: str, capture_metadata: dict) -> None:
"""Add the EXIF metadata for a JPEG image.
This adds:
- UserComment (JSON-encoded metadata from the Things)
- Capture time (DateTimeOriginal, DateTimeDigitized, 0th DateTime)
- Camera Make and Model
"""
# Load existing EXIF
exif_dict = piexif.load(jpeg_path)
user_metadata = capture_metadata["things_states"]
capture_time = capture_metadata["capture_time"]
# Update UserComment with JSON-encoded metadata
exif_dict["Exif"][piexif.ExifIFD.UserComment] = json.dumps(
user_metadata
).encode("utf-8")
capture_time_str = datetime.fromtimestamp(capture_time).strftime(
"%Y:%m:%d %H:%M:%S"
)
# Update all relevant EXIF date fields
exif_dict["Exif"][piexif.ExifIFD.DateTimeOriginal] = capture_time_str
exif_dict["Exif"][piexif.ExifIFD.DateTimeDigitized] = capture_time_str
exif_dict["0th"][piexif.ImageIFD.DateTime] = capture_time_str
# Update Make and Model
exif_dict["0th"][piexif.ImageIFD.Make] = capture_metadata["make"]
exif_dict["0th"][piexif.ImageIFD.Model] = capture_metadata["model"]
# Write the updated EXIF back to the file
piexif.insert(piexif.dump(exif_dict), jpeg_path)
def _save_capture( def _save_capture(
self, self,
jpeg_path: str, jpeg_path: str,
@ -512,12 +563,7 @@ class BaseCamera(lt.Thing):
# disabled, file size increases and quality is barely or not affected # disabled, file size increases and quality is barely or not affected
image.save(jpeg_path, quality=95, subsampling=0) image.save(jpeg_path, quality=95, subsampling=0)
try: try:
# Load EXIF metadata from image so it can be added to. self._add_metadata_to_capture(jpeg_path, metadata)
exif_dict = piexif.load(jpeg_path)
exif_dict["Exif"][piexif.ExifIFD.UserComment] = json.dumps(
metadata
).encode("utf-8")
piexif.insert(piexif.dump(exif_dict), jpeg_path)
except Exception: except Exception:
# We need to capture any exception as there are many reasons metadata # We need to capture any exception as there are many reasons metadata
# might not be added. We warn rather than log the error. # might not be added. We warn rather than log the error.
@ -652,8 +698,8 @@ class BaseCamera(lt.Thing):
@property @property
def thing_state(self) -> Mapping[str, Any]: def thing_state(self) -> Mapping[str, Any]:
"""Summary metadata describing the current state of the Thing.""" """Empty metadata dict for subclasses to populate."""
return {"capture_time": time.time()} return {}
CameraDependency = lt.deps.direct_thing_client_dependency(BaseCamera, "/camera/") CameraDependency = lt.deps.direct_thing_client_dependency(BaseCamera, "/camera/")