Skip to content

Commit afe9c86

Browse files
committed
ENH: Add auto parc and annot parsing
1 parent 0129278 commit afe9c86

File tree

3 files changed

+157
-106
lines changed

3 files changed

+157
-106
lines changed

examples/plot_morphometry.py

+6-4
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,26 @@
1111

1212
from surfer import Brain
1313

14-
brain = Brain("fsaverage", "lh", "pial",
14+
brain = Brain("fsaverage", "both", "pial", views="frontal",
1515
config_opts=dict(background="dimgray"))
1616

1717
"""
1818
Because the morphometry files generated by
1919
recon-all live in a predicatble location,
2020
all you need to call the add_morphometry
2121
method with is the name of the measure you want.
22-
Here, we'll look at cortical curvatuve values.
22+
Here, we'll look at cortical curvatuve values,
23+
and plot them for both hemispheres.
2324
"""
2425
brain.add_morphometry("curv")
2526

2627
"""
2728
Each of the possible values is displayed in an
2829
appropriate full-color map, but you can also
29-
display in grayscale.
30+
display in grayscale. Here we only plot the
31+
left hemisphere.
3032
"""
31-
brain.add_morphometry("sulc", grayscale=True)
33+
brain.add_morphometry("sulc", hemi='lh', grayscale=True)
3234

3335
"""
3436
The Brain object can only hold one morphometry

examples/plot_parcellation.py

+9-4
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@
1313

1414

1515
subject_id = 'fsaverage'
16-
hemi = 'lh'
16+
hemi = 'both'
1717
surface = 'inflated'
18+
view = 'frontal'
19+
1820

1921
"""
2022
Bring up the visualization
2123
"""
22-
brain = Brain(subject_id, hemi, surface,
24+
brain = Brain(subject_id, hemi, surface, views=view,
2325
config_opts={"cortex": "bone",
2426
"background": "ivory"})
2527

@@ -37,8 +39,11 @@
3739

3840
"""
3941
You may also provide a full path to an annotation file
40-
at an arbitray location on the disc.
42+
at an arbitray location on the disc. You can also
43+
plot things separately for the left and right hemispheres.
4144
"""
4245
subjects_dir = os.environ["SUBJECTS_DIR"]
4346
annot_path = pjoin(subjects_dir, subject_id, "label", "lh.aparc.annot")
44-
brain.add_annotation(annot_path)
47+
brain.add_annotation(annot_path, hemi='lh', borders=False)
48+
annot_path = pjoin(subjects_dir, subject_id, "label", "rh.aparc.a2009s.annot")
49+
brain.add_annotation(annot_path, hemi='rh', remove_existing=False)

surfer/viz.py

+142-98
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)