예제 #1
0
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
예제 #2
0
    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
예제 #8
0
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
예제 #9
0
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
예제 #10
0
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
예제 #11
0
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
예제 #12
0
    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
예제 #13
0
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
예제 #14
0
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