|
40 | 40 | DynamicTraitedSpec, Undefined)
|
41 | 41 | from nipype.utils.filemanip import fname_presuffix, split_filename
|
42 | 42 |
|
43 |
| -# for backwards compatibility |
44 |
| -from nipype.algorithms.tsnr import TSNR |
45 |
| - |
46 | 43 | iflogger = logging.getLogger('interface')
|
47 | 44 |
|
48 | 45 |
|
@@ -263,6 +260,108 @@ def _list_outputs(self):
|
263 | 260 | outputs['nifti_file'] = self._gen_output_file_name()
|
264 | 261 | return outputs
|
265 | 262 |
|
| 263 | +class TSNRInputSpec(BaseInterfaceInputSpec): |
| 264 | + in_file = InputMultiPath(File(exists=True), mandatory=True, |
| 265 | + desc='realigned 4D file or a list of 3D files') |
| 266 | + regress_poly = traits.Range(low=1, desc='Remove polynomials') |
| 267 | + tsnr_file = File('tsnr.nii.gz', usedefault=True, hash_files=False, |
| 268 | + desc='output tSNR file') |
| 269 | + mean_file = File('mean.nii.gz', usedefault=True, hash_files=False, |
| 270 | + desc='output mean file') |
| 271 | + stddev_file = File('stdev.nii.gz', usedefault=True, hash_files=False, |
| 272 | + desc='output tSNR file') |
| 273 | + detrended_file = File('detrend.nii.gz', usedefault=True, hash_files=False, |
| 274 | + desc='input file after detrending') |
| 275 | + |
| 276 | + |
| 277 | +class TSNROutputSpec(TraitedSpec): |
| 278 | + tsnr_file = File(exists=True, desc='tsnr image file') |
| 279 | + mean_file = File(exists=True, desc='mean image file') |
| 280 | + stddev_file = File(exists=True, desc='std dev image file') |
| 281 | + detrended_file = File(desc='detrended input file') |
| 282 | + |
| 283 | + |
| 284 | +class TSNR(BaseInterface): |
| 285 | + """Computes the time-course SNR for a time series |
| 286 | +
|
| 287 | + Typically you want to run this on a realigned time-series. |
| 288 | +
|
| 289 | + Example |
| 290 | + ------- |
| 291 | +
|
| 292 | + >>> tsnr = TSNR() |
| 293 | + >>> tsnr.inputs.in_file = 'functional.nii' |
| 294 | + >>> res = tsnr.run() # doctest: +SKIP |
| 295 | +
|
| 296 | + """ |
| 297 | + input_spec = TSNRInputSpec |
| 298 | + output_spec = TSNROutputSpec |
| 299 | + |
| 300 | + def _run_interface(self, runtime): |
| 301 | + img = nb.load(self.inputs.in_file[0]) |
| 302 | + header = img.header.copy() |
| 303 | + vollist = [nb.load(filename) for filename in self.inputs.in_file] |
| 304 | + data = np.concatenate([vol.get_data().reshape( |
| 305 | + vol.get_shape()[:3] + (-1,)) for vol in vollist], axis=3) |
| 306 | + data = np.nan_to_num(data) |
| 307 | + |
| 308 | + if data.dtype.kind == 'i': |
| 309 | + header.set_data_dtype(np.float32) |
| 310 | + data = data.astype(np.float32) |
| 311 | + |
| 312 | + if isdefined(self.inputs.regress_poly): |
| 313 | + data = regress_poly(self.inputs.regress_poly, data) |
| 314 | + img = nb.Nifti1Image(data, img.get_affine(), header) |
| 315 | + nb.save(img, op.abspath(self.inputs.detrended_file)) |
| 316 | + |
| 317 | + meanimg = np.mean(data, axis=3) |
| 318 | + stddevimg = np.std(data, axis=3) |
| 319 | + tsnr = np.zeros_like(meanimg) |
| 320 | + tsnr[stddevimg > 1.e-3] = meanimg[stddevimg > 1.e-3] / stddevimg[stddevimg > 1.e-3] |
| 321 | + img = nb.Nifti1Image(tsnr, img.get_affine(), header) |
| 322 | + nb.save(img, op.abspath(self.inputs.tsnr_file)) |
| 323 | + img = nb.Nifti1Image(meanimg, img.get_affine(), header) |
| 324 | + nb.save(img, op.abspath(self.inputs.mean_file)) |
| 325 | + img = nb.Nifti1Image(stddevimg, img.get_affine(), header) |
| 326 | + nb.save(img, op.abspath(self.inputs.stddev_file)) |
| 327 | + return runtime |
| 328 | + |
| 329 | + def _list_outputs(self): |
| 330 | + outputs = self._outputs().get() |
| 331 | + for k in ['tsnr_file', 'mean_file', 'stddev_file']: |
| 332 | + outputs[k] = op.abspath(getattr(self.inputs, k)) |
| 333 | + |
| 334 | + if isdefined(self.inputs.regress_poly): |
| 335 | + outputs['detrended_file'] = op.abspath(self.inputs.detrended_file) |
| 336 | + return outputs |
| 337 | + |
| 338 | +def regress_poly(degree, data): |
| 339 | + ''' returns data with degree polynomial regressed out. |
| 340 | + The last dimension (i.e. data.shape[-1]) should be time. |
| 341 | + ''' |
| 342 | + datashape = data.shape |
| 343 | + timepoints = datashape[-1] |
| 344 | + |
| 345 | + # Rearrange all voxel-wise time-series in rows |
| 346 | + data = data.reshape((-1, timepoints)) |
| 347 | + |
| 348 | + # Generate design matrix |
| 349 | + X = np.ones((timepoints, 1)) |
| 350 | + for i in range(degree): |
| 351 | + polynomial_func = legendre(i+1) |
| 352 | + value_array = np.linspace(-1, 1, timepoints) |
| 353 | + X = np.hstack((X, polynomial_func(value_array)[:, np.newaxis])) |
| 354 | + |
| 355 | + # Calculate coefficients |
| 356 | + betas = np.linalg.pinv(X).dot(data.T) |
| 357 | + |
| 358 | + # Estimation |
| 359 | + datahat = X[:, 1:].dot(betas[1:, ...]).T |
| 360 | + regressed_data = data - datahat |
| 361 | + |
| 362 | + # Back to original shape |
| 363 | + return regressed_data.reshape(datashape) |
| 364 | + |
266 | 365 | class GunzipInputSpec(BaseInterfaceInputSpec):
|
267 | 366 | in_file = File(exists=True, mandatory=True)
|
268 | 367 |
|
|
0 commit comments