示例#1
0
def dilate_lesions(labels_dir, result_dir, recompute=True):

    utils.mkdir(result_dir)

    path_labels = utils.list_images_in_folder(labels_dir)
    for path_label in path_labels:
        path_result_label = os.path.join(result_dir,
                                         os.path.basename(path_label))
        if (not os.path.isfile(path_result_label)) | recompute:

            label, aff, h = utils.load_volume(path_label, im_only=False)

            # define lesion, WM, and LV masks
            WM = (label == 2) | (label == 41)
            lesion = label == 77
            LV_and_lesion = (label == 4) | lesion

            # morphological operations to bridge the gaps between lesions and LV
            morph_struct = utils.build_binary_structure(2, len(WM.shape))
            LV_and_lesion = binary_dilation(LV_and_lesion, morph_struct)
            LV_and_lesion = binary_erosion(LV_and_lesion, morph_struct)
            lesion = (LV_and_lesion & WM) | lesion
            label[lesion] = 77

            # save new label maps
            utils.save_volume(label, aff, h, path_result_label)
示例#2
0
def build_longitudinal_consensus(labels_dir_1,
                                 labels_dir_2,
                                 result_dir,
                                 recompute=True):

    # create result dir
    utils.mkdir(result_dir)

    # list all segmentations
    path_labels_1 = utils.list_files(labels_dir_1)
    path_labels_2 = utils.list_files(labels_dir_2)

    for path_lab_1, path_lab_2 in zip(path_labels_1, path_labels_2):

        # check if result is already saved
        path_result = os.path.join(result_dir, os.path.basename(path_lab_1))
        if (not os.path.isfile(path_result)) | recompute:

            # load volumes
            lab_1, aff, h = utils.load_volume(path_lab_1, im_only=False)
            lab_2 = utils.load_volume(path_lab_2)

            # compute and save consensus
            dist_masp_1 = edit_volumes.compute_distance_map(lab_1,
                                                            crop_margin=20)
            dist_masp_2 = edit_volumes.compute_distance_map(lab_2,
                                                            crop_margin=20)
            consensus = (np.mean(np.stack([dist_masp_1, dist_masp_2], axis=-1),
                                 axis=-1) > 0) * 1
            utils.save_volume(consensus, aff, h, path_result)
示例#3
0
def paste_lesions_on_buckner(lesion_dir,
                             buckner_dir,
                             result_dir,
                             dilate=2,
                             recompute=False):

    path_lesions = utils.list_images_in_folder(lesion_dir)
    path_buckners = utils.list_images_in_folder(buckner_dir)

    utils.mkdir(result_dir)

    # loop over buckner label maps
    loop_info = utils.LoopInfo(len(path_buckners), 1, 'processing', True)
    for idx_buckner, path_buckner in enumerate(path_buckners):
        loop_info.update(idx_buckner)
        buckner_name = os.path.basename(path_buckner).replace(
            '_seg', '').replace('.nii.gz', '')
        buckner = utils.load_volume(path_buckner)
        WM = (buckner == 2) | (buckner == 7) | (buckner == 16) | (
            buckner == 41) | (buckner == 46)

        # loop over challenge data
        for path_lesion in path_lesions:
            lesion_name = os.path.basename(path_lesion).replace(
                '.samseg_and_lesions.nii.gz', '')
            path_result = os.path.join(
                result_dir, buckner_name + '_' + lesion_name + '.nii.gz')
            if (not os.path.isfile(path_result)) | recompute:

                lesion = utils.load_volume(path_lesion)
                assert lesion.shape == buckner.shape, 'lesions should have same shape as buckner labels'

                # define lesion, WM, and LV masks
                lesion = (lesion == 77) & WM
                LV_and_lesion = (buckner == 4) | lesion

                # morphological operations to bridge the gaps between lesions and LV
                morph_struct = utils.build_binary_structure(
                    dilate, len(lesion.shape))
                lesion = binary_dilation(LV_and_lesion, morph_struct)
                lesion = binary_erosion(lesion, morph_struct)
                lesion = lesion & WM
                buckner_lesions = np.where(lesion, 77, buckner)

                # save map
                utils.save_volume(buckner_lesions, None, None, path_result)
示例#4
0
def preprocess_asegs(aseg_dir,
                     lesion_gt_dir,
                     list_incorrect,
                     list_correct,
                     lesion_label_in_gt=77,
                     dilate=2,
                     recompute=False):

    # align asegs to gt dir (cropping to same dimension)
    cropped_dir = aseg_dir + '_cropped'
    edit_volumes.mri_convert_images_in_dir(aseg_dir,
                                           cropped_dir,
                                           interpolation='nearest',
                                           reference_dir=lesion_gt_dir,
                                           recompute=recompute)

    # correct for aseg labels
    corrected_dir = cropped_dir + '_corrected'
    edit_volumes.correct_labels_in_dir(cropped_dir,
                                       list_incorrect,
                                       list_correct,
                                       corrected_dir,
                                       smooth=False,
                                       recompute=recompute)

    # list gt and aseg, and create result dir
    list_lesion_labels = utils.list_images_in_folder(lesion_gt_dir)
    list_aseg_labels = utils.list_images_in_folder(corrected_dir)
    inpainted_dir = corrected_dir + '_lesion_inpainted'
    utils.mkdir(inpainted_dir)

    # loop over subjects
    for path_lesion_label, path_aseg_label in zip(list_lesion_labels,
                                                  list_aseg_labels):
        path_result = os.path.join(inpainted_dir,
                                   os.path.basename(path_aseg_label))
        if (not os.path.isfile(path_result)) | recompute:

            # paste lesion label
            lesions = utils.load_volume(path_lesion_label)
            aseg_label, aff, h = utils.load_volume(path_aseg_label,
                                                   im_only=False)
            lesion_mask = lesions == lesion_label_in_gt
            aseg_label[lesion_mask] = 77
            utils.save_volume(aseg_label, aff, h, path_result)

    # dilate lesion and ventricle
    dilated_dir = inpainted_dir + '_dilated'
    utils.mkdir(dilated_dir)
    list_inpainted_aseg = utils.list_images_in_folder(inpainted_dir)
    for path_aseg in list_inpainted_aseg:

        path_result = os.path.join(dilated_dir, os.path.basename(path_aseg))
        if (not os.path.isfile(path_result)) | recompute:

            # define lesion, WM, and LV masks
            aseg, aff, h = utils.load_volume(path_aseg, im_only=False)
            WM = aseg == 2
            LV = aseg == 4
            lesion = aseg == 77

            # morphological operations to bridge the gaps between lesions and LV
            morph_struct = utils.build_binary_structure(
                dilate, len(aseg.shape))
            dilated_LV_or_lesion = binary_dilation(LV | lesion, morph_struct)
            filled_LV_or_lesion = binary_erosion(dilated_LV_or_lesion,
                                                 morph_struct)
            LV = LV | (filled_LV_or_lesion & WM)
            aseg[LV] = 4

            # save map
            utils.save_volume(aseg, aff, h, path_result)
示例#5
0
########################################################################################################

# instantiate BrainGenerator object
brain_generator = BrainGenerator(labels_dir=path_label_maps,
                                 generation_labels=generation_labels,
                                 output_labels=segmentation_labels,
                                 n_neutral_labels=n_neutral_labels,
                                 output_shape=output_shape,
                                 output_div_by_n=output_divisible_by_n,
                                 prior_distributions=prior_distributions,
                                 flipping=flipping)

utils.mkdir(result_folder)

for n in range(n_examples):

    # generate new image and corresponding labels
    start = time.time()
    im, lab = brain_generator.generate_brain()
    end = time.time()
    print('deformation {0:d} took {1:.01f}s'.format(n, end - start))

    # save image
    utils.save_volume(
        np.squeeze(im), brain_generator.aff, brain_generator.header,
        os.path.join(result_folder, 'SynthSeg_image_%s.nii.gz' % n))
    utils.save_volume(
        np.squeeze(lab), brain_generator.aff, brain_generator.header,
        os.path.join(result_folder, 'SynthSeg_labels_%s.nii.gz' % n))
示例#6
0
# Very simple script showing how to generate new images with lab2im

import os
import numpy as np
from ext.lab2im.utils import save_volume
from SynthSeg.brain_generator import BrainGenerator

# path of the input label map
path_label_map = '../data_example/brain_label_map.nii.gz'
# path where to save the generated image
result_dir = '../generated_images'

# generate an image from the label map.
# Because the image is spatially deformed, we also output the corresponding deformed label map.
brain_generator = BrainGenerator(path_label_map)
im, lab = brain_generator.generate_brain()

# save output image and label map
if not os.path.exists(os.path.join(result_dir)):
    os.mkdir(result_dir)
save_volume(np.squeeze(im), brain_generator.aff, brain_generator.header,
            os.path.join(result_dir, 'brain.nii.gz'))
save_volume(np.squeeze(lab), brain_generator.aff, brain_generator.header,
            os.path.join(result_dir, 'labels.nii.gz'))
示例#7
0
def prepare_hippo_training_atlases(labels_dir,
                                   result_dir,
                                   image_dir=None,
                                   image_result_dir=None,
                                   smooth=True,
                                   crop_margin=50,
                                   recompute=True):
    """This function prepares training label maps from CobraLab. It first crops each atlas around the right and left
    hippocampi, with a margin. It then equalises the shape of these atlases by croppping them to the size of the
    smallest hippocampus. Finally it realigns the obtained atlases to FS orientation axes.
    :param labels_dir: path of directory with label maps to prepare
    :param result_dir: path of directory where prepared atlases will be writen
    :param image_dir: (optional) path of directory with images corresponding to the label maps to prepare.
    This can be sued to prepare a dataset of real images for supervised training.
    :param image_result_dir: (optional) path of directory where images corresponding to prepared atlases will be writen
    :param smooth: (optional) whether to smooth the final cropped label maps
    :param crop_margin: (optional) margin to add around hippocampi when cropping
    :param recompute: (optional) whether to recompute result files even if they already exists"""

    # create results dir
    if not os.path.exists(result_dir):
        os.mkdir(result_dir)
    tmp_result_dir = os.path.join(result_dir, 'first_cropping')
    if not os.path.exists(tmp_result_dir):
        os.mkdir(tmp_result_dir)
    if image_dir is not None:
        assert image_result_dir is not None, 'image_result_dir should not be None if image_dir is specified'
        if not os.path.exists(image_result_dir):
            os.mkdir(image_result_dir)
        tmp_image_result_dir = os.path.join(image_result_dir, 'first_cropping')
        if not os.path.exists(tmp_image_result_dir):
            os.mkdir(tmp_image_result_dir)
    else:
        tmp_image_result_dir = None

    # list labels and images
    labels_paths = utils.list_images_in_folder(labels_dir)
    if image_dir is not None:
        path_images = utils.list_images_in_folder(image_dir)
    else:
        path_images = [None] * len(labels_paths)

    # crop all atlases around hippo
    print('\ncropping around hippo')
    shape_array = np.zeros((len(labels_paths)*2, 3))
    for idx, (path_label, path_image) in enumerate(zip(labels_paths, path_images)):
        utils.print_loop_info(idx, len(labels_paths), 1)

        # crop left hippo first
        path_label_first_crop_l = os.path.join(tmp_result_dir,
                                               os.path.basename(path_label).replace('.nii', '_left.nii'))
        lab, aff, h = utils.load_volume(path_label, im_only=False)
        lab_l, croppping_idx, aff_l = edit_volumes.crop_volume_around_region(lab, crop_margin,
                                                                             list(range(20101, 20109)), aff=aff)
        if (not os.path.exists(path_label_first_crop_l)) | recompute:
            utils.save_volume(lab_l, aff_l, h, path_label_first_crop_l)
        else:
            lab_l = utils.load_volume(path_label_first_crop_l)
        if path_image is not None:
            path_image_first_crop_l = os.path.join(tmp_image_result_dir,
                                                   os.path.basename(path_image).replace('.nii', '_left.nii'))
            if (not os.path.exists(path_image_first_crop_l)) | recompute:
                im, aff, h = utils.load_volume(path_image, im_only=False)
                im, aff = edit_volumes.crop_volume_with_idx(im, croppping_idx, aff)
                utils.save_volume(im, aff, h, path_image_first_crop_l)
        shape_array[2*idx, :] = np.array(lab_l.shape)

        # crop right hippo and flip them
        path_label_first_crop_r = os.path.join(tmp_result_dir,
                                               os.path.basename(path_label).replace('.nii', '_right_flipped.nii'))
        lab, aff, h = utils.load_volume(path_label, im_only=False)
        lab_r, croppping_idx, aff_r = edit_volumes.crop_volume_around_region(lab, crop_margin,
                                                                             list(range(20001, 20009)), aff=aff)
        if (not os.path.exists(path_label_first_crop_r)) | recompute:
            lab_r = edit_volumes.flip_volume(lab_r, direction='rl', aff=aff_r)
            utils.save_volume(lab_r, aff_r, h, path_label_first_crop_r)
        else:
            lab_r = utils.load_volume(path_label_first_crop_r)
        if path_image is not None:
            path_image_first_crop_r = os.path.join(tmp_image_result_dir,
                                                   os.path.basename(path_image).replace('.nii', '_right.nii'))
            if (not os.path.exists(path_image_first_crop_r)) | recompute:
                im, aff, h = utils.load_volume(path_image, im_only=False)
                im, aff = edit_volumes.crop_volume_with_idx(im, croppping_idx, aff)
                im = edit_volumes.flip_volume(im, direction='rl', aff=aff)
                utils.save_volume(im, aff, h, path_image_first_crop_r)
        shape_array[2*idx+1, :] = np.array(lab_r.shape)

    # list croppped files
    path_labels_first_cropped = utils.list_images_in_folder(tmp_result_dir)
    if tmp_image_result_dir is not None:
        path_images_first_cropped = utils.list_images_in_folder(tmp_image_result_dir)
    else:
        path_images_first_cropped = [None] * len(path_labels_first_cropped)

    # crop all label maps to same size
    print('\nequalising shapes')
    new_shape = np.min(shape_array, axis=0).astype('int32')
    for i, (path_label, path_image) in enumerate(zip(path_labels_first_cropped, path_images_first_cropped)):
        utils.print_loop_info(i, len(path_labels_first_cropped), 1)

        # get cropping indices
        path_lab_cropped = os.path.join(result_dir, os.path.basename(path_label))
        lab, aff, h = utils.load_volume(path_label, im_only=False)
        lab_shape = lab.shape
        min_cropping = np.array([np.maximum(int((lab_shape[i]-new_shape[i])/2), 0) for i in range(3)])
        max_cropping = np.array([min_cropping[i] + new_shape[i] for i in range(3)])

        # crop labels and realign on adni format
        if (not os.path.exists(path_lab_cropped)) | recompute:
            lab, aff = edit_volumes.crop_volume_with_idx(lab, np.concatenate([min_cropping, max_cropping]), aff)
            # realign on adni format
            lab = np.flip(lab, axis=2)
            aff[0:3, 0:3] = np.array([[-0.6, 0, 0], [0, 0, -0.6], [0, -0.6, 0]])
            utils.save_volume(lab, aff, h, path_lab_cropped)

        # crop image and realign on adni format
        if path_image is not None:
            path_im_cropped = os.path.join(image_result_dir, os.path.basename(path_image))
            if (not os.path.exists(path_im_cropped)) | recompute:
                im, aff, h = utils.load_volume(path_image, im_only=False)
                im, aff = edit_volumes.crop_volume_with_idx(im, np.concatenate([min_cropping, max_cropping]), aff)
                im = np.flip(im, axis=2)
                aff[0:3, 0:3] = np.array([[-0.6, 0, 0], [0, 0, -0.6], [0, -0.6, 0]])
                im = edit_volumes.mask_volume(im, lab)
                utils.save_volume(im, aff, h, path_im_cropped)

    # correct all labels to left values
    print('\ncorrecting labels')
    list_incorrect_labels = [77, 80, 251, 252, 253, 254, 255, 29, 41, 42, 43, 44, 46, 47, 49, 50, 51, 52, 54, 58, 60,
                             61, 62, 63, 7012, 20001, 20002, 20004, 20005, 20006, 20007, 20008]
    list_correct_labels = [2, 3, 2, 2, 2, 2, 2, 2, 2, 3, 4, 5, 7, 8, 10, 11, 12, 13, 18, 26, 28, 2, 30, 31, 20108,
                           20101, 20102, 20104, 20105, 20106, 20107, 20108]
    edit_volumes.correct_labels_in_dir(result_dir, list_incorrect_labels, list_correct_labels, result_dir)

    # smooth labels
    if smooth:
        print('\nsmoothing labels')
        edit_volumes.smooth_labels_in_dir(result_dir, result_dir)
示例#8
0
def preprocess_adni_hippo(path_t1,
                          path_t2,
                          path_aseg,
                          result_dir,
                          target_res,
                          padding_margin=85,
                          remove=False,
                          path_freesurfer='/usr/local/freesurfer/',
                          verbose=True,
                          recompute=True):
    """This function builds a T1+T2 multimodal image from the ADNI dataset.
    It first rescales intensities of each channel between 0 and 255.
    It then resamples the T2 image (which are 0.4*0.4*2.0 resolution) to target resolution.
    The obtained T2 is then padded in all directions by the padding_margin param (typically large 85).
    The T1 and aseg are then resampled like the T2 using mri_convert.
    Now that the T1, T2 and asegs are aligned and at the same resolution, we crop them around the right and left hippo.
    Finally, the T1 and T2 are concatenated into one single multimodal image.
    :param path_t1: path input T1 (typically at 1mm isotropic)
    :param path_t2: path input T2 (typically cropped around the hippo in sagittal axis, 0.4x0.4x2.0)
    :param path_aseg: path input segmentation (typically at 1mm isotropic)
    :param result_dir: path of directory where prepared images and labels will be writen.
    :param target_res: resolution at which to resample the label maps, and the images.
    Can be a number (isotropic resolution), a sequence, or a 1d numpy array.
    :param padding_margin: (optional) margin to add around hippocampi when cropping
    :param remove: (optional) whether to delete temporary files. Default is True.
    :param path_freesurfer: (optional) path of FreeSurfer home, to use mri_convert
    :param verbose: (optional) whether to print out mri_convert output when resampling images
    :param recompute: (optional) whether to recompute result files even if they already exists
    """

    # create results dir
    if not os.path.isdir(result_dir):
        os.mkdir(result_dir)

    path_test_im_right = os.path.join(result_dir, 'hippo_right.nii.gz')
    path_test_aseg_right = os.path.join(result_dir, 'hippo_right_aseg.nii.gz')
    path_test_im_left = os.path.join(result_dir, 'hippo_left.nii.gz')
    path_test_aseg_left = os.path.join(result_dir, 'hippo_left_aseg.nii.gz')
    if (not os.path.isfile(path_test_im_right)) | (not os.path.isfile(path_test_aseg_right)) | \
       (not os.path.isfile(path_test_im_left)) | (not os.path.isfile(path_test_aseg_left)) | recompute:

        # set up FreeSurfer
        os.environ['FREESURFER_HOME'] = path_freesurfer
        os.system(os.path.join(path_freesurfer, 'SetUpFreeSurfer.sh'))
        mri_convert = os.path.join(path_freesurfer, 'bin/mri_convert.bin')

        # rescale T1
        path_t1_rescaled = os.path.join(result_dir, 't1_rescaled.nii.gz')
        if (not os.path.isfile(path_t1_rescaled)) | recompute:
            im, aff, h = utils.load_volume(path_t1, im_only=False)
            im = edit_volumes.rescale_volume(im)
            utils.save_volume(im, aff, h, path_t1_rescaled)
        # rescale T2
        path_t2_rescaled = os.path.join(result_dir, 't2_rescaled.nii.gz')
        if (not os.path.isfile(path_t2_rescaled)) | recompute:
            im, aff, h = utils.load_volume(path_t2, im_only=False)
            im = edit_volumes.rescale_volume(im)
            utils.save_volume(im, aff, h, path_t2_rescaled)

        # resample T2 to target res
        path_t2_resampled = os.path.join(result_dir, 't2_rescaled_resampled.nii.gz')
        if (not os.path.isfile(path_t2_resampled)) | recompute:
            str_res = ' '.join([str(r) for r in utils.reformat_to_list(target_res, length=3)])
            cmd = mri_convert + ' ' + path_t2_rescaled + ' ' + path_t2_resampled + ' --voxsize ' + str_res
            cmd += ' -odt float'
            if not verbose:
                cmd += ' >/dev/null 2>&1'
            _ = os.system(cmd)

        # pad T2
        path_t2_padded = os.path.join(result_dir, 't2_rescaled_resampled_padded.nii.gz')
        if (not os.path.isfile(path_t2_padded)) | recompute:
            t2, aff, h = utils.load_volume(path_t2_resampled, im_only=False)
            t2_padded = np.pad(t2, padding_margin, 'constant')
            aff[:3, -1] = aff[:3, -1] - (aff[:3, :3] @ (padding_margin * np.ones((3, 1)))).T
            utils.save_volume(t2_padded, aff, h, path_t2_padded)

        # resample T1 and aseg accordingly
        path_t1_resampled = os.path.join(result_dir, 't1_rescaled_resampled.nii.gz')
        if (not os.path.isfile(path_t1_resampled)) | recompute:
            cmd = mri_convert + ' ' + path_t1_rescaled + ' ' + path_t1_resampled + ' -rl ' + path_t2_padded
            cmd += ' -odt float'
            if not verbose:
                cmd += ' >/dev/null 2>&1'
            _ = os.system(cmd)
        path_aseg_resampled = os.path.join(result_dir, 'aseg_resampled.nii.gz')
        if (not os.path.isfile(path_aseg_resampled)) | recompute:
            cmd = mri_convert + ' ' + path_aseg + ' ' + path_aseg_resampled + ' -rl ' + path_t2_padded
            cmd += ' -rt nearest -odt float'
            if not verbose:
                cmd += ' >/dev/null 2>&1'
            _ = os.system(cmd)

        # crop images and concatenate T1 and T2
        for lab, side in zip([17, 53], ['left', 'right']):
            path_test_image = os.path.join(result_dir, 'hippo_{}.nii.gz'.format(side))
            path_test_aseg = os.path.join(result_dir, 'hippo_{}_aseg.nii.gz'.format(side))
            if (not os.path.isfile(path_test_image)) | (not os.path.isfile(path_test_aseg)) | recompute:
                aseg, aff, h = utils.load_volume(path_aseg_resampled, im_only=False)
                tmp_aseg, cropping, tmp_aff = edit_volumes.crop_volume_around_region(aseg,
                                                                                     margin=30,
                                                                                     masking_labels=lab,
                                                                                     aff=aff)
                if side == 'right':
                    tmp_aseg = edit_volumes.flip_volume(tmp_aseg, direction='rl', aff=tmp_aff)
                utils.save_volume(tmp_aseg, tmp_aff, h, path_test_aseg)
                if (not os.path.isfile(path_test_image)) | recompute:
                    t1 = utils.load_volume(path_t1_resampled)
                    t1 = edit_volumes.crop_volume_with_idx(t1, crop_idx=cropping)
                    t1 = edit_volumes.mask_volume(t1, tmp_aseg, dilate=6, erode=5)
                    t2 = utils.load_volume(path_t2_padded)
                    t2 = edit_volumes.crop_volume_with_idx(t2, crop_idx=cropping)
                    t2 = edit_volumes.mask_volume(t2, tmp_aseg, dilate=6, erode=5)
                    if side == 'right':
                        t1 = edit_volumes.flip_volume(t1, direction='rl', aff=tmp_aff)
                        t2 = edit_volumes.flip_volume(t2, direction='rl', aff=tmp_aff)
                    test_image = np.stack([t1, t2], axis=-1)
                    utils.save_volume(test_image, tmp_aff, h, path_test_image)

        # remove unnecessary files
        if remove:
            list_files_to_remove = [path_t1_rescaled,
                                    path_t2_rescaled,
                                    path_t2_resampled,
                                    path_t2_padded,
                                    path_t1_resampled,
                                    path_aseg_resampled]
            for path in list_files_to_remove:
                os.remove(path)
示例#9
0
文件: predict.py 项目: mu40/SynthSeg
def predict(path_images,
            path_model,
            segmentation_label_list,
            path_segmentations=None,
            path_posteriors=None,
            path_volumes=None,
            skip_background_volume=True,
            padding=None,
            cropping=None,
            resample=None,
            aff_ref='FS',
            sigma_smoothing=0,
            keep_biggest_component=False,
            conv_size=3,
            n_levels=5,
            nb_conv_per_level=2,
            unet_feat_count=24,
            feat_multiplier=2,
            no_batch_norm=False,
            activation='elu',
            gt_folder=None,
            evaluation_label_list=None,
            verbose=True):
    """
    This function uses trained models to segment images.
    It is crucial that the inputs match the architecture parameters of the trained model.
    :param path_images: path of the images to segment. Can be the path to a directory or the path to a single image.
    :param path_model: path ot the trained model.
    :param segmentation_label_list: List of labels for which to compute Dice scores. It should contain the same values
    as the segmentation label list used for training the network.
    Can be a sequence, a 1d numpy array, or the path to a numpy 1d array.
    :param path_segmentations: (optional) path where segmentations will be writen.
    Should be a dir, if path_images is a dir, and afile if path_images is a file.
    Should not be None, if path_posteriors is None.
    :param path_posteriors: (optional) path where posteriors will be writen.
    Should be a dir, if path_images is a dir, and afile if path_images is a file.
    Should not be None, if path_segmentations is None.
    :param path_volumes: (optional) path of a csv file where the soft volumes of all segmented regions will be writen.
    The rows of the csv file correspond to subjects, and the columns correspond to segmentation labels.
    The soft volume of a structure corresponds to the sum of its predicted probability map.
    :param skip_background_volume: (optional) whether to skip computing the volume of the background. This assumes the
    background correspond to the first value in label list.
    :param padding: (optional) crop the images to the specified shape before predicting the segmentation maps.
    If padding and cropping are specified, images are padded before being cropped.
    Can be an int, a sequence or a 1d numpy array.
    :param cropping: (optional) crop the images to the specified shape before predicting the segmentation maps.
    If padding and cropping are specified, images are padded before being cropped.
    Can be an int, a sequence or a 1d numpy array.
    :param resample: (optional) resample the images to the specified resolution before predicting the segmentation maps.
    Can be an int, a sequence or a 1d numpy array.
    :param aff_ref: (optional) type of affine matrix of the images used for training. By default this is set to the
    FreeSurfer orientation ('FS'), as it was the configuration in which SynthSeg was trained. However, the new models
    are now trained on data aligned with identity vox2ras matrix, so you need to change aff_ref to 'identity'.
    :param sigma_smoothing: (optional) If not None, the posteriors are smoothed with a gaussian kernel of the specified
    standard deviation.
    :param keep_biggest_component: (optional) whether to only keep the biggest component in the predicted segmentation.
    :param conv_size: (optional) size of unet's convolution masks. Default is 3.
    :param n_levels: (optional) number of levels for unet. Default is 5.
    :param nb_conv_per_level: (optional) number of convolution layers per level. Default is 2.
    :param unet_feat_count: (optional) number of features for the first layer of the unet. Default is 24.
    :param feat_multiplier: (optional) multiplicative factor for the number of feature for each new level. Default is 2.
    :param no_batch_norm: (optional) whether to deactivate batch norm. Default is False.
    :param activation: (optional) activation function. Can be 'elu', 'relu'.
    :param gt_folder: (optional) folder containing ground truth files for evaluation.
    A numpy array containing all dice scores (labels in rows, subjects in columns) will be writen either at
    segmentations_dir (if not None), or posteriors_dir.
    :param evaluation_label_list: (optional) if gt_folder is True you can evaluate the Dice scores on a subset of the
    segmentation labels, by providing another label list here. Can be a sequence, a 1d numpy array, or the path to a
    numpy 1d array. Default is the same as segmentation_label_list.
    :param verbose: (optional) whether to print out info about the remaining number of cases.
    """

    assert path_model, "A model file is necessary"
    assert path_segmentations or path_posteriors, "output segmentation (or posteriors) is required"

    # prepare output filepaths
    images_to_segment, path_segmentations, path_posteriors, path_volumes = prepare_output_files(path_images,
                                                                                                path_segmentations,
                                                                                                path_posteriors,
                                                                                                path_volumes)

    # get label and classes lists
    label_list, _ = utils.get_list_labels(label_list=segmentation_label_list, FS_sort=True)
    if evaluation_label_list is None:
        evaluation_label_list = segmentation_label_list

    # prepare volume file if needed
    if path_volumes is not None:
        if skip_background_volume:
            csv_header = [['subject'] + [str(lab) for lab in label_list[1:]]]
        else:
            csv_header = [['subject'] + [str(lab) for lab in label_list]]
        with open(path_volumes, 'w') as csvFile:
            writer = csv.writer(csvFile)
            writer.writerows(csv_header)
        csvFile.close()

    # perform segmentation
    net = None
    previous_model_input_shape = None
    for idx, (path_image, path_segmentation, path_posterior) in enumerate(zip(images_to_segment,
                                                                              path_segmentations,
                                                                              path_posteriors)):
        if verbose:
            utils.print_loop_info(idx, len(images_to_segment), 10)

        # preprocess image and get information
        image, aff, h, im_res, n_channels, n_dims, shape, pad_shape, cropping, crop_idx = \
            preprocess_image(path_image, n_levels, cropping, padding, aff_ref=aff_ref)
        model_input_shape = list(image.shape[1:])

        # prepare net for first image or if input's size has changed
        if (idx == 0) | (previous_model_input_shape != model_input_shape):

            # check for image size compatibility
            if (idx != 0) & (previous_model_input_shape != model_input_shape):
                print('image of different shape as previous ones, redefining network')
            previous_model_input_shape = model_input_shape

            # build network
            net = build_model(path_model, model_input_shape, resample, im_res, n_levels, len(label_list), conv_size,
                              nb_conv_per_level, unet_feat_count, feat_multiplier, no_batch_norm, activation,
                              sigma_smoothing)

        # predict posteriors
        prediction_patch = net.predict(image)

        # get posteriors and segmentation
        seg, posteriors = postprocess(prediction_patch, cropping, pad_shape, shape, crop_idx, n_dims, label_list,
                                      keep_biggest_component, aff, aff_ref=aff_ref)

        # compute volumes
        if path_volumes is not None:
            if skip_background_volume:
                volumes = np.sum(posteriors[..., 1:], axis=tuple(range(0, len(posteriors.shape) - 1)))
            else:
                volumes = np.sum(posteriors, axis=tuple(range(0, len(posteriors.shape) - 1)))
            volumes = np.around(volumes * np.prod(im_res), 3)
            row = [os.path.basename(path_image).replace('.nii.gz', '')] + [str(vol) for vol in volumes]
            row += [np.sum(volumes[:int(len(volumes) / 2)]), np.sum(volumes[int(len(volumes) / 2):])]
            with open(path_volumes, 'a') as csvFile:
                writer = csv.writer(csvFile)
                writer.writerow(row)
            csvFile.close()

        # write results to disk
        if path_segmentation is not None:
            utils.save_volume(seg.astype('int'), aff, h, path_segmentation)
        if path_posterior is not None:
            if n_channels > 1:
                new_shape = list(posteriors.shape)
                new_shape.insert(-1, 1)
                new_shape = tuple(new_shape)
                posteriors = np.reshape(posteriors, new_shape)
            utils.save_volume(posteriors.astype('float'), aff, h, path_posterior)

    # evaluate
    if gt_folder is not None:
        if path_segmentations[0] is not None:
            eval_folder = os.path.dirname(path_segmentations[0])
        else:
            eval_folder = os.path.dirname(path_posteriors[0])
        path_result_dice = os.path.join(eval_folder, 'dice.npy')
        evaluate.dice_evaluation(gt_folder, eval_folder, evaluation_label_list, path_result_dice, verbose=verbose)
示例#10
0
def predict(path_images,
            path_model,
            segmentation_label_list,
            dist_map=False,
            path_segmentations=None,
            path_posteriors=None,
            path_volumes=None,
            segmentation_names_list=None,
            padding=None,
            cropping=None,
            resample=None,
            aff_ref='FS',
            sigma_smoothing=0,
            keep_biggest_component=False,
            conv_size=3,
            n_levels=5,
            nb_conv_per_level=2,
            unet_feat_count=24,
            feat_multiplier=2,
            activation='elu',
            gt_folder=None,
            evaluation_label_list=None,
            compute_distances=False,
            recompute=True,
            verbose=True):
    """
    This function uses trained models to segment images.
    It is crucial that the inputs match the architecture parameters of the trained model.
    :param path_images: path of the images to segment. Can be the path to a directory or the path to a single image.
    :param path_model: path ot the trained model.
    :param segmentation_label_list: List of labels for which to compute Dice scores. It should contain the same values
    as the segmentation label list used for training the network.
    Can be a sequence, a 1d numpy array, or the path to a numpy 1d array.
    :param dist_map: (optional) whether the input will contain distance maps channels (between each intenisty channels)
    Default is False.
    :param path_segmentations: (optional) path where segmentations will be writen.
    Should be a dir, if path_images is a dir, and afile if path_images is a file.
    Should not be None, if path_posteriors is None.
    :param path_posteriors: (optional) path where posteriors will be writen.
    Should be a dir, if path_images is a dir, and afile if path_images is a file.
    Should not be None, if path_segmentations is None.
    :param path_volumes: (optional) path of a csv file where the soft volumes of all segmented regions will be writen.
    The rows of the csv file correspond to subjects, and the columns correspond to segmentation labels.
    The soft volume of a structure corresponds to the sum of its predicted probability map.
    :param segmentation_names_list: (optional) List of names correponding to the names of the segmentation labels.
    Only used when path_volumes is provided. Must be of the same size as segmentation_label_list. Can be given as a
    list, a numpy array of strings, or the path to such a numpy array. Default is None.
    :param padding: (optional) pad the images to the specified shape before predicting the segmentation maps.
    Can be an int, a sequence or a 1d numpy array.
    :param cropping: (optional) crop the images to the specified shape before predicting the segmentation maps.
    If padding and cropping are specified, images are padded before being cropped.
    Can be an int, a sequence or a 1d numpy array.
    :param resample: (optional) resample the images to the specified resolution before predicting the segmentation maps.
    Can be an int, a sequence or a 1d numpy array.
    :param aff_ref: (optional) type of affine matrix of the images used for training. By default this is set to the
    FreeSurfer orientation ('FS'), as it was the configuration in which SynthSeg was trained. However, the new models
    are now trained on data aligned with identity vox2ras matrix, so you need to change aff_ref to 'identity'.
    :param sigma_smoothing: (optional) If not None, the posteriors are smoothed with a gaussian kernel of the specified
    standard deviation.
    :param keep_biggest_component: (optional) whether to only keep the biggest component in the predicted segmentation.
    :param conv_size: (optional) size of unet's convolution masks. Default is 3.
    :param n_levels: (optional) number of levels for unet. Default is 5.
    :param nb_conv_per_level: (optional) number of convolution layers per level. Default is 2.
    :param unet_feat_count: (optional) number of features for the first layer of the unet. Default is 24.
    :param feat_multiplier: (optional) multiplicative factor for the number of feature for each new level. Default is 2.
    :param activation: (optional) activation function. Can be 'elu', 'relu'.
    :param gt_folder: (optional) folder containing ground truth files for evaluation.
    A numpy array containing all dice scores (labels in rows, subjects in columns) will be writen either at
    segmentations_dir (if not None), or posteriors_dir.
    :param evaluation_label_list: (optional) if gt_folder is True you can evaluate the Dice scores on a subset of the
    segmentation labels, by providing another label list here. Can be a sequence, a 1d numpy array, or the path to a
    numpy 1d array. Default is the same as segmentation_label_list.
    :param recompute: (optional) whether to recompute segmentations that were already computed. This also applies to
    Dice scores, if gt_folder is not None. Default is True.
    :param verbose: (optional) whether to print out info about the remaining number of cases.
    """

    # prepare output filepaths
    images_to_segment, path_segmentations, path_posteriors, path_volumes, compute = \
        prepare_output_files(path_images, path_segmentations, path_posteriors, path_volumes, recompute)

    # get label and classes lists
    label_list, n_neutral_labels = utils.get_list_labels(label_list=segmentation_label_list, FS_sort=True)
    if evaluation_label_list is None:
        evaluation_label_list = segmentation_label_list

    # prepare volume file if needed
    if path_volumes is not None:
        if segmentation_names_list is not None:
            csv_header = [[''] + utils.reformat_to_list(segmentation_names_list, load_as_numpy=True)]
            csv_header += [[''] + [str(lab) for lab in label_list[1:]]]
        else:
            csv_header = [['subjects'] + [str(lab) for lab in label_list[1:]]]
        with open(path_volumes, 'w') as csvFile:
            writer = csv.writer(csvFile)
            writer.writerows(csv_header)
        csvFile.close()

    # perform segmentation
    net = None
    previous_model_input_shape = None
    loop_info = utils.LoopInfo(len(images_to_segment), 10, 'predicting', True)
    for idx, (path_image, path_segmentation, path_posterior, tmp_compute) in enumerate(zip(images_to_segment,
                                                                                           path_segmentations,
                                                                                           path_posteriors,
                                                                                           compute)):
        # compute segmentation only if needed
        if tmp_compute:

            # preprocess image and get information
            image, aff, h, im_res, n_channels, n_dims, shape, pad_shape, crop_idx = \
                preprocess_image(path_image, n_levels, cropping, padding, aff_ref=aff_ref, dist_map=dist_map)
            model_input_shape = list(image.shape[1:])

            # prepare net for first image or if input's size has changed
            if (net is None) | (previous_model_input_shape != model_input_shape):

                # check for image size compatibility
                if (net is not None) & (previous_model_input_shape != model_input_shape) & verbose:
                    print('image of different shape as previous ones, redefining network')
                previous_model_input_shape = model_input_shape

                # build network
                net = build_model(path_model, model_input_shape, resample, im_res, n_levels, len(label_list), conv_size,
                                  nb_conv_per_level, unet_feat_count, feat_multiplier, activation, sigma_smoothing)

            if verbose:
                loop_info.update(idx)

            # predict posteriors
            prediction_patch = net.predict(image)

            # get posteriors and segmentation
            seg, posteriors = postprocess(prediction_patch, pad_shape, shape, crop_idx, n_dims, label_list,
                                          keep_biggest_component, aff, aff_ref=aff_ref,
                                          keep_biggest_of_each_group=keep_biggest_component,
                                          n_neutral_labels=n_neutral_labels)

            # write results to disk
            if path_segmentation is not None:
                utils.save_volume(seg.astype('int'), aff, h, path_segmentation)
            if path_posterior is not None:
                if n_channels > 1:
                    posteriors = utils.add_axis(posteriors, axis=[0, -1])
                utils.save_volume(posteriors.astype('float'), aff, h, path_posterior)

        else:
            if path_volumes is not None:
                posteriors, _, _, _, _, _, im_res = utils.get_volume_info(path_posterior, True, aff_ref=np.eye(4))
            else:
                posteriors = im_res = None

        # compute volumes
        if path_volumes is not None:
            volumes = np.sum(posteriors[..., 1:], axis=tuple(range(0, len(posteriors.shape) - 1)))
            volumes = np.around(volumes * np.prod(im_res), 3)
            row = [os.path.basename(path_image).replace('.nii.gz', '')] + [str(vol) for vol in volumes]
            with open(path_volumes, 'a') as csvFile:
                writer = csv.writer(csvFile)
                writer.writerow(row)
            csvFile.close()

    # evaluate
    if gt_folder is not None:

        # find path evaluation folder
        path_first_result = path_segmentations[0] if (path_segmentations[0] is not None) else path_posteriors[0]
        eval_folder = os.path.dirname(path_first_result)

        # compute evaluation metrics
        evaluate.dice_evaluation(gt_folder,
                                 eval_folder,
                                 evaluation_label_list,
                                 compute_distances=compute_distances,
                                 compute_score_whole_structure=False,
                                 path_dice=os.path.join(eval_folder, 'dice.npy'),
                                 path_hausdorff=os.path.join(eval_folder, 'hausdorff.npy'),
                                 path_mean_distance=os.path.join(eval_folder, 'mean_distance.npy'),
                                 recompute=recompute,
                                 verbose=verbose)
示例#11
0
    if args['ct']:
        im[im < 0] = 0
        im[im > 80] = 80
    im, aff = edit_volumes.resample_volume(im, aff, [1.0, 1.0, 1.0])
    aff_ref = np.eye(4)
    im, aff2 = edit_volumes.align_volume_to_ref(im,
                                                aff,
                                                aff_ref=aff_ref,
                                                return_aff=True,
                                                n_dims=3)
    im = im - np.min(im)
    im = im / np.max(im)
    I = im[np.newaxis, ..., np.newaxis]
    W = (np.ceil(np.array(I.shape[1:-1]) / 32.0) * 32).astype('int')
    idx = np.floor((W - I.shape[1:-1]) / 2).astype('int')
    S = np.zeros([1, *W, 1])
    S[0, idx[0]:idx[0] + I.shape[1], idx[1]:idx[1] + I.shape[2],
      idx[2]:idx[2] + I.shape[3], :] = I
    output = unet_model.predict(S)
    pred = np.squeeze(output)
    pred = 255 * pred
    pred[pred < 0] = 0
    pred[pred > 128] = 128
    pred = pred[idx[0]:idx[0] + I.shape[1], idx[1]:idx[1] + I.shape[2],
                idx[2]:idx[2] + I.shape[3]]
    utils.save_volume(pred, aff2, None, path_prediction)

print(' ')
print('All done!')
print(' ')
示例#12
0
def predict(path_images,
            path_model,
            segmentation_label_list,
            path_segmentations=None,
            path_posteriors=None,
            path_volumes=None,
            voxel_volume=1.,
            skip_background_volume=True,
            padding=None,
            cropping=None,
            resample=None,
            sigma_smoothing=0,
            keep_biggest_component=False,
            conv_size=3,
            n_levels=5,
            nb_conv_per_level=2,
            unet_feat_count=24,
            feat_multiplier=2,
            no_batch_norm=False,
            gt_folder=None):
    """
    This function uses trained models to segment images.
    It is crucial that the inputs match the architecture parameters of the trained model.
    :param path_images: path of the images to segment. Can be the path to a directory or the path to a single image.
    :param path_model: path ot the trained model.
    :param segmentation_label_list: List of labels for which to compute Dice scores. It should contain the same values
    as the segmentation label list used for training the network.
    Can be a sequence, a 1d numpy array, or the path to a numpy 1d array.
    :param path_segmentations: (optional) path where segmentations will be writen.
    Should be a dir, if path_images is a dir, and afile if path_images is a file.
    Should not be None, if path_posteriors is None.
    :param path_posteriors: (optional) path where posteriors will be writen.
    Should be a dir, if path_images is a dir, and afile if path_images is a file.
    Should not be None, if path_segmentations is None.
    :param path_volumes: (optional) path of a csv file where the soft volumes of all segmented regions will be writen.
    The rows of the csv file correspond to subjects, and the columns correspond to segmentation labels.
    The soft volume of a structure corresponds to the sum of its predicted probability map.
    :param voxel_volume: (optional) volume of voxel. Default is 1 (i.e. returned volumes are voxel counts).
    :param skip_background_volume: (optional) whether to skip computing the volume of the background. This assumes the
    background correspond to the first value in label list.
    :param padding: (optional) crop the images to the specified shape before predicting the segmentation maps.
    If padding and cropping are specified, images are padded before being cropped.
    Can be an int, a sequence or a 1d numpy array.
    :param cropping: (optional) crop the images to the specified shape before predicting the segmentation maps.
    If padding and cropping are specified, images are padded before being cropped.
    Can be an int, a sequence or a 1d numpy array.
    :param resample: (optional) resample the images to the specified resolution before predicting the segmentation maps.
    Can be an int, a sequence or a 1d numpy array.
    :param sigma_smoothing: (optional) If not None, the posteriors are smoothed with a gaussian kernel of the specified
    standard deviation.
    :param keep_biggest_component: (optional) whether to only keep the biggest component in the predicted segmentation.
    :param conv_size: (optional) size of unet's convolution masks. Default is 3.
    :param n_levels: (optional) number of levels for unet. Default is 5.
    :param nb_conv_per_level: (optional) number of convolution layers per level. Default is 2.
    :param unet_feat_count: (optional) number of features for the first layer of the unet. Default is 24.
    :param feat_multiplier: (optional) multiplicative factor for the number of feature for each new level. Default is 2.
    :param no_batch_norm: (optional) whether to deactivate batch norm. Default is False.
    :param gt_folder: (optional) folder containing ground truth files for evaluation.
    A numpy array containing all dice scores (labels in rows, subjects in columns) will be writen either at
    segmentations_dir (if not None), or posteriors_dir.
    """

    assert path_model, "A model file is necessary"
    assert path_segmentations or path_posteriors, "output segmentation (or posteriors) is required"

    # prepare output filepaths
    images_to_segment, path_segmentations, path_posteriors, path_volumes = prepare_output_files(
        path_images, path_segmentations, path_posteriors, path_volumes)

    # get label and classes lists
    label_list, _ = utils.get_list_labels(label_list=segmentation_label_list,
                                          FS_sort=True)

    # prepare volume file if needed
    if path_volumes is not None:
        if skip_background_volume:
            csv_header = [['subject'] + [str(lab) for lab in label_list[1:]]]
        else:
            csv_header = [['subject'] + [str(lab) for lab in label_list]]
        with open(path_volumes, 'w') as csvFile:
            writer = csv.writer(csvFile)
            writer.writerows(csv_header)
        csvFile.close()

    # perform segmentation
    net = None
    previous_model_input_shape = None
    for idx, (im_path, seg_path, posteriors_path) in enumerate(
            zip(images_to_segment, path_segmentations, path_posteriors)):
        utils.print_loop_info(idx, len(images_to_segment), 10)

        # preprocess image and get information
        image, aff, h, n_channels, n_dims, shape, pad_shape, cropping, crop_idx = preprocess_image(
            im_path, n_levels, cropping, padding)
        model_input_shape = image.shape[1:]

        # prepare net for first image or if input's size has changed
        if (idx == 0) | (previous_model_input_shape != model_input_shape):

            # check for image size compatibility
            if (idx != 0) & (previous_model_input_shape != model_input_shape):
                print(
                    'image of different shape as previous ones, redefining network'
                )
            previous_model_input_shape = model_input_shape
            net = None

            if resample is not None:
                net, resample_shape = preprocessing_model(
                    resample, model_input_shape, h, n_channels, n_dims,
                    n_levels)
            else:
                resample_shape = previous_model_input_shape
            net = prepare_unet(resample_shape,
                               len(label_list),
                               conv_size,
                               n_levels,
                               nb_conv_per_level,
                               unet_feat_count,
                               feat_multiplier,
                               no_batch_norm,
                               path_model,
                               input_model=net)
            if (resample is not None) | (sigma_smoothing != 0):
                net = postprocessing_model(net, model_input_shape, resample,
                                           sigma_smoothing, n_dims)

        # predict posteriors
        prediction_patch = net.predict(image)

        # get posteriors and segmentation
        seg, posteriors = postprocess(prediction_patch, cropping, pad_shape,
                                      shape, crop_idx, n_dims, label_list,
                                      keep_biggest_component)

        # compute volumes
        if path_volumes is not None:
            if skip_background_volume:
                volumes = np.around(
                    np.sum(posteriors[..., 1:],
                           axis=tuple(range(0,
                                            len(posteriors.shape) - 1))), 3)
            else:
                volumes = np.around(
                    np.sum(posteriors,
                           axis=tuple(range(0,
                                            len(posteriors.shape) - 1))), 3)
            volumes = voxel_volume * volumes
            row = [os.path.basename(im_path)] + [str(vol) for vol in volumes]
            with open(path_volumes, 'a') as csvFile:
                writer = csv.writer(csvFile)
                writer.writerow(row)
            csvFile.close()

        # write results to disk
        if seg_path is not None:
            utils.save_volume(seg.astype('int'), aff, h, seg_path)
        if posteriors_path is not None:
            if n_channels > 1:
                new_shape = list(posteriors.shape)
                new_shape.insert(-1, 1)
                new_shape = tuple(new_shape)
                posteriors = np.reshape(posteriors, new_shape)
            utils.save_volume(posteriors.astype('float'), aff, h,
                              posteriors_path)

    # evaluate
    if gt_folder is not None:
        if path_segmentations[0] is not None:
            eval_folder = os.path.dirname(path_segmentations[0])
        else:
            eval_folder = os.path.dirname(path_posteriors[0])
        path_result_dice = os.path.join(eval_folder, 'dice.npy')
        evaluate.dice_evaluation(gt_folder, eval_folder,
                                 segmentation_label_list, path_result_dice)


If you use this code, please cite one of the SynthSeg papers:
https://github.com/BBillot/SynthSeg/blob/master/bibtex.bib

Copyright 2020 Benjamin Billot

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is
distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied. See the License for the specific language governing permissions and limitations under the
License.
"""

from ext.lab2im import utils
from SynthSeg.brain_generator import BrainGenerator

# generate an image from the label map.
brain_generator = BrainGenerator(
    '../../data/training_label_maps/training_seg_01.nii.gz')
im, lab = brain_generator.generate_brain()

# save output image and label map
utils.save_volume(im, brain_generator.aff, brain_generator.header,
                  './generated_examples/image_default.nii.gz')
utils.save_volume(lab, brain_generator.aff, brain_generator.header,
                  './generated_examples/labels_default.nii.gz')
示例#14
0
def preprocess_image(im_path,
                     n_levels,
                     target_res,
                     crop=None,
                     padding=None,
                     flip=False,
                     path_resample=None):

    # read image and corresponding info
    im, _, aff, n_dims, n_channels, header, im_res = utils.get_volume_info(
        im_path, True)

    # resample image if necessary
    if target_res is not None:
        target_res = np.squeeze(
            utils.reformat_to_n_channels_array(target_res, n_dims))
        if np.any((im_res > target_res + 0.05) | (im_res < target_res - 0.05)):
            im_res = target_res
            im, aff = edit_volumes.resample_volume(im, aff, im_res)
            if path_resample is not None:
                utils.save_volume(im, aff, header, path_resample)

    # align image
    im = edit_volumes.align_volume_to_ref(im,
                                          aff,
                                          aff_ref=np.eye(4),
                                          n_dims=n_dims)
    shape = list(im.shape)

    # pad image if specified
    if padding:
        im = edit_volumes.pad_volume(im, padding_shape=padding)
        pad_shape = im.shape[:n_dims]
    else:
        pad_shape = shape

    # check that patch_shape or im_shape are divisible by 2**n_levels
    if crop is not None:
        crop = utils.reformat_to_list(crop, length=n_dims, dtype='int')
        if not all([pad_shape[i] >= crop[i] for i in range(len(pad_shape))]):
            crop = [min(pad_shape[i], crop[i]) for i in range(n_dims)]
        if not all([size % (2**n_levels) == 0 for size in crop]):
            crop = [
                utils.find_closest_number_divisible_by_m(size, 2**n_levels)
                for size in crop
            ]
    else:
        if not all([size % (2**n_levels) == 0 for size in pad_shape]):
            crop = [
                utils.find_closest_number_divisible_by_m(size, 2**n_levels)
                for size in pad_shape
            ]

    # crop image if necessary
    if crop is not None:
        im, crop_idx = edit_volumes.crop_volume(im,
                                                cropping_shape=crop,
                                                return_crop_idx=True)
    else:
        crop_idx = None

    # normalise image
    if n_channels == 1:
        im = edit_volumes.rescale_volume(im,
                                         new_min=0.,
                                         new_max=1.,
                                         min_percentile=0.5,
                                         max_percentile=99.5)
    else:
        for i in range(im.shape[-1]):
            im[..., i] = edit_volumes.rescale_volume(im[..., i],
                                                     new_min=0.,
                                                     new_max=1.,
                                                     min_percentile=0.5,
                                                     max_percentile=99.5)

    # flip image along right/left axis
    if flip & (n_dims > 2):
        im_flipped = edit_volumes.flip_volume(im,
                                              direction='rl',
                                              aff=np.eye(4))
        im_flipped = utils.add_axis(
            im_flipped) if n_channels > 1 else utils.add_axis(im_flipped,
                                                              axis=[0, -1])
    else:
        im_flipped = None

    # add batch and channel axes
    im = utils.add_axis(im) if n_channels > 1 else utils.add_axis(im,
                                                                  axis=[0, -1])

    return im, aff, header, im_res, n_channels, n_dims, shape, pad_shape, crop_idx, im_flipped
示例#15
0
def predict(path_images,
            path_segmentations,
            path_model,
            segmentation_labels,
            n_neutral_labels=None,
            path_posteriors=None,
            path_resampled=None,
            path_volumes=None,
            segmentation_label_names=None,
            padding=None,
            cropping=None,
            target_res=1.,
            gradients=False,
            flip=True,
            topology_classes=None,
            sigma_smoothing=0.5,
            keep_biggest_component=True,
            conv_size=3,
            n_levels=5,
            nb_conv_per_level=2,
            unet_feat_count=24,
            feat_multiplier=2,
            activation='elu',
            gt_folder=None,
            evaluation_labels=None,
            mask_folder=None,
            list_incorrect_labels=None,
            list_correct_labels=None,
            compute_distances=False,
            recompute=True,
            verbose=True):
    """
    This function uses trained models to segment images.
    It is crucial that the inputs match the architecture parameters of the trained model.
    :param path_images: path of the images to segment. Can be the path to a directory or the path to a single image.
    :param path_segmentations: path where segmentations will be writen.
    Should be a dir, if path_images is a dir, and a file if path_images is a file.
    :param path_model: path ot the trained model.
    :param segmentation_labels: List of labels for which to compute Dice scores. It should be the same list as the
    segmentation_labels used in training.
    :param n_neutral_labels: (optional) if the label maps contain some right/left specific labels and if test-time
    flipping is applied (see parameter 'flip'), please provide the number of non-sided labels (including background).
    It should be the same value as for training. Default is None.
    :param path_posteriors: (optional) path where posteriors will be writen.
    Should be a dir, if path_images is a dir, and a file if path_images is a file.
    :param path_resampled: (optional) path where images resampled to 1mm isotropic will be writen.
    We emphasise that images are resampled as soon as the resolution in one of the axes is not in the range [0.9; 1.1].
    Should be a dir, if path_images is a dir, and a file if path_images is a file. Default is None, where resampled
    images are not saved.
    :param path_volumes: (optional) path of a csv file where the soft volumes of all segmented regions will be writen.
    The rows of the csv file correspond to subjects, and the columns correspond to segmentation labels.
    The soft volume of a structure corresponds to the sum of its predicted probability map.
    :param segmentation_label_names: (optional) List of names correponding to the names of the segmentation labels.
    Only used when path_volumes is provided. Must be of the same size as segmentation_labels. Can be given as a
    list, a numpy array of strings, or the path to such a numpy array. Default is None.
    :param padding: (optional) pad the images to the specified shape before predicting the segmentation maps.
    Can be an int, a sequence or a 1d numpy array.
    :param cropping: (optional) crop the images to the specified shape before predicting the segmentation maps.
    If padding and cropping are specified, images are padded before being cropped.
    Can be an int, a sequence or a 1d numpy array.
    :param target_res: (optional) target resolution at which the network operates (and thus resolution of the output
    segmentations). This must match the resolution of the training data ! target_res is used to automatically resampled
    the images with resolutions outside [target_res-0.05, target_res+0.05].
    Can be a sequence, a 1d numpy array. Set to None to disable the automatic resampling. Default is 1mm.
    :param flip: (optional) whether to perform test-time augmentation, where the input image is segmented along with
    a right/left flipped version on it. If set to True (default), be careful because this requires more memory.
    :param topology_classes: List of classes corresponding to all segmentation labels, in order to group them into
    classes, for each of which we will operate a smooth version of biggest connected component.
    Can be a sequence, a 1d numpy array, or the path to a numpy 1d array in the same order as segmentation_labels.
    Default is None, where no topological analysis is performed.
    :param sigma_smoothing: (optional) If not None, the posteriors are smoothed with a gaussian kernel of the specified
    standard deviation.
    :param keep_biggest_component: (optional) whether to only keep the biggest component in the predicted segmentation.
    This is applied independently of topology_classes, and it is applied to the whole segmentation
    :param conv_size: (optional) size of unet's convolution masks. Default is 3.
    :param n_levels: (optional) number of levels for unet. Default is 5.
    :param nb_conv_per_level: (optional) number of convolution layers per level. Default is 2.
    :param unet_feat_count: (optional) number of features for the first layer of the unet. Default is 24.
    :param feat_multiplier: (optional) multiplicative factor for the number of feature for each new level. Default is 2.
    :param activation: (optional) activation function. Can be 'elu', 'relu'.
    :param gt_folder: (optional) path of the ground truth label maps corresponding to the input images. Should be a dir,
    if path_images is a dir, or a file if path_images is a file.
    Providing a gt_folder will trigger a Dice evaluation, where scores will be writen along with the path_segmentations.
    Specifically, the scores are contained in a numpy array, where labels are in rows, and subjects in columns.
    :param evaluation_labels: (optional) if gt_folder is True you can evaluate the Dice scores on a subset of the
    segmentation labels, by providing another label list here. Can be a sequence, a 1d numpy array, or the path to a
    numpy 1d array. Default is np.unique(segmentation_labels).
    :param mask_folder: (optional) path of masks that will be used to mask out some parts of the obtained segmentations
    during the evaluation. Default is None, where nothing is masked.
    :param list_incorrect_labels: (optional) this option enables to replace some label values in the obtained
    segmentations by other label values. Can be a list, a 1d numpy array, or the path to such an array.
    :param list_correct_labels: (optional) list of values to correct the labels specified in list_incorrect_labels.
    Correct values must have the same order as their corresponding value in list_incorrect_labels.
    :param compute_distances: (optional) whether to add Hausdorff and mean surface distance evaluations to the default
    Dice evaluation. Default is True.
    :param recompute: (optional) whether to recompute segmentations that were already computed. This also applies to
    Dice scores, if gt_folder is not None. Default is True.
    :param verbose: (optional) whether to print out info about the remaining number of cases.
    """

    # prepare input/output filepaths
    path_images, path_segmentations, path_posteriors, path_resampled, path_volumes, compute = \
        prepare_output_files(path_images, path_segmentations, path_posteriors, path_resampled, path_volumes, recompute)

    # get label list
    segmentation_labels, _ = utils.get_list_labels(
        label_list=segmentation_labels)
    n_labels = len(segmentation_labels)

    # get unique label values, and build correspondance table between contralateral structures if necessary
    if (n_neutral_labels is not None) & flip:
        n_sided_labels = int((n_labels - n_neutral_labels) / 2)
        lr_corresp = np.stack([
            segmentation_labels[n_neutral_labels:n_neutral_labels +
                                n_sided_labels],
            segmentation_labels[n_neutral_labels + n_sided_labels:]
        ])
        segmentation_labels, indices = np.unique(segmentation_labels,
                                                 return_index=True)
        lr_corresp_unique, lr_corresp_indices = np.unique(lr_corresp[0, :],
                                                          return_index=True)
        lr_corresp_unique = np.stack(
            [lr_corresp_unique, lr_corresp[1, lr_corresp_indices]])
        lr_corresp_unique = lr_corresp_unique[:, 1:] if not np.all(
            lr_corresp_unique[:, 0]) else lr_corresp_unique
        lr_indices = np.zeros_like(lr_corresp_unique)
        for i in range(lr_corresp_unique.shape[0]):
            for j, lab in enumerate(lr_corresp_unique[i]):
                lr_indices[i, j] = np.where(segmentation_labels == lab)[0]
    else:
        segmentation_labels, indices = np.unique(segmentation_labels,
                                                 return_index=True)
        lr_indices = None

    # prepare topology classes
    if topology_classes is not None:
        topology_classes = utils.load_array_if_path(
            topology_classes, load_as_numpy=True)[indices]

    # prepare volume file if needed
    if path_volumes is not None:
        if segmentation_label_names is not None:
            segmentation_label_names = utils.load_array_if_path(
                segmentation_label_names)[indices]
            csv_header = [[''] + segmentation_label_names[1:].tolist()]
            csv_header += [[''] +
                           [str(lab) for lab in segmentation_labels[1:]]]
        else:
            csv_header = [['subjects'] +
                          [str(lab) for lab in segmentation_labels[1:]]]
        with open(path_volumes, 'w') as csvFile:
            writer = csv.writer(csvFile)
            writer.writerows(csv_header)
        csvFile.close()

    # build network
    _, _, n_dims, n_channels, _, _ = utils.get_volume_info(path_images[0])
    model_input_shape = [None] * n_dims + [n_channels]
    net = build_model(path_model, model_input_shape, n_levels,
                      len(segmentation_labels), conv_size, nb_conv_per_level,
                      unet_feat_count, feat_multiplier, activation,
                      sigma_smoothing, gradients)

    # perform segmentation
    loop_info = utils.LoopInfo(len(path_images), 10, 'predicting', True)
    for idx, (path_image, path_segmentation, path_posterior, path_resample, tmp_compute) in \
            enumerate(zip(path_images, path_segmentations, path_posteriors, path_resampled, compute)):

        # compute segmentation only if needed
        if tmp_compute:
            if verbose:
                loop_info.update(idx)

            # preprocessing
            image, aff, h, im_res, _, _, shape, pad_shape, crop_idx, im_flipped = \
                preprocess_image(path_image, n_levels, target_res, cropping, padding, flip, path_resample)

            # prediction
            prediction_patch = net.predict(image)
            prediction_patch_flip = net.predict(im_flipped) if flip else None

            # postprocessing
            seg, posteriors = postprocess(
                prediction_patch,
                pad_shape,
                shape,
                crop_idx,
                n_dims,
                segmentation_labels,
                lr_indices,
                keep_biggest_component,
                aff,
                topology_classes=topology_classes,
                post_patch_flip=prediction_patch_flip)

            # write results to disk
            if path_segmentation is not None:
                utils.save_volume(seg,
                                  aff,
                                  h,
                                  path_segmentation,
                                  dtype='int32')
            if path_posterior is not None:
                if n_channels > 1:
                    posteriors = utils.add_axis(posteriors, axis=[0, -1])
                utils.save_volume(posteriors,
                                  aff,
                                  h,
                                  path_posterior,
                                  dtype='float32')

        else:
            if path_volumes is not None:
                posteriors, _, _, _, _, _, im_res = utils.get_volume_info(
                    path_posterior, True, aff_ref=np.eye(4))
            else:
                posteriors = im_res = None

        # compute volumes
        if path_volumes is not None:
            volumes = np.sum(posteriors[..., 1:],
                             axis=tuple(range(0,
                                              len(posteriors.shape) - 1)))
            volumes = np.around(volumes * np.prod(im_res), 3)
            row = [os.path.basename(path_image).replace('.nii.gz', '')
                   ] + [str(vol) for vol in volumes]
            with open(path_volumes, 'a') as csvFile:
                writer = csv.writer(csvFile)
                writer.writerow(row)
            csvFile.close()

    # evaluate
    if gt_folder is not None:

        # find path where segmentations are saved evaluation folder, and get labels on which to evaluate
        eval_folder = os.path.dirname(path_segmentations[0])
        if evaluation_labels is None:
            evaluation_labels = segmentation_labels

        # set path of result arrays for surface distance if necessary
        if compute_distances:
            path_hausdorff = os.path.join(eval_folder, 'hausdorff.npy')
            path_hausdorff_99 = os.path.join(eval_folder, 'hausdorff_99.npy')
            path_hausdorff_95 = os.path.join(eval_folder, 'hausdorff_95.npy')
            path_mean_distance = os.path.join(eval_folder, 'mean_distance.npy')
        else:
            path_hausdorff = path_hausdorff_99 = path_hausdorff_95 = path_mean_distance = None

        # compute evaluation metrics
        evaluate.evaluation(gt_folder,
                            eval_folder,
                            evaluation_labels,
                            mask_dir=mask_folder,
                            path_dice=os.path.join(eval_folder, 'dice.npy'),
                            path_hausdorff=path_hausdorff,
                            path_hausdorff_99=path_hausdorff_99,
                            path_hausdorff_95=path_hausdorff_95,
                            path_mean_distance=path_mean_distance,
                            list_incorrect_labels=list_incorrect_labels,
                            list_correct_labels=list_correct_labels,
                            recompute=recompute,
                            verbose=verbose)
示例#16
0
########################################################################################################

# instantiate BrainGenerator object
brain_generator = BrainGenerator(labels_dir=path_label_map,
                                 generation_labels=generation_labels,
                                 output_labels=output_labels,
                                 generation_classes=generation_classes,
                                 prior_distributions=prior_distribution,
                                 prior_means=prior_means,
                                 prior_stds=prior_stds,
                                 output_shape=output_shape)

# create result dir
utils.mkdir(result_dir)

for n in range(n_examples):

    # generate new image and corresponding labels
    start = time.time()
    im, lab = brain_generator.generate_brain()
    end = time.time()
    print('generation {0:d} took {1:.01f}s'.format(n, end - start))

    # save output image and label map
    utils.save_volume(np.squeeze(im), brain_generator.aff,
                      brain_generator.header,
                      os.path.join(result_dir, 't1_%s.nii.gz' % n))
    utils.save_volume(np.squeeze(lab), brain_generator.aff,
                      brain_generator.header,
                      os.path.join(result_dir, 't1_labels_%s.nii.gz' % n))
示例#17
0
def postprocess_samseg(list_samseg_dir,
                       list_gt_dir,
                       path_segmentation_labels,
                       incorrect_labels,
                       correct_labels,
                       list_posteriors_dir=None,
                       list_thresholds=None,
                       recompute=False):
    """ This function processes the samseg segmentations: it corrects the labels (right/left and 99 to 77), resamples
    them to the space of gt_dir, and computes the Dice scores for 1) all_subjects vs. testing subjects only, and 2) all
    ROIs vs. lesions only.
    It requires that all segmentations are sorted in three subfolders inside samseg_main_dir: t1, flair, and t1_flair.
    IMPORTANT: Images are expected to have to following naming convention: <subject_id>.samseg.<contrast>.lesion.mgz,
    where <contrast> must either be t1, flair, ***t1_flair***
    :param list_samseg_dir: main samseg dir containing the three subfolders t1, flair, t1_flair
    :param list_gt_dir: folder with the gt label maps for all subjects
    :param path_segmentation_labels: list of segmentation labels
    :param incorrect_labels: list of samseg incorrect labels
    :param correct_labels: list of labels to correct the wrong one with
    :param recompute: whether to recompute files
    """

    if list_posteriors_dir is None:
        list_posteriors_dir = [None] * len(list_samseg_dir)

    for samseg_dir, gt_dir, posteriors_dir, threshold in zip(
            list_samseg_dir, list_gt_dir, list_posteriors_dir,
            list_thresholds):

        # define result directories
        samseg_corrected_dir = samseg_dir + '_corrected'
        samseg_preprocessed_dir = samseg_dir + '_preprocessed'
        if (not os.path.isdir(samseg_preprocessed_dir)) | recompute:

            # regroup right/left labels and change 99 to 77
            edit_volumes.correct_labels_in_dir(samseg_dir,
                                               incorrect_labels,
                                               correct_labels,
                                               samseg_corrected_dir,
                                               recompute=recompute)

            # resample to gt format
            edit_volumes.mri_convert_images_in_dir(samseg_corrected_dir,
                                                   samseg_preprocessed_dir,
                                                   interpolation='nearest',
                                                   reference_dir=gt_dir,
                                                   recompute=recompute)

        # replace lesions by thresholded lesion posteriors
        if posteriors_dir is not None:

            # resample posteriors to gt format
            posteriors_preprocessed_dir = posteriors_dir + '_preprocessed'
            edit_volumes.mri_convert_images_in_dir(posteriors_dir,
                                                   posteriors_preprocessed_dir,
                                                   reference_dir=gt_dir,
                                                   recompute=recompute)

            # list hard segmentations and posteriors
            samseg_postprocessed_dir = samseg_dir + '_postprocessed'
            utils.mkdir(samseg_postprocessed_dir)
            path_segs = [
                path for path in utils.list_images_in_folder(
                    samseg_preprocessed_dir)
            ]
            path_posteriors = [
                path for path in utils.list_images_in_folder(
                    posteriors_preprocessed_dir)
            ]

            for subject_idx, (path_seg, path_post) in enumerate(
                    zip(path_segs, path_posteriors)):
                path_result = os.path.join(samseg_postprocessed_dir,
                                           os.path.basename(path_seg))
                if (not os.path.isfile(path_result)) | recompute:

                    # replace segmented lesions by thresholded posteriors
                    seg, aff, h = utils.load_volume(path_seg, im_only=False)
                    posteriors = utils.load_volume(path_post)
                    seg[seg == 77] = 2
                    seg[posteriors > threshold] = 77
                    utils.save_volume(seg, aff, h, path_result)

        else:
            samseg_postprocessed_dir = samseg_preprocessed_dir

        # compute dice scores with
        path_dice_testing = os.path.join(samseg_postprocessed_dir, 'dice.npy')
        path_dice_lesions_testing = os.path.join(samseg_postprocessed_dir,
                                                 'dice_lesions.npy')
        if (not os.path.isfile(path_dice_testing)) | recompute:
            dice_evaluation(gt_dir, samseg_postprocessed_dir,
                            path_segmentation_labels, path_dice_testing)
        if (not os.path.isfile(path_dice_lesions_testing)) | recompute:
            dice = np.load(path_dice_testing)
            np.save(path_dice_lesions_testing, dice[4, :])
示例#18
0
downsample = True

# ------------------------------------------------------ Generate ------------------------------------------------------

# instantiate BrainGenerator object
brain_generator = BrainGenerator(labels_dir=path_label_map,
                                 generation_labels=generation_labels,
                                 output_labels=output_labels,
                                 n_neutral_labels=n_neutral_labels,
                                 output_shape=output_shape,
                                 prior_distributions=prior_distributions,
                                 generation_classes=generation_classes,
                                 prior_means=prior_means,
                                 prior_stds=prior_stds,
                                 randomise_res=randomise_res,
                                 data_res=data_res,
                                 thickness=thickness,
                                 downsample=downsample,
                                 blur_range=blur_range)

for n in range(n_examples):

    # generate new image and corresponding labels
    im, lab = brain_generator.generate_brain()

    # save output image and label map
    utils.save_volume(im, brain_generator.aff, brain_generator.header,
                      os.path.join(result_dir, 'image_t1_%s.nii.gz' % n))
    utils.save_volume(lab, brain_generator.aff, brain_generator.header,
                      os.path.join(result_dir, 'labels_t1_%s.nii.gz' % n))