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))
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)
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)
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
def not_inscribed_ellipse() -> Ellipse: return Ellipse(x1=0.0, y1=0.0, x2=0.01, y2=0.01)
def ellipse(self) -> Ellipse: return Ellipse(x1=0.4, y1=0.1, x2=0.9, y2=0.8, labels=self.generate_labels_list())
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())
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])