Capture metadata with more info, including default time tags
This commit is contained in:
parent
f8ee71b9c0
commit
057376a799
1 changed files with 58 additions and 12 deletions
|
|
@ -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/")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue