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
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"])
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
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
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
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
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
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
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
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
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
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
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 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
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
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
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
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
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
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
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
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
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
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
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