@@ -734,23 +734,21 @@ def get_bvals_bvecs(self):
734
734
n_slices , n_vols = self .get_data_shape ()[- 2 :]
735
735
bvals = self .image_defs ['diffusion_b_factor' ][reorder ].reshape (
736
736
(n_slices , n_vols ), order = 'F' )
737
- if not self .strict_sort :
738
- # All bvals within volume should be the same
739
- assert not np .any (np .diff (bvals , axis = 0 ))
737
+ # All bvals within volume should be the same
738
+ assert not np .any (np .diff (bvals , axis = 0 ))
740
739
bvals = bvals [0 ]
741
740
bvecs = self .image_defs ['diffusion' ][reorder ].reshape (
742
741
(n_slices , n_vols , 3 ), order = 'F' )
743
- if not self .strict_sort :
744
- # All 3 values of bvecs should be same within volume
745
- assert not np .any (np .diff (bvecs , axis = 0 ))
742
+ # All 3 values of bvecs should be same within volume
743
+ assert not np .any (np .diff (bvecs , axis = 0 ))
746
744
bvecs = bvecs [0 ]
747
745
# rotate bvecs to match stored image orientation
748
746
permute_to_psl = ACQ_TO_PSL [self .get_slice_orientation ()]
749
747
bvecs = apply_affine (np .linalg .inv (permute_to_psl ), bvecs )
750
748
return bvals , bvecs
751
749
752
750
def get_def (self , name ):
753
- """ Return a single image definition field. """
751
+ """Return a single image definition field (or None if missing) """
754
752
idef = self .image_defs
755
753
return idef [name ] if name in idef .dtype .names else None
756
754
@@ -1006,33 +1004,48 @@ def get_rec_shape(self):
1006
1004
inplane_shape = tuple (self ._get_unique_image_prop ('recon resolution' ))
1007
1005
return inplane_shape + (len (self .image_defs ),)
1008
1006
1009
- def _strict_sort_keys (self ):
1007
+ def _strict_sort_order (self ):
1010
1008
""" Determine the sort order based on several image definition fields.
1011
1009
1012
- If the sort keys are not unique for each volume, we calculate a volume
1013
- number by looking for repeating slice numbers
1014
- (see :func:`vol_numbers`). This may occur for diffusion scans from
1015
- older V4 .PAR format, where diffusion direction info is not stored.
1010
+ The fields taken into consideration, if present, are (in order from
1011
+ slowest to fastest variation after sorting):
1012
+
1013
+ - image_defs['image_type_mr'] # Re, Im, Mag, Phase
1014
+ - image_defs['dynamic scan number'] # repetition
1015
+ - image_defs['label type'] # ASL tag/control
1016
+ - image_defs['diffusion b value number'] # diffusion b value
1017
+ - image_defs['gradient orientation number'] # diffusion directoin
1018
+ - image_defs['cardiac phase number'] # cardiac phase
1019
+ - image_defs['echo number'] # echo
1020
+ - image_defs['slice number'] # slice
1016
1021
1017
1022
Data sorting is done in two stages:
1018
- - run an initial sort using several keys of interest
1019
- - call `vol_is_full` to identify potentially missing volumes
1020
- and add the result to the list of sort keys
1021
- """
1022
- # Sort based on a larger number of keys. This is more complicated
1023
- # but works for .PAR files that get missorted by the above method
1024
- slice_nos = self .image_defs ['slice number' ]
1025
- dynamics = self .image_defs ['dynamic scan number' ]
1026
- phases = self .image_defs ['cardiac phase number' ]
1027
- echos = self .image_defs ['echo number' ]
1028
- image_type = self .image_defs ['image_type_mr' ]
1029
1023
1030
- # try adding keys only present in a subset of .PAR files
1031
- idefs = self .image_defs
1032
- asl_keys = (idefs ['label type' ], ) if 'label type' in \
1033
- idefs .dtype .names else ()
1024
+ 1. an initial sort using the keys described above
1025
+ 2. a resort after generating two additional sort keys:
1034
1026
1035
- if not self .general_info ['diffusion' ] == 0 :
1027
+ * a key to assign unique volume numbers to any volumes that
1028
+ didn't have a unique sort based on the keys above
1029
+ (see :func:`vol_numbers`).
1030
+ * a sort key based on `vol_is_full` to identify truncated
1031
+ volumes
1032
+
1033
+ A case where the initial sort may not create a unique label for each
1034
+ volume is diffusion scans acquired in the older V4 .PAR format, where
1035
+ diffusion direction info is not available.
1036
+ """
1037
+ # sort keys present in all supported .PAR versions
1038
+ idefs = self .image_defs
1039
+ slice_nos = idefs ['slice number' ]
1040
+ dynamics = idefs ['dynamic scan number' ]
1041
+ phases = idefs ['cardiac phase number' ]
1042
+ echos = idefs ['echo number' ]
1043
+ image_type = idefs ['image_type_mr' ]
1044
+
1045
+ # sort keys only present in a subset of .PAR files
1046
+ asl_keys = ((idefs ['label type' ], ) if 'label type' in
1047
+ idefs .dtype .names else ())
1048
+ if self .general_info ['diffusion' ] != 0 :
1036
1049
bvals = self .get_def ('diffusion b value number' )
1037
1050
if bvals is None :
1038
1051
bvals = self .get_def ('diffusion_b_factor' )
@@ -1045,30 +1058,34 @@ def _strict_sort_keys(self):
1045
1058
else :
1046
1059
diffusion_keys = ()
1047
1060
1048
- # Define the desired sort order (last key is highest precedence)
1061
+ # initial sort (last key is highest precedence)
1049
1062
keys = (slice_nos , echos , phases ) + \
1050
1063
diffusion_keys + asl_keys + (dynamics , image_type )
1051
-
1052
1064
initial_sort_order = np .lexsort (keys )
1065
+
1066
+ # sequentially number the volumes based on the initial sort
1053
1067
vol_nos = vol_numbers (slice_nos [initial_sort_order ])
1068
+ # identify truncated volumes
1054
1069
is_full = vol_is_full (slice_nos [initial_sort_order ],
1055
1070
self .general_info ['max_slices' ])
1056
1071
1057
- # have to "unsort" is_full and volumes to match the other sort keys
1058
- unsort_indices = np .argsort (initial_sort_order )
1059
- is_full = is_full [unsort_indices ]
1060
- vol_nos = np .asarray (vol_nos )[unsort_indices ]
1061
-
1062
- # final set of sort keys
1063
- return (keys [0 ], vol_nos ) + keys [1 :] + (np .logical_not (is_full ), )
1064
-
1065
- def get_sorted_slice_indices (self ):
1066
- """Return indices to sort (and maybe discard) slices in REC file.
1072
+ # second stage of sorting
1073
+ return initial_sort_order [np .lexsort ((vol_nos , is_full ))]
1067
1074
1075
+ def _lax_sort_order (self ):
1076
+ """
1068
1077
Sorts by (fast to slow): slice number, volume number.
1069
1078
1070
1079
We calculate volume number by looking for repeating slice numbers (see
1071
1080
:func:`vol_numbers`).
1081
+ """
1082
+ slice_nos = self .image_defs ['slice number' ]
1083
+ is_full = vol_is_full (slice_nos , self .general_info ['max_slices' ])
1084
+ keys = (slice_nos , vol_numbers (slice_nos ), np .logical_not (is_full ))
1085
+ return np .lexsort (keys )
1086
+
1087
+ def get_sorted_slice_indices (self ):
1088
+ """Return indices to sort (and maybe discard) slices in REC file.
1072
1089
1073
1090
If the recording is truncated, the returned indices take care of
1074
1091
discarding any slice indices from incomplete volumes.
@@ -1086,13 +1103,10 @@ def get_sorted_slice_indices(self):
1086
1103
``self.image_defs``.
1087
1104
"""
1088
1105
if not self .strict_sort :
1089
- slice_nos = self .image_defs ['slice number' ]
1090
- is_full = vol_is_full (slice_nos , self .general_info ['max_slices' ])
1091
- keys = (slice_nos , vol_numbers (slice_nos ), np .logical_not (is_full ))
1106
+ sort_order = self ._lax_sort_order ()
1092
1107
else :
1093
- keys = self ._strict_sort_keys ()
1108
+ sort_order = self ._strict_sort_order ()
1094
1109
1095
- sort_order = np .lexsort (keys )
1096
1110
# Figure out how many we need to remove from the end, and trim them.
1097
1111
# Based on our sorting, they should always be last.
1098
1112
n_used = np .prod (self .get_data_shape ()[2 :])
0 commit comments