Further unification of camera array functionality
This commit is contained in:
parent
5484b51a1e
commit
7cd6b11411
4 changed files with 40 additions and 37 deletions
|
|
@ -8,7 +8,6 @@ See repository root for licensing information.
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
from typing import Literal, Optional, Tuple, Any
|
from typing import Literal, Optional, Tuple, Any
|
||||||
from types import EllipsisType
|
|
||||||
import json
|
import json
|
||||||
import io
|
import io
|
||||||
import time
|
import time
|
||||||
|
|
@ -263,7 +262,7 @@ class BaseCamera(lt.Thing):
|
||||||
metadata_getter: lt.deps.GetThingStates,
|
metadata_getter: lt.deps.GetThingStates,
|
||||||
logger: lt.deps.InvocationLogger,
|
logger: lt.deps.InvocationLogger,
|
||||||
stream_name: str = "main",
|
stream_name: str = "main",
|
||||||
wait: Optional[float | EllipsisType] = ...,
|
wait: Optional[float] = None,
|
||||||
) -> JPEGBlob:
|
) -> JPEGBlob:
|
||||||
"""Acquire one image from the camera as a JPEG.
|
"""Acquire one image from the camera as a JPEG.
|
||||||
|
|
||||||
|
|
@ -274,20 +273,15 @@ class BaseCamera(lt.Thing):
|
||||||
injected.
|
injected.
|
||||||
:param logger: LabThings InvocationLogger dependency, automatically injected.
|
:param logger: LabThings InvocationLogger dependency, automatically injected.
|
||||||
:param stream_name: A stream name supported by this camera.
|
:param stream_name: A stream name supported by this camera.
|
||||||
:param wait: (Optional, float) Set a timeout in seconds. If not set it will
|
:param wait: (Optional, float) Set a timeout in seconds. If None it will
|
||||||
use the default for the underlying camera.
|
use the default for the underlying camera.
|
||||||
"""
|
"""
|
||||||
fname = datetime.now().strftime("%Y-%m-%d-%H%M%S.jpeg")
|
fname = datetime.now().strftime("%Y-%m-%d-%H%M%S.jpeg")
|
||||||
directory = tempfile.TemporaryDirectory()
|
directory = tempfile.TemporaryDirectory()
|
||||||
jpeg_path = os.path.join(directory.name, fname)
|
jpeg_path = os.path.join(directory.name, fname)
|
||||||
|
|
||||||
# Using Ellipsis to specify no input specified. As `None` set for wait may
|
img = self.capture_image(stream_name, wait)
|
||||||
# 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(
|
self._save_capture(
|
||||||
jpeg_path=jpeg_path,
|
jpeg_path=jpeg_path,
|
||||||
image=img,
|
image=img,
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,8 @@ class OpenCVCamera(BaseCamera):
|
||||||
@lt.thing_action
|
@lt.thing_action
|
||||||
def capture_array(
|
def capture_array(
|
||||||
self,
|
self,
|
||||||
resolution: Literal["main", "full"] = "full",
|
stream_name: Literal["main", "full"] = "full",
|
||||||
|
wait: Optional[float] = None,
|
||||||
) -> NDArray:
|
) -> NDArray:
|
||||||
"""Acquire one image from the camera and return as an array.
|
"""Acquire one image from the camera and return as an array.
|
||||||
|
|
||||||
|
|
@ -91,7 +92,9 @@ class OpenCVCamera(BaseCamera):
|
||||||
It's likely to be highly inefficient - raw and/or uncompressed captures using
|
It's likely to be highly inefficient - raw and/or uncompressed captures using
|
||||||
binary image formats will be added in due course.
|
binary image formats will be added in due course.
|
||||||
"""
|
"""
|
||||||
logging.warning(f"OpenCV camera doesn't respect {resolution} setting")
|
if wait is not None:
|
||||||
|
logging.warning("OpenCV camera has no wait option. Use None.")
|
||||||
|
logging.warning(f"OpenCV camera doesn't respect {stream_name=}")
|
||||||
ret, frame = self.cap.read()
|
ret, frame = self.cap.read()
|
||||||
if not ret:
|
if not ret:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
|
|
@ -108,7 +111,7 @@ class OpenCVCamera(BaseCamera):
|
||||||
|
|
||||||
This function will produce a JPEG image.
|
This function will produce a JPEG image.
|
||||||
"""
|
"""
|
||||||
logging.warning(
|
if wait is not None:
|
||||||
f"Simulation camera doesn't respect {stream_name=} or {wait=} arguments."
|
logging.warning("OpenCV camera has no wait option. Use None.")
|
||||||
)
|
logging.warning(f"OpenCV camera doesn't respect {stream_name=}")
|
||||||
return Image.fromarray(self.capture_array())
|
return Image.fromarray(self.capture_array())
|
||||||
|
|
|
||||||
|
|
@ -541,12 +541,15 @@ class StreamingPiCamera2(BaseCamera):
|
||||||
|
|
||||||
:param stream_name: (Optional) The PiCamera2 stream to use, should be one of
|
:param stream_name: (Optional) The PiCamera2 stream to use, should be one of
|
||||||
["main", "lores", "raw", "full"]. Default = "main"
|
["main", "lores", "raw", "full"]. Default = "main"
|
||||||
:param wait: (Optional, float) Set a timeout in seconds.
|
:param wait: (Optional, float) Set a timeout in seconds. Default = 0.9s,
|
||||||
|
|
||||||
:rasises TimeoutError: if this time is exceeded during capture. Default = 0.9s,
|
|
||||||
lower than the 1s timeout for the camera. This ensures that our code times
|
lower than the 1s timeout for the camera. This ensures that our code times
|
||||||
out and returns before the camera times out.
|
out and returns before the camera times out. If None is set the default
|
||||||
|
value of 0.9 will be used to prevent the possibility of the camera locking.
|
||||||
|
|
||||||
|
:raises TimeoutError: if this time is exceeded during capture.
|
||||||
"""
|
"""
|
||||||
|
if wait is None:
|
||||||
|
wait = 0.9
|
||||||
if stream_name in ["main", "lores", "raw"]:
|
if stream_name in ["main", "lores", "raw"]:
|
||||||
with self._streaming_picamera() as cam:
|
with self._streaming_picamera() as cam:
|
||||||
return cam.capture_image(stream_name, wait=wait)
|
return cam.capture_image(stream_name, wait=wait)
|
||||||
|
|
@ -572,20 +575,20 @@ class StreamingPiCamera2(BaseCamera):
|
||||||
It's likely to be highly inefficient - raw and/or uncompressed captures using
|
It's likely to be highly inefficient - raw and/or uncompressed captures using
|
||||||
binary image formats will be added in due course.
|
binary image formats will be added in due course.
|
||||||
|
|
||||||
stream_name: (Optional) The PiCamera2 stream to use, should be one of ["main",
|
:param stream_name: (Optional) The PiCamera2 stream to use, should be one of
|
||||||
"lores", "raw", "full"]. Default = "main"
|
["main", "lores", "raw", "full"]. Default = "main"
|
||||||
wait: (Optional, float) Set a timeout in seconds.
|
:param wait: (Optional, float) Set a timeout in seconds. Default = 0.9s,
|
||||||
A TimeoutError is raised if this time is exceeded during capture.
|
lower than the 1s timeout for the camera. This ensures that our code times
|
||||||
Default = 0.9s, lower than the 1s timeout default in picamera yaml settings
|
out and returns before the camera times out. If None is set the default
|
||||||
|
value of 0.9 will be used to prevent the possibility of the camera locking.
|
||||||
|
|
||||||
|
:raises TimeoutError: if this time is exceeded during capture.
|
||||||
"""
|
"""
|
||||||
# This was slower than capture_image for our use case, but directly returning
|
# Note that internally the PiCamera creates a PIL image and then converts to
|
||||||
# an image as an array is still a useful feature
|
# numpy with ``np.array(Image.open(io.BytesIO(self.make_buffer(name))))``.
|
||||||
if stream_name == "full":
|
# As such we use capture_image to get an Image from the picamera and return
|
||||||
with self._streaming_picamera(pause_stream=True) as picam2:
|
# as array
|
||||||
capture_config = picam2.create_still_configuration()
|
return np.array(self.capture_image(stream_name, wait))
|
||||||
return picam2.switch_mode_and_capture_array(capture_config, wait=wait)
|
|
||||||
with self._streaming_picamera() as cam:
|
|
||||||
return cam.capture_array(stream_name, wait=wait)
|
|
||||||
|
|
||||||
@lt.thing_property
|
@lt.thing_property
|
||||||
def camera_configuration(self) -> Mapping:
|
def camera_configuration(self) -> Mapping:
|
||||||
|
|
|
||||||
|
|
@ -277,7 +277,8 @@ class SimulatedCamera(BaseCamera):
|
||||||
@lt.thing_action
|
@lt.thing_action
|
||||||
def capture_array(
|
def capture_array(
|
||||||
self,
|
self,
|
||||||
resolution: Literal["main", "full"] = "full",
|
stream_name: Literal["main", "full"] = "full",
|
||||||
|
wait: Optional[float] = None,
|
||||||
) -> ArrayModel:
|
) -> ArrayModel:
|
||||||
"""Acquire one image from the camera and return as an array.
|
"""Acquire one image from the camera and return as an array.
|
||||||
|
|
||||||
|
|
@ -285,7 +286,9 @@ class SimulatedCamera(BaseCamera):
|
||||||
It's likely to be highly inefficient - raw and/or uncompressed captures using
|
It's likely to be highly inefficient - raw and/or uncompressed captures using
|
||||||
binary image formats will be added in due course.
|
binary image formats will be added in due course.
|
||||||
"""
|
"""
|
||||||
logging.warning(f"Simulation camera doesn't respect {resolution=} setting")
|
if wait is not None:
|
||||||
|
logging.warning("Simulation camera has no wait option. Use None.")
|
||||||
|
logging.warning(f"Simulation camera camera doesn't respect {stream_name=}")
|
||||||
return self.generate_frame()
|
return self.generate_frame()
|
||||||
|
|
||||||
def capture_image(
|
def capture_image(
|
||||||
|
|
@ -297,9 +300,9 @@ class SimulatedCamera(BaseCamera):
|
||||||
|
|
||||||
It is used for capture to memory.
|
It is used for capture to memory.
|
||||||
"""
|
"""
|
||||||
logging.warning(
|
if wait is not None:
|
||||||
f"Simulation camera doesn't respect {stream_name=} or {wait=} arguments."
|
logging.warning("Simulation camera has no wait option. Use None.")
|
||||||
)
|
logging.warning(f"Simulation camera camera doesn't respect {stream_name=}")
|
||||||
return Image.fromarray(self.generate_frame())
|
return Image.fromarray(self.generate_frame())
|
||||||
|
|
||||||
@lt.thing_action
|
@lt.thing_action
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue