def assign_plot_values(y_true, y_pred, error_dict): """Generates a matrix with cells belong to error classes numbered for plotting Args: y_true: 2D matrix of true labels y_pred 2D matrix of predicted labels error_dict: dictionary produced by save_error_ids with IDs of all error cells Returns: plotting_tiff: 2D matrix with cells belonging to same error class having same value """ plotting_tif = np.zeros_like(y_true) # erode edges for easier visualization of adjacent cells y_true = erode_edges(y_true, 1) y_pred = erode_edges(y_pred, 1) # missed detections are tracked with true labels misses = error_dict.pop('misses')['y_true'] plotting_tif[np.isin(y_true, misses)] = 1 # all other events are tracked with predicted labels category_id = 2 for key in error_dict.keys(): labels = error_dict[key]['y_pred'] plotting_tif[np.isin(y_pred, labels)] = category_id category_id += 1 return plotting_tif
def plot_errors(self): """Plots the errors identified from linear assignment code. This must be run with sequentially relabeled data. TODO: this is not working! """ import matplotlib as mpl import matplotlib.pyplot as plt # erode edges for easier visualization of adjacent cells y_true = erode_edges(self.y_true.copy(), 1) y_pred = erode_edges(self.y_pred.copy(), 1) # semantic labels for each error categories = [ 'Background', 'missed', 'splits', 'merges', 'gained', 'catastrophes', 'correct' ] # Background is set to zero plotting_tif = np.zeros_like(y_true) # missed detections are tracked with true labels misses = [d.true_index for d in self._missed] plotting_tif[np.isin(y_true, misses)] = 1 # skip background and misses, already done for i, category in enumerate(categories[2:]): # the rest are all on y_pred labels = list(getattr(self, '_{}'.format(category))) plotting_tif[np.isin(y_pred, labels)] = i + 2 plotting_colors = [ 'Black', 'Pink', 'Blue', 'Green', 'tan', 'Red', 'Grey' ] cmap = mpl.colors.ListedColormap(plotting_colors) fig, ax = plt.subplots(nrows=1, ncols=1) mat = ax.imshow(plotting_tif, cmap=cmap, vmin=np.min(plotting_tif) - .5, vmax=np.max(plotting_tif) + .5) # tell the colorbar to tick at integers ticks = np.arange(len(categories)) cbar = fig.colorbar(mat, ticks=ticks) cbar.ax.set_yticklabels(categories) fig.tight_layout()
def distance_transform_3d(maskstack, bins=4, erosion_width=None): """Transforms a label mask for a z stack into distance classes. Uses scipy's distance_transform_edt Args: maskstack (numpy.array): a z-stack of label masks (y data) bins (int): the number of transformed distance classes erosion_width (int): number of pixels to erode edges of each labels Returns: numpy.array: 3D Euclidiean Distance Transform """ maskstack = np.squeeze(maskstack) # squeeze the channels maskstack = erode_edges(maskstack, erosion_width) distance = ndimage.distance_transform_edt(maskstack, sampling=[0.5, 0.217, 0.217]) # normalize by maximum distance for cell_label in np.unique(maskstack): if cell_label == 0: # distance is only found for non-zero regions continue index = np.nonzero(maskstack == cell_label) distance[index] = distance[index] / np.amax(distance[index]) # divide into bins min_dist = np.amin(distance.flatten()) max_dist = np.amax(distance.flatten()) bins = np.linspace(min_dist - K.epsilon(), max_dist + K.epsilon(), num=bins + 1) distance = np.digitize(distance, bins, right=True) return distance - 1 # minimum distance should be 0, not 1
def centroid_transform_continuous_2d(mask, erosion_width=None, alpha=0.1): """Transform a label mask into a continuous centroid value. Args: mask (numpy.array): a label mask (y data) erosion_width (int): number of pixels to erode edges of each labels alpha (float): coefficent to reduce the magnitude of the distance value. Returns: numpy.array: a mask of same shape as input mask, with each label being a distance class from 1 to bins """ mask = np.squeeze(mask) mask = erode_edges(mask, erosion_width) distance = ndimage.distance_transform_edt(mask) distance = distance.astype(K.floatx()) label_matrix = label(mask) inner_distance = np.zeros(distance.shape, dtype=K.floatx()) for prop in regionprops(label_matrix, distance): coords = prop.coords center = prop.weighted_centroid distance_to_center = np.sum((coords - center) ** 2, axis=1) center_transform = 1 / (1 + alpha * distance_to_center) coords_x = coords[:, 0] coords_y = coords[:, 1] inner_distance[coords_x, coords_y] = center_transform return inner_distance
def distance_transform_continuous_movie(mask, erosion_width=None): """Transform a label mask into a continuous distance value. Args: mask (numpy.array): a label mask (y data) erosion_width (int): number of pixels to erode edges of each labels Returns: numpy.array: a mask of same shape as input mask, with each label being a distance class from 1 to bins """ distances = [] for frame in range(mask.shape[0]): mask_frame = mask[frame] mask_frame = np.squeeze(mask_frame) # squeeze the channels mask_frame = erode_edges(mask_frame, erosion_width) distance = ndimage.distance_transform_edt(mask_frame) distance = distance.astype(K.floatx()) # normalized distances are floats # uniquely label each cell and normalize the distance values # by that cells maximum distance value label_matrix = label(mask_frame) for prop in regionprops(label_matrix): labeled_distance = distance[label_matrix == prop.label] normalized_distance = labeled_distance / np.amax(labeled_distance) distance[label_matrix == prop.label] = normalized_distance distances.append(distance) distances = np.stack(distances, axis=0) return distances # minimum distance should be 0, not 1
def distance_transform_continuous_2d(mask, erosion_width=None): """Transform a label mask into distance classes. Args: mask (numpy.array): a label mask (y data) bins (int): the number of transformed distance classes erosion_width (int): number of pixels to erode edges of each labels Returns: numpy.array: a mask of same shape as input mask, with each label being a distance class from 1 to bins """ mask = np.squeeze(mask) # squeeze the channels mask = erode_edges(mask, erosion_width) distance = ndimage.distance_transform_edt(mask) distance = distance.astype(K.floatx()) # normalized distances are floats # uniquely label each cell and normalize the distance values # by that cells maximum distance value label_matrix = label(mask) for prop in regionprops(label_matrix): labeled_distance = distance[label_matrix == prop.label] normalized_distance = labeled_distance / np.amax(labeled_distance) distance[label_matrix == prop.label] = normalized_distance return distance # minimum distance should be 0, not 1
def distance_transform_2d(mask, bins=16, erosion_width=None): """Transform a label mask into distance classes. Args: mask (numpy.array): a label mask (y data) bins (int): the number of transformed distance classes erosion_width (int): number of pixels to erode edges of each labels Returns: numpy.array: a mask of same shape as input mask, with each label being a distance class from 1 to bins """ mask = np.squeeze(mask) # squeeze the channels mask = erode_edges(mask, erosion_width) distance = ndimage.distance_transform_edt(mask) distance = distance.astype(K.floatx()) # normalized distances are floats # uniquely label each cell and normalize the distance values # by that cells maximum distance value label_matrix = label(mask) for prop in regionprops(label_matrix): labeled_distance = distance[label_matrix == prop.label] normalized_distance = labeled_distance / np.amax(labeled_distance) distance[label_matrix == prop.label] = normalized_distance # bin each distance value into a class from 1 to bins min_dist = np.amin(distance) max_dist = np.amax(distance) bins = np.linspace(min_dist - K.epsilon(), max_dist + K.epsilon(), num=bins + 1) distance = np.digitize(distance, bins, right=True) return distance - 1 # minimum distance should be 0, not 1
def centroid_transform_continuous_2d(mask, erosion_width=None, alpha=0.1, beta=1): """Transform a label mask into a continuous centroid value. Args: mask (numpy.array): a label mask (y data) erosion_width (int): number of pixels to erode edges of each labels alpha (float, str): coefficent to reduce the magnitude of the distance value. If 'auto', determines alpha for each cell based on the cell area. Defaults to 0.1. beta (float): scale parameter that is used when alpha is set to auto. Defaults to 1. Returns: numpy.array: a mask of same shape as input mask, with each label being a distance class from 1 to bins """ # Check input to alpha if isinstance(alpha, str): if alpha.lower() != 'auto': raise ValueError('alpha must be set to "auto"') mask = np.squeeze(mask) mask = erode_edges(mask, erosion_width) distance = ndimage.distance_transform_edt(mask) distance = distance.astype(K.floatx()) label_matrix = label(mask) inner_distance = np.zeros(distance.shape, dtype=K.floatx()) for prop in regionprops(label_matrix, distance): coords = prop.coords center = prop.weighted_centroid distance_to_center = np.sum((coords - center)**2, axis=1) # Determine alpha to use if str(alpha).lower() == 'auto': _alpha = 1 / np.sqrt(prop.area) else: _alpha = float(alpha) center_transform = 1 / (1 + beta * _alpha * distance_to_center) coords_x = coords[:, 0] coords_y = coords[:, 1] inner_distance[coords_x, coords_y] = center_transform return inner_distance
def centroid_transform_continuous_movie(mask, erosion_width=None, alpha=0.1, beta=1): """Transform a label mask into a continuous centroid value. Args: mask (numpy.array): a label mask (y data) erosion_width (int): number of pixels to erode edges of each labels alpha (float): coefficent to reduce the magnitude of the distance value. Returns: numpy.array: a mask of same shape as input mask, with each label being a distance class from 1 to bins """ inner_distances = [] for frame in range(mask.shape[0]): mask_frame = mask[frame] mask_frame = np.squeeze(mask_frame) mask_frame = erode_edges(mask_frame, erosion_width) distance = ndimage.distance_transform_edt(mask_frame) distance = distance.astype(K.floatx()) label_matrix = label(mask_frame) inner_distance = np.zeros(distance.shape, dtype=K.floatx()) for prop in regionprops(label_matrix, distance): coords = prop.coords center = prop.weighted_centroid distance_to_center = np.sum((coords - center)**2, axis=1) # Determine alpha to use if str(alpha).lower() == 'auto': _alpha = 1 / np.sqrt(prop.area) else: _alpha = float(alpha) center_transform = 1 / (1 + beta * _alpha * distance_to_center) coords_x = coords[:, 0] coords_y = coords[:, 1] inner_distance[coords_x, coords_y] = center_transform inner_distances.append(inner_distance) inner_distances = np.stack(inner_distances, axis=0) return inner_distances
def outer_distance_transform_3d(mask, bins=None, erosion_width=None, normalize=True, sampling=[0.5, 0.217, 0.217]): """Transforms a label mask for a z stack with an outer distance transform. Uses scipy's distance_transform_edt Args: mask (numpy.array): A z-stack of label masks (y data). bins (int): The number of transformed distance classes. Defaults to None. erosion_width (int): Number of pixels to erode edges of each labels. Defaults to None. normalize (boolean): Normalize the transform of each cell by that cell's largest distance. Defaults to True. sampling (list): Spacing of pixels along each dimension. Defaults to [0.5, 0.217, 0.217]. Returns: numpy.array: 3D Euclidiean Distance Transform """ maskstack = np.squeeze(mask) # squeeze the channels maskstack = erode_edges(maskstack, erosion_width) distance = ndimage.distance_transform_edt(maskstack, sampling=sampling) # normalize by maximum distance if normalize: for cell_label in np.unique(maskstack): if cell_label == 0: # distance is only found for non-zero regions continue index = np.nonzero(maskstack == cell_label) distance[index] = distance[index] / np.amax(distance[index]) if bins is None: return distance # divide into bins min_dist = np.amin(distance.flatten()) max_dist = np.amax(distance.flatten()) distance_bins = np.linspace(min_dist - K.epsilon(), max_dist + K.epsilon(), num=bins + 1) distance = np.digitize(distance, distance_bins, right=True) return distance - 1 # minimum distance should be 0, not 1
def outer_distance_transform_2d(mask, bins=None, erosion_width=None, normalize=True): """Transform a label mask with an outer distance transform. Args: mask (numpy.array): A label mask (``y`` data). bins (int): The number of transformed distance classes. If ``None``, returns the continuous outer transform. erosion_width (int): Number of pixels to erode edges of each labels normalize (bool): Normalize the transform of each cell by that cell's largest distance. Returns: numpy.array: A mask of same shape as input mask, with each label being a distance class from 1 to ``bins``. """ mask = np.squeeze(mask) # squeeze the channels mask = erode_edges(mask, erosion_width) distance = ndimage.distance_transform_edt(mask) distance = distance.astype(K.floatx()) # normalized distances are floats if normalize: # uniquely label each cell and normalize the distance values # by that cells maximum distance value label_matrix = label(mask) for prop in regionprops(label_matrix): labeled_distance = distance[label_matrix == prop.label] normalized_distance = labeled_distance / np.amax(labeled_distance) distance[label_matrix == prop.label] = normalized_distance if bins is None: return distance # bin each distance value into a class from 1 to bins min_dist = np.amin(distance) max_dist = np.amax(distance) distance_bins = np.linspace(min_dist - K.epsilon(), max_dist + K.epsilon(), num=bins + 1) distance = np.digitize(distance, distance_bins, right=True) return distance - 1 # minimum distance should be 0, not 1
def test_assign_plot_values(self): y_true, _ = random_shapes(image_shape=(200, 200), max_shapes=30, min_shapes=15, min_size=10, multichannel=False) # invert background y_true[y_true == 255] = 0 y_true, _, _ = relabel_sequential(y_true) error_dict = { 'misses': { 'y_true': [1, 2] }, 'splits': { 'y_pred': [3, 4] }, 'merges': { 'y_pred': [5, 6] }, 'gains': { 'y_pred': [7, 8] }, 'catastrophes': { 'y_pred': [9, 10] }, 'correct': { 'y_pred': [11, 12] } } plotting_tiff = metrics.assign_plot_values(y_true, y_true, error_dict) # erode edges so that shape matches shape in plotting tiff y_true = erode_edges(y_true, 1) for error_type in error_dict.keys(): vals = list(error_dict[error_type].values()) mask = np.isin(y_true, vals) assert len(np.unique(plotting_tiff[mask])) == 1
def inner_distance_transform_3d(mask, bins=None, erosion_width=None, alpha=0.1, beta=1, sampling=[0.5, 0.217, 0.217]): """Transform a label mask for a z-stack with an inner distance transform. .. code-block:: python inner_distance = 1 / (1 + beta * alpha * distance_to_center) Args: mask (numpy.array): A label mask (``y`` data). bins (int): The number of transformed distance classes. erosion_width (int): Number of pixels to erode edges of each labels alpha (float, str): Coefficent to reduce the magnitude of the distance value. If ``'auto'``, determines alpha for each cell based on the cell area. beta (float): Scale parameter that is used when ``alpha`` is "auto". sampling (list): Spacing of pixels along each dimension. Returns: numpy.array: A mask of same shape as input mask, with each label being a distance class from 1 to ``bins``. Raises: ValueError: ``alpha`` is a string but not set to "auto". """ # Check input to alpha if isinstance(alpha, str): if alpha.lower() != 'auto': raise ValueError('alpha must be set to "auto"') mask = np.squeeze(mask) mask = erode_edges(mask, erosion_width) distance = ndimage.distance_transform_edt(mask, sampling=sampling) distance = distance.astype(K.floatx()) label_matrix = label(mask) inner_distance = np.zeros(distance.shape, dtype=K.floatx()) for prop in regionprops(label_matrix, distance): coords = prop.coords center = prop.weighted_centroid distance_to_center = (coords - center) * np.array(sampling) distance_to_center = np.sum(distance_to_center**2, axis=1) # Determine alpha to use if str(alpha).lower() == 'auto': _alpha = 1 / np.cbrt(prop.area) else: _alpha = float(alpha) center_transform = 1 / (1 + beta * _alpha * distance_to_center) coords_z = coords[:, 0] coords_x = coords[:, 1] coords_y = coords[:, 2] inner_distance[coords_z, coords_x, coords_y] = center_transform if bins is None: return inner_distance # divide into bins min_dist = np.amin(inner_distance.flatten()) max_dist = np.amax(inner_distance.flatten()) distance_bins = np.linspace(min_dist - K.epsilon(), max_dist + K.epsilon(), num=bins + 1) inner_distance = np.digitize(inner_distance, distance_bins, right=True) return inner_distance - 1 # minimum distance should be 0, not 1
def inner_distance_transform_2d(mask, bins=None, erosion_width=None, alpha=0.1, beta=1): """Transform a label mask with an inner distance transform. inner_distance = 1 / (1 + beta * alpha * distance_to_center) Args: mask (numpy.array): A label mask (y data). bins (int): The number of transformed distance classes. Defaults to None. erosion_width (int): number of pixels to erode edges of each labels alpha (float, str): coefficent to reduce the magnitude of the distance value. If 'auto', determines alpha for each cell based on the cell area. Defaults to 0.1. beta (float): scale parameter that is used when alpha is set to auto. Defaults to 1. Returns: numpy.array: a mask of same shape as input mask, with each label being a distance class from 1 to bins. Raises: ValueError: alpha is a string but not set to "auto". """ # Check input to alpha if isinstance(alpha, str): if alpha.lower() != 'auto': raise ValueError('alpha must be set to "auto"') mask = np.squeeze(mask) mask = erode_edges(mask, erosion_width) distance = ndimage.distance_transform_edt(mask) distance = distance.astype(K.floatx()) label_matrix = label(mask) inner_distance = np.zeros(distance.shape, dtype=K.floatx()) for prop in regionprops(label_matrix, distance): coords = prop.coords center = prop.weighted_centroid distance_to_center = np.sum((coords - center)**2, axis=1) # Determine alpha to use if str(alpha).lower() == 'auto': _alpha = 1 / np.sqrt(prop.area) else: _alpha = float(alpha) center_transform = 1 / (1 + beta * _alpha * distance_to_center) coords_x = coords[:, 0] coords_y = coords[:, 1] inner_distance[coords_x, coords_y] = center_transform if bins is None: return inner_distance # divide into bins min_dist = np.amin(inner_distance.flatten()) max_dist = np.amax(inner_distance.flatten()) distance_bins = np.linspace(min_dist - K.epsilon(), max_dist + K.epsilon(), num=bins + 1) inner_distance = np.digitize(inner_distance, distance_bins, right=True) return inner_distance - 1 # minimum distance should be 0, not 1