def check_device(gpu_number=0, callback_log=None): """ Check on which device (GPU or CPU) CellPose will be running. Parameters ---------- gpu_number : int, optional Index of GPU to be tested, by default 0 """ use_gpu = utils.use_gpu(gpu_number=gpu_number) if use_gpu: log_message('CellPose will be USING GPU', callback_fun=callback_log) device = mx.gpu() else: log_message('CellPose will be USING CPU', callback_fun=callback_log) device = mx.cpu() return device
def create_img_closest_obj(path_scan, str_label, strs_save, path_save=None, search_recursive=False, truncate_distance=None, callback_log=None, callback_status=None, callback_progress=None): """ Function to process label images and facilitate assignment to closest segmented object. Will create two 2D images with the same size as the label image. Pixel values in either image encode 1. Distance to the closted object, e.g. nuclei. 2. Index of clostest object. Parameters ---------- path_scan : pathlib Path object Path to scan for label images. str_label : str Unique string contained in label iamge to be saved. strs_save : tuple of strings Strings defining how to save the result images, either string will replace str_save. path_save : pathlib Path object Path to save results, - If Pathlib object, then this absolute path is used. - If 'string' a replacement operation path containing the analyzed mask will be applied (see create_output_path). truncate_distance : int Distance above which distances will be truncated. Using a value of 255 has the advantage that the saved image is 8bit and thus small. callback_log : callback, optional Callback function to provide function log. If none, print will be used. For more details see segwrap.utils_general.log_message """ # Check scan directory if not path_scan.is_dir(): log_message(f'Path {path_scan} does not exist.', callback_fun=callback_log) return # Path to save results if isinstance(path_save, pathlib.PurePath): path_save_results = path_save if not path_save_results.is_dir(): path_save_results.mkdir(parents=True) path_save_settings = path_save_results else: path_save_str_replace = path_save # Search files: recursively or not files_proc = [] if search_recursive: for path_mask in path_scan.rglob(f'*{str_label}*'): files_proc.append(path_mask) else: for path_mask in path_scan.glob(f'*{str_label}*'): files_proc.append(path_mask) # Process files n_files = len(files_proc) for idx, file_label in enumerate(files_proc): log_message(f'\n>>> Processing file:\n{file_label}', callback_fun=callback_status) # >>> Create path to save data if necessary if not isinstance(path_save, pathlib.PurePath): path_save_results = create_output_path(file_label.parent, path_save_str_replace, subfolder=None, create_path=True) log_message(f'Results will be save here : {path_save_results}', callback_fun=callback_status) # >>>> Read label image img_labels = imread(file_label) props = regionprops(img_labels) labels = np.array([prop.label for prop in props]) n_objs = len(labels) # Loop over all nuclei and create create distance map log_message( f' Creating distance maps for {n_objs} objects. This can take a while ...', callback_fun=callback_log) dist_mat = np.zeros((img_labels.shape[0], img_labels.shape[1], n_objs), dtype=np.uint16) mask_fill_indiv = np.zeros( (img_labels.shape[0], img_labels.shape[1], n_objs), dtype=np.uint16) for indx, obj_int in enumerate(tqdm(np.nditer(labels), total=n_objs)): # Create binary mask for current object and find contour img_label_loop = np.zeros( (img_labels.shape[0], img_labels.shape[1])) img_label_loop[img_labels == obj_int] = 1 mask_fill_indiv[:, :, indx] = img_label_loop dist_obj = ndimage.distance_transform_edt( np.logical_not(img_label_loop)) if truncate_distance: dist_obj[dist_obj > truncate_distance] = truncate_distance dist_mat[:, :, indx] = dist_obj.astype(np.uint16) # >>> Condense distmap in two matrixes: index and distance to closest object dist_obj_ind_3D = np.argsort(dist_mat, axis=2) dist_obj_dist_3D = np.take_along_axis(dist_mat, dist_obj_ind_3D, axis=2) # For index: replace Python matrix index with actual index from label image ind_obj_closest = np.zeros((img_labels.shape[0], img_labels.shape[1])) dist_obj_ind_2D = np.copy(dist_obj_ind_3D[:, :, 0]) for indx, obj_int in enumerate(np.nditer(labels)): ind_obj_closest[dist_obj_ind_2D == indx] = obj_int # Save index of closest object name_save_ind = path_save_results / f'{file_label.stem.replace(str_label, strs_save[0])}.png' if str(name_save_ind) != str(file_label): imsave(name_save_ind, ind_obj_closest.astype('uint16'), check_contrast=False) else: log_message( f'Name to save index matrix could not be established: {name_save_ind}', callback_fun=callback_log) # Save distances to closest object name_save_dist = path_save_results / f'{file_label.stem.replace(str_label, strs_save[1])}.png' if str(name_save_dist) != str(file_label): imsave(name_save_dist, dist_obj_dist_3D[:, :, 0].astype('uint16'), check_contrast=False) else: log_message( f'Name to save index matrix could not be established: {name_save_dist}', callback_fun=callback_log)
def cellpose_predict(data, config, path_save, callback_log=None): """ Perform prediction with CellPose. Parameters ---------- data : dict Contains data on which prediction should be performed. config : dict Configuration of CellPose prediction. path_save : pathline Path object Path where results will be saved. """ # Get data imgs = data['imgs'] file_names = data['file_names'] sizes_orginal = data['sizes_orginal'] channels = data['channels'] new_size = data['new_size'] obj_name = data['obj_name'] # Get config model_type = config['model_type'] obj_size = config['obj_size'] device = config['device'] log_message(f'\nPerforming segmentation of {obj_name}\n', callback_fun=callback_log) start_time = time.time() if not path_save.is_dir(): path_save.mkdir() # Perform segmentation with CellPose model = models.Cellpose( device, model_type=model_type) # model_type can be 'cyto' or 'nuclei' masks, flows, styles, diams = model.eval(imgs, diameter=obj_size, channels=channels) # Display and save results log_message(f'\n Creating outputs ...\n', callback_fun=callback_log) n_img = len(imgs) for idx in tqdm(range(n_img)): file_name = file_names[idx] maski = masks[idx] flowi = flows[idx][0] imgi = imgs[idx] # Rescale each channel separately imgi_rescale = imgi.copy() for idim in range(3): imgdum = imgi[:, :, idim] pa, pb = np.percentile(imgdum, (0.1, 99.9)) imgi_rescale[:, :, idim] = rescale_intensity( imgdum, in_range=(pa, pb), out_range=np.uint8).astype('uint8') # Save overview image fig = plt.figure(figsize=(12, 3)) plot.show_segmentation(fig, imgi_rescale.astype('uint8'), maski, flowi, channels=channels) plt.tight_layout() plt.savefig(path_save / f'{file_name.stem}__segment__{obj_name}.png', dpi=600) plt.close() # Save mask and flow images imsave(path_save / f'{file_name.stem}__flow__{obj_name}.png', flowi, check_contrast=False) if new_size: mask_full = resize_mask(maski, sizes_orginal[idx]) imsave(path_save / f'{file_name.stem}__mask__{obj_name}.png', mask_full.astype('uint16'), check_contrast=False) imsave(path_save / f'{file_name.stem}__mask_resize__{obj_name}.png', maski.astype('uint16'), check_contrast=False) else: imsave(path_save / f'{file_name.stem}__mask__{obj_name}.png', maski.astype('uint16'), check_contrast=False) log_message( f"\nSegmentation of provided images finished ({(time.time() - start_time)}s)", callback_fun=callback_log)
def segment_cells_nuclei_indiv(path_scan, strings, img_ext, new_size, sizes, models, path_save, input_subfolder=None, callback_log=None, callback_status=None, callback_progress=None): """[summary] segment cells and nuclei in bulk, e.g. first all images are loaded and then segmented. TODO: specify parameters Parameters ---------- path_scan : [type] [description] strings : [type] [description] img_ext : [type] [description] new_size : tuple Defines resizing of image. If two elements, new size of image. If one element, resizing factor. If emtpy, no resizing. sizes : [type] [description] models : [type] [description] path_save : pathlin object or string Path to save results, - If Pathlib object, then this absolute path is used. - If 'string' a replacement operation on the provided name of the data path will be applied (see create_output_path). And results will be stored in subfolder 'segmentation-input' callback_log : [type], optional [description], by default None callback_status : [type], optional [description], by default None callback_progress : [type], optional [description], by default None """ par_dict = locals() par_dict = clean_par_dict(par_dict) log_message(f"Function (segment_obj_indiv) called with: {str(par_dict)} ", callback_fun=callback_log) # Get parameters (str_cyto, str_nuclei) = strings (size_cells, size_nuclei) = sizes (model_cells, model_nuclei) = models # Use provided absolute user-path to save images. if isinstance(path_save, pathlib.PurePath): path_save_results = path_save if not path_save_results.is_dir(): path_save_results.mkdir(parents=True) else: path_save_str_replace = path_save # Configurations device = check_device(callback_log=callback_log) config_nuclei = { 'model_type': model_nuclei, 'obj_size': size_nuclei, 'device': device } config_cyto = { 'model_type': model_cells, 'obj_size': size_cells, 'device': device } channels_cyto = [1, 3] channels_nuclei = [0, 1] # Loop over data log_message(f'\nLoading image pairs and segment them on the fly', callback_fun=callback_log) if not path_scan.is_dir(): log_message(f'Path {path_scan} does not exist.', callback_fun=callback_log) return # Search for file to be analyzed log_message(f'\nLoading images and segment them on the fly', callback_fun=callback_log) files_proc = [] for path_img in path_scan.rglob(f'*{str_cyto}*{img_ext}'): if input_subfolder: if path_img.parts[-2] == input_subfolder: files_proc.append(path_img) else: files_proc.append(path_img) n_imgs = len(files_proc) if n_imgs == 0: log_message(f'NO IMAGES FOUND. Check your settings.', callback_fun=callback_log) return # Process files for idx, path_cyto in enumerate(files_proc): imgs_cyto = [] imgs_nuclei = [] files_cyto = [] files_nuclei = [] sizes_orginal = [] log_message(f'Segmenting image : {path_cyto.name}', callback_fun=callback_log) if callback_status: callback_status(f'Segmenting image : {path_cyto.name}') if callback_progress: progress = float((idx + 1) / n_imgs) callback_progress(progress) # DAPI image: existing? path_nuclei = Path(str(path_cyto).replace(str_cyto, str_nuclei)) if not path_nuclei.is_file(): log_message(f'DAPI image not found : {path_nuclei}', callback_fun=callback_log) continue # Read images img_cyto = imread(str(path_cyto)) if img_cyto.ndim != 2: log_message( f'\nERROR\n Input image of cell has to be 2D. Current image is {img_cyto.ndim}D', callback_fun=callback_log) continue img_nuclei = imread(str(path_nuclei)) if img_nuclei.ndim != 2: log_message( f'\nERROR\n Input image of cell has to be 2D. Current image is {img_nuclei.ndim}D', callback_fun=callback_log) continue sizes_orginal.append(img_cyto.shape) # Resize if new_size: # New size can also be defined as a scalar factor if len(new_size) == 1: scale_factor = new_size[0] img_size = img_cyto.shape new_size = tuple(int(ti / scale_factor) for ti in img_size) img_cyto = resize(img_cyto, new_size) img_nuclei = resize(img_nuclei, new_size) img_zeros = np.zeros(img_cyto.shape) # For cell segmentation img_3d = np.dstack([img_cyto, img_zeros, img_nuclei]) imgs_cyto.append(img_3d) files_cyto.append(path_cyto) # For nuclei segmentation img_3d_dpi = np.dstack([img_zeros, img_zeros, img_nuclei]) imgs_nuclei.append(img_3d_dpi) files_nuclei.append(path_nuclei) # Create new output path if specified if not isinstance(path_save, pathlib.PurePath): path_save_results = create_output_path(path_cyto.parent, path_save_str_replace, subfolder='', create_path=True) path_save_settings = path_save_results # >>> Call function for prediction of cell data_cyto = { 'imgs': imgs_cyto, 'file_names': files_cyto, 'sizes_orginal': sizes_orginal, 'channels': channels_cyto, 'new_size': new_size, 'obj_name': 'cells' } cellpose_predict(data_cyto, config_cyto, path_save=path_save_results, callback_log=callback_log) # >>> Call function for prediction of nuclei data_nuclei = { 'imgs': imgs_nuclei, 'file_names': files_nuclei, 'sizes_orginal': sizes_orginal, 'channels': channels_nuclei, 'new_size': new_size, 'obj_name': 'nuclei' } cellpose_predict(data_nuclei, config_nuclei, path_save=path_save_results, callback_log=callback_log) # Save settings if len(imgs_cyto) > 0: fp = open(str(path_save_results / 'segmentation_settings.json'), "w") json.dump(par_dict, fp, indent=4, sort_keys=True) fp.close() log_message(f'\n BATCH SEGMENTATION finished', callback_fun=callback_log)
def segment_obj_indiv(path_scan, obj_name, str_channel, img_ext, new_size, obj_size, model_type, path_save, input_subfolder=None, callback_log=None, callback_status=None, callback_progress=None): """ Will recursively search folder for images to be analyzed! Parameters ---------- path_scan : [type] [description] str_nuclei : [type] [description] img_ext : [type] [description] new_size : tuple Defines resizing of image. If two elements, new size of image. If one element, resizing factor. If emtpy, no resizing. size_nuclei : [type] [description] model_nuclei : [type] [description] path_save : pathlib object or string Path to save results, - If Pathlib object, then this absolute path is used. - If 'string' a replacement operation on the provided name of the data path will be applied (see create_output_path). input_subfolder : str Name of subfolder that contains results. If specified ONLY files in this folder will be processed. callback_log : [type], optional [description], by default None callback_status : [type], optional [description], by default None callback_progress : [type], optional [description], by default None Returns ------- [type] [description] """ # Print all input parameters par_dict = locals() par_dict = clean_par_dict(par_dict) log_message(f"Function (segment_obj_indiv) called with: {str(par_dict)} ", callback_fun=callback_log) # Configurations device = check_device(callback_log=callback_log) config = {'model_type': model_type, 'obj_size': obj_size, 'device': device} channels = [0, 1] # Use provided absolute user-path to save images. if isinstance(path_save, pathlib.PurePath): path_save_results = path_save if not path_save_results.is_dir(): path_save_results.mkdir(parents=True) else: path_save_str_replace = path_save if not path_scan.is_dir(): log_message(f'Path {path_scan} does not exist.', callback_fun=callback_log) return # Search for file to be analyzed log_message(f'\nLoading images and segment them on the fly', callback_fun=callback_log) files_proc = [] for path_img in path_scan.rglob(f'*{str_channel}*{img_ext}'): if input_subfolder: if path_img.parts[-2] == input_subfolder: files_proc.append(path_img) else: files_proc.append(path_img) n_imgs = len(files_proc) if n_imgs == 0: log_message(f'NO IMAGES FOUND. Check your settings.', callback_fun=callback_log) return # Process files for idx, path_img in enumerate(files_proc): imgs = [] files = [] sizes_orginal = [] log_message(f'Segmenting image : {path_img.name}', callback_fun=callback_log) if callback_status: callback_status(f'Segmenting image : {path_img.name}') if callback_progress: progress = float((idx + 1) / n_imgs) callback_progress(progress) # Read images img = imread(str(path_img)) if img.ndim != 2: log_message( f'\nERROR\n Input image has to be 2D. Current image is {img.ndim}D', callback_fun=callback_log) continue sizes_orginal.append(img.shape) # Resize if new_size: # New size can also be defined as a scalar factor if len(new_size) == 1: scale_factor = new_size[0] img_size = img.shape new_size = tuple(int(ti / scale_factor) for ti in img_size) img = resize(img, new_size) img_zeros = np.zeros(img.shape) # For object segmentation img_3d_dpi = np.dstack([img_zeros, img_zeros, img]) imgs.append(img_3d_dpi) files.append(path_img) # >>> Call function for prediction data = { 'imgs': imgs, 'file_names': files, 'sizes_orginal': sizes_orginal, 'channels': channels, 'new_size': new_size, 'obj_name': obj_name } # Create new output path if specified if not isinstance(path_save, pathlib.PurePath): path_save_results = create_output_path(path_img.parent, path_save_str_replace, subfolder='', create_path=True) path_save_settings = path_save_results cellpose_predict(data, config, path_save=path_save_results, callback_log=callback_log) # Save settings if len(imgs) > 0: fp = open(str(path_save_results / 'segmentation_settings.json'), "w") json.dump(par_dict, fp, indent=4, sort_keys=True) fp.close() log_message(f'\n BATCH SEGMENTATION finished', callback_fun=callback_log)
def folder_prepare_prediction(path_process, channel_ident, img_ext, path_save, projection_type, subfolder=None, search_recursive=False, callback_log=None, callback_status=None, callback_progress=None): """[summary] Parameters ---------- path_process : [type] [description] channel_ident : str [description] img_ext : str Image extension path_save : pathlin object or string Path to save results, - If Pathlib object, then this absolute path is used. - If 'string' a replacement operation on the provided name of the data path will be applied (see create_output_path). And results will be stored in subfolder 'segmentation-input' projection_type : [type] [description] subfolder: str subfolder where data should be stored. Will only be used when string replacement for path is used. search_recursive : bool Recursively search folder, default: false. callback_log : [type], optional [description], by default None callback_status : [type], optional [description], by default None callback_progress : [type], optional [description], by default None """ # Print all input parameters log_message(f"Function (segment_obj_indiv) called with: {str(locals())} ", callback_fun=callback_log) # Use provided absolute user-path to save images. if isinstance(path_save, pathlib.PurePath): path_save_results = path_save if not path_save_results.is_dir(): path_save_results.mkdir(parents=True) path_save_settings = path_save_results else: path_save_str_replace = path_save # How to look for files files_proc = [] if search_recursive: for path_img in path_process.rglob(f'*{channel_ident}*{img_ext}'): files_proc.append(path_img) else: for path_img in path_process.glob(f'*{channel_ident}*{img_ext}'): files_proc.append(path_img) # Process files n_files = len(files_proc) for idx, file_proc in enumerate(files_proc): log_message(f'\n>>> Processing file: {file_proc}', callback_fun=callback_status) if callback_progress: progress = float((idx + 1) / n_files) callback_progress(progress) name_base = file_proc.stem # Create new output path if specified if not isinstance(path_save, pathlib.PurePath): path_save_results = create_output_path(file_proc.parent, path_save_str_replace, subfolder=subfolder, create_path=True) log_message(f'Results will be save here : {path_save_results}', callback_fun=callback_status) path_save_settings = path_save_results # Create subfolder when processing individual images if projection_type == 'indiv': path_save_indiv = path_save_results / name_base if not path_save_indiv.is_dir(): path_save_indiv.mkdir(parents=True) path_save_settings = path_save_indiv # Open image print(file_proc) img = imread(str(file_proc)) img_properties = { "file_process": str(file_proc), "img_name": file_proc.name, "img_path": str(file_proc.parent), "channel_ident": channel_ident, "projection_type": projection_type } name_json = path_save_settings / f'img-prop__{name_base}.json' with open(name_json, 'w') as fp: json.dump(img_properties, fp, sort_keys=True, indent=4) # Process depending specified option if projection_type == 'indiv': for i in range(img.shape[0]): name_save = path_save_indiv / f'{name_base}_Z{str(i+1).zfill(3)}.png' if name_save.is_file(): log_message( f'File already exists. Will be overwritten {name_save}', callback_fun=callback_log) imsave(str(name_save), img[i, :, :]) else: if projection_type == 'mean': img_proj = img.mean(axis=0) elif projection_type == 'max': img_proj = img.max(axis=0) name_save = path_save_results / f'{name_base}.png' if name_save.is_file(): log_message( f'File already exists. Will be overwritten {name_save}', callback_fun=callback_log) imsave(str(name_save), img_proj.astype('uint16'))
def cellpose_predict(data, config, path_save, callback_log=None): """ Perform prediction with CellPose. Parameters ---------- data : dict Contains data on which prediction should be performed. config : dict Configuration of CellPose prediction. path_save : pathline Path object Path where results will be saved. """ # Get data imgs = data['imgs'] file_names = data['file_names'] channels = data['channels'] obj_name = data['obj_name'] sizes_orginal = data['sizes_orginal'] new_size = data['new_size'] # Get config model_type = config['model_type'] diameter = config['diameter'] net_avg = config['net_avg'] resample = config['resample'] log_message(f'\nPerforming segmentation of {obj_name}\n', callback_fun=callback_log) start_time = time.time() if not path_save.is_dir(): path_save.mkdir() # Perform segmentation with CellPose model = models.Cellpose( gpu=False, model_type=model_type) # model_type can be 'cyto' or 'nuclei' masks, flows, styles, diams = model.eval(imgs, diameter=diameter, channels=channels, net_avg=net_avg, resample=resample) # Display and save results log_message(f'\n Creating outputs ...\n', callback_fun=callback_log) n_img = len(imgs) for idx in tqdm(range(n_img)): # Get images and file-name file_name = file_names[idx] maski = masks[idx] flowi = flows[idx][0] imgi = imgs[idx] # Rescale each channel separately imgi_norm = imgi.copy() for idim in range(3): imgdum = imgi[:, :, idim] # Renormalize to 8bit between 1 and 99 percentile pa = np.percentile(imgdum, 0.5) pb = np.percentile(imgdum, 99.5) if pb > pa: imgi_norm[:, :, idim] = 255 * (imgdum - pa) / (pb - pa) # Save flow io.imsave(str(path_save / f'{file_name.stem}__flow__{obj_name}.png'), flowi) # Resize masks if necessary if new_size: mask_full = resize_mask(maski, sizes_orginal[idx]) io.imsave( str(path_save / f'{file_name.stem}__mask__{obj_name}.png'), mask_full) io.imsave( str(path_save / f'{file_name.stem}__mask_resize__{obj_name}.png'), maski) else: io.imsave( str(path_save / f'{file_name.stem}__mask__{obj_name}.png'), maski) # Save mask and flow images #f_mask = str(path_save / f'{file_name.stem}__mask__{obj_name}.png') #log_message(f'\nMask saved to file: {f_mask}\n', callback_fun=callback_log) #io.imsave(str(path_save / f'{file_name.stem}__mask__{obj_name}.png'), maski) # Save overview image fig = plt.figure(figsize=(12, 3)) plot.show_segmentation(fig, imgi_norm.astype('uint8'), maski, flowi) plt.tight_layout() fig.savefig(str(path_save / f'{file_name.stem}__seg__{obj_name}.png'), dpi=300) plt.close(fig) log_message( f"\nSegmentation of provided images finished ({(time.time() - start_time)}s)", callback_fun=callback_log)