def test_ellipse_magic_methods(self):
        """
        <b>Description:</b>
        Check Ellipse __repr__, __eq__, __hash__ methods

        <b>Input data:</b>
        Initialized instance of Ellipse

        <b>Expected results:</b>
        Test passes if Ellipse magic methods returns correct values

        <b>Steps</b>
        1. Initialize Ellipse instance
        2. Check returning value of magic methods
        """

        x1 = self.ellipse_params()["x1"]
        x2 = self.ellipse_params()["x2"]
        y1 = self.ellipse_params()["y1"]
        y2 = self.ellipse_params()["y2"]

        ellipse = self.ellipse()
        assert repr(ellipse) == f"Ellipse(x1={x1}, y1={y1}, x2={x2}, y2={y2})"

        other_ellipse_params = {
            "x1": 0.5,
            "x2": 1.0,
            "y1": 0.0,
            "y2": 0.5,
            "modification_date": self.modification_date,
        }

        third_ellipse_params = {
            "x1": 0.3,
            "y1": 0.5,
            "x2": 0.4,
            "y2": 0.6,
            "modification_date": self.modification_date,
        }

        other_ellipse = Ellipse(**other_ellipse_params)
        third_ellipse = Ellipse(**third_ellipse_params)

        assert ellipse == other_ellipse
        assert ellipse != third_ellipse
        assert ellipse != str

        assert hash(ellipse) == hash(str(ellipse))
Beispiel #2
0
    def shape_as_ellipse(shape: ShapeEntity) -> Ellipse:
        """
        Returns the inner-fitted ellipse for a given shape.

        :param shape: Shape to convert
        :return: Converted Shape
        """
        if isinstance(shape, Ellipse):
            return shape

        if isinstance(shape, Rectangle):
            x1 = shape.x1
            x2 = shape.x2
            y1 = shape.y1
            y2 = shape.y2
        elif isinstance(shape, Polygon):
            x1 = shape.min_x
            x2 = shape.max_x
            y1 = shape.min_y
            y2 = shape.max_y
        else:
            raise NotImplementedError(
                f"Conversion of a {type(shape)} to an ellipse is not implemented yet: {shape}"
            )
        return Ellipse(x1=x1, y1=y1, x2=x2, y2=y2)
    def test_ellipse_get_evenly_distributed_ellipse_coordinates(self):
        """
        <b>Description:</b>
        Check Ellipse get_evenly_distributed_ellipse_coordinates methods

        <b>Input data:</b>
        Initialized instance of Ellipse

        <b>Expected results:</b>
        Test passes if Ellipse get_evenly_distributed_ellipse_coordinates returns correct values

        <b>Steps</b>
        1. Initialize Ellipse instance
        2. Check returning value
        """

        ellipse = self.ellipse()
        number_of_coordinates = 3
        coordinates_ellipse_line = ellipse.get_evenly_distributed_ellipse_coordinates(
            number_of_coordinates)
        assert len(coordinates_ellipse_line) == 3
        assert coordinates_ellipse_line[0] == (1.0, 0.25)
        assert coordinates_ellipse_line[1] == (0.625, 0.4665063509461097)
        assert coordinates_ellipse_line[2] == (0.6249999999999999,
                                               0.033493649053890406)

        width_gt_height_ellipse = Ellipse(
            **self.width_gt_height_ellipse_params())
        coordinates_ellipse_line = (
            width_gt_height_ellipse.get_evenly_distributed_ellipse_coordinates(
                number_of_coordinates))
        assert width_gt_height_ellipse.height < width_gt_height_ellipse.width
        assert len(coordinates_ellipse_line) == 3
        assert coordinates_ellipse_line[0] == (0.65, 0.3)
        assert coordinates_ellipse_line[1] == (0.7666223198362645,
                                               0.1371094972158116)
        assert coordinates_ellipse_line[2] == (0.5333776801637811,
                                               0.13710949721577403)
Beispiel #4
0
    def test_shape_entity_not_implemented_methods(self):
        """
        <b>Description:</b>
        Check not implemented methods of ShapeEntity class

        <b>Expected results:</b>
        Test passes if NotImplementedError exception raises when using not implemented methods on ShapeEntity instance
        """
        rectangle_entity = Rectangle(x1=0.2, y1=0.2, x2=0.6, y2=0.7)
        ellipse_entity = Ellipse(x1=0.4, y1=0.1, x2=0.9, y2=0.8)
        polygon_entity = Polygon([
            Point(0.3, 0.4),
            Point(0.3, 0.7),
            Point(0.5, 0.75),
            Point(0.8, 0.7),
            Point(0.8, 0.4),
        ])
        for shape in [rectangle_entity, ellipse_entity, polygon_entity]:
            with pytest.raises(NotImplementedError):
                ShapeEntity.get_area(shape)
            with pytest.raises(NotImplementedError):
                ShapeEntity.intersects(shape, shape)
            with pytest.raises(NotImplementedError):
                ShapeEntity.intersect_percentage(shape, shape)
            with pytest.raises(NotImplementedError):
                ShapeEntity.get_labels(shape)
            with pytest.raises(NotImplementedError):
                ShapeEntity.append_label(
                    shape,
                    ScoredLabel(
                        LabelEntity(name="classification",
                                    domain=Domain.CLASSIFICATION)),
                )
            with pytest.raises(NotImplementedError):
                ShapeEntity.set_labels(
                    shape,
                    [
                        ScoredLabel(
                            LabelEntity(name="detection",
                                        domain=Domain.DETECTION))
                    ],
                )
            with pytest.raises(NotImplementedError):
                ShapeEntity.normalize_wrt_roi_shape(shape, rectangle_entity)
            with pytest.raises(NotImplementedError):
                ShapeEntity.denormalize_wrt_roi_shape(shape, rectangle_entity)
            with pytest.raises(NotImplementedError):
                ShapeEntity._as_shapely_polygon(shape)
Beispiel #5
0
    def test_ellipse_shape_conversion(self):
        """
        <b>Description:</b>
        Checks that conversions from Ellipse to other shapes works correctly

        <b>Input data:</b>
        A rectangle at [0.1, 0.1, 0.5, 0.2]

        <b>Expected results:</b>
        The test passes if the Ellipse can be converted to Rectangle and Polygon

        <b>Steps</b>
        1. Create Ellipse and get coordinates
        2. Convert to Ellipse
        3. Convert to Polygon
        4. Convert to Rectangle
        """
        ellipse = Ellipse(x1=0.1, y1=0.1, x2=0.5, y2=0.2)
        ellipse_coords = (ellipse.x1, ellipse.y1, ellipse.x2, ellipse.y2)

        ellipse2 = ShapeFactory.shape_as_ellipse(ellipse)
        assert isinstance(ellipse, Ellipse)
        assert ellipse == ellipse2

        polygon = ShapeFactory.shape_as_polygon(ellipse)
        assert isinstance(polygon, Polygon)
        assert polygon.min_x == pytest.approx(ellipse.x1, 0.1)
        assert polygon.min_y == pytest.approx(ellipse.y1, 0.1)
        assert polygon.max_x == pytest.approx(ellipse.x2, 0.1)
        assert polygon.max_y == pytest.approx(ellipse.y2, 0.1)

        rectangle = ShapeFactory.shape_as_rectangle(ellipse)
        assert isinstance(rectangle, Rectangle)
        assert (
            rectangle.x1,
            rectangle.y1,
            rectangle.x2,
            rectangle.y2,
        ) == ellipse_coords
    def test_annotation_setters(self):
        """
        <b>Description:</b>
        Check that Annotation can correctly return modified property value

        <b>Input data:</b>
        Annotation class

        <b>Expected results:</b>
        Test passes if the Annotation return correct values

        <b>Steps</b>
        1. Create Annotation instances
        2. Set another values
        3. Check changed values
        """

        annotation = self.annotation
        ellipse = Ellipse(x1=0.5, y1=0.1, x2=0.8, y2=0.3)
        annotation.shape = ellipse
        annotation.id = ID(123456789)

        assert annotation.id == ID(123456789)
        assert annotation.shape == ellipse
def generate_random_annotated_image(
    image_width: int,
    image_height: int,
    labels: Sequence[LabelEntity],
    min_size=50,
    max_size=250,
    shape: Optional[str] = None,
    max_shapes: int = 10,
    intensity_range: List[Tuple[int, int]] = None,
    random_seed: Optional[int] = None,
) -> Tuple[np.ndarray, List[Annotation]]:
    """
    Generate a random image with the corresponding annotation entities.

    :param intensity_range: Intensity range for RGB channels ((r_min, r_max), (g_min, g_max), (b_min, b_max))
    :param max_shapes: Maximum amount of shapes in the image
    :param shape: {"rectangle", "ellipse", "triangle"}
    :param image_height: Height of the image
    :param image_width: Width of the image
    :param labels: Task Labels that should be applied to the respective shape
    :param min_size: Minimum size of the shape(s)
    :param max_size: Maximum size of the shape(s)
    :param random_seed: Seed to initialize the random number generator
    :return: uint8 array, list of shapes
    """
    from skimage.draw import random_shapes, rectangle

    if intensity_range is None:
        intensity_range = [(100, 200)]

    image1: Optional[np.ndarray] = None
    sc_labels = []
    # Sporadically, it might happen there is no shape in the image, especially on low-res images.
    # It'll retry max 5 times until we see a shape, and otherwise raise a runtime error
    if (
            shape == "ellipse"
    ):  # ellipse shape is not available in random_shapes function. use circle instead
        shape = "circle"
    for _ in range(5):
        rand_image, sc_labels = random_shapes(
            (image_height, image_width),
            min_shapes=1,
            max_shapes=max_shapes,
            intensity_range=intensity_range,
            min_size=min_size,
            max_size=max_size,
            shape=shape,
            random_seed=random_seed,
        )
        num_shapes = len(sc_labels)
        if num_shapes > 0:
            image1 = rand_image
            break

    if image1 is None:
        raise RuntimeError(
            "Was not able to generate a random image that contains any shapes")

    annotations: List[Annotation] = []
    for sc_label in sc_labels:
        sc_label_name = sc_label[0]
        sc_label_shape_r = sc_label[1][0]
        sc_label_shape_c = sc_label[1][1]
        y_min, y_max = max(0.0,
                           float(sc_label_shape_r[0] / image_height)), min(
                               1.0, float(sc_label_shape_r[1] / image_height))
        x_min, x_max = max(0.0, float(sc_label_shape_c[0] / image_width)), min(
            1.0, float(sc_label_shape_c[1] / image_width))

        if sc_label_name == "ellipse":
            # Fix issue with newer scikit-image libraries that generate ellipses.
            # For now we render a rectangle on top of it
            sc_label_name = "rectangle"
            rr, cc = rectangle(
                start=(sc_label_shape_r[0], sc_label_shape_c[0]),
                end=(sc_label_shape_r[1] - 1, sc_label_shape_c[1] - 1),
                shape=image1.shape,
            )
            image1[rr, cc] = (
                random.randint(0, 200),  # nosec
                random.randint(0, 200),  # nosec
                random.randint(0, 200),  # nosec
            )
        if sc_label_name == "circle":
            sc_label_name = "ellipse"

        label_matches = [
            label for label in labels if sc_label_name == label.name
        ]
        if len(label_matches) > 0:
            label = label_matches[0]
            box_annotation = Annotation(
                Rectangle(x1=x_min, y1=y_min, x2=x_max, y2=y_max),
                labels=[ScoredLabel(label, probability=1.0)],
            )

            annotation: Annotation

            if label.name == "ellipse":
                annotation = Annotation(
                    Ellipse(
                        x1=box_annotation.shape.x1,
                        y1=box_annotation.shape.y1,
                        x2=box_annotation.shape.x2,
                        y2=box_annotation.shape.y2,
                    ),
                    labels=box_annotation.get_labels(include_empty=True),
                )
            elif label.name == "triangle":
                points = [
                    Point(
                        x=(box_annotation.shape.x1 + box_annotation.shape.x2) /
                        2,
                        y=box_annotation.shape.y1,
                    ),
                    Point(x=box_annotation.shape.x1,
                          y=box_annotation.shape.y2),
                    Point(x=box_annotation.shape.x2,
                          y=box_annotation.shape.y2),
                ]

                annotation = Annotation(
                    Polygon(points=points),
                    labels=box_annotation.get_labels(include_empty=True),
                )
            else:
                annotation = box_annotation

            annotations.append(annotation)
        else:
            logger.warning(
                "Generated a random image, but was not able to associate a label with a shape. "
                f"The name of the shape was `{sc_label_name}`. ")

    return image1, annotations
Beispiel #8
0
 def not_inscribed_ellipse() -> Ellipse:
     return Ellipse(x1=0.0, y1=0.0, x2=0.01, y2=0.01)
Beispiel #9
0
 def ellipse(self) -> Ellipse:
     return Ellipse(x1=0.4,
                    y1=0.1,
                    x2=0.9,
                    y2=0.8,
                    labels=self.generate_labels_list())
Beispiel #10
0
 def fully_covering_ellipse() -> Ellipse:
     return Ellipse(x1=0.0, y1=0.0, x2=1.0, y2=1.0)
    def test_ellipse(self):
        """
        <b>Description:</b>
        Check ellipse parameters

        <b>Input data:</b>
        Coordinates

        <b>Expected results:</b>
        Test passes if Ellipse correctly calculates parameters and returns default values

        <b>Steps</b>
        1. Check ellipse params
        2. Check ellipse default values
        3. Check ellipse with incorrect coordinates
        """

        ellipse = self.ellipse()
        modification_date = self.modification_date

        assert ellipse.width == 0.5
        assert ellipse.height == 0.5
        assert ellipse.x_center == 0.75
        assert ellipse.y_center == 0.25
        assert ellipse.minor_axis == 0.25
        assert ellipse.major_axis == 0.25
        assert ellipse._labels == []
        assert ellipse.modification_date == modification_date

        incorrect_ellipse_params = {
            "x1": 0,
            "x2": 0,
            "y1": 0,
            "y2": 0,
        }

        with pytest.raises(ValueError):
            Ellipse(**incorrect_ellipse_params)

        width_lt_height_ellipse_params = {
            "x1": 0.4,
            "x2": 0.5,
            "y1": 0.3,
            "y2": 0.4,
        }

        width_lt_height_ellipse = Ellipse(**width_lt_height_ellipse_params)
        assert width_lt_height_ellipse.height > width_lt_height_ellipse.width
        assert width_lt_height_ellipse.major_axis == width_lt_height_ellipse.height / 2
        assert width_lt_height_ellipse.width == 0.09999999999999998
        assert width_lt_height_ellipse.height == 0.10000000000000003
        assert width_lt_height_ellipse.x_center == 0.45
        assert width_lt_height_ellipse.y_center == 0.35
        assert width_lt_height_ellipse.minor_axis == 0.04999999999999999
        assert width_lt_height_ellipse.major_axis == 0.05000000000000002

        width_gt_height_ellipse = Ellipse(
            **self.width_gt_height_ellipse_params())
        assert width_gt_height_ellipse.height < width_gt_height_ellipse.width
        assert width_gt_height_ellipse.minor_axis == width_gt_height_ellipse.height / 2
        assert width_gt_height_ellipse.width == 0.30000000000000004
        assert width_gt_height_ellipse.height == 0.19999999999999998
        assert width_gt_height_ellipse.x_center == 0.65
        assert width_gt_height_ellipse.y_center == 0.2
        assert width_gt_height_ellipse.minor_axis == 0.09999999999999999
        assert width_gt_height_ellipse.major_axis == 0.15000000000000002
 def ellipse(self):
     return Ellipse(**self.ellipse_params())
Beispiel #13
0
    def test_dataset_item_roi_numpy(self):
        """
        <b>Description:</b>
        Check DatasetItemEntity class "roi_numpy" method

        <b>Input data:</b>
        DatasetItemEntity class object with specified "media", "annotation_scene", "roi", "metadata" and "subset"
        parameters

        <b>Expected results:</b>
        Test passes if array returned by "roi_numpy" method is equal to expected

        <b>Steps</b>
        1. Check array returned by roi_numpy method with not specified "roi" parameter for DatasetItemEntity with
        "roi" attribute is "None"
        2. Check array returned by roi_numpy method with Rectangle-shape "roi" parameter
        3. Check array returned by roi_numpy method with Ellipse-shape "roi" parameter
        4. Check array returned by roi_numpy method with Polygon-shape "roi" parameter
        5. Check array returned by roi_numpy method with non-specified "roi" parameter for DatasetItemEntity with "roi"
        attribute
        """
        media = DatasetItemParameters.generate_random_image()
        annotation_scene = DatasetItemParameters().annotations_entity()
        roi_label = LabelEntity("ROI label",
                                Domain.DETECTION,
                                id=ID("roi_label"))
        dataset_item = DatasetItemEntity(media, annotation_scene)
        # Checking array returned by "roi_numpy" method with non-specified "roi" parameter for DatasetItemEntity
        # "roi" attribute is "None"
        assert np.array_equal(dataset_item.roi_numpy(), media.numpy)
        # Checking array returned by "roi_numpy" method with specified Rectangle-shape "roi" parameter
        rectangle_roi = Annotation(
            Rectangle(x1=0.2, y1=0.1, x2=0.8, y2=0.9),
            [ScoredLabel(roi_label)],
            ID("rectangle_roi"),
        )
        assert np.array_equal(dataset_item.roi_numpy(rectangle_roi),
                              media.numpy[1:9, 3:13])
        # Checking array returned by "roi_numpy" method with specified Ellipse-shape "roi" parameter
        ellipse_roi = Annotation(
            Ellipse(x1=0.1, y1=0.0, x2=0.9, y2=0.8),
            [ScoredLabel(roi_label)],
            ID("ellipse_roi"),
        )
        assert np.array_equal(dataset_item.roi_numpy(ellipse_roi),
                              media.numpy[0:8, 2:14])
        # Checking array returned by "roi_numpy" method with specified Polygon-shape "roi" parameter
        polygon_roi = Annotation(
            shape=Polygon([
                Point(0.3, 0.4),
                Point(0.3, 0.7),
                Point(0.5, 0.75),
                Point(0.8, 0.7),
                Point(0.8, 0.4),
            ]),
            labels=[],
            id=ID("polygon_roi"),
        )
        assert np.array_equal(dataset_item.roi_numpy(polygon_roi),
                              media.numpy[4:8, 5:13])
        # Checking array returned by "roi_numpy" method with not specified "roi" parameter for DatasetItemEntity with
        # "roi" attribute
        roi_specified_dataset_item = DatasetItemEntity(
            media, annotation_scene,
            DatasetItemParameters().roi())
        roi_specified_dataset_item.roi_numpy()
        assert np.array_equal(roi_specified_dataset_item.roi_numpy(),
                              media.numpy[1:9, 2:14])