@@ -635,7 +635,7 @@ def data(self):
635635 return data
636636
637637 def _check_hemi (self , hemi ):
638- """Check for safe hemi input"""
638+ """Check for safe single- hemi input, returns str """
639639 if hemi is None :
640640 if self ._hemi not in ['lh' , 'rh' ]:
641641 raise ValueError ('hemi must not be None when both '
@@ -647,6 +647,20 @@ def _check_hemi(self, hemi):
647647 raise ValueError ('hemi must be either "lh" or "rh"' + extra )
648648 return hemi
649649
650+ def _check_hemis (self , hemi ):
651+ """Check for safe dual or single-hemi input, returns list"""
652+ if hemi is None :
653+ if self ._hemi not in ['lh' , 'rh' ]:
654+ hemi = ['lh' , 'rh' ]
655+ else :
656+ hemi = [self ._hemi ]
657+ elif hemi not in ['lh' , 'rh' ]:
658+ extra = ' or None' if self ._hemi in ['lh' , 'rh' ] else ''
659+ raise ValueError ('hemi must be either "lh" or "rh"' + extra )
660+ else :
661+ hemi = [hemi ]
662+ return hemi
663+
650664 def _read_scalar_data (self , source , hemi , name = None , cast = True ):
651665 """Load in scalar data from an image stored in a file or an array
652666
@@ -919,7 +933,8 @@ def add_data(self, array, min=None, max=None, thresh=None,
919933 data ['orig_ctable' ] = ct
920934 self .data_dict [hemi ] = data
921935
922- def add_annotation (self , annot , borders = True , alpha = 1 , hemi = None ):
936+ def add_annotation (self , annot , borders = True , alpha = 1 , hemi = None ,
937+ remove_existing = True ):
923938 """Add an annotation file.
924939
925940 Parameters
@@ -932,62 +947,83 @@ def add_annotation(self, annot, borders=True, alpha=1, hemi=None):
932947 Alpha level to control opacity
933948 hemi : str | None
934949 If None, it is assumed to belong to the hemipshere being
935- shown. If two hemispheres are being shown, an error will
936- be thrown.
950+ shown. If two hemispheres are being shown, data must exist
951+ for both hemispheres.
952+ remove_existing : bool
953+ If True (default), remove old annotations.
937954 """
938- hemi = self ._check_hemi (hemi )
939-
940- # Get rid of any old annots
941- for a in self .annot_list :
942- a ['surface' ].remove ()
955+ hemis = self ._check_hemis (hemi )
943956
944957 # Figure out where the data is coming from
945958 if os .path .isfile (annot ):
946959 filepath = annot
947- annot = os .path .basename (filepath ).split ('.' )[1 ]
960+ path = os .path .split (filepath )[0 ]
961+ file_hemi , annot = os .path .basename (filepath ).split ('.' )[:2 ]
962+ if len (hemis ) > 1 :
963+ if annot [:2 ] == 'lh.' :
964+ filepaths = [filepath , pjoin (path , 'rh' + annot [2 :])]
965+ elif annot [:2 ] == 'rh.' :
966+ filepaths = [pjoin (path , 'lh' + annot [2 :], filepath )]
967+ else :
968+ raise RuntimeError ('To add both hemispheres '
969+ 'simultaneously, filename must '
970+ 'begin with "lh." or "rh."' )
971+ else :
972+ filepaths = [filepath ]
948973 else :
949- filepath = pjoin (self .subjects_dir ,
950- self .subject_id ,
951- 'label' ,
952- "." .join ([hemi , annot , 'annot' ]))
953- if not os .path .exists (filepath ):
954- raise ValueError ('Annotation file %s does not exist'
955- % filepath )
956-
957- # Read in the data
958- labels , cmap , _ = nib .freesurfer .read_annot (filepath , orig_ids = True )
959-
960- # Maybe zero-out the non-border vertices
961- if borders :
962- n_vertices = labels .size
963- edges = utils .mesh_edges (self .geo [hemi ].faces )
964- border_edges = labels [edges .row ] != labels [edges .col ]
965- show = np .zeros (n_vertices , dtype = np .int )
966- show [np .unique (edges .row [border_edges ])] = 1
967- labels *= show
968-
969- # Handle null labels properly
970- # (tksurfer doesn't use the alpha channel, so sometimes this
971- # is set weirdly. For our purposes, it should always be 0.
972- # Unless this sometimes causes problems?
973- cmap [np .where (cmap [:, 4 ] == 0 ), 3 ] = 0
974- if np .any (labels == 0 ) and not np .any (cmap [:, - 1 ] == 0 ):
975- cmap = np .vstack ((cmap , np .zeros (5 , int )))
976-
977- # Set label ids sensibly
978- ord = np .argsort (cmap [:, - 1 ])
979- ids = ord [np .searchsorted (cmap [ord , - 1 ], labels )]
980- cmap = cmap [:, :4 ]
981-
982- # Set the alpha level
983- alpha_vec = cmap [:, 3 ]
984- alpha_vec [alpha_vec > 0 ] = alpha * 255
985-
986- al = []
974+ filepaths = []
975+ for hemi in hemis :
976+ filepath = pjoin (self .subjects_dir ,
977+ self .subject_id ,
978+ 'label' ,
979+ "." .join ([hemi , annot , 'annot' ]))
980+ if not os .path .exists (filepath ):
981+ raise ValueError ('Annotation file %s does not exist'
982+ % filepath )
983+ filepaths += [filepath ]
984+
987985 views = self ._toggle_render (False )
988- for brain in self ._brain_list :
989- if brain ['hemi' ] == hemi :
990- al .append (brain ['brain' ].add_annotation (annot , ids , cmap ))
986+ if remove_existing is True :
987+ # Get rid of any old annots
988+ for a in self .annot_list :
989+ a ['surface' ].remove ()
990+ self .annot_list = []
991+
992+ al = self .annot_list
993+ for hemi , filepath in zip (hemis , filepaths ):
994+ # Read in the data
995+ labels , cmap , _ = nib .freesurfer .read_annot (filepath ,
996+ orig_ids = True )
997+
998+ # Maybe zero-out the non-border vertices
999+ if borders :
1000+ n_vertices = labels .size
1001+ edges = utils .mesh_edges (self .geo [hemi ].faces )
1002+ border_edges = labels [edges .row ] != labels [edges .col ]
1003+ show = np .zeros (n_vertices , dtype = np .int )
1004+ show [np .unique (edges .row [border_edges ])] = 1
1005+ labels *= show
1006+
1007+ # Handle null labels properly
1008+ # (tksurfer doesn't use the alpha channel, so sometimes this
1009+ # is set weirdly. For our purposes, it should always be 0.
1010+ # Unless this sometimes causes problems?
1011+ cmap [np .where (cmap [:, 4 ] == 0 ), 3 ] = 0
1012+ if np .any (labels == 0 ) and not np .any (cmap [:, - 1 ] == 0 ):
1013+ cmap = np .vstack ((cmap , np .zeros (5 , int )))
1014+
1015+ # Set label ids sensibly
1016+ ord = np .argsort (cmap [:, - 1 ])
1017+ ids = ord [np .searchsorted (cmap [ord , - 1 ], labels )]
1018+ cmap = cmap [:, :4 ]
1019+
1020+ # Set the alpha level
1021+ alpha_vec = cmap [:, 3 ]
1022+ alpha_vec [alpha_vec > 0 ] = alpha * 255
1023+
1024+ for brain in self ._brain_list :
1025+ if brain ['hemi' ] == hemi :
1026+ al .append (brain ['brain' ].add_annotation (annot , ids , cmap ))
9911027 self .annot_list = al
9921028 self ._toggle_render (True , views )
9931029
@@ -1117,7 +1153,8 @@ def remove_labels(self, labels=None, hemi=None):
11171153 for ll in label :
11181154 ll .remove ()
11191155
1120- def add_morphometry (self , measure , grayscale = False , hemi = None ):
1156+ def add_morphometry (self , measure , grayscale = False , hemi = None ,
1157+ remove_existing = True ):
11211158 """Add a morphometry overlay to the image.
11221159
11231160 Parameters
@@ -1128,57 +1165,64 @@ def add_morphometry(self, measure, grayscale=False, hemi=None):
11281165 whether to load the overlay with a grayscale colormap
11291166 hemi : str | None
11301167 If None, it is assumed to belong to the hemipshere being
1131- shown. If two hemispheres are being shown, an error will
1132- be thrown.
1168+ shown. If two hemispheres are being shown, data must exist
1169+ for both hemispheres.
1170+ remove_existing : bool
1171+ If True (default), remove old annotations.
11331172 """
1134- hemi = self ._check_hemi (hemi )
1173+ hemis = self ._check_hemis (hemi )
1174+ morph_files = []
1175+ for hemi in hemis :
1176+ # Find the source data
1177+ surf_dir = pjoin (self .subjects_dir , self .subject_id , 'surf' )
1178+ morph_file = pjoin (surf_dir , '.' .join ([hemi , measure ]))
1179+ if not os .path .exists (morph_file ):
1180+ raise ValueError (
1181+ 'Could not find %s in subject directory' % morph_file )
1182+ morph_files += [morph_file ]
11351183
1136- # Find the source data
1137- surf_dir = pjoin (self .subjects_dir , self .subject_id , 'surf' )
1138- morph_file = pjoin (surf_dir , '.' .join ([hemi , measure ]))
1139- if not os .path .exists (morph_file ):
1140- raise ValueError (
1141- 'Could not find %s in subject directory' % morph_file )
1142-
1143- # Preset colormaps
1144- cmap_dict = dict (area = "pink" ,
1145- curv = "RdBu" ,
1146- jacobian_white = "pink" ,
1147- sulc = "RdBu" ,
1148- thickness = "pink" )
1149-
1150- # Get rid of any old overlays
1151- for m in self .morphometry_list :
1152- m ['surface' ].remove ()
1153- m ['colorbar' ].visible = False
1154-
1155- # Read in the morphometric data
1156- morph_data = nib .freesurfer .read_morph_data (morph_file )
1157-
1158- # Get a cortex mask for robust range
1159- self .geo [hemi ].load_label ("cortex" )
1160- ctx_idx = self .geo [hemi ].labels ["cortex" ]
1161-
1162- # Get the display range
1163- if measure == "thickness" :
1164- min , max = 1 , 4
1165- else :
1166- min , max = stats .describe (morph_data [ctx_idx ])[1 ]
1184+ views = self ._toggle_render (False )
1185+ if remove_existing is True :
1186+ # Get rid of any old overlays
1187+ for m in self .morphometry_list :
1188+ m ['surface' ].remove ()
1189+ m ['colorbar' ].visible = False
1190+ self .morphometry_list = []
1191+ ml = self .morphometry_list
1192+ for hemi , morph_file in zip (hemis , morph_files ):
1193+ # Preset colormaps
1194+ cmap_dict = dict (area = "pink" ,
1195+ curv = "RdBu" ,
1196+ jacobian_white = "pink" ,
1197+ sulc = "RdBu" ,
1198+ thickness = "pink" )
1199+
1200+ # Read in the morphometric data
1201+ morph_data = nib .freesurfer .read_morph_data (morph_file )
1202+
1203+ # Get a cortex mask for robust range
1204+ self .geo [hemi ].load_label ("cortex" )
1205+ ctx_idx = self .geo [hemi ].labels ["cortex" ]
1206+
1207+ # Get the display range
1208+ if measure == "thickness" :
1209+ min , max = 1 , 4
1210+ else :
1211+ min , max = stats .describe (morph_data [ctx_idx ])[1 ]
11671212
1168- # Set up the Mayavi pipeline
1169- morph_data = _prepare_data (morph_data )
1213+ # Set up the Mayavi pipeline
1214+ morph_data = _prepare_data (morph_data )
11701215
1171- if grayscale :
1172- colormap = "gray"
1173- else :
1174- colormap = cmap_dict [measure ]
1216+ if grayscale :
1217+ colormap = "gray"
1218+ else :
1219+ colormap = cmap_dict [measure ]
11751220
1176- views = self ._toggle_render (False )
1177- ml = []
1178- for brain in self ._brain_list :
1179- if brain ['hemi' ] == hemi :
1180- ml .append (brain ['brain' ].add_morphometry (morph_data , colormap ,
1181- measure , min , max ))
1221+ for brain in self ._brain_list :
1222+ if brain ['hemi' ] == hemi :
1223+ ml .append (brain ['brain' ].add_morphometry (morph_data ,
1224+ colormap , measure ,
1225+ min , max ))
11821226 self .morphometry_list = ml
11831227 self ._toggle_render (True , views )
11841228
0 commit comments