예제 #1
0
 def corners(self):
     return [
         PointLocation(row=self.top, col=self.left),
         PointLocation(row=self.top, col=self.right),
         PointLocation(row=self.bottom, col=self.right),
         PointLocation(row=self.bottom, col=self.left)
     ]
예제 #2
0
 def corners(self):
     '''
     :return: list of coners(PointLocation class objects) of Rectangle
     '''
     return [
         PointLocation(row=self.top, col=self.left),
         PointLocation(row=self.top, col=self.right),
         PointLocation(row=self.bottom, col=self.right),
         PointLocation(row=self.bottom, col=self.left)
     ]
예제 #3
0
    def crop(self, rect):
        '''
        Crop the current Polygon with a given rectangle, if polygon cat't be cropped it generate exception error
        :param rect: Rectangle class object
        :return: list of Poligon class objects
        '''
        from supervisely_lib.geometry.point_location import PointLocation
        try:
            points = [
                PointLocation(row=rect.top, col=rect.left),
                PointLocation(row=rect.top, col=rect.right + 1),
                PointLocation(row=rect.bottom + 1, col=rect.right + 1),
                PointLocation(row=rect.bottom + 1, col=rect.left)
            ]
            #points = rect.corners # old implementation with 1 pixel error (right bottom) #@TODO: investigate here (critical issue)

            clipping_window_shpl = ShapelyPolygon(
                points_to_row_col_list(points))
            self_shpl = ShapelyPolygon(self.exterior_np,
                                       holes=self.interior_np)
            intersections_shpl = self_shpl.buffer(0).intersection(
                clipping_window_shpl)
            mapping_shpl = mapping(intersections_shpl)
        except Exception:
            logger.warn('Polygon cropping exception, shapely.', exc_info=True)
            # raise
            # if polygon is invalid, just print warning and skip it
            # @TODO: need more investigation here
            return []

        intersections = shapely_figure_to_coords_list(mapping_shpl)

        # Check for bad cropping cases (e.g. empty points list)
        out_polygons = []
        for intersection in intersections:
            if isinstance(intersection,
                          list) and len(intersection) > 0 and len(
                              intersection[0]) >= 3:
                exterior = row_col_list_to_points(intersection[0],
                                                  do_round=True)
                interiors = []
                for interior_contour in intersection[1:]:
                    if len(interior_contour) > 2:
                        interiors.append(
                            row_col_list_to_points(interior_contour,
                                                   do_round=True))
                out_polygons.append(Polygon(exterior, interiors))
        return out_polygons
예제 #4
0
def extract_labels_from_mask(mask: np.ndarray, color_id_to_obj_class: collections.Mapping) -> list:
    """
    Extract multiclass instances from grayscale mask and save it to labels list.
    Args:
        mask: multiclass grayscale mask
        color_id_to_obj_class: dict of objects classes assigned to color id (e.g. {1: ObjClass('cat), ...})
    Returns:
        list of labels with bitmap geometry
    """
    zero_offset = 1 if 0 in color_id_to_obj_class else 0
    if zero_offset > 0:
        mask = mask + zero_offset

    labeled, labels_count = measure.label(mask, connectivity=1, return_num=True)
    objects_slices = ndimage.find_objects(labeled)
    labels = []

    for object_index, slices in enumerate(objects_slices, start=1):
        crop = mask[slices]
        sub_mask = crop * (labeled[slices] == object_index).astype(np.int)

        class_index = np.max(sub_mask) - zero_offset

        if class_index in color_id_to_obj_class:
            bitmap = Bitmap(data=sub_mask.astype(np.bool), origin=PointLocation(slices[0].start, slices[1].start))
            label = Label(geometry=bitmap, obj_class=color_id_to_obj_class.get(class_index))
            labels.append(label)
    return labels
예제 #5
0
    def test_to_contours(self):
        bitmap = Bitmap(data=np.array([[1, 1, 0, 1, 1, 1],
                                       [1, 1, 0, 1, 0, 1],
                                       [0, 0, 0, 1, 1, 1],
                                       [1, 0, 0, 1, 0, 1],
                                       [1, 0, 0, 1, 1, 1],
                                       [1, 0, 0, 0, 0, 0],
                                       [1, 0, 0, 1, 1, 1]], dtype=np.bool),
                        origin=PointLocation(10, 110))
        polygons = bitmap.to_contours()

        exteriors_points = [np.array([[10, 113], [14, 113], [14, 115], [10, 115]]),
                            np.array([[10, 110], [11, 110], [11, 111], [10, 111]])]

        interiors_points = [[],
                            [np.array([[13, 113], [12, 114], [13, 115], [14, 114]]),
                             np.array([[11, 113], [10, 114], [11, 115], [12, 114]])],
                           []]

        self.assertEqual(len(polygons), 2)
        for polygon, target_exterior, target_interiors in zip(polygons, exteriors_points, interiors_points):
            self.assertTrue(np.equal(polygon.exterior_np, target_exterior).all())
            self.assertTrue(all(np.equal(p_inter, t_inter)
                                for p_inter, t_inter in zip(polygon.interior_np, target_interiors)))
            json.dumps(polygon.to_json())
            self.assertIsInstance(polygon, Polygon)
예제 #6
0
    def __init__(self,
                 data: np.ndarray,
                 origin: PointLocation = None,
                 expected_data_dims=None):
        """
        :param origin: PointLocation
        :param data: np.ndarray
        """
        if origin is None:
            origin = PointLocation(row=0, col=0)

        if not isinstance(origin, PointLocation):
            raise TypeError(
                'BitmapBase "origin" argument must be "PointLocation" object!')

        if not isinstance(data, np.ndarray):
            raise TypeError(
                'BitmapBase "data" argument must be numpy array object!')

        data_dims = len(data.shape)
        if expected_data_dims is not None and data_dims != expected_data_dims:
            raise ValueError(
                'BitmapBase "data" argument must be a {}-dimensional numpy array. '
                + 'Instead got {} dimensions'.format(expected_data_dims,
                                                     data_dims))

        self._origin = origin.clone()
        self._data = data.copy()
예제 #7
0
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]
예제 #8
0
    def from_json(cls, json_data):
        '''
        The function from_json convert bitmap from json format to Bitmap class object.
        :param json_data: input bitmap in json format
        :return: Bitmap class object
        '''
        json_root_key = cls._impl_json_class_name()
        if json_root_key not in json_data:
            raise ValueError(
                'Data must contain {} field to create MultichannelBitmap object.'
                .format(json_root_key))

        if ORIGIN not in json_data[json_root_key] or DATA not in json_data[
                json_root_key]:
            raise ValueError(
                '{} field must contain {} and {} fields to create MultichannelBitmap object.'
                .format(json_root_key, ORIGIN, DATA))

        col, row = json_data[json_root_key][ORIGIN]
        data = cls.base64_2_data(json_data[json_root_key][DATA])

        labeler_login = json_data.get(LABELER_LOGIN, None)
        updated_at = json_data.get(UPDATED_AT, None)
        created_at = json_data.get(CREATED_AT, None)
        sly_id = json_data.get(ID, None)
        class_id = json_data.get(CLASS_ID, None)
        return cls(data=data,
                   origin=PointLocation(row=row, col=col),
                   sly_id=sly_id,
                   class_id=class_id,
                   labeler_login=labeler_login,
                   updated_at=updated_at,
                   created_at=created_at)
예제 #9
0
 def from_json(cls, data):
     '''
     The function from_json convert Node from json format to Node class object.
     :param data: input node in json format
     :return: Node class object
     '''
     # TODO validations
     loc = data[LOC]
     return cls(location=PointLocation(row=loc[1], col=loc[0]), disabled=data.get(DISABLED, False))
예제 #10
0
 def crop(self, rect):
     maybe_cropped_area = self.to_bbox().crop(rect)
     if len(maybe_cropped_area) == 0:
         return []
     else:
         [cropped_area] = maybe_cropped_area
         cropped_origin = PointLocation(row=cropped_area.top, col=cropped_area.left)
         cropped_area_in_data = cropped_area.translate(drow=-self._origin.row, dcol=-self.origin.col)
         return [MultichannelBitmap(data=cropped_area_in_data.get_cropped_numpy_slice(self._data),
                                    origin=cropped_origin,)]
예제 #11
0
 def transform_point(self, point):
     '''
     The function transform_point calculates new parameters of point after rotating
     :param point: PointLocation class object
     :return: PointLocation class object
     '''
     point_np_uniform = np.array([point.row, point.col, 1])
     transformed_np = self.affine_matrix.dot(point_np_uniform)
     # Unwrap numpy types so that round() produces integer results.
     return PointLocation(row=round(transformed_np[0].item()), col=round(transformed_np[1].item()))
예제 #12
0
 def crop(self, rect):
     maybe_cropped_bbox = self.to_bbox().crop(rect)
     if len(maybe_cropped_bbox) == 0:
         return []
     else:
         [cropped_bbox] = maybe_cropped_bbox
         cropped_bbox_relative = cropped_bbox.translate(drow=-self.origin.row, dcol=-self.origin.col)
         cropped_mask = cropped_bbox_relative.get_cropped_numpy_slice(self._data)
         if not np.any(cropped_mask):
             return []
         return [Bitmap(data=cropped_mask, origin=PointLocation(row=cropped_bbox.top, col=cropped_bbox.left))]
예제 #13
0
 def setUp(self):
     self.origin = PointLocation(0, 4)
     self.mask = np.array([[0, 0, 0, 1, 0, 0, 0],
                           [0, 0, 1, 1, 1, 0, 0],
                           [0, 1, 0, 1, 0, 1, 0],
                           [0, 0, 0, 1, 0, 0, 0],
                           [0, 0, 0, 1, 0, 0, 0],
                           [0, 0, 0, 1, 0, 0, 0],
                           [0, 0, 0, 1, 0, 0, 0]], dtype=np.bool)
     self.mask_no_margins = self.mask[:, 1:-1]
     self.bitmap = Bitmap(data=self.mask, origin=self.origin)
예제 #14
0
 def rotate(self, rotator):
     # Render the bitmap within the full image canvas and rotate the whole canvas.
     full_img_data = np.zeros(rotator.src_imsize + self.data.shape[2:], dtype=self.data.dtype)
     full_img_data[
         self.origin.row:(self.origin.row + self.data.shape[0]),
         self.origin.col:(self.origin.col + self.data.shape[1]), ...] = self.data[:, :, ...]
     rotated_full_data = rotator.rotate_img(full_img_data, use_inter_nearest=True)
     # Rotate the bounding box to find out the bounding box of the rotated bitmap within the full image.
     rotated_bbox = self.to_bbox().rotate(rotator)
     rotated_origin = PointLocation(row=rotated_bbox.top, col=rotated_bbox.left)
     return MultichannelBitmap(data=rotated_bbox.get_cropped_numpy_slice(rotated_full_data), origin=rotated_origin)
예제 #15
0
 def flipud(self, img_size):
     '''
     The function fliplr flip the current Bitmap object in vertical and return the copy of the
     current Bitmap object
     :param img_size: size of the image
     :return: Bitmap class object with new data(numpy array) and PointLocation
     '''
     flipped_mask = np.flip(self.data, axis=0)
     flipped_origin = PointLocation(
         row=(img_size[0] - flipped_mask.shape[0] - self.origin.row),
         col=self.origin.col)
     return self.__class__(data=flipped_mask, origin=flipped_origin)
예제 #16
0
    def __init__(self, top, left, bottom, right,
                 sly_id=None, class_id=None, labeler_login=None, updated_at=None, created_at=None):
        """
        Float-type coordinates will be deprecated soon.
        Args:
            top: minimal vertical value
            left: minimal horizontal value
            bottom: maximal vertical value
            right: maximal horizontal value
        """

        if top > bottom:
            raise ValueError('Rectangle "top" argument must have less or equal value then "bottom"!')

        if left > right:
            raise ValueError('Rectangle "left" argument must have less or equal value then "right"!')

        super().__init__(sly_id=sly_id, class_id=class_id, labeler_login=labeler_login, updated_at=updated_at,
                         created_at=created_at)

        self._points = [PointLocation(row=top, col=left), PointLocation(row=bottom, col=right)]
예제 #17
0
    def from_json(cls, json_data):
        json_root_key = cls._impl_json_class_name()
        if json_root_key not in json_data:
            raise ValueError(
                'Data must contain {} field to create MultichannelBitmap object.'.format(json_root_key))

        if ORIGIN not in json_data[json_root_key] or DATA not in json_data[json_root_key]:
            raise ValueError('{} field must contain {} and {} fields to create MultichannelBitmap object.'.format(
                json_root_key, ORIGIN, DATA))

        col, row = json_data[json_root_key][ORIGIN]
        data = cls.base64_2_data(json_data[json_root_key][DATA])
        return cls(data=data, origin=PointLocation(row=row, col=col))
예제 #18
0
    def __init__(self, top, left, bottom, right):
        """
        Float-type coordinates will be deprecated soon.
        Args:
            top: minimal vertical value
            left: minimal horizontal value
            bottom: maximal vertical value
            right: maximal horizontal value
        """
        if top > bottom:
            raise ValueError(
                'Rectangle "top" argument must have less or equal value then "bottom"!'
            )

        if left > right:
            raise ValueError(
                'Rectangle "left" argument must have less or equal value then "right"!'
            )

        self._points = [
            PointLocation(row=top, col=left),
            PointLocation(row=bottom, col=right)
        ]
예제 #19
0
def resize_origin_and_bitmap(origin: PointLocation, bitmap: np.ndarray, in_size, out_size):
    new_size = restore_proportional_size(in_size=in_size, out_size=out_size)

    row_scale = new_size[0] / in_size[0]
    col_scale = new_size[1] / in_size[1]

    # TODO: Double check (+restore_proportional_size) or not? bitmap.shape and in_size are equal?
    # Make sure the resulting size has at least one pixel in every direction (i.e. limit the shrinkage to avoid having
    # empty bitmaps as a result).
    scaled_rows = max(round(bitmap.shape[0] * row_scale), 1)
    scaled_cols = max(round(bitmap.shape[1] * col_scale), 1)

    scaled_origin = PointLocation(row=round(origin.row * row_scale), col=round(origin.col * col_scale))
    scaled_bitmap = resize_inter_nearest(bitmap, (scaled_rows, scaled_cols))
    return scaled_origin, scaled_bitmap
예제 #20
0
 def rotate(self, rotator):
     '''
     The function rotate render the bitmap within the full image canvas and rotate the whole canvas
     with a given rotator (ImageRotator class object contain size of image and angle to rotate)
     :param rotator: ImageRotator class object
     :return: MultichannelBitmap class object
     '''
     full_img_data = np.zeros(rotator.src_imsize + self.data.shape[2:],
                              dtype=self.data.dtype)
     full_img_data[self.origin.row:(self.origin.row + self.data.shape[0]),
                   self.origin.col:(self.origin.col + self.data.shape[1]),
                   ...] = self.data[:, :, ...]
     rotated_full_data = rotator.rotate_img(full_img_data,
                                            use_inter_nearest=True)
     # Rotate the bounding box to find out the bounding box of the rotated bitmap within the full image.
     rotated_bbox = self.to_bbox().rotate(rotator)
     rotated_origin = PointLocation(row=rotated_bbox.top,
                                    col=rotated_bbox.left)
     return MultichannelBitmap(
         data=rotated_bbox.get_cropped_numpy_slice(rotated_full_data),
         origin=rotated_origin)
예제 #21
0
 def crop(self, rect):
     '''
     Crop the current MultichannelBitmap object with a given rectangle
     :param rect: Rectangle class object
     :return: MultichannelBitmap class object
     '''
     maybe_cropped_area = self.to_bbox().crop(rect)
     if len(maybe_cropped_area) == 0:
         return []
     else:
         [cropped_area] = maybe_cropped_area
         cropped_origin = PointLocation(row=cropped_area.top,
                                        col=cropped_area.left)
         cropped_area_in_data = cropped_area.translate(
             drow=-self._origin.row, dcol=-self.origin.col)
         return [
             MultichannelBitmap(
                 data=cropped_area_in_data.get_cropped_numpy_slice(
                     self._data),
                 origin=cropped_origin,
             )
         ]
예제 #22
0
 def crop(self, rect):
     '''
     Crop the current Bitmap object with a given rectangle
     :param rect: Rectangle class object
     :return: Bitmap class object
     '''
     maybe_cropped_bbox = self.to_bbox().crop(rect)
     if len(maybe_cropped_bbox) == 0:
         return []
     else:
         [cropped_bbox] = maybe_cropped_bbox
         cropped_bbox_relative = cropped_bbox.translate(
             drow=-self.origin.row, dcol=-self.origin.col)
         cropped_mask = cropped_bbox_relative.get_cropped_numpy_slice(
             self._data)
         if not np.any(cropped_mask):
             return []
         return [
             Bitmap(data=cropped_mask,
                    origin=PointLocation(row=cropped_bbox.top,
                                         col=cropped_bbox.left))
         ]
예제 #23
0
    def __init__(self,
                 data: np.ndarray,
                 origin: PointLocation = None,
                 expected_data_dims=None,
                 sly_id=None,
                 class_id=None,
                 labeler_login=None,
                 updated_at=None,
                 created_at=None):
        super().__init__(sly_id=sly_id,
                         class_id=class_id,
                         labeler_login=labeler_login,
                         updated_at=updated_at,
                         created_at=created_at)
        """
        :param origin: PointLocation class object
        :param data: np.ndarray
        """
        if origin is None:
            origin = PointLocation(row=0, col=0)

        if not isinstance(origin, PointLocation):
            raise TypeError(
                'BitmapBase "origin" argument must be "PointLocation" object!')

        if not isinstance(data, np.ndarray):
            raise TypeError(
                'BitmapBase "data" argument must be numpy array object!')

        data_dims = len(data.shape)
        if expected_data_dims is not None and data_dims != expected_data_dims:
            raise ValueError(
                'BitmapBase "data" argument must be a {}-dimensional numpy array. '
                'Instead got {} dimensions'.format(expected_data_dims,
                                                   data_dims))

        self._origin = origin.clone()
        self._data = data.copy()
예제 #24
0
 def flipud(self, img_size):
     flipped_mask = np.flip(self.data, axis=0)
     flipped_origin = PointLocation(
         row=(img_size[0] - flipped_mask.shape[0] - self.origin.row),
         col=self.origin.col)
     return self.__class__(data=flipped_mask, origin=flipped_origin)
예제 #25
0
 def fliplr(self, img_size):
     flipped_mask = np.flip(self.data, axis=1)
     flipped_origin = PointLocation(
         row=self.origin.row,
         col=(img_size[1] - flipped_mask.shape[1] - self.origin.col))
     return self.__class__(data=flipped_mask, origin=flipped_origin)
예제 #26
0
 def center(self):
     return PointLocation(row=(self.top + self.bottom) // 2,
                          col=(self.left + self.right) // 2)
예제 #27
0
 def point_location(self) -> PointLocation:
     return PointLocation(row=self.row, col=self.col)
예제 #28
0
 def setUp(self):
     self.origin = PointLocation(row=0, col=4)
     self.data = np.array([[[0.0, 0.1], [0.2, 0.3], [0.4, 0.5]],
                           [[0.6, 0.7], [0.8, 0.9], [1.0, 1.1]]], dtype=np.float64)
     self.bitmap = MultichannelBitmap(data=self.data, origin=self.origin)
예제 #29
0
 def center(self):
     '''
     :return: center of rectangle(PointLocation class obgect)
     '''
     return PointLocation(row=(self.top + self.bottom) // 2, col=(self.left + self.right) // 2)
예제 #30
0
 def point_location(self) -> PointLocation:
     '''
     The function point_location create PointLocation class object from Point class object
     :return: PointLocation class object
     '''
     return PointLocation(row=self.row, col=self.col)