Exemple #1
0
def paintCorners(painter, corners, rect):
    # FIXME: This only works right for orthogonal maps right now
    hx = rect.width() / 2
    hy = rect.height() / 2
    x = corners
    if x==Corners.TopLeft:
        painter.drawPie(rect.translated(-hx, -hy), -90 * 16, 90 * 16)
    elif x==Corners.TopRight:
        painter.drawPie(rect.translated(hx, -hy), 180 * 16, 90 * 16)
    elif x==Corners.TopRight | Corners.TopLeft:
        painter.drawRect(rect.x(), rect.y(), rect.width(), hy)
    elif x==Corners.BottomLeft:
        painter.drawPie(rect.translated(-hx, hy), 0, 90 * 16)
    elif x==Corners.BottomLeft | Corners.TopLeft:
        painter.drawRect(rect.x(), rect.y(), hx, rect.height())
    elif x==Corners.BottomLeft | Corners.TopRight:
        painter.drawPie(rect.translated(-hx, hy), 0, 90 * 16)
        painter.drawPie(rect.translated(hx, -hy), 180 * 16, 90 * 16)
    elif x==Corners.BottomLeft | Corners.TopRight | Corners.TopLeft:
        fill = QPainterPath()
        ellipse = QPainterPath()
        fill.addRect(rect)
        ellipse.addEllipse(rect.translated(hx, hy))
        painter.drawPath(fill.subtracted(ellipse))
    elif x==Corners.BottomRight:
        painter.drawPie(rect.translated(hx, hy), 90 * 16, 90 * 16)
    elif x==Corners.BottomRight | Corners.TopLeft:
        painter.drawPie(rect.translated(-hx, -hy), -90 * 16, 90 * 16)
        painter.drawPie(rect.translated(hx, hy), 90 * 16, 90 * 16)
    elif x==Corners.BottomRight | Corners.TopRight:
        painter.drawRect(rect.x() + hx, rect.y(), hx, rect.height())
    elif x==Corners.BottomRight | Corners.TopRight | Corners.TopLeft:
        fill = QPainterPath()
        ellipse = QPainterPath()
        fill.addRect(rect)
        ellipse.addEllipse(rect.translated(-hx, hy))
        painter.drawPath(fill.subtracted(ellipse))
    elif x==Corners.BottomRight | Corners.BottomLeft:
        painter.drawRect(rect.x(), rect.y() + hy, rect.width(), hy)
    elif x==Corners.BottomRight | Corners.BottomLeft | Corners.TopLeft:
        fill = QPainterPath()
        ellipse = QPainterPath()
        fill.addRect(rect)
        ellipse.addEllipse(rect.translated(hx, -hy))
        painter.drawPath(fill.subtracted(ellipse))
    elif x==Corners.BottomRight | Corners.BottomLeft | Corners.TopRight:
        fill = QPainterPath()
        ellipse = QPainterPath()
        fill.addRect(rect)
        ellipse.addEllipse(rect.translated(-hx, -hy))
        painter.drawPath(fill.subtracted(ellipse))
    elif x==Corners.BottomRight | Corners.BottomLeft | Corners.TopRight | Corners.TopLeft:
        painter.drawRect(rect)
Exemple #2
0
def paintCorners(painter, corners, rect):
    # FIXME: This only works right for orthogonal maps right now
    hx = rect.width() / 2
    hy = rect.height() / 2
    x = corners
    if x == Corners.TopLeft:
        painter.drawPie(rect.translated(-hx, -hy), -90 * 16, 90 * 16)
    elif x == Corners.TopRight:
        painter.drawPie(rect.translated(hx, -hy), 180 * 16, 90 * 16)
    elif x == Corners.TopRight | Corners.TopLeft:
        painter.drawRect(rect.x(), rect.y(), rect.width(), hy)
    elif x == Corners.BottomLeft:
        painter.drawPie(rect.translated(-hx, hy), 0, 90 * 16)
    elif x == Corners.BottomLeft | Corners.TopLeft:
        painter.drawRect(rect.x(), rect.y(), hx, rect.height())
    elif x == Corners.BottomLeft | Corners.TopRight:
        painter.drawPie(rect.translated(-hx, hy), 0, 90 * 16)
        painter.drawPie(rect.translated(hx, -hy), 180 * 16, 90 * 16)
    elif x == Corners.BottomLeft | Corners.TopRight | Corners.TopLeft:
        fill = QPainterPath()
        ellipse = QPainterPath()
        fill.addRect(rect)
        ellipse.addEllipse(rect.translated(hx, hy))
        painter.drawPath(fill.subtracted(ellipse))
    elif x == Corners.BottomRight:
        painter.drawPie(rect.translated(hx, hy), 90 * 16, 90 * 16)
    elif x == Corners.BottomRight | Corners.TopLeft:
        painter.drawPie(rect.translated(-hx, -hy), -90 * 16, 90 * 16)
        painter.drawPie(rect.translated(hx, hy), 90 * 16, 90 * 16)
    elif x == Corners.BottomRight | Corners.TopRight:
        painter.drawRect(rect.x() + hx, rect.y(), hx, rect.height())
    elif x == Corners.BottomRight | Corners.TopRight | Corners.TopLeft:
        fill = QPainterPath()
        ellipse = QPainterPath()
        fill.addRect(rect)
        ellipse.addEllipse(rect.translated(-hx, hy))
        painter.drawPath(fill.subtracted(ellipse))
    elif x == Corners.BottomRight | Corners.BottomLeft:
        painter.drawRect(rect.x(), rect.y() + hy, rect.width(), hy)
    elif x == Corners.BottomRight | Corners.BottomLeft | Corners.TopLeft:
        fill = QPainterPath()
        ellipse = QPainterPath()
        fill.addRect(rect)
        ellipse.addEllipse(rect.translated(hx, -hy))
        painter.drawPath(fill.subtracted(ellipse))
    elif x == Corners.BottomRight | Corners.BottomLeft | Corners.TopRight:
        fill = QPainterPath()
        ellipse = QPainterPath()
        fill.addRect(rect)
        ellipse.addEllipse(rect.translated(-hx, -hy))
        painter.drawPath(fill.subtracted(ellipse))
    elif x == Corners.BottomRight | Corners.BottomLeft | Corners.TopRight | Corners.TopLeft:
        painter.drawRect(rect)
Exemple #3
0
    def paintEvent(self, event: QPaintEvent):
        """
        重写绘图事件
        :param event:
        :return:
        """
        try:
            backPath = QPainterPath()
            backPath.addRect(0, 0, self.width(), self.height())
            fillPath = QPainterPath()
            if hasattr(self, "startPoint") and hasattr(self, "endPoint"):
                movePath = QPainterPath()
                movePath.addRect(QRectF(self.startPoint, self.endPoint))
                fillPath = backPath.subtracted(movePath)
            else:
                fillPath = backPath
            # 创建绘图设备
            painter = QPainter(self)
            # 绘制背景图
            painter.drawImage(QPoint(0, 0), self.backImg)
            painter.setPen(QPen(QColor(87, 170, 255), 5, Qt.SolidLine))
            # painter.drawPath(fillPath)
            # 填充非选择区域
            painter.fillPath(fillPath, QColor(0, 0, 0, 100))

        except Exception as e:
            traceback.print_exc()
Exemple #4
0
 def _dbgRingShape(self, p):
     r, R, = self.boundings()
     opath = QPainterPath()
     opath.setFillRule(Qt.WindingFill)
     opath.addEllipse(-R, -R, 2*R, 2*R)
     ipath = QPainterPath()
     ipath.setFillRule(Qt.WindingFill)
     ipath.addEllipse(-r, -r, 2*r, 2*r)
     p.fillPath(opath.subtracted(ipath), QColor(255, 255, 0, 50))
     p.strokePath(opath.simplified(), QPen(Qt.black, 3))
     p.strokePath(ipath, QPen(Qt.black, 1))
Exemple #5
0
class Blob(object):
    """
    Blob data. A blob is a group of pixels.
    It can be tagged with the class and other information.
    It is stored as an outer contour (the border) plus a list of inner contours (holes).
    """
    def __init__(self, region, offset_x, offset_y, id):
        self.version = 0
        self.id = int(id)

        self.area = 0.0
        self.surface_area = 0.0
        self.perimeter = 0.0
        self.centroid = np.zeros((2))
        self.bbox = np.zeros((4))

        # placeholder; empty contour
        self.contour = np.zeros((2, 2))
        self.inner_contours = []
        self.qpath = None
        self.qpath_gitem = None

        self.instance_name = "noname"
        self.blob_name = "noname"

        if region:

            # extract properties

            self.centroid = np.array(region.centroid)
            cy = self.centroid[0]
            cx = self.centroid[1]
            self.centroid[0] = cx + offset_x
            self.centroid[1] = cy + offset_y

            # Bounding box (min_row, min_col, max_row, max_col).
            # Pixels belonging to the bounding box are in the half-open
            # interval [min_row, max_row) and [min_col, max_col).
            self.bbox = np.array(region.bbox)

            width = self.bbox[3] - self.bbox[1]
            height = self.bbox[2] - self.bbox[0]

            # BBOX ->  TOP, LEFT, WIDTH, HEIGHT
            self.bbox[0] = self.bbox[0] + offset_y
            self.bbox[1] = self.bbox[1] + offset_x
            self.bbox[2] = width
            self.bbox[3] = height

            # QPainterPath associated with the contours
            self.qpath = None

            # QGraphicsItem associated with the QPainterPath
            self.qpath_gitem = None

            # to extract the contour we use the mask cropped according to the bbox
            input_mask = region.image.astype(int)
            self.contour = np.zeros((2, 2))
            self.inner_contours = []
            self.updateUsingMask(self.bbox, input_mask)

            # a string with a progressive number to identify the instance
            self.instance_name = "coral" + str(id)

            # a string with a number to identify the blob plus its centroid
            xc = self.centroid[0]
            yc = self.centroid[1]
            self.blob_name = "c-{:d}-{:.1f}x-{:.1f}y".format(self.id, xc, yc)

        # deep extreme points (for fine-tuning)
        self.deep_extreme_points = np.zeros((4, 2))

        # name of the class
        self.class_name = "Empty"

        # color of the class
        self.class_color = [128, 128, 128]

        self.genet = None

        # note about the coral, i.e. damage type
        self.note = ""

        # QImage corresponding to the current mask
        self.qimg_mask = None

        # QPixmap associated with the mask (for pixel-level editing operations)
        self.pxmap_mask = None

        # QGraphicsItem associated with the pixmap mask
        self.pxmap_mask_gitem = None

        # membership group (if any)
        self.group = None

    def copy(self):
        blob = Blob(None, 0, 0, 0)

        blob.area = self.area
        blob.surface_area = self.surface_area
        blob.perimeter = self.perimeter
        blob.centroid = self.centroid
        blob.bbox = self.bbox

        blob.contour = self.contour.copy()
        for inner in self.inner_contours:
            blob.inner_contours.append(inner.copy())
        blob.qpath_gitem = None
        blob.qpath = None

        blob.instance_name = blob.instance_name
        blob.blob_name = self.blob_name
        blob.id = self.id
        blob.version = self.version + 1

        blob.genet = self.genet
        blob.class_name = self.class_name

        blob.deep_extreme_points = self.deep_extreme_points

        self.note = ""
        self.qimg_mask = None
        self.pxmap_mask = None
        self.pxmap_mask_gitem = None

        return blob

    def __deepcopy__(self, memo):
        #avoid recursion!
        deepcopy_method = self.__deepcopy__
        self.__deepcopy__ = None
        #save and later restore qobjects
        path = self.qpath
        pathitem = self.qpath_gitem
        #no deep copy for qobjects
        self.qpath = None
        self.qpath_gitem = None

        blob = copy.deepcopy(self)
        blob.contour = self.contour.copy()
        blob.inner_contours.clear()
        for inner in self.inner_contours:
            blob.inner_contours.append(inner.copy())

        blob.qpath = None
        blob.qpath_gitem = None
        self.qpath = path
        self.qpath_gitem = pathitem
        #restore deepcopy (also to the newly created Blob!
        blob.__deepcopy__ = self.__deepcopy__ = deepcopy_method
        return blob

    def setId(self, id):
        # a string with a number to identify the blob plus its centroid
        xc = self.centroid[0]
        yc = self.centroid[1]
        self.id = id
        self.blob_name = "c-{:d}-{:.1f}x-{:.1f}y".format(self.id, xc, yc)

    def getMask(self):
        """
        It creates the mask from the contour and returns it.
        """

        r = self.bbox[3]
        c = self.bbox[2]
        origin = np.array([int(self.bbox[1]), int(self.bbox[0])])

        mask = np.zeros((r, c), np.uint8)
        points = self.contour.round().astype(int)
        fillPoly(mask, pts=[points - origin], color=(1))

        # holes
        for inner_contour in self.inner_contours:
            points = inner_contour.round().astype(int)
            fillPoly(mask, pts=[points - origin], color=(0, 0, 0))

        return mask

    def updateUsingMask(self, bbox, mask):
        self.createContourFromMask(mask, bbox)
        self.calculatePerimeter()
        self.calculateCentroid(mask, bbox)
        self.calculateArea(mask)
        self.bbox = Mask.pointsBox(self.contour, 4)

    def createFromClosedCurve(self, lines):
        """
        It creates a blob starting from a closed curve. If the curve is not closed False is returned.
        If the curve intersect itself many times the first segmented region is created.
        """
        points = self.lineToPoints(lines)
        box = Mask.pointsBox(points, 4)

        (mask, box) = Mask.jointMask(box, box)
        Mask.paintPoints(mask, box, points, 1)
        before = np.count_nonzero(mask)
        mask = ndi.binary_fill_holes(mask)
        after = np.count_nonzero(mask)

        if before == after:
            return False

        selem = np.array([[0, 0, 0], [0, 0, 1], [0, 1, 0]])
        mask = binary_erosion(mask, selem)
        self.updateUsingMask(box, mask)
        return True

    def createContourFromMask(self, mask, bbox):
        """
        It creates the contour (and the corrisponding polygon) from the blob mask.
        """

        # NOTE: The mask is expected to be cropped around its bbox (!!) (see the __init__)

        self.inner_contours.clear()

        # we need to pad the mask to avoid to break the contour that touches the borders
        PADDED_SIZE = 4
        img_padded = pad(mask, (PADDED_SIZE, PADDED_SIZE),
                         mode="constant",
                         constant_values=(0, 0))

        contours = measure.find_contours(img_padded, 0.6)
        inner_contours = measure.find_contours(img_padded, 0.4)
        number_of_contours = len(contours)

        threshold = 20  #min number of points in a small hole

        if number_of_contours > 1:

            # search the contour with the largest bounding box (area)
            max_area = 0
            longest = 0
            for i, contour in enumerate(contours):
                cbox = Mask.pointsBox(contour, 0)
                area = cbox[2] * cbox[3]
                if area > max_area:
                    max_area = area
                    longest = i

            max_area = 0
            inner_longest = 0
            for i, contour in enumerate(inner_contours):
                cbox = Mask.pointsBox(contour, 0)
                area = cbox[2] * cbox[3]
                if area > max_area:
                    max_area = area
                    inner_longest = i

            # divide the contours in OUTER contour and INNER contours
            for i, contour in enumerate(contours):
                if i == longest:
                    self.contour = np.array(contour)

            for i, contour in enumerate(inner_contours):
                if i != inner_longest:
                    if contour.shape[0] > threshold:
                        coordinates = np.array(contour)
                        self.inner_contours.append(coordinates)

            # adjust the coordinates of the outer contour
            # (NOTE THAT THE COORDINATES OF THE BBOX ARE IN THE GLOBAL MAP COORDINATES SYSTEM)
            for i in range(self.contour.shape[0]):
                ycoor = self.contour[i, 0]
                xcoor = self.contour[i, 1]
                self.contour[i, 0] = xcoor - PADDED_SIZE + bbox[1]
                self.contour[i, 1] = ycoor - PADDED_SIZE + bbox[0]

            # adjust coordinates of the INNER contours
            for j, contour in enumerate(self.inner_contours):
                for i in range(contour.shape[0]):
                    ycoor = contour[i, 0]
                    xcoor = contour[i, 1]
                    self.inner_contours[j][i,
                                           0] = xcoor - PADDED_SIZE + bbox[1]
                    self.inner_contours[j][i,
                                           1] = ycoor - PADDED_SIZE + bbox[0]
        elif number_of_contours == 1:

            coords = measure.approximate_polygon(contours[0], tolerance=0.2)
            self.contour = np.array(coords)

            # adjust the coordinates of the outer contour
            # (NOTE THAT THE COORDINATES OF THE BBOX ARE IN THE GLOBAL MAP COORDINATES SYSTEM)
            for i in range(self.contour.shape[0]):
                ycoor = self.contour[i, 0]
                xcoor = self.contour[i, 1]
                self.contour[i, 0] = xcoor - PADDED_SIZE + bbox[1]
                self.contour[i, 1] = ycoor - PADDED_SIZE + bbox[0]
        else:
            raise Exception("Empty contour")
        #TODO optimize the bbox
        self.bbox = bbox

    def lineToPoints(self, lines, snap=False):
        points = np.empty(shape=(0, 2), dtype=int)

        for line in lines:
            p = self.drawLine(line)
            if p.shape[0] == 0:
                continue
            if snap:
                p = self.snapToBorder(p)
            if p is None:
                continue
            points = np.append(points, p, axis=0)
        return points

    def dilate(self, size=1):
        """Dilate the blob"""

        mask = self.getMask()
        dilated_mask = binary_dilation(mask, square(size))
        self.updateUsingMask(self.bbox, dilated_mask)

    def erode(self, size=1):
        """Erode the blob"""

        mask = self.getMask()
        eroded_mask = binary_erosion(mask, square(size))
        self.updateUsingMask(self.bbox, eroded_mask)

    def drawLine(self, line):
        (x, y) = utils.draw_open_polygon(line[:, 1], line[:, 0])
        points = np.asarray([x, y]).astype(int)
        points = points.transpose()
        points[:, [1, 0]] = points[:, [0, 1]]
        return points

    def snapToBorder(self, points):
        return self.snapToContour(points, self.contour)

    def snapToContour(self, points, contour):
        """
        Given a curve specified as a set of points, snap the curve on the blob mask:
          1) the initial segments of the curve are removed until they snap
          2) the end segments of the curve are removed until they snap

        """
        test = points_in_poly(points, contour)
        if test is None or test.shape[0] <= 3:
            return None
        jump = np.gradient(test.astype(int))
        ind = np.nonzero(jump)
        ind = np.asarray(ind)

        snappoints = None
        if ind.shape[1] > 2:
            first_el = ind[0, 0]
            last_el = ind[0, -1]
            snappoints = points[first_el:last_el + 1, :].copy()

        return snappoints

    def snapToInternalBorders(self, points):
        if not self.inner_contours:
            return None
        snappoints = np.zeros(shape=(0, 2))
        for contour in self.inner_contours:
            snappoints = np.append(snappoints,
                                   self.snapToContour(points, contour))
        return snappoints

    def setupForDrawing(self):
        """
        Create the QPolygon and the QPainterPath according to the blob's contours.
        """

        # QPolygon to draw the blob
        #working with mask the center of the pixels is in 0, 0
        #if drawing the center of the pixel is 0.5, 0.5
        qpolygon = QPolygonF()
        for i in range(self.contour.shape[0]):
            qpolygon << QPointF(self.contour[i, 0] + 0.5,
                                self.contour[i, 1] + 0.5)

        self.qpath = QPainterPath()
        self.qpath.addPolygon(qpolygon)

        for inner_contour in self.inner_contours:
            qpoly_inner = QPolygonF()
            for i in range(inner_contour.shape[0]):
                qpoly_inner << QPointF(inner_contour[i, 0], inner_contour[i,
                                                                          1])

            path_inner = QPainterPath()
            path_inner.addPolygon(qpoly_inner)
            self.qpath = self.qpath.subtracted(path_inner)

    def createQPixmapFromMask(self):

        w = self.bbox[2]
        h = self.bbox[3]
        self.qimg_mask = QImage(w, h, QImage.Format_ARGB32)
        self.qimg_mask.fill(qRgba(0, 0, 0, 0))

        if self.class_name == "Empty":
            rgba = qRgba(255, 255, 255, 255)
        else:
            rgba = qRgba(self.class_color[0], self.class_color[1],
                         self.class_color[2], 100)

        blob_mask = self.getMask()
        for x in range(w):
            for y in range(h):
                if blob_mask[y, x] == 1:
                    self.qimg_mask.setPixel(x, y, rgba)

        self.pxmap_mask = QPixmap.fromImage(self.qimg_mask)

    #bbox is used to place the mask!
    def calculateCentroid(self, mask, bbox):
        m = measure.moments(mask)
        c = np.array((m[0, 1] / m[0, 0], m[1, 0] / m[0, 0]))

        #centroid is (x, y) while measure returns (y,x and bbox is yx)
        self.centroid = np.array((c[0] + bbox[1], c[1] + bbox[0]))
        self.blob_name = "c-{:d}-{:.1f}x-{:.1f}y".format(
            self.id, self.centroid[0], self.centroid[1])

    def calculateContourPerimeter(self, contour):

        #self.perimeter = measure.perimeter(mask) instead?

        # perimeter of the outer contour
        px1 = contour[0, 0]
        py1 = contour[0, 1]
        N = contour.shape[0]
        pxlast = contour[N - 1, 0]
        pylast = contour[N - 1, 1]
        perim = math.sqrt((px1 - pxlast) * (px1 - pxlast) + (py1 - pylast) *
                          (py1 - pylast))
        for i in range(1, contour.shape[0]):
            px2 = contour[i, 0]
            py2 = contour[i, 1]

            d = math.sqrt((px1 - px2) * (px1 - px2) + (py1 - py2) *
                          (py1 - py2))
            perim += d

            px1 = px2
            py1 = py2

        return perim

    def calculatePerimeter(self):
        #tole = 2
        #simplified = measure.approximate_polygon(self.contour, tolerance=tole)
        self.perimeter = self.calculateContourPerimeter(self.contour)

        for contour in self.inner_contours:
            #simplified = measure.approximate_polygon(contour, tolerance=tole)
            self.perimeter += self.calculateContourPerimeter(contour)

    def calculateArea(self, mask):
        self.area = mask.sum().astype(float)

    def fromDict(self, dict):
        """
        Set the blob information given it represented as a dictionary.
        """

        self.bbox = np.asarray(dict["bbox"])

        self.centroid = np.asarray(dict["centroid"])
        self.area = dict["area"]
        self.perimeter = dict["perimeter"]

        self.contour = np.asarray(dict["contour"])
        inner_contours = dict["inner contours"]
        self.inner_contours = []
        for c in inner_contours:
            self.inner_contours.append(np.asarray(c))

        self.deep_extreme_points = np.asarray(dict["deep_extreme_points"])
        self.class_name = dict["class name"]
        self.class_color = dict["class color"]
        self.instance_name = dict["instance name"]
        self.blob_name = dict["blob name"]
        self.id = int(dict["id"])
        self.note = dict["note"]

    def save(self):
        return self.toDict()

    def toDict(self):
        """
        Get the blob information as a dictionary.
        """

        dict = {}

        dict["bbox"] = self.bbox.tolist()

        dict["centroid"] = self.centroid.tolist()
        dict["area"] = self.area
        dict["perimeter"] = self.perimeter

        dict["contour"] = self.contour.tolist()

        dict["inner contours"] = []
        for c in self.inner_contours:
            dict["inner contours"].append(c.tolist())

        dict["deep_extreme_points"] = self.deep_extreme_points.tolist()

        dict["class name"] = self.class_name
        dict["class color"] = self.class_color

        dict["instance name"] = self.instance_name
        dict["blob name"] = self.blob_name
        dict["id"] = self.id
        dict["note"] = self.note

        return dict
Exemple #6
0
class Blob(object):
    """
    Blob data. A blob is a group of pixels.
    A blob can be tagged with the class and other information.
    Both the set of pixels and the corresponding vectorized version are stored.
    """
    def __init__(self, region, offset_x, offset_y, id):

        if region == None:

            # AN EMPTY BLOB IS CREATED..

            self.area = 0.0
            self.perimeter = 0.0
            self.centroid = np.zeros((2))
            self.bbox = np.zeros((4))

            # placeholder; empty contour
            self.contour = np.zeros((2, 2))
            self.inner_contours = []
            self.qpath = None
            self.qpath_gitem = None

            self.instance_name = "noname"
            self.blob_name = "noname"
            self.id = 0

        else:

            # extract properties

            self.centroid = np.array(region.centroid)
            cy = self.centroid[0]
            cx = self.centroid[1]
            self.centroid[0] = cx + offset_x
            self.centroid[1] = cy + offset_y

            # Bounding box (min_row, min_col, max_row, max_col).
            # Pixels belonging to the bounding box are in the half-open
            # interval [min_row, max_row) and [min_col, max_col).
            self.bbox = np.array(region.bbox)

            width = self.bbox[3] - self.bbox[1]
            height = self.bbox[2] - self.bbox[0]

            # BBOX ->  TOP, LEFT, WIDTH, HEIGHT
            self.bbox[0] = self.bbox[0] + offset_y
            self.bbox[1] = self.bbox[1] + offset_x
            self.bbox[2] = width
            self.bbox[3] = height

            # QPainterPath associated with the contours
            self.qpath = None

            # QGraphicsItem associated with the QPainterPath
            self.qpath_gitem = None

            # to extract the contour we use the mask cropped according to the bbox
            input_mask = region.image.astype(int)
            self.contour = np.zeros((2, 2))
            self.inner_contours = []
            self.createContourFromMask(input_mask)
            self.setupForDrawing()

            self.calculatePerimeter()
            self.calculateArea(input_mask)

            # a string with a progressive number to identify the instance
            self.instance_name = "coral" + str(id)

            # a string with a number to identify the blob plus its centroid
            xc = int(self.centroid[0])
            yc = int(self.centroid[1])
            self.blob_name = "blob" + str(id) + "-" + str(xc) + "-" + str(yc)
            self.id = id

        # deep extreme points (for fine-tuning)
        self.deep_extreme_points = np.zeros((4, 2))

        # name of the class
        self.class_name = "Empty"

        # color of the class
        self.class_color = [128, 128, 128]

        # note about the coral, i.e. damage type
        self.note = ""

        # QImage corresponding to the current mask
        self.qimg_mask = None

        # QPixmap associated with the mask (for pixel-level editing operations)
        self.pxmap_mask = None

        # QGraphicsItem associated with the pixmap mask
        self.pxmap_mask_gitem = None

        # membership group (if any)
        self.group = None

    def copy(self):

        blob = Blob()

        blob.centroid = self.centroid.copy()
        blob.bbox = self.bbox.copy()

        blob.classname = self.classname
        blob.classcolor = self.classcolor
        blob.instance_name = self.instance_name
        blob.id = self.id
        blob.note = self.note

        return blob

    def setId(self, id):

        # a string with a number to identify the blob plus its centroid
        xc = int(self.centroid[0])
        yc = int(self.centroid[1])
        self.blob_name = "blob" + str(id) + "-" + str(xc) + "-" + str(yc)
        self.id = id

    def getMask(self):
        """
        It creates the mask from the contour and returns it.
        """

        r = self.bbox[3]
        c = self.bbox[2]

        mask = np.zeros((r, c))

        # main polygon
        [rr, cc] = polygon(self.contour[:, 1], self.contour[:, 0])
        rr = rr - int(self.bbox[0])
        cc = cc - int(self.bbox[1])
        mask[rr, cc] = 1

        # holes
        for inner_contour in self.inner_contours:
            [rr, cc] = polygon(inner_contour[:, 1], inner_contour[:, 0])
            rr = rr - int(self.bbox[0])
            cc = cc - int(self.bbox[1])
            mask[rr, cc] = 0

        return mask

    def updateUsingMask(self, new_bbox, new_mask):

        self.bbox = new_bbox

        self.createContourFromMask(new_mask)
        self.setupForDrawing()

        self.calculatePerimeter()
        self.calculateArea(new_mask)
        self.calculateCentroid(new_mask)

    def snapToBorder(self, points):
        """
        Given a curve specified as a set of points, snap the curve on the blob mask:
          1) the initial segments of the curve are removed until they snap
          2) the end segments of the curve are removed until they snap

        """

        contour = self.contour.copy()
        test = points_in_poly(points, contour)
        jump = np.gradient(test.astype(int))
        ind = np.nonzero(jump)
        ind = np.asarray(ind)

        snappoints = None
        if ind.shape[1] > 2:
            first_el = ind[0, 0] + 1
            last_el = ind[0, -1]
            snappoints = points[first_el:last_el, :].copy()

        return snappoints

    def createFromClosedCurve(self, points):
        """
        It creates a blob starting from a closed curve. If the curve is not closed False is returned.
        If the curve intersect itself many times the first segmented region is created.
        """

        pt_min = np.amin(points, axis=0)
        xmin = pt_min[0]
        ymin = pt_min[1]
        pt_max = np.amax(points, axis=0)
        xmax = pt_max[0]
        ymax = pt_max[1]

        x_left = int(xmin) - 8
        y_top = int(ymin) - 8
        x_right = int(xmax) + 8
        y_bottom = int(ymax) + 8

        bbox = np.array([y_top, x_left, x_right - x_left, y_bottom - y_top])
        mask = np.zeros((bbox[3], bbox[2]))
        mask_area = bbox[3] * bbox[2]

        for i in range(points.shape[0]):

            x = points[i, 0]
            y = points[i, 1]

            yy = int(y) - bbox[0]
            xx = int(x) - bbox[1]

            for offsetx in range(-1, 2):
                for offsety in range(-1, 2):
                    mask[yy + offsety, xx + offsetx] = 1

        mask_flood = flood(mask, (4, 4), tolerance=0).astype(int)

        for i in range(points.shape[0]):

            x = points[i, 0]
            y = points[i, 1]

            yy = int(y) - bbox[0]
            xx = int(x) - bbox[1]

            for offsetx in range(-1, 2):
                for offsety in range(-1, 2):
                    mask_flood[yy + offsety, xx + offsetx] = 1

        for y in range(mask_flood.shape[0]):
            for x in range(mask_flood.shape[1]):
                if mask_flood[y, x] == 1:
                    mask[y, x] = 0
                else:
                    mask[y, x] = 1

        mask = ndi.binary_fill_holes(mask).astype(int)

        # check
        regions = measure.regionprops(mask)

        if len(regions) != 1:
            return False
        else:

            region = regions[0]
            check = region.area / mask_area
            if check > 0.95:
                return False
            else:
                self.updateUsingMask(bbox, mask)
                return True

    def createCrack(self, input_arr, x, y, tolerance, preview=True):
        """
        Given a inner blob point (x,y), the function use it as a seed for a paint butcket tool and create
        a correspondent blob hole
        """

        x_crop = x - self.bbox[1]
        y_crop = y - self.bbox[0]

        input_arr = gaussian(input_arr, 2)
        # input_arr = segmentation.inverse_gaussian_gradient(input_arr, alpha=1, sigma=1)

        blob_mask = self.getMask()

        crack_mask = flood(input_arr, (int(y_crop), int(x_crop)),
                           tolerance=tolerance).astype(int)
        cracked_blob = np.logical_and((blob_mask > 0), (crack_mask < 1))
        cracked_blob = cracked_blob.astype(int)

        if preview:
            return cracked_blob
        else:
            self.updateUsingMask(self.bbox, cracked_blob)
            return cracked_blob

    def addToMask(self, points):
        """
        Given a curve specified as a set of points, the pixels OUTSIDE the blob but inside the handle created
        by the curve are added to the blob.
        """

        # store the original inner contours (i.e. the holes)
        original_inner_contours = []
        for inner_contour in self.inner_contours:
            duplicate_inner_contour = inner_contour.copy()
            original_inner_contours.append(duplicate_inner_contour)

        # enlarge the mask
        y1A = self.bbox[0]
        x1A = self.bbox[1]
        x2A = x1A + self.bbox[2]
        y2A = y1A + self.bbox[3]

        pt_min = np.amin(points, axis=0)
        xmin = pt_min[0]
        ymin = pt_min[1]
        pt_max = np.amax(points, axis=0)
        xmax = pt_max[0]
        ymax = pt_max[1]

        x1B = int(xmin)
        y1B = int(ymin)
        x2B = int(xmax)
        y2B = int(ymax)

        x_left = min(x1A, x1B) - 2
        y_top = min(y1A, y1B) - 2
        x_right = max(x2A, x2B) + 2
        y_bottom = max(y2A, y2B) + 2

        bbox_union = np.array(
            [y_top, x_left, x_right - x_left, y_bottom - y_top])
        mask_union = np.zeros((bbox_union[3], bbox_union[2]))

        blob_mask = self.getMask()
        for y in range(blob_mask.shape[0]):
            for x in range(blob_mask.shape[1]):

                yy = y + (self.bbox[0] - bbox_union[0])
                xx = x + (self.bbox[1] - bbox_union[1])
                mask_union[yy, xx] = blob_mask[y, x]

        for i in range(points.shape[0]):

            x = points[i, 0]
            y = points[i, 1]

            yy = int(y) - bbox_union[0]
            xx = int(x) - bbox_union[1]

            for offsetx in range(-1, 2):
                for offsety in range(-1, 2):
                    mask_union[yy + offsety, xx + offsetx] = 1

        mask_union = ndi.binary_fill_holes(mask_union).astype(int)

        self.updateUsingMask(bbox_union, mask_union)

        # RE-ADD THE ORIGINAL INNER CONTOURS (I.E. THE HOLES)
        if original_inner_contours:
            self.inner_contours.clear()
            for inner_contour in original_inner_contours:

                # recover inner contour list
                self.inner_contours.append(inner_contour)

            # update qpainterpath
            self.setupForDrawing()

    def cutFromMask(self, points):
        """
        Given a curve specified as a set of points, the pixels INSIDE the blob but "cutted" by the curve
        are removed from the blob.
        """

        # enlarge the mask
        y1A = self.bbox[0]
        x1A = self.bbox[1]
        x2A = x1A + self.bbox[2]
        y2A = y1A + self.bbox[3]

        pt_min = np.amin(points, axis=0)
        xmin = pt_min[0]
        ymin = pt_min[1]
        pt_max = np.amax(points, axis=0)
        xmax = pt_max[0]
        ymax = pt_max[1]

        x1B = int(xmin)
        y1B = int(ymin)
        x2B = int(xmax)
        y2B = int(ymax)

        x_left = min(x1A, x1B) - 2
        y_top = min(y1A, y1B) - 2
        x_right = max(x2A, x2B) + 2
        y_bottom = max(y2A, y2B) + 2

        bbox_union = np.array(
            [y_top, x_left, x_right - x_left, y_bottom - y_top])
        mask_union = np.zeros((bbox_union[3], bbox_union[2]))

        blob_mask = self.getMask()
        for y in range(blob_mask.shape[0]):
            for x in range(blob_mask.shape[1]):

                yy = y + (self.bbox[0] - bbox_union[0])
                xx = x + (self.bbox[1] - bbox_union[1])
                mask_union[yy, xx] = blob_mask[y, x]

        for i in range(points.shape[0]):

            x = points[i, 0]
            y = points[i, 1]

            yy = int(y) - bbox_union[0]
            xx = int(x) - bbox_union[1]

            for offsetx in range(-1, 2):
                for offsety in range(-1, 2):
                    mask_union[yy + offsety, xx + offsetx] = 0

        label_image = measure.label(mask_union)
        regions = measure.regionprops(label_image)

        if len(regions) > 1:

            # TENERE SOLO QUELLA CON AREA MASSIMA ??

            area_max = 0
            region_to_remove = None
            for region in regions:

                if region.area > area_max:
                    area_max = region.area

            for region in regions:
                if region.area < area_max:
                    for coords in region.coords:
                        mask_union[coords[0], coords[1]] = 0

        self.updateUsingMask(bbox_union, mask_union)

    def createContourFromMask(self, mask):
        """
        It creates the contour (and the corrisponding polygon) from the blob mask.
        """

        # NOTE: The mask is expected to be cropped around its bbox (!!) (see the __init__)

        self.inner_contours.clear()

        # we need to pad the mask to avoid to break the contour that touchs the borders
        PADDED_SIZE = 2
        img_padded = pad(mask, (PADDED_SIZE, PADDED_SIZE),
                         mode="constant",
                         constant_values=(0, 0))

        contours = measure.find_contours(img_padded, 0.5)

        number_of_contours = len(contours)

        if number_of_contours > 1:

            # search the longest contour
            npoints_max = 0
            index = 0
            for i, contour in enumerate(contours):
                npoints = contour.shape[0]
                if npoints > npoints_max:
                    npoints_max = npoints
                    index = i

            # divide the contours in OUTER contour and INNER contours
            for i, contour in enumerate(contours):
                if i == index:
                    self.contour = np.array(contour)
                else:
                    coordinates = np.array(contour)
                    self.inner_contours.append(coordinates)

            # adjust the coordinates of the outer contour
            # (NOTE THAT THE COORDINATES OF THE BBOX ARE IN THE GLOBAL MAP COORDINATES SYSTEM)
            for i in range(self.contour.shape[0]):
                ycoor = self.contour[i, 0]
                xcoor = self.contour[i, 1]
                self.contour[i, 0] = xcoor - PADDED_SIZE + self.bbox[1]
                self.contour[i, 1] = ycoor - PADDED_SIZE + self.bbox[0]

            # adjust coordinates of the INNER contours
            for j, contour in enumerate(self.inner_contours):
                for i in range(contour.shape[0]):
                    ycoor = contour[i, 0]
                    xcoor = contour[i, 1]
                    self.inner_contours[j][
                        i, 0] = xcoor - PADDED_SIZE + self.bbox[1]
                    self.inner_contours[j][
                        i, 1] = ycoor - PADDED_SIZE + self.bbox[0]

        elif number_of_contours == 1:

            coords = measure.approximate_polygon(contours[0], tolerance=1.2)
            self.contour = np.array(coords)

            # adjust the coordinates of the outer contour
            # (NOTE THAT THE COORDINATES OF THE BBOX ARE IN THE GLOBAL MAP COORDINATES SYSTEM)
            for i in range(self.contour.shape[0]):
                ycoor = self.contour[i, 0]
                xcoor = self.contour[i, 1]
                self.contour[i, 0] = xcoor - PADDED_SIZE + self.bbox[1]
                self.contour[i, 1] = ycoor - PADDED_SIZE + self.bbox[0]
        else:

            print("ZERO CONTOURS -> THERE ARE SOME PROBLEMS HERE !!!!!!)")

    def setupForDrawing(self):
        """
        Create the QPolygon and the QPainterPath according to the blob's contours.
        """

        # QPolygon to draw the blob
        qpolygon = QPolygonF()
        for i in range(self.contour.shape[0]):
            qpolygon << QPointF(self.contour[i, 0], self.contour[i, 1])

        self.qpath = QPainterPath()
        self.qpath.addPolygon(qpolygon)

        for inner_contour in self.inner_contours:
            qpoly_inner = QPolygonF()
            for i in range(inner_contour.shape[0]):
                qpoly_inner << QPointF(inner_contour[i, 0], inner_contour[i,
                                                                          1])

            path_inner = QPainterPath()
            path_inner.addPolygon(qpoly_inner)
            self.qpath = self.qpath.subtracted(path_inner)

    def createQPixmapFromMask(self):

        w = self.bbox[2]
        h = self.bbox[3]
        self.qimg_mask = QImage(w, h, QImage.Format_ARGB32)
        self.qimg_mask.fill(qRgba(0, 0, 0, 0))

        if self.class_name == "Empty":
            rgba = qRgba(255, 255, 255, 255)
        else:
            rgba = qRgba(self.class_color[0], self.class_color[1],
                         self.class_color[2], 100)

        blob_mask = self.getMask()
        for x in range(w):
            for y in range(h):
                if mask[y, x] == 1:
                    self.qimg_mask.setPixel(x, y, rgba)

        self.pxmap_mask = QPixmap.fromImage(self.qimg_mask)

    def calculateCentroid(self, mask):

        sumx = 0.0
        sumy = 0.0
        n = 0

        for y in range(mask.shape[0]):
            for x in range(mask.shape[1]):
                if mask[y, x] == 1:
                    sumx += float(x)
                    sumy += float(y)
                    n += 1

        # NOTE: centroid is (x,y), bbox is [width height]
        self.centroid[0] = int(sumx / n) + self.bbox[1]
        self.centroid[1] = int(sumy / n) + self.bbox[0]

        xc = int(self.centroid[0])
        yc = int(self.centroid[1])
        self.instance_name = "coral-" + str(xc) + "-" + str(yc)

    def calculateContourPerimeter(self, contour):

        # perimeter of the outer contour
        px1 = contour[0, 0]
        py1 = contour[0, 1]
        N = contour.shape[0]
        pxlast = contour[N - 1, 0]
        pylast = contour[N - 1, 1]
        perim = math.sqrt((px1 - pxlast) * (px1 - pxlast) + (py1 - pylast) *
                          (py1 - pylast))
        for i in range(1, contour.shape[0]):
            px2 = contour[i, 0]
            py2 = contour[i, 1]

            d = math.sqrt((px1 - px2) * (px1 - px2) + (py1 - py2) *
                          (py1 - py2))
            perim += d

            px1 = px2
            py1 = py2

        return perim

    def calculatePerimeter(self):

        self.perimeter = self.calculateContourPerimeter(self.contour)

        for contour in self.inner_contours:
            self.perimeter += self.calculateContourPerimeter(self.contour)

    def calculateArea(self, mask):

        self.area = 0.0
        for y in range(mask.shape[0]):
            for x in range(mask.shape[1]):
                if mask[y, x] == 1:
                    self.area += 1.0

    def fromDict(self, dict):
        """
        Set the blob information given it represented as a dictionary.
        """

        self.bbox = np.asarray(dict["bbox"])

        self.centroid = np.asarray(dict["centroid"])
        self.area = dict["area"]
        self.perimeter = dict["perimeter"]

        self.contour = np.asarray(dict["contour"])
        inner_contours = dict["inner contours"]
        self.inner_contours = []
        for c in inner_contours:
            self.inner_contours.append(np.asarray(c))

        self.deep_extreme_points = np.asarray(dict["deep_extreme_points"])
        self.class_name = dict["class name"]
        self.class_color = dict["class color"]
        self.instance_name = dict["instance name"]
        self.blob_name = dict["blob name"]
        self.id = dict["id"]
        self.note = dict["note"]

        # finalize blob
        self.setupForDrawing()

    def toDict(self):
        """
        Get the blob information as a dictionary.
        """

        dict = {}

        dict["bbox"] = self.bbox.tolist()

        dict["centroid"] = self.centroid.tolist()
        dict["area"] = self.area
        dict["perimeter"] = self.perimeter

        dict["contour"] = self.contour.tolist()

        dict["inner contours"] = []
        for c in self.inner_contours:
            dict["inner contours"].append(c.tolist())

        dict["deep_extreme_points"] = self.deep_extreme_points.tolist()

        dict["class name"] = self.class_name
        dict["class color"] = self.class_color

        dict["instance name"] = self.instance_name
        dict["blob name"] = self.blob_name
        dict["id"] = self.id
        dict["note"] = self.note

        return dict
Exemple #7
0
class Blob(object):
    """
    Blob data. A blob is a group of pixels.
    A blob can be tagged with the class and other information.
    Both the set of pixels and the corresponding vectorized version are stored.
    """
    def __init__(self, region, offset_x, offset_y, id):

        if region == None:  # AN EMPTY BLOB IS CREATED..
            self.area = 0.0
            self.perimeter = 0.0
            self.centroid = np.zeros((2))
            self.bbox = np.zeros((4))

            # placeholder; empty contour
            self.contour = np.zeros((2, 2))
            self.inner_contours = []
            self.qpath = None
            self.qpath_gitem = None

            self.instance_name = "noname"
            self.blob_name = "noname"
            self.id = 0

        else:

            # extract properties

            self.centroid = np.array(region.centroid)
            cy = self.centroid[0]
            cx = self.centroid[1]
            self.centroid[0] = cx + offset_x
            self.centroid[1] = cy + offset_y

            # Bounding box (min_row, min_col, max_row, max_col).
            # Pixels belonging to the bounding box are in the half-open
            # interval [min_row, max_row) and [min_col, max_col).
            self.bbox = np.array(region.bbox)

            width = self.bbox[3] - self.bbox[1]
            height = self.bbox[2] - self.bbox[0]

            # BBOX ->  TOP, LEFT, WIDTH, HEIGHT
            self.bbox[0] = self.bbox[0] + offset_y
            self.bbox[1] = self.bbox[1] + offset_x
            self.bbox[2] = width
            self.bbox[3] = height

            # QPainterPath associated with the contours
            self.qpath = None

            # QGraphicsItem associated with the QPainterPath
            self.qpath_gitem = None

            # to extract the contour we use the mask cropped according to the bbox
            input_mask = region.image.astype(int)
            self.contour = np.zeros((2, 2))
            self.inner_contours = []
            self.updateUsingMask(self.bbox, input_mask)

            # a string with a progressive number to identify the instance
            self.instance_name = "coral" + str(id)

            # a string with a number to identify the blob plus its centroid
            xc = int(self.centroid[0])
            yc = int(self.centroid[1])
            self.blob_name = "blob" + str(id) + "-" + str(xc) + "-" + str(yc)
            self.id = id

        # deep extreme points (for fine-tuning)
        self.deep_extreme_points = np.zeros((4, 2))

        # name of the class
        self.class_name = "Empty"

        # color of the class
        self.class_color = [128, 128, 128]

        # note about the coral, i.e. damage type
        self.note = ""

        # QImage corresponding to the current mask
        self.qimg_mask = None

        # QPixmap associated with the mask (for pixel-level editing operations)
        self.pxmap_mask = None

        # QGraphicsItem associated with the pixmap mask
        self.pxmap_mask_gitem = None

        # membership group (if any)
        self.group = None

    def copy(self):
        blob = Blob(None, 0, 0, 0)

        blob.area = self.area
        blob.perimeter = self.perimeter
        blob.centroid = self.centroid
        blob.bbox = self.bbox

        blob.contour = self.contour.copy()
        for inner in self.inner_contours:
            blob.inner_contours.append(inner.copy())
        blob.qpath_gitem = None
        blob.qpath = None

        blob.instance_name = blob.instance_name
        blob.blob_name = self.blob_name
        blob.id = self.id

        blob.class_name = self.class_name

        blob.deep_extreme_points = self.deep_extreme_points

        self.note = ""
        self.qimg_mask = None
        self.pxmap_mask = None
        self.pxmap_mask_gitem = None
        return blob

    def __deepcopy__(self, memo):
        #avoid recursion!
        deepcopy_method = self.__deepcopy__
        self.__deepcopy__ = None
        #save and later restore qobjects
        path = self.qpath
        pathitem = self.qpath_gitem
        #no deep copy for qobjects
        self.qpath = None
        self.qpath_gitem = None

        blob = copy.deepcopy(self)
        blob.contour = self.contour.copy()
        blob.inner_contours.clear()
        for inner in self.inner_contours:
            blob.inner_contours.append(inner.copy())

        blob.qpath = None
        blob.qpath_gitem = None
        self.qpath = path
        self.qpath_gitem = pathitem
        #restore deepcopy (also to the newly created Blob!
        blob.__deepcopy__ = self.__deepcopy__ = deepcopy_method
        return blob

    def setId(self, id):

        # a string with a number to identify the blob plus its centroid
        xc = int(self.centroid[0])
        yc = int(self.centroid[1])
        self.blob_name = "blob" + str(id) + "-" + str(xc) + "-" + str(yc)
        self.id = id

    def getMask(self):
        """
        It creates the mask from the contour and returns it.
        """

        r = self.bbox[3]
        c = self.bbox[2]

        mask = np.zeros((r, c), dtype=np.bool_)

        # main polygon
        [rr, cc] = polygon(self.contour[:, 1], self.contour[:, 0])
        rr = rr - int(self.bbox[0])
        cc = cc - int(self.bbox[1])
        mask[rr, cc] = 1

        # holes
        for inner_contour in self.inner_contours:
            [rr, cc] = polygon(inner_contour[:, 1], inner_contour[:, 0])
            rr = rr - int(self.bbox[0])
            cc = cc - int(self.bbox[1])
            mask[rr, cc] = 0

        return mask

    def updateUsingMask(self, bbox, mask):

        self.bbox = bbox
        self.createContourFromMask(mask)
        self.calculatePerimeter()
        self.calculateCentroid(mask)
        self.calculateArea(mask)

    def lineToPoints(self, lines, snap=False):
        points = np.empty(shape=(0, 2), dtype=int)

        for line in lines:
            p = self.drawLine(line)
            if p.shape[0] == 0:
                continue
            if snap:
                p = self.snapToBorder(p)
            if p is None:
                continue
            points = np.append(points, p, axis=0)
        return points

    def drawLine(self, line):
        (x, y) = utils.draw_open_polygon(line[:, 1], line[:, 0])
        points = np.asarray([x, y]).astype(int)
        points = points.transpose()
        points[:, [1, 0]] = points[:, [0, 1]]
        return points

    def snapToBorder(self, points):
        return self.snapToContour(points, self.contour)

    def snapToContour(self, points, contour):
        """
        Given a curve specified as a set of points, snap the curve on the blob mask:
          1) the initial segments of the curve are removed until they snap
          2) the end segments of the curve are removed until they snap

        """
        test = points_in_poly(points, contour)
        if test is None or test.shape[0] == 0:
            return None
        jump = np.gradient(test.astype(int))
        ind = np.nonzero(jump)
        ind = np.asarray(ind)

        snappoints = None
        if ind.shape[1] > 2:
            first_el = ind[0, 0] + 1
            last_el = ind[0, -1]
            snappoints = points[first_el:last_el, :].copy()

        return snappoints

    def snapToInternalBorders(self, points):
        if not self.inner_contours:
            return None
        snappoints = np.zeros(shape=(0, 2))
        for contour in self.inner_contours:
            snappoints = np.append(snappoints,
                                   self.snapToContour(points, contour))
        return snappoints

    def createFromClosedCurve(self, lines):
        """
        It creates a blob starting from a closed curve. If the curve is not closed False is returned.
        If the curve intersect itself many times the first segmented region is created.
        """

        points = self.lineToPoints(lines)

        box = Mask.pointsBox(points, 4)

        (mask, box) = Mask.jointMask(box, box)
        Mask.paintPoints(mask, box, points, 1)
        mask = ndi.binary_fill_holes(mask)
        mask = binary_erosion(mask)
        mask = binary_erosion(mask)
        mask = binary_erosion(mask)
        mask = binary_dilation(mask)
        mask = binary_dilation(mask)
        self.updateUsingMask(box, mask)
        return True

    def createCrack(self, input_arr, x, y, tolerance, preview=True):
        """
        Given a inner blob point (x,y), the function use it as a seed for a paint butcket tool and create
        a correspondent blob hole
        """

        x_crop = x - self.bbox[1]
        y_crop = y - self.bbox[0]

        input_arr = gaussian(input_arr, 2)
        # input_arr = segmentation.inverse_gaussian_gradient(input_arr, alpha=1, sigma=1)

        blob_mask = self.getMask()

        crack_mask = flood(input_arr, (int(y_crop), int(x_crop)),
                           tolerance=tolerance).astype(int)
        cracked_blob = np.logical_and((blob_mask > 0), (crack_mask < 1))
        cracked_blob = cracked_blob.astype(int)

        if not preview:
            self.updateUsingMask(self.bbox, cracked_blob)

        return cracked_blob

    def createContourFromMask(self, mask):
        """
        It creates the contour (and the corrisponding polygon) from the blob mask.
        """

        # NOTE: The mask is expected to be cropped around its bbox (!!) (see the __init__)

        self.inner_contours.clear()

        # we need to pad the mask to avoid to break the contour that touches the borders
        PADDED_SIZE = 2
        img_padded = pad(mask, (PADDED_SIZE, PADDED_SIZE),
                         mode="constant",
                         constant_values=(0, 0))

        contours = measure.find_contours(img_padded, 0.5)

        number_of_contours = len(contours)

        threshold = 20  #min number of points in a small hole

        if number_of_contours > 1:

            # search the longest contour
            npoints_max = 0
            index = 0
            for i, contour in enumerate(contours):
                npoints = contour.shape[0]
                if npoints > npoints_max:
                    npoints_max = npoints
                    index = i

            # divide the contours in OUTER contour and INNER contours
            for i, contour in enumerate(contours):
                if i == index:
                    self.contour = np.array(contour)
                else:
                    if contour.shape[0] > threshold:
                        coordinates = np.array(contour)
                        self.inner_contours.append(coordinates)

            # adjust the coordinates of the outer contour
            # (NOTE THAT THE COORDINATES OF THE BBOX ARE IN THE GLOBAL MAP COORDINATES SYSTEM)
            for i in range(self.contour.shape[0]):
                ycoor = self.contour[i, 0]
                xcoor = self.contour[i, 1]
                self.contour[i, 0] = xcoor - PADDED_SIZE + self.bbox[1]
                self.contour[i, 1] = ycoor - PADDED_SIZE + self.bbox[0]

            # adjust coordinates of the INNER contours
            for j, contour in enumerate(self.inner_contours):
                for i in range(contour.shape[0]):
                    ycoor = contour[i, 0]
                    xcoor = contour[i, 1]
                    self.inner_contours[j][
                        i, 0] = xcoor - PADDED_SIZE + self.bbox[1]
                    self.inner_contours[j][
                        i, 1] = ycoor - PADDED_SIZE + self.bbox[0]

        elif number_of_contours == 1:

            coords = measure.approximate_polygon(contours[0], tolerance=1.2)
            self.contour = np.array(coords)

            # adjust the coordinates of the outer contour
            # (NOTE THAT THE COORDINATES OF THE BBOX ARE IN THE GLOBAL MAP COORDINATES SYSTEM)
            for i in range(self.contour.shape[0]):
                ycoor = self.contour[i, 0]
                xcoor = self.contour[i, 1]
                self.contour[i, 0] = xcoor - PADDED_SIZE + self.bbox[1]
                self.contour[i, 1] = ycoor - PADDED_SIZE + self.bbox[0]
        else:

            print("ZERO CONTOURS -> THERE ARE SOME PROBLEMS HERE !!!!!!)")

    def setupForDrawing(self):
        """
        Create the QPolygon and the QPainterPath according to the blob's contours.
        """

        # QPolygon to draw the blob
        qpolygon = QPolygonF()
        for i in range(self.contour.shape[0]):
            qpolygon << QPointF(self.contour[i, 0], self.contour[i, 1])

        self.qpath = QPainterPath()
        self.qpath.addPolygon(qpolygon)

        for inner_contour in self.inner_contours:
            qpoly_inner = QPolygonF()
            for i in range(inner_contour.shape[0]):
                qpoly_inner << QPointF(inner_contour[i, 0], inner_contour[i,
                                                                          1])

            path_inner = QPainterPath()
            path_inner.addPolygon(qpoly_inner)
            self.qpath = self.qpath.subtracted(path_inner)

    def createQPixmapFromMask(self):

        w = self.bbox[2]
        h = self.bbox[3]
        self.qimg_mask = QImage(w, h, QImage.Format_ARGB32)
        self.qimg_mask.fill(qRgba(0, 0, 0, 0))

        if self.class_name == "Empty":
            rgba = qRgba(255, 255, 255, 255)
        else:
            rgba = qRgba(self.class_color[0], self.class_color[1],
                         self.class_color[2], 100)

        blob_mask = self.getMask()
        for x in range(w):
            for y in range(h):
                if blob_mask[y, x] == 1:
                    self.qimg_mask.setPixel(x, y, rgba)

        self.pxmap_mask = QPixmap.fromImage(self.qimg_mask)

    def calculateCentroid(self, mask):
        m = measure.moments(mask)
        c = np.array((m[0, 1] / m[0, 0], m[1, 0] / m[0, 0]))

        #centroid is (x, y) while measure returns (y,x and bbox is yx)
        self.centroid = np.array((c[1] + self.bbox[1], c[0] + self.bbox[0]))
        self.blob_name = "coral-" + str(self.centroid[0]) + "-" + str(
            self.centroid[1])

    def calculateContourPerimeter(self, contour):

        #self.perimeter = measure.perimeter(mask) instead?

        # perimeter of the outer contour
        px1 = contour[0, 0]
        py1 = contour[0, 1]
        N = contour.shape[0]
        pxlast = contour[N - 1, 0]
        pylast = contour[N - 1, 1]
        perim = math.sqrt((px1 - pxlast) * (px1 - pxlast) + (py1 - pylast) *
                          (py1 - pylast))
        for i in range(1, contour.shape[0]):
            px2 = contour[i, 0]
            py2 = contour[i, 1]

            d = math.sqrt((px1 - px2) * (px1 - px2) + (py1 - py2) *
                          (py1 - py2))
            perim += d

            px1 = px2
            py1 = py2

        return perim

    def calculatePerimeter(self):

        self.perimeter = self.calculateContourPerimeter(self.contour)

        for contour in self.inner_contours:
            self.perimeter += self.calculateContourPerimeter(self.contour)

    def calculateArea(self, mask):
        self.area = mask.sum().astype(float)

    def fromDict(self, dict):
        """
        Set the blob information given it represented as a dictionary.
        """

        self.bbox = np.asarray(dict["bbox"])

        self.centroid = np.asarray(dict["centroid"])
        self.area = dict["area"]
        self.perimeter = dict["perimeter"]

        self.contour = np.asarray(dict["contour"])
        inner_contours = dict["inner contours"]
        self.inner_contours = []
        for c in inner_contours:
            self.inner_contours.append(np.asarray(c))

        self.deep_extreme_points = np.asarray(dict["deep_extreme_points"])
        self.class_name = dict["class name"]
        self.class_color = dict["class color"]
        self.instance_name = dict["instance name"]
        self.blob_name = dict["blob name"]
        self.id = dict["id"]
        self.note = dict["note"]

        # finalize blob
        #self.setupForDrawing()

    def toDict(self):
        """
        Get the blob information as a dictionary.
        """

        dict = {}

        dict["bbox"] = self.bbox.tolist()

        dict["centroid"] = self.centroid.tolist()
        dict["area"] = self.area
        dict["perimeter"] = self.perimeter

        dict["contour"] = self.contour.tolist()

        dict["inner contours"] = []
        for c in self.inner_contours:
            dict["inner contours"].append(c.tolist())

        dict["deep_extreme_points"] = self.deep_extreme_points.tolist()

        dict["class name"] = self.class_name
        dict["class color"] = self.class_color

        dict["instance name"] = self.instance_name
        dict["blob name"] = self.blob_name
        dict["id"] = self.id
        dict["note"] = self.note

        return dict