1
+ # coding: utf-8
2
+
1
3
import nipype .pipeline .engine as pe
2
4
import nipype .interfaces .utility as util
3
5
import nipype .interfaces .fsl as fsl
4
-
6
+ import os
5
7
6
8
def transpose (samples_over_fibres ):
7
9
import numpy as np
@@ -214,4 +216,264 @@ def create_eddy_correct_pipeline(name="eddy_correct"):
214
216
215
217
pipeline .connect ([(merge , outputnode , [("merged_file" , "eddy_corrected" )])])
216
218
217
- return pipeline
219
+ return pipeline
220
+
221
+ def create_epi_correct_pipeline (name = "epi_correct" , register_to_ref = False ):
222
+ """ Replaces the epidewarp.fsl script (http://www.nmr.mgh.harvard.edu/~greve/fbirn/b0/epidewarp.fsl)
223
+ for EPI distortion correction with the fieldmap information in
224
+ dMRI and fMRI (Jezzard et al., MRM 1995 ) using FSL's FUGUE.
225
+
226
+ Example
227
+ -------
228
+
229
+ >>> nipype_epicorrect = create_epi_correct_pipeline("nipype_epicorrect")
230
+ >>> nipype_epicorrect.inputs.inputnode.in_file = 'epi_data.nii'
231
+ >>> nipype_epicorrect.inputs.inputnode.fieldmap_mag = 'magnitude.nii'
232
+ >>> nipype_epicorrect.inputs.inputnode.fieldmap_pha = 'phase.nii'
233
+ >>> nipype_epicorrect.inputs.inputnode.te_diff = 2.46
234
+ >>> nipype_epicorrect.inputs.inputnode.epi_echospacing = 0.51
235
+ >>> nipype_epicorrect.inputs.inputnode.epi_rev_encoding = False
236
+ >>> nipype_epicorrect.inputs.inputnode.ref_volume = 0
237
+ >>> nipype_epicorrect.inputs.inputnode.epi_parallel = True
238
+ >>> nipype_epicorrect.run() # doctest: +SKIP
239
+
240
+ Inputs::
241
+
242
+ inputnode.in_file - The volume acquired with EPI sequence
243
+ inputnode.fieldmap_mag - The magnitude of the fieldmap
244
+ inputnode.fieldmap_pha - The phase difference of the fieldmap
245
+ inputnode.te_diff - Time difference between TE in ms.
246
+ inputnode.epi_echospacing - The echo spacing (aka dwell time) in the EPI sequence
247
+ inputnode.epi_ph_encoding_dir - The phase encoding direction in EPI acquisition (default y)
248
+ inputnode.epi_rev_encoding - True if it is acquired with reverse encoding
249
+ inputnode.epi_parallel - True if EPI was acquired in a parallel imaging scheme
250
+ inputnode.vsm_sigma - Sigma value of the gaussian smoothing filter applied to the vsm (voxel shift map)
251
+ inputnode.ref_volume - The reference volume (B=0 in dMRI or a central frame in fMRI)
252
+
253
+
254
+ Outputs::
255
+
256
+ outputnode.epi_corrected
257
+ """
258
+
259
+ inputnode = pe .Node (interface = util .IdentityInterface (fields = ["in_file" ,
260
+ "fieldmap_mag" ,
261
+ "fieldmap_pha" ,
262
+ "te_diff" ,
263
+ "epi_echospacing" ,
264
+ "epi_ph_encoding_dir" ,
265
+ "epi_rev_encoding" ,
266
+ "epi_parallel" ,
267
+ "vsm_sigma" ,
268
+ "ref_volume" ,
269
+ "unwarp_direction"
270
+ ]), name = "inputnode" )
271
+
272
+ pipeline = pe .Workflow (name = name )
273
+
274
+
275
+ matrix_file = os .path .abspath ( './flirt.txt' )
276
+
277
+ # Keep first frame from magnitude
278
+ select_mag = pe .Node ( interface = fsl .utils .ExtractROI (t_size = 1 ,t_min = 0 ), name = "select_magnitude" )
279
+
280
+ # mask_brain
281
+ mask_mag = pe .Node ( interface = fsl .BET (mask = True ),name = 'mask_magnitude' )
282
+ mask_mag_dil = pe .Node ( interface = util .Function ( input_names = ["in_file" ], output_names = ["out_file" ], function = _dilate_mask ), name = 'mask_dilate' )
283
+
284
+ # Compute dwell time
285
+ dwell_time = pe .Node ( interface = util .Function ( input_names = ["dwell_time" ,"is_parallel" ,"is_reverse_encoding" ], output_names = ["dwell_time" ], function = _compute_dwelltime ), name = 'dwell_time' )
286
+
287
+ # Normalize phase diff to be [-pi, pi)
288
+ norm_pha = pe .Node ( interface = util .Function ( input_names = ["in_file" ], output_names = ["out_file" ], function = _prepare_phasediff ), name = 'normalize_phasediff' )
289
+ # Execute FSL PRELUDE: prelude -p %s -a %s -o %s -f -v -m %s
290
+ prelude = pe .Node ( interface = fsl .PRELUDE (process3d = True ), name = 'phase_unwrap' )
291
+ fill_phase = pe .Node ( interface = util .Function ( input_names = ["in_file" ], output_names = ["out_file" ], function = _fill_phase ), name = 'fill_phasediff' )
292
+
293
+ # to assure that vsm is same dimension as mag. The input only affects the output dimension.
294
+ # The content of the input has no effect on the vsm. The de-warped mag volume is
295
+ # meaningless and will be thrown away
296
+ # fugue -i %s -u %s -p %s --dwell=%s --asym=%s --mask=%s --saveshift=%s % ( mag_name, magdw_name, ph_name, esp, tediff, mask_name, vsmmag_name)
297
+ vsm = pe .Node ( interface = fsl .FUGUE (save_shift = True ), name = "generate_vsm" )
298
+ vsm_mean = pe .Node ( interface = util .Function ( input_names = ["in_file" ,"mask_file" ,"in_unwarped" ], output_names = ["out_file" ], function = _vsm_remove_mean ), name = "vsm_mean_shift" )
299
+
300
+ # fugue_epi
301
+ dwi_split = pe .Node ( interface = util .Function ( input_names = ['in_file' ], output_names = ['out_files' ], function = _split_dwi ), name = 'dwi_split' )
302
+ # 'fugue -i %s -u %s --loadshift=%s --mask=%s' % ( vol_name, out_vol_name, vsm_name, mask_name )
303
+ dwi_applyxfm = pe .MapNode ( interface = fsl .FUGUE (icorr = True ,save_shift = False ), iterfield = ['in_file' ], name = 'dwi_fugue' )
304
+ # Merge back all volumes
305
+ dwi_merge = pe .Node ( interface = fsl .utils .Merge (dimension = 't' ), name = 'dwi_merge' )
306
+
307
+ outputnode = pe .Node (interface = util .IdentityInterface (fields = ["epi_corrected" ]),
308
+ name = "outputnode" )
309
+
310
+
311
+ pipeline .connect ([
312
+ (inputnode , dwell_time , [('epi_echospacing' ,'dwell_time' ), ('epi_parallel' ,'is_parallel' ),('epi_rev_encoding' ,'is_reverse_encoding' ) ])
313
+ ,(inputnode , select_mag , [('fieldmap_mag' ,'in_file' )] )
314
+ ,(inputnode , norm_pha , [('fieldmap_pha' ,'in_file' )] )
315
+ ,(select_mag , mask_mag , [('roi_file' , 'in_file' )] )
316
+ ,(mask_mag , mask_mag_dil , [('mask_file' ,'in_file' )] )
317
+ ,(select_mag , prelude , [('roi_file' , 'magnitude_file' ) ])
318
+ ,(norm_pha , prelude , [('out_file' , 'phase_file' ) ])
319
+ ,(mask_mag_dil , prelude , [('out_file' , 'mask_file' ) ])
320
+ ,(prelude , fill_phase , [('unwrapped_phase_file' ,'in_file' )] )
321
+ ,(inputnode , vsm , [('fieldmap_mag' , 'in_file' )])
322
+ ,(fill_phase , vsm , [('out_file' ,'phasemap_file' )]) #, (('out_file',_genvsmpath),'shift_out_file') ] )
323
+ ,(inputnode , vsm , [(('te_diff' ,_ms2sec ),'asym_se_time' ),('vsm_sigma' ,'smooth2d' )])
324
+ ,(dwell_time , vsm , [(('dwell_time' ,_ms2sec ),'dwell_time' ) ])
325
+ ,(mask_mag_dil , vsm , [('out_file' ,'mask_file' )] )
326
+ ,(mask_mag_dil , vsm_mean , [('out_file' ,'mask_file' )] )
327
+ ,(vsm , vsm_mean , [('unwarped_file' ,'in_unwarped' ),('shift_out_file' ,'in_file' )])
328
+ ,(inputnode , dwi_split , [('in_file' ,'in_file' )])
329
+ ,(dwi_split , dwi_applyxfm , [('out_files' ,'in_file' )])
330
+ ,(dwi_applyxfm , dwi_merge , [('unwarped_file' ,'in_files' )])
331
+ ,(dwi_merge , outputnode , [('merged_file' ,'epi_corrected' )])
332
+ ])
333
+
334
+ if register_to_ref :
335
+ # register to ref volume
336
+ """ Register magfw to example epi. There are some parameters here that may need to be tweaked. Should probably strip the mag
337
+ Pre-condition: forward warp the mag in order to reg with func. What does mask do here?
338
+ """
339
+ # Select reference volume from EPI (B0 in dMRI and a middle frame in fMRI)
340
+ select_epi = pe .Node ( interface = fsl .utils .ExtractROI (t_size = 1 ), name = "select_epi" )
341
+
342
+
343
+ # fugue -i %s -w %s --loadshift=%s --mask=%s % ( mag_name, magfw_name, vsmmag_name, mask_name ), log ) # Forward Map
344
+ vsm_fwd = pe .Node ( interface = fsl .FUGUE (save_warped = True ), name = "vsm_fwd" )
345
+ vsm_reg = pe .Node ( interface = fsl .FLIRT ( bins = 256 , cost = 'corratio' , dof = 6 , interp = 'trilinear' , searchr_x = [- 10 ,10 ], searchr_y = [- 10 ,10 ], searchr_z = [- 10 ,10 ] ), name = "vsm_registration" )
346
+ # 'flirt -in %s -ref %s -out %s -init %s -applyxfm' % ( vsmmag_name, ref_epi, vsmmag_name, magfw_mat_out )
347
+ vsm_applyxfm = pe .Node ( interface = fsl .ApplyXfm (), name = 'vsm_apply_xfm' )
348
+ # 'flirt -in %s -ref %s -out %s -init %s -applyxfm' % ( mask_name, ref_epi, mask_name, magfw_mat_out )
349
+ msk_applyxfm = pe .Node ( interface = fsl .ApplyXfm (), name = 'msk_apply_xfm' )
350
+
351
+ pipeline .connect ([
352
+ (inputnode , select_epi , [('in_file' ,'in_file' ), ('ref_volume' ,'t_min' )] )
353
+ ,(select_epi , vsm_reg , [('roi_file' ,'reference' )])
354
+ ,(vsm , vsm_fwd , [('shift_out_file' ,'shift_in_file' )])
355
+ ,(mask_mag_dil , vsm_fwd , [('out_file' ,'mask_file' )] )
356
+ ,(inputnode , vsm_fwd , [('fieldmap_mag' , 'in_file' )])
357
+ ,(vsm_fwd , vsm_reg , [('warped_file' ,'in_file' )])
358
+ ,(vsm_reg , msk_applyxfm , [('out_matrix_file' ,'in_matrix_file' )])
359
+ ,(select_epi , msk_applyxfm , [('roi_file' ,'reference' )])
360
+ ,(mask_mag_dil , msk_applyxfm , [('out_file' ,'in_file' ) ])
361
+ ,(vsm_reg , vsm_applyxfm , [('out_matrix_file' ,'in_matrix_file' )])
362
+ ,(select_epi , vsm_applyxfm , [('roi_file' ,'reference' )])
363
+ ,(vsm_mean , vsm_applyxfm , [('out_file' ,'in_file' )])
364
+ ,(msk_applyxfm , dwi_applyxfm , [('out_file' ,'mask_file' )])
365
+ ,(vsm_applyxfm , dwi_applyxfm , [('out_file' ,'shift_in_file' )])
366
+ ])
367
+ else :
368
+ pipeline .connect ([
369
+ (mask_mag_dil , dwi_applyxfm , [('out_file' ,'mask_file' )])
370
+ ,(vsm_mean , dwi_applyxfm , [('out_file' ,'shift_in_file' )])
371
+ ])
372
+
373
+
374
+ return pipeline
375
+
376
+ def _compute_dwelltime ( dwell_time = 0.68 , is_parallel = False , is_reverse_encoding = False ):
377
+ if is_parallel :
378
+ dwell_time *= 0.5
379
+
380
+ if is_reverse_encoding :
381
+ dwell_time *= - 1.0
382
+
383
+ return dwell_time
384
+
385
+ def _prepare_phasediff ( in_file ):
386
+ import nibabel as nib
387
+ import os
388
+ import numpy as np
389
+ img = nib .load ( in_file )
390
+ max_diff = np .max ( img .get_data ().reshape (- 1 ) )
391
+ min_diff = np .min ( img .get_data ().reshape (- 1 ) )
392
+ A = ( 2.0 * np .pi )/ ( max_diff - min_diff )
393
+ B = np .pi - ( A * max_diff )
394
+ diff_norm = img .get_data () * A + B
395
+
396
+ name ,fext = os .path .splitext ( os .path .basename (in_file ) )
397
+ if fext == '.gz' : name ,_ = os .path .splitext (name )
398
+ out_file = os .path .abspath ( './%s_2pi.nii.gz' % name )
399
+ nib .save ( nib .Nifti1Image ( diff_norm , img .get_affine (), img .get_header () ), out_file )
400
+ return out_file
401
+
402
+ def _dilate_mask ( in_file , iterations = 4 ):
403
+ import nibabel as nib
404
+ import scipy .ndimage as ndimage
405
+ import os
406
+ img = nib .load ( in_file )
407
+ img ._data = ndimage .binary_dilation ( img .get_data (), iterations = iterations )
408
+
409
+ name ,fext = os .path .splitext ( os .path .basename (in_file ) )
410
+ if fext == '.gz' : name ,_ = os .path .splitext (name )
411
+ out_file = os .path .abspath ( './%s_dil.nii.gz' % name )
412
+ nib .save ( img , out_file )
413
+ return out_file
414
+
415
+
416
+ def _fill_phase ( in_file ):
417
+ import nibabel as nib
418
+ import os
419
+ import numpy as np
420
+ img = nib .load ( in_file )
421
+ dumb_img = nib .Nifti1Image ( np .zeros ( img .get_shape () ), img .get_affine (), img .get_header () )
422
+ out_nii = nib .funcs .concat_images ( ( img , dumb_img ) )
423
+ name ,fext = os .path .splitext ( os .path .basename (in_file ) )
424
+ if fext == '.gz' : name ,_ = os .path .splitext (name )
425
+ out_file = os .path .abspath ( './%s_phase_unwrapped.nii.gz' % name )
426
+ nib .save ( out_nii , out_file )
427
+ return out_file
428
+
429
+ def _vsm_remove_mean ( in_file , mask_file , in_unwarped ):
430
+ import nibabel as nib
431
+ import os
432
+ import numpy as np
433
+ import numpy .ma as ma
434
+ img = nib .load ( in_file )
435
+ msk = nib .load ( mask_file ).get_data ()
436
+ img_data = img .get_data ()
437
+ img_data [ msk == 0 ] = 0
438
+ vsmmag_masked = ma .masked_values ( img_data .reshape (- 1 ), 0.0 )
439
+ vsmmag_masked = vsmmag_masked - vsmmag_masked .mean ()
440
+ img ._data = vsmmag_masked .reshape ( img .get_shape () )
441
+ name ,fext = os .path .splitext ( os .path .basename (in_file ) )
442
+ if fext == '.gz' : name ,_ = os .path .splitext (name )
443
+ out_file = os .path .abspath ( './%s_demeaned.nii.gz' % name )
444
+ nib .save ( img , out_file )
445
+ return out_file
446
+
447
+ #def _generate_fwdmap( in_file, mask_file, shift_in_file, in_unwarped):
448
+ # import os
449
+ # out_file = os.path.abspath( './fwdmap.nii.gz' )
450
+ # cmd = 'fugue -i %s -w %s --loadshift=%s --mask=%s' % ( in_file, out_file, shift_in_file, mask_file )
451
+ # print " Running:",cmd
452
+ # os.system(cmd)
453
+ # return out_file
454
+
455
+ def _genvsmpath ( in_file ):
456
+ import os
457
+ name ,fext = os .path .splitext ( os .path .basename (in_file ) )
458
+ if fext == '.gz' : name ,_ = os .path .splitext (name )
459
+ out_file = os .path .abspath ( os .path .join ( os .path .dirname (in_file ), '%s_vsm.nii.gz' % name ))
460
+ return out_file
461
+
462
+ def _ms2sec ( val ):
463
+ return val * 1e-3 ;
464
+
465
+ def _yield ( in_file , in_block ):
466
+ return in_file
467
+
468
+ def _split_dwi ( in_file ):
469
+ import nibabel as nib
470
+ import os
471
+ out_files = []
472
+ frames = nib .funcs .four_to_three ( nib .load ( in_file ) )
473
+ name ,fext = os .path .splitext ( os .path .basename (in_file ) )
474
+ if fext == '.gz' : name ,_ = os .path .splitext (name )
475
+ for i ,frame in enumerate (frames ):
476
+ out_file = os .path .abspath ( './%s_%03d.nii.gz' % (name ,i ) )
477
+ nib .save ( frame , out_file )
478
+ out_files .append ( out_file )
479
+ return out_files
0 commit comments