|
| 1 | +#!/usr/bin/env python |
| 2 | +# -*- coding: utf-8 -*- |
| 3 | +# @Author: oesteban |
| 4 | +# @Date: 2014-09-01 10:33:35 |
| 5 | +# @Last Modified by: oesteban |
| 6 | +# @Last Modified time: 2014-09-03 15:07:46 |
| 7 | +from nipype.interfaces.base import (traits, TraitedSpec, BaseInterface, |
| 8 | + File, isdefined) |
| 9 | +from nipype.utils.filemanip import split_filename |
| 10 | +import os.path as op |
| 11 | +import nibabel as nb |
| 12 | +import numpy as np |
| 13 | +from nipype.utils.misc import package_check |
| 14 | +import warnings |
| 15 | +from ... import logging |
| 16 | +iflogger = logging.getLogger('interface') |
| 17 | + |
| 18 | +have_dipy = True |
| 19 | +try: |
| 20 | + package_check('dipy', version='0.6.0') |
| 21 | +except Exception, e: |
| 22 | + have_dipy = False |
| 23 | +else: |
| 24 | + from dipy.align.aniso2iso import resample |
| 25 | + from dipy.core.gradients import GradientTable |
| 26 | + from dipy.denoise.nlmeans import nlmeans |
| 27 | + |
| 28 | + |
| 29 | +class ResampleInputSpec(TraitedSpec): |
| 30 | + in_file = File(exists=True, mandatory=True, |
| 31 | + desc='The input 4D diffusion-weighted image file') |
| 32 | + vox_size = traits.Tuple(traits.Float, traits.Float, traits.Float, |
| 33 | + desc=('specify the new voxel zooms. If no vox_size' |
| 34 | + ' is set, then isotropic regridding will ' |
| 35 | + 'be performed, with spacing equal to the ' |
| 36 | + 'smallest current zoom.')) |
| 37 | + interp = traits.Int(1, mandatory=True, usedefault=True, desc=('order of ' |
| 38 | + 'the interpolator (0 = nearest, 1 = linear, etc.')) |
| 39 | + |
| 40 | + |
| 41 | +class ResampleOutputSpec(TraitedSpec): |
| 42 | + out_file = File(exists=True) |
| 43 | + |
| 44 | + |
| 45 | +class Resample(BaseInterface): |
| 46 | + """ |
| 47 | + An interface to reslicing diffusion datasets. |
| 48 | + See |
| 49 | + http://nipy.org/dipy/examples_built/reslice_datasets.html#example-reslice-datasets. |
| 50 | +
|
| 51 | + Example |
| 52 | + ------- |
| 53 | +
|
| 54 | + >>> import nipype.interfaces.dipy as dipy |
| 55 | + >>> reslice = dipy.Resample() |
| 56 | + >>> reslice.inputs.in_file = 'diffusion.nii' |
| 57 | + >>> reslice.run() # doctest: +SKIP |
| 58 | + """ |
| 59 | + input_spec = ResampleInputSpec |
| 60 | + output_spec = ResampleOutputSpec |
| 61 | + |
| 62 | + def _run_interface(self, runtime): |
| 63 | + order = self.inputs.interp |
| 64 | + vox_size = None |
| 65 | + |
| 66 | + if isdefined(self.inputs.vox_size): |
| 67 | + vox_size = self.inputs.vox_size |
| 68 | + |
| 69 | + out_file = op.abspath(self._gen_outfilename()) |
| 70 | + resample_proxy(self.inputs.in_file, order=order, |
| 71 | + new_zooms=vox_size, out_file=out_file) |
| 72 | + |
| 73 | + iflogger.info('Resliced image saved as {i}'.format(i=out_file)) |
| 74 | + return runtime |
| 75 | + |
| 76 | + def _list_outputs(self): |
| 77 | + outputs = self._outputs().get() |
| 78 | + outputs['out_file'] = op.abspath(self._gen_outfilename()) |
| 79 | + return outputs |
| 80 | + |
| 81 | + def _gen_outfilename(self): |
| 82 | + fname, fext = op.splitext(op.basename(self.inputs.in_file)) |
| 83 | + if fext == '.gz': |
| 84 | + fname, fext2 = op.splitext(fname) |
| 85 | + fext = fext2 + fext |
| 86 | + return op.abspath('%s_reslice%s' % (fname, fext)) |
| 87 | + |
| 88 | + |
| 89 | +class DenoiseInputSpec(TraitedSpec): |
| 90 | + in_file = File(exists=True, mandatory=True, |
| 91 | + desc='The input 4D diffusion-weighted image file') |
| 92 | + in_mask = File(exists=True, desc='brain mask') |
| 93 | + noise_model = traits.Enum('rician', 'gaussian', mandatory=True, |
| 94 | + usedefault=True, |
| 95 | + desc=('noise distribution model')) |
| 96 | + noise_mask = File(desc=('mask in which the standard deviation of noise ' |
| 97 | + 'will be computed'), exists=True) |
| 98 | + patch_radius = traits.Int(1, desc='patch radius') |
| 99 | + block_radius = traits.Int(5, desc='block_radius') |
| 100 | + |
| 101 | + |
| 102 | +class DenoiseOutputSpec(TraitedSpec): |
| 103 | + out_file = File(exists=True) |
| 104 | + |
| 105 | + |
| 106 | +class Denoise(BaseInterface): |
| 107 | + """ |
| 108 | + An interface to denoising diffusion datasets [Coupe2008]_. |
| 109 | + See |
| 110 | + http://nipy.org/dipy/examples_built/denoise_nlmeans.html#example-denoise-nlmeans. |
| 111 | +
|
| 112 | + .. [Coupe2008] Coupe P et al., `An Optimized Blockwise Non Local Means |
| 113 | + Denoising Filter for 3D Magnetic Resonance Images |
| 114 | + <http://dx.doi.org/10.1109%2FTMI.2007.906087>`_, |
| 115 | + IEEE Transactions on Medical Imaging, 27(4):425-441, 2008. |
| 116 | +
|
| 117 | +
|
| 118 | + Example |
| 119 | + ------- |
| 120 | +
|
| 121 | + >>> import nipype.interfaces.dipy as dipy |
| 122 | + >>> denoise = dipy.Denoise() |
| 123 | + >>> denoise.inputs.in_file = 'diffusion.nii' |
| 124 | + >>> denoise.run() # doctest: +SKIP |
| 125 | + """ |
| 126 | + input_spec = DenoiseInputSpec |
| 127 | + output_spec = DenoiseOutputSpec |
| 128 | + |
| 129 | + def _run_interface(self, runtime): |
| 130 | + out_file = op.abspath(self._gen_outfilename()) |
| 131 | + |
| 132 | + settings = dict(mask=None, |
| 133 | + rician=(self.inputs.noise_model == 'rician')) |
| 134 | + |
| 135 | + if isdefined(self.inputs.in_mask): |
| 136 | + settings['mask'] = nb.load(self.inputs.in_mask).get_data() |
| 137 | + |
| 138 | + if isdefined(self.inputs.patch_radius): |
| 139 | + settings['patch_radius'] = self.inputs.patch_radius |
| 140 | + |
| 141 | + if isdefined(self.inputs.block_radius): |
| 142 | + settings['block_radius'] = self.inputs.block_radius |
| 143 | + |
| 144 | + noise_mask = None |
| 145 | + if isdefined(self.inputs.in_mask): |
| 146 | + noise_mask = nb.load(self.inputs.noise_mask).get_data() |
| 147 | + |
| 148 | + _, s = nlmeans_proxy(self.inputs.in_file, |
| 149 | + settings, |
| 150 | + noise_mask=noise_mask, |
| 151 | + out_file=out_file) |
| 152 | + iflogger.info(('Denoised image saved as {i}, estimated ' |
| 153 | + 'sigma={s}').format(i=out_file, s=s)) |
| 154 | + return runtime |
| 155 | + |
| 156 | + def _list_outputs(self): |
| 157 | + outputs = self._outputs().get() |
| 158 | + outputs['out_file'] = op.abspath(self._gen_outfilename()) |
| 159 | + return outputs |
| 160 | + |
| 161 | + def _gen_outfilename(self): |
| 162 | + fname, fext = op.splitext(op.basename(self.inputs.in_file)) |
| 163 | + if fext == '.gz': |
| 164 | + fname, fext2 = op.splitext(fname) |
| 165 | + fext = fext2 + fext |
| 166 | + return op.abspath('%s_denoise%s' % (fname, fext)) |
| 167 | + |
| 168 | + |
| 169 | +def resample_proxy(in_file, order=3, new_zooms=None, out_file=None): |
| 170 | + """ |
| 171 | + Performs regridding of an image to set isotropic voxel sizes using dipy. |
| 172 | + """ |
| 173 | + |
| 174 | + if out_file is None: |
| 175 | + fname, fext = op.splitext(op.basename(in_file)) |
| 176 | + if fext == '.gz': |
| 177 | + fname, fext2 = op.splitext(fname) |
| 178 | + fext = fext2 + fext |
| 179 | + out_file = op.abspath('./%s_reslice%s' % (fname, fext)) |
| 180 | + |
| 181 | + img = nb.load(in_file) |
| 182 | + hdr = img.get_header().copy() |
| 183 | + data = img.get_data().astype(np.float32) |
| 184 | + affine = img.get_affine() |
| 185 | + im_zooms = hdr.get_zooms()[:3] |
| 186 | + |
| 187 | + if new_zooms is None: |
| 188 | + minzoom = np.array(im_zooms).min() |
| 189 | + new_zooms = tuple(np.ones((3,)) * minzoom) |
| 190 | + |
| 191 | + if np.all(im_zooms == new_zooms): |
| 192 | + return in_file |
| 193 | + |
| 194 | + data2, affine2 = resample(data, affine, im_zooms, new_zooms, order=order) |
| 195 | + tmp_zooms = np.array(hdr.get_zooms()) |
| 196 | + tmp_zooms[:3] = new_zooms[0] |
| 197 | + hdr.set_zooms(tuple(tmp_zooms)) |
| 198 | + hdr.set_data_shape(data2.shape) |
| 199 | + hdr.set_xyzt_units('mm') |
| 200 | + nb.Nifti1Image(data2.astype(hdr.get_data_dtype()), |
| 201 | + affine2, hdr).to_filename(out_file) |
| 202 | + return out_file, new_zooms |
| 203 | + |
| 204 | + |
| 205 | +def nlmeans_proxy(in_file, settings, |
| 206 | + noise_mask=None, out_file=None): |
| 207 | + """ |
| 208 | + Uses non-local means to denoise 4D datasets |
| 209 | + """ |
| 210 | + |
| 211 | + if out_file is None: |
| 212 | + fname, fext = op.splitext(op.basename(in_file)) |
| 213 | + if fext == '.gz': |
| 214 | + fname, fext2 = op.splitext(fname) |
| 215 | + fext = fext2 + fext |
| 216 | + out_file = op.abspath('./%s_denoise%s' % (fname, fext)) |
| 217 | + |
| 218 | + img = nb.load(in_file) |
| 219 | + hdr = img.get_header() |
| 220 | + data = img.get_data() |
| 221 | + aff = img.get_affine() |
| 222 | + |
| 223 | + nmask = data[..., 0] > 80 |
| 224 | + if noise_mask is not None: |
| 225 | + nmask = noise_mask > 0 |
| 226 | + |
| 227 | + sigma = np.std(data[nmask == 1]) |
| 228 | + den = nlmeans(data, sigma, **settings) |
| 229 | + |
| 230 | + nb.Nifti1Image(den.astype(hdr.get_data_dtype()), aff, |
| 231 | + hdr).to_filename(out_file) |
| 232 | + return out_file, sigma |
0 commit comments