From 54e811b521cc34594770d11a1d4a9f755c42d7d6 Mon Sep 17 00:00:00 2001 From: hexbabe Date: Thu, 28 Aug 2025 12:02:29 -0400 Subject: [PATCH 1/7] Cast mime type to str always in SDK tests; Fix failing tests and vision logic --- docs/examples/example.ipynb | 4 +- examples/server/v1/client.py | 2 +- examples/server/v1/components.py | 2 +- src/viam/components/camera/camera.py | 3 + src/viam/components/camera/client.py | 2 +- src/viam/components/camera/service.py | 4 +- src/viam/media/utils/pil/__init__.py | 6 +- src/viam/media/video.py | 3 + src/viam/services/vision/client.py | 6 +- src/viam/services/vision/service.py | 4 +- tests/mocks/components.py | 6 +- tests/test_camera.py | 21 ++-- tests/test_media.py | 35 ++++--- tests/test_vision_service.py | 133 +++++++++++++++++++------- 14 files changed, 158 insertions(+), 73 deletions(-) diff --git a/docs/examples/example.ipynb b/docs/examples/example.ipynb index 08c4c2539..d35077fb2 100644 --- a/docs/examples/example.ipynb +++ b/docs/examples/example.ipynb @@ -151,7 +151,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "metadata": { "tags": [ "hide-output" @@ -165,7 +165,7 @@ "\n", "robot = await connect_with_channel()\n", "camera = Camera.from_robot(robot, \"camera0\")\n", - "image = await camera.get_image(CameraMimeType.JPEG)\n", + "image = await camera.get_image(CameraMimeType.JPEG.value)\n", "pil = viam_to_pil_image(image)\n", "pil.save(\"foo.png\")\n", "\n", diff --git a/examples/server/v1/client.py b/examples/server/v1/client.py index a16f53a16..5d581e396 100644 --- a/examples/server/v1/client.py +++ b/examples/server/v1/client.py @@ -34,7 +34,7 @@ async def client(): print("\n#### CAMERA ####") camera = Camera.from_robot(robot, "camera0") - img = await camera.get_image(mime_type=CameraMimeType.PNG) + img = await camera.get_image(mime_type=CameraMimeType.PNG.value) assert isinstance(img, Image) img.show() await asyncio.sleep(1) diff --git a/examples/server/v1/components.py b/examples/server/v1/components.py index 7647dff08..17e636bd0 100644 --- a/examples/server/v1/components.py +++ b/examples/server/v1/components.py @@ -322,7 +322,7 @@ def __init__(self, name: str): img = Image.open(p.parent.absolute().joinpath("viam.jpeg")) buf = BytesIO() img.copy().save(buf, format="JPEG") - self.image = ViamImage(buf.getvalue(), CameraMimeType.JPEG) + self.image = ViamImage(buf.getvalue(), CameraMimeType.JPEG.value) img.close() super().__init__(name) diff --git a/src/viam/components/camera/camera.py b/src/viam/components/camera/camera.py index 6cade1a6a..0275814d2 100644 --- a/src/viam/components/camera/camera.py +++ b/src/viam/components/camera/camera.py @@ -74,6 +74,9 @@ async def get_images( """Get simultaneous images from different imagers, along with associated metadata. This should not be used for getting a time series of images from the same imager. + The extra parameter can be used to pass additional options to the camera resource. The filter_source_names parameter can be used to filter + only the images from the specified source names. When unspecified, all images are returned. + :: my_camera = Camera.from_robot(robot=machine, name="my_camera") diff --git a/src/viam/components/camera/client.py b/src/viam/components/camera/client.py index 6d18296ca..819ff98af 100644 --- a/src/viam/components/camera/client.py +++ b/src/viam/components/camera/client.py @@ -74,7 +74,7 @@ async def get_point_cloud( **kwargs, ) -> Tuple[bytes, str]: md = kwargs.get("metadata", self.Metadata()).proto - request = GetPointCloudRequest(name=self.name, mime_type=CameraMimeType.PCD, extra=dict_to_struct(extra)) + request = GetPointCloudRequest(name=self.name, mime_type=CameraMimeType.PCD.value, extra=dict_to_struct(extra)) response: GetPointCloudResponse = await self.client.GetPointCloud(request, timeout=timeout, metadata=md) return (response.point_cloud, response.mime_type) diff --git a/src/viam/components/camera/service.py b/src/viam/components/camera/service.py index d41af7ab6..dfc0feb0b 100644 --- a/src/viam/components/camera/service.py +++ b/src/viam/components/camera/service.py @@ -21,6 +21,8 @@ from viam.resource.rpc_service_base import ResourceRPCServiceBase from viam.utils import dict_to_struct, struct_to_dict +from viam.media.video import CameraMimeType + from . import Camera @@ -62,7 +64,7 @@ async def GetImages(self, stream: Stream[GetImagesRequest, GetImagesResponse]) - 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=name, mime_type=img.mime_type, format=fmt, image=img_bytes)) + img_bytes_lst.append(Image(source_name=name, format=fmt, mime_type=img.mime_type, image=img_bytes)) response = GetImagesResponse(images=img_bytes_lst, response_metadata=metadata) await stream.send_message(response) diff --git a/src/viam/media/utils/pil/__init__.py b/src/viam/media/utils/pil/__init__.py index 7914e0120..bfee9ed8e 100644 --- a/src/viam/media/utils/pil/__init__.py +++ b/src/viam/media/utils/pil/__init__.py @@ -25,7 +25,7 @@ def viam_to_pil_image(image: ViamImage) -> Image.Image: return Image.open(BytesIO(image.data), formats=LIBRARY_SUPPORTED_FORMATS) -def pil_to_viam_image(image: Image.Image, mime_type: CameraMimeType) -> ViamImage: +def pil_to_viam_image(image: Image.Image, mime_type: str) -> ViamImage: """ Convert a PIL.Image to a ViamImage. @@ -34,7 +34,7 @@ def pil_to_viam_image(image: Image.Image, mime_type: CameraMimeType) -> ViamImag Args: image (Image.Image): The image to convert. - mime_type (CameraMimeType): The mime type to convert the image to. + mime_type (str): The mime type to convert the image to. Must be of type `CameraMimeType`. Returns: ViamImage: The resulting ViamImage @@ -52,4 +52,4 @@ def pil_to_viam_image(image: Image.Image, mime_type: CameraMimeType) -> ViamImag else: raise ValueError(f"Cannot encode to unsupported mimetype: {mime_type}") - return ViamImage(data, mime_type) + return ViamImage(data, mime_type.value) diff --git a/src/viam/media/video.py b/src/viam/media/video.py index 909924ce2..882815dde 100644 --- a/src/viam/media/video.py +++ b/src/viam/media/video.py @@ -57,6 +57,9 @@ def CUSTOM(cls, mime_type: str) -> Self: """ return cls.from_string(mime_type) + def __str__(self) -> str: + return self.value + @classmethod def from_string(cls, value: str) -> Self: """Return the mimetype from a string. diff --git a/src/viam/services/vision/client.py b/src/viam/services/vision/client.py index 77182c21d..89f10b8d7 100644 --- a/src/viam/services/vision/client.py +++ b/src/viam/services/vision/client.py @@ -106,7 +106,7 @@ async def get_detections( **kwargs, ) -> List[Detection]: md = kwargs.get("metadata", self.Metadata()).proto - mime_type = CameraMimeType.JPEG + mime_type = CameraMimeType.JPEG.value if image.width is None or image.height is None: raise ViamError(f"image {image} needs to have a specified width and height") @@ -149,7 +149,7 @@ async def get_classifications( ) -> List[Classification]: md = kwargs.get("metadata", self.Metadata()).proto - mime_type = CameraMimeType.JPEG + mime_type = CameraMimeType.JPEG.value if image.width is None or image.height is None: raise ViamError(f"image {image} needs to have a specified width and height") request = GetClassificationsRequest( @@ -176,7 +176,7 @@ async def get_object_point_clouds( request = GetObjectPointCloudsRequest( name=self.name, camera_name=camera_name, - mime_type=CameraMimeType.PCD, + mime_type=CameraMimeType.PCD.value, extra=dict_to_struct(extra), ) response: GetObjectPointCloudsResponse = await self.client.GetObjectPointClouds(request, timeout=timeout, metadata=md) diff --git a/src/viam/services/vision/service.py b/src/viam/services/vision/service.py index e6510614f..963384428 100644 --- a/src/viam/services/vision/service.py +++ b/src/viam/services/vision/service.py @@ -2,7 +2,7 @@ from viam.media.video import CameraMimeType, ViamImage from viam.proto.common import DoCommandRequest, DoCommandResponse -from viam.proto.component.camera import Image +from viam.proto.component.camera import Format, Image from viam.proto.service.vision import ( CaptureAllFromCameraRequest, CaptureAllFromCameraResponse, @@ -36,7 +36,7 @@ class VisionRPCService(UnimplementedVisionServiceBase, ResourceRPCServiceBase[Vi async def CaptureAllFromCamera(self, stream: Stream[CaptureAllFromCameraRequest, CaptureAllFromCameraResponse]) -> None: request = await stream.recv_message() assert request is not None - vision = self.get_resource(request.name) + vision: Vision = self.get_resource(request.name) extra = struct_to_dict(request.extra) timeout = stream.deadline.time_remaining() if stream.deadline else None result = await vision.capture_all_from_camera( diff --git a/tests/mocks/components.py b/tests/mocks/components.py index fd80dc51e..f8be6a9f1 100644 --- a/tests/mocks/components.py +++ b/tests/mocks/components.py @@ -353,7 +353,7 @@ async def read() -> AsyncIterator[Tick]: class MockCamera(Camera): def __init__(self, name: str): - self.image = ViamImage(b"data", CameraMimeType.PNG) + self.image = ViamImage(b"data", CameraMimeType.PNG.value) self.geometries = GEOMETRIES self.point_cloud = b"THIS IS A POINT CLOUD" self.extra = None @@ -361,7 +361,7 @@ def __init__(self, name: str): supports_pcd=False, intrinsic_parameters=IntrinsicParameters(width_px=1, height_px=2, focal_x_px=3, focal_y_px=4, center_x_px=5, center_y_px=6), distortion_parameters=DistortionParameters(model="no_distortion"), - mime_types=[CameraMimeType.PNG, CameraMimeType.JPEG], + mime_types=[CameraMimeType.PNG.value, CameraMimeType.JPEG.value], frame_rate=10.0, ) self.timeout: Optional[float] = None @@ -386,7 +386,7 @@ async def get_point_cloud( ) -> Tuple[bytes, str]: self.extra = extra self.timeout = timeout - return self.point_cloud, CameraMimeType.PCD + return self.point_cloud, CameraMimeType.PCD.value async def get_properties(self, *, timeout: Optional[float] = None, **kwargs) -> Camera.Properties: self.timeout = timeout diff --git a/tests/test_camera.py b/tests/test_camera.py index 229a8b750..a3d781e25 100644 --- a/tests/test_camera.py +++ b/tests/test_camera.py @@ -11,6 +11,7 @@ from viam.media.video import CameraMimeType, NamedImage, ViamImage from viam.proto.common import DoCommandRequest, DoCommandResponse, GetGeometriesRequest, GetGeometriesResponse, ResponseMetadata from viam.proto.component.camera import ( + Format, CameraServiceStub, DistortionParameters, Format, @@ -40,7 +41,7 @@ @pytest.fixture(scope="function") def image() -> ViamImage: - return ViamImage(b"data", CameraMimeType.PNG) + return ViamImage(b"data", CameraMimeType.PNG.value) @pytest.fixture(scope="function") @@ -61,7 +62,7 @@ def properties() -> Camera.Properties: supports_pcd=False, intrinsic_parameters=IntrinsicParameters(width_px=1, height_px=2, focal_x_px=3, focal_y_px=4, center_x_px=5, center_y_px=6), distortion_parameters=DistortionParameters(model="no_distortion"), - mime_types=[CameraMimeType.PNG, CameraMimeType.JPEG], + mime_types=[CameraMimeType.PNG.value, CameraMimeType.JPEG.value], frame_rate=10.0, ) @@ -85,11 +86,11 @@ def generic_service(camera: Camera) -> GenericRPCService: class TestCamera: async def test_get_image(self, camera: MockCamera, image: ViamImage): - img = await camera.get_image(CameraMimeType.PNG) + img = await camera.get_image(CameraMimeType.PNG.value) assert img.data == image.data assert img.mime_type == image.mime_type - img = await camera.get_image(CameraMimeType.PNG, {"1": 1}) + img = await camera.get_image(CameraMimeType.PNG.value, {"1": 1}) assert camera.extra == {"1": 1} async def test_get_images(self, camera: Camera, image: ViamImage, metadata: ResponseMetadata): @@ -139,10 +140,10 @@ async def test_get_image(self, camera: MockCamera, service: CameraRPCService, im client = CameraServiceStub(channel) # Test known mime type - request = GetImageRequest(name="camera", mime_type=CameraMimeType.PNG) + request = GetImageRequest(name="camera", mime_type=CameraMimeType.PNG.value) response: GetImageResponse = await client.GetImage(request, timeout=18.1) assert response.image == image.data - assert response.mime_type == CameraMimeType.PNG + assert response.mime_type == CameraMimeType.PNG.value assert camera.timeout == loose_approx(18.1) # Test empty mime type. Empty mime type should default to response mime type @@ -169,9 +170,9 @@ async def test_render_frame(self, camera: MockCamera, service: CameraRPCService, assert camera.timeout is None async with ChannelFor([service]) as channel: client = CameraServiceStub(channel) - request = RenderFrameRequest(name="camera", mime_type=CameraMimeType.PNG) + request = RenderFrameRequest(name="camera", mime_type=CameraMimeType.PNG.value) response: HttpBody = await client.RenderFrame(request, timeout=4.4) - assert response.content_type == CameraMimeType.PNG + assert response.content_type == CameraMimeType.PNG.value assert response.data == image.data assert camera.timeout == loose_approx(4.4) @@ -179,7 +180,7 @@ async def test_get_point_cloud(self, camera: MockCamera, service: CameraRPCServi assert camera.timeout is None async with ChannelFor([service]) as channel: client = CameraServiceStub(channel) - request = GetPointCloudRequest(name="camera", mime_type=CameraMimeType.PCD) + request = GetPointCloudRequest(name="camera", mime_type=CameraMimeType.PCD.value) response: GetPointCloudResponse = await client.GetPointCloud(request, timeout=7.86) assert response.point_cloud == point_cloud assert camera.timeout == loose_approx(7.86) @@ -219,7 +220,7 @@ async def test_get_image(self, camera: MockCamera, service: CameraRPCService, im async with ChannelFor([service]) as channel: client = CameraClient("camera", channel) - img = await client.get_image(timeout=1.82, mime_type=CameraMimeType.PNG) + img = await client.get_image(timeout=1.82, mime_type=CameraMimeType.PNG.value) assert img.data == image.data assert img.mime_type == image.mime_type diff --git a/tests/test_media.py b/tests/test_media.py index ca0a82071..88c5498b6 100644 --- a/tests/test_media.py +++ b/tests/test_media.py @@ -9,6 +9,8 @@ from viam.media.utils.pil import pil_to_viam_image, viam_to_pil_image from viam.media.video import CameraMimeType, Format, NamedImage, ViamImage +UNSUPPORTED_MIME_TYPE = "unsupported_string_mime_type" + class TestViamImage: UNSUPPORTED_MIME_TYPE = "unsupported_string_mime_type" @@ -17,8 +19,8 @@ def test_supported_image(self): i = Image.new("RGBA", (100, 100), "#AABBCCDD") b = BytesIO() i.save(b, "PNG") - img = ViamImage(b.getvalue(), CameraMimeType.PNG) - assert img._mime_type == CameraMimeType.PNG + img = ViamImage(b.getvalue(), CameraMimeType.PNG.value) + assert img._mime_type == CameraMimeType.PNG.value pil_img = viam_to_pil_image(img) assert pil_img.tobytes() == i.tobytes() @@ -27,19 +29,19 @@ def test_dimensions(self): HEIGHT = 300 i = Image.new("RGBA", (WIDTH, HEIGHT), "#AABBCCDD") - img1 = pil_to_viam_image(i, CameraMimeType.JPEG) + img1 = pil_to_viam_image(i, CameraMimeType.JPEG.value) assert img1.width == WIDTH assert img1.height == HEIGHT - img2 = pil_to_viam_image(i, CameraMimeType.PNG) + img2 = pil_to_viam_image(i, CameraMimeType.PNG.value) assert img2.width == WIDTH assert img2.height == HEIGHT - img3 = pil_to_viam_image(i, CameraMimeType.VIAM_RGBA) + img3 = pil_to_viam_image(i, CameraMimeType.VIAM_RGBA.value) assert img3.width == WIDTH assert img3.height == HEIGHT - img4 = ViamImage(b"data", CameraMimeType.PCD) + img4 = ViamImage(b"data", CameraMimeType.PCD.value) assert img4.width is None assert img4.height is None @@ -48,8 +50,10 @@ def test_dimensions(self): assert img5.height is None def test_bytes_to_depth_array(self): - with open(f"{os.path.dirname(__file__)}/data/fakeDM.vnd.viam.dep", "rb") as depth_map: - img = ViamImage(depth_map.read(), CameraMimeType.VIAM_RAW_DEPTH) + with open( + f"{os.path.dirname(__file__)}/data/fakeDM.vnd.viam.dep", "rb" + ) as depth_map: + img = ViamImage(depth_map.read(), CameraMimeType.VIAM_RAW_DEPTH.value) assert isinstance(img, ViamImage) standard_data = img.bytes_to_depth_array() assert len(standard_data) == 10 @@ -62,7 +66,7 @@ def test_bytes_to_depth_array(self): assert standard_data[-1][3] == 9 * 3 assert standard_data[4][4] == 4 * 4 - img2 = ViamImage(b"data", CameraMimeType.PCD) + img2 = ViamImage(b"data", CameraMimeType.PCD.value) with pytest.raises(NotSupportedError): img2.bytes_to_depth_array() @@ -70,7 +74,7 @@ def test_bytes_to_depth_array(self): class TestNamedImage: def test_name(self): name = "img" - img = NamedImage(name, b"123", CameraMimeType.JPEG) + img = NamedImage(name, b"123", CameraMimeType.JPEG.value) assert img.name == name @@ -163,13 +167,16 @@ def test_to_proto(self): def test_image_conversion(): i = Image.new("RGBA", (100, 100), "#AABBCCDD") - v_img = pil_to_viam_image(i, CameraMimeType.JPEG) + v_img = pil_to_viam_image(i, CameraMimeType.JPEG.value) assert isinstance(v_img, ViamImage) - assert v_img.mime_type == CameraMimeType.JPEG + assert v_img.mime_type == CameraMimeType.JPEG.value pil_img = viam_to_pil_image(v_img) - v_img2 = pil_to_viam_image(pil_img, CameraMimeType.JPEG) + v_img2 = pil_to_viam_image(pil_img, CameraMimeType.JPEG.value) assert v_img2.data == v_img.data - with pytest.raises(ValueError, match=f"Cannot encode to unsupported mimetype: {TestViamImage.UNSUPPORTED_MIME_TYPE}"): + with pytest.raises( + ValueError, + match=f"Cannot encode to unsupported mimetype: {TestViamImage.UNSUPPORTED_MIME_TYPE}", + ): pil_to_viam_image(i, CameraMimeType.CUSTOM(TestViamImage.UNSUPPORTED_MIME_TYPE)) diff --git a/tests/test_vision_service.py b/tests/test_vision_service.py index 071ead48e..a36b41284 100644 --- a/tests/test_vision_service.py +++ b/tests/test_vision_service.py @@ -41,7 +41,7 @@ from .mocks.services import MockVision i = Image.new("RGBA", (100, 100), "#AABBCCDD") -IMAGE = pil_to_viam_image(i, CameraMimeType.JPEG) +IMAGE = pil_to_viam_image(i, CameraMimeType.JPEG.value) DETECTORS = [ "detector-0", "detector-1", @@ -84,7 +84,12 @@ reference_frame="depth1", geometries=[ Geometry( - center=Pose(x=282.45238095238096, y=241.66666666666666, z=902.8809523809524, o_z=1.0), + center=Pose( + x=282.45238095238096, + y=241.66666666666666, + z=902.8809523809524, + o_z=1.0, + ), box=RectangularPrism(dims_mm=Vector3(x=13, y=7, z=11)), ) ], @@ -96,7 +101,12 @@ reference_frame="depth1", geometries=[ Geometry( - center=Pose(x=-129.84615384615384, y=165.53846153846155, z=511.46153846153845, o_z=1.0), + center=Pose( + x=-129.84615384615384, + y=165.53846153846155, + z=511.46153846153845, + o_z=1.0, + ), box=RectangularPrism(dims_mm=Vector3(x=5.0, y=4.0, z=7.0)), ) ], @@ -104,7 +114,8 @@ ), ] -VISION_IMAGE = ViamImage(bytes([0, 100]), CameraMimeType.JPEG) +# Cast to string because ViamImage accepts a string mime type in the worst case. +VISION_IMAGE = ViamImage(bytes([0, 100]), CameraMimeType.JPEG.value) PROPERTIES = Vision.Properties( classifications_supported=True, @@ -174,7 +185,9 @@ async def test_get_detections(self, vision: MockVision): async def test_get_classifications_from_camera(self, vision: MockVision): extra = {"foo": "get_classifications_from_camera"} - response = await vision.get_classifications_from_camera("fake-camera", 1, extra=extra) + response = await vision.get_classifications_from_camera( + "fake-camera", 1, extra=extra + ) assert response == CLASSIFICATIONS assert vision.extra == extra @@ -197,16 +210,25 @@ async def test_do(self, vision: MockVision): class TestService: - async def test_capture_all_from_camera(self, vision: MockVision, service: VisionRPCService): + async def test_capture_all_from_camera( + self, vision: MockVision, service: VisionRPCService + ): async with ChannelFor([service]) as channel: client = VisionServiceStub(channel) extra = {"foo": "capture_all_from_camera"} request = CaptureAllFromCameraRequest( - name=vision.name, camera_name="fake-camera", return_image=True, return_classifications=True, extra=dict_to_struct(extra) + name=vision.name, + camera_name="fake-camera", + return_image=True, + return_classifications=True, + extra=dict_to_struct(extra), + ) + response: CaptureAllFromCameraResponse = await client.CaptureAllFromCamera( + request ) - response: CaptureAllFromCameraResponse = await client.CaptureAllFromCamera(request) assert response.image.image == VISION_IMAGE.data assert response.image.mime_type == VISION_IMAGE.mime_type + # TODO(RSDK-11728): remove this once we deleted the format field assert response.image.format == VISION_IMAGE.mime_type.to_proto() assert response.classifications == CLASSIFICATIONS assert response.detections == [] @@ -217,19 +239,33 @@ async def test_get_properties(self, vision: MockVision, service: VisionRPCServic async with ChannelFor([service]) as channel: client = VisionServiceStub(channel) extra = {"foo": "get_properties"} - request = GetPropertiesRequest(name=vision.name, extra=dict_to_struct(extra)) + request = GetPropertiesRequest( + name=vision.name, extra=dict_to_struct(extra) + ) response: GetPropertiesResponse = await client.GetProperties(request) - assert response.classifications_supported == PROPERTIES.classifications_supported + assert ( + response.classifications_supported + == PROPERTIES.classifications_supported + ) assert response.detections_supported == PROPERTIES.detections_supported - assert response.object_point_clouds_supported == PROPERTIES.object_point_clouds_supported + assert ( + response.object_point_clouds_supported + == PROPERTIES.object_point_clouds_supported + ) assert vision.extra == extra - async def test_get_detections_from_camera(self, vision: MockVision, service: VisionRPCService): + async def test_get_detections_from_camera( + self, vision: MockVision, service: VisionRPCService + ): async with ChannelFor([service]) as channel: client = VisionServiceStub(channel) extra = {"foo": "get_detections_from_camera"} - request = GetDetectionsFromCameraRequest(name=vision.name, camera_name="fake-camera", extra=dict_to_struct(extra)) - response: GetDetectionsFromCameraResponse = await client.GetDetectionsFromCamera(request) + request = GetDetectionsFromCameraRequest( + name=vision.name, camera_name="fake-camera", extra=dict_to_struct(extra) + ) + response: GetDetectionsFromCameraResponse = ( + await client.GetDetectionsFromCamera(request) + ) assert response.detections == DETECTIONS assert vision.extra == extra @@ -242,23 +278,34 @@ async def test_get_detections(self, vision: MockVision, service: VisionRPCServic image=IMAGE.data, width=100, height=100, - mime_type=CameraMimeType.JPEG, + mime_type=CameraMimeType.JPEG.value, extra=dict_to_struct(extra), ) response: GetDetectionsResponse = await client.GetDetections(request) assert response.detections == DETECTIONS assert vision.extra == extra - async def test_get_classifications_from_camera(self, vision: MockVision, service: VisionRPCService): + async def test_get_classifications_from_camera( + self, vision: MockVision, service: VisionRPCService + ): async with ChannelFor([service]) as channel: client = VisionServiceStub(channel) extra = {"foo": "get_classifications_from_camera"} - request = GetClassificationsFromCameraRequest(name=vision.name, camera_name="fake-camera", n=1, extra=dict_to_struct(extra)) - response: GetClassificationsFromCameraResponse = await client.GetClassificationsFromCamera(request) + request = GetClassificationsFromCameraRequest( + name=vision.name, + camera_name="fake-camera", + n=1, + extra=dict_to_struct(extra), + ) + response: GetClassificationsFromCameraResponse = ( + await client.GetClassificationsFromCamera(request) + ) assert response.classifications == CLASSIFICATIONS assert vision.extra == extra - async def test_get_classifications(self, vision: MockVision, service: VisionRPCService): + async def test_get_classifications( + self, vision: MockVision, service: VisionRPCService + ): async with ChannelFor([service]) as channel: client = VisionServiceStub(channel) extra = {"foo": "get_classifications"} @@ -267,25 +314,31 @@ async def test_get_classifications(self, vision: MockVision, service: VisionRPCS image=IMAGE.data, width=100, height=100, - mime_type=CameraMimeType.JPEG, + mime_type=CameraMimeType.JPEG.value, n=1, extra=dict_to_struct(extra), ) - response: GetClassificationsResponse = await client.GetClassifications(request) + response: GetClassificationsResponse = await client.GetClassifications( + request + ) assert response.classifications == CLASSIFICATIONS assert vision.extra == extra - async def test_get_object_point_clouds(self, vision: MockVision, service: VisionRPCService): + async def test_get_object_point_clouds( + self, vision: MockVision, service: VisionRPCService + ): async with ChannelFor([service]) as channel: client = VisionServiceStub(channel) extra = {"foo": "get_object_point_clouds"} request = GetObjectPointCloudsRequest( name=vision.name, camera_name="camera", - mime_type=CameraMimeType.PCD, + mime_type=CameraMimeType.PCD.value, extra=dict_to_struct(extra), ) - response: GetObjectPointCloudsResponse = await client.GetObjectPointClouds(request) + response: GetObjectPointCloudsResponse = await client.GetObjectPointClouds( + request + ) assert response.objects == POINT_CLOUDS assert vision.extra == extra @@ -293,7 +346,9 @@ async def test_do(self, vision: MockVision, service: VisionRPCService): async with ChannelFor([service]) as channel: client = VisionServiceStub(channel) command = {"command": "args"} - request = DoCommandRequest(name=vision.name, command=dict_to_struct(command)) + request = DoCommandRequest( + name=vision.name, command=dict_to_struct(command) + ) response: DoCommandResponse = await client.DoCommand(request) assert struct_to_dict(response.result)["cmd"] == command @@ -307,7 +362,9 @@ async def test_get_properties(self, vision: MockVision, service: VisionRPCServic assert response == PROPERTIES assert vision.extra == extra - async def test_capture_all_from_camera(self, vision: MockVision, service: VisionRPCService): + async def test_capture_all_from_camera( + self, vision: MockVision, service: VisionRPCService + ): async with ChannelFor([service]) as channel: client = VisionClient(VISION_SERVICE_NAME, channel) extra = {"foo": "capture_all_from_camera"} @@ -325,11 +382,15 @@ async def test_capture_all_from_camera(self, vision: MockVision, service: Vision assert response.objects == POINT_CLOUDS assert vision.extra == extra - async def test_get_detections_from_camera(self, vision: MockVision, service: VisionRPCService): + async def test_get_detections_from_camera( + self, vision: MockVision, service: VisionRPCService + ): async with ChannelFor([service]) as channel: client = VisionClient(VISION_SERVICE_NAME, channel) extra = {"foo": "get_detections_from_camera"} - response = await client.get_detections_from_camera("fake-camera", extra=extra) + response = await client.get_detections_from_camera( + "fake-camera", extra=extra + ) assert response == DETECTIONS assert vision.extra == extra @@ -341,15 +402,21 @@ async def test_get_detections(self, vision: MockVision, service: VisionRPCServic assert response == DETECTIONS assert vision.extra == extra - async def test_get_classifications_from_camera(self, vision: MockVision, service: VisionRPCService): + async def test_get_classifications_from_camera( + self, vision: MockVision, service: VisionRPCService + ): async with ChannelFor([service]) as channel: client = VisionClient(VISION_SERVICE_NAME, channel) extra = {"foo": "get_classifications_from_camera"} - response = await client.get_classifications_from_camera("fake-camera", 1, extra=extra) + response = await client.get_classifications_from_camera( + "fake-camera", 1, extra=extra + ) assert response == CLASSIFICATIONS assert vision.extra == extra - async def test_get_classifications(self, vision: MockVision, service: VisionRPCService): + async def test_get_classifications( + self, vision: MockVision, service: VisionRPCService + ): async with ChannelFor([service]) as channel: client = VisionClient(VISION_SERVICE_NAME, channel) extra = {"foo": "get_classifications"} @@ -357,7 +424,9 @@ async def test_get_classifications(self, vision: MockVision, service: VisionRPCS assert response == CLASSIFICATIONS assert vision.extra == extra - async def test_get_object_point_clouds(self, vision: MockVision, service: VisionRPCService): + async def test_get_object_point_clouds( + self, vision: MockVision, service: VisionRPCService + ): async with ChannelFor([service]) as channel: client = VisionClient(VISION_SERVICE_NAME, channel) extra = {"foo": "get_object_point_clouds"} From 40937f33495e355b081e15ef6552a5e3db01eefd Mon Sep 17 00:00:00 2001 From: hexbabe Date: Thu, 28 Aug 2025 12:21:53 -0400 Subject: [PATCH 2/7] Clarify comment --- src/viam/media/utils/pil/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/viam/media/utils/pil/__init__.py b/src/viam/media/utils/pil/__init__.py index bfee9ed8e..7acdfc2c4 100644 --- a/src/viam/media/utils/pil/__init__.py +++ b/src/viam/media/utils/pil/__init__.py @@ -34,7 +34,7 @@ def pil_to_viam_image(image: Image.Image, mime_type: str) -> ViamImage: Args: image (Image.Image): The image to convert. - mime_type (str): The mime type to convert the image to. Must be of type `CameraMimeType`. + mime_type (str): The mime type to convert the image to. Must be one of the `CameraMimeType` enum string literals. Returns: ViamImage: The resulting ViamImage From c01d9ab00cb8b6e77af5823a0222fb55dedcb733 Mon Sep 17 00:00:00 2001 From: hexbabe Date: Thu, 28 Aug 2025 16:15:10 -0400 Subject: [PATCH 3/7] Remove .value for mime types as that was just for checking if unit tests fail due to them --- docs/examples/example.ipynb | 2 +- examples/server/v1/client.py | 2 +- examples/server/v1/components.py | 2 +- src/viam/components/camera/camera.py | 3 --- src/viam/components/camera/client.py | 2 +- src/viam/media/utils/pil/__init__.py | 6 +++--- src/viam/services/vision/client.py | 6 +++--- tests/mocks/components.py | 6 +++--- tests/test_camera.py | 20 ++++++++++---------- tests/test_media.py | 26 ++++++++++++-------------- tests/test_vision_service.py | 8 ++++---- 11 files changed, 39 insertions(+), 44 deletions(-) diff --git a/docs/examples/example.ipynb b/docs/examples/example.ipynb index d35077fb2..0fdee77da 100644 --- a/docs/examples/example.ipynb +++ b/docs/examples/example.ipynb @@ -165,7 +165,7 @@ "\n", "robot = await connect_with_channel()\n", "camera = Camera.from_robot(robot, \"camera0\")\n", - "image = await camera.get_image(CameraMimeType.JPEG.value)\n", + "image = await camera.get_image(CameraMimeType.JPEG)\n", "pil = viam_to_pil_image(image)\n", "pil.save(\"foo.png\")\n", "\n", diff --git a/examples/server/v1/client.py b/examples/server/v1/client.py index 5d581e396..a16f53a16 100644 --- a/examples/server/v1/client.py +++ b/examples/server/v1/client.py @@ -34,7 +34,7 @@ async def client(): print("\n#### CAMERA ####") camera = Camera.from_robot(robot, "camera0") - img = await camera.get_image(mime_type=CameraMimeType.PNG.value) + img = await camera.get_image(mime_type=CameraMimeType.PNG) assert isinstance(img, Image) img.show() await asyncio.sleep(1) diff --git a/examples/server/v1/components.py b/examples/server/v1/components.py index 17e636bd0..7647dff08 100644 --- a/examples/server/v1/components.py +++ b/examples/server/v1/components.py @@ -322,7 +322,7 @@ def __init__(self, name: str): img = Image.open(p.parent.absolute().joinpath("viam.jpeg")) buf = BytesIO() img.copy().save(buf, format="JPEG") - self.image = ViamImage(buf.getvalue(), CameraMimeType.JPEG.value) + self.image = ViamImage(buf.getvalue(), CameraMimeType.JPEG) img.close() super().__init__(name) diff --git a/src/viam/components/camera/camera.py b/src/viam/components/camera/camera.py index 0275814d2..6cade1a6a 100644 --- a/src/viam/components/camera/camera.py +++ b/src/viam/components/camera/camera.py @@ -74,9 +74,6 @@ async def get_images( """Get simultaneous images from different imagers, along with associated metadata. This should not be used for getting a time series of images from the same imager. - The extra parameter can be used to pass additional options to the camera resource. The filter_source_names parameter can be used to filter - only the images from the specified source names. When unspecified, all images are returned. - :: my_camera = Camera.from_robot(robot=machine, name="my_camera") diff --git a/src/viam/components/camera/client.py b/src/viam/components/camera/client.py index 819ff98af..6d18296ca 100644 --- a/src/viam/components/camera/client.py +++ b/src/viam/components/camera/client.py @@ -74,7 +74,7 @@ async def get_point_cloud( **kwargs, ) -> Tuple[bytes, str]: md = kwargs.get("metadata", self.Metadata()).proto - request = GetPointCloudRequest(name=self.name, mime_type=CameraMimeType.PCD.value, extra=dict_to_struct(extra)) + request = GetPointCloudRequest(name=self.name, mime_type=CameraMimeType.PCD, extra=dict_to_struct(extra)) response: GetPointCloudResponse = await self.client.GetPointCloud(request, timeout=timeout, metadata=md) return (response.point_cloud, response.mime_type) diff --git a/src/viam/media/utils/pil/__init__.py b/src/viam/media/utils/pil/__init__.py index 7acdfc2c4..7914e0120 100644 --- a/src/viam/media/utils/pil/__init__.py +++ b/src/viam/media/utils/pil/__init__.py @@ -25,7 +25,7 @@ def viam_to_pil_image(image: ViamImage) -> Image.Image: return Image.open(BytesIO(image.data), formats=LIBRARY_SUPPORTED_FORMATS) -def pil_to_viam_image(image: Image.Image, mime_type: str) -> ViamImage: +def pil_to_viam_image(image: Image.Image, mime_type: CameraMimeType) -> ViamImage: """ Convert a PIL.Image to a ViamImage. @@ -34,7 +34,7 @@ def pil_to_viam_image(image: Image.Image, mime_type: str) -> ViamImage: Args: image (Image.Image): The image to convert. - mime_type (str): The mime type to convert the image to. Must be one of the `CameraMimeType` enum string literals. + mime_type (CameraMimeType): The mime type to convert the image to. Returns: ViamImage: The resulting ViamImage @@ -52,4 +52,4 @@ def pil_to_viam_image(image: Image.Image, mime_type: str) -> ViamImage: else: raise ValueError(f"Cannot encode to unsupported mimetype: {mime_type}") - return ViamImage(data, mime_type.value) + return ViamImage(data, mime_type) diff --git a/src/viam/services/vision/client.py b/src/viam/services/vision/client.py index 89f10b8d7..77182c21d 100644 --- a/src/viam/services/vision/client.py +++ b/src/viam/services/vision/client.py @@ -106,7 +106,7 @@ async def get_detections( **kwargs, ) -> List[Detection]: md = kwargs.get("metadata", self.Metadata()).proto - mime_type = CameraMimeType.JPEG.value + mime_type = CameraMimeType.JPEG if image.width is None or image.height is None: raise ViamError(f"image {image} needs to have a specified width and height") @@ -149,7 +149,7 @@ async def get_classifications( ) -> List[Classification]: md = kwargs.get("metadata", self.Metadata()).proto - mime_type = CameraMimeType.JPEG.value + mime_type = CameraMimeType.JPEG if image.width is None or image.height is None: raise ViamError(f"image {image} needs to have a specified width and height") request = GetClassificationsRequest( @@ -176,7 +176,7 @@ async def get_object_point_clouds( request = GetObjectPointCloudsRequest( name=self.name, camera_name=camera_name, - mime_type=CameraMimeType.PCD.value, + mime_type=CameraMimeType.PCD, extra=dict_to_struct(extra), ) response: GetObjectPointCloudsResponse = await self.client.GetObjectPointClouds(request, timeout=timeout, metadata=md) diff --git a/tests/mocks/components.py b/tests/mocks/components.py index f8be6a9f1..fd80dc51e 100644 --- a/tests/mocks/components.py +++ b/tests/mocks/components.py @@ -353,7 +353,7 @@ async def read() -> AsyncIterator[Tick]: class MockCamera(Camera): def __init__(self, name: str): - self.image = ViamImage(b"data", CameraMimeType.PNG.value) + self.image = ViamImage(b"data", CameraMimeType.PNG) self.geometries = GEOMETRIES self.point_cloud = b"THIS IS A POINT CLOUD" self.extra = None @@ -361,7 +361,7 @@ def __init__(self, name: str): supports_pcd=False, intrinsic_parameters=IntrinsicParameters(width_px=1, height_px=2, focal_x_px=3, focal_y_px=4, center_x_px=5, center_y_px=6), distortion_parameters=DistortionParameters(model="no_distortion"), - mime_types=[CameraMimeType.PNG.value, CameraMimeType.JPEG.value], + mime_types=[CameraMimeType.PNG, CameraMimeType.JPEG], frame_rate=10.0, ) self.timeout: Optional[float] = None @@ -386,7 +386,7 @@ async def get_point_cloud( ) -> Tuple[bytes, str]: self.extra = extra self.timeout = timeout - return self.point_cloud, CameraMimeType.PCD.value + return self.point_cloud, CameraMimeType.PCD async def get_properties(self, *, timeout: Optional[float] = None, **kwargs) -> Camera.Properties: self.timeout = timeout diff --git a/tests/test_camera.py b/tests/test_camera.py index a3d781e25..acb7f9992 100644 --- a/tests/test_camera.py +++ b/tests/test_camera.py @@ -41,7 +41,7 @@ @pytest.fixture(scope="function") def image() -> ViamImage: - return ViamImage(b"data", CameraMimeType.PNG.value) + return ViamImage(b"data", CameraMimeType.PNG) @pytest.fixture(scope="function") @@ -62,7 +62,7 @@ def properties() -> Camera.Properties: supports_pcd=False, intrinsic_parameters=IntrinsicParameters(width_px=1, height_px=2, focal_x_px=3, focal_y_px=4, center_x_px=5, center_y_px=6), distortion_parameters=DistortionParameters(model="no_distortion"), - mime_types=[CameraMimeType.PNG.value, CameraMimeType.JPEG.value], + mime_types=[CameraMimeType.PNG, CameraMimeType.JPEG], frame_rate=10.0, ) @@ -86,11 +86,11 @@ def generic_service(camera: Camera) -> GenericRPCService: class TestCamera: async def test_get_image(self, camera: MockCamera, image: ViamImage): - img = await camera.get_image(CameraMimeType.PNG.value) + img = await camera.get_image(CameraMimeType.PNG) assert img.data == image.data assert img.mime_type == image.mime_type - img = await camera.get_image(CameraMimeType.PNG.value, {"1": 1}) + img = await camera.get_image(CameraMimeType.PNG, {"1": 1}) assert camera.extra == {"1": 1} async def test_get_images(self, camera: Camera, image: ViamImage, metadata: ResponseMetadata): @@ -140,10 +140,10 @@ async def test_get_image(self, camera: MockCamera, service: CameraRPCService, im client = CameraServiceStub(channel) # Test known mime type - request = GetImageRequest(name="camera", mime_type=CameraMimeType.PNG.value) + request = GetImageRequest(name="camera", mime_type=CameraMimeType.PNG) response: GetImageResponse = await client.GetImage(request, timeout=18.1) assert response.image == image.data - assert response.mime_type == CameraMimeType.PNG.value + assert response.mime_type == CameraMimeType.PNG assert camera.timeout == loose_approx(18.1) # Test empty mime type. Empty mime type should default to response mime type @@ -170,9 +170,9 @@ async def test_render_frame(self, camera: MockCamera, service: CameraRPCService, assert camera.timeout is None async with ChannelFor([service]) as channel: client = CameraServiceStub(channel) - request = RenderFrameRequest(name="camera", mime_type=CameraMimeType.PNG.value) + request = RenderFrameRequest(name="camera", mime_type=CameraMimeType.PNG) response: HttpBody = await client.RenderFrame(request, timeout=4.4) - assert response.content_type == CameraMimeType.PNG.value + assert response.content_type == CameraMimeType.PNG assert response.data == image.data assert camera.timeout == loose_approx(4.4) @@ -180,7 +180,7 @@ async def test_get_point_cloud(self, camera: MockCamera, service: CameraRPCServi assert camera.timeout is None async with ChannelFor([service]) as channel: client = CameraServiceStub(channel) - request = GetPointCloudRequest(name="camera", mime_type=CameraMimeType.PCD.value) + request = GetPointCloudRequest(name="camera", mime_type=CameraMimeType.PCD) response: GetPointCloudResponse = await client.GetPointCloud(request, timeout=7.86) assert response.point_cloud == point_cloud assert camera.timeout == loose_approx(7.86) @@ -220,7 +220,7 @@ async def test_get_image(self, camera: MockCamera, service: CameraRPCService, im async with ChannelFor([service]) as channel: client = CameraClient("camera", channel) - img = await client.get_image(timeout=1.82, mime_type=CameraMimeType.PNG.value) + img = await client.get_image(timeout=1.82, mime_type=CameraMimeType.PNG) assert img.data == image.data assert img.mime_type == image.mime_type diff --git a/tests/test_media.py b/tests/test_media.py index 88c5498b6..ef80e91d8 100644 --- a/tests/test_media.py +++ b/tests/test_media.py @@ -19,8 +19,8 @@ def test_supported_image(self): i = Image.new("RGBA", (100, 100), "#AABBCCDD") b = BytesIO() i.save(b, "PNG") - img = ViamImage(b.getvalue(), CameraMimeType.PNG.value) - assert img._mime_type == CameraMimeType.PNG.value + img = ViamImage(b.getvalue(), CameraMimeType.PNG) + assert img._mime_type == CameraMimeType.PNG pil_img = viam_to_pil_image(img) assert pil_img.tobytes() == i.tobytes() @@ -29,11 +29,11 @@ def test_dimensions(self): HEIGHT = 300 i = Image.new("RGBA", (WIDTH, HEIGHT), "#AABBCCDD") - img1 = pil_to_viam_image(i, CameraMimeType.JPEG.value) + img1 = pil_to_viam_image(i, CameraMimeType.JPEG) assert img1.width == WIDTH assert img1.height == HEIGHT - img2 = pil_to_viam_image(i, CameraMimeType.PNG.value) + img2 = pil_to_viam_image(i, CameraMimeType.PNG) assert img2.width == WIDTH assert img2.height == HEIGHT @@ -41,7 +41,7 @@ def test_dimensions(self): assert img3.width == WIDTH assert img3.height == HEIGHT - img4 = ViamImage(b"data", CameraMimeType.PCD.value) + img4 = ViamImage(b"data", CameraMimeType.PCD) assert img4.width is None assert img4.height is None @@ -50,10 +50,8 @@ def test_dimensions(self): assert img5.height is None def test_bytes_to_depth_array(self): - with open( - f"{os.path.dirname(__file__)}/data/fakeDM.vnd.viam.dep", "rb" - ) as depth_map: - img = ViamImage(depth_map.read(), CameraMimeType.VIAM_RAW_DEPTH.value) + with open(f"{os.path.dirname(__file__)}/data/fakeDM.vnd.viam.dep", "rb") as depth_map: + img = ViamImage(depth_map.read(), CameraMimeType.VIAM_RAW_DEPTH) assert isinstance(img, ViamImage) standard_data = img.bytes_to_depth_array() assert len(standard_data) == 10 @@ -66,7 +64,7 @@ def test_bytes_to_depth_array(self): assert standard_data[-1][3] == 9 * 3 assert standard_data[4][4] == 4 * 4 - img2 = ViamImage(b"data", CameraMimeType.PCD.value) + img2 = ViamImage(b"data", CameraMimeType.PCD) with pytest.raises(NotSupportedError): img2.bytes_to_depth_array() @@ -74,7 +72,7 @@ def test_bytes_to_depth_array(self): class TestNamedImage: def test_name(self): name = "img" - img = NamedImage(name, b"123", CameraMimeType.JPEG.value) + img = NamedImage(name, b"123", CameraMimeType.JPEG) assert img.name == name @@ -167,12 +165,12 @@ def test_to_proto(self): def test_image_conversion(): i = Image.new("RGBA", (100, 100), "#AABBCCDD") - v_img = pil_to_viam_image(i, CameraMimeType.JPEG.value) + v_img = pil_to_viam_image(i, CameraMimeType.JPEG) assert isinstance(v_img, ViamImage) - assert v_img.mime_type == CameraMimeType.JPEG.value + assert v_img.mime_type == CameraMimeType.JPEG pil_img = viam_to_pil_image(v_img) - v_img2 = pil_to_viam_image(pil_img, CameraMimeType.JPEG.value) + v_img2 = pil_to_viam_image(pil_img, CameraMimeType.JPEG) assert v_img2.data == v_img.data with pytest.raises( diff --git a/tests/test_vision_service.py b/tests/test_vision_service.py index a36b41284..6750beef1 100644 --- a/tests/test_vision_service.py +++ b/tests/test_vision_service.py @@ -41,7 +41,7 @@ from .mocks.services import MockVision i = Image.new("RGBA", (100, 100), "#AABBCCDD") -IMAGE = pil_to_viam_image(i, CameraMimeType.JPEG.value) +IMAGE = pil_to_viam_image(i, CameraMimeType.JPEG) DETECTORS = [ "detector-0", "detector-1", @@ -278,7 +278,7 @@ async def test_get_detections(self, vision: MockVision, service: VisionRPCServic image=IMAGE.data, width=100, height=100, - mime_type=CameraMimeType.JPEG.value, + mime_type=CameraMimeType.JPEG, extra=dict_to_struct(extra), ) response: GetDetectionsResponse = await client.GetDetections(request) @@ -314,7 +314,7 @@ async def test_get_classifications( image=IMAGE.data, width=100, height=100, - mime_type=CameraMimeType.JPEG.value, + mime_type=CameraMimeType.JPEG, n=1, extra=dict_to_struct(extra), ) @@ -333,7 +333,7 @@ async def test_get_object_point_clouds( request = GetObjectPointCloudsRequest( name=vision.name, camera_name="camera", - mime_type=CameraMimeType.PCD.value, + mime_type=CameraMimeType.PCD, extra=dict_to_struct(extra), ) response: GetObjectPointCloudsResponse = await client.GetObjectPointClouds( From f8d714095e83348609618af51398a62dc48501fb Mon Sep 17 00:00:00 2001 From: hexbabe Date: Thu, 28 Aug 2025 16:24:04 -0400 Subject: [PATCH 4/7] Remove str override method --- docs/examples/example.ipynb | 2 +- src/viam/components/camera/service.py | 2 -- src/viam/media/video.py | 3 --- tests/test_media.py | 2 +- 4 files changed, 2 insertions(+), 7 deletions(-) diff --git a/docs/examples/example.ipynb b/docs/examples/example.ipynb index 0fdee77da..08c4c2539 100644 --- a/docs/examples/example.ipynb +++ b/docs/examples/example.ipynb @@ -151,7 +151,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "metadata": { "tags": [ "hide-output" diff --git a/src/viam/components/camera/service.py b/src/viam/components/camera/service.py index dfc0feb0b..2703c19dc 100644 --- a/src/viam/components/camera/service.py +++ b/src/viam/components/camera/service.py @@ -21,8 +21,6 @@ from viam.resource.rpc_service_base import ResourceRPCServiceBase from viam.utils import dict_to_struct, struct_to_dict -from viam.media.video import CameraMimeType - from . import Camera diff --git a/src/viam/media/video.py b/src/viam/media/video.py index 882815dde..909924ce2 100644 --- a/src/viam/media/video.py +++ b/src/viam/media/video.py @@ -57,9 +57,6 @@ def CUSTOM(cls, mime_type: str) -> Self: """ return cls.from_string(mime_type) - def __str__(self) -> str: - return self.value - @classmethod def from_string(cls, value: str) -> Self: """Return the mimetype from a string. diff --git a/tests/test_media.py b/tests/test_media.py index ef80e91d8..3835b9b3a 100644 --- a/tests/test_media.py +++ b/tests/test_media.py @@ -37,7 +37,7 @@ def test_dimensions(self): assert img2.width == WIDTH assert img2.height == HEIGHT - img3 = pil_to_viam_image(i, CameraMimeType.VIAM_RGBA.value) + img3 = pil_to_viam_image(i, CameraMimeType.VIAM_RGBA) assert img3.width == WIDTH assert img3.height == HEIGHT From 18a84cdef2249d58b50f725c76833cd0b4c3f246 Mon Sep 17 00:00:00 2001 From: hexbabe Date: Thu, 28 Aug 2025 17:04:05 -0400 Subject: [PATCH 5/7] Invalid -> unsupported --- tests/test_vision_service.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_vision_service.py b/tests/test_vision_service.py index 6750beef1..3cf61d2c3 100644 --- a/tests/test_vision_service.py +++ b/tests/test_vision_service.py @@ -114,7 +114,8 @@ ), ] -# Cast to string because ViamImage accepts a string mime type in the worst case. +# Use string value of CameraMimeType because ViamImage accepts a string mime type in the worst case +# and it may not have the expected CameraMimeType methods defined on it. VISION_IMAGE = ViamImage(bytes([0, 100]), CameraMimeType.JPEG.value) PROPERTIES = Vision.Properties( From 0e4fd50a34f40e267830b534e4ca7dd509de6a53 Mon Sep 17 00:00:00 2001 From: hexbabe Date: Fri, 29 Aug 2025 13:57:41 -0400 Subject: [PATCH 6/7] Change so class inherits --- src/viam/services/vision/service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/viam/services/vision/service.py b/src/viam/services/vision/service.py index 963384428..cb97f8d87 100644 --- a/src/viam/services/vision/service.py +++ b/src/viam/services/vision/service.py @@ -36,7 +36,7 @@ class VisionRPCService(UnimplementedVisionServiceBase, ResourceRPCServiceBase[Vi async def CaptureAllFromCamera(self, stream: Stream[CaptureAllFromCameraRequest, CaptureAllFromCameraResponse]) -> None: request = await stream.recv_message() assert request is not None - vision: Vision = self.get_resource(request.name) + vision = self.get_resource(request.name) extra = struct_to_dict(request.extra) timeout = stream.deadline.time_remaining() if stream.deadline else None result = await vision.capture_all_from_camera( From 73a6150ff4ef7e44867b32889f00000ca8d89cfa Mon Sep 17 00:00:00 2001 From: hexbabe Date: Fri, 29 Aug 2025 14:06:08 -0400 Subject: [PATCH 7/7] Switch order of kwargs to correspond between vision service and camera service --- src/viam/components/camera/service.py | 2 +- src/viam/services/vision/service.py | 2 +- tests/test_camera.py | 1 - tests/test_media.py | 7 +------ tests/test_vision_service.py | 14 ++------------ 5 files changed, 5 insertions(+), 21 deletions(-) diff --git a/src/viam/components/camera/service.py b/src/viam/components/camera/service.py index 2703c19dc..d41af7ab6 100644 --- a/src/viam/components/camera/service.py +++ b/src/viam/components/camera/service.py @@ -62,7 +62,7 @@ async def GetImages(self, stream: Stream[GetImagesRequest, GetImagesResponse]) - 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=name, format=fmt, mime_type=img.mime_type, image=img_bytes)) + img_bytes_lst.append(Image(source_name=name, mime_type=img.mime_type, format=fmt, image=img_bytes)) response = GetImagesResponse(images=img_bytes_lst, response_metadata=metadata) await stream.send_message(response) diff --git a/src/viam/services/vision/service.py b/src/viam/services/vision/service.py index cb97f8d87..e6510614f 100644 --- a/src/viam/services/vision/service.py +++ b/src/viam/services/vision/service.py @@ -2,7 +2,7 @@ from viam.media.video import CameraMimeType, ViamImage from viam.proto.common import DoCommandRequest, DoCommandResponse -from viam.proto.component.camera import Format, Image +from viam.proto.component.camera import Image from viam.proto.service.vision import ( CaptureAllFromCameraRequest, CaptureAllFromCameraResponse, diff --git a/tests/test_camera.py b/tests/test_camera.py index acb7f9992..0905bb75d 100644 --- a/tests/test_camera.py +++ b/tests/test_camera.py @@ -14,7 +14,6 @@ Format, CameraServiceStub, DistortionParameters, - Format, GetImageRequest, GetImageResponse, GetImagesRequest, diff --git a/tests/test_media.py b/tests/test_media.py index 3835b9b3a..ca0a82071 100644 --- a/tests/test_media.py +++ b/tests/test_media.py @@ -9,8 +9,6 @@ from viam.media.utils.pil import pil_to_viam_image, viam_to_pil_image from viam.media.video import CameraMimeType, Format, NamedImage, ViamImage -UNSUPPORTED_MIME_TYPE = "unsupported_string_mime_type" - class TestViamImage: UNSUPPORTED_MIME_TYPE = "unsupported_string_mime_type" @@ -173,8 +171,5 @@ def test_image_conversion(): v_img2 = pil_to_viam_image(pil_img, CameraMimeType.JPEG) assert v_img2.data == v_img.data - with pytest.raises( - ValueError, - match=f"Cannot encode to unsupported mimetype: {TestViamImage.UNSUPPORTED_MIME_TYPE}", - ): + with pytest.raises(ValueError, match=f"Cannot encode to unsupported mimetype: {TestViamImage.UNSUPPORTED_MIME_TYPE}"): pil_to_viam_image(i, CameraMimeType.CUSTOM(TestViamImage.UNSUPPORTED_MIME_TYPE)) diff --git a/tests/test_vision_service.py b/tests/test_vision_service.py index 3cf61d2c3..baa44c0be 100644 --- a/tests/test_vision_service.py +++ b/tests/test_vision_service.py @@ -84,12 +84,7 @@ reference_frame="depth1", geometries=[ Geometry( - center=Pose( - x=282.45238095238096, - y=241.66666666666666, - z=902.8809523809524, - o_z=1.0, - ), + center=Pose(x=282.45238095238096, y=241.66666666666666, z=902.8809523809524, o_z=1.0), box=RectangularPrism(dims_mm=Vector3(x=13, y=7, z=11)), ) ], @@ -101,12 +96,7 @@ reference_frame="depth1", geometries=[ Geometry( - center=Pose( - x=-129.84615384615384, - y=165.53846153846155, - z=511.46153846153845, - o_z=1.0, - ), + center=Pose(x=-129.84615384615384, y=165.53846153846155, z=511.46153846153845, o_z=1.0), box=RectangularPrism(dims_mm=Vector3(x=5.0, y=4.0, z=7.0)), ) ],