@@ -266,6 +266,74 @@ def run_dmriprep_pe(dwi_file, dwi_file_AP, dwi_file_PA, bvec_file, bval_file,
266
266
eddy .inputs .repol = True
267
267
eddy .inputs .niter = 1 # TODO: make this a parameter to the function with default 5
268
268
269
+ def id_outliers_fn (outlier_report , threshold , dwi_file ):
270
+ """Get list of scans that exceed threshold for number of outliers
271
+
272
+ Parameters
273
+ ----------
274
+ outlier_report: string
275
+ Path to the fsl_eddy outlier report
276
+
277
+ threshold: int or float
278
+ If threshold is an int, it is treated as number of allowed outlier
279
+ slices. If threshold is a float between 0 and 1 (exclusive), it is
280
+ treated the fraction of allowed outlier slices before we drop the
281
+ whole volume. Float param in not yet implemented
282
+
283
+ dwi_file: string
284
+ Path to nii dwi file to determine total number of slices
285
+
286
+ Returns
287
+ -------
288
+ drop_scans: numpy.ndarray
289
+ List of scan indices to drop
290
+ """
291
+ import nibabel as nib
292
+ import numpy as np
293
+ import os .path as op
294
+ import parse
295
+
296
+ with open (op .abspath (outlier_report ), 'r' ) as fp :
297
+ lines = fp .readlines ()
298
+
299
+ p = parse .compile (
300
+ "Slice {slice:d} in scan {scan:d} is an outlier with "
301
+ "mean {mean_sd:f} standard deviations off, and mean "
302
+ "squared {mean_sq_sd:f} standard deviations off."
303
+ )
304
+
305
+ outliers = [p .parse (l ).named for l in lines ]
306
+ scans = {d ['scan' ] for d in outliers }
307
+
308
+ def num_outliers (scan , outliers ):
309
+ return len ([d for d in outliers if d ['scan' ] == scan ])
310
+
311
+ if 0 < threshold < 1 :
312
+ threshold *= nib .load (dwi_file ).shape [2 ]
313
+
314
+ drop_scans = np .array ([
315
+ s for s in scans
316
+ if num_outliers (s , outliers ) > threshold
317
+ ])
318
+
319
+ outpath = op .abspath ("dropped_scans.txt" )
320
+ np .savetxt (outpath , drop_scans , fmt = "%d" )
321
+
322
+ return drop_scans , outpath
323
+
324
+ id_outliers_node = pe .Node (niu .Function (
325
+ input_names = ["outlier_report" , "threshold" , "dwi_file" ],
326
+ output_names = ["drop_scans" , "outpath" ],
327
+ function = id_outliers_fn ),
328
+ name = "id_outliers_node"
329
+ )
330
+
331
+ # TODO: make this a parameter to the function
332
+ id_outliers_node .inputs .threshold = 0.02
333
+ id_outliers_node .inputs .dwi_file = dwi_file
334
+ wf .connect (prep , "fsl_eddy.out_outlier_report" ,
335
+ id_outliers_node , "outlier_report" )
336
+
269
337
merge = pe .Node (fsl .Merge (dimension = 't' ), name = "mergeAPPA" )
270
338
merge .inputs .in_files = [dwi_file_AP , dwi_file_PA ]
271
339
wf .connect (merge , 'merged_file' , prep , 'inputnode.alt_file' )
@@ -281,10 +349,76 @@ def run_dmriprep_pe(dwi_file, dwi_file_AP, dwi_file_PA, bvec_file, bval_file,
281
349
bbreg .inputs .subject_id = 'freesurfer' # bids_sub_name
282
350
wf .connect (fslroi , "roi_file" , bbreg , "source_file" )
283
351
352
+ def drop_outliers_fn (in_file , in_bval , in_bvec , drop_scans ):
353
+ """Drop outlier volumes from dwi file
354
+
355
+ Parameters
356
+ ----------
357
+ in_file: string
358
+ Path to nii dwi file to drop outlier volumes from
359
+
360
+ in_bval: string
361
+ Path to bval file to drop outlier volumes from
362
+
363
+ in_bvec: string
364
+ Path to bvec file to drop outlier volumes from
365
+
366
+ drop_scans: numpy.ndarray
367
+ List of scan indices to drop
368
+
369
+ Returns
370
+ -------
371
+ out_file: string
372
+ Path to "thinned" output dwi file
373
+
374
+ out_bval: string
375
+ Path to "thinned" output bval file
376
+
377
+ out_bvec: string
378
+ Path to "thinned" output bvec file
379
+ """
380
+ import nibabel as nib
381
+ import numpy as np
382
+ import os .path as op
383
+
384
+ img = nib .load (op .abspath (in_file ))
385
+ img_data = img .get_fdata ()
386
+ img_data_thinned = np .delete (img_data , drop_scans , axis = 3 )
387
+ img_thinned = nib .Nifti1Image (img_data_thinned .astype (np .float64 ), img .affine )
388
+
389
+ root , ext1 = op .splitext (in_file )
390
+ root , ext0 = op .splitext (root )
391
+ out_file = '' .join ([root + "_thinned" , ext0 , ext1 ])
392
+ nib .save (img_thinned , op .abspath (out_file ))
393
+
394
+ bval = np .loadtxt (in_bval )
395
+ bval_thinned = np .delete (bval , drop_scans , axis = 0 )
396
+ out_bval = '_thinned' .join (op .splitext (in_bval ))
397
+ np .savetxt (out_bval , bval_thinned )
398
+
399
+ bvec = np .loadtxt (in_bvec )
400
+ bvec_thinned = np .delete (bvec , drop_scans , axis = 1 )
401
+ out_bvec = '_thinned' .join (op .splitext (in_bvec ))
402
+ np .savetxt (out_bvec , bvec_thinned )
403
+
404
+ return out_file , out_bval , out_bvec
405
+
406
+ drop_outliers_node = pe .Node (niu .Function (
407
+ input_names = ["in_file" , "in_bval" , "in_bvec" , "drop_scans" ],
408
+ output_names = ["out_file" , "out_bval" , "out_bvec" ],
409
+ function = drop_outliers_fn ),
410
+ name = "drop_outliers_node"
411
+ )
412
+
413
+ wf .connect (id_outliers_node , "drop_scans" , drop_outliers_node , "drop_scans" )
414
+ wf .connect (prep , "outputnode.out_file" , drop_outliers_node , "in_file" )
415
+ drop_outliers_node .inputs .in_bval = bval_file
416
+ wf .connect (prep , "outputnode.out_bvec" , drop_outliers_node , "in_bvec" )
417
+
284
418
get_tensor = pe .Node (DTI (), name = "dipy_tensor" )
285
- wf .connect (prep , "outputnode. out_file" , get_tensor , "in_file" )
286
- get_tensor . inputs . in_bval = bval_file
287
- wf .connect (prep , "outputnode. out_bvec" , get_tensor , "in_bvec" )
419
+ wf .connect (drop_outliers_node , "out_file" , get_tensor , "in_file" )
420
+ wf . connect ( drop_outliers_node , "out_bval" , get_tensor , "in_bval" )
421
+ wf .connect (drop_outliers_node , "out_bvec" , get_tensor , "in_bvec" )
288
422
289
423
scale_tensor = pe .Node (name = 'scale_tensor' , interface = fsl .BinaryMaths ())
290
424
scale_tensor .inputs .operation = 'mul'
@@ -371,6 +505,10 @@ def get_orig(subjects_dir, sub):
371
505
("derivatives/dmriprep" , "derivatives/{}/dmriprep" .format (bids_sub_name ))
372
506
]
373
507
508
+ wf .connect (drop_outliers_node , "out_file" , datasink , "dmriprep.dwi.@thinned" )
509
+ wf .connect (drop_outliers_node , "out_bval" , datasink , "dmriprep.dwi.@bval_thinned" )
510
+ wf .connect (drop_outliers_node , "out_bvec" , datasink , "dmriprep.dwi.@bvec_thinned" )
511
+
374
512
wf .connect (prep , "outputnode.out_file" , datasink , "dmriprep.dwi.@corrected" )
375
513
wf .connect (prep , "outputnode.out_bvec" , datasink , "dmriprep.dwi.@rotated" )
376
514
wf .connect (prep , "fsl_eddy.out_parameter" ,
@@ -385,6 +523,8 @@ def get_orig(subjects_dir, sub):
385
523
wf .connect (prep , "fsl_eddy.out_shell_alignment_parameters" ,
386
524
datasink , "dmriprep.qc.@eddyparamsshellalign" )
387
525
526
+ wf .connect (id_outliers_node , "outpath" , datasink , "dmriprep.qc.@droppedscans" )
527
+
388
528
wf .connect (get_tensor , "out_file" , datasink , "dmriprep.dti.@tensor" )
389
529
wf .connect (get_tensor , "fa_file" , datasink , "dmriprep.dti.@fa" )
390
530
wf .connect (get_tensor , "md_file" , datasink , "dmriprep.dti.@md" )
0 commit comments