Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 9 additions & 19 deletions docs/examples/example.ipynb

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion examples/apis.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"importName": "GenericComponent"
},
"camera": {
"func": "get_image",
"func": "get_images",
"packagePath": "viam.components"
},
"encoder": {
Expand Down
4 changes: 3 additions & 1 deletion examples/server/v1/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from viam.components.camera import Camera
from viam.components.encoder import Encoder
from viam.components.motor import Motor
from viam.media.utils.pil import viam_to_pil_image
from viam.media.video import CameraMimeType
from viam.robot.client import RobotClient
from viam.rpc.dial import DialOptions
Expand Down Expand Up @@ -34,7 +35,8 @@ async def client():

print("\n#### CAMERA ####")
camera = Camera.from_robot(robot, "camera0")
img = await camera.get_image(mime_type=CameraMimeType.PNG)
images, _ = await camera.get_images()
img = viam_to_pil_image(images[0])
assert isinstance(img, Image)
img.show()
await asyncio.sleep(1)
Expand Down
11 changes: 6 additions & 5 deletions examples/server/v1/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@
else:
from typing import AsyncIterator

from datetime import timedelta
from datetime import datetime, timedelta
from io import BytesIO
from multiprocessing import Lock
from pathlib import Path
from typing import Any, Dict, List, Mapping, Optional, Tuple

from google.protobuf.timestamp_pb2 import Timestamp
from PIL import Image

from viam.components.arm import Arm
Expand Down Expand Up @@ -434,11 +435,11 @@ def __init__(self, name: str):
img.close()
super().__init__(name)

async def get_image(self, mime_type: str = "", extra: Optional[Dict[str, Any]] = None, **kwargs) -> ViamImage:
return self.image

async def get_images(self, timeout: Optional[float] = None, **kwargs) -> Tuple[List[NamedImage], ResponseMetadata]:
raise NotImplementedError()
ts = Timestamp()
ts.FromDatetime(datetime.now())
metadata = ResponseMetadata(captured_at=ts)
return [NamedImage(self.name, self.image.data, self.image.mime_type)], metadata

async def get_point_cloud(self, extra: Optional[Dict[str, Any]] = None, **kwargs) -> Tuple[bytes, str]:
raise NotImplementedError()
Expand Down
2 changes: 1 addition & 1 deletion src/viam/app/data_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -571,7 +571,7 @@ async def get_latest_tabular_data(
part_id="77ae3145-7b91-123a-a234-e567cdca8910",
resource_name="camera-1",
resource_api="rdk:component:camera",
method_name="GetImage",
method_name="GetImages",
additional_params={"docommand_input": {"test": "test"}}
)

Expand Down
28 changes: 1 addition & 27 deletions src/viam/components/camera/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import sys
from typing import Any, Dict, Final, Optional, Sequence, Tuple

from viam.media.video import NamedImage, ViamImage
from viam.media.video import NamedImage
from viam.proto.common import ResponseMetadata
from viam.proto.component.camera import GetPropertiesResponse
from viam.resource.types import API, RESOURCE_NAMESPACE_RDK, RESOURCE_TYPE_COMPONENT
Expand Down Expand Up @@ -36,32 +36,6 @@ class Camera(ComponentBase):

Properties: "TypeAlias" = GetPropertiesResponse

@abc.abstractmethod
async def get_image(
self, mime_type: str = "", *, extra: Optional[Dict[str, Any]] = None, timeout: Optional[float] = None, **kwargs
) -> ViamImage:
"""Get the next image from the camera as a ViamImage.
Be sure to close the image when finished.

NOTE: If the mime type is ``image/vnd.viam.dep`` you can use :func:`viam.media.video.ViamImage.bytes_to_depth_array`
to convert the data to a standard representation.

::

my_camera = Camera.from_robot(machine, "my_camera")
frame = await my_camera.get_image()
print(f"Frame: {frame}")

Args:
mime_type (str): The desired mime type of the image. This does not guarantee output type

Returns:
ViamImage: The frame.

For more information, see `Camera component <https://docs.viam.com/dev/reference/apis/components/camera/#getimage>`_.
"""
...

@abc.abstractmethod
async def get_images(
self,
Expand Down
23 changes: 2 additions & 21 deletions src/viam/components/camera/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@

from grpclib.client import Channel

from viam.media.video import CameraMimeType, NamedImage, ViamImage
from viam.media.video import CameraMimeType, NamedImage
from viam.proto.common import DoCommandRequest, DoCommandResponse, Geometry, ResponseMetadata
from viam.proto.component.camera import (
CameraServiceStub,
GetImageRequest,
GetImageResponse,
GetImagesRequest,
GetImagesResponse,
GetPointCloudRequest,
Expand All @@ -30,19 +28,6 @@ def __init__(self, name: str, channel: Channel):
self.client = CameraServiceStub(channel)
super().__init__(name)

async def get_image(
self,
mime_type: str = "",
*,
extra: Optional[Dict[str, Any]] = None,
timeout: Optional[float] = None,
**kwargs,
) -> ViamImage:
md = kwargs.get("metadata", self.Metadata()).proto
request = GetImageRequest(name=self.name, mime_type=mime_type, extra=dict_to_struct(extra))
response: GetImageResponse = await self.client.GetImage(request, timeout=timeout, metadata=md)
return ViamImage(response.image, CameraMimeType.from_string(response.mime_type))

async def get_images(
self,
*,
Expand All @@ -56,11 +41,7 @@ async def get_images(
response: GetImagesResponse = await self.client.GetImages(request, timeout=timeout, metadata=md)
imgs = []
for img_data in response.images:
if img_data.mime_type:
mime_type = CameraMimeType.from_string(img_data.mime_type)
else:
# TODO(RSDK-11728): remove this once we deleted the format field
mime_type = CameraMimeType.from_proto(img_data.format)
mime_type = CameraMimeType.from_string(img_data.mime_type)
img = NamedImage(img_data.source_name, img_data.image, mime_type)
imgs.append(img)
resp_metadata: ResponseMetadata = response.response_metadata
Expand Down
37 changes: 8 additions & 29 deletions src/viam/components/camera/service.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
# TODO: Update type checking based with RSDK-4089
# pyright: reportGeneralTypeIssues=false
from google.api.httpbody_pb2 import HttpBody # type: ignore
from grpclib.server import Stream

from viam.media.video import CameraMimeType
from viam.errors import NotSupportedError
from viam.proto.common import DoCommandRequest, DoCommandResponse, GetGeometriesRequest, GetGeometriesResponse
from viam.proto.component.camera import (
CameraServiceBase,
GetImageRequest,
GetImageResponse,
GetImagesRequest,
GetImagesResponse,
GetPointCloudRequest,
GetPointCloudResponse,
GetPropertiesRequest,
GetPropertiesResponse,
Image,
RenderFrameRequest,
)
from viam.resource.rpc_service_base import ResourceRPCServiceBase
from viam.utils import dict_to_struct, struct_to_dict
Expand All @@ -31,16 +27,13 @@ class CameraRPCService(CameraServiceBase, ResourceRPCServiceBase[Camera]):

RESOURCE_TYPE = Camera

async def GetImage(self, stream: Stream[GetImageRequest, GetImageResponse]) -> None:
request = await stream.recv_message()
assert request is not None
name = request.name
camera = self.get_resource(name)
async def GetImage(self, stream: Stream) -> None:
"""Deprecated: Use GetImages instead."""
raise NotSupportedError("GetImage is deprecated. Use GetImages instead.")

timeout = stream.deadline.time_remaining() if stream.deadline else None
image = await camera.get_image(request.mime_type, extra=struct_to_dict(request.extra), timeout=timeout, metadata=stream.metadata)
response = GetImageResponse(mime_type=image.mime_type, image=image.data)
await stream.send_message(response)
async def RenderFrame(self, stream: Stream) -> None:
"""Deprecated: Use GetImages instead."""
raise NotSupportedError("RenderFrame is deprecated. Use GetImages instead.")

async def GetImages(self, stream: Stream[GetImagesRequest, GetImagesResponse]) -> None:
request = await stream.recv_message()
Expand All @@ -56,25 +49,11 @@ async def GetImages(self, stream: Stream[GetImagesRequest, GetImagesResponse]) -
)
img_bytes_lst = []
for img in images:
mime_type = CameraMimeType.from_string(img.mime_type)
# TODO(RSDK-11728): remove this fmt logic once we deleted the format field
fmt = mime_type.to_proto() # Will be Format.FORMAT_UNSPECIFIED if an unsupported/custom mime type is set

img_bytes = img.data
img_bytes_lst.append(Image(source_name=img.name, mime_type=img.mime_type, format=fmt, image=img_bytes))
img_bytes_lst.append(Image(source_name=img.name, mime_type=img.mime_type, image=img_bytes))
response = GetImagesResponse(images=img_bytes_lst, response_metadata=metadata)
await stream.send_message(response)

async def RenderFrame(self, stream: Stream[RenderFrameRequest, HttpBody]) -> None: # pyright: ignore [reportInvalidTypeForm]
request = await stream.recv_message()
assert request is not None
name = request.name
camera = self.get_resource(name)
timeout = stream.deadline.time_remaining() if stream.deadline else None
image = await camera.get_image(request.mime_type, timeout=timeout, metadata=stream.metadata)
response = HttpBody(data=image.data, content_type=image.mime_type) # type: ignore
await stream.send_message(response)

async def GetPointCloud(self, stream: Stream[GetPointCloudRequest, GetPointCloudResponse]) -> None:
request = await stream.recv_message()
assert request is not None
Expand Down
39 changes: 0 additions & 39 deletions src/viam/media/video.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from typing_extensions import ClassVar, Self

from viam.errors import NotSupportedError
from viam.proto.component.camera import Format

from .viam_rgba import RGBA_HEADER_LENGTH, RGBA_MAGIC_NUMBER

Expand Down Expand Up @@ -70,44 +69,6 @@ def from_string(cls, value: str) -> Self:
value_mime = value[:-5] if value.endswith("+lazy") else value # ViamImage lazy encodes by default
return cls(value_mime)

@classmethod
def from_proto(cls, format: Format.ValueType) -> Self:
"""Returns the mimetype from a proto enum.

Args:
format (Format.ValueType): The mimetype in a proto enum.

Returns:
Self: The mimetype.
"""
mimetypes = {
Format.FORMAT_RAW_RGBA: cls.VIAM_RGBA,
Format.FORMAT_RAW_DEPTH: cls.VIAM_RAW_DEPTH,
Format.FORMAT_JPEG: cls.JPEG,
Format.FORMAT_PNG: cls.PNG,
}
return cls(mimetypes.get(format, cls.JPEG))

@property
def proto(self) -> Format.ValueType:
"""Returns the mimetype in a proto enum.

Returns:
Format.ValueType: The mimetype in a proto enum.
"""
formats = {
self.VIAM_RGBA: Format.FORMAT_RAW_RGBA,
self.VIAM_RAW_DEPTH: Format.FORMAT_RAW_DEPTH,
self.JPEG: Format.FORMAT_JPEG,
self.PNG: Format.FORMAT_PNG,
}
return formats.get(self, Format.FORMAT_UNSPECIFIED)

def to_proto(self) -> Format.ValueType:
"""
DEPRECATED: Use `CameraMimeType.proto`
"""
return self.proto


CameraMimeType.VIAM_RGBA = CameraMimeType.from_string("image/vnd.viam.rgba")
Expand Down
2 changes: 0 additions & 2 deletions src/viam/proto/component/camera/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from ....gen.component.camera.v1.camera_grpc import CameraServiceBase, CameraServiceStub, UnimplementedCameraServiceBase
from ....gen.component.camera.v1.camera_pb2 import (
DistortionParameters,
Format,
GetImageRequest,
GetImageResponse,
GetImagesRequest,
Expand All @@ -28,7 +27,6 @@
"CameraServiceStub",
"UnimplementedCameraServiceBase",
"DistortionParameters",
"Format",
"GetImageRequest",
"GetImageResponse",
"GetImagesRequest",
Expand Down
6 changes: 1 addition & 5 deletions src/viam/services/vision/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,7 @@ async def capture_all_from_camera(
result = CaptureAllResult()
result.extra = struct_to_dict(response.extra)
if return_image:
# TODO(RSDK-11728): remove this branching logic once we deleted the format field
if response.image.mime_type:
mime_type = CameraMimeType.from_string(response.image.mime_type)
else:
mime_type = CameraMimeType.from_proto(response.image.format)
mime_type = CameraMimeType.from_string(response.image.mime_type)
img = ViamImage(response.image.image, mime_type)
result.image = img
if return_classifications:
Expand Down
5 changes: 1 addition & 4 deletions src/viam/services/vision/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,8 @@ async def CaptureAllFromCamera(self, stream: Stream[CaptureAllFromCameraRequest,
)
img = None
if result.image is not None:
mime_type = CameraMimeType.from_string(result.image.mime_type)
# TODO(RSDK-11728): remove this fmt logic once we deleted the format field
fmt = mime_type.to_proto() # Will be Format.FORMAT_UNSPECIFIED if an unsupported/custom mime type is set
img_bytes = result.image.data
img = Image(source_name=request.camera_name, mime_type=mime_type, format=fmt, image=img_bytes)
img = Image(source_name=request.camera_name, mime_type=result.image.mime_type, image=img_bytes)
response = CaptureAllFromCameraResponse(
image=img,
detections=result.detections,
Expand Down
2 changes: 1 addition & 1 deletion src/viam/services/vision/vision.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def __init__(
):
"""
Args:
image (ViamImage|None): The image from the GetImage request of the camera, if it was requested.
image (ViamImage|None): The image from the GetImages request of the camera, if it was requested.
classifications (List[Classification]|None): The classifications from GetClassifications, if it was requested.
detections (List[Detection]|None): The detections from GetDetections, if it was requested.
objects (List[PointCloudObject]|None): the object point clouds from GetObjectPointClouds, if it was requested.
Expand Down
7 changes: 0 additions & 7 deletions tests/mocks/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -475,13 +475,6 @@ def __init__(self, name: str):
self.metadata = ResponseMetadata(captured_at=ts)
super().__init__(name)

async def get_image(
self, mime_type: str = "", extra: Optional[Dict[str, Any]] = None, timeout: Optional[float] = None, **kwargs
) -> ViamImage:
self.extra = extra
self.timeout = timeout
return self.image

async def get_images(self, timeout: Optional[float] = None, **kwargs) -> Tuple[List[NamedImage], ResponseMetadata]:
self.timeout = timeout
return [NamedImage(self.name, self.image.data, self.image.mime_type)], self.metadata
Expand Down
Loading
Loading