def test_gain_and_exposure_as_functions(): class GainAndExposure(): def __init__(self): self.gain = 0 self.exposure = 0 def get_gain(self): self.gain += 1 return self.gain def get_exposure(self): self.exposure += 1 return self.exposure # Check that gain/exposure functions are accepted inputs and get recalled each time you get the # capture parameters proto. ge = GainAndExposure() visual_src = VisualImageSource("source1", FakeCamera(capture_fake, decode_fake), gain=ge.get_gain, exposure=ge.get_exposure) capture_params = visual_src.get_image_capture_params() assert capture_params.gain == 1 assert capture_params.exposure_duration.seconds == 1 capture_params = visual_src.get_image_capture_params() assert capture_params.gain == 2 assert capture_params.exposure_duration.seconds == 2
def test_make_image_source(): # Create a visual source with no rows/cols/image type provided. src_name = "source1" visual_src = VisualImageSource(src_name, FakeCamera(capture_fake, decode_fake)) assert visual_src.image_source_proto.name == src_name assert visual_src.image_source_proto.image_type == image_pb2.ImageSource.IMAGE_TYPE_VISUAL assert visual_src.image_source_proto.rows == 0 assert visual_src.image_source_proto.cols == 0 # Create a visual source with rows and cols provided. src_name2 = "source2" src_rows2 = 60 src_cols2 = 100 visual_src2 = VisualImageSource(src_name2, FakeCamera(capture_fake, decode_fake), src_rows2, src_cols2) assert visual_src2.image_source_proto.name == src_name2 assert visual_src2.image_source_proto.image_type == image_pb2.ImageSource.IMAGE_TYPE_VISUAL assert visual_src2.image_source_proto.rows == src_rows2 assert visual_src2.image_source_proto.cols == src_cols2 # Test the static method to create an ImageSourceProto src_name3 = "source3" src_rows3 = 10 src_cols3 = 15 src_type3 = image_pb2.ImageSource.IMAGE_TYPE_DEPTH img_proto = VisualImageSource.make_image_source(src_name3, src_rows3, src_cols3, src_type3) assert img_proto.name == src_name3 assert img_proto.image_type == src_type3 assert img_proto.rows == src_rows3 assert img_proto.cols == src_cols3
def test_clear_not_active_faults(): # Check that clearing faults works with non active faults. class MockFaultClientFaultNotActive: def clear_service_fault(self, service_fault_id, clear_all_service_faults=False, clear_all_payload_faults=False, **kwargs): raise ServiceFaultDoesNotExistError(None, "error") visual_src = VisualImageSource("source1", FakeCamera(capture_with_error, decode_with_error)) fault_client = MockFaultClientFaultNotActive() visual_src.initialize_faults(fault_client, "service1") assert len(visual_src.active_fault_id_names) == 0
def test_make_capture_params(): # Create a visual source with no gain/exposure provided. visual_src = VisualImageSource("source1", FakeCamera(capture_fake, decode_fake)) params = visual_src.get_image_capture_params() assert params.gain == 0 assert params.exposure_duration.seconds == 0 assert params.exposure_duration.nanos == 0 # Create a visual source with gain and exposure provided. gain = 1.5 exposure = 101.005 visual_src = VisualImageSource("source1", FakeCamera(capture_fake, decode_fake), gain=gain, exposure=exposure) params = visual_src.get_image_capture_params() assert abs(params.gain - gain) < 1e-3 assert abs(params.exposure_duration.seconds - 101) < 1e-3 assert abs(params.exposure_duration.nanos - int(.005 * 1e9)) <= 1 # Test the static method to create a CaptureParameters proto cap_proto = VisualImageSource.make_capture_parameters(gain, exposure) assert abs(cap_proto.gain - gain) < 1e-3 assert abs(cap_proto.exposure_duration.seconds - 101) < 1e-3 assert abs(cap_proto.exposure_duration.nanos - int(.005 * 1e9)) <= 1 # Test the static method while passing functions for gain and exposure cap_proto = VisualImageSource.make_capture_parameters( lambda: gain, lambda: exposure) assert abs(cap_proto.gain - gain) < 1e-3 assert abs(cap_proto.exposure_duration.seconds - 101) < 1e-3 assert abs(cap_proto.exposure_duration.nanos - int(.005 * 1e9)) <= 1
def test_visual_source_with_thread(): barrier = threading.Barrier(2) inc = Increment(barrier) visual_src = VisualImageSource( "source1", FakeCamera(inc.capture_increment_count, decode_fake)) visual_src.create_capture_thread() # The Increment class has a barrier looking for "2" wait calls. If the blocking capture function gets # called as expected, and we call wait in the main thread of the test, then it will release. try: barrier.wait(timeout=1) except threading.BrokenBarrierError: pytest.fail( "Barrier reached the timeout, therefore blocking_capture is not called." )
def make_ricoh_theta_image_service(theta_ssid, theta_password, theta_client, robot, logger=None, use_background_capture_thread=False): # Create an theta instance, which will perform the HTTP requests to the ricoh theta # camera (using the Ricoh Theta API: https://api.ricoh/docs/#ricoh-theta-api). theta_instance = Theta(theta_ssid=theta_ssid, theta_pw=theta_password, client_mode=theta_client, show_state_at_init=False) try: # Test that communication to the camera works before creating the complete image service. theta_instance.showState() except json.decoder.JSONDecodeError as err: _LOGGER.warning("Ricoh Theta faulted on initialization.") # The JSONDecodeError signifies that the response from the ricoh theta HTTP post requests are # coming back empty, meaning the camera is not setup correctly to communicate with the theta instance. fault_client = robot.ensure_client(FaultClient.default_service_name) fault = CAMERA_SETUP_FAULT fault.error_message = "Failed to communicate with the camera at %s: server is " % theta_instance.baseip + \ "responding with empty json messages." resp = fault_client.trigger_service_fault_async(fault) return False, None except Exception as err: error = "Exception raised when attempting to communicate with the ricoh theta: %s" % str( err) _LOGGER.warning(error) fault_client = robot.ensure_client(FaultClient.default_service_name) fault = CAMERA_SETUP_FAULT fault.error_message = error resp = fault_client.trigger_service_fault_async(fault) return False, None ricoh_helper = RicohThetaServiceHelper(theta_ssid, theta_instance, logger) img_src = VisualImageSource(ricoh_helper.image_source_name, ricoh_helper, ricoh_helper.rows, ricoh_helper.cols, ricoh_helper.camera_gain, ricoh_helper.camera_exposure, logger) return True, CameraBaseImageServicer(robot, DIRECTORY_NAME, [img_src], logger, use_background_capture_thread)
def test_not_camera_interface(): class WrongFakeCamera(): def blocking_capture(self): return 1 # Check that the camera_interface argument ensures that it is a class which subclasses # the provided CameraInterface class. with pytest.raises(AssertionError): visual_src = VisualImageSource("source1", WrongFakeCamera()) # Check that instantiating a class with camera interface without the methods expected # will fail on creation. class BadFakeCamera(CameraInterface): def missing_everything(self): pass with pytest.raises(TypeError): visual_src = VisualImageSource("source2", BadFakeCamera())
def make_webcam_image_service(bosdyn_sdk_robot, service_name, device_names, logger=None): image_sources = [] for device in device_names: web_cam = WebCam(device) img_src = VisualImageSource(web_cam.image_source_name, web_cam, rows=web_cam.rows, cols=web_cam.cols, gain=web_cam.camera_gain, exposure=web_cam.camera_exposure) image_sources.append(img_src) return CameraBaseImageServicer(bosdyn_sdk_robot, service_name, image_sources, logger)
def test_faults_in_visual_source(): # Create the fault client fault_client = MockFaultClient() # Populate fault client with at least one "old" fault. fault_client.trigger_service_fault_async( service_fault_pb2.ServiceFault( fault_id=service_fault_pb2.ServiceFaultId(fault_name="fault1"))) init_fault_amount = fault_client.get_total_fault_count() visual_src = VisualImageSource( "source1", FakeCamera(capture_with_error, decode_with_error)) # Attempt to get an image with no fault client enabled. Make sure no error is raised, and # values are returned as none. image, timestamp = visual_src.get_image_and_timestamp() assert image is None assert timestamp is None # attempt to decode an image with no fault client enabled. Make sure no error is raised. im_proto = image_pb2.Image(rows=10) success = visual_src.image_decode_with_error_checking( None, im_proto, None, None) assert im_proto.rows == 15 assert not success # Setup faults visual_src.initialize_faults(fault_client, "service1") assert fault_client.get_total_fault_count() == 0 assert visual_src.camera_capture_fault is not None assert visual_src.camera_capture_fault.fault_id.service_name == "service1" assert visual_src.decode_data_fault is not None assert visual_src.decode_data_fault.fault_id.service_name == "service1" # With fault client setup, check that faults are thrown when the bad functions get called. image, timestamp = visual_src.get_image_and_timestamp() assert image is None assert timestamp is None assert fault_client.service_fault_counts[ visual_src.camera_capture_fault.fault_id.fault_name] == 1 im_proto = image_pb2.Image(rows=21) success = visual_src.image_decode_with_error_checking( None, im_proto, None, None) assert im_proto.rows == 15 assert fault_client.service_fault_counts[ visual_src.decode_data_fault.fault_id.fault_name] == 1 assert not success
def test_faults_are_cleared_on_success(): # Check that captures/decodes that fail and then later succeed will cause the faults to get cleared. class FailsThenSucceeds(CameraInterface): def __init__(self): self.capture_count = 0 self.decode_count = 0 def blocking_capture(self): self.capture_count += 1 if self.capture_count == 1: raise Exception("Fake bad capture.") return "image", 1 def image_decode(self, image_data, image_proto, image_format, quality_percent): self.decode_count += 1 if self.decode_count == 1: raise Exception("Fake bad decode.") visual_src = VisualImageSource("source1", FailsThenSucceeds()) fault_client = MockFaultClient() visual_src.initialize_faults(fault_client, "service1") # The first call to the capture and decode functions cause a fault to be thrown. image, timestamp = visual_src.get_image_and_timestamp() assert fault_client.service_fault_counts[ visual_src.camera_capture_fault.fault_id.fault_name] == 1 success = visual_src.image_decode_with_error_checking( None, image_pb2.Image(rows=21), None, None) assert fault_client.service_fault_counts[ visual_src.decode_data_fault.fault_id.fault_name] == 1 # The second calls will succeed, and now cause the faults to be cleared. image, timestamp = visual_src.get_image_and_timestamp() assert fault_client.service_fault_counts[ visual_src.camera_capture_fault.fault_id.fault_name] == 0 success = visual_src.image_decode_with_error_checking( None, image_pb2.Image(rows=21), None, None) assert fault_client.service_fault_counts[ visual_src.decode_data_fault.fault_id.fault_name] == 0
def make_webcam_image_service(bosdyn_sdk_robot, service_name, device_names, show_debug_information=False, logger=None, codec="", res_width=-1, res_height=-1): image_sources = [] for device in device_names: web_cam = WebCam(device, show_debug_information=show_debug_information, codec=codec, res_width=res_width, res_height=res_height) img_src = VisualImageSource(web_cam.image_source_name, web_cam, rows=web_cam.rows, cols=web_cam.cols, gain=web_cam.camera_gain, exposure=web_cam.camera_exposure) image_sources.append(img_src) return CameraBaseImageServicer(bosdyn_sdk_robot, service_name, image_sources, logger)
def _test_camera_service(use_background_capture_thread, logger=None): robot = MockRobot() src_name = "source1" r_amt = 10 c_amt = 21 gain = 25 visual_src = VisualImageSource(src_name, FakeCamera(capture_fake, decode_fake), rows=r_amt, cols=c_amt, gain=gain) src_name2 = "source_cap_error" visual_src2 = VisualImageSource(src_name2, FakeCamera(capture_with_error, decode_fake), rows=r_amt, cols=c_amt) src_name3 = "source_decode_error" visual_src3 = VisualImageSource(src_name3, FakeCamera(capture_fake, decode_with_error), rows=r_amt, cols=c_amt) src_name4 = "source_capture_malformed" visual_src4 = VisualImageSource(src_name4, FakeCamera(capture_return_onething, decode_fake), rows=r_amt, cols=c_amt) src_name5 = "source_decode_malformed" visual_src5 = VisualImageSource(src_name5, FakeCamera(capture_fake, decode_less_args), rows=r_amt, cols=c_amt) src_name6 = "source2" visual_src6 = VisualImageSource(src_name6, FakeCamera(capture_fake, decode_fake), rows=r_amt, cols=c_amt, gain=gain) image_sources = [ visual_src, visual_src2, visual_src3, visual_src4, visual_src5, visual_src6 ] camera_service = CameraBaseImageServicer( robot, "camera-service", image_sources, use_background_capture_thread=use_background_capture_thread, logger=logger) req = image_pb2.ListImageSourcesRequest() resp = camera_service.ListImageSources(req, None) assert resp.header.error.code == header_pb2.CommonError.CODE_OK assert len(resp.image_sources) == 6 found_src1, found_src2, found_src3, found_src4, found_src5, found_src6 = False, False, False, False, False, False for src in resp.image_sources: if src.name == src_name: found_src1 = True if src.name == src_name2: found_src2 = True if src.name == src_name3: found_src3 = True if src.name == src_name4: found_src4 = True if src.name == src_name5: found_src5 = True if src.name == src_name6: found_src6 = True assert found_src1 and found_src2 and found_src3 and found_src4 and found_src5 and found_src6 # Request a known image source and make sure the response is as expected. req = image_pb2.GetImageRequest() req.image_requests.extend([ image_pb2.ImageRequest(image_source_name=src_name, quality_percent=10) ]) resp = camera_service.GetImage(req, None) assert resp.header.error.code == header_pb2.CommonError.CODE_OK assert len(resp.image_responses) == 1 img_resp = resp.image_responses[0] assert img_resp.source.name == src_name assert img_resp.source.rows == r_amt assert img_resp.source.cols == c_amt assert img_resp.shot.capture_params.gain == gain assert img_resp.shot.image.rows == 15 # Output of decode_fake assert img_resp.shot.image.cols == c_amt assert abs(img_resp.shot.acquisition_time.seconds - 10) < 1e-3 # Robot converted timestamp assert abs(img_resp.shot.acquisition_time.nanos - 20) < 1e-3 # Robot converted timestamp # Request multiple image sources and make sure the response is complete. req = image_pb2.GetImageRequest() req.image_requests.extend([ image_pb2.ImageRequest(image_source_name=src_name, quality_percent=10), image_pb2.ImageRequest(image_source_name=src_name6) ]) resp = camera_service.GetImage(req, None) assert resp.header.error.code == header_pb2.CommonError.CODE_OK assert len(resp.image_responses) == 2 found_src1, found_src6 = False, False for src in resp.image_responses: if src.source.name == src_name: found_src1 = True if src.source.name == src_name6: found_src6 = True assert found_src6 and found_src1 # Request an image source that does not exist. req = image_pb2.GetImageRequest() req.image_requests.extend([ image_pb2.ImageRequest(image_source_name="unknown", quality_percent=10) ]) resp = camera_service.GetImage(req, None) assert resp.header.error.code == header_pb2.CommonError.CODE_OK assert len(resp.image_responses) == 1 img_resp = resp.image_responses[0] assert img_resp.status == image_pb2.ImageResponse.STATUS_UNKNOWN_CAMERA # Request an image from a source with a bad capture function. req = image_pb2.GetImageRequest() req.image_requests.extend([ image_pb2.ImageRequest(image_source_name=src_name2, quality_percent=10) ]) resp = camera_service.GetImage(req, None) assert resp.header.error.code == header_pb2.CommonError.CODE_OK assert len(resp.image_responses) == 1 img_resp = resp.image_responses[0] assert img_resp.status == image_pb2.ImageResponse.STATUS_IMAGE_DATA_ERROR # Request an image from a source with a decode error. req = image_pb2.GetImageRequest() req.image_requests.extend([ image_pb2.ImageRequest(image_source_name=src_name3, quality_percent=10) ]) resp = camera_service.GetImage(req, None) assert resp.header.error.code == header_pb2.CommonError.CODE_OK assert len(resp.image_responses) == 1 img_resp = resp.image_responses[0] print(img_resp) assert img_resp.status == image_pb2.ImageResponse.STATUS_UNSUPPORTED_IMAGE_FORMAT_REQUESTED # Request an image with a malformed capture function (should raise an error so developer can fix). req = image_pb2.GetImageRequest() req.image_requests.extend([ image_pb2.ImageRequest(image_source_name=src_name4, quality_percent=10) ]) resp = camera_service.GetImage(req, None) assert resp.header.error.code == header_pb2.CommonError.CODE_OK assert len(resp.image_responses) == 1 img_resp = resp.image_responses[0] assert img_resp.status == image_pb2.ImageResponse.STATUS_IMAGE_DATA_ERROR
def test_active_faults(): # Check that active faults are managed correctly. visual_src = VisualImageSource("source1", FakeCamera(capture_with_error, decode_with_error)) fault_client = MockFaultClient() visual_src.initialize_faults(fault_client, "service1") # Initialize function calls the clear_service_fault RPC. assert fault_client.clear_service_fault_called_count == 1 assert len(visual_src.active_fault_id_names) == 0 fake_fault_id = service_fault_pb2.ServiceFaultId(fault_name="Fake Capture Failure") fake_fault = service_fault_pb2.ServiceFault( fault_id=fake_fault_id, severity=service_fault_pb2.ServiceFault.SEVERITY_WARN) # Make sure we don't call clear_service_fault RPC because the fake fault is not active yet. visual_src.clear_fault(fake_fault) assert len(visual_src.active_fault_id_names) == 0 assert fault_client.clear_service_fault_called_count == 1 # Make the fake fault active. visual_src.trigger_fault("error", fake_fault) assert len(visual_src.active_fault_id_names) == 1 assert fault_client.service_fault_counts[fake_fault.fault_id.fault_name] == 1 # Clear the fake fault, but make sure RPC is only called once. visual_src.clear_fault(fake_fault) assert fault_client.clear_service_fault_called_count == 2 visual_src.clear_fault(fake_fault) # Make sure clear_service_fault is not called for a cleared fault. assert fault_client.clear_service_fault_called_count == 2 assert len(visual_src.active_fault_id_names) == 0