Esempio n. 1
0
def csf_mask_intersection(img_dir, masks=None, prob=1):
    """
    use all nifti T1w images in data_dir to create csf mask in common areas

    Args:
        img_dir (str): directory containing MR images to be normalized
        masks (str or ants.core.ants_image.ANTsImage): if images are not skull-stripped,
            then provide brain mask as either a corresponding directory or an individual mask
        prob (float): given all data, proportion of data labeled as csf to be
            used for intersection

    Returns:
        intersection (np.ndarray): binary mask of common csf areas for all provided imgs
    """
    if not (0 <= prob <= 1):
        raise NormalizationError(
            'prob must be between 0 and 1. {} given.'.format(prob))
    data = io.glob_nii(img_dir)
    masks = io.glob_nii(masks) if isinstance(masks,
                                             str) else [masks] * len(data)
    csf = []
    for i, (img, mask) in enumerate(zip(data, masks)):
        _, base, _ = io.split_filename(img)
        logger.info('Creating CSF mask for image {} ({:d}/{:d})'.format(
            base, i + 1, len(data)))
        imgn = ants.image_read(img)
        maskn = ants.image_read(mask) if isinstance(mask, str) else mask
        csf.append(csf_mask(imgn, maskn))
    csf_sum = reduce(
        add, csf)  # need to use reduce instead of sum b/c data structure
    intersection = np.zeros(csf_sum.shape)
    intersection[csf_sum >= np.floor(len(data) * prob)] = 1
    return intersection
Esempio n. 2
0
def nyul_normalize(img_dir, mask_dir=None, output_dir=None, standard_hist=None, write_to_disk=True):
    """
    Use Nyul and Udupa method ([1,2]) to normalize the intensities of a set of MR images

    Args:
        img_dir (str): directory containing MR images
        img_dir (str): directory containing masks for MR images
        output_dir (str): directory to save images if you do not want them saved in
            same directory as data_dir
        standard_hist (str): path to output or use standard histogram landmarks
        write_to_disk (bool): write the normalized data to disk or nah

    Returns:
        normalized (np.ndarray): last normalized image from img_dir

    References:
        [1] N. Laszlo G and J. K. Udupa, “On Standardizing the MR Image
            Intensity Scale,” Magn. Reson. Med., vol. 42, pp. 1072–1081,
            1999.
        [2] M. Shah, Y. Xiao, N. Subbanna, S. Francis, D. L. Arnold,
            D. L. Collins, and T. Arbel, “Evaluating intensity
            normalization on MRIs of human brain with multiple sclerosis,”
            Med. Image Anal., vol. 15, no. 2, pp. 267–282, 2011.
    """
    input_files = io.glob_nii(img_dir)
    if output_dir is None:
        out_fns = [None] * len(input_files)
    else:
        out_fns = []
        for fn in input_files:
            _, base, ext = io.split_filename(fn)
            out_fns.append(os.path.join(output_dir, base + '_hm' + ext))
        if not os.path.exists(output_dir):
            os.mkdir(output_dir)

    mask_files = [None] * len(input_files) if mask_dir is None else io.glob_nii(mask_dir)

    if standard_hist is None:
        logger.info('Learning standard scale for the set of images')
        standard_scale, percs = train(input_files, mask_files)
    elif not os.path.isfile(standard_hist):
        logger.info('Learning standard scale for the set of images')
        standard_scale, percs = train(input_files, mask_files)
        np.save(standard_hist, np.vstack((standard_scale, percs)))
    else:
        logger.info('Loading standard scale ({}) for the set of images'.format(standard_hist))
        standard_scale, percs = np.load(standard_hist)

    for i, (img_fn, mask_fn, out_fn) in enumerate(zip(input_files, mask_files, out_fns)):
        _, base, _ = io.split_filename(img_fn)
        logger.info('Transforming image {} to standard scale ({:d}/{:d})'.format(base, i+1, len(input_files)))
        img = io.open_nii(img_fn)
        mask = io.open_nii(mask_fn) if mask_fn is not None else None
        normalized = do_hist_norm(img, percs, standard_scale, mask)
        if write_to_disk:
            io.save_nii(normalized, out_fn, is_nii=True)

    return normalized
def lsq_normalize(img_dir, mask_dir=None, output_dir=None, write_to_disk=True):
    """
    normalize intensities of a set of MR images by minimizing the squared distance
    between CSF, GM, and WM means within the set

    Args:
        img_dir (str): directory containing MR images
        mask_dir (str): directory containing masks for MR images
        output_dir (str): directory to save images if you do not want them saved in
            same directory as data_dir
        write_to_disk (bool): write the normalized data to disk or nah

    Returns:
        normalized (np.ndarray): last normalized image from img_dir
    """
    input_files = io.glob_nii(img_dir)
    if output_dir is None:
        out_fns = [None] * len(input_files)
    else:
        out_fns = []
        for fn in input_files:
            _, base, ext = io.split_filename(fn)
            out_fns.append(os.path.join(output_dir, base + '_lsq' + ext))
        if not os.path.exists(output_dir):
            os.mkdir(output_dir)

    mask_files = [None] * len(
        input_files) if mask_dir is None else io.glob_nii(mask_dir)

    standard_tissue_means = None
    normalized = None
    for i, (img_fn, mask_fn,
            out_fn) in enumerate(zip(input_files, mask_files, out_fns)):
        _, base, _ = io.split_filename(img_fn)
        logger.info(
            'Transforming image {} to standard scale ({:d}/{:d})'.format(
                base, i + 1, len(input_files)))
        img = io.open_nii(img_fn)
        mask = io.open_nii(mask_fn) if mask_fn is not None else None
        tissue_mem = mask_util.fcm_class_mask(img, mask)
        if standard_tissue_means is None:
            csf_tissue_mask = find_tissue_mask(img, mask, tissue_type='csf')
            csf_normed_data = fcm_normalize(img, csf_tissue_mask).get_fdata()
            standard_tissue_means = calc_tissue_means(csf_normed_data,
                                                      tissue_mem)
            del csf_tissue_mask, csf_normed_data
        img_data = img.get_fdata()
        tissue_means = calc_tissue_means(img_data, tissue_mem)
        sf = find_scaling_factor(tissue_means, standard_tissue_means)
        logger.debug('Scaling factor for {}: {:0.3e}'.format(base, sf))
        normalized = nib.Nifti1Image(sf * img_data, img.affine, img.header)
        if write_to_disk:
            io.save_nii(normalized, out_fn, is_nii=True)

    return normalized
def main(args=None):
    args = arg_parser().parse_args(args)
    if args.verbosity == 1:
        level = logging.getLevelName('INFO')
    elif args.verbosity >= 2:
        level = logging.getLevelName('DEBUG')
    else:
        level = logging.getLevelName('WARNING')
    logging.basicConfig(
        format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
        level=level)
    logger = logging.getLogger(__name__)
    try:
        if not os.path.isdir(args.img_dir):
            raise ValueError(
                '(-i / --img-dir) argument needs to be a directory of NIfTI images.'
            )
        if args.mask_dir is not None:
            if not os.path.isdir(args.mask_dir):
                raise ValueError(
                    '(-m / --mask-dir) argument needs to be a directory of NIfTI images.'
                )

        img_fns = io.glob_nii(args.img_dir)
        if args.mask_dir is not None:
            mask_fns = io.glob_nii(args.mask_dir)
        else:
            mask_fns = [None] * len(img_fns)
        if not os.path.exists(args.output_dir):
            logger.info('Making Output Directory: {}'.format(args.output_dir))
            os.mkdir(args.output_dir)
        hard_seg = not args.memberships
        for i, (img_fn, mask_fn) in enumerate(zip(img_fns, mask_fns), 1):
            _, base, _ = io.split_filename(img_fn)
            logger.info('Creating Mask for Image: {}, ({:d}/{:d})'.format(
                base, i, len(img_fns)))
            img = io.open_nii(img_fn)
            mask = io.open_nii(mask_fn)
            tm = fcm_class_mask(img, mask,
                                hard_seg) if not args.gmm else gmm_class_mask(
                                    img, mask, 't1', False, hard_seg)
            tissue_mask = os.path.join(args.output_dir, base + '_tm')
            if args.memberships:
                classes = ('csf', 'gm', 'wm')
                for j, c in enumerate(classes):
                    io.save_nii(img, tissue_mask + '_' + c + '.nii.gz', tm[...,
                                                                           j])
            else:
                io.save_nii(img, tissue_mask + '.nii.gz', tm)
        return 0
    except Exception as e:
        logger.exception(e)
        return 1
def main(args=None):
    args = arg_parser().parse_args(args)
    if args.verbosity == 1:
        level = logging.getLevelName('INFO')
    elif args.verbosity >= 2:
        level = logging.getLevelName('DEBUG')
    else:
        level = logging.getLevelName('WARNING')
    logging.basicConfig(
        format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
        level=level)
    logger = logging.getLogger(__name__)
    try:
        if not args.single_img:
            if not os.path.isdir(args.image) or not os.path.isdir(
                    args.brain_mask):
                raise NormalizationError(
                    'if single-img option off, then image and brain-mask must be directories'
                )
            img_fns = io.glob_nii(args.image)
            mask_fns = io.glob_nii(args.brain_mask)
            if len(img_fns) != len(mask_fns) and len(img_fns) > 0:
                raise NormalizationError(
                    'input images and masks must be in correspondence and greater than zero '
                    '({:d} != {:d})'.format(len(img_fns), len(mask_fns)))
            for i, (img, mask) in enumerate(zip(img_fns, mask_fns), 1):
                _, base, _ = io.split_filename(img)
                logger.info('Normalizing image {} ({:d}/{:d})'.format(
                    img, i, len(img_fns)))
                process(img, mask, args, logger)
        else:
            if not os.path.isfile(args.image) or not os.path.isfile(
                    args.brain_mask):
                raise NormalizationError(
                    'if single-img option on, then image and brain-mask must be files'
                )
            process(args.image, args.brain_mask, args, logger)

        if args.plot_hist:
            with warnings.catch_warnings():
                warnings.filterwarnings('ignore', category=FutureWarning)
                from intensity_normalization.plot.hist import all_hists
                import matplotlib.pyplot as plt
            ax = all_hists(args.output_dir, args.brain_mask)
            ax.set_title('GMM')
            plt.savefig(os.path.join(args.output_dir, 'hist.png'))

        return 0
    except Exception as e:
        logger.exception(e)
        return 1
Esempio n. 6
0
def main(args=None):
    args = arg_parser().parse_args(args)
    if args.verbosity == 1:
        level = logging.getLevelName('INFO')
    elif args.verbosity >= 2:
        level = logging.getLevelName('DEBUG')
    else:
        level = logging.getLevelName('WARNING')
    logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=level)
    logger = logging.getLogger(__name__)
    try:
        img_fns = glob_nii(args.img_dir)
        if not os.path.exists(args.output_dir):
            logger.info('Making Output Directory: {}'.format(args.output_dir))
            os.mkdir(args.output_dir)
        if args.template_dir is None:
            logger.info('Registering image to MNI template')
            template = ants.image_read(ants.get_ants_data('mni')).reorient_image2(args.orientation)
            orientation = args.orientation
        else:
            template_fns = glob_nii(args.template_dir)
            if len(template_fns) != len(img_fns):
                raise NormalizationError('If template images are provided, they must be in '
                                         'correspondence (i.e., equal number) with the source images')
        for i, img in enumerate(img_fns):
            _, base, _ = split_filename(img)
            logger.info('Registering image to template: {} ({:d}/{:d})'.format(base, i+1, len(img_fns)))
            if args.template_dir is not None:
                template = ants.image_read(template_fns[i])
                orientation = template.orientation if hasattr(template, 'orientation') else None
            input_img = ants.image_read(img)
            input_img = input_img.reorient_image2(orientation) if orientation is not None else input_img
            if not args.no_rigid:
                logger.info('Starting rigid registration: {} ({:d}/{:d})'.format(base, i+1, len(img_fns)))
                mytx = ants.registration(fixed=template, moving=input_img, type_of_transform="Rigid")
                tx = mytx['fwdtransforms'][0]
            else:
                tx = None
            logger.info('Starting {} registration: {} ({:d}/{:d})'.format(args.registration, base, i+1, len(img_fns)))
            mytx = ants.registration(fixed=template, moving=input_img, initial_transform=tx, type_of_transform=args.registration)
            logger.debug(mytx)
            moved = ants.apply_transforms(template, input_img, mytx['fwdtransforms'], interpolator='bSpline')
            registered = os.path.join(args.output_dir, base + '_reg.nii.gz')
            ants.image_write(moved, registered)
        return 0
    except Exception as e:
        logger.exception(e)
        return 1
Esempio n. 7
0
def pairwise_jsd(img_dir, mask_dir, nbins=200):
    """
    Calculate the Jensen-Shannon Divergence for all pairs of images in the image directory

    Args:
        img_dir (str): path to directory of images
        mask_dir (str): path to directory of masks
        nbins (int): number of bins to use in the histograms

    Returns:
        pairwise_jsd (np.ndarray): array of pairwise Jensen-Shannon divergence
    """
    eps = np.finfo(np.float32).eps

    img_fns = io.glob_nii(img_dir)
    mask_fns = io.glob_nii(mask_dir)

    if len(img_fns) != len(mask_fns):
        raise NormalizationError(
            f'Number of images ({len(img_fns)}) must be equal to the number of masks ({len(mask_fns)}).'
        )

    min_intensities, max_intensities = [], []
    for img_fn, mask_fn in zip(img_fns, mask_fns):
        data = nib.load(img_fn).get_fdata()[nib.load(mask_fn).get_fdata() == 1]
        min_intensities.append(np.min(data))
        max_intensities.append(np.max(data))
    intensity_range = (min(min_intensities), max(max_intensities))

    hists = []
    for img_fn, mask_fn in zip(img_fns, mask_fns):
        data = nib.load(img_fn).get_fdata()[nib.load(mask_fn).get_fdata() == 1]
        hist, _ = np.histogram(data.flatten(),
                               nbins,
                               range=intensity_range,
                               density=True)
        hists.append(hist + eps)

    pairwise_jsd = []
    for i in range(len(hists)):
        for j in range(i + 1, len(hists)):
            pairwise_jsd.append(jsd(hists[i], hists[j]))

    return np.array(pairwise_jsd)
Esempio n. 8
0
def all_hists(img_dir, mask_dir=None, alpha=0.8, figsize=(12, 10), **kwargs):
    """
    plot all histograms over one another to get an idea of the
    spread for a sample/population

    note that all histograms are for the intensities within a given brain mask
    or estimated foreground mask (the estimate is just all intensities above the mean)

    Args:
        img_dir (str): path to images
        mask_dir (str): path to corresponding masks of imgs
        alpha (float): controls alpha parameter of individual line plots (default: 0.8)
        figsize (tuple): size of figure (default: (12,10))
        **kwargs: for numpy histogram routine

    Returns:
        ax (matplotlib.axes.Axes): plotted on ax obj
    """
    imgs = glob_nii(img_dir)
    if mask_dir is not None:
        masks = glob_nii(mask_dir)
    else:
        masks = [None] * len(imgs)
    if len(imgs) != len(masks):
        raise NormalizationError(
            'Number of images and masks must be equal ({:d} != {:d})'.format(
                len(imgs), len(masks)))
    _, ax = plt.subplots(figsize=figsize)
    for i, (img_fn, mask_fn) in enumerate(zip(imgs, masks), 1):
        logger.info('Creating histogram for image {:d}/{:d}'.format(
            i, len(imgs)))
        img = nib.load(img_fn)
        if mask_fn is not None:
            mask = nib.load(mask_fn)
        else:
            mask = None
        _ = hist(img, mask, ax=ax, alpha=alpha, **kwargs)
    ax.set_xlabel('Intensity')
    ax.set_ylabel(r'Log$_{10}$ Count')
    ax.set_ylim((0, None))
    return ax
Esempio n. 9
0
def main(args=None):
    args = arg_parser().parse_args(args)
    if args.verbosity == 1:
        level = logging.getLevelName('INFO')
    elif args.verbosity >= 2:
        level = logging.getLevelName('DEBUG')
    else:
        level = logging.getLevelName('WARNING')
    logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=level)
    logger = logging.getLogger(__name__)
    try:
        img_fns = glob_nii(args.img_dir)
        if not os.path.exists(args.mask_dir):
            logger.info('Making Output Mask Directory: {}'.format(args.mask_dir))
            os.mkdir(args.mask_dir)
        for i, img in enumerate(img_fns, 1):
            _, base, _ = split_filename(img)
            logger.info('Creating Mask for Image: {}, ({:d}/{:d})'.format(base, i, len(img_fns)))
            mask = os.path.join(args.mask_dir, base + '_mask.nii.gz')
            _ = robex(os.path.abspath(img), os.path.abspath(mask), args.return_skull_stripped)
        return 0
    except Exception as e:
        logger.exception(e)
        return 1
Esempio n. 10
0
def preprocess(img_dir,
               out_dir,
               mask_dir=None,
               res=(1., 1., 1.),
               orientation='RAI',
               n4_opts=None):
    """
    preprocess.py MR images according to a simple scheme,
    that is:
        1) N4 bias field correction
        2) resample to x mm x y mm x z mm
        3) reorient images to RAI

    Args:
        img_dir (str): path to directory containing images
        out_dir (str): path to directory for output preprocessed files
        mask_dir (str): path to directory containing masks
        res (tuple): resolution for resampling (default: (1,1,1) in mm)
        n4_opts (dict): n4 processing options. See ANTsPy for details. (default: None)

    Returns:
        None, outputs preprocessed images to file in given out_dir
    """

    if n4_opts is None:
        n4_opts = {'iters': [200, 200, 200, 200], 'tol': 0.0005}
    logger.debug('N4 Options are: {}'.format(n4_opts))

    # get and check the images and masks
    img_fns = glob_nii(img_dir)
    mask_fns = glob_nii(
        mask_dir) if mask_dir is not None else [None] * len(img_fns)
    assert len(img_fns) == len(mask_fns), 'Number of images and masks must be equal ({:d} != {:d})' \
        .format(len(img_fns), len(mask_fns))

    # create the output directory structure
    out_img_dir = os.path.join(out_dir, 'imgs')
    out_mask_dir = os.path.join(out_dir, 'masks')
    if not os.path.exists(out_dir):
        logger.info('Making output directory structure: {}'.format(out_dir))
        os.mkdir(out_dir)
    if not os.path.exists(out_img_dir):
        logger.info('Making image output directory: {}'.format(out_img_dir))
        os.mkdir(out_img_dir)
    if not os.path.exists(out_mask_dir) and mask_dir is not None:
        logger.info('Making mask output directory: {}'.format(out_mask_dir))
        os.mkdir(out_mask_dir)

    # preprocess the images by n4 correction, resampling, and reorientation
    for i, (img_fn, mask_fn) in enumerate(zip(img_fns, mask_fns), 1):
        _, img_base, img_ext = split_filename(img_fn)
        logger.info('Preprocessing image: {} ({:d}/{:d})'.format(
            img_base, i, len(img_fns)))
        img = ants.image_read(img_fn)
        if mask_dir is not None:
            _, mask_base, mask_ext = split_filename(mask_fn)
            mask = ants.image_read(mask_fn)
            smoothed_mask = ants.smooth_image(mask, 1)
            # this should be a second n4 after an initial n4 (and coregistration), once masks are obtained
            img = ants.n4_bias_field_correction(img,
                                                convergence=n4_opts,
                                                weight_mask=smoothed_mask)
            if res is not None:
                if res != img.spacing:
                    mask = ants.resample_image(mask, res, False, 1)
            mask = mask.reorient_image2(orientation) if hasattr(img, 'reorient_image2') else \
                mask.reorient_image((1, 0, 0))['reoimage']
            out_mask = os.path.join(out_mask_dir, mask_base + mask_ext)
            ants.image_write(mask, out_mask)
        else:
            img = ants.n4_bias_field_correction(img, convergence=n4_opts)
        if res is not None:
            if res != img.spacing:
                img = ants.resample_image(img, res, False, 4)
        if hasattr(img, 'reorient_image2'):
            img = img.reorient_image2(orientation)
        else:
            logger.info(
                'Cannot reorient image to a custom orientation. Update ANTsPy to a version >= 0.1.5.'
            )
            img = img.reorient_image((1, 0, 0))['reoimage']
        logger.info('Writing preprocessed image: {} ({:d}/{:d})'.format(
            img_base, i, len(img_fns)))
        out_img = os.path.join(out_img_dir, img_base + img_ext)
        ants.image_write(img, out_img)
def ws_normalize(img_dir,
                 contrast,
                 mask_dir=None,
                 output_dir=None,
                 write_to_disk=True):
    """
    Use WhiteStripe normalization method ([1]) to normalize the intensities of
    a set of MR images by normalizing an area around the white matter peak of the histogram

    Args:
        img_dir (str): directory containing MR images to be normalized
        contrast (str): contrast of MR images to be normalized (T1, T2, or FLAIR)
        mask_dir (str): if images are not skull-stripped, then provide brain mask
        output_dir (str): directory to save images if you do not want them saved in
            same directory as img_dir
        write_to_disk (bool): write the normalized data to disk or nah

    Returns:
        normalized (np.ndarray): last normalized image data from img_dir
            I know this is an odd behavior, but yolo

    References:
        [1] R. T. Shinohara, E. M. Sweeney, J. Goldsmith, N. Shiee,
            F. J. Mateen, P. A. Calabresi, S. Jarso, D. L. Pham,
            D. S. Reich, and C. M. Crainiceanu, “Statistical normalization
            techniques for magnetic resonance imaging,” NeuroImage Clin.,
            vol. 6, pp. 9–19, 2014.
    """

    # grab the file names for the images of interest
    data = io.glob_nii(img_dir)

    # define and get the brain masks for the images, if defined
    if mask_dir is None:
        masks = [None] * len(data)
    else:
        masks = io.glob_nii(mask_dir)
        if len(data) != len(masks):
            raise NormalizationError(
                'Number of images and masks must be equal, Images: {}, Masks: {}'
                .format(len(data), len(masks)))

    # define the output directory and corresponding output file names
    if output_dir is None:
        output_files = [None] * len(data)
    else:
        output_files = []
        for fn in data:
            _, base, ext = io.split_filename(fn)
            output_files.append(os.path.join(output_dir, base + '_ws' + ext))
        if not os.path.exists(output_dir):
            os.mkdir(output_dir)

    # do whitestripe normalization and save the results
    for i, (img_fn, mask_fn,
            output_fn) in enumerate(zip(data, masks, output_files), 1):
        logger.info('Normalizing image: {} ({:d}/{:d})'.format(
            img_fn, i, len(data)))
        img = io.open_nii(img_fn)
        mask = io.open_nii(mask_fn) if mask_fn is not None else None
        indices = whitestripe(img, contrast, mask=mask)
        normalized = whitestripe_norm(img, indices)
        if write_to_disk:
            logger.info('Saving normalized image: {} ({:d}/{:d})'.format(
                output_fn, i, len(data)))
            io.save_nii(normalized, output_fn)

    # output the last normalized image (mostly for testing purposes)
    return normalized
def main(args=None):
    args = arg_parser().parse_args(args)
    if not (args.brain_mask is None) ^ (args.tissue_mask is None):
        raise NormalizationError(
            'Only one of {brain mask, tissue mask} should be given')
    if args.verbosity == 1:
        level = logging.getLevelName('INFO')
    elif args.verbosity >= 2:
        level = logging.getLevelName('DEBUG')
    else:
        level = logging.getLevelName('WARNING')
    logging.basicConfig(
        format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
        level=level)
    logger = logging.getLogger(__name__)
    try:
        if not args.single_img:
            if not os.path.isdir(
                    args.image) or (False if args.brain_mask is None else
                                    not os.path.isdir(args.brain_mask)):
                raise NormalizationError(
                    'if single-img option off, then image and brain-mask must be directories'
                )
            img_fns = io.glob_nii(args.image)
            mask_fns = io.glob_nii(
                args.brain_mask
            ) if args.brain_mask is not None else [None] * len(img_fns)
            if len(img_fns) != len(mask_fns) and len(img_fns) > 0:
                raise NormalizationError(
                    'input images and masks must be in correspondence and greater than zero '
                    '({:d} != {:d})'.format(len(img_fns), len(mask_fns)))
            args.output_dir = args.output_dir or 'fcm'
            output_dir_base = os.path.abspath(
                os.path.join(args.output_dir, '..'))

            if args.contrast.lower() == 't1' and args.tissue_mask is None:
                tissue_mask_dir = os.path.join(output_dir_base, 'tissue_masks')
                if os.path.exists(tissue_mask_dir):
                    logger.warning(
                        'Tissue mask directory already exists, may overwrite existing tissue masks!'
                    )
                else:
                    logger.info('Creating tissue mask directory: {}'.format(
                        tissue_mask_dir))
                    os.mkdir(tissue_mask_dir)
                for i, (img, mask) in enumerate(zip(img_fns, mask_fns), 1):
                    _, base, _ = io.split_filename(img)
                    _, mask_base, _ = io.split_filename(mask)
                    logger.info(
                        'Creating tissue mask for {} ({:d}/{:d})'.format(
                            base, i, len(img_fns)))
                    logger.debug('Tissue mask {} ({:d}/{:d})'.format(
                        mask_base, i, len(img_fns)))
                    process(img, mask, None, tissue_mask_dir, args, logger)
            elif os.path.exists(args.tissue_mask):
                tissue_mask_dir = args.tissue_mask
            else:
                raise NormalizationError(
                    'If contrast is not t1, then tissue mask directory ({}) '
                    'must already be created!'.format(args.tissue_mask))

            tissue_masks = io.glob_nii(tissue_mask_dir)
            for i, (img, tissue_mask) in enumerate(zip(img_fns, tissue_masks),
                                                   1):
                dirname, base, _ = io.split_filename(img)
                _, tissue_base, _ = io.split_filename(tissue_mask)
                logger.info('Normalizing image {} ({:d}/{:d})'.format(
                    base, i, len(img_fns)))
                logger.debug('Tissue mask {} ({:d}/{:d})'.format(
                    tissue_base, i, len(img_fns)))
                if args.output_dir is not None:
                    dirname = args.output_dir
                process(img, None, tissue_mask, dirname, args, logger)

        else:
            if not os.path.isfile(args.image):
                raise NormalizationError(
                    'if single-img option on, then image must be a file')
            if args.tissue_mask is None and args.contrast.lower() == 't1':
                logger.info('Creating tissue mask for {}'.format(args.image))
                process(args.image, args.brain_mask, None, args.output_dir,
                        args, logger)
            elif os.path.isfile(args.tissue_mask):
                pass
            else:
                raise NormalizationError(
                    'If contrast is not t1, then tissue mask must be provided!'
                )
            logger.info('Normalizing image {}'.format(args.image))
            dirname, base, _ = io.split_filename(args.image)
            dirname = args.output_dir or dirname
            if args.tissue_mask is None:
                tissue_mask = os.path.join(
                    dirname, base + '_{}_mask.nii.gz'.format(args.tissue_type))
            else:
                tissue_mask = args.tissue_mask
            process(args.image, args.brain_mask, tissue_mask, dirname, args,
                    logger)

        if args.plot_hist:
            with warnings.catch_warnings():
                warnings.filterwarnings('ignore', category=FutureWarning)
                from intensity_normalization.plot.hist import all_hists
                import matplotlib.pyplot as plt
            ax = all_hists(args.output_dir, args.brain_mask)
            ax.set_title('Fuzzy C-Means')
            plt.savefig(os.path.join(args.output_dir, 'hist.png'))

        return 0
    except Exception as e:
        logger.exception(e)
        return 1
def main(args=None):
    args = arg_parser().parse_args(args)
    if args.verbosity == 1:
        level = logging.getLevelName('INFO')
    elif args.verbosity >= 2:
        level = logging.getLevelName('DEBUG')
    else:
        level = logging.getLevelName('WARNING')
    logging.basicConfig(
        format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
        level=level)
    logger = logging.getLogger(__name__)
    try:
        img_fns = io.glob_nii(args.img_dir)
        mask_fns = io.glob_nii(args.mask_dir)
        if len(img_fns) != len(mask_fns) or len(img_fns) == 0:
            raise NormalizationError(
                'Image directory ({}) and mask directory ({}) must contain the same '
                '(positive) number of images!'.format(args.img_dir,
                                                      args.mask_dir))

        logger.info('Normalizing the images according to RAVEL')
        Z, _ = ravel.ravel_normalize(
            args.img_dir,
            args.mask_dir,
            args.contrast,
            do_whitestripe=args.no_whitestripe,
            b=args.num_unwanted_factors,
            membership_thresh=args.control_membership_threshold,
            do_registration=args.no_registration,
            segmentation_smoothness=args.segmentation_smoothness,
            use_fcm=not args.use_atropos,
            sparse_svd=args.sparse_svd,
            csf_masks=args.csf_masks)

        V = ravel.image_matrix(img_fns, args.contrast, masks=mask_fns)
        V_norm = ravel.ravel_correction(V, Z)
        normalized = ravel.image_matrix_to_images(V_norm, img_fns)

        # save the normalized images to disk
        output_dir = os.getcwd(
        ) if args.output_dir is None else args.output_dir
        out_fns = []
        for fn in img_fns:
            _, base, ext = io.split_filename(fn)
            out_fns.append(os.path.join(output_dir, base + '_ravel' + ext))
        if not os.path.exists(output_dir):
            os.mkdir(output_dir)
        for norm, out_fn in zip(normalized, out_fns):
            norm.to_filename(out_fn)

        if args.plot_hist:
            with warnings.catch_warnings():
                warnings.filterwarnings('ignore', category=FutureWarning)
                from intensity_normalization.plot.hist import all_hists
                import matplotlib.pyplot as plt
            ax = all_hists(output_dir, args.mask_dir)
            ax.set_title('RAVEL')
            plt.savefig(os.path.join(output_dir, 'hist.png'))

        return 0
    except Exception as e:
        logger.exception(e)
        return 1
Esempio n. 14
0
def ravel_normalize(img_dir,
                    mask_dir,
                    contrast,
                    output_dir=None,
                    write_to_disk=False,
                    do_whitestripe=True,
                    b=1,
                    membership_thresh=0.99,
                    segmentation_smoothness=0.25,
                    do_registration=False,
                    use_fcm=True):
    """
    Use RAVEL [1] to normalize the intensities of a set of MR images to eliminate
    unwanted technical variation in images (but, hopefully, preserve biological variation)

    this function has an option that is modified from [1] in where no registration is done,
    the control mask is defined dynamically by finding a tissue segmentation of the brain and
    thresholding the membership at a very high level (this seems to work well and is *much* faster)
    but there seems to be some more inconsistency in the results

    Args:
        img_dir (str): directory containing MR images to be normalized
        mask_dir (str): brain masks for imgs
        contrast (str): contrast of MR images to be normalized (T1, T2, or FLAIR)
        output_dir (str): directory to save images if you do not want them saved in
            same directory as data_dir
        write_to_disk (bool): write the normalized data to disk or nah
        do_whitestripe (bool): whitestripe normalize the images before applying RAVEL correction
        b (int): number of unwanted factors to estimate
        membership_thresh (float): threshold of membership for control voxels
        segmentation_smoothness (float): segmentation smoothness parameter for atropos ANTsPy
            segmentation scheme (i.e., mrf parameter)
        do_registration (bool): deformably register images to find control mask
        use_fcm (bool): use FCM for segmentation instead of atropos (may be less accurate)

    Returns:
        Z (np.ndarray): unwanted factors (used in ravel correction)
        normalized (np.ndarray): set of normalized images from data_dir

    References:
        [1] J. P. Fortin, E. M. Sweeney, J. Muschelli, C. M. Crainiceanu,
            and R. T. Shinohara, “Removing inter-subject technical variability
            in magnetic resonance imaging studies,” Neuroimage, vol. 132,
            pp. 198–212, 2016.
    """
    img_fns = io.glob_nii(img_dir)
    mask_fns = io.glob_nii(mask_dir)

    if output_dir is None or not write_to_disk:
        out_fns = None
    else:
        out_fns = []
        for fn in img_fns:
            _, base, ext = io.split_filename(fn)
            out_fns.append(os.path.join(output_dir, base + ext))
        if not os.path.exists(output_dir):
            os.mkdir(output_dir)

    # get parameters necessary and setup the V array
    V, Vc = image_matrix(img_fns,
                         contrast,
                         masks=mask_fns,
                         do_whitestripe=do_whitestripe,
                         return_ctrl_matrix=True,
                         membership_thresh=membership_thresh,
                         do_registration=do_registration,
                         smoothness=segmentation_smoothness,
                         use_fcm=use_fcm)

    # estimate the unwanted factors Z
    _, _, vh = np.linalg.svd(Vc)
    Z = vh.T[:, 0:b]

    # perform the ravel correction
    V_norm = ravel_correction(V, Z)

    # save the results to disk if desired
    if write_to_disk:
        for i, (img_fn, out_fn) in enumerate(zip(img_fns, out_fns)):
            img = io.open_nii(img_fn)
            norm = V_norm[:, i].reshape(img.get_data().shape)
            io.save_nii(img, out_fn, data=norm)

    return Z, V_norm