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)
|
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/")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue