예제 #1
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
    """
    from skimage import measure
    from scipy import ndimage
    
    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
예제 #2
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)
예제 #3
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]
예제 #4
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)
예제 #5
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.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 = [
                PointLocation(row=rect.top, col=rect.left),
                PointLocation(row=rect.top, col=rect.right),
                PointLocation(row=rect.bottom, col=rect.right),
                PointLocation(row=rect.bottom, 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
예제 #6
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)
예제 #7
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))
예제 #8
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()))
예제 #9
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)
예제 #10
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()
예제 #11
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,)]
예제 #12
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)
예제 #13
0
 def from_json(cls, data):
     '''
     The function from_json convert Point from json format to Point class object.
     :param data: input Point in json format
     :return: Point class object
     '''
     labeler_login = data.get(LABELER_LOGIN, None)
     updated_at = data.get(UPDATED_AT, None)
     created_at = data.get(CREATED_AT, None)
     sly_id = data.get(ID, None)
     class_id = data.get(CLASS_ID, None)
     return cls.from_point_location(PointLocation.from_json(data),
                                    sly_id=sly_id,
                                    class_id=class_id,
                                    labeler_login=labeler_login,
                                    updated_at=updated_at,
                                    created_at=created_at)
예제 #14
0
def resize_origin_and_bitmap(origin: PointLocation, bitmap: np.ndarray,
                             in_size, out_size):
    '''
    Change PointLocation and resize numpy array to match a certain size
    :return: new PointLocation class object and numpy array
    '''
    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
예제 #15
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))
         ]
예제 #16
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)
예제 #17
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)