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)))
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
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)
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
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
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
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)
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()
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))
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()