def test_crop(self): crop_rect = Rectangle(top=1, left=0, bottom=10, right=4) res_geoms = self.bitmap.crop(crop_rect) self.assertEqual(len(res_geoms), 1) res_bitmap = res_geoms[0] res_data = np.array([[[0.6, 0.7]]], dtype=np.float64) self.assertMultichannelBitmapEquals(res_bitmap, 1, 4, res_data)
def _rect_from_bounds(padding_config: dict, img_h: int, img_w: int) -> Rectangle: def get_padding_pixels(raw_side, dim_name): side_padding_config = padding_config.get(dim_name) if side_padding_config is None: padding_pixels = 0 elif side_padding_config.endswith('px'): padding_pixels = int(side_padding_config[:-len('px')]) elif side_padding_config.endswith('%'): padding_fraction = float(side_padding_config[:-len('%')]) padding_pixels = int(raw_side * padding_fraction / 100.0) else: raise ValueError( 'Unknown padding size format: {}. Expected absolute values as "5px" or relative as "5%"' .format(side_padding_config)) return padding_pixels def get_padded_side(raw_side, l_name, r_name): l_bound = -get_padding_pixels(raw_side, l_name) r_bound = raw_side + get_padding_pixels(raw_side, r_name) return l_bound, r_bound left, right = get_padded_side(img_w, 'left', 'right') top, bottom = get_padded_side(img_h, 'top', 'bottom') return Rectangle(top=top, left=left, bottom=bottom, right=right)
def _rectangle_from_cropping_or_padding_bounds(img_shape, crop_config, do_crop: bool): def get_crop_pixels(raw_side, dim_name): side_crop_config = crop_config.get(dim_name) if side_crop_config is None: crop_pixels = 0 elif side_crop_config.endswith(PX): crop_pixels = int(side_crop_config[:-len(PX)]) elif side_crop_config.endswith(PERCENT): padding_fraction = float(side_crop_config[:-len(PERCENT)]) crop_pixels = int(raw_side * padding_fraction / 100.0) else: raise ValueError( 'Unknown padding size format: {}. Expected absolute values as "5px" or relative as "5%"' .format(side_crop_config)) if not do_crop: crop_pixels *= -1 # Pad instead of crop. return crop_pixels # TODO more informative error message. return Rectangle( top=get_crop_pixels(img_shape[0], TOP), left=get_crop_pixels(img_shape[1], LEFT), bottom=img_shape[0] - get_crop_pixels(img_shape[0], BOTTOM) - 1, right=img_shape[1] - get_crop_pixels(img_shape[1], RIGHT) - 1)
def crop(img: np.ndarray, ann: Annotation, top_pad: int = 0, left_pad: int = 0, bottom_pad: int = 0, right_pad: int = 0) -> (np.ndarray, Annotation): """ Crops the given image array and annotation from all sides with the given values. Args: img: Input image array. ann: Input annotation. top_pad: The size in pixels of the piece of picture that will be cut from the top side. left_pad: The size in pixels of the piece of picture that will be cut from the left side. bottom_pad: The size in pixels of the piece of picture that will be cut from the bottom side. right_pad: The size in pixels of the piece of picture that will be cut from the right side. Returns: A tuple containing cropped image array and annotation. """ _validate_image_annotation_shape(img, ann) height, width = img.shape[:2] crop_rect = Rectangle(top_pad, left_pad, height - bottom_pad - 1, width - right_pad - 1) res_img = sly_image.crop(img, crop_rect) res_ann = ann.relative_crop(crop_rect) return res_img, res_ann
def to_bbox(self): exterior_np = self.exterior_np rows, cols = exterior_np[:, 0], exterior_np[:, 1] return Rectangle(top=round(min(rows).item()), left=round(min(cols).item()), bottom=round(max(rows).item()), right=round(max(cols).item()))
def detection_preds_to_sly_rects( idx_to_class, network_prediction: DetectionNetworkPrediction, img_shape, min_score_threshold, score_tag_meta) -> list: """ Converts network detection results to Supervisely Labels with Rectangle geometry. Args: idx_to_class: Dict matching predicted boxes with appropriate ObjClass. network_prediction: Network predictions packed into DetectionNetworkPrediction instance. img_shape: Size(height, width) of image that was used for inference. min_score_threshold: All detections with less scores will be dropped. score_tag_meta: TagMeta instance for score tags. Returns: A list containing labels with detection rectangles. """ labels = [] thr_mask = np.squeeze(network_prediction.scores) > min_score_threshold for box, class_id, score in zip( np.squeeze(network_prediction.boxes)[thr_mask], np.squeeze(network_prediction.classes)[thr_mask], np.squeeze(network_prediction.scores)[thr_mask]): xmin = round(float(box[1] * img_shape[1])) ymin = round(float(box[0] * img_shape[0])) xmax = round(float(box[3] * img_shape[1])) ymax = round(float(box[2] * img_shape[0])) rect = Rectangle(top=ymin, left=xmin, bottom=ymax, right=xmax) class_obj = idx_to_class[int(class_id)] label = Label(geometry=rect, obj_class=class_obj) score_tag = Tag(score_tag_meta, value=round(float(score), 4)) label = label.add_tag(score_tag) labels.append(label) return labels
def to_bbox(self): rows = [keypoint.location.row for keypoint in self.points] cols = [keypoint.location.col for keypoint in self.points] return Rectangle(top=round(min(rows)), left=round(min(cols)), bottom=round(max(rows)), right=round(max(cols)))
def geometry_to_bitmap(geometry, radius: int = 0, crop_image_shape: tuple = None) -> list: """ Args: geometry: Geometry type which implemented 'draw', 'translate' and 'to_bbox` methods radius: half of thickness of drawed vector elements crop_image_shape: if not None - crop bitmap object by this shape (HxW) Returns: Bitmap (geometry) object """ thickness = radius + 1 bbox = geometry.to_bbox() extended_bbox = Rectangle(top=bbox.top - radius, left=bbox.left - radius, bottom=bbox.bottom + radius, right=bbox.right + radius) bitmap_data = np.full(shape=(extended_bbox.height, extended_bbox.width), fill_value=False) geometry = geometry.translate(-extended_bbox.top, -extended_bbox.left) geometry.draw(bitmap_data, color=True, thickness=thickness) origin = PointLocation(extended_bbox.top, extended_bbox.left) bitmap_geometry = Bitmap(data=bitmap_data, origin=origin) if crop_image_shape is not None: crop_rect = Rectangle.from_size(*crop_image_shape) return bitmap_geometry.crop(crop_rect) return [bitmap_geometry]
def test_crop(self): crop_rect = Rectangle(25, 0, 200, 200) res_geoms = self.poly.crop(crop_rect) self.assertEqual(len(res_geoms), 1) crop = res_geoms[0] self.assertPolyEquals(crop, [[10, 25], [20, 25], [20, 30], [30, 30], [30, 25], [35, 25], [30, 40], [10, 30]], [])
def to_bbox(self): points_np = np.array([[self._points[p].row, self._points[p].col] for face in self._faces for p in face.tolist()]) rows, cols = points_np[:, 0], points_np[:, 1] return Rectangle(top=round(min(rows).item()), left=round(min(cols).item()), bottom=round(max(rows).item()), right=round(max(cols).item()))
def to_bbox(self): ''' The function to_bbox create Rectangle class object from current VectorGeometry class object :return: Rectangle class object ''' exterior_np = self.exterior_np rows, cols = exterior_np[:, 0], exterior_np[:, 1] return Rectangle(top=round(min(rows).item()), left=round(min(cols).item()), bottom=round(max(rows).item()), right=round(max(cols).item()))
def _find_mask_tight_bbox(raw_mask: np.ndarray) -> Rectangle: rows = list(np.any(raw_mask, axis=1).tolist()) # Redundant conversion to list to help PyCharm static analysis. cols = list(np.any(raw_mask, axis=0).tolist()) top_margin = rows.index(True) bottom_margin = rows[::-1].index(True) left_margin = cols.index(True) right_margin = cols[::-1].index(True) return Rectangle(top=top_margin, left=left_margin, bottom=len(rows) - 1 - bottom_margin, right=len(cols) - 1 - right_margin)
def to_bbox(self): ''' The function to_bbox create Rectangle class object from Point class object :return: Rectangle class object ''' return Rectangle(top=self.row, left=self.col, bottom=self.row, right=self.col)
def test_crop_by_border(self): exterior = [[10, 10], [40, 10], [40, 40], [10, 40]] interiors = [[[11, 11], [11, 20], [20, 11]], [[20, 20], [21, 20], [20, 21]]] poly = Polygon(exterior=row_col_list_to_points(exterior, flip_row_col_order=True), interior=[row_col_list_to_points(interior, flip_row_col_order=True) for interior in interiors]) crop_rect = Rectangle(0, 0, 100, 10) res_geoms = poly.crop(crop_rect) self.assertEqual(len(res_geoms), 0)
def to_bbox(self): ''' The function to_bbox create Rectangle class object from current Cuboid class object :return: Rectangle class object ''' points_np = np.array([[self._points[p].row, self._points[p].col] for face in self._faces for p in face.tolist()]) rows, cols = points_np[:, 0], points_np[:, 1] return Rectangle(top=round(min(rows).item()), left=round(min(cols).item()), bottom=round(max(rows).item()), right=round(max(cols).item()))
def _calc_inner_crop(self): """ Given a rectangle of self.src_imsize HxW that has been rotated by self.angle_degrees_ccw (in degrees), computes the location of the largest possible axis-aligned rectangle within the rotated rectangle. """ # TODO This needs significant streamlinig. a_ccw = np.deg2rad(self.angle_degrees_ccw) quadrant = math.floor(a_ccw / (math.pi / 2)) & 3 sign_alpha = a_ccw if ((quadrant & 1) == 0) else math.pi - a_ccw alpha = (sign_alpha % math.pi + math.pi) % math.pi h, w = self.src_imsize bb_w = w * math.cos(alpha) + h * math.sin(alpha) bb_h = w * math.sin(alpha) + h * math.cos(alpha) gamma = math.atan2(bb_w, bb_w) if (w < h) else math.atan2(bb_w, bb_w) delta = math.pi - alpha - gamma length = h if (w < h) else w d = length * math.cos(alpha) a = d * math.sin(alpha) / math.sin(delta) y = a * math.cos(gamma) x = y * math.tan(gamma) largest_w, largest_h = bb_w - 2 * x, bb_h - 2 * y new_h, new_w = self.new_imsize left = round((new_w - largest_w) * 0.5) right = round((new_w + largest_w) * 0.5) top = round((new_h - largest_h) * 0.5) bottom = round((new_h + largest_h) * 0.5) some_inner_crop = Rectangle(top, left, bottom, right) new_img_bbox = Rectangle(0, 0, self.new_imsize[0] - 1, self.new_imsize[1] - 1) self.inner_crop = new_img_bbox.crop(some_inner_crop)[0]
def test_complex_crop(self): # Crop generate GeometryCollection here exterior = [[0, 0], [0, 3], [5, 8], [5, 9], [5, 10], [0, 15], [10, 20], [0, 25], [20, 25], [20, 0]] interiors = [[[2, 2], [4, 4], [4, 2]]] poly = Polygon(exterior=row_col_list_to_points(exterior, flip_row_col_order=True), interior=[row_col_list_to_points(interior, flip_row_col_order=True) for interior in interiors]) crop_rect = Rectangle(0, 0, 30, 5) res_geoms = poly.crop(crop_rect) self.assertEqual(len(res_geoms), 3) self.assertPolyEquals(res_geoms[0], [[0, 0], [5, 0], [5, 8], [0, 3]], interiors)
def test_crop(self): # @TODO: mb delete compress while cropping crop_rect = Rectangle(0, 0, 8, 8) res_geoms = self.bitmap.crop(crop_rect) self.assertEqual(len(res_geoms), 1) res_bitmap = res_geoms[0] res_mask = np.array([[0, 0, 1, 0], [0, 1, 1, 1], [1, 0, 1, 0], [0, 0, 1, 0], [0, 0, 1, 0], [0, 0, 1, 0], [0, 0, 1, 0]], dtype=np.bool) self.assertBitmapEquals(res_bitmap, 0, 5, res_mask)
def add_background(ann: Annotation, bg_class: ObjClass) -> Annotation: """ Adds background rectangle (size equals to image size) to annotation. Args: ann: Input annotation. bg_class: ObjClass instance for background class label. Returns: Annotation with added background rectangle. """ img_size = ann.img_size rect = Rectangle(0, 0, img_size[0] - 1, img_size[1] - 1) new_label = Label(rect, bg_class) return ann.add_label(new_label)
def get_change_size(self, source_shape): source_rect = Rectangle.from_size(source_shape) window_rect = Rectangle.from_size(self.window_shape) if not source_rect.contains(window_rect): raise RuntimeError( 'Sliding window: window is larger than source (image).') hw_limit = tuple(source_shape[i] - self.window_shape[i] for i in (0, 1)) for wind_top in range(0, hw_limit[0] + self.stride[0], self.stride[0]): for wind_left in range(0, hw_limit[1] + self.stride[1], self.stride[1]): wind_bottom = min(wind_top + self.stride[0], source_shape[0]) wind_right = min(wind_left + self.stride[1], source_shape[1]) roi = Rectangle(wind_top, wind_left, wind_bottom - 1, wind_right - 1) if not source_rect.contains(roi): raise RuntimeError( 'Sliding window: result crop bounds are invalid.') yield roi
def test_draw(self): rect = Rectangle(1, 1, 3, 3) bitmap_1 = np.zeros((5, 5), dtype=np.uint8) rect.draw_contour(bitmap_1, 1) expected_mask_1 = np.array([[0, 0, 0, 0, 0], [0, 1, 1, 1, 0], [0, 1, 0, 1, 0], [0, 1, 1, 1, 0], [0, 0, 0, 0, 0]], dtype=np.uint8) self.assertTrue(np.array_equal(bitmap_1, expected_mask_1)) bitmap_2 = np.zeros((5, 5), dtype=np.uint8) rect.draw(bitmap_2, 1) expected_mask_2 = np.array([[0, 0, 0, 0, 0], [0, 1, 1, 1, 0], [0, 1, 1, 1, 0], [0, 1, 1, 1, 0], [0, 0, 0, 0, 0]], dtype=np.uint8) self.assertTrue(np.array_equal(bitmap_2, expected_mask_2))
def from_imgaug(cls, img, ia_boxes=None, ia_masks=None, index_to_class=None, meta: ProjectMeta = None): if ((ia_boxes is not None) or (ia_masks is not None)) and meta is None: raise ValueError("Project meta has to be provided") labels = [] if ia_boxes is not None: for ia_box in ia_boxes: obj_class = meta.get_obj_class(ia_box.label) if obj_class is None: raise KeyError( "Class {!r} not found in project meta".format( ia_box.label)) lbl = Label( Rectangle(top=ia_box.y1, left=ia_box.x1, bottom=ia_box.y2, right=ia_box.x2), obj_class) labels.append(lbl) if ia_masks is not None: if index_to_class is None: raise ValueError( "mapping from index to class name is needed to transform masks to SLY format" ) class_mask = ia_masks.get_arr() # mask = white_mask == 255 (unique, counts) = np.unique(class_mask, return_counts=True) for index, count in zip(unique, counts): if index == 0: continue mask = class_mask == index bitmap = Bitmap(data=mask[:, :, 0]) restore_class = meta.get_obj_class(index_to_class[index]) labels.append(Label(geometry=bitmap, obj_class=restore_class)) return cls(img_size=img.shape[:2], labels=labels)
def setUp(self): self._obj_class_gt = ObjClass(name='a', geometry_type=Rectangle) self._obj_class_pred = ObjClass(name='b', geometry_type=Rectangle) self._confidence_tag_meta = TagMeta(name='confidence', value_type=TagValueType.ANY_NUMBER) self._meta = ProjectMeta( obj_classes=ObjClassCollection([self._obj_class_gt, self._obj_class_pred]), tag_metas=TagMetaCollection([self._confidence_tag_meta])) # Will match self._pred_obj_1 self._gt_obj_1 = Label(obj_class=self._obj_class_gt, geometry=Rectangle(0, 0, 10, 10)) # Will match self._pred_obj_3 self._gt_obj_2 = Label(obj_class=self._obj_class_gt, geometry=Rectangle(13, 13, 15, 15)) # Will be a false negative self._gt_obj_3 = Label(obj_class=self._obj_class_gt, geometry=Rectangle(43, 43, 45, 45)) # Will match self._gt_obj_1 self._pred_obj_1 = Label( obj_class=self._obj_class_pred, geometry=Rectangle(0, 0, 9, 9), tags=TagCollection([Tag(meta=self._confidence_tag_meta, value=0.7)])) # Will be a false positive (self._pred_obj_1 has higher IoU). self._pred_obj_2 = Label( obj_class=self._obj_class_pred, geometry=Rectangle(0, 0, 8, 8), tags=TagCollection([Tag(meta=self._confidence_tag_meta, value=0.6)])) # Will match self._gt_obj_2 self._pred_obj_3 = Label( obj_class=self._obj_class_pred, geometry=Rectangle(13, 13, 15, 15), tags=TagCollection([Tag(meta=self._confidence_tag_meta, value=0.1)])) # More false positives. self._pred_objs_fp = [ Label(obj_class=self._obj_class_pred, geometry=Rectangle(20, 20, 30, 30), tags=TagCollection([Tag(meta=self._confidence_tag_meta, value=v / 100)])) for v in range(15, 85, 10)] self._metric_calculator = MAPMetric(class_mapping={'a': 'b'}, iou_threshold=0.5)
def test_crop(self): crop_rect = Rectangle(0, 0, 100, 100) res_geoms = self.point.crop(crop_rect) self.assertEqual(len(res_geoms), 1) res_point = res_geoms[0] self.assertPointEquals(res_point, self.point.row, self.point.col)
def to_bbox(self): return Rectangle(top=self.row, left=self.col, bottom=self.row, right=self.col)
def test_empty_crop(self): crop_rect = Rectangle(100, 100, 200, 200) res_geoms = self.poly.crop(crop_rect) self.assertEqual(len(res_geoms), 0)
def test_empty_crop(self): crop_rect = Rectangle(top=0, left=0, bottom=10, right=3) res_geoms = self.bitmap.crop(crop_rect) self.assertEqual(len(res_geoms), 0)
def test_empty_crop(self): crop_rect = Rectangle(0, 0, 4, 4) res_geoms = self.bitmap.crop(crop_rect) self.assertEqual(len(res_geoms), 0)
def test_relative_crop(self): crop_rect = Rectangle(5, 5, 100, 100) res_geoms = self.point.relative_crop(crop_rect) self.assertEqual(len(res_geoms), 1) res_point = res_geoms[0] self.assertPointEquals(res_point, 5, 0)
def test_relative_crop(self): crop_rect = Rectangle(25, 0, 200, 200) res_geoms = self.poly.relative_crop(crop_rect) self.assertEqual(len(res_geoms), 1) crop = res_geoms[0] self.assertPolyEquals(crop, [[10, 0], [20, 0], [20, 5], [30, 5], [30, 0], [35, 0], [30, 15], [10, 5]], [])