@@ -103,7 +103,6 @@ def __init__(
103
103
104
104
if dwi_file is not None :
105
105
self .affine = dwi_file
106
- self ._dwi_file = dwi_file
107
106
if rasb_file is not None :
108
107
self .gradients = rasb_file
109
108
if self .affine is not None :
@@ -279,18 +278,6 @@ def to_filename(self, filename, filetype="rasb"):
279
278
else :
280
279
raise ValueError ('Unknown filetype "%s"' % filetype )
281
280
282
- # @property
283
- # def gradient_consistency(self):
284
- # """
285
- # Check that b gradients and signal variation in dwi image are consistent with one another.
286
- # """
287
- # if (Path(self._dwi_file).exists() is True) and (self._image_consistency is True):
288
- # return image_gradient_consistency_check(str(self._dwi_file), self.bvecs, self.bvals,
289
- # b0_threshold=self._b0_thres)
290
- # else:
291
- # raise FileNotFoundError(
292
- # "DWI file not found, and is required for checking image-gradient consistency.")
293
-
294
281
295
282
def normalize_gradients (
296
283
bvecs ,
@@ -498,73 +485,32 @@ def bvecs2ras(affine, bvecs, norm=True, bvec_norm_epsilon=0.2):
498
485
return rotated_bvecs
499
486
500
487
501
- def image_gradient_consistency_check (dwi_file , bvecs , bvals , b0_threshold = B0_THRESHOLD ):
488
+ def rasb_dwi_length_check (dwi_file , rasb_file ):
489
+ """Check the number of encoding vectors and number of orientations in the DWI file."""
490
+ return nb .load (dwi_file ).shape [- 1 ] == len (np .loadtxt (rasb_file , skiprows = 1 ))
491
+
492
+
493
+ def b0mask_from_data (dwi_file , mask_file , z_thres = 3.0 ):
502
494
"""
503
- Check that gradient encoding patterns found in the b-values correspond to those
504
- found in the signal intensity variance across volumes of the dwi image.
495
+ Evaluate B0 locations relative to mean signal variation.
496
+
497
+ Standardizes (z-score) the average DWI signal within mask and threshold.
498
+ This is a data-driven way of estimating which volumes in the DWI dataset are
499
+ really encoding *low-b* acquisitions.
505
500
506
501
Parameters
507
502
----------
508
- dwi_file : str
509
- Optionally provide a file path to the diffusion-weighted image series to which the
510
- bvecs/bvals should correspond.
511
- bvecs : m x n 2d array
512
- B-vectors array.
513
- bvals : 1d array
514
- B-values float array.
515
- b0_threshold : float
516
- Gradient threshold below which volumes and vectors are considered B0's.
503
+ dwi_file : :obj:`str`
504
+ File path to the diffusion-weighted image series.
505
+ mask_file : :obj:`str`
506
+ File path to a mask corresponding to the DWI file.
507
+ z_thres : :obj:`float`
508
+ The z-value to consider a volume as a *low-b* orientation.
509
+
517
510
"""
518
- import nibabel as nib
519
- from dipy .core .gradients import gradient_table_from_bvals_bvecs
520
- from sklearn .cluster import MeanShift , estimate_bandwidth
521
-
522
- # Build gradient table object
523
- gtab = gradient_table_from_bvals_bvecs (bvals , bvecs , b0_threshold = b0_threshold )
524
-
525
- # Check that number of image volumes corresponds to the number of gradient encodings.
526
- volume_num = nib .load (dwi_file ).shape [- 1 ]
527
- if not len (bvals ) == volume_num :
528
- raise Exception ("Expected %d total image samples but found %d total gradient encoding values" , volume_num ,
529
- len (bvals ))
530
- # Evaluate B0 locations relative to mean signal variation.
531
- data = np .array (nib .load (dwi_file ).dataobj )
532
- signal_means = []
533
- for vol in range (data .shape [- 1 ]):
534
- signal_means .append (np .mean (data [:, :, :, vol ]))
535
- signal_b0_indices = np .where (signal_means > np .mean (signal_means )+ 3 * np .std (signal_means ))[0 ]
536
-
537
- # Check B0 locations first
538
- if not np .allclose (np .where (gtab .b0s_mask == True )[0 ], signal_b0_indices ):
539
- raise UserWarning ('B0 indices in vectors do not correspond to relative high-signal contrast volumes '
540
- 'detected in dwi image.' )
541
-
542
- # Next check number of unique b encodings (i.e. shells) and their indices
543
- X = np .array (signal_means ).reshape (- 1 , 1 )
544
- ms = MeanShift (bandwidth = estimate_bandwidth (X , quantile = 0.1 ), bin_seeding = True )
545
- ms .fit (X )
546
- labs , idx = np .unique (ms .labels_ , return_index = True )
547
- ix_img = []
548
- i = - 1
549
- for val in range (len (ms .labels_ )):
550
- if val in np .sort (idx [::- 1 ]):
551
- i = i + 1
552
- ix_img .append (labs [i ])
553
- ix_img = np .array (ix_img )
554
-
555
- ix_vec = []
556
- i = - 1
557
- for val in range (len (bvals )):
558
- if bvals [val ] != bvals [val - 1 ]:
559
- i = i + 1
560
- ix_vec .append (i )
561
- ix_vec = np .array (ix_vec )
562
-
563
- if len (ms .cluster_centers_ ) != len (np .unique (bvals )):
564
- raise UserWarning ('Number of unique b-values does not correspond to number of unique signal gradient '
565
- 'encoding intensities in the dwi image.' )
566
-
567
- if np .all (np .isclose (ix_img , ix_vec )) is True :
568
- raise UserWarning ('Positions of b-value B0\' s and shell(s) do not correspond to signal intensity '
569
- 'fluctuation patterns in the dwi image.' )
570
- return
511
+ data = np .asanyarray (nb .load (dwi_file ).dataobj )
512
+ mask = np .asanyarray (nb .load (mask_file ).dataobj ) > 0.5
513
+ signal_means = np .median (data [mask , np .newaxis ], axis = 0 )
514
+ zscored_means = signal_means - signal_means .mean ()
515
+ zscored_means /= zscored_means .std ()
516
+ return zscored_means > z_thres
0 commit comments