예제 #1
0
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
예제 #2
0
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
예제 #4
0
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
예제 #5
0
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)
예제 #6
0
    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
예제 #7
0
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
예제 #8
0
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
예제 #9
0
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)
예제 #10
0
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])
예제 #11
0
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)
예제 #12
0
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)
예제 #13
0
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)
예제 #14
0
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
예제 #15
0
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
예제 #16
0
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
예제 #17
0
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
예제 #18
0
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
예제 #19
0
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
예제 #20
0
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
예제 #21
0
    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()
예제 #22
0
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
예제 #23
0
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
예제 #24
0
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
예제 #25
0
    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
예제 #26
0
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
예제 #27
0
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
예제 #28
0
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")
예제 #29
0
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
예제 #30
0
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