Unify JPEG capture, metadata, and saving; reducing duplication

This commit is contained in:
Julian Stirling 2025-08-22 10:24:16 +01:00
parent 4450fda945
commit 5484b51a1e
5 changed files with 83 additions and 135 deletions

View file

@ -8,10 +8,14 @@ See repository root for licensing information.
from __future__ import annotations
from typing import Literal, Optional, Tuple, Any
from types import EllipsisType
import json
import io
import time
import logging
from datetime import datetime
import tempfile
import os
import numpy as np
from pydantic import RootModel
@ -257,14 +261,42 @@ class BaseCamera(lt.Thing):
def capture_jpeg(
self,
metadata_getter: lt.deps.GetThingStates,
resolution: Literal["lores", "main", "full"] = "main",
wait: Optional[float] = 5,
logger: lt.deps.InvocationLogger,
stream_name: str = "main",
wait: Optional[float | EllipsisType] = ...,
) -> JPEGBlob:
"""Acquire one image from the camera and return as a JPEG blob."""
raise NotImplementedError(
"CameraThings must define their own capture_jpeg method"
"""Acquire one image from the camera as a JPEG.
This will use the internal capture image functionally of capture_image if
the specific camera being used.
:param metadata_getter: LabThings GetThingStates dependency, automatically
injected.
:param logger: LabThings InvocationLogger dependency, automatically injected.
:param stream_name: A stream name supported by this camera.
:param wait: (Optional, float) Set a timeout in seconds. If not set it will
use the default for the underlying camera.
"""
fname = datetime.now().strftime("%Y-%m-%d-%H%M%S.jpeg")
directory = tempfile.TemporaryDirectory()
jpeg_path = os.path.join(directory.name, fname)
# Using Ellipsis to specify no input specified. As `None` set for wait may
# have a meaning as it does for the Picamera. If wait is Ellipsis then
# do not specify.
if wait is Ellipsis:
img = self.capture_image(stream_name)
else:
img = self.capture_image(stream_name, wait)
self._save_capture(
jpeg_path=jpeg_path,
image=img,
metadata=metadata_getter(),
logger=logger,
)
return JPEGBlob.from_temporary_directory(directory, fname)
@lt.thing_action
def grab_jpeg(
self,
@ -331,7 +363,7 @@ class BaseCamera(lt.Thing):
def capture_image(
self,
stream_name: Literal["main", "lores", "raw"],
wait: Optional[float],
wait: Optional[float] = None,
) -> Image:
"""Capture a PIL image from stream stream_name with timeout wait."""
raise NotImplementedError(
@ -486,10 +518,10 @@ class BaseCamera(lt.Thing):
metadata
).encode("utf-8")
piexif.insert(piexif.dump(exif_dict), jpeg_path)
except: # noqa: E722
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.
logger.warning(f"Failed to add metadata to {jpeg_path}")
logger.exception(f"Failed to add metadata to {jpeg_path}")
except Exception as e:
raise IOError(f"An error occurred while saving {jpeg_path}") from e