Esempio n. 1
0
    def assign_foci_ts(self):
        '''
        Split foci into foci that overlap the nucleus (ts)
        and foci which do not overlap.
        Extract the results for the FOV.
        '''
        nuc_label = stack.read_image(self.nuc_mask)
        if self.cell_mask is None:
            cell_label = np.ones(nuc_label.shape, dtype='int64')

        self.spots_no_ts, self.non_ts_foci, self.ts = stack.remove_transcription_site(
            self.spots_and_foci, self.foci, nuc_label, ndim=3)

        image_contrasted = stack.rescale(self.rna, channel_to_stretch=0)
        image_contrasted = stack.maximum_projection(image_contrasted)
        self.nuc_mip = stack.maximum_projection(self.nuc)

        #Get results for field of view
        self.fov_results = stack.extract_cell(cell_label=cell_label,
                                              ndim=3,
                                              nuc_label=nuc_label,
                                              rna_coord=self.spots_no_ts,
                                              others_coord={
                                                  "foci": self.non_ts_foci,
                                                  "transcription_site": self.ts
                                              },
                                              image=image_contrasted,
                                              others_image={
                                                  "dapi": self.nuc_mip,
                                                  "smfish": self.rna_mip
                                              },
                                              remove_cropped_cell=False,
                                              check_nuc_in_cell=False)

        print("number of cells identified: {0}".format(len(self.fov_results)))
Esempio n. 2
0
def test_maximum_projection():
    # uint8
    y = stack.maximum_projection(x)
    expected_y = np.array([[3, 0, 0, 0, 0], [0, 3, 0, 0, 3], [0, 3, 0, 0, 0],
                           [0, 3, 3, 3, 0], [0, 0, 0, 0, 0]],
                          dtype=np.uint8)
    assert_array_equal(y, expected_y)
    assert y.dtype == np.uint8

    # uint16
    y = stack.maximum_projection(x.astype(np.uint16))
    expected_y = expected_y.astype(np.uint16)
    assert_array_equal(y, expected_y)
    assert y.dtype == np.uint16
Esempio n. 3
0
 def __init__(self,
              fov_name,
              fish_img,
              dapi_img,
              args,
              nuc_mask=None,
              cell_mask=None,
              show=False):
     self.voxel_size_z = args.voxel_size_z
     self.voxel_size_yx = args.voxel_size_yx
     self.nuc_mask = nuc_mask
     self.cell_mask = cell_mask
     if hasattr(args, 'manual_threshold'):
         self.manual_threshold = args.manual_threshold
     else:
         self.manual_threshold = None
     self.show = show
     self.psf_z, self.psf_yx = calculate_psf(self.voxel_size_z,
                                             self.voxel_size_yx, args.Ex,
                                             args.Em, args.NA, args.RI,
                                             args.microscope)
     self.rna = stack.read_image(fish_img)
     self.nuc = stack.read_image(dapi_img)
     self.rna_mip = stack.maximum_projection(self.rna)
     self.fov_name = fov_name
     #would be good to determine vmax automatically from intensity of single RNAs
     self.vmax = args.vmax
     self.foci_radius = args.foci_radius
     self.nb_in_foci = args.nb_in_foci
     self.plotdir = os.path.join(args.outdir, 'plots')
     self.datadir = os.path.join(args.outdir, 'results')
     os.makedirs(self.plotdir, exist_ok=True)
     os.makedirs(self.datadir, exist_ok=True)
Esempio n. 4
0
def test_maximum_projection(dtype):
    x = x_3d.astype(dtype)
    expected_y = np.array([[3, 0, 0, 0, 0], [0, 3, 0, 0, 3], [0, 3, 0, 0, 0],
                           [0, 3, 3, 3, 0], [0, 0, 0, 0, 0]],
                          dtype=dtype)
    y = stack.maximum_projection(x)
    assert_array_equal(y, expected_y)
    assert y.dtype == dtype
Esempio n. 5
0
def cell_watershed(image, nuc_label, threshold, alpha=0.8):
    """Apply watershed algorithm to segment cell instances.

    In a watershed algorithm we consider cells as watershed to be flooded. The
    watershed relief is inversely proportional to both the pixel intensity and
    the closeness to nuclei. Pixels with a high intensity or close to labelled
    nuclei have a low watershed relief value. They will be flooded in priority.
    Flooding the watersheds allows to propagate nuclei labels through potential
    cytoplasm areas. The lines separating watershed are the final segmentation
    of the cells.

    Parameters
    ----------
    image : np.ndarray, np.uint
        Cells image with shape (y, x).
    nuc_label : np.ndarray, np.int64
        Result of the nuclei segmentation with shape (y, x) and nuclei
        instances labelled.
    threshold : int or float
        Threshold to discriminate cells surfaces from background.
    alpha : float or int
        Weight of the pixel intensity values to compute the watershed relief.

    Returns
    -------
    cell_label : np.ndarray, np.int64
        Segmentation of cells with shape (y, x).

    """
    # build relief
    relief = get_watershed_relief(image, nuc_label, alpha)

    # TODO improve cell mask methods
    # TODO add options for clean_segmentation
    # build cells mask
    if image.ndim == 3:
        image_2d = stack.maximum_projection(image)
    else:
        image_2d = image
    cell_mask = thresholding(image_2d, threshold)
    cell_mask[nuc_label > 0] = True
    cell_mask = clean_segmentation(
        cell_mask,
        small_object_size=5000,
        fill_holes=True)

    # segment cells
    cell_label = apply_watershed(relief, nuc_label, cell_mask)

    return cell_label
Esempio n. 6
0
    def fct_to_process(i, n):
        # simulate images
        image, ground_truth = sim.simulate_image(
            ndim=ndim,
            n_spots=n_spots,
            random_n_spots=random_n_spots,
            n_clusters=n_clusters,
            random_n_clusters=random_n_clusters,
            n_spots_cluster=n,
            random_n_spots_cluster=random_n_spots_cluster,
            centered_cluster=centered_cluster,
            image_shape=image_shape,
            image_dtype=np.uint16,
            subpixel_factors=subpixel_factors,
            voxel_size=voxel_size,
            sigma=sigma,
            random_sigma=random_sigma,
            amplitude=amplitude,
            random_amplitude=random_amplitude,
            noise_level=noise_level,
            random_noise=random_noise)

        # save image
        path = os.path.join(path_directory_image, "image_{0}.tif".format(i))
        stack.save_image(image, path)

        # complete ground truth and save it
        new_column = np.array([n] * len(ground_truth))
        new_column = new_column[:, np.newaxis]
        ground_truth = np.hstack([ground_truth, new_column])
        path = os.path.join(path_directory_gt, "gt_{0}.csv".format(i))
        stack.save_data_to_csv(ground_truth, path)

        # plot
        path = os.path.join(path_directory_plot, "plot_{0}.png".format(i))
        image_mip = stack.maximum_projection(image)
        plot.plot_images(
            images=image_mip,
            rescale=True,
            titles=["Number of spots: {0}".format(len(ground_truth))],
            framesize=(8, 8),
            remove_frame=False,
            path_output=path,
            show=False)

        return
Esempio n. 7
0
 def __init__(self, fov_name, fish_img, dapi_img, args, nuc_mask=None, cell_mask=None):
     self.voxel_size_z = args.voxel_size_z
     self.voxel_size_xy = args.voxel_size_xy
     self.nuc_mask = nuc_mask
     self.cell_mask = cell_mask
     self.psf_z, self.psf_xy = calculate_psf(self.voxel_size_z, self.voxel_size_xy,
                                             args.Ex, args.Em, args.NA, args.RI,
                                             args.microscope)
     self.rna = stack.read_image(fish_img)
     self.nuc = stack.read_image(dapi_img)
     self.rna_mip = stack.maximum_projection(self.rna)
     self.fov_name = fov_name
     self.cluster_radius = args.cluster_radius
     self.nb_in_cluster = args.nb_in_cluster
     self.plotdir = os.path.join(args.outdir, 'plots')
     self.datadir = os.path.join(args.outdir, 'results')
     os.makedirs(self.plotdir, exist_ok = True)
     os.makedirs(self.datadir, exist_ok = True)
Esempio n. 8
0
def plot_reference_spot(reference_spot,
                        rescale=False,
                        contrast=False,
                        title=None,
                        framesize=(5, 5),
                        remove_frame=True,
                        path_output=None,
                        ext="png",
                        show=True):
    """Plot the selected yx plan of the selected dimensions of an image.

    Parameters
    ----------
    reference_spot : np.ndarray
        Spot image with shape (z, y, x) or (y, x).
    rescale : bool, default=False
        Rescale pixel values of the image (made by default in matplotlib).
    contrast : bool, default=False
        Contrast image.
    title : str, optional
        Title of the image.
    framesize : tuple, default=(5, 5)
        Size of the frame used to plot with ``plt.figure(figsize=framesize)``.
    remove_frame : bool, default=True
        Remove axes and frame.
    path_output : str, optional
        Path to save the image (without extension).
    ext : str or list, default='png'
        Extension used to save the plot. If it is a list of strings, the plot
        will be saved several times.
    show : bool, default=True
        Show the figure or not.

    """
    # check parameters
    stack.check_array(
        reference_spot,
        ndim=[2, 3],
        dtype=[np.uint8, np.uint16, np.int64, np.float32, np.float64])
    stack.check_parameter(rescale=bool,
                          contrast=bool,
                          title=(str, type(None)),
                          framesize=tuple,
                          remove_frame=bool,
                          path_output=(str, type(None)),
                          ext=(str, list),
                          show=bool)

    # project spot in 2-d if necessary
    if reference_spot.ndim == 3:
        reference_spot = stack.maximum_projection(reference_spot)

    # plot reference spot
    if remove_frame:
        fig = plt.figure(figsize=framesize, frameon=False)
        ax = fig.add_axes([0, 0, 1, 1])
        ax.axis('off')
    else:
        plt.figure(figsize=framesize)
    if not rescale and not contrast:
        vmin, vmax = get_minmax_values(reference_spot)
        plt.imshow(reference_spot, vmin=vmin, vmax=vmax)
    elif rescale and not contrast:
        plt.imshow(reference_spot)
    else:
        if reference_spot.dtype not in [np.int64, bool]:
            reference_spot = stack.rescale(reference_spot,
                                           channel_to_stretch=0)
        plt.imshow(reference_spot)
    if title is not None and not remove_frame:
        plt.title(title, fontweight="bold", fontsize=25)
    if not remove_frame:
        plt.tight_layout()
    if path_output is not None:
        save_plot(path_output, ext)
    if show:
        plt.show()
    else:
        plt.close()
Esempio n. 9
0
def compute_features(cell_mask, nuc_mask, ndim, rna_coord, smfish=None,
                     voxel_size_yx=None, foci_coord=None,
                     centrosome_coord=None,
                     compute_distance=False, compute_intranuclear=False,
                     compute_protrusion=False, compute_dispersion=False,
                     compute_topography=False, compute_foci=False,
                     compute_area=False, compute_centrosome=False,
                     return_names=False):
    """Compute requested features.

    Parameters
    ----------
    cell_mask : np.ndarray, np.uint, np.int or bool
        Surface of the cell with shape (y, x).
    nuc_mask: np.ndarray, np.uint, np.int or bool
        Surface of the nucleus with shape (y, x).
    ndim : int
        Number of spatial dimensions to consider (2 or 3).
    rna_coord : np.ndarray, np.int64
        Coordinates of the detected spots with shape (nb_spots, 4) or
        (nb_spots, 3). One coordinate per dimension (zyx or yx dimensions)
        plus the index of the cluster assigned to the spot. If no cluster was
        assigned, value is -1. If cluster id is not provided foci related
        features are not computed.
    smfish : np.ndarray, np.uint
        Image of RNAs, with shape (y, x).
    voxel_size_yx : int, float or None
        Size of a voxel on the yx plan, in nanometer.
    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.
    centrosome_coord : np.ndarray, np.int64
        Coordinates of the detected centrosome with shape (nb_elements, 3) or
        (nb_elements, 2). One coordinate per dimension (zyx or yx dimensions).
        These coordinates are mandatory to compute centrosome related features.
    compute_distance : bool
        Compute distance related features.
    compute_intranuclear : bool
        Compute nucleus related features.
    compute_protrusion : bool
        Compute protrusion related features.
    compute_dispersion : bool
        Compute dispersion indices.
    compute_topography : bool
        Compute topographic features.
    compute_foci : bool
        Compute foci related features.
    compute_area : bool
        Compute area related features.
    compute_centrosome : bool
        Compute centrosome related features.
    return_names : bool
        Return features names.

    Returns
    -------
    features : np.ndarray, np.float32
        Array of features.

    """
    # check parameters
    stack.check_parameter(voxel_size_yx=(int, float, type(None)),
                          compute_distance=bool,
                          compute_intranuclear=bool,
                          compute_protrusion=bool,
                          compute_dispersion=bool,
                          compute_topography=bool,
                          compute_foci=bool,
                          compute_area=bool,
                          compute_centrosome=bool,
                          return_names=bool)
    if smfish is not None:
        stack.check_array(smfish, ndim=[2, 3], dtype=[np.uint8, np.uint16])
        if smfish.ndim == 3:
            smfish = stack.maximum_projection(smfish)
    if foci_coord is not None:
        stack.check_array(foci_coord, ndim=2, dtype=np.int64)

    # prepare input data
    (cell_mask,
     distance_cell, distance_cell_normalized,
     centroid_cell, distance_centroid_cell,
     nuc_mask, cell_mask_out_nuc,
     distance_nuc, distance_nuc_normalized,
     centroid_nuc, distance_centroid_nuc,
     rna_coord_out_nuc,
     centroid_rna, distance_centroid_rna,
     centroid_rna_out_nuc, distance_centroid_rna_out_nuc,
     distance_centrosome) = prepare_extracted_data(
        cell_mask, nuc_mask, ndim, rna_coord, centrosome_coord)

    # initialization
    features = ()
    names_features_distance = False
    names_features_intranuclear = False
    names_features_protrusion = False
    names_features_dispersion = False
    names_features_topography = False
    names_features_foci = False
    names_features_area = False
    names_features_centrosome = False

    # distance related features
    if compute_distance:
        features += features_distance(
            rna_coord, distance_cell, distance_nuc, cell_mask, ndim, False)
        names_features_distance = True

    # nucleus related features
    if compute_intranuclear:
        features += features_in_out_nucleus(
            rna_coord, rna_coord_out_nuc, False)
        names_features_intranuclear = True

    # protrusion related features
    if compute_protrusion:
        features += features_protrusion(
            rna_coord, cell_mask, nuc_mask, ndim, voxel_size_yx, False)
        names_features_protrusion = True

    # dispersion indices
    if compute_dispersion and smfish is not None:
        features += features_dispersion(
            smfish, rna_coord, centroid_rna, cell_mask, centroid_cell,
            centroid_nuc, ndim, False)
        names_features_dispersion = True
    elif compute_dispersion and smfish is None:
        raise ValueError("Dispersion features can't be computed because "
                         "'smfish' is not provided.")

    # topographic features
    if compute_topography and voxel_size_yx is not None:
        features += features_topography(
            rna_coord, cell_mask, nuc_mask, cell_mask_out_nuc, ndim,
            voxel_size_yx, False)
        names_features_topography = True
    elif compute_topography and voxel_size_yx is None:
        raise ValueError("Topographic features can't be computed because "
                         "'voxel_size_yx' is not provided.")

    # foci related features
    if compute_foci and foci_coord is not None:
        features += features_foci(
            rna_coord, foci_coord, ndim, False)
        names_features_foci = True
    elif compute_foci and foci_coord is None:
        raise ValueError("Foci related features can't be computed because "
                         "'foci_coord' is not provided.")

    # area related features
    if compute_area:
        features += features_area(
            cell_mask, nuc_mask, cell_mask_out_nuc, False)
        names_features_area = True

    # centrosome related features
    if (compute_centrosome and centrosome_coord is not None
            and voxel_size_yx is not None and smfish is not None):
        features += features_centrosome(
            smfish, rna_coord, distance_centrosome, cell_mask, ndim,
            voxel_size_yx, False)
        names_features_centrosome = True
    elif compute_centrosome and centrosome_coord is None:
        raise ValueError("Centrosome related features can't be computed "
                         "because 'centrosome_coord' is not provided.")
    elif compute_centrosome and voxel_size_yx is None:
        raise ValueError("Centrosome related features can't be computed "
                         "because 'voxel_size_yx' is not provided.")
    elif compute_centrosome and smfish is None:
        raise ValueError("Centrosome related features can't be computed "
                         "because 'smfish' is not provided.")

    # format features
    features = np.array(features, dtype=np.float32)
    features = np.round(features, decimals=2)

    if return_names:
        features_names = get_features_name(
            names_features_distance=names_features_distance,
            names_features_intranuclear=names_features_intranuclear,
            names_features_protrusion=names_features_protrusion,
            names_features_dispersion=names_features_dispersion,
            names_features_topography=names_features_topography,
            names_features_foci=names_features_foci,
            names_features_area=names_features_area,
            names_features_centrosome=names_features_centrosome)
        return features, features_names

    return features
        # cyt and nuc
        nuc = image[0, 0, :, :, :]
        cyt = image[0, 1, :, :, :]

        # projections
        nuc_focus = stack.focus_projection_fast(nuc,
                                                proportion=0.7,
                                                neighborhood_size=7)
        nuc_focus = stack.rescale(nuc_focus, channel_to_stretch=0)
        cyt_focus = stack.focus_projection_fast(cyt,
                                                proportion=0.75,
                                                neighborhood_size=7)
        cyt_in_focus = stack.in_focus_selection(cyt,
                                                proportion=0.80,
                                                neighborhood_size=30)
        cyt_mip = stack.maximum_projection(cyt_in_focus)

        # save projections
        path = os.path.join(output_directory_nuc_focus, filename + ".png")
        stack.save_image(nuc_focus, path)
        path = os.path.join(output_directory_cyt_focus, filename + ".png")
        stack.save_image(cyt_focus, path)
        path = os.path.join(output_directory_cyt_mip, filename + ".png")
        stack.save_image(cyt_mip, path)

        nb_images += 1

    print("Done ({0} images)!".format(nb_images), "\n")

    end_time = time.time()
    duration = int(round((end_time - start_time) / 60))
Esempio n. 11
0
def build_template(path_template_directory,
                   i_cell=None,
                   index_template=None,
                   protrusion=None):
    """Build template from sparse coordinates. Outcomes are binary masks and
    distance maps, for cell, nucleus and protrusion.

    Parameters
    ----------
    path_template_directory : str
        Path of the templates directory.
    i_cell : int, optional
        Template id to build (between 0 and 317). If None, a random template
        is built.
    index_template : pd.DataFrame, optional
        Dataframe with the templates metadata. If None, dataframe is load from
        'path_template_directory'. 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.
    protrusion : bool, optional
        Generate only templates with protrusion or not. If None, all the
        templates are generated.

    Returns
    -------
    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,
                          i_cell=(int, type(None)),
                          index_template=(pd.DataFrame, type(None)),
                          protrusion=(bool, type(None)))

    # get index template
    if index_template is None:
        df = read_index_template(path_template_directory)
    else:
        df = index_template

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

    # check specific template or sample one
    if i_cell is not None:
        if i_cell not in indices and protrusion:
            raise ValueError(
                "Requested template {0} does not have protrusion.".format(
                    i_cell))
        elif i_cell not in indices and not protrusion:
            raise ValueError(
                "Requested template {0} has protrusion.".format(i_cell))
    else:
        i_cell = np.random.choice(indices)

    # get metadata and build filename
    shape_str = df.loc[i_cell, "shape"]
    protrusion_flag = df.loc[i_cell, "protrusion_flag"]
    filename = "{0}_{1}_{2}".format(i_cell, shape_str, protrusion_flag)

    # read files
    path = os.path.join(path_template_directory,
                        "cell_mask_{0}.npy".format(filename))
    coord_cell_mask = stack.read_array(path)
    path = os.path.join(path_template_directory,
                        "cell_map_{0}.npy".format(filename))
    coord_cell_map = stack.read_array(path)
    path = os.path.join(path_template_directory,
                        "nuc_mask_{0}.npy".format(filename))
    coord_nuc_mask = stack.read_array(path)
    path = os.path.join(path_template_directory,
                        "nuc_map_{0}.npy".format(filename))
    coord_nuc_map = stack.read_array(path)

    # get frame shape
    shape_z, shape_y, shape_x = map(int, shape_str.split("_"))

    # build masks and distance map for cell
    cell_mask = np.zeros((shape_z, shape_y, shape_x), dtype=bool)
    cell_mask[coord_cell_mask[:, 0], coord_cell_mask[:, 1],
              coord_cell_mask[:, 2]] = True
    cell_map = np.zeros((shape_z, shape_y, shape_x), dtype=np.float32)
    cell_map[coord_cell_mask[:, 0], coord_cell_mask[:, 1],
             coord_cell_mask[:, 2]] = coord_cell_map

    # build masks and distance map for nucleus
    nuc_mask = np.zeros((shape_z, shape_y, shape_x), dtype=bool)
    nuc_mask[coord_nuc_mask[:, 0], coord_nuc_mask[:, 1],
             coord_nuc_mask[:, 2]] = True
    nuc_map = np.zeros((shape_z, shape_y, shape_x), dtype=np.float32)
    nuc_map[coord_cell_mask[:, 0], coord_cell_mask[:, 1],
            coord_cell_mask[:, 2]] = coord_nuc_map

    # build masks and distance map for protrusion
    if protrusion_flag == "protrusion":
        path = os.path.join(path_template_directory,
                            "protrusion_mask_{0}.npy".format(filename))
        coord_protrusion_mask = stack.read_array(path)
        path = os.path.join(path_template_directory,
                            "protrusion_map_{0}.npy".format(filename))
        coord_protrusion_map = stack.read_array(path)
        protrusion_mask = np.zeros((shape_y, shape_x), dtype=bool)
        protrusion_mask[coord_protrusion_mask[:, 0],
                        coord_protrusion_mask[:, 1]] = True
        cell_mask_2d = stack.maximum_projection(cell_mask.astype(np.uint8))
        cell_mask_2d = cell_mask_2d.astype(bool)
        tuple_coord_cell_mask_2d = np.nonzero(cell_mask_2d)
        coord_cell_mask_2d = np.zeros((cell_mask_2d.sum(), 2), np.uint16)
        coord_cell_mask_2d[:, 0] = tuple_coord_cell_mask_2d[0]
        coord_cell_mask_2d[:, 1] = tuple_coord_cell_mask_2d[1]
        protrusion_map = np.zeros((shape_y, shape_x), dtype=np.float32)
        protrusion_map[coord_cell_mask_2d[:, 0],
                       coord_cell_mask_2d[:, 1]] = coord_protrusion_map
    else:
        protrusion_mask = np.zeros((shape_y, shape_x), dtype=bool)
        protrusion_map = np.zeros((shape_y, shape_x), dtype=np.float32)

    return (cell_mask, cell_map, nuc_mask, nuc_map, protrusion_mask,
            protrusion_map)
def image_processing_function(image_loc, config):
    # Read the image into a numpy array of format ZCYX
    if isinstance(image_loc, str):
        image_name = pathlib.Path(image_loc).stem
        image = tifffile.imread(image_loc)
    else:
        # Establish connection with OMERO and actually connect
        conn = omero.gateway.BlitzGateway(
            host="omero1.bioch.ox.ac.uk",
            port=4064,
            # group=config["OMERO_group"],
            username=config["OMERO_user"],
            passwd=config["password"],
        )
        conn.connect()
        conn.SERVICE_OPTS.setOmeroGroup(-1)
        # Create a thread to keep the connection alive
        ka_thread = threading.Thread(target=keep_connection_alive,
                                     args=(conn, ))
        ka_thread.daemon = True
        ka_thread.start()
        # Derive image and its name
        image_name = pathlib.Path(image_loc[0]).stem
        remote_image = conn.getObject("Image", image_loc[1])
        image = np.array(
            list(remote_image.getPrimaryPixels().getPlanes([
                (z, c, 0) for z in range(0, remote_image.getSizeZ())
                for c in range(0, remote_image.getSizeC())
            ])))
        image = image.reshape(
            image.shape[0] // remote_image.getSizeC(),
            remote_image.getSizeC(),
            image.shape[1],
            image.shape[2],
        )

    # segment with cellpose
    seg_img = np.max(image[:, config["seg_ch"], :, :], 0)
    if config["cp_search_string"] in image_name:
        seg_img = np.clip(seg_img, 0, config["cp_clip"])
    seg_img = scipy.ndimage.median_filter(seg_img,
                                          size=config["median_filter"])
    model = models.Cellpose(gpu=config["gpu"], model_type="cyto")
    channels = [0, 0]  # greyscale segmentation
    masks = model.eval(
        seg_img,
        channels=channels,
        diameter=config["diameter"],
        do_3D=config["do_3D"],
        flow_threshold=config["flow_threshold"],
        cellprob_threshold=config["cellprob_threshold"],
    )[0]

    # Calculate PSF
    psf_z, psf_yx = calculate_psf(
        config["voxel_size_z"],
        config["voxel_size_yx"],
        config["ex"],
        config["em"],
        config["NA"],
        config["RI"],
        config["microscope"],
    )
    sigma = detection.get_sigma(config["voxel_size_z"],
                                config["voxel_size_yx"], psf_z, psf_yx)

    for image_channel in config["channels"]:
        # detect spots
        rna = image[:, image_channel, :, :]
        # subtract background
        rna_no_bg = []
        for z in rna:
            z_no_bg = subtract_background(z, config["bg_radius"])
            rna_no_bg.append(z_no_bg)
        rna = np.array(rna_no_bg)

        # LoG filter
        rna_log = stack.log_filter(rna, sigma)

        # local maximum detection
        mask = detection.local_maximum_detection(rna_log, min_distance=sigma)

        # tresholding
        if image_channel == config["smFISH_ch1"]:
            threshold = config["smFISH_ch1_thresh"]
        elif image_channel == config["smFISH_ch2"]:
            threshold = config["smFISH_ch2_thresh"]
        else:
            print("smFISH channel and threshold not correctly defined!")

        spots, _ = detection.spots_thresholding(rna_log, mask, threshold)

        # detect and decompose clusters
        spots_post_decomposition = detection.decompose_cluster(
            rna,
            spots,
            config["voxel_size_z"],
            config["voxel_size_yx"],
            psf_z,
            psf_yx,
            alpha=config["alpha"],  # impacts number of spots per cluster
            beta=config["beta"],  # impacts the number of detected clusters
        )[0]

        # separate spots from clusters
        spots_post_clustering, foci = detection.detect_foci(
            spots_post_decomposition,
            config["voxel_size_z"],
            config["voxel_size_yx"],
            config["bf_radius"],
            config["nb_min_spots"],
        )

        # extract cell level results
        image_contrasted = stack.rescale(rna, channel_to_stretch=0)
        image_contrasted = stack.maximum_projection(image_contrasted)
        rna_mip = stack.maximum_projection(rna)

        fov_results = stack.extract_cell(
            cell_label=masks.astype(np.int64),
            ndim=3,
            rna_coord=spots_post_clustering,
            others_coord={"foci": foci},
            image=image_contrasted,
            others_image={"smfish": rna_mip},
        )

        # save bigfish results
        for i, cell_results in enumerate(fov_results):
            output_path = pathlib.Path(config["output_dir"]).joinpath(
                f"{image_name}_ch{image_channel + 1}_results_cell_{i}.npz")
            stack.save_cell_extracted(cell_results, str(output_path))

        # save reference spot for each image
        # (Using undenoised image! not from denoised!)
        reference_spot_undenoised = detection.build_reference_spot(
            rna,
            spots,
            config["voxel_size_z"],
            config["voxel_size_yx"],
            psf_z,
            psf_yx,
            alpha=config["alpha"],
        )
        spot_output_path = pathlib.Path(config["output_refspot_dir"]).joinpath(
            f"{image_name}_reference_spot_ch{image_channel + 1}")
        stack.save_image(reference_spot_undenoised, str(spot_output_path),
                         "tif")

    # Close the OMERO connection
    conn.close()