def editBorder(self, blob, lines): points = [blob.drawLine(line) for line in lines] if points is None or len(points) == 0: return # compute the box for the outer contour intersected = False (mask, box, intersected) = self.editBorderContour(blob, blob.contour, points) pointIntersectsContours = intersected for contour in blob.inner_contours: (inner_mask, inner_box, intersected) = self.editBorderContour(blob, contour, points) pointIntersectsContours = pointIntersectsContours or intersected Mask.paintMask(mask, box, inner_mask, inner_box, 0) if not pointIntersectsContours: #probably a hole, draw the points fill the hole and subtract from mask allpoints = np.empty(shape=(0, 2), dtype=int) for arc in points: allpoints = np.append(allpoints, arc, axis=0) points_box = Mask.pointsBox(allpoints, 4) (points_mask, points_box) = Mask.jointMask(points_box, points_box) Mask.paintPoints(points_mask, points_box, allpoints, 1) origin = np.array([points_box[1], points_box[0]]) Mask.paintPoints(points_mask, points_box, allpoints - origin, 1) points_mask = ndi.binary_fill_holes(points_mask) points_mask = binary_erosion(points_mask) Mask.paintMask(mask, box, points_mask, points_box, 0) blob.updateUsingMask(box, mask)
def editBorderContour(self, blob, contour, points): snapped_points = np.empty(shape=(0, 2), dtype=int) for arc in points: snapped = blob.snapToContour(arc, contour) if snapped is not None: snapped_points = np.append(snapped_points, snapped, axis=0) contour_box = Mask.pointsBox(contour, 4) #if the countour did not intersect with the outer contour, get the mask of the outer contour if snapped_points is None or len(snapped_points) == 0: # not very elegant repeated code... (mask, box) = Mask.jointMask(contour_box, contour_box) origin = np.array([box[1], box[0]]) contour_points = contour.round().astype(int) fillPoly(mask, pts=[contour_points - origin], color=(1)) return (mask, box, False) points_box = Mask.pointsBox(snapped_points, 4) # create a mask large enough to accomodate the points and the contour and paint. (mask, box) = Mask.jointMask(contour_box, points_box) origin = np.array([box[1], box[0]]) contour_points = contour.round().astype(int) fillPoly(mask, pts=[contour_points - origin], color=(1, 1, 1)) Mask.paintPoints(mask, box, snapped_points, 1) mask = ndi.binary_fill_holes(mask) # now draw in black the part of the points inside the contour Mask.paintPoints(mask, box, snapped_points, 0) # now we label all the parts and keep the larges only regions = measure.regionprops(measure.label(mask, connectivity=1)) largest = max(regions, key=lambda region: region.area) # adjust the image bounding box (relative to the region mask) to directly use area.image mask box = np.array([ box[0] + largest.bbox[0], box[1] + largest.bbox[1], largest.bbox[3] - largest.bbox[1], largest.bbox[2] - largest.bbox[0] ]) return (largest.image, box, True)
def editBorder(self, blob, lines): #need padding #would be lovely to be able do edit holes too. #the main problem is snapping to the external contour points = blob.lineToPoints(lines, snap=True) if points is None: return if len(points) == 0: return pointsbox = Mask.pointsBox(points, 3) blobmask = blob.getMask() #add to mask painting the points as 1 and filling the holes. (mask, box) = Mask.jointMask(blob.bbox, pointsbox) Mask.paintMask(mask, box, blobmask, blob.bbox, 1) #save holes full = ndi.binary_fill_holes(mask.astype(int)) holes = full & ~mask #cut from mask Mask.paintPoints(mask, box, points, 1) mask = ndi.binary_fill_holes(mask.astype(int)) #erase the points to carve to remove the internal parts. Mask.paintPoints(mask, box, points, 0) #add back holes mask = mask & ~holes regions = measure.regionprops(measure.label(mask)) if len(regions): largest = regions[0] for region in regions: if region.area > largest.area: largest = region #adjust the image bounding box (relative to the region mask) to directly use area.image mask #image box is standard (minx, miny, maxx, maxy) box = np.array([ box[0] + largest.bbox[0], box[1] + largest.bbox[1], largest.bbox[3], largest.bbox[2] ]) blob.updateUsingMask(box, largest.image.astype(int))
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_dilation(mask) self.updateUsingMask(box, mask) return True
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 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 segmentation(self): # compute bbox of scribbles (working area) bboxes = [] for i, curve in enumerate(self.scribbles.points): bbox = Mask.pointsBox(curve, int(self.scribbles.size[i] / 2)) bboxes.append(bbox) working_area = Mask.jointBox(bboxes) if working_area[0] < 0: working_area[0] = 0 if working_area[1] < 0: working_area[1] = 0 if working_area[0] + working_area[3] > self.viewerplus.img_map.height( ) - 1: working_area[3] = self.viewerplus.img_map.height( ) - 1 - working_area[0] if working_area[1] + working_area[2] > self.viewerplus.img_map.width( ) - 1: working_area[2] = self.viewerplus.img_map.width( ) - 1 - working_area[1] crop_img = utils.cropQImage(self.viewerplus.img_map, working_area) crop_imgnp = utils.qimageToNumpyArray(crop_img) # create markers mask = np.zeros((working_area[3], working_area[2], 3), dtype=np.int32) color_codes = dict() counter = 1 for i, curve in enumerate(self.scribbles.points): col = self.scribbles.label[i].fill b = col[2] g = col[1] r = col[0] color = (b, g, r) color_code = b + 256 * g + 65536 * r color_key = str(color_code) if color_codes.get(color_key) is None: name = self.scribbles.label[i].name color_codes[color_key] = (counter, name) counter = counter + 1 curve = np.int32(curve) curve[:, 0] = curve[:, 0] - working_area[1] curve[:, 1] = curve[:, 1] - working_area[0] curve = curve.reshape((-1, 1, 2)) mask = cv2.polylines(mask, pts=[curve], isClosed=False, color=color, thickness=self.scribbles.size[i], lineType=cv2.LINE_8) mask = np.uint8(mask) markers = np.zeros((working_area[3], working_area[2]), dtype='int32') for label in self.scribbles.label: col = label.fill b = col[2] g = col[1] r = col[0] color_code = b + 256 * g + 65536 * r color_key = str(color_code) idx = np.where((mask[:, :, 0] == b) & (mask[:, :, 1] == g) & (mask[:, :, 2] == r)) (value, name) = color_codes[color_key] markers[idx] = value # markers = np.int32(255*rgb2gray(mask)) # markersprint = 255*rgb2gray(mask) markersprint = markers cv2.imwrite('mask.png', markersprint) # watershed segmentation segmentation = cv2.watershed(crop_imgnp, markers) segmentation = filters.median(segmentation, disk(5), mode="mirror") cv2.imwrite('segmentation.png', segmentation) # the result of the segmentation must be converted into labels again lbls = measure.label(segmentation) blobs = [] for region in measure.regionprops(lbls): blob = Blob(region, working_area[1], working_area[0], self.viewerplus.annotations.getFreeId()) color_index = segmentation[region.coords[0][0], region.coords[0][1]] data = list(color_codes.items()) index = 0 for i in range(len(data)): (color_code, t) = data[i] if t[0] == color_index: color_code = int(color_code) r = int(color_code / 65536) g = int(int(color_code - r * 65536) / 256) b = int(color_code - r * 65536 - g * 256) color = [r, g, b] name = t[1] break blob.class_color = color blob.class_name = name blobs.append(blob) return blobs