Example #1
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
Example #2
0
def count_nb_fov(recipe):
    """Count the number of different fields of view that can be defined from
    the recipe.

    Parameters
    ----------
    recipe : dict
        Map the images according to their field of view, their round,
        their channel and their spatial dimensions. Can only contain the keys
        `pattern`, `fov`, `r`, `c`, `z`, `ext` or `opt`.

    Returns
    -------
    nb_fov : int
        Number of different fields of view in the recipe.

    """
    # check parameters
    stack.check_parameter(recipe=dict)

    # check if the recipe is fitted
    if not _is_recipe_fitted(recipe):
        recipe = fit_recipe(recipe)

    # a good recipe should have a list in the 'fov' key
    if not isinstance(recipe["fov"], list):
        raise TypeError("'fov' should be a List or a str, not {0}".format(
            type(recipe["fov"])))
    else:
        return len(recipe["fov"])
Example #3
0
def _rescaled_erf(low, high, mu, sigma):
    """Rescaled the Error function along a specific axis.

    # TODO add equations

    Parameters
    ----------
    low : np.ndarray, np.float
        Lower bound of the voxel along a specific axis.
    high : np.ndarray, np.float
        Upper bound of the voxel along a specific axis.
    mu : float
        Estimated mean of the gaussian signal along a specific axis.
    sigma : float
        Estimated standard deviation of the gaussian signal along a specific
        axis.

    Returns
    -------
    rescaled_erf : np.ndarray, np.float
        Rescaled erf along a specific axis.

    """
    # check parameters
    stack.check_parameter(low=np.ndarray,
                          high=np.ndarray,
                          mu=(float, int),
                          sigma=(float, int))

    # compute erf and normalize it
    low_ = (low - mu) / (np.sqrt(2) * sigma)
    high_ = (high - mu) / (np.sqrt(2) * sigma)
    rescaled_erf = sigma * np.sqrt(np.pi / 2) * (erf(high_) - erf(low_))

    return rescaled_erf
Example #4
0
def check_datamap(data_map):
    """Check and validate a data map.

    Checking a data map consists in validating the recipe-folder pairs.

    Parameters
    ----------
    data_map : List[tuple]
        Map between input directories and recipes.

    Returns
    -------
    _ : bool
        Assert if the data map is well formatted.

    """
    stack.check_parameter(data_map=list)
    for pair in data_map:
        if not isinstance(pair, (tuple, list)):
            raise TypeError("A data map is a list with tuples or lists. "
                            "Not {0}".format(type(pair)))
        if len(pair) != 2:
            raise ValueError(
                "Elements of a data map are tuples or lists that "
                "map a recipe (dict) to an input directory "
                "(string). Here {0} elements are given {1}".format(
                    len(pair), pair))
        (recipe, input_folder) = pair
        if not isinstance(input_folder, str):
            raise TypeError("A data map map a recipe (dict) to an input "
                            "directory (string). Not ({0}, {1})".format(
                                type(recipe), type(input_folder)))
        check_recipe(recipe, data_directory=input_folder)

    return True
Example #5
0
def get_nb_element_per_dimension(recipe):
    """Count the number of element to stack for each dimension (`r`, `c`
    and `z`).

    Parameters
    ----------
    recipe : dict
        Map the images according to their field of view, their round,
        their channel and their spatial dimensions. Only contain the keys
        `fov`, `r`, `c`, `z`, `ext` or `opt`.

    Returns
    -------
    nb_r : int
        Number of rounds to be stacked.
    nb_c : int
        Number of channels to be stacked.
    nb_z : int
        Number of z layers to be stacked.

    """
    # check parameters
    stack.check_parameter(recipe=dict)

    # check if the recipe is fitted
    if not _is_recipe_fitted(recipe):
        recipe = fit_recipe(recipe)

    return len(recipe["r"]), len(recipe["c"]), len(recipe["z"])
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
Example #7
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
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
Example #9
0
def build_distance_layers(cyt_coord, nuc_coord, rna_coord, normalized=True):
    """Compute distance layers as input for the model.

    Parameters
    ----------
    cyt_coord : np.ndarray, np.int64
        Array of cytoplasm boundaries coordinates with shape (nb_points, 2).
    nuc_coord : np.ndarray, np.int64
        Array of nucleus boundaries coordinates with shape (nb_points, 2).
    rna_coord : np.ndarray, np.int64
        Array of mRNAs coordinates with shape (nb_points, 2) or
        (nb_points, 3).
    normalized : bool
        Normalized the layers between 0 and 1.
    Returns
    -------
    distance_cyt : np.ndarray, np.float32
        A 2-d tensor with shape (y, x) showing distance to the cytoplasm
        border. Normalize between 0 and 1 if 'normalized' True.
    distance_nuc : np.ndarray, np.float32
        A 2-d tensor with shape (y, x) showing distance to the nucleus border.
        Normalize between 0 and 1 if 'normalized' True.

    """
    # check parameters
    stack.check_array(cyt_coord,
                      ndim=2,
                      dtype=[np.int64])
    if nuc_coord is not None:
        stack.check_array(nuc_coord,
                          ndim=2,
                          dtype=[np.int64])
    if rna_coord is not None:
        stack.check_array(rna_coord,
                          ndim=2,
                          dtype=[np.int64])
    stack.check_parameter(normalized=bool)

    # build surface binary matrices from coordinates
    cyt_surface, nuc_surface, rna_layer, _ = stack.from_coord_to_surface(
        cyt_coord=cyt_coord,
        nuc_coord=nuc_coord,
        rna_coord=rna_coord)

    # compute distances map for cytoplasm and nucleus
    cyt_distance = ndi.distance_transform_edt(cyt_surface)
    nuc_distance_ = ndi.distance_transform_edt(~nuc_surface)
    nuc_distance = cyt_surface * nuc_distance_

    if normalized:
        # cast to np.float32 and normalize it between 0 and 1
        cyt_distance = cyt_distance / cyt_distance.max()
        nuc_distance = nuc_distance / nuc_distance.max()

    # cast layer in float32
    cyt_distance = stack.cast_img_float32(cyt_distance)
    nuc_distance = stack.cast_img_float32(nuc_distance)
    rna_layer = stack.cast_img_float32(rna_layer)

    return cyt_distance, nuc_distance, rna_layer
Example #10
0
def read_index_template(path_template_directory):
    """Load and read dataframe with templates metadata.

    Parameters
    ----------
    path_template_directory : str
        Path of the templates directory.

    Returns
    -------
    df : pd.DataFrame
        Dataframe with the templates metadata. Columns are:

        * 'id' instance id.
        * 'shape' shape of the cell image (with the format '{z}_{y}_{x}').
        * 'protrusion_flag' presence or not of protrusion in  the instance.

    """
    # check parameter
    stack.check_parameter(path_template_directory=str)

    # read dataframe
    path = os.path.join(path_template_directory, "index.csv")
    df = stack.read_dataframe_from_csv(path)

    return df
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
Example #12
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
Example #13
0
 def foo(a, b, c, d, e, f, g, h):
     stack.check_parameter(a=(list, type(None)),
                           b=str,
                           c=int,
                           d=float,
                           e=np.ndarray,
                           f=bool,
                           g=(pd.DataFrame, pd.Series),
                           h=pd.DataFrame)
     return True
Example #14
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
Example #15
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
Example #16
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
Example #17
0
def get_object_radius_pixel(voxel_size_nm, object_radius_nm, ndim):
    """Convert the object radius in pixel.

    When the object considered is a spot this value can be interpreted as the
    standard deviation of the spot PSF, in pixel. For any object modelled with
    a gaussian signal, this value can be interpreted as the standard deviation
    of the gaussian.

    Parameters
    ----------
    voxel_size_nm : int, float, Tuple(int, float) or List(int, float)
        Size of a voxel, in nanometer. One value per spatial dimension (zyx or
        yx dimensions). If it's a scalar, the same value is applied to every
        dimensions.
    object_radius_nm : int, float, Tuple(int, float) or List(int, float)
        Radius of the object, in nanometer. One value per spatial dimension
        (zyx or yx dimensions). If it's a scalar, the same radius is applied to
        every dimensions.
    ndim : int
        Number of spatial dimension to consider.

    Returns
    -------
    object_radius_px : Tuple[float]
        Radius of the object in pixel, one element per dimension (zyx or yx
        dimensions).

    """
    # check parameters
    stack.check_parameter(voxel_size_nm=(int, float, tuple, list),
                          object_radius_nm=(int, float, tuple, list),
                          ndim=int)

    # check consistency between parameters
    if isinstance(voxel_size_nm, (tuple, list)):
        if len(voxel_size_nm) != ndim:
            raise ValueError("'voxel_size_nm' must be a scalar or a sequence "
                             "with {0} elements.".format(ndim))
    else:
        voxel_size_nm = (voxel_size_nm, ) * ndim
    if isinstance(object_radius_nm, (tuple, list)):
        if len(object_radius_nm) != ndim:
            raise ValueError("'object_radius_nm' must be a scalar or a "
                             "sequence with {0} elements.".format(ndim))
    else:
        object_radius_nm = (object_radius_nm, ) * ndim

    # get radius in pixel
    object_radius_px = [b / a for a, b in zip(voxel_size_nm, object_radius_nm)]
    object_radius_px = tuple(object_radius_px)

    return object_radius_px
Example #18
0
def build_templates(path_template_directory, protrusion=None):
    """Return a generator to simulate several templates.

    Parameters
    ----------
    path_template_directory : str
        Path of the templates directory.
    protrusion : bool, optional
        Generate only templates with protrusion or not. If None, all the
        templates are generated.

    Returns
    -------
    _ : Tuple generator
        cell_mask : np.ndarray, bool
            Binary mask of the cell surface with shape (z, y, x).
        cell_map : np.ndarray, np.float32
            Distance map from cell membrane with shape (z, y, x).
        nuc_mask : np.ndarray, bool
            Binary mask of the nucleus surface with shape (z, y, x).
        nuc_map : np.ndarray, np.float32
            Distance map from nucleus membrane with shape (z, y, x).
        protrusion_mask : np.ndarray, bool
            Binary mask of the protrusion surface with shape (y, x).
        protrusion_map : np.ndarray, np.float32
            Distance map from protrusion region with shape (y, x).

    """
    # check parameters
    stack.check_parameter(path_template_directory=str,
                          protrusion=(bool, type(None)))

    # get index template
    df = read_index_template(path_template_directory)

    # filter templates with protrusion
    if protrusion is None:
        indices = list(df.loc[:, "id"])
    elif protrusion:
        indices = df.loc[df.loc[:, "protrusion_flag"] == "protrusion", "id"]
    else:
        indices = df.loc[df.loc[:, "protrusion_flag"] == "noprotrusion", "id"]

    # loop over requested templates
    for i in indices:
        template = build_template(
            path_template_directory=path_template_directory,
            i_cell=i,
            index_template=df,
            protrusion=protrusion)

        yield template
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
Example #20
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 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
Example #22
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
Example #23
0
def _get_spot_volume(image, spot_z, spot_y, spot_x, radius_z, radius_yx):
    """Get a subimage of a detected spot in 3-d.

    Parameters
    ----------
    image : np.ndarray, np.uint
        A 3-d image with detected spot and shape (z, y, x)).
    spot_z : np.int64
        Coordinate of the detected spot along the z axis.
    spot_y : np.int64
        Coordinate of the detected spot along the y axis.
    spot_x : np.int64
        Coordinate of the detected spot along the x axis.
    radius_z : float or int
        Estimated radius of the spot along the z-dimension.
    radius_yx : float or int
        Estimated radius of the spot on the yx-plan.

    Returns
    -------
    image_spot : np.ndarray
        Reference spot with shape (2*radius_z+1, 2*radius_y+1, 2*radius_x+1).

    """
    # check parameters
    stack.check_array(image,
                      ndim=3,
                      dtype=[np.uint8, np.uint16, np.float32, np.float64],
                      allow_nan=True)
    stack.check_parameter(spot_z=np.int64,
                          spot_y=np.int64,
                          spot_x=np.int64,
                          radius_z=(float, int),
                          radius_yx=(float, int))

    # get boundaries of the volume surrounding the spot
    z_spot_min = max(0, int(spot_z - radius_z))
    z_spot_max = min(image.shape[0], int(spot_z + radius_z))
    y_spot_min = max(0, int(spot_y - radius_yx))
    y_spot_max = min(image.shape[1], int(spot_y + radius_yx))
    x_spot_min = max(0, int(spot_x - radius_yx))
    x_spot_max = min(image.shape[2], int(spot_x + radius_yx))

    # get the volume of the spot
    image_spot = image[z_spot_min:z_spot_max + 1, y_spot_min:y_spot_max + 1,
                       x_spot_min:x_spot_max + 1]

    return image_spot
Example #24
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
Example #25
0
def features_area(cell_mask, nuc_mask, cell_mask_out_nuc, check_input=True):
    """Compute area related features.

    Parameters
    ----------
    cell_mask : np.ndarray, bool
        Surface of the cell with shape (y, x).
    nuc_mask : np.ndarray, bool
        Surface of the nucleus with shape (y, x).
    cell_mask_out_nuc : np.ndarray, bool
        Surface of the cell (outside the nucleus) with shape (y, x).
    check_input : bool
        Check input validity.

    Returns
    -------
    nuc_relative_area : float
        Proportion of nucleus area in the cell.
    cell_area : float
        Cell area (in pixels).
    nuc_area : float
        Nucleus area (in pixels).
    cell_area_out_nuc : float
        Cell area outside the nucleus (in pixels).

    """
    # check parameters
    stack.check_parameter(check_input=bool)
    if check_input:
        stack.check_array(cell_mask, ndim=2, dtype=bool)
        stack.check_array(nuc_mask, ndim=2, dtype=bool)
        stack.check_array(cell_mask_out_nuc, ndim=2, dtype=bool)

    # get area of the cell and the nucleus
    cell_area = float(cell_mask.sum())
    nuc_area = float(nuc_mask.sum())

    # compute relative area of the nucleus
    nuc_relative_area = nuc_area / cell_area

    # compute area of the cell outside nucleus
    cell_area_out_nuc = float(cell_mask_out_nuc.sum())

    # return features
    features = (nuc_relative_area, cell_area, nuc_area, cell_area_out_nuc)

    return features
Example #26
0
def extract_spots_from_frame(spots, z_lim=None, y_lim=None, x_lim=None):
    """Get spots coordinates within a given frame.

    Parameters
    ----------
    spots : np.ndarray
        Coordinate of the spots detected inside foci, with shape (nb_spots, 3)
        or (nb_spots, 4). One coordinate per dimension (zyx coordinates) plus
        the index of the foci if necessary.
    z_lim : tuple[int, int]
        Minimum and maximum coordinate of the frame along the z axis.
    y_lim : tuple[int, int]
        Minimum and maximum coordinate of the frame along the y axis.
    x_lim : tuple[int, int]
        Minimum and maximum coordinate of the frame along the x axis.

    Returns
    -------
    extracted_spots : np.ndarray
        Coordinate of the spots detected inside foci, with shape (nb_spots, 3)
        or (nb_spots, 4). One coordinate per dimension (zyx coordinates) plus
        the index of the foci if necessary.

    """
    # check parameters
    stack.check_array(spots, ndim=2, dtype=[np.int64, np.float64])
    stack.check_parameter(z_lim=(tuple, type(None)),
                          y_lim=(tuple, type(None)),
                          x_lim=(tuple, type(None)))

    # extract spots
    extracted_spots = spots.copy()
    if z_lim is not None:
        extracted_spots = extracted_spots[extracted_spots[:, 0] < z_lim[1]]
        extracted_spots = extracted_spots[z_lim[0] < extracted_spots[:, 0]]
        extracted_spots[:, 0] -= z_lim[0]
    if y_lim is not None:
        extracted_spots = extracted_spots[extracted_spots[:, 1] < y_lim[1]]
        extracted_spots = extracted_spots[y_lim[0] < extracted_spots[:, 1]]
        extracted_spots[:, 1] -= y_lim[0]
    if x_lim is not None:
        extracted_spots = extracted_spots[extracted_spots[:, 2] < x_lim[1]]
        extracted_spots = extracted_spots[x_lim[0] < extracted_spots[:, 2]]
        extracted_spots[:, 2] -= x_lim[0]

    return extracted_spots
Example #27
0
def convert_spot_coordinates(spots, voxel_size):
    """Convert spots coordinates from pixel to nanometer.

    Parameters
    ----------
    spots : np.ndarray
        Coordinates of the detected spots with shape (nb_spots, 3) or
        (nb_spots, 2).
    voxel_size : int, float, Tuple(int, float) or List(int, float)
        Size of a voxel, in nanometer. One value per spatial dimension (zyx or
        yx dimensions). If it's a scalar, the same value is applied to every
        dimensions.

    Returns
    -------
    spots_nanometer : np.ndarray
        Coordinates of the detected spots with shape (nb_spots, 3) or
        (nb_spots, 3), in nanometer.

    """
    # check parameters
    stack.check_parameter(voxel_size=(int, float, tuple, list))
    stack.check_array(spots, ndim=2, dtype=[np.float64, np.int64])
    dtype = spots.dtype

    # check consistency between parameters
    ndim = spots.shape[1]
    if isinstance(voxel_size, (tuple, list)):
        if len(voxel_size) != ndim:
            raise ValueError("'voxel_size' must be a scalar or a sequence "
                             "with {0} elements.".format(ndim))
    else:
        voxel_size = (voxel_size, ) * ndim

    # convert spots coordinates in nanometer
    spots_nanometer = spots.copy()
    if ndim == 3:
        spots_nanometer[:, 0] *= voxel_size[0]
        spots_nanometer[:, 1:] *= voxel_size[-1]

    else:
        spots_nanometer *= voxel_size[-1]
    spots_nanometer = spots_nanometer.astype(dtype)

    return spots_nanometer
Example #28
0
def from_coord_to_frame(coord, external_coord=True):
    """Initialize a frame shape to represent coordinates values in 2-d matrix.

    If coordinates represent the external boundaries of an object, we add 1 to
    the minimum coordinate and substract 1 to the maximum coordinate in order
    to build the frame. The frame centers the coordinates by default.

    Parameters
    ----------
    coord : np.ndarray, np.int64
        Array of cell boundaries coordinates with shape (nb_points, 2) or
        (nb_points, 3).
    external_coord : bool
        Coordinates represent external boundaries of object.

    Returns
    -------
    frame_shape : tuple
        Shape of the 2-d matrix.
    min_y : int
        Value tu substract from the y coordinate axis.
    min_x : int
        Value tu substract from the x coordinate axis.
    marge : int
        Value to add to the coordinates.

    """
    # check parameter
    stack.check_parameter(external_coord=bool)

    # initialize marge
    marge = stack.get_margin_value()

    # from 2D coordinates boundaries to binary boundaries
    if external_coord:
        min_y, max_y = coord[:, 0].min() + 1, coord[:, 0].max() - 1
        min_x, max_x = coord[:, 1].min() + 1, coord[:, 1].max() - 1
    else:
        min_y, max_y = coord[:, 0].min(), coord[:, 0].max()
        min_x, max_x = coord[:, 1].min(), coord[:, 1].max()
    shape_y = max_y - min_y + 1
    shape_x = max_x - min_x + 1
    frame_shape = (shape_y + 2 * marge, shape_x + 2 * marge)

    return frame_shape, min_y, min_x, marge
Example #29
0
def features_foci(rna_coord, foci_coord, ndim, check_input=True):
    """Compute foci 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.
    foci_coord : np.ndarray, np.int64
        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
        spots detected in the foci and its index.
    ndim : int
        Number of spatial dimensions to consider.
    check_input : bool
        Check input validity.

    Returns
    -------
    proportion_rna_in_foci : float
        Proportion of RNAs detected in a foci.

    """
    # check parameters
    stack.check_parameter(check_input=bool)
    if check_input:
        stack.check_parameter(ndim=int)
        if ndim not in [2, 3]:
            raise ValueError("'ndim' should be 2 or 3, not {0}.".format(ndim))
        stack.check_array(rna_coord, ndim=2, dtype=np.int64)
        stack.check_array(foci_coord, ndim=2, dtype=np.int64)

    if len(rna_coord) == 0 or len(foci_coord) == 0:
        features = (0.,)
        return features

    # compute proportion RNAs in foci
    nb_rna = len(rna_coord)
    nb_rna_in_foci = foci_coord[:, ndim].sum()
    proportion_rna_in_foci = nb_rna_in_foci / nb_rna

    features = (proportion_rna_in_foci,)

    return features
Example #30
0
def fit_recipe(recipe):
    """Fit a recipe.

    Fitting a recipe consists in wrapping every values of `fov`, `r`, `c` and
    `z` in a list (an empty one if necessary). Values for `ext` and `opt` are
    also initialized.

    Parameters
    ----------
    recipe : dict
        Map the images according to their field of view, their round,
        their channel and their spatial dimensions. Can only contain the keys
        `pattern`, `fov`, `r`, `c`, `z`, `ext` or `opt`.

    Returns
    -------
    new_recipe : dict
        Map the images according to their field of view, their round,
        their channel and their spatial dimensions. Contain the keys
        `pattern`, `fov`, `r`, `c`, `z`, `ext` and `opt`, initialized if
        necessary.

    """
    # check parameters
    stack.check_parameter(recipe=dict)

    # initialize recipe
    new_recipe = copy.deepcopy(recipe)

    # initialize and fit the dimensions 'fov', 'r', 'c' and 'z'
    for key in ['fov', 'r', 'c', 'z']:
        if key not in new_recipe:
            new_recipe[key] = [None]
        value = new_recipe[key]
        if isinstance(value, str):
            new_recipe[key] = [value]

    # initialize the dimensions 'ext', 'opt'
    for key in ['ext', 'opt']:
        if key not in new_recipe:
            new_recipe[key] = ""

    return new_recipe