104 lines
4.0 KiB
Python
104 lines
4.0 KiB
Python
"""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])
|