Пример #1
0
def _compute_background_amplitude(image_spot):
    """Compute amplitude of a spot and background minimum value.

    Parameters
    ----------
    image_spot : np.ndarray, np.uint
        A 3-d image with detected spot and shape (z, y, x).

    Returns
    -------
    psf_amplitude : float or int
        Amplitude of the spot.
    psf_background : float or int
        Background minimum value of the voxel.

    """
    # check parameters
    stack.check_array(image_spot,
                      ndim=[2, 3],
                      dtype=[np.uint8, np.uint16, np.float32, np.float64],
                      allow_nan=False)

    # compute values
    image_min, image_max = image_spot.min(), image_spot.max()
    psf_amplitude = image_max - image_min
    psf_background = image_min

    return psf_amplitude, psf_background
Пример #2
0
def from_surface_to_boundaries(binary_surface):
    """Convert the binary surface to binary boundaries.

    Parameters
    ----------
    binary_surface : np.ndarray, np.uint or np.int or bool
        Binary image with shape (y, x).

    Returns
    -------
    binary_boundaries : np.ndarray, np.uint or np.int or bool
        Binary image with shape (y, x).

    """
    # check parameters
    stack.check_array(binary_surface,
                      ndim=2,
                      dtype=[np.uint8, np.uint16, np.int64, bool])
    original_dtype = binary_surface.dtype

    # pad the binary surface in case object if on the edge
    binary_surface_ = np.pad(binary_surface, [(1, 1)], mode="constant")

    # compute distance map of the surface binary mask
    distance_map = ndi.distance_transform_edt(binary_surface_)

    # get binary boundaries
    binary_boundaries_ = (distance_map < 2) & (distance_map > 0)
    binary_boundaries_ = binary_boundaries_.astype(original_dtype)

    # remove pad
    binary_boundaries = binary_boundaries_[1:-1, 1:-1]

    return binary_boundaries
def build_cyt_binary_mask(image_projected, threshold=None):
    """Compute a binary mask of the cytoplasm.

    Parameters
    ----------
    image_projected : np.ndarray, np.uint
        A 2-d projection of the cytoplasm with shape (y, x).
    threshold : int
        Intensity pixel threshold to compute the binary mask. If None, an Otsu
        threshold is computed.

    Returns
    -------
    mask : np.ndarray, bool
        Binary mask of the cytoplasm with shape (y, x).

    """
    # check parameters
    stack.check_array(image_projected,
                      ndim=2,
                      dtype=[np.uint8, np.uint16])
    stack.check_parameter(threshold=(int, type(None)))

    # get a threshold
    if threshold is None:
        threshold = threshold_otsu(image_projected)

    # compute a binary mask
    mask = (image_projected > threshold)
    mask = remove_small_objects(mask, 3000)
    mask = remove_small_holes(mask, 2000)

    return mask
def log_cc(image, sigma, threshold):
    """Find connected regions above a fixed threshold on a LoG filtered image.

    Parameters
    ----------
    image : np.ndarray
        Image with shape (z, y, x) or (y, x).
    sigma : float or Tuple(float)
        Sigma used for the gaussian filter (one for each dimension). If it's a
        float, the same sigma is applied to every dimensions.
    threshold : float or int
        A threshold to detect peaks. Considered as a relative threshold if
        float.

    Returns
    -------
    cc : np.ndarray, np.int64
        Image labelled with shape (z, y, x) or (y, x).

    """
    # check parameters
    stack.check_array(image,
                      ndim=[2, 3],
                      dtype=[np.uint8, np.uint16, np.float32, np.float64])
    stack.check_parameter(sigma=(float, int, tuple), threshold=(float, int))

    # cast image in np.float and apply LoG filter
    image_filtered = stack.log_filter(image, sigma, keep_dtype=True)

    # find connected components
    cc = get_cc(image_filtered, threshold)

    # TODO return coordinate of the centroid

    return cc
Пример #5
0
def simulate_fitted_gaussian_3d(f, grid, popt, original_shape=None):
    """Use the optimized parameter to simulate a gaussian signal.

    Parameters
    ----------
    f : func
        A 3-d gaussian function with some parameters fixed.
    grid : np.ndarray, np.float
        Grid data to compute the gaussian function for different voxel within
        a volume V. In nanometer, with shape (3, V_z * V_y * V_x).
    popt : np.ndarray
        Fitted parameters.
    original_shape : Tuple
        Shape of the spot image to reshape the simulation.

    Returns
    -------
    values : np.ndarray, np.float
        Value of each voxel within the volume V according to the 3-d gaussian
        parameters. Shape (V_z, V_y, V_x,) or (V_z * V_y * V_x,).

    """
    # check parameters
    stack.check_array(grid, ndim=2, dtype=np.float32, allow_nan=False)
    stack.check_parameter(popt=np.ndarray, original_shape=(tuple, type(None)))

    # compute gaussian values
    values = f(grid, *popt)

    # reshape values if necessary
    if original_shape is not None:
        values = np.reshape(values, original_shape).astype(np.float32)

    return values
Пример #6
0
def complete_coord_boundaries(coord):
    """Complete a 2-d coordinates array, by generating/interpolating missing
    points.

    Parameters
    ----------
    coord : np.ndarray, np.int64
        Array of coordinates to complete, with shape (nb_points, 2).

    Returns
    -------
    coord_completed : np.ndarray, np.int64
        Completed coordinates arrays, with shape (nb_points, 2).

    """
    # check parameters
    stack.check_array(coord, ndim=2, dtype=[np.int64])

    # for each array in the list, complete its coordinates using the scikit
    # image method 'polygon_perimeter'
    coord_y, coord_x = polygon_perimeter(coord[:, 0], coord[:, 1])
    coord_y = coord_y[:, np.newaxis]
    coord_x = coord_x[:, np.newaxis]
    coord_completed = np.concatenate((coord_y, coord_x), axis=-1)

    return coord_completed
Пример #7
0
def from_binary_to_coord(binary):
    """Extract coordinates from a 2-d binary matrix.

    As the resulting coordinates represent the external boundaries of the
    object, the coordinates values can be negative.

    Parameters
    ----------
    binary : np.ndarray, np.uint or np.int or bool
        Binary image with shape (y, x).

    Returns
    -------
    coord : np.ndarray, np.int64
        Array of boundaries coordinates with shape (nb_points, 2).

    """
    # check parameters
    stack.check_array(binary,
                      ndim=2,
                      dtype=[np.uint8, np.uint16, np.int64, bool])

    # we enlarge the binary mask with one pixel to be sure the external
    # boundaries of the object still fit within the frame
    binary_ = np.pad(binary, [(1, 1)], mode="constant")

    # get external boundaries coordinates
    coord = find_contours(binary_, level=0)[0].astype(np.int64)

    # remove the pad
    coord -= 1

    return coord
def get_cc(image, threshold):
    """Find connected regions above a fixed threshold.

    Parameters
    ----------
    image : np.ndarray
        Image with shape (z, y, x) or (y, x).
    threshold : float or int
        A threshold to detect peaks.

    Returns
    -------
    cc : np.ndarray, np.int64
        Image labelled with shape (z, y, x) or (y, x).

    """
    # check parameters
    stack.check_array(image,
                      ndim=[2, 3],
                      dtype=[np.uint8, np.uint16, np.float32, np.float64])
    stack.check_parameter(threshold=(float, int))

    # Compute binary mask of the filtered image
    mask = image > threshold

    # find connected components
    cc = label(mask)

    return cc
Пример #9
0
def compute_mean_size_object(image_labelled):
    """Compute the averaged size of the segmented objects.

    For each object, we compute the diameter of an object with an equivalent
    surface. Then, we average the diameters.

    Parameters
    ----------
    image_labelled : np.ndarray, np.uint
        Labelled image with shape (y, x).

    Returns
    -------
    mean_diameter : float
        Averaged size of the segmented objects.

    """
    # check parameters
    stack.check_array(image_labelled,
                      ndim=2,
                      dtype=[np.uint8, np.uint16, np.int64])

    # compute properties of the segmented object
    props = regionprops(image_labelled)

    # get equivalent diameter and average it
    diameter = []
    for prop in props:
        diameter.append(prop.equivalent_diameter)
    mean_diameter = np.mean(diameter)

    return mean_diameter
Пример #10
0
def from_3_classes_to_instances(label_3_classes):
    """Extract instance labels from 3-classes Unet output.

    Parameters
    ----------
    label_3_classes : np.ndarray, np.float32
        Model prediction about the nucleus surface and boundaries, with shape
        (y, x, 3).

    Returns
    -------
    label : np.ndarray, np.int64
        Labelled image. Each instance is characterized by the same pixel value.

    """
    # check parameters
    stack.check_array(label_3_classes, ndim=3, dtype=[np.float32])

    # get classes indices
    label_3_classes = np.argmax(label_3_classes, axis=-1)

    # keep foreground predictions
    mask = label_3_classes > 1

    # instantiate each individual foreground surface predicted
    label = label_instances(mask)

    # dilate label
    label = label.astype(np.float64)
    label = stack.dilation_filter(label, kernel_shape="disk", kernel_size=1)
    label = label.astype(np.int64)

    return label
Пример #11
0
def _get_nuclear_edge_probability_map(cell_mask, nuc_map):
    """Compute a probability map to sample a nuclear edge pattern.

    Parameters
    ----------
    cell_mask : np.ndarray, bool
        Binary mask of the cell surface with shape (z, y, x).
    nuc_map : np.ndarray, np.float32
        Distance map from the nucleus edge with shape (z, y, x).

    Returns
    -------
    probability_map : np.ndarray, np.float32
        Probability map to sample spots coordinates with shape (z, y, x).

    """
    # check parameters
    stack.check_array(cell_mask, ndim=3, dtype=bool)
    stack.check_array(nuc_map, ndim=3, dtype=[np.float32,  np.float64])

    # nuclear edge probability map
    probability_map = nuc_map.copy().astype(np.float32)
    probability_map[nuc_map > 2.] = 0.
    probability_map[nuc_map <= 2.] = 1.
    probability_map[~cell_mask] = 0.
    probability_map /= probability_map.sum()

    return probability_map
Пример #12
0
def dilate_erode_labels(label):
    """Substract an eroded label to a dilated one in order to prevent
    boundaries contact.

    Parameters
    ----------
    label : np.ndarray, np.uint or np.int
        Labelled image with shape (y, x).

    Returns
    -------
    label_final : np.ndarray, np.int64
        Labelled image with shape (y, x).

    """
    # check parameters
    stack.check_array(label, ndim=2, dtype=[np.uint8, np.uint16, np.int64])

    # handle 64 bit integer
    if label.dtype == np.int64:
        label = label.astype(np.uint16)

    # erode-dilate mask
    label_dilated = stack.dilation_filter(label, "disk", 2)
    label_eroded = stack.erosion_filter(label, "disk", 2)
    borders = label_dilated - label_eroded
    label_final = label.copy()
    label_final[borders > 0] = 0
    label_final = label_final.astype(np.int64)

    return label_final
Пример #13
0
def _get_random_in_probability_map(cell_mask, nuc_mask):
    """Compute a probability map to sample a random pattern inside nucleus.

    Parameters
    ----------
    cell_mask : np.ndarray, bool
        Binary mask of the cell surface with shape (z, y, x).
    nuc_mask : np.ndarray, bool
        Binary mask of the nucleus surface with shape (z, y, x).

    Returns
    -------
    probability_map : np.ndarray, np.float32
        Probability map to sample spots coordinates with shape (z, y, x).

    """
    # check parameters
    stack.check_array(cell_mask, ndim=3, dtype=bool)
    stack.check_array(nuc_mask, ndim=3, dtype=bool)

    # random in probability map
    probability_map = nuc_mask.copy().astype(np.float32)
    probability_map /= probability_map.sum()

    return probability_map
Пример #14
0
def count_instances(image_label):
    """Count the number of instances annotated in the image.

    Parameters
    ----------
    image_label : np.ndarray, np.int or np.uint
        Labelled image with shape (y, x).

    Returns
    -------
    nb_instances : int
        Number of instances in the image.

    """
    # check parameters
    stack.check_array(image_label,
                      ndim=2,
                      dtype=[np.uint8, np.uint16, np.int64])

    indices = set(image_label.ravel())
    if 0 in indices:
        nb_instances = len(indices) - 1
    else:
        nb_instances = len(indices)

    return nb_instances
Пример #15
0
def compute_surface_ratio(image_label):
    """Compute the averaged surface ratio of the segmented instances.

    We compute the proportion of surface occupied by instances.

    Parameters
    ----------
    image_label : np.ndarray, np.int or np.uint
        Labelled image with shape (y, x).

    Returns
    -------
    surface_ratio : float
        Surface ratio of the segmented instances.

    """
    # check parameters
    stack.check_array(image_label,
                      ndim=2,
                      dtype=[np.uint8, np.uint16, np.int64])

    # compute surface ratio
    surface_instances = image_label > 0
    area_instances = surface_instances.sum()
    surface_ratio = area_instances / image_label.size

    return surface_ratio
Пример #16
0
def thresholding(image, threshold):
    """Segment a 2-d image to discriminate object from background applying a
    threshold.

    Parameters
    ----------
    image : np.ndarray, np.uint
        A 2-d image to segment with shape (y, x).
    threshold : int or float
        Pixel intensity threshold used to discriminate foreground from
        background.

    Returns
    -------
    image_segmented : np.ndarray, bool
        Binary 2-d image with shape (y, x).

    """
    # check parameters
    stack.check_array(image, ndim=2, dtype=[np.uint8, np.uint16])
    stack.check_parameter(threshold=(float, int))

    # discriminate nuclei from background, applying a threshold.
    image_segmented = image >= threshold

    return image_segmented
Пример #17
0
def compute_instances_mean_diameter(image_label):
    """Compute the averaged size of the segmented instances.

    For each instance, we compute the diameter of an object with an equivalent
    surface. Then, we average the diameters.

    Parameters
    ----------
    image_label : np.ndarray, np.int64
        Labelled image with shape (y, x).

    Returns
    -------
    mean_diameter : float
        Averaged size of the segmented instances.

    """
    # check parameters
    stack.check_array(image_label, ndim=2, dtype=np.int64)

    # compute properties of the segmented instances
    props = regionprops(image_label)

    # get equivalent diameter and average it
    diameter = []
    for prop in props:
        diameter.append(prop.equivalent_diameter)
    mean_diameter = np.mean(diameter)

    return mean_diameter
Пример #18
0
def fit_gaussian_3d(f,
                    grid,
                    image_spot,
                    p0,
                    lower_bound=None,
                    upper_bound=None):
    """Fit a gaussian function to a 3-d image.

    # TODO add equations and algorithm

    Parameters
    ----------
    f : func
        A 3-d gaussian function with some parameters fixed.
    grid : np.ndarray, np.float
        Grid data to compute the gaussian function for different voxel within
        a volume V. In nanometer, with shape (3, V_z * V_y * V_x).
    image_spot : np.ndarray, np.uint
        A 3-d image with detected spot and shape (z, y, x).
    p0 : List
        List of parameters to estimate.
    lower_bound : List
        List of lower bound values for the different parameters.
    upper_bound : List
        List of upper bound values for the different parameters.

    Returns
    -------
    popt : np.ndarray
        Fitted parameters.
    pcov : np.ndarray
        Estimated covariance of 'popt'.

    """
    # check parameters
    stack.check_array(grid, ndim=2, dtype=np.float32, allow_nan=False)
    stack.check_array(image_spot,
                      ndim=3,
                      dtype=[np.uint8, np.uint16, np.float32, np.float64],
                      allow_nan=False)
    stack.check_parameter(p0=list,
                          lower_bound=(list, type(None)),
                          upper_bound=(list, type(None)))

    # compute lower bound and upper bound
    if lower_bound is None:
        lower_bound = [-np.inf for _ in p0]
    if upper_bound is None:
        upper_bound = [np.inf for _ in p0]
    bounds = (lower_bound, upper_bound)

    # Apply non-linear least squares to fit a gaussian function to a 3-d image
    y = np.reshape(image_spot, (image_spot.size, )).astype(np.float32)
    popt, pcov = curve_fit(f=f, xdata=grid, ydata=y, p0=p0, bounds=bounds)

    return popt, pcov
Пример #19
0
def local_maximum_detection(image, min_distance):
    """Compute a mask to keep only local maximum, in 2-d and 3-d.

    #. We apply a multidimensional maximum filter.
    #. A pixel which has the same value in the original and filtered images
       is a local maximum.

    Several connected pixels can have the same value. In such a case, the
    local maximum is not unique.

    In order to make the detection robust, it should be applied to a
    filtered image (using :func:`bigfish.stack.log_filter` for example).

    Parameters
    ----------
    image : np.ndarray
        Image to process with shape (z, y, x) or (y, x).
    min_distance : int, float, Tuple(int, float), List(int, float)
        Minimum distance (in pixels) between two spots we want to be able to
        detect separately. One value per spatial dimension (zyx or yx
        dimensions). If it's a scalar, the same distance is applied to every
        dimensions.

    Returns
    -------
    mask : np.ndarray, bool
        Mask with shape (z, y, x) or (y, x) indicating the local peaks.

    """
    # check parameters
    stack.check_array(image,
                      ndim=[2, 3],
                      dtype=[np.uint8, np.uint16, np.float32, np.float64])
    stack.check_parameter(min_distance=(int, float, tuple, list))

    # compute the kernel size (centered around our pixel because it is uneven)
    if isinstance(min_distance, (tuple, list)):
        if len(min_distance) != image.ndim:
            raise ValueError(
                "'min_distance' should be a scalar or a sequence with one "
                "value per dimension. Here the image has {0} dimensions and "
                "'min_distance' {1} elements.".format(image.ndim,
                                                      len(min_distance)))
    else:
        min_distance = (min_distance, ) * image.ndim
    min_distance = np.ceil(min_distance).astype(image.dtype)
    kernel_size = 2 * min_distance + 1

    # apply maximum filter to the original image
    image_filtered = ndi.maximum_filter(image, size=kernel_size)

    # we keep the pixels with the same value before and after the filtering
    mask = image == image_filtered

    return mask
Пример #20
0
def identify_objects_in_region(mask, coord, ndim):
    """Identify cellular objects in specific region.

    Parameters
    ----------
    mask : np.ndarray, bool
        Binary mask of the targeted region with shape (y, x).
    coord : np.ndarray
        Array with two dimensions. One object per row, zyx or yx coordinates
        in the first 3 or 2 columns.
    ndim : int
        Number of spatial dimensions to consider (2 or 3).

    Returns
    -------
    coord_in : np.ndarray
        Coordinates of the objects detected inside the region.
    coord_out : np.ndarray
        Coordinates of the objects detected outside the region.

    """
    # check parameters
    stack.check_parameter(ndim=int)
    stack.check_array(mask,
                      ndim=2,
                      dtype=[np.uint8, np.uint16, np.int64, bool])
    stack.check_array(coord, ndim=2, dtype=[np.int64, np.float64])

    # check number of dimensions
    if ndim not in [2, 3]:
        raise ValueError("The number of spatial dimension requested should be "
                         "2 or 3, not {0}.".format(ndim))
    if coord.shape[1] < ndim:
        raise ValueError("Coord array should have at least {0} features to "
                         "match the number of spatial dimensions requested. "
                         "Currently {1} is not enough.".format(
                             ndim, coord.shape[1]))

    # binarize nuclei mask if needed
    if mask.dtype != bool:
        mask = mask.astype(bool)

    # cast coordinates dtype if necessary
    if coord.dtype == np.int64:
        coord_int = coord
    else:
        coord_int = np.round(coord).astype(np.int64)

    # remove objects inside the region
    mask_in = mask[coord_int[:, ndim - 2], coord_int[:, ndim - 1]]
    coord_in = coord[mask_in]
    coord_out = coord[~mask_in]

    return coord_in, coord_out
Пример #21
0
def clean_segmentation(image,
                       small_object_size=None,
                       fill_holes=False,
                       smoothness=None,
                       delimit_instance=False):
    """Clean segmentation results (binary masks or integer labels).

    Parameters
    ----------
    image : np.ndarray, np.int64 or bool
        Labelled or masked image with shape (y, x).
    small_object_size : int or None
        Areas with a smaller surface (in pixels) are removed.
    fill_holes : bool
        Fill holes within a labelled or masked area.
    smoothness : int or None
        Radius of a median kernel filter. The higher the smoother instance
        boundaries are.
    delimit_instance : bool
        Delimit clearly instances boundaries by preventing contact between each
        others.

    Returns
    -------
    image_cleaned : np.ndarray, np.int64 or bool
        Cleaned image with shape (y, x).

    """
    # check parameters
    stack.check_array(image, ndim=2, dtype=[np.int64, bool])
    stack.check_parameter(small_object_size=(int, type(None)),
                          fill_holes=bool,
                          smoothness=(int, type(None)),
                          delimit_instance=bool)

    # initialize cleaned image
    image_cleaned = image.copy()

    # remove small object
    if small_object_size is not None:
        image_cleaned = _remove_small_area(image_cleaned, small_object_size)

    # fill holes
    if fill_holes:
        image_cleaned = _fill_hole(image_cleaned)

    if smoothness:
        image_cleaned = _smooth_instance(image_cleaned, smoothness)

    if delimit_instance:
        image_cleaned = _delimit_instance(image_cleaned)

    return image_cleaned
def plot_illumination_surface(illumination_surface,
                              r=0,
                              framesize=(15, 15),
                              titles=None,
                              path_output=None,
                              ext="png"):
    """Subplot the yx plan of the dimensions of an illumination surface for
    all channels.

    Parameters
    ----------
    illumination_surface : np.ndarray, np.float
        A 4-d tensor with shape (r, c, y, x) approximating the average
        differential of illumination in our stack of images, for each channel
        and each round.
    r : int
        Index of the round to keep.
    framesize : tuple
        Size of the frame used to plot with 'plt.figure(figsize=framesize)'.
    titles : List[str]
        Titles of the subplots (one per channel).
    path_output : str
        Path to save the image (without extension).
    ext : str or List[str]
        Extension used to save the plot. If it is a list of strings, the plot
        will be saved several times.

    Returns
    -------

    """
    # TODO add title in the plot and remove axes
    # TODO add parameter for vmin and vmax
    # check tensor
    stack.check_array(illumination_surface,
                      ndim=4,
                      dtype=[np.float32, np.float64])

    # get the number of channels
    nb_channels = illumination_surface.shape[1]

    # plot
    fig, ax = plt.subplots(1, nb_channels, sharex='col', figsize=framesize)
    for i in range(nb_channels):
        ax[i].imshow(illumination_surface[r, i, :, :])
        if titles is not None:
            ax[i].set_title(titles[i], fontweight="bold", fontsize=15)
    plt.tight_layout()
    save_plot(path_output, ext)
    plt.show()

    return
Пример #23
0
def remove_transcription_site(rna, clusters, nuc_mask, ndim):
    """Distinguish RNA molecules detected in a transcription site from the
    rest.

    A transcription site is defined as as a foci detected within the nucleus.

    Parameters
    ----------
    rna : np.ndarray
        Coordinates of the detected RNAs with shape (nb_spots, 4) or
        (nb_spots, 3). One coordinate per dimension (zyx or yx coordinates)
        plus the index of the cluster assigned to the RNA. If no cluster was
        assigned, value is -1.
    clusters : np.ndarray
        Array with shape (nb_clusters, 5) or (nb_clusters, 4). One coordinate
        per dimension for the clusters centroid (zyx or yx coordinates),
        the number of RNAs detected in the clusters and their index.
    nuc_mask : np.ndarray, bool
        Binary mask of the nuclei region with shape (y, x).
    ndim : int
        Number of spatial dimensions to consider (2 or 3).

    Returns
    -------
    rna_out_ts : np.ndarray
        Coordinates of the detected RNAs with shape (nb_spots, 4) or
        (nb_spots, 3). One coordinate per dimension (zyx or yx coordinates)
        plus the index of the foci assigned to the RNA. If no foci was
        assigned, value is -1. RNAs from transcription sites are removed.
    foci : np.ndarray
        Array with shape (nb_foci, 5) or (nb_foci, 4). One coordinate per
        dimension for the foci centroid (zyx or yx coordinates),
        the number of RNAs detected in the foci and its index.
    ts : np.ndarray
        Array with shape (nb_ts, 5) or (nb_ts, 4). One coordinate per
        dimension for the transcription site centroid (zyx or yx coordinates),
        the number of RNAs detected in the transcription site and its index.

    """
    # check parameters
    stack.check_array(rna, ndim=2, dtype=[np.int64, np.float64])

    # discriminate foci from transcription sites
    ts, foci = identify_objects_in_region(nuc_mask, clusters, ndim)

    # filter out rna from transcription sites
    rna_in_ts = ts[:, ndim + 1]
    mask_rna_in_ts = np.isin(rna[:, ndim], rna_in_ts)
    rna_out_ts = rna[~mask_rna_in_ts]

    return rna_out_ts, foci, ts
Пример #24
0
def add_white_noise(image, noise_level, random_noise=0.05):
    """Generate and add white noise to an image.

    Parameters
    ----------
    image : np.ndarray
        Image with shape (z, y, x) or (y, x).
    noise_level : int or float
        Reference level of noise background to add in the image.
    random_noise : int or float, default=0.05
        Background noise follows a normal distribution around the provided
        noise values. The scale used is:

        .. math::
                \\mbox{scale} = \\mbox{noise_level} * \\mbox{random_noise}

    Returns
    -------
    noised_image : np.ndarray
        Noised image with shape (z, y, x) or (y, x).

    """
    # check parameters
    stack.check_array(image,
                      ndim=[2, 3],
                      dtype=[np.uint8, np.uint16, np.float32, np.float64])
    stack.check_parameter(noise_level=(int, float), random_noise=(int, float))

    # original dtype
    original_dtype = image.dtype
    if np.issubdtype(original_dtype, np.integer):
        original_max_bound = np.iinfo(original_dtype).max
    else:
        original_max_bound = np.finfo(original_dtype).max

    # compute scale
    scale = noise_level * random_noise

    # generate noise
    noise_raw = np.random.normal(loc=noise_level, scale=scale, size=image.size)
    noise_raw[noise_raw < 0] = 0.
    noise_raw = np.random.poisson(lam=noise_raw, size=noise_raw.size)
    noise = np.reshape(noise_raw, image.shape)

    # add noise
    noised_image = image.astype(np.float64) + noise
    noised_image = np.clip(noised_image, 0, original_max_bound)
    noised_image = noised_image.astype(original_dtype)

    return noised_image
def spots_thresholding(image, sigma, mask_lm, threshold):
    """Filter detected spots and get coordinates of the remaining
    spots.

    Parameters
    ----------
    image : np.ndarray, np.uint
        Image with shape (z, y, x) or (y, x).
    sigma : float or Tuple(float)
        Sigma used for the gaussian filter (one for each dimension). If it's a
        float, the same sigma is applied to every dimensions.
    mask_lm : np.ndarray, bool
        Mask with shape (z, y, x) or (y, x) indicating the local peaks.
    threshold : float or int
        A threshold to detect peaks.

    Returns
    -------
    spots : np.ndarray, np.int64
        Coordinate of the local peaks with shape (nb_peaks, 3) or
        (nb_peaks, 2) for 3-d or 2-d images respectively.
    radius : float or Tuple(float)
        Radius of the detected peaks.
    mask : np.ndarray, bool
        Mask with shape (z, y, x) or (y, x) indicating the spots.

    """
    # TODO make 'radius' output more consistent
    # check parameters
    stack.check_array(image,
                      ndim=[2, 3],
                      dtype=[np.uint8, np.uint16, np.float32, np.float64])
    stack.check_array(mask_lm, ndim=[2, 3], dtype=[bool])
    stack.check_parameter(sigma=(float, int, tuple), threshold=(float, int))

    # remove peak with a low intensity
    mask = (mask_lm & (image > threshold))

    # get peak coordinates
    spots = np.nonzero(mask)
    spots = np.column_stack(spots)

    # compute radius
    if isinstance(sigma, tuple):
        radius = [np.sqrt(image.ndim) * sigma_ for sigma_ in sigma]
        radius = tuple(radius)
    else:
        radius = np.sqrt(image.ndim) * sigma

    return spots, radius, mask
Пример #26
0
def automated_threshold_setting(image, mask_local_max):
    """Automatically set the optimal threshold to detect spots.

    In order to make the thresholding robust, it should be applied to a
    filtered image (using :func:`bigfish.stack.log_filter` for example). The
    optimal threshold is selected based on the spots distribution. The latter
    should have an elbow curve discriminating a fast decreasing stage from a
    more stable one (a plateau).

    Parameters
    ----------
    image : np.ndarray
        Image with shape (z, y, x) or (y, x).
    mask_local_max : np.ndarray, bool
        Mask with shape (z, y, x) or (y, x) indicating the local peaks.

    Returns
    -------
    optimal_threshold : int
        Optimal threshold to discriminate spots from noisy blobs.

    """
    # check parameters
    stack.check_array(image,
                      ndim=[2, 3],
                      dtype=[np.uint8, np.uint16, np.float32, np.float64])
    stack.check_array(mask_local_max, ndim=[2, 3], dtype=[bool])

    # get threshold values we want to test
    thresholds = _get_candidate_thresholds(image.ravel())

    # get spots count and its logarithm
    first_threshold = float(thresholds[0])
    spots, mask_spots = spots_thresholding(image,
                                           mask_local_max,
                                           first_threshold,
                                           remove_duplicate=False)
    value_spots = image[mask_spots]
    thresholds, count_spots = _get_spot_counts(thresholds, value_spots)

    # select threshold where the break of the distribution is located
    if count_spots.size > 0:
        optimal_threshold, _, _ = get_breaking_point(thresholds, count_spots)

    # case where no spots were detected
    else:
        optimal_threshold = None

    return optimal_threshold
def log_lm(image, sigma, threshold, minimum_distance=1):
    """Apply LoG filter followed by a Local Maximum algorithm to detect spots
    in a 2-d or 3-d image.

    1) We smooth the image with a LoG filter.
    2) We apply a multidimensional maximum filter.
    3) A pixel which has the same value in the original and filtered images
    is a local maximum.
    4) We remove local peaks under a threshold.

    Parameters
    ----------
    image : np.ndarray
        Image with shape (z, y, x) or (y, x).
    sigma : float or Tuple(float)
        Sigma used for the gaussian filter (one for each dimension). If it's a
        float, the same sigma is applied to every dimensions.
    threshold : float or int
        A threshold to detect peaks.
    minimum_distance : int
        Minimum distance (in number of pixels) between two local peaks.

    Returns
    -------
    spots : np.ndarray, np.int64
        Coordinate of the spots with shape (nb_spots, 3) or (nb_spots, 2)
        for 3-d or 2-d images respectively.
    radius : float, Tuple[float]
        Radius of the detected peaks.

    """
    # check parameters
    stack.check_array(image,
                      ndim=[2, 3],
                      dtype=[np.uint8, np.uint16, np.float32, np.float64])
    stack.check_parameter(sigma=(float, int, tuple),
                          minimum_distance=(float, int),
                          threshold=(float, int))

    # cast image in np.float and apply LoG filter
    image_filtered = stack.log_filter(image, sigma, keep_dtype=True)

    # find local maximum
    mask = local_maximum_detection(image_filtered, minimum_distance)

    # remove spots with a low intensity and return coordinates and radius
    spots, radius, _ = spots_thresholding(image, sigma, mask, threshold)

    return spots, radius
Пример #28
0
def match_nuc_cell(nuc_label, cell_label):
    """Match each nucleus instance with the most overlapping cell instance.

    Parameters
    ----------
    nuc_label : np.ndarray, np.int or np.uint
        Labelled image of nuclei with shape (y, x).
    cell_label : np.ndarray, np.int or np.uint
        Labelled image of cells with shape (y, x).

    Returns
    -------
    new_nuc_label : np.ndarray, np.int or np.uint
        Labelled image of nuclei with shape (y, x).
    new_cell_label : np.ndarray, np.int or np.uint
        Labelled image of cells with shape (y, x).

    """
    # check parameters
    stack.check_array(nuc_label, ndim=2, dtype=[np.uint8, np.uint16, np.int64])
    stack.check_array(cell_label,
                      ndim=2,
                      dtype=[np.uint8, np.uint16, np.int64])

    # initialize new labelled images
    new_nuc_label = np.zeros_like(nuc_label)
    new_cell_label = np.zeros_like(cell_label)

    # match nuclei and cells
    label_max = nuc_label.max()
    for i_nuc in range(1, label_max + 1):
        # check if a nucleus is labelled with this value
        nuc_mask = nuc_label == i_nuc
        if nuc_mask.sum() == 0:
            continue
        # check if a cell is labelled with this value
        i_cell = _get_most_frequent_value(cell_label[nuc_mask])
        if i_cell == 0:
            continue
        # assign cell and nucleus
        cell_mask = cell_label == i_cell
        cell_mask |= nuc_mask
        new_nuc_label[nuc_mask] = i_nuc
        new_cell_label[cell_mask] = i_nuc
        # remove pixel already assigned
        nuc_label[nuc_mask] = 0
        cell_label[cell_mask] = 0

    return new_nuc_label, new_cell_label
Пример #29
0
def local_maximum_detection(image, min_distance):
    """Compute a mask to keep only local maximum, in 2-d and 3-d.

    1) We apply a multidimensional maximum filter.
    2) A pixel which has the same value in the original and filtered images
    is a local maximum.

    Parameters
    ----------
    image : np.ndarray
        Image to process with shape (z, y, x) or (y, x).
    min_distance : int, float or Tuple(float)
        Minimum distance (in pixels) between two spots we want to be able to
        detect separately. One value per spatial dimension (zyx or
        yx dimensions). If it's a scalar, the same distance is applied to
        every dimensions.

    Returns
    -------
    mask : np.ndarray, bool
        Mask with shape (z, y, x) or (y, x) indicating the local peaks.

    """
    # check parameters
    stack.check_array(image,
                      ndim=[2, 3],
                      dtype=[np.uint8, np.uint16, np.float32, np.float64])
    stack.check_parameter(min_distance=(float, int, tuple))

    # compute the kernel size (centered around our pixel because it is uneven)
    if isinstance(min_distance, (int, float)):
        min_distance = (min_distance,) * image.ndim
        min_distance = np.ceil(min_distance).astype(image.dtype)
    elif image.ndim != len(min_distance):
        raise ValueError("'min_distance' should be a scalar or a tuple with "
                         "one value per dimension. Here the image has {0} "
                         "dimensions and 'min_distance' {1} elements."
                         .format(image.ndim, len(min_distance)))
    else:
        min_distance = np.ceil(min_distance).astype(image.dtype)
    kernel_size = 2 * min_distance + 1

    # apply maximum filter to the original image
    image_filtered = ndi.maximum_filter(image, size=kernel_size)

    # we keep the pixels with the same value before and after the filtering
    mask = image == image_filtered

    return mask
Пример #30
0
def features_in_out_nucleus(rna_coord, rna_coord_out_nuc, check_input=True):
    """Compute nucleus related features.

    Parameters
    ----------
    rna_coord : np.ndarray, np.int64
        Coordinates of the detected RNAs with zyx or yx coordinates in the
        first 3 or 2 columns.
    rna_coord_out_nuc : np.ndarray, np.int64
        Coordinates of the detected RNAs with zyx or yx coordinates in the
        first 3 or 2 columns. Spots detected inside the nucleus are removed.
    check_input : bool
        Check input validity.

    Returns
    -------
    proportion_rna_in_nuc : float
        Proportion of RNAs detected inside the nucleus.
    nb_rna_out_nuc : float
        Number of RNAs detected outside the nucleus.
    nb_rna_in_nuc : float
        Number of RNAs detected inside the nucleus.

    """
    # check parameters
    stack.check_parameter(check_input=bool)
    if check_input:
        stack.check_array(rna_coord, ndim=2, dtype=np.int64)
        stack.check_array(rna_coord_out_nuc, ndim=2, dtype=np.int64)

    # count total number of rna
    nb_rna = float(len(rna_coord))

    # case where no rna are detected
    if nb_rna == 0:
        features = (0., 0., 0.)
        return features

    # number of rna outside and inside nucleus
    nb_rna_out_nuc = float(len(rna_coord_out_nuc))
    nb_rna_in_nuc = nb_rna - nb_rna_out_nuc

    # compute the proportion of rna in the nucleus
    proportion_rna_in_nuc = nb_rna_in_nuc / nb_rna

    features = (proportion_rna_in_nuc, nb_rna_out_nuc, nb_rna_in_nuc)

    return features