def retrieve_training_data(path_model, path_model_init=None): """ :param path_model: path of the folder with the model parameters .ckpt :param path_model_init: if the model is initialized by another, path of its folder :return: dictionary {steps, accuracy, loss} describing the evolution over epochs of the performance of the model. Stacks the initial model if needed """ # If string, convert to Path objects path_model = convert_path(path_model) file = open(path_model / "evolution.pkl", "rb") # training variables : loss, accuracy, epoch evolution = pickle.load(file) if path_model_init: # If string, convert to Path objects path_model_init = convert_path(path_model_init) file_init = open(path_model_init / "evolution.pkl", "rb") evolution_init = pickle.load(file_init) last_epoch = evolution_init["steps"][-1] evolution_merged = ( {} ) # Merging the two plots : learning of the init and learning of the model for key in ["steps", "accuracy", "loss"]: evolution_merged[key] = evolution_init[key] + evolution[key] evolution = evolution_merged return evolution
def raw_img_to_patches(path_raw_data, path_patched_data, thresh_indices = [0, 0.2, 0.8], patch_size=512, resampling_resolution=0.1): """ Transform a raw acquisition to a folder of patches of size indicated in the arguments. Also performs resampling. Note: this functions needs to be run as many times as there are different general pixel size (thus different acquisition types / resolutions). :param path_raw_data: Path to where the raw image folders are located. :param path_patched_data: Path to where we will store the patched acquisitions. :param thresh_indices: List of float, determining the thresholds separating the classes. :param patch_size: Int, size of the patches to generate (and consequently input size of the network). :param resampling_resolution: Float, the resolution we need to resample to so that each sample has the same resolution in a dataset. :return: Nothing. """ # If string, convert to Path objects path_raw_data = convert_path(path_raw_data) path_patched_data = convert_path(path_patched_data) # First we define where we are going to store the patched data and we create the directory if it does not exist. if not path_patched_data.exists(): path_patched_data.mkdir(parents=True) # Loop over each raw image folder img_folder_names = [im.name for im in path_raw_data.iterdir()] for img_folder in tqdm(img_folder_names): path_img_folder = path_raw_data / img_folder if path_img_folder.is_dir(): # We are now in the image folder. file = open(path_img_folder / 'pixel_size_in_micrometer.txt', 'r') pixel_size = float(file.read()) resample_coeff = float(pixel_size) / resampling_resolution # Used to set the resolution to the general_pixel_size # We go through every file in the image folder data_names = [d.name for d in path_img_folder.iterdir()] for data in data_names: if 'image' in data: # If it's the raw image. img = imread(path_img_folder / data, flatten=False, mode='L') img = rescale(img, resample_coeff, preserve_range=True, mode='constant').astype(int) elif 'mask.png' in data: mask_init = imread(path_img_folder / data, flatten=False, mode='L') mask = rescale(mask_init, resample_coeff, preserve_range=True, mode='constant') # Set the mask values to the classes' values mask = labellize_mask_2d(mask, thresh_indices) # shape (size, size), values float 0.0-1.0 to_extract = [img, mask] patches = extract_patch(to_extract, patch_size) # The patch extraction is done, now we put the new patches in the corresponding folders # We create it if it does not exist path_patched_folder = path_patched_data / img_folder if not path_patched_folder.exists(): path_patched_folder.mkdir(parents=True) for j, patch in enumerate(patches): imsave(path_patched_folder.joinpath('image_%s.png'%j), patch[0],'png') imsave(path_patched_folder.joinpath('mask_%s.png'%j), patch[1],'png')
def generate_axons_from_myelin(path_prediction, path_myelin_corrected): """ :param path_prediction: path of the prediction i.e. image of axon+myelin segmentation (output of AxonDeepSeg) :param path_myelin_corrected: path of corrected myelin by the user i.e. myelin mask (uint8 type with myelin=255, background=0) :return: merged and corrected axon+myelin image """ # If string, convert to Path objects path_prediction = convert_path(path_prediction) path_myelin_corrected = convert_path(path_myelin_corrected) # read output from axondeepseg and myelin mask corrected by user prediction = ads.imread(path_prediction) myelin_corrected = ads.imread(path_myelin_corrected) # compute the axon mask from axondeepseg (axon=255, myelin=127, background=0) axon_ads = prediction > 200 # get the myelin mask corrected by user (myelin=255, background=0) myelin_corrected = myelin_corrected > 200 # compute logical OR between axondeepseg axon mask and myelin corrected mask fused = np.logical_or(axon_ads, myelin_corrected) # compute new axon mask by logical XOR between corrected myelin mask and fused new_axon_mask = np.logical_xor(myelin_corrected, fused) # merge corrected myelin mask and generated axon mask both = new_axon_mask * 255 + myelin_corrected * 127 # save the corrected axon+myelin image path = path_prediction.parent / 'axon_myelin_mask_corrected.png' ads.imwrite(path, both) return both
def segment_image(path_testing_image, path_model, overlap_value, config, resolution_model, acquired_resolution = None, verbosity_level=0): ''' Segment the image located at the path_testing_image location. :param path_testing_image: the path of the image to segment. :param path_model: where to access the model :param overlap_value: the number of pixels to be used for overlap when doing prediction. Higher value means less border effects but more time to perform the segmentation. :param config: dict containing the configuration of the network :param resolution_model: the resolution the model was trained on. :param verbosity_level: Level of verbosity. The higher, the more information is given about the segmentation process. :return: Nothing. ''' # If string, convert to Path objects path_testing_image = convert_path(path_testing_image) path_model = convert_path(path_model) if path_testing_image.exists(): # Extracting the image name and its folder path from the total path. path_parts = path_testing_image.parts acquisition_name = Path(path_parts[-1]) path_acquisition = Path(*path_parts[:-1]) # Get type of model we are using selected_model = path_model.name img_name_original = acquisition_name.stem # Performing the segmentation axon_segmentation(path_acquisitions_folders=path_acquisition, acquisitions_filenames=[acquisition_name], path_model_folder=path_model, config_dict=config, ckpt_name='model', inference_batch_size=1, overlap_value=overlap_value, resampled_resolutions=resolution_model, verbosity_level=verbosity_level, acquired_resolution=acquired_resolution, prediction_proba_activate=False, write_mode=True) if verbosity_level >= 1: print(("Image {0} segmented.".format(path_testing_image))) else: print(("The path {0} does not exist.".format(path_testing_image))) return None
def save_metrics(model_statistics_dict, path_model_folder, statistics_filename): """ Saves the computed metrics in a json file. :param model_statistics_dict: Dict, the computed metrics to save. :param path_model_folder: Path to the folder containing the model to use. :param statistics_filename: Name of the file where we will store the computed statistics. :return: """ # If string, convert to Path objects path_model_folder = convert_path(path_model_folder) # If the file already exists we rename the old one with a .old suffix. path_statistics_file = path_model_folder / statistics_filename if path_statistics_file.exists(): with open(path_statistics_file) as f: original_stats_dict = json.load(f) path_statistics_file.unlink() else: original_stats_dict = {} #original_stats_dict.update(model_statistics_dict) rec_update(original_stats_dict, model_statistics_dict) with open(path_statistics_file, 'w') as f: json.dump(original_stats_dict, f, indent=2)
def __init__( self, ids, path, augmentations, batch_size=8, image_size=512, thresh_indices=[0, 0.2, 0.8], ): """ Initalization for the DataGen class :param ids: List of strings, ids of all the images/masks in the training set. :param batch_size: Int, the batch size used for training. :param image_size: Int, input image size. :param image_size: Int, input image size. :param augmentations: Compose object, a set of data augmentation operations to be applied. :return: the original image, a list of patches, and their positions. """ # If string, convert to Path object path = convert_path(path) self.ids = ids self.path = path self.batch_size = batch_size self.image_size = image_size self.on_epoch_end() self.thresh_indices = thresh_indices self.augment = augmentations
def get_masks(path_prediction): # If string, convert to Path objects path_prediction = convert_path(path_prediction) prediction = imageio.imread(path_prediction) # compute the axon mask axon_prediction = prediction > 200 # compute the myelin mask myelin_prediction = prediction > 100 myelin_prediction = myelin_prediction ^ axon_prediction # We want to keep the filename path up to the '_seg-axonmyelin' part folder_path = path_prediction.parent filename_part = path_prediction.name.split('_seg-axonmyelin')[0] # Extra check to ensure that the extension was removed if filename_part.endswith('.png'): filename_part = filename_part.split('.png')[0] # Save masks filename_axon = filename_part + '_seg-axon.png' filename_myelin = filename_part + '_seg-myelin.png' imageio.imwrite(folder_path / filename_axon, axon_prediction.astype(int)) imageio.imwrite(folder_path / filename_myelin, myelin_prediction.astype(int)) return axon_prediction, myelin_prediction
def generate_default_parameters(type_acquisition, new_path): ''' Generates the parameters used for segmentation for the default model corresponding to the type_model acquisition. :param type_model: String, the type of model to get the parameters from. :param new_path: Path to the model to use. :return: the config dictionary. ''' # If string, convert to Path objects new_path = convert_path(new_path) # Building the path of the requested model if it exists and was supplied, else we load the default model. if type_acquisition == 'SEM': if (new_path is not None) and new_path.exists(): path_model = new_path else: path_model = MODELS_PATH / SEM_DEFAULT_MODEL_NAME elif type_acquisition == 'TEM': if (new_path is not None) and new_path.exists(): path_model = new_path else: path_model = MODELS_PATH / TEM_DEFAULT_MODEL_NAME path_config_file = path_model / 'config_network.json' config = generate_config_dict(path_config_file) return path_model, config
def metrics_single_wrapper( path_model_folder, path_images_folder, resampled_resolution, overlap_value=25, statistics_filename='model_statistics_validation.json', create_statistics_file=True, verbosity_level=0): """ Procedure to compute the metrics using a model on several images. Computation is made on one image after the other. :param path_model_folder: Path to the folder where the model is located. :param path_images_folder: Path to the folders that contain the images to compute the metrics on. :param resampled_resolution: Float, the resolution to resample to to make the predictions. :param overlap_value: Int, the number of pixels to use for overlap. :param statistics_filename: String, the name of the file to use when saving the computed metrics. :param create_statistics_file: Boolean. If true, creates a statistics file where the computed metrics are stored. :param verbosity_level: Int. The higher, the more displayed information. :return: Nothing. """ # If string, convert to Path objects path_model_folder = convert_path(path_model_folder) path_images_folder = convert_path(path_images_folder) # First we load every information independent of the model # We generate the list of testing folders, each one containing one image images_folders = filter(lambda p: p.is_dir(), path_images_folder.iterdir()) path_images_folder = [path_images_folder / x for x in images_folders] # We check that the model path we were given exists. if path_model_folder.is_dir(): # Generation of statistics for each image one after the other for current_path_images_folder in path_images_folder: stats_dict = generate_statistics(path_model_folder, [current_path_images_folder], resampled_resolution, overlap_value, verbosity_level=verbosity_level) # We now save the data in a corresponding json file. save_metrics(stats_dict, path_model_folder, statistics_filename) # Finally we print out the results using pprint. print_metrics(stats_dict)
def split_data(raw_dir, out_dir, seed=2019, split=[0.8, 0.2], override=False): """ Splits a dataset into training and validation folders. :param raw_dir: Raw dataset folder containing a set of subdirectories to be split. :param out_dir: Output directory of splitted dataset :param seed: Random number generator seed. :param split: Split fractions ([Train, Validation]). :param override: Bool. If out_dir exists and is True, the directory will be overwritten. """ # If string, convert to Path objects raw_dir = convert_path(raw_dir) out_dir = convert_path(out_dir) # Handle case if output dir already exists if out_dir.is_dir(): if override == True: shutil.rmtree(out_dir) else: raise IOError("Directory " + str(out_dir) + " already exist. Delete or change override option.") # Get splits for Training and Validation split_train = split[0] split_valid = split[1] # get sorted list of image directories dirs = sorted([x for x in raw_dir.iterdir() if x.is_dir()]) # Create directories for splitted datasets train_dir = out_dir / "Train" train_dir.mkdir(parents=True, exist_ok=True) valid_dir = out_dir / "Validation" valid_dir.mkdir(parents=True, exist_ok=True) # Randomly assign a number to each image folder for splitting np.random.seed(seed=seed) sorted_indices = np.random.choice(len(dirs), size=len(dirs), replace=False) # Move the image dirs from the raw folder to the split folder for index in sorted_indices[:round(len(sorted_indices) * split_train)]: dirs[index].rename(train_dir / dirs[index].parts[1]) for index in sorted_indices[-round(len(sorted_indices) * split_valid):]: dirs[index].rename(valid_dir / dirs[index].parts[1])
def metrics_classic_wrapper( path_model_folder, path_images_folder, resampled_resolution, overlap_value=25, statistics_filename='model_statistics_validation.json', create_statistics_file=True, verbosity_level=0): """ Procedure to compute metrics on all the images we want, at the same time. :param path_model_folder: Path to the (only) model we want to use for statistics generation . :param path_images_folder: Path to the folder containing all the folders containing the images (each image has its own folder). :param resampled_resolution: The size in micrometer of a pixel we resample to. :param overlap_value: The number of pixels used for overlap. :param statistics_filename: String, the file name to use when creating a statistics file. :param create_statistics_file: Boolean. If False, the function just displays the statistics. True by default. :param verbosity_level: Int. The higher, the more information displayed about the metrics computing process. :return: Nothing. """ # If string, convert to Path objects path_model_folder = convert_path(path_model_folder) path_images_folder = convert_path(path_images_folder) # First we load every information independent of the model # We generate the list of testing folders, each one containing one image images_folders = filter(lambda p: p.is_dir(), path_images_folder.iterdir()) path_images_folder = [path_images_folder / x for x in images_folders] # We check that the model path we were given exists. if path_model_folder.is_dir(): # Generation of statistics stats_dict = generate_statistics(path_model_folder, path_images_folder, resampled_resolution, overlap_value, verbosity_level=verbosity_level) # We now save the data in a corresponding json file. save_metrics(stats_dict, path_model_folder, statistics_filename) # Finally we print out the results using pprint. print_metrics(stats_dict)
def save_map_of_axon_diameters(path_folder, axon_diameter_figure): """ :param path_folder: absolute path of the sample and the segmentation folder :param axon_diameter_figure: figure create with draw_axon_diameter :return: None """ # If string, convert to Path objects path_folder = convert_path(path_folder) file_path = path_folder / "AxonDeepSeg_map-axondiameter.png" axon_diameter_figure.savefig(file_path)
def retrieve_hyperparameters(path_model): """ :param path_model: path of the folder with the model parameters .ckpt :return: the dict containing the hyperparameters """ # If string, convert to Path objects path_model = convert_path(path_model) file = open(path_model / "hyperparameters.pkl", "r") # training variables : loss, accuracy, epoch return pickle.load(file)
def load_acquisitions(path_acquisitions, acquisitions_resolutions, resampled_resolutions, verbose_mode=0): """ Load and resamples acquisitions located in the indicated folders' paths. :param path_acquisitions: List of paths to the acquisitions images. :param acquisitions_resolutions: List of float containing the resolutions the acquisitions were acquired with. :param resampled_resolutions: List of resolutions (floats) to resample to. :param verbose_mode: Int, how much information to display. :return: """ # If string, convert to Path objects path_acquisitions = convert_path(path_acquisitions) path_acquisitions, acquisitions_resolutions, resampled_resolutions = list( map(ensure_list_type, [ path_acquisitions, acquisitions_resolutions, resampled_resolutions ])) if verbose_mode >= 2: print("Loading acquisitions ...") # Reading acquisitions images and loading them in the RAM, with their respective acquisition resolution. # Then resampling the acquisitions images to the target resolution that the network uses. original_acquisitions, resampled_acquisitions, original_acquisitions_shapes = [], [], [] for path_img in path_acquisitions: original_acquisitions.append(ads.imread(path_img)) original_acquisitions_shapes.append(original_acquisitions[-1].shape) # Resampling acquisitions to the target resolution if verbose_mode >= 2: print("Rescaling acquisitions to the target resolution ...") resampling_coeffs = [ current_acquisition_resolution / resampled_resolutions[i] for i, current_acquisition_resolution in enumerate(acquisitions_resolutions) ] for i, current_original_acquisition in enumerate(original_acquisitions): resampled_acquisitions.append( rescale(current_original_acquisition, resampling_coeffs[i], preserve_range=True).astype(int)) return resampled_acquisitions, resampling_coeffs, original_acquisitions_shapes
def visualize_training(path_model, iteration_start_for_viz=0): """ :param path_model: path of the folder with the model parameters .ckpt :param iteration_start_for_viz: first iterations can reach extreme values, iteration_start_for_viz set a beginning other than epoch 0 :return: matplotlib.figure.Figure The returned figure represents the evolution of the loss and the accuracy evaluated on the validation set along the learning process. If the learning began from an initial model, the figure plots first the accuracy and loss evolution from this initial model and then stacks the evolution of the model. """ # If string, convert to Path objects path_model = convert_path(path_model) def _create_figure_helper(data_evolution): fig = Figure() FigureCanvas(fig) # Drawing the evolution curves ax1 = fig.subplots() ax2 = ax1.twinx() ax1.plot( data_evolution["steps"][iteration_start_for_viz:], data_evolution["accuracy"][iteration_start_for_viz:], "-", label="accuracy", ) ax1.set_ylim(ymin=0) ax2.plot( data_evolution["steps"][iteration_start_for_viz:], data_evolution["loss"][iteration_start_for_viz:], "-r", label="loss", ) # Annotating the graph ax1.set_title("Accuracy and loss evolution") ax1.set_xlabel("Epoch") ax1.set_ylabel("Accuracy") ax2.set_ylabel("Loss") return fig evolution = retrieve_training_data(path_model) fig = _create_figure_helper(evolution) return fig
def merge_masks(path_axon, path_myelin): # If string, convert to Path objects path_axon = convert_path(path_axon) axon = ads.imread(path_axon) myelin = ads.imread(path_myelin) both = (axon / 255) * 255 + (myelin / 255) * 127 # get main path path_folder = path_axon.parent # save the masks ads.imwrite(path_folder / 'axon_myelin_mask.png', both) return both
def save_axon_morphometrics(path_folder, stats_array): """ :param path_folder: absolute path of the sample and the segmentation folder :param stats_array: list of dictionaries containing axon morphometrics :return: """ # If string, convert to Path objects path_folder = convert_path(path_folder) try: np.save(str(path_folder / 'axonlist.npy'), stats_array) except IOError as e: print(("\nError: Could not save file \"{0}\" in " "directory \"{1}\".\n".format('axonlist.npy', path_folder))) raise
def write_aggregate_morphometrics(path_folder, aggregate_metrics): """ :param path_folder: absolute path of folder containing sample + segmentation :param aggregate_metrics: dictionary containing values of aggregate metrics :return: nothing """ # If string, convert to Path objects path_folder = convert_path(path_folder) try: with open(path_folder / 'aggregate_morphometrics.txt', 'w') as text_file: text_file.write('aggregate_metrics: ' + repr(aggregate_metrics) + '\n') except IOError as e: print(("\nError: Could not save file \"{0}\" in " "directory \"{1}\".\n".format('aggregate_morphometrics.txt', path_folder))) raise
def draw_axon_diameter(img, path_prediction, pred_axon, pred_myelin): """ :param img: sample grayscale image (png) :param path_prediction: full path to the segmented file (*_seg-axonmyelin.png) from axondeepseg segmentation output :param pred_axon: axon mask from axondeepseg segmentation output :param pred_myelin: myelin mask from axondeepseg segmentation output :return: matplotlib.figure.Figure """ # If string, convert to Path objects path_prediction = convert_path(path_prediction) path_folder = path_prediction.parent stats_array = get_axon_morphometrics(pred_axon, path_folder) axon_diam_list = [d["axon_diam"] for d in stats_array] axon_diam_array = np.asarray(axon_diam_list) labels = measure.label(pred_axon) axon_diam_display = np.zeros((np.shape(labels)[0], np.shape(labels)[1])) for pix_x in np.arange(np.shape(labels)[0]): for pix_y in np.arange(np.shape(labels)[1]): if labels[pix_x, pix_y] != 0: axon_diam_display[pix_x, pix_y] = axon_diam_array[ labels[pix_x, pix_y] - 1 ] # Axon overlay on original image + myelin display (same color for every # myelin sheath) fig = Figure(figsize=(12, 9)) FigureCanvas(fig) ax = fig.subplots() ax.imshow(img, cmap="gray", alpha=0.8) ax.imshow(pred_myelin, cmap="gray", alpha=0.3) im = ax.imshow(axon_diam_display, cmap="hot", alpha=0.5) fig.colorbar(im, fraction=0.03, pad=0.02) ax.set_title( "Axon overlay (colorcoded with axon diameter in um) and myelin display", fontsize=12, ) return fig
def get_aggregate_morphometrics(pred_axon, pred_myelin, path_folder): """ :param pred_axon: axon mask from axondeepseg segmentation output :param pred_myelin: myelin mask from axondeepseg segmentation output :param path_folder: absolute path of folder containing pixel size file :return: aggregate_metrics: dictionary containing values of aggregate metrics """ # If string, convert to Path objects path_folder = convert_path(path_folder) # Compute AVF (axon volume fraction) = area occupied by axons in sample avf = np.count_nonzero(pred_axon) / float((pred_axon.size)) # Compute MVF (myelin volume fraction) = area occupied by myelin sheaths in sample mvf = np.count_nonzero(pred_myelin) / float((pred_myelin.size)) # Estimate aggregate g-ratio = sqrt(1/(1+MVF/AVF)) gratio = math.sqrt(1 / (1 + (float(mvf) / float(avf)))) # Get individual axons metrics and compute mean axon diameter stats_array = get_axon_morphometrics(pred_axon, path_folder) axon_diam_list = [d["axon_diam"] for d in stats_array] mean_axon_diam = np.mean(axon_diam_list) # Estimate mean myelin diameter (axon+myelin diameter) by using # aggregate g-ratio = mean_axon_diam/mean_myelin_diam mean_myelin_diam = mean_axon_diam / gratio # Estimate mean myelin thickness = mean_myelin_radius - mean_axon_radius mean_myelin_thickness = (float(mean_myelin_diam) / 2) - (float(mean_axon_diam) / 2) # Compute axon density (number of axons per mm2) px_size_um = get_pixelsize(path_folder / 'pixel_size_in_micrometer.txt') img_area_mm2 = float(pred_axon.size) * px_size_um * px_size_um / (float(1000000)) axon_density_mm2 = float(len(axon_diam_list)) / float(img_area_mm2) # Create disctionary to store aggregate metrics aggregate_metrics = {'avf': avf, 'mvf': mvf, 'gratio_aggr': gratio, 'mean_axon_diam': mean_axon_diam, 'mean_myelin_diam': mean_myelin_diam, 'mean_myelin_thickness': mean_myelin_thickness, 'axon_density_mm2': axon_density_mm2} return aggregate_metrics
def load_models(self): for path in self.path_models: # If string, convert to Path objects path = convert_path(path) try: with open(path / self.statistics_filename) as f: stats_dict = json.loads(f.read())['data'] except: raise ValueError( 'No config file found: statistics json file missing in the model folder.' ) # Now we add a line to the stats dataframe for each model for ckpt_name, ckpt in list(stats_dict.items()): # Getting each part of data model_name = ckpt['id_model'] ckpt_name = ckpt['ckpt'] config = ckpt['config'] testing_stats_list = ckpt['testing_stats'] for name_image, testing_stats in list( testing_stats_list.items()): type_image = name_image.split("_")[0] pw_dice_myelin = testing_stats['pw_dice_myelin'] pw_dice_axon = testing_stats['pw_dice_axon'] testing_log_loss = testing_stats['log_loss'] testing_accuracy = testing_stats['accuracy'] new_line = [[ model_name, ckpt_name, config['trainingset'].split('_')[0], type_image, pw_dice_myelin, pw_dice_axon, testing_log_loss, testing_accuracy, name_image ]] # Updating the dataframe with the latest data self.stats = self.stats.append( pd.DataFrame(columns=self.columns, data=new_line)) self.filtered_stats = self.stats.copy()
def generate_config_dict(path_to_config_file): ''' Generates the dictionary version of the configuration file from the path where it is located. :param path_to_config: relative path where the file config_network.json is located. :return: dict containing the configuration of the network, or None if no configuration file was found at the mentioned path. ''' # If string, convert to Path objects path_to_config_file = convert_path(path_to_config_file) try: with open(path_to_config_file, 'r') as fd: config_network = json.loads(fd.read()) except: raise ValueError("No configuration file available at this path.") return config_network
def rgb_rendering_of_mask(pred_img, writing_path=None): """ Returns a segmentation mask in red and blue display :param pred_img: segmented image - 3-class mask :param save_mask: Boolean: whether or not to save the returned mask :param writing_path: string: path where to save the mask if save_mask=True :return: rgb_mask: imageio.core.util.Image """ pred_axon = pred_img == 255 pred_myelin = pred_img == 127 rgb_mask = np.zeros([np.shape(pred_img)[0], np.shape(pred_img)[1], 3]) rgb_mask[pred_axon] = [0, 0, 255] rgb_mask[pred_myelin] = [255, 0, 0] if writing_path is not None: # If string, convert to Path objects writing_path = convert_path(writing_path) imageio.imwrite(writing_path, rgb_mask) return rgb_mask
def get_pixelsize(path_pixelsize_file): """ :param path_pixelsize_file: path of the txt file indicating the pixel size of the sample :return: the pixel size value. """ # If string, convert to Path objects path_pixelsize_file = convert_path(path_pixelsize_file) try: with open(path_pixelsize_file, "r") as text_file: pixelsize = float(text_file.read()) except IOError as e: print(("\nError: Could not open file \"{0}\" from " "directory \"{1}\".\n".format(path_pixelsize_file, Path.cwd()))) raise except ValueError as e: print(("\nError: Pixel size data in file \"{0}\" is not valid – must " "be a plain text file with a single a numerical value (float) " " on the fist line.".format(path_pixelsize_file))) raise else: return pixelsize
def __init__(self, ids, path, batch_size=8, image_size=512, thresh_indices=[0, 0.2, 0.8]): ''' Initalization for the DataGen class :param ids: List of strings, ids of all the images/masks in the training set. :param batch_size: Int, the batch size used for training. :param image_size: Int, input image size. :param image_size: Int, input image size. :return: the original image, a list of patches, and their positions. ''' # If string, convert to Path objects path = convert_path(path) self.ids = ids self.path = path self.batch_size = batch_size self.image_size = image_size self.on_epoch_end() self.thresh_indices = thresh_indices
def axon_segmentation(path_acquisitions_folders, acquisitions_filenames, path_model_folder, config_dict, ckpt_name='model', segmentations_filenames=[str(axonmyelin_suffix)], inference_batch_size=1, overlap_value=25, resampled_resolutions=0.1, acquired_resolution=None, prediction_proba_activate=False, write_mode=True, gpu_per=1.0, verbosity_level=0): """ Wrapper performing the segmentation of all the requested acquisitions and generates (if requested) the segmentation images. :param path_acquisitions_folders: List of folders where the acquisitions to segment are located. :param acquisitions_filenames: List of names of acquisitions to segment. :param path_model_folder: Path to the folder where the model is located. :param config_dict: Dictionary containing the configuration of the training parameters of the model. :param ckpt_name: String, name of the checkpoint to use. :param segmentations_filenames: List of the names of the segmentations files, to be used when creating the files. :param inference_batch_size: Size of the batches fed to the network. :param overlap_value: Int, number of pixels to use for overlapping the predictions. :param resampled_resolutions: List of the resolutions (in µm) to resample to. :param acquired_resolution: List of the resolutions (in µm) for native images. :param prediction_proba_activate: Boolean, whether to compute probability maps or not. :param write_mode: Boolean, whether to create segmentation images or not. :param gpu_per: Percentage of the GPU to use, if we use it. :param verbosity_level: Int, level of verbosity. The higher, the more information is displayed. :return: List of predictions, and optionally of probability maps. """ # If string, convert to Path objects path_acquisitions_folders = convert_path(path_acquisitions_folders) path_model_folder = convert_path(path_model_folder) # Processing input so they are lists in every situation path_acquisitions_folders, acquisitions_filenames, resampled_resolutions, segmentations_filenames = \ list(map(ensure_list_type, [path_acquisitions_folders, acquisitions_filenames, resampled_resolutions, segmentations_filenames])) if len(segmentations_filenames) != len(path_acquisitions_folders): segmentations_filenames = [str(axonmyelin_suffix) ] * len(path_acquisitions_folders) if len(acquisitions_filenames) != len(path_acquisitions_folders): acquisitions_filenames = ['image.png'] * len(path_acquisitions_folders) if len(resampled_resolutions) != len(path_acquisitions_folders): resampled_resolutions = [resampled_resolutions[0] ] * len(path_acquisitions_folders) # Generating the patch to acquisitions and loading the acquisitions resolutions. path_acquisitions = [ path_acquisitions_folders[i] / e for i, e in enumerate(acquisitions_filenames) ] # If we did not receive any resolution we read the pixel size in micrometer from each pixel. if acquired_resolution == None: if (path_acquisitions_folders[0] / 'pixel_size_in_micrometer.txt').exists(): resolutions_files = [ open(path_acquisition_folder / 'pixel_size_in_micrometer.txt', 'r') for path_acquisition_folder in path_acquisitions_folders ] acquisitions_resolutions = [ float(file_.read()) for file_ in resolutions_files ] else: exception_msg = "ERROR: No pixel size is provided, and there is no pixel_size_in_micrometer.txt file in image folder. " \ "Please provide a pixel size (using argument -s), or add a pixel_size_in_micrometer.txt file " \ "containing the pixel size value." raise Exception(exception_msg) # If resolution is specified as input argument, use it else: acquisitions_resolutions = [acquired_resolution ] * len(path_acquisitions_folders) # Ensuring that the config file is valid config_dict = update_config(default_configuration(), config_dict) # Perform the segmentation of all the requested images. if prediction_proba_activate: prediction, prediction_proba = apply_convnet( path_acquisitions, acquisitions_resolutions, path_model_folder, config_dict, ckpt_name=ckpt_name, inference_batch_size=inference_batch_size, overlap_value=overlap_value, resampled_resolutions=resampled_resolutions, prediction_proba_activate=prediction_proba_activate, gpu_per=gpu_per, verbosity_level=verbosity_level) # Predictions are shape of image, value = class of pixel else: prediction = apply_convnet( path_acquisitions, acquisitions_resolutions, path_model_folder, config_dict, ckpt_name=ckpt_name, inference_batch_size=inference_batch_size, overlap_value=overlap_value, resampled_resolutions=resampled_resolutions, prediction_proba_activate=prediction_proba_activate, gpu_per=gpu_per, verbosity_level=verbosity_level) # Predictions are shape of image, value = class of pixel # Final part of the function : generating the image if needed/ returning values if write_mode: for i, pred in enumerate(prediction): # Transform the prediction to an image n_classes = config_dict['n_classes'] paint_vals = [ int(255 * float(j) / (n_classes - 1)) for j in range(n_classes) ] # Create the mask with values in range 0-255 mask = np.zeros_like(pred) for j in range(n_classes): mask[pred == j] = paint_vals[j] # Then we save the image image_name = convert_path(acquisitions_filenames[i]).stem ads.imwrite( path_acquisitions_folders[i] / (image_name + segmentations_filenames[i]), mask, 'png') axon_prediction, myelin_prediction = get_masks( path_acquisitions_folders[i] / (image_name + segmentations_filenames[i])) if prediction_proba_activate: return prediction, prediction_proba else: return prediction
def apply_convnet(path_acquisitions, acquisitions_resolutions, path_model_folder, config_dict, ckpt_name='model', inference_batch_size=1, overlap_value=25, resampled_resolutions=[0.1], prediction_proba_activate=False, gpu_per=1.0, verbosity_level=0): """ Preprocesses the images, transform them into patches, applies the network, stitches the predictions and return them. :param path_acquisitions: List of path to the acquisitions. :param acquisitions_resolutions: List of the acquisitions resolutions (floats). :param path_model_folder: Path to the model folder. :param config_dict: Dictionary containing the model's parameters. :param ckpt_name: String, checkpoint to use. :param inference_batch_size: Int, batch size to use when doing inference. :param overlap_value: Int, number of pixels to use when overlapping the predictions of the network. :param resampled_resolutions: List of resolutions (flaots) to resample to before performing inference. :param prediction_proba_activate: Boolean, whether to compute the probability maps or not. :param gpu_per: Float, percentage of GPU to use if we use it. :param verbosity_level: Int, how much information to display. :return: List of segmentations, and list of probability maps if requested. """ # If string, convert to Path objects path_acquisitions = convert_path(path_acquisitions) path_model_folder = convert_path(path_model_folder) # We set the logging from python and Tensorflow to a high level, to avoid messages # in the console when performing segmentation. from logging import ERROR tf.logging.set_verbosity(ERROR) import warnings warnings.filterwarnings('ignore') # Network Parameters patch_size = config_dict["trainingset_patchsize"] n_classes = config_dict["n_classes"] # STEP 1: Load and rescale the acquisitions, and transform them into patches. rs_acquisitions, rs_coeffs, original_acquisitions_shapes = load_acquisitions( path_acquisitions, acquisitions_resolutions, resampled_resolutions, verbose_mode=verbosity_level) # If we are unable to load the model, we return an error message if not path_model_folder.exists(): print('Error: unable to find the requested model.') return [None] * len(path_acquisitions) L_data, L_n_patches, L_positions = prepare_patches(rs_acquisitions, patch_size, overlap_value) # STEP 2: Construct Tensorflow's computing graph and restoration of the session # Construction of the graph if verbosity_level >= 2: print("Graph construction ...") x = tf.placeholder(tf.float32, shape=(None, patch_size, patch_size, 1)) model = uconv_net(config_dict, bn_updated_decay=None, verbose=True) # inference pred = model.output saver = tf.train.Saver() # Load previous model # We limit the amount of GPU for inference config_gpu = tf.ConfigProto(log_device_placement=False) config_gpu.gpu_options.per_process_gpu_memory_fraction = gpu_per # Launch the session (this part takes time). All images will be processed by loading the session just once. sess = tf.Session(config=config_gpu) K.set_session(sess) model_previous_path = path_model_folder.joinpath(ckpt_name).with_suffix( '.ckpt') saver.restore(sess, str(model_previous_path)) # STEP 3: Inference if verbosity_level >= 2: print("Beginning inference ...") n_patches = len(L_data) it, rem = divmod(n_patches, inference_batch_size) predictions_list = [] predictions_proba_list = [] # Inference of complete batches for i in range(it): if verbosity_level >= 3: print(('processing patch %s of %s' % (i + 1, it))) batch_x = np.array(L_data[i * inference_batch_size:(i + 1) * inference_batch_size], dtype=np.uint8) if prediction_proba_activate: # First we perform inference on the input. current_batch_prediction, current_batch_prediction_proba = perform_batch_inference( model, sess, pred, x, batch_x, inference_batch_size, patch_size, n_classes, prediction_proba_activate=prediction_proba_activate) # Update of the predictions lists. predictions_list.extend(current_batch_prediction) predictions_proba_list.extend(current_batch_prediction_proba) else: current_batch_prediction = perform_batch_inference( model, sess, pred, x, batch_x, inference_batch_size, patch_size, n_classes, prediction_proba_activate=prediction_proba_activate) # Update of the predictions lists. predictions_list.extend(current_batch_prediction) # Last batch if needed if rem != 0: if verbosity_level >= 4: print('processing last patch') batch_x = np.asarray(L_data[it * inference_batch_size:]) if prediction_proba_activate: # First we perform inference on the input. current_batch_prediction, current_batch_prediction_proba = perform_batch_inference( model, sess, pred, batch_x, rem, patch_size, n_classes, prediction_proba_activate=prediction_proba_activate) # Update of the predictions lists. predictions_list.extend(current_batch_prediction) predictions_proba_list.extend(current_batch_prediction_proba) else: current_batch_prediction = perform_batch_inference( model, sess, pred, batch_x, rem, patch_size, n_classes, prediction_proba_activate=prediction_proba_activate) # Update of the predictions lists. predictions_list.extend(current_batch_prediction) # End of the inference step. tf.reset_default_graph() # Now we have to transform the list of predictions in list of lists, # one for each full image : we put in each sublist the patches corresponding to a full image. ########### STEP 4: Reconstruction of the segmented patches into segmentations of acquisitions and # resampling to the original size if prediction_proba_activate: predictions, predictions_proba = process_segmented_patches( predictions_list, L_n_patches, L_positions, original_acquisitions_shapes, overlap_value, n_classes, predictions_proba_list=predictions_proba_list, prediction_proba_activate=prediction_proba_activate, verbose_mode=0) return predictions, predictions_proba else: predictions = process_segmented_patches( predictions_list, L_n_patches, L_positions, original_acquisitions_shapes, overlap_value, n_classes, predictions_proba_list=None, prediction_proba_activate=prediction_proba_activate, verbose_mode=0) return predictions
def train_model(path_trainingset, path_model, config, path_model_init=None, save_trainable=True, gpu=None, debug_mode=False, gpu_per=1.0): """ Main function. Trains a model using the configuration parameters. :param path_trainingset: Path to access the trainingset. :param path_model: Path indicating where to save the model. :param config: Dict, containing the configuration parameters of the network. :param path_model_init: Path to where the model to use for initialization is stored. :param save_trainable: Boolean. If True, only saves in the model variables that are trainable (evolve from gradient) :param gpu: String, name of the gpu to use. Prefer use of CUDA_VISIBLE_DEVICES environment variable. :param debug_mode: Boolean. If activated, saves more information about the distributions of most trainable variables, and also outputs more information. :param gpu_per: Float, between 0 and 1. Percentage of GPU to use. :return: Nothing. """ # If string, convert to Path objects path_trainingset = convert_path(path_trainingset) path_model = convert_path(path_model) ################################################################################################################### ############################################## VARIABLES INITIALIZATION ########################################### ################################################################################################################### # Results and Models if not path_model.exists(): path_model.mkdir(parents=True) # Translating useful variables from the config file. learning_rate = config["learning_rate"] batch_size = config["batch_size"] epochs = config["epochs"] image_size = config["trainingset_patchsize"] thresh_indices = config["thresholds"] # Training and Validation Path path_training_set = path_trainingset / "Train" path_validation_set = path_trainingset / "Validation" # List of Training Ids no_train_images = int(len(os.listdir(path_training_set)) / 2) train_ids = [str(i) for i in range(no_train_images)] # List of Validation Ids no_valid_images = int(len(os.listdir(path_validation_set)) / 2) valid_ids = [str(i) for i in range(no_valid_images)] # Loading the Training images and masks in batch train_gen = DataGen(train_ids, path_training_set, batch_size=no_train_images, image_size=image_size, thresh_indices=thresh_indices) image_train_gen, mask_train_gen = train_gen.__getitem__(0) # Loading the Validation images and masks in batch valid_gen = DataGen(valid_ids, path_validation_set, batch_size=no_valid_images, image_size=image_size, thresh_indices=thresh_indices) image_valid_gen, mask_valid_gen = valid_gen.__getitem__(0) ################################################################################################################### ############################################# DATA AUGMENTATION ################################################## ################################################################################################################### # Data dictionary to feed into image generator data_gen_args = dict( horizontal_flip=True, # flipping()[1], vertical_flip=True, # preprocessing_function = elastic # flipping()[0], # rotation_range = random_rotation()[0], # width_shift_range = shifting(patch_size, n_classes)[1], # height_shift_range = shifting(patch_size, n_classes) [0], # fill_mode = "constant", # cval = 0 ) #####################Training################### image_datagen = ImageDataGenerator(**data_gen_args) mask_datagen = ImageDataGenerator(**data_gen_args) seed = 2018 # Image and Mask Data Generator image_generator_train = image_datagen.flow(image_train_gen, y=None, seed=seed, batch_size=batch_size, shuffle=True) mask_generator_train = mask_datagen.flow(mask_train_gen, y=None, seed=seed, batch_size=batch_size, shuffle=True) # Just zip the two generators to get a generator that provides augmented images and masks at the same time train_generator = zip(image_generator_train, mask_generator_train) ###########################Validation########### data_gen_args = dict() image_datagen = ImageDataGenerator(**data_gen_args) mask_datagen = ImageDataGenerator(**data_gen_args) image_generator_valid = image_datagen.flow(x=image_valid_gen, y=None, batch_size=batch_size, seed=seed, shuffle=False) mask_generator_valid = mask_datagen.flow(x=mask_valid_gen, y=None, batch_size=batch_size, seed=seed, shuffle=False) # Just zip the two generators to get a generator that provides augmented images and masks at the same time valid_generator = zip(image_generator_valid, mask_generator_valid) ########################### Initalizing U-Net Model ########### model = uconv_net(config, bn_updated_decay=None, verbose=True) ########################### Tensorboard for Visualization ########### # Name = "SEM_3c_dataset-{}".format(time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime())) tensorboard = TensorBoard(log_dir=str(path_model)) ########################## Training Unet Model ########### # Adam Optimizer for Unet adam = keras.optimizers.Adam(lr=learning_rate, beta_1=0.9, beta_2=0.999, epsilon=1e-08, decay=0.0) # Compile the model with Categorical Cross Entropy loss and Adam Optimizer model.compile(optimizer=adam, loss="categorical_crossentropy", metrics=["accuracy", dice_axon, dice_myelin]) train_steps = len(train_ids) // batch_size valid_steps = len(valid_ids) // batch_size ########################## Use Checkpoints to save best Acuuracy and Loss ########### # Save the checkpoint in the /models/path_model folder filepath_acc = str(path_model) + "/best_acc_model.ckpt" # Keep only a single checkpoint, the best over test accuracy. checkpoint_acc = ModelCheckpoint(filepath_acc, monitor='val_acc', verbose=0, save_best_only=True, mode='max', period=5) # Save the checkpoint in the /models/path_model folder filepath_loss = str(path_model) + "/best_loss_model.ckpt" # Keep only a single checkpoint, the best over test loss. checkpoint_loss = ModelCheckpoint(filepath_loss, monitor='val_loss', verbose=0, save_best_only=True, mode='min', period=5) ########################## Use Checkpoints to save best Acuuracy and Loss ########### model.fit_generator( train_generator, validation_data=(valid_generator), steps_per_epoch=train_steps, validation_steps=valid_steps, epochs=epochs, callbacks=[tensorboard, checkpoint_loss, checkpoint_acc]) ########################## Save the model after Training ########### model.save(str(path_model) + '/model.hdf5') # Add ops to save and restore all the variables. saver = tf.train.Saver() # Save Model in ckpt format custom_objects = {'dice_axon': dice_axon, 'dice_myelin': dice_myelin} model = load_model(str(path_model) + "/model.hdf5", custom_objects=custom_objects) sess = K.get_session() # Save the model to be used by TF framework save_path = saver.save(sess, str(path_model) + "/model.ckpt")
def patched_to_dataset(path_patched_data, path_dataset, type_, random_seed=None): """ Creates a dataset using already created patches. :param path_patched_data: Path to where to find the folders where the patches folders are located. :param path_dataset: Path to where to create the newly formed dataset. :param type_: String, either 'unique' or 'mixed'. Unique means that we create a dataset with only TEM or only SEM data. "Mixed" means that we are creating a dataset with both type of images. :param random_seed: Int, the random seed to use to be able to consistenly recreate generated datasets. :return: None. """ # If string, convert to Path objects path_patched_data = convert_path(path_patched_data) path_dataset = convert_path(path_dataset) # Using the randomseed fed so that given a fixed input, the generation of the datasets is always the same. np.random.seed(random_seed) # First we define where we are going to store the patched data if not path_dataset.exists(): path_dataset.mkdir(parents=True) # First case: there is only one type of acquisition to use. if type_ == 'unique': i = 0 # Total patches index # We loop through all folders containing patches patches_folder_names = [f for f in path_patched_data.iterdir()] for patches_folder in tqdm(patches_folder_names): path_patches_folder = path_patched_data / patches_folder.name if path_patches_folder.is_dir(): # We are now in the patches folder L_img, L_mask = [], [] filenames = [f for f in path_patches_folder.iterdir()] for data in filenames: root, index = data.stem.split('_') if 'image' in data.name: img = ads.imread(path_patches_folder / data.name) L_img.append((img, int(index))) elif 'mask' in data.name: mask = ads.imread(path_patches_folder / data.name) L_mask.append((mask, int(index))) # Now we sort the patches to be sure we get them in the right order L_img_sorted, L_mask_sorted = sort_list_files(L_img, L_mask) # Saving the images in the new folder for img, k in L_img_sorted: ads.imwrite(path_dataset.joinpath('image_%s.png' % i), img) ads.imwrite(path_dataset.joinpath('mask_%s.png' % i), L_mask_sorted[k][0]) i = i + 1 # Using the global i here. # Else we are using different types of acquisitions. It's important to have them separated in a SEM folder # and in a TEM folder. elif type_ == 'mixed': # We determine which acquisition type we are going to upsample (understand : take the same images multiple times) SEM_patches_folder = path_patched_data / 'SEM' TEM_patches_folder = path_patched_data / 'TEM' minority_patches_folder, len_minority, majority_patches_folder, len_majority = find_minority_type( SEM_patches_folder, TEM_patches_folder) # First we move all patches from the majority acquisition type to the new dataset foldernames = [ folder.name for folder in majority_patches_folder.iterdir() ] i = 0 for patches_folder in tqdm(foldernames): path_patches_folder = majority_patches_folder / patches_folder if path_patches_folder.is_dir(): # We are now in the patches folder L_img, L_mask = [], [] filenames = [f for f in path_patches_folder.iterdir()] for data in path_patches_folder.iterdir(): root, index = data.stem.split('_') if 'image' in data.name: img = ads.imread(path_patches_folder / data.name) L_img.append((img, int(index))) elif 'mask' in data.name: mask = ads.imread(path_patches_folder / data.name) L_mask.append((mask, int(index))) # Now we sort the patches to be sure we get them in the right order L_img_sorted, L_mask_sorted = sort_list_files(L_img, L_mask) # Saving the images in the new folder for img, k in L_img_sorted: ads.imwrite(path_dataset.joinpath('image_%s.png' % i), img) ads.imwrite(path_dataset.joinpath('mask_%s.png' % i), L_mask_sorted[k][0]) i = i + 1 # Then we stratify - oversample the minority acquisition to the new dataset # We determine the ratio to take ratio_oversampling = float(len_majority) / len_minority # We go through each image folder in the minorty patches foldernames = [ folder.name for folder in minority_patches_folder.iterdir() ] for patches_folder in tqdm(foldernames): path_patches_folder = minority_patches_folder / patches_folder if path_patches_folder.is_dir(): # We are now in the patches folder # We load every image filenames = [f for f in path_patches_folder.iterdir()] n_img = np.floor(len(filenames) / 2) for data in filenames: root, index = data.stem.split('_') if 'image' in data.name: img = ads.imread(path_patches_folder / data.name) L_img.append((img, int(index))) elif 'mask' in data.name: mask = ads.imread(path_patches_folder / data.name) L_mask.append((mask, int(index))) # Now we sort the patches to be sure we get them in the right order L_img_sorted, L_mask_sorted = sort_list_files(L_img, L_mask) L_merged_sorted = np.asarray([ L_img_sorted[j] + L_mask_sorted[j] for j in range(len(L_img_sorted)) ]) # We create a new array composed of enough elements so that the two types of acquisitions are balanced # (oversampling) L_elements_to_save = L_merged_sorted[ np.random.choice(int(L_merged_sorted.shape[0]), int(np.ceil(ratio_oversampling * n_img)), replace=True), :] # Finally we save all the images in order at the new dataset path. for j in range(L_elements_to_save.shape[0]): img = L_elements_to_save[j][0] mask = L_elements_to_save[j][2] ads.imwrite(path_dataset.joinpath('image_%s.png' % i), img) ads.imwrite(path_dataset.joinpath('mask_%s.png' % i), mask) i = i + 1
def get_axon_morphometrics(im_axon, path_folder, im_myelin=None): """ Find each axon and compute axon-wise morphometric data, e.g., equivalent diameter, eccentricity, etc. If a mask of myelin is provided, also compute myelin-related metrics (myelin thickness, g-ratio, etc.). :param im_axon: Array: axon binary mask, output of axondeepseg :param path_folder: str: absolute path of folder containing pixel size file :param im_myelin: Array: myelin binary mask, output of axondeepseg :return: Array(dict): dictionaries containing morphometric results for each axon """ # If string, convert to Path objects path_folder = convert_path(path_folder) pixelsize = get_pixelsize(path_folder / 'pixel_size_in_micrometer.txt') stats_array = np.empty(0) # Label each axon object im_axon_label = measure.label(im_axon) # Measure properties for each axon object axon_objects = measure.regionprops(im_axon_label) # Deal with myelin mask if im_myelin is not None: im_axonmyelin = im_axon + im_myelin # Compute distance between each pixel and the background. distance = ndi.distance_transform_edt(im_axon) # Note: this distance is calculated from the im_axon, # note from the im_axonmyelin image, because we know that each axon # object is already isolated, therefore the distance metric will be # more useful for the watershed algorithm below. # Get axon centroid as int (not float) to be used as index ind_centroid = ([int(props.centroid[0]) for props in axon_objects], [int(props.centroid[1]) for props in axon_objects]) # Create an image with axon centroids, which value corresponds to the value of the axon object im_centroid = np.zeros_like(im_axon, dtype='uint16') for i in range(len(ind_centroid[0])): # Note: The value "i" corresponds to the label number of im_axon_label im_centroid[ind_centroid[0][i], ind_centroid[1][i]] = i + 1 # Watershed segmentation of axonmyelin using distance map im_axonmyelin_label = morphology.watershed(-distance, im_centroid, mask=im_axonmyelin) # Measure properties of each axonmyelin object axonmyelin_objects = measure.regionprops(im_axonmyelin_label) # Create list of the exiting labels if im_myelin is not None: axonmyelin_labels_list = [axm.label for axm in axonmyelin_objects] # Loop across axon property and fill up dictionary with morphometrics of interest for prop_axon in axon_objects: # Centroid y0, x0 = prop_axon.centroid # Solidity solidity = prop_axon.solidity # Eccentricity eccentricity = prop_axon.eccentricity # Axon equivalent diameter in micrometers axon_diam = prop_axon.equivalent_diameter * pixelsize # Axon area in µm^2 axon_area = prop_axon.area * (pixelsize ** 2) # Axon orientation angle orientation = prop_axon.orientation # Add metrics to list of dictionaries stats = {'y0': y0, 'x0': x0, 'axon_diam': axon_diam, 'axon_area': axon_area, 'solidity': solidity, 'eccentricity': eccentricity, 'orientation': orientation} # Deal with myelin if im_myelin is not None: # Find label of axonmyelin corresponding to axon centroid label_axonmyelin = im_axonmyelin_label[int(y0), int(x0)] if label_axonmyelin: idx = axonmyelin_labels_list.index(label_axonmyelin) prop_axonmyelin = axonmyelin_objects[idx] _res1 = evaluate_myelin_thickness_in_px(prop_axon, prop_axonmyelin) myelin_thickness = pixelsize * _res1 _res2 = evaluate_myelin_area_in_px(prop_axon, prop_axonmyelin) myelin_area = (pixelsize ** 2) * _res2 axonmyelin_area = (pixelsize ** 2) * prop_axonmyelin.area stats['myelin_thickness'] = myelin_thickness stats['myelin_area'] = myelin_area stats['axonmyelin_area'] = axonmyelin_area stats['gratio'] = np.sqrt(axon_area / axonmyelin_area) else: print( "WARNING: Myelin object not found for axon" + "centroid [y:{0}, x:{1}]".format(y0, x0) ) stats_array = np.append(stats_array, [stats], axis=0) return stats_array