Get images as a PIL image from camera, use resizing and saving as settling time in stack
This commit is contained in:
parent
dbed1c6971
commit
744ec28f93
3 changed files with 46 additions and 31 deletions
|
|
@ -14,13 +14,11 @@ from typing import Annotated, Mapping, Optional, Sequence
|
|||
import os
|
||||
import shutil
|
||||
import glob
|
||||
import os
|
||||
import shutil
|
||||
import glob
|
||||
|
||||
from fastapi import Depends
|
||||
import numpy as np
|
||||
from pydantic import BaseModel
|
||||
from PIL import Image
|
||||
|
||||
from labthings_fastapi.thing import Thing
|
||||
from labthings_fastapi.dependencies.blocking_portal import BlockingPortal
|
||||
|
|
@ -34,13 +32,12 @@ from .camera import RawCameraDependency as Camera
|
|||
from .camera import CameraDependency as WrappedCamera
|
||||
from .stage import StageDependency as Stage
|
||||
from .capture import CaptureThing
|
||||
import numpy as np
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
CaptureDep = direct_thing_client_dependency(CaptureThing, "/capture/")
|
||||
|
||||
SETTLING_TIME = 0.3
|
||||
STACK_OVERSHOOT = 200
|
||||
|
||||
|
||||
class JPEGSharpnessMonitor:
|
||||
|
|
@ -302,7 +299,8 @@ class AutofocusThing(Thing):
|
|||
images_to_capture = self.stack_images_to_capture
|
||||
|
||||
stack_z_range = stack_dz * (images_to_capture - 1)
|
||||
stage.move_relative(z=-stack_z_range / 2)
|
||||
stage.move_relative(z=-(STACK_OVERSHOOT + stack_z_range / 2))
|
||||
stage.move_relative(z=STACK_OVERSHOOT)
|
||||
|
||||
for capture_count in range(images_to_capture):
|
||||
time.sleep(SETTLING_TIME)
|
||||
|
|
@ -311,17 +309,29 @@ class AutofocusThing(Thing):
|
|||
stack_dir,
|
||||
f"{capture_count}.jpeg",
|
||||
)
|
||||
capture._capture_and_save(
|
||||
jpeg_path=jpeg_path,
|
||||
start = time.time()
|
||||
image, metadata = capture._capture_image(
|
||||
cam=cam,
|
||||
logger=logger,
|
||||
metadata_getter=metadata_getter,
|
||||
target_resolution=target_resolution,
|
||||
logger=logger,
|
||||
)
|
||||
|
||||
# If the stack isn't complete yet, move
|
||||
if capture_count + 1 < images_to_capture:
|
||||
stage.move_relative(z=stack_dz)
|
||||
captured = time.time()
|
||||
# There's an unnecessary move up at the end of the stack
|
||||
stage.move_relative(z=stack_dz)
|
||||
moved = time.time()
|
||||
image = image.resize(target_resolution, Image.LANCZOS)
|
||||
downsampled = time.time()
|
||||
capture._save_capture(
|
||||
jpeg_path=jpeg_path,
|
||||
image=image,
|
||||
metadata=metadata,
|
||||
logger=logger,
|
||||
)
|
||||
saved = time.time()
|
||||
logger.info(f"Capturing took {round(captured - start, 2)} s")
|
||||
logger.info(f"Resizing took {round(downsampled - moved, 2)} s")
|
||||
logger.info(f"Saving took {round(saved - downsampled, 2)} s")
|
||||
logger.info(f"Effective settling time was {round(saved - moved, 2)} s")
|
||||
|
||||
self.copy_central_image_from_stack(images_dir, stack_dir)
|
||||
|
||||
|
|
|
|||
|
|
@ -84,6 +84,11 @@ class CameraProtocol(Protocol):
|
|||
for the main stream"""
|
||||
...
|
||||
|
||||
@thing_action
|
||||
def capture_image(self, stream_name, wait):
|
||||
"""Capture a PIL image from stream stream_name with timeout wait"""
|
||||
...
|
||||
|
||||
|
||||
class BaseCamera(Thing):
|
||||
"""A Thing representing a camera
|
||||
|
|
@ -194,6 +199,11 @@ class CameraStub(BaseCamera):
|
|||
for the main stream"""
|
||||
raise NotImplementedError("Cameras must not inherit from CameraStub")
|
||||
|
||||
@thing_action
|
||||
def capture_image(self, stream_name, wait):
|
||||
"""Capture a PIL image from stream stream_name with timeout wait"""
|
||||
raise Exception
|
||||
|
||||
|
||||
CameraDependency = direct_thing_client_dependency(CameraStub, "/camera/")
|
||||
RawCameraDependency = raw_thing_dependency(CameraProtocol)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import numpy as np
|
||||
from PIL import Image
|
||||
import time
|
||||
import piexif
|
||||
import json
|
||||
|
|
@ -28,7 +26,6 @@ class CaptureThing(Thing):
|
|||
cam: CamDep,
|
||||
logger: InvocationLogger,
|
||||
metadata_getter: GetThingStates,
|
||||
target_resolution: tuple[int, int],
|
||||
) -> None:
|
||||
"""Capture an image and save it to disk
|
||||
|
||||
|
|
@ -36,14 +33,18 @@ class CaptureThing(Thing):
|
|||
that the stage may be moved while it's saved.
|
||||
"""
|
||||
capture_start = time.time()
|
||||
image, metadata = self._capture_array(
|
||||
image, metadata = self._capture_image(
|
||||
cam,
|
||||
metadata_getter,
|
||||
target_resolution=target_resolution,
|
||||
logger=logger,
|
||||
)
|
||||
acquisition_time = time.time()
|
||||
self._save_capture(jpeg_path, image, metadata, logger)
|
||||
self._save_capture(
|
||||
jpeg_path,
|
||||
image,
|
||||
metadata,
|
||||
logger,
|
||||
)
|
||||
save_time = time.time()
|
||||
acquisition_duration = round(acquisition_time - capture_start, 1)
|
||||
saving_duration = round(save_time - acquisition_time, 1)
|
||||
|
|
@ -56,7 +57,7 @@ class CaptureThing(Thing):
|
|||
"""Capture a JPEG (from a JPEGBlob) to disk"""
|
||||
for capture_attempts in range(5):
|
||||
try:
|
||||
jpeg = cam.capture_jpeg(resolution="full", wait=5)
|
||||
jpeg = cam.capture_jpeg(resolution="main", wait=5)
|
||||
jpeg.save(filename)
|
||||
return
|
||||
except TimeoutError:
|
||||
|
|
@ -76,12 +77,11 @@ class CaptureThing(Thing):
|
|||
cam.restart_stream()
|
||||
|
||||
@thing_action
|
||||
def _capture_array(
|
||||
def _capture_image(
|
||||
self,
|
||||
cam: CamDep,
|
||||
metadata_getter: GetThingStates,
|
||||
logger: InvocationLogger,
|
||||
target_resolution: tuple[int, int] = (1640, 1232),
|
||||
):
|
||||
"""Capture an image in memory and return it with metadata
|
||||
CaptureError raised if the capture fails for any reason
|
||||
|
|
@ -90,13 +90,7 @@ class CaptureThing(Thing):
|
|||
for capture_attempts in range(5):
|
||||
try:
|
||||
metadata = metadata_getter()
|
||||
image = Image.fromarray(
|
||||
cam.capture_array(stream_name="main", wait=5)[..., :3].astype(
|
||||
"uint8"
|
||||
),
|
||||
"RGB",
|
||||
)
|
||||
image = image.resize(target_resolution, Image.LANCZOS)
|
||||
image = cam.capture_image(stream_name="main", wait=5)
|
||||
return image, metadata
|
||||
except TimeoutError:
|
||||
logger.warning(
|
||||
|
|
@ -104,10 +98,11 @@ class CaptureThing(Thing):
|
|||
)
|
||||
raise CaptureError("An error occurred while capturing after 5 attempts")
|
||||
|
||||
@thing_action
|
||||
def _save_capture(
|
||||
self,
|
||||
jpeg_path: str,
|
||||
image: np.ndarray,
|
||||
image,
|
||||
metadata: dict,
|
||||
logger: InvocationLogger,
|
||||
) -> None:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue