1
1
#!/usr/bin/env python
2
2
# -*- coding: utf-8 -*-
3
- """Change directory to provide relative paths for doctests
3
+ """
4
+ Change directory to provide relative paths for doctests
4
5
>>> import os
5
6
>>> filepath = os.path.dirname( os.path.realpath( __file__ ) )
6
7
>>> datadir = os.path.realpath(os.path.join(filepath, '../../testing/data'))
7
8
>>> os.chdir(datadir)
8
9
"""
9
10
import os .path as op
10
- import warnings
11
-
12
11
import nibabel as nb
13
12
import numpy as np
14
13
15
- from ..base import (traits , TraitedSpec , BaseInterface , File , isdefined )
16
- from ...utils .filemanip import split_filename
17
- from ...utils .misc import package_check
14
+ from ..base import (traits , TraitedSpec , File , isdefined )
15
+ from .base import DipyBaseInterface
18
16
from ... import logging
19
- iflogger = logging .getLogger ('interface' )
20
-
21
- have_dipy = True
22
- try :
23
- package_check ('dipy' , version = '0.6.0' )
24
- except Exception as e :
25
- have_dipy = False
17
+ IFLOGGER = logging .getLogger ('interface' )
26
18
27
19
28
20
class ResampleInputSpec (TraitedSpec ):
@@ -33,15 +25,17 @@ class ResampleInputSpec(TraitedSpec):
33
25
' is set, then isotropic regridding will '
34
26
'be performed, with spacing equal to the '
35
27
'smallest current zoom.' ))
36
- interp = traits .Int (1 , mandatory = True , usedefault = True , desc = ('order of '
37
- 'the interpolator (0 = nearest, 1 = linear, etc.' ))
28
+ interp = traits .Int (
29
+ 1 , mandatory = True , usedefault = True ,
30
+ desc = ('order of the interpolator (0 = nearest, 1 = linear, etc.' ))
38
31
39
32
40
33
class ResampleOutputSpec (TraitedSpec ):
41
34
out_file = File (exists = True )
42
35
43
36
44
- class Resample (BaseInterface ):
37
+ class Resample (DipyBaseInterface ):
38
+
45
39
"""
46
40
An interface to reslicing diffusion datasets.
47
41
See
@@ -69,7 +63,7 @@ def _run_interface(self, runtime):
69
63
resample_proxy (self .inputs .in_file , order = order ,
70
64
new_zooms = vox_size , out_file = out_file )
71
65
72
- iflogger .info ('Resliced image saved as {i}' .format (i = out_file ))
66
+ IFLOGGER .info ('Resliced image saved as {i}' .format (i = out_file ))
73
67
return runtime
74
68
75
69
def _list_outputs (self ):
@@ -92,17 +86,21 @@ class DenoiseInputSpec(TraitedSpec):
92
86
noise_model = traits .Enum ('rician' , 'gaussian' , mandatory = True ,
93
87
usedefault = True ,
94
88
desc = ('noise distribution model' ))
89
+ signal_mask = File (desc = ('mask in which the mean signal '
90
+ 'will be computed' ), exists = True )
95
91
noise_mask = File (desc = ('mask in which the standard deviation of noise '
96
92
'will be computed' ), exists = True )
97
93
patch_radius = traits .Int (1 , desc = 'patch radius' )
98
94
block_radius = traits .Int (5 , desc = 'block_radius' )
95
+ snr = traits .Float (desc = 'manually set an SNR' )
99
96
100
97
101
98
class DenoiseOutputSpec (TraitedSpec ):
102
99
out_file = File (exists = True )
103
100
104
101
105
- class Denoise (BaseInterface ):
102
+ class Denoise (DipyBaseInterface ):
103
+
106
104
"""
107
105
An interface to denoising diffusion datasets [Coupe2008]_.
108
106
See
@@ -140,16 +138,24 @@ def _run_interface(self, runtime):
140
138
if isdefined (self .inputs .block_radius ):
141
139
settings ['block_radius' ] = self .inputs .block_radius
142
140
141
+ snr = None
142
+ if isdefined (self .inputs .snr ):
143
+ snr = self .inputs .snr
144
+
145
+ signal_mask = None
146
+ if isdefined (self .inputs .signal_mask ):
147
+ signal_mask = nb .load (self .inputs .signal_mask ).get_data ()
143
148
noise_mask = None
144
- if isdefined (self .inputs .in_mask ):
149
+ if isdefined (self .inputs .noise_mask ):
145
150
noise_mask = nb .load (self .inputs .noise_mask ).get_data ()
146
151
147
- _ , s = nlmeans_proxy (self .inputs .in_file ,
148
- settings ,
149
- noise_mask = noise_mask ,
152
+ _ , s = nlmeans_proxy (self .inputs .in_file , settings ,
153
+ snr = snr ,
154
+ smask = signal_mask ,
155
+ nmask = noise_mask ,
150
156
out_file = out_file )
151
- iflogger .info (('Denoised image saved as {i}, estimated '
152
- 'sigma ={s}' ).format (i = out_file , s = s ))
157
+ IFLOGGER .info (('Denoised image saved as {i}, estimated '
158
+ 'SNR ={s}' ).format (i = out_file , s = str ( s ) ))
153
159
return runtime
154
160
155
161
def _list_outputs (self ):
@@ -169,7 +175,7 @@ def resample_proxy(in_file, order=3, new_zooms=None, out_file=None):
169
175
"""
170
176
Performs regridding of an image to set isotropic voxel sizes using dipy.
171
177
"""
172
- from dipy .align .aniso2iso import resample
178
+ from dipy .align .reslice import reslice
173
179
174
180
if out_file is None :
175
181
fname , fext = op .splitext (op .basename (in_file ))
@@ -191,7 +197,7 @@ def resample_proxy(in_file, order=3, new_zooms=None, out_file=None):
191
197
if np .all (im_zooms == new_zooms ):
192
198
return in_file
193
199
194
- data2 , affine2 = resample (data , affine , im_zooms , new_zooms , order = order )
200
+ data2 , affine2 = reslice (data , affine , im_zooms , new_zooms , order = order )
195
201
tmp_zooms = np .array (hdr .get_zooms ())
196
202
tmp_zooms [:3 ] = new_zooms [0 ]
197
203
hdr .set_zooms (tuple (tmp_zooms ))
@@ -203,12 +209,16 @@ def resample_proxy(in_file, order=3, new_zooms=None, out_file=None):
203
209
204
210
205
211
def nlmeans_proxy (in_file , settings ,
206
- noise_mask = None , out_file = None ):
212
+ snr = None ,
213
+ smask = None ,
214
+ nmask = None ,
215
+ out_file = None ):
207
216
"""
208
217
Uses non-local means to denoise 4D datasets
209
218
"""
210
- package_check ('dipy' , version = '0.8.0.dev' )
211
219
from dipy .denoise .nlmeans import nlmeans
220
+ from scipy .ndimage .morphology import binary_erosion
221
+ from scipy import ndimage
212
222
213
223
if out_file is None :
214
224
fname , fext = op .splitext (op .basename (in_file ))
@@ -222,13 +232,69 @@ def nlmeans_proxy(in_file, settings,
222
232
data = img .get_data ()
223
233
aff = img .affine
224
234
225
- nmask = data [..., 0 ] > 80
226
- if noise_mask is not None :
227
- nmask = noise_mask > 0
228
-
229
- sigma = np .std (data [nmask == 1 ])
230
- den = nlmeans (data , sigma , ** settings )
235
+ if data .ndim < 4 :
236
+ data = data [..., np .newaxis ]
237
+
238
+ data = np .nan_to_num (data )
239
+
240
+ if data .max () < 1.0e-4 :
241
+ raise RuntimeError ('There is no signal in the image' )
242
+
243
+ df = 1.0
244
+ if data .max () < 1000.0 :
245
+ df = 1000. / data .max ()
246
+ data *= df
247
+
248
+ b0 = data [..., 0 ]
249
+
250
+ if smask is None :
251
+ smask = np .zeros_like (b0 )
252
+ smask [b0 > np .percentile (b0 , 85. )] = 1
253
+
254
+ smask = binary_erosion (
255
+ smask .astype (np .uint8 ), iterations = 2 ).astype (np .uint8 )
256
+
257
+ if nmask is None :
258
+ nmask = np .ones_like (b0 , dtype = np .uint8 )
259
+ bmask = settings ['mask' ]
260
+ if bmask is None :
261
+ bmask = np .zeros_like (b0 )
262
+ bmask [b0 > np .percentile (b0 [b0 > 0 ], 10 )] = 1
263
+ label_im , nb_labels = ndimage .label (bmask )
264
+ sizes = ndimage .sum (bmask , label_im , range (nb_labels + 1 ))
265
+ maxidx = np .argmax (sizes )
266
+ bmask = np .zeros_like (b0 , dtype = np .uint8 )
267
+ bmask [label_im == maxidx ] = 1
268
+ nmask [bmask > 0 ] = 0
269
+ else :
270
+ nmask = np .squeeze (nmask )
271
+ nmask [nmask > 0.0 ] = 1
272
+ nmask [nmask < 1 ] = 0
273
+ nmask = nmask .astype (bool )
274
+
275
+ nmask = binary_erosion (nmask , iterations = 1 ).astype (np .uint8 )
276
+
277
+ den = np .zeros_like (data )
278
+
279
+ est_snr = True
280
+ if snr is not None :
281
+ snr = [snr ] * data .shape [- 1 ]
282
+ est_snr = False
283
+ else :
284
+ snr = []
285
+
286
+ for i in range (data .shape [- 1 ]):
287
+ d = data [..., i ]
288
+ if est_snr :
289
+ s = np .mean (d [smask > 0 ])
290
+ n = np .std (d [nmask > 0 ])
291
+ snr .append (s / n )
292
+
293
+ den [..., i ] = nlmeans (d , snr [i ], ** settings )
294
+
295
+ den = np .squeeze (den )
296
+ den /= df
231
297
232
298
nb .Nifti1Image (den .astype (hdr .get_data_dtype ()), aff ,
233
299
hdr ).to_filename (out_file )
234
- return out_file , sigma
300
+ return out_file , snr
0 commit comments