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
32 changes: 24 additions & 8 deletions src/motion_detector.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import math
from typing import Any, ClassVar, Dict, List, Mapping, Optional, Sequence
from typing import Any, ClassVar, Dict, List, Mapping, Optional, Sequence, Tuple

import cv2
import numpy as np
Expand Down Expand Up @@ -51,7 +51,7 @@ def new_service(

# Validates JSON Configuration
@classmethod
def validate_config(cls, config: ServiceConfig) -> Sequence[str]:
def validate_config(cls, config: ServiceConfig) -> Tuple[Sequence[str], Sequence[str]]:
validate_cam_name = config.attributes.fields["cam_name"].string_value
validate_camera_name = config.attributes.fields["camera_name"].string_value

Expand Down Expand Up @@ -126,7 +126,7 @@ def validate_config(cls, config: ServiceConfig) -> Sequence[str]:
raise ValueError("x1_rel should be less than x2_rel")
if y1_rel > y2_rel:
raise ValueError("y1_rel should be less than y2_rel")
return [source_cam]
return [source_cam], []

# Handles attribute reconfiguration
def reconfigure(
Expand Down Expand Up @@ -172,7 +172,10 @@ async def get_classifications(
**kwargs,
) -> List[Classification]:
# Grab and grayscale 2 images
input1 = await self.camera.get_image(mime_type=CameraMimeType.JPEG)
images, _ = await self.camera.get_images()
if images is None or len(images) == 0:
raise ValueError("No images returned by get_images")
input1 = images[0]
if input1.mime_type not in [CameraMimeType.JPEG, CameraMimeType.PNG]:
raise ValueError(
"image mime type must be PNG or JPEG, not ", input1.mime_type
Expand All @@ -181,7 +184,10 @@ async def get_classifications(
img1, _, _ = self.crop_image(img1)
gray1 = cv2.cvtColor(np.array(img1), cv2.COLOR_BGR2GRAY)

input2 = await self.camera.get_image()
camera_images, _ = await self.camera.get_images()
if camera_images is None or len(camera_images) == 0:
raise ValueError("No images were returned by get_images")
input2 = camera_images[0]
if input2.mime_type not in [CameraMimeType.JPEG, CameraMimeType.PNG]:
raise ValueError(
"image mime type must be PNG or JPEG, not ", input2.mime_type
Expand Down Expand Up @@ -222,7 +228,10 @@ async def get_detections(
**kwargs,
) -> List[Detection]:
# Grab and grayscale 2 images
input1 = await self.camera.get_image(mime_type=CameraMimeType.JPEG)
images, _ = await self.camera.get_images()
if images is None or len(images) == 0:
raise ValueError("No images returned by get_images")
input1 = images[0]
if input1.mime_type not in [CameraMimeType.JPEG, CameraMimeType.PNG]:
raise ValueError(
"image mime type must be PNG or JPEG, not ", input1.mime_type
Expand All @@ -231,7 +240,10 @@ async def get_detections(
img1, width, height = self.crop_image(img1)
gray1 = cv2.cvtColor(np.array(img1), cv2.COLOR_BGR2GRAY)

input2 = await self.camera.get_image()
camera_images2, _ = await self.camera.get_images()
if camera_images2 is None or len(camera_images2) == 0:
raise ValueError("No images were returned by get_images")
input2 = camera_images2[0]
if input2.mime_type not in [CameraMimeType.JPEG, CameraMimeType.PNG]:
raise ValueError(
"image mime type must be PNG or JPEG, not ", input2.mime_type
Expand Down Expand Up @@ -302,7 +314,11 @@ async def capture_all_from_camera( # pylint: disable=too-many-positional-argume
"is not the configured 'cam_name':",
self.cam_name,
)
img = await self.camera.get_image(mime_type=CameraMimeType.JPEG)
imgs, _ = await self.camera.get_images()
if (imgs is None or len(imgs) == 0) and \
(return_image or return_classifications or return_detections):
raise ValueError("No images returned by get_images")
img = imgs[0]
if return_image:
result.image = img
if return_classifications:
Expand Down
3 changes: 2 additions & 1 deletion tests/fakecam.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ async def get_image(self, mime_type: str = "") -> Coroutine[Any, Any, ViamImage]
return pil.pil_to_viam_image(self.images[self.count%2], CameraMimeType.JPEG)

async def get_images(self) -> Coroutine[Any, Any, Tuple[List[NamedImage] | ResponseMetadata]]:
raise NotImplementedError
self.count +=1
return [pil.pil_to_viam_image(self.images[self.count%2], CameraMimeType.JPEG)], None

async def get_properties(self) -> Coroutine[Any, Any, GetPropertiesResponse]:
raise NotImplementedError
Expand Down
12 changes: 6 additions & 6 deletions tests/test_motiondetector.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def test_empty(self):
with pytest.raises(
ValueError, match=pytest.source_camera_name_none_defined_error_message
):
response = md.validate_config(config=empty_config)
response, _ = md.validate_config(config=empty_config)

# For each way to specify a valid min/max size, have a test that checks it's valid.
@parameterized.expand(
Expand Down Expand Up @@ -74,7 +74,7 @@ def test_valid(self, unused_test_name, extra_config_values):
raw_config = {"cam_name": "test"}
raw_config.update(extra_config_values)
config = make_component_config(raw_config)
response = md.validate_config(config=config)
response, _ = md.validate_config(config=config)
assert response == ["test"]

# For each type of invalid config, test that the expected error is raised.
Expand Down Expand Up @@ -120,7 +120,7 @@ def test_invalid(self, error_message, extra_config_values):
raw_config.update(extra_config_values)
config = make_component_config(raw_config)
with pytest.raises(ValueError, match=error_message):
response = md.validate_config(config=config)
response, _ = md.validate_config(config=config)

def test_empty_config_name(self):
md = getMD()
Expand All @@ -129,7 +129,7 @@ def test_empty_config_name(self):
with pytest.raises(
ValueError, match=pytest.source_camera_name_none_defined_error_message
):
response = md.validate_config(config=config)
response, _ = md.validate_config(config=config)

# For each way to specify a valid camera name, test that the return is valid.
@parameterized.expand(
Expand All @@ -141,7 +141,7 @@ def test_empty_config_name(self):
def test_valid_camera_names(self, unused_test_name, cam_config):
md = getMD()
config = make_component_config(cam_config)
response = md.validate_config(config=config)
response, _ = md.validate_config(config=config)
assert response == ["test"]

# For each way to spedify an invalid camera name, test that the expected error is raised.
Expand All @@ -168,7 +168,7 @@ def test_invalid_camera_names(self, unused_test_name, cam_config, error_message)
md = getMD()
config = make_component_config(cam_config)
with pytest.raises(ValueError, match=error_message):
response = md.validate_config(config=config)
response, _ = md.validate_config(config=config)


class TestMotionDetector:
Expand Down