Merge branch 'v3-png-capture' into 'v3'
PNG Capture See merge request openflexure/openflexure-microscope-server!569
This commit is contained in:
commit
1e3548e803
3 changed files with 51 additions and 16 deletions
Binary file not shown.
|
|
@ -437,13 +437,44 @@ class BaseCamera(OFMThing):
|
||||||
capture_metadata = self._capture_metadata()
|
capture_metadata = self._capture_metadata()
|
||||||
|
|
||||||
self._save_capture(
|
self._save_capture(
|
||||||
jpeg_path=jpeg_path,
|
path=jpeg_path,
|
||||||
image=img,
|
image=img,
|
||||||
metadata=capture_metadata,
|
metadata=capture_metadata,
|
||||||
)
|
)
|
||||||
|
|
||||||
return JPEGBlob.from_temporary_directory(directory, fname)
|
return JPEGBlob.from_temporary_directory(directory, fname)
|
||||||
|
|
||||||
|
@lt.action
|
||||||
|
def capture_png(
|
||||||
|
self,
|
||||||
|
stream_name: Literal["main", "lores", "full"] = "main",
|
||||||
|
wait: Optional[float] = None,
|
||||||
|
) -> PNGBlob:
|
||||||
|
"""Acquire one image from the camera as a PNG.
|
||||||
|
|
||||||
|
This will use the internal capture image functionally of capture_image of
|
||||||
|
the specific camera being used.
|
||||||
|
|
||||||
|
:param stream_name: A stream name supported by this camera.
|
||||||
|
:param wait: (Optional, float) Set a timeout in seconds. If None it will
|
||||||
|
use the default for the underlying camera.
|
||||||
|
"""
|
||||||
|
fname = datetime.now().strftime("%Y-%m-%d-%H%M%S.jpeg")
|
||||||
|
directory = tempfile.TemporaryDirectory()
|
||||||
|
png_path = os.path.join(directory.name, fname)
|
||||||
|
|
||||||
|
img = self.capture_image(stream_name, wait)
|
||||||
|
|
||||||
|
capture_metadata = self._capture_metadata()
|
||||||
|
|
||||||
|
self._save_capture(
|
||||||
|
path=png_path,
|
||||||
|
image=img,
|
||||||
|
metadata=capture_metadata,
|
||||||
|
)
|
||||||
|
|
||||||
|
return PNGBlob.from_temporary_directory(directory, fname)
|
||||||
|
|
||||||
@lt.action
|
@lt.action
|
||||||
def grab_jpeg(
|
def grab_jpeg(
|
||||||
self,
|
self,
|
||||||
|
|
@ -564,7 +595,7 @@ class BaseCamera(OFMThing):
|
||||||
image, metadata = self._memory_buffer.get_image(buffer_id)
|
image, metadata = self._memory_buffer.get_image(buffer_id)
|
||||||
|
|
||||||
self._save_capture(
|
self._save_capture(
|
||||||
jpeg_path=jpeg_path,
|
path=jpeg_path,
|
||||||
image=image,
|
image=image,
|
||||||
metadata=metadata,
|
metadata=metadata,
|
||||||
save_resolution=save_resolution,
|
save_resolution=save_resolution,
|
||||||
|
|
@ -656,7 +687,7 @@ class BaseCamera(OFMThing):
|
||||||
|
|
||||||
def _save_capture(
|
def _save_capture(
|
||||||
self,
|
self,
|
||||||
jpeg_path: str,
|
path: str,
|
||||||
image: Image.Image,
|
image: Image.Image,
|
||||||
metadata: Mapping[str, Any],
|
metadata: Mapping[str, Any],
|
||||||
save_resolution: Optional[Tuple[int, int]] = None,
|
save_resolution: Optional[Tuple[int, int]] = None,
|
||||||
|
|
@ -672,22 +703,25 @@ class BaseCamera(OFMThing):
|
||||||
if save_resolution is not None and image.size != save_resolution:
|
if save_resolution is not None and image.size != save_resolution:
|
||||||
image = image.resize(save_resolution, Image.Resampling.BOX)
|
image = image.resize(save_resolution, Image.Resampling.BOX)
|
||||||
try:
|
try:
|
||||||
# Per PIL documentation,
|
save_kwargs: dict[str, Any] = {}
|
||||||
# (https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html#jpeg)
|
if path.endswith("jpg"):
|
||||||
# there are two factors when saving a JPEG. Subsampling affects the colour,
|
# Per PIL documentation,
|
||||||
# quality affects the pixels.
|
# (https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html#jpeg)
|
||||||
# subsampling = 0 disables subsampling of colour
|
# there are two factors when saving a JPEG. Subsampling affects the colour,
|
||||||
# quality = 95 is the maximum recommended - above this, JPEG compression is
|
# quality affects the pixels.
|
||||||
# disabled, file size increases and quality is barely or not affected
|
# subsampling = 0 disables subsampling of colour
|
||||||
image.save(jpeg_path, quality=95, subsampling=0)
|
# quality = 95 is the maximum recommended - above this, JPEG compression is
|
||||||
|
# disabled, file size increases and quality is barely or not affected
|
||||||
|
save_kwargs = {"quality": 95, "subsampling": 0}
|
||||||
|
image.save(path, **save_kwargs)
|
||||||
try:
|
try:
|
||||||
self._add_metadata_to_capture(jpeg_path, dict(metadata))
|
self._add_metadata_to_capture(path, dict(metadata))
|
||||||
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.
|
||||||
self.logger.exception(f"Failed to add metadata to {jpeg_path}")
|
self.logger.exception(f"Failed to add metadata to {path}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise IOError(f"An error occurred while saving {jpeg_path}") from e
|
raise IOError(f"An error occurred while saving {path}") from e
|
||||||
|
|
||||||
settling_time: float = lt.setting(default=0.2, ge=0)
|
settling_time: float = lt.setting(default=0.2, ge=0)
|
||||||
"""The settling time when calling the ``settle()`` method."""
|
"""The settling time when calling the ``settle()`` method."""
|
||||||
|
|
|
||||||
|
|
@ -40,10 +40,11 @@ def test_grab_jpeg(simulation_test_env):
|
||||||
assert image.size == (820, 616)
|
assert image.size == (820, 616)
|
||||||
|
|
||||||
|
|
||||||
def test_capture_jpeg_metadata(simulation_test_env):
|
@pytest.mark.parametrize("fmt", ["jpeg", "png"])
|
||||||
|
def test_capture_and_metadata(simulation_test_env, fmt):
|
||||||
"""Check that the position is encoded into the image metadata."""
|
"""Check that the position is encoded into the image metadata."""
|
||||||
camera = simulation_test_env.get_thing_client("camera")
|
camera = simulation_test_env.get_thing_client("camera")
|
||||||
blob = camera.capture_jpeg()
|
blob = getattr(camera, f"capture_{fmt}")()
|
||||||
image = Image.open(blob.open())
|
image = Image.open(blob.open())
|
||||||
exif_dict = piexif.load(image.info["exif"])
|
exif_dict = piexif.load(image.info["exif"])
|
||||||
encoded_metadata = exif_dict["Exif"][piexif.ExifIFD.UserComment]
|
encoded_metadata = exif_dict["Exif"][piexif.ExifIFD.UserComment]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue