def resize(mask, output_shape): """Resize a mask to the requested output shape with nearest neighbors Parameters ---------- mask : ndarray binary array output_shape : tuple requested shape Returns ------- output_shape : tuple requested output shape TODO: It might be possible to combine this function with one of the transforms """ assert_nD(mask, 2, 'mask') assert_binary(mask, 'mask') out = _resize( mask.astype(np.float), output_shape, order=0, mode='constant', cval=0, clip=True, preserve_range=False) return out.astype('uint8')
def random_rescale_2d(arr, zoom_perc): """Rescale an array in 2D. Parameters ---------- arr : ndarray array to rescale zoom_percentage : float number between 0 and 1 to denote the percentage to scale Returns ------- Randomly rescaled array of zoom_perc% and the selected zoom. """ assert_nD(arr, 2) if zoom_perc < 0 or zoom_perc > 1: raise ValueError('zoom percentage should be in [0, 1]') zoom_range = 1 + zoom_perc if LooseVersion(__version__) < SKIMAGE_VERSION: raise RuntimeError('scikit-image >= %s needed for rescaling.' % SKIMAGE_VERSION) # scale to [2 - range, range] zoom = 2 - zoom_range + 2*(zoom_range - 1)*np.random.rand() arr = rescale(arr, zoom, anti_aliasing=True if zoom < 1 else False, mode='constant', multichannel=False) return arr, zoom
def mask_to_coords(mask, get_contours=False): """Convert a mask to the coordinates of the centroids of each label. Parameters ---------- mask : ndarray binary mask get_contours : bool if True, also output contours Returns ------- centroids and optionally the coordinates. """ assert_nD(mask, 2) assert_binary(mask) labels, num_labels = label(mask, background=0, return_num=True) regions = regionprops(labels, mask) # We can extract the contours as well, if we want. if get_contours: raise NotImplementedError( 'Not Implemented. Use find_countours, mask is stored in `intensity_image` property.' ) centroids = [] for region in regions: centroids.append(region.centroid) return centroids
def tpr(pred, gt, dist=1, mode='region'): """Compute the true positive rate of predictions ground truth (binary map). Both are lists of coordinates. If there is a point in the predictions list which is at most `dist` away from a point in the ground truth, this is counted as a true positive. If there are multiple ground truths, the result is the number of correct hits in the `region` mode, if the mode is `case` then the result is either 0 or 1, depending on if a region is matched or not. Parameters ---------- pred : list or ndarray either a list of lists or a n x 2 ndarray. gt : list or ndarray either a list of lists or a n x 2 ndarray. dist : number distance between points which are considered as positives or negatives. mode : str either `region` or `case`. Returns ------- float """ pred = np.asarray(pred) gt = np.asarray(gt) if mode not in ['region', 'case']: raise ValueError('{} not a valid mode.'.format(mode)) assert_nD(pred, 2, 'predictions') assert_nD(gt, 2, 'ground truth') assert pred.shape[1] == gt.shape[ 1], 'Both the predictions and ground truth should have the same dimensions.' if pred.shape[1] != 2: raise NotImplementedError( 'Currently only 2D comparisons are available. If you wish to extend, ' 'take into account that the distance in the third dimension is often scaled because of a different resolution.' ) hits = np.any(scipy.spatial.distance.cdist(pred, gt) <= dist, axis=0).astype(np.float) if mode == 'region': tpr = hits.sum() / hits.size elif mode == 'case': tpr = float(hits.sum() > 0) return tpr
def random_rotate_2d(arr, angle_range): """Random rotate an array. Parameters ---------- arr : ndarray array to zoom angle_range : int rotation range in degrees Returns ------- Randomly rotated array between -angle_range and angle_range, and the randomly selected angle. """ assert_nD(arr, 2) angle = np.random.randint(-angle_range, angle_range) arr = rotate(arr, angle, mode='constant') return arr, angle
def add_2d_contours(mask, axes, linewidth=0.5, color='r'): """Plot the contours around the `1`'s in the mask Parameters ---------- mask : ndarray 2D binary array. axis : axis object matplotlib axis object. linewidth : float thickness of the overlay lines. color : str matplotlib supported color string for contour overlay. TODO: In utils.mask_utils we have function which computes one contour, perhaps these can be merged. """ assert_nD(mask, 2, 'mask') assert_binary(mask, 'mask') contours = find_contours(mask, 0.5) for contour in contours: axes.plot(*(contour[:, [1, 0]].T), color=color, linewidth=linewidth)
def add_2d_overlay(overlay, ax, linewidth, threshold=0.1, cmap='jet', alpha=0.1, closing_radius=15, contour_color='g'): """Adds an overlay of the probability map and predicted regions overlay : ndarray ax : axis object matplotlib axis object linewidth : float thickness of the overlay lines; threshold : float cmap : str matplotlib supported cmap. alpha : float alpha values for the overlay. closing_radius : int radius for the postprocessing closing contour_color : str matplotlib supported color for the contour. """ assert_nD(overlay, 2, 'overlay') assert_prob(overlay, 'overlay') if threshold: overlay = ma.masked_where(overlay < threshold, overlay) ax.imshow(overlay, cmap=cmap, alpha=alpha) if contour_color: mask = closing(overlay.copy(), disk(closing_radius)) mask[mask < threshold] = 0 mask[mask >= threshold] = 1 add_2d_contours(mask, ax, linewidth=linewidth, color=contour_color)
def find_contour(mask, tolerance=0): """""" assert_nD(mask, 2) assert_binary(mask) contours = find_contours(mask, 0.5) if len(contours) != 1: raise ValueError('To find the contour, the mask cannot have holes.') contour = contours[0] # Check if contour is closed if np.all(contour[0] == contour[-1]): return contour # Otherwise we close the contour # Check if mask is to the left or right point_0 = contour[0] point_1 = contour[1] point_end = contour[-1] diff = point_1 - point_0 is_left = diff[0] > 0 # If mask is left, connect first point along a straight line to the edge if is_left: new_start = np.array([point_0[0], 0]) new_end = np.array([point_end[0], 0]) else: new_start = np.array([point_0[0], mask.shape[1] - 1]) new_end = np.array([point_end[0], mask.shape[1] - 1]) contour = np.vstack([new_start, contour, new_end, new_start]) contour = approximate_polygon(contour, tolerance) return contour
def bounding_box(mask): """Compute the bounding box for a given binary mask, and the center of mass Parameters ---------- mask : ndarray binary array Returns ------- List of ((col, row, height, width), (col, row)) tuples. """ assert_nD(mask, 2, 'mask') assert_binary(mask, 'mask') regions = regionprops(label(mask)) bboxes = [] for region in regions: center_of_mass = map(int, region.centroid) bbox_ = np.array(region.bbox).astype(np.int) bbox = np.concatenate([bbox_[:2], bbox_[2:] - bbox_[:2]]).tolist() bboxes.append((bbox, center_of_mass)) return bboxes