"""This test suite evaluates and tests behavior of the ``ObstacleLocator`` class""" # Standard library from csv import reader from math import radians from pathlib import Path # Typing from typing import cast from typing import Tuple # Testing from unittest import TestCase # Scientific from cv2 import imread # Module under test from pyrate.sense.vision.image_line import ImageLine from pyrate.sense.vision.obstacle_locator import ObstacleLocator PATH_TO_DATASET = Path(__file__).parent / "resources" / "testing_dataset_successful" DATASET_IMAGES_PATHS = sorted(list((PATH_TO_DATASET / "testims").glob("*.jpg"))) DATASET_ANNOTATIONS_PATHS = sorted(list((PATH_TO_DATASET / "annotations").glob("*.txt"))) PATH_TO_FAILING = ( Path(__file__).parent / "resources" / "testing_dataset_no_horizon" / "testims" / "Preprocessed_test_0.jpg" ) IMAGE_HEIGHT, IMAGE_WIDTH = imread(PATH_TO_FAILING.as_posix()).shape[:2] class TestObstacleLocator(TestCase): """Test for correct predictions made by ``ObstacleLocator``""" @staticmethod def parse_annotation(file_path: str, obstacle_locator: ObstacleLocator) -> Tuple[ImageLine, float]: """Helper function to parse the ground truth labels from the dataset. Args: file_path: Label file path obstacle_locator: the ObstacleLocator that returns the ImageLine that should be compared to the returned ImageLine of this function Returns: ImageLine as described in the annotation, angle read from annotation (for testing correct angle calculation) """ with open(file_path, "rt", encoding="UTF-8") as label_file: content = label_file.read().split("\n") csvreader = reader(content, delimiter=",") point_a = cast(Tuple[int, int], tuple(int(x) for x in next(csvreader))) point_b = cast(Tuple[int, int], tuple(int(x) for x in next(csvreader))) label_angle = radians(float(next(csvreader)[0])) line = ImageLine.from_points( image_shape=(obstacle_locator.image_width, obstacle_locator.image_height), points=(point_a, point_b), ) return line, label_angle def test_horizon_angle(self): """Compares ``ObstacleLocator`` horizon estimates to ground truth annotations""" uut_ol = ObstacleLocator(image_width=IMAGE_WIDTH, image_height=IMAGE_HEIGHT) # unit/module under test for image_path, label_path in zip(DATASET_IMAGES_PATHS, DATASET_ANNOTATIONS_PATHS): with self.subTest(image=image_path.name): # Assert that we have the correct label for the test image self.assertEqual( image_path.name.split(".")[0], label_path.name.split(".")[0], msg="That isn't the right label for the image. This shouldn't happen.", ) image = imread(image_path.as_posix()) # read annotation and test if ImageLine calculates the line's angle correctly label_image_line, label_angle = self.parse_annotation(label_path.as_posix(), uut_ol) self.assertAlmostEqual(label_angle, label_image_line.angle, places=2) result = uut_ol.detect_horizon(image) horizons = result[0] # Test that a) a horizon is detected and b) it has the correct angle self.assertTrue(len(horizons) > 0, msg="No horizon was detected.") self.assertAlmostEqual( horizons[0].angle, label_image_line.angle, places=1, msg="Horizon angle mismatch." ) def test_missing_lines(self): """Tests the branch when no horizon line is detected in the image""" uut_ol = ObstacleLocator(image_width=IMAGE_WIDTH, image_height=IMAGE_HEIGHT) # unit/module under test image = imread(PATH_TO_FAILING.as_posix()) # ObstacleLocator does not find a horizon line in this image result = uut_ol.detect_horizon(image) self.assertFalse(result[0])