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)
capture_metadata = self._capture_metadata(metadata_getter())
self._save_capture(
jpeg_path=jpeg_path,
image=img,
metadata=metadata_getter(),
metadata=capture_metadata,
logger=logger,
)
@ -388,7 +390,7 @@ class BaseCamera(lt.Thing):
: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.
"""
image, metadata = self._robust_image_capture(
image, capture_metadata = self._robust_image_capture(
metadata_getter,
logger=logger,
)
@ -396,7 +398,7 @@ class BaseCamera(lt.Thing):
self._save_capture(
jpeg_path,
image,
metadata,
capture_metadata,
logger,
save_resolution,
)
@ -476,14 +478,63 @@ class BaseCamera(lt.Thing):
for capture_attempts in range(5):
try:
metadata = metadata_getter()
capture_metadata = self._capture_metadata(metadata)
image = self.capture_image(stream_name="main", wait=5)
return image, metadata
return image, capture_metadata
except TimeoutError:
logger.warning(
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")
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(
self,
jpeg_path: str,
@ -512,12 +563,7 @@ class BaseCamera(lt.Thing):
# disabled, file size increases and quality is barely or not affected
image.save(jpeg_path, quality=95, subsampling=0)
try:
# Load EXIF metadata from image so it can be added to.
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)
self._add_metadata_to_capture(jpeg_path, metadata)
except Exception:
# We need to capture any exception as there are many reasons metadata
# might not be added. We warn rather than log the error.
@ -652,8 +698,8 @@ class BaseCamera(lt.Thing):
@property
def thing_state(self) -> Mapping[str, Any]:
"""Summary metadata describing the current state of the Thing."""
return {"capture_time": time.time()}
"""Empty metadata dict for subclasses to populate."""
return {}
CameraDependency = lt.deps.direct_thing_client_dependency(BaseCamera, "/camera/")