Skip to content

Commit e2cd50f

Browse files
committed
[REF] Refactor of more nnsurf functions
1 parent a6ea31d commit e2cd50f

File tree

3 files changed

+111
-68
lines changed

3 files changed

+111
-68
lines changed

netneurotools/datasets/fetchers.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
Functions for fetching datasets from the internet
44
"""
55

6+
from collections import namedtuple
67
import itertools
78
import json
89
import os.path as op
@@ -15,6 +16,8 @@
1516
from .utils import _get_data_dir, _get_dataset_info
1617
from ..utils import check_fs_subjid
1718

19+
ANNOT = namedtuple('Surface', ('lh', 'rh'))
20+
1821

1922
def fetch_cammoun2012(version='MNI152NLin2009aSym', data_dir=None, url=None,
2023
resume=True, verbose=1):
@@ -134,7 +137,7 @@ def fetch_cammoun2012(version='MNI152NLin2009aSym', data_dir=None, url=None,
134137
if version == 'MNI152NLin2009aSym':
135138
keys += ['info']
136139
elif version in ('fslr32k', 'fsaverage', 'fsaverage5', 'fsaverage6'):
137-
data = [data[i:i + 2] for i in range(0, len(data), 2)]
140+
data = [ANNOT(*data[i:i + 2]) for i in range(0, len(data), 2)]
138141
else:
139142
data = [data[::2][i:i + 2] for i in range(0, len(data) // 2, 2)]
140143
# deal with the fact that last scale is split into three files :sigh:
@@ -209,7 +212,7 @@ def fetch_conte69(data_dir=None, url=None, resume=True, verbose=1):
209212
data[-1] = json.load(src)
210213

211214
# bundle hemispheres together
212-
data = [data[:-1][i:i + 2] for i in range(0, 6, 2)] + [data[-1]]
215+
data = [ANNOT(*data[:-1][i:i + 2]) for i in range(0, 6, 2)] + [data[-1]]
213216

214217
return Bunch(**dict(zip(keys + ['info'], data)))
215218

@@ -336,7 +339,7 @@ def fetch_fsaverage(version='fsaverage', data_dir=None, url=None, resume=True,
336339
files=[(op.join(dataset_name, f), url, opts)
337340
for f in filenames])
338341

339-
data = [data[i:i + 2] for i in range(0, len(keys) * 2, 2)]
342+
data = [ANNOT(*data[i:i + 2]) for i in range(0, len(keys) * 2, 2)]
340343

341344
return Bunch(**dict(zip(keys, data)))
342345

@@ -561,7 +564,7 @@ def fetch_schaefer2018(version='fsaverage', data_dir=None, url=None,
561564
data = _fetch_files(data_dir, files=files, resume=resume, verbose=verbose)
562565

563566
if suffix == 'annot':
564-
data = [data[i:i + 2] for i in range(0, len(keys) * 2, 2)]
567+
data = [ANNOT(*data[i:i + 2]) for i in range(0, len(keys) * 2, 2)]
565568

566569
return Bunch(**dict(zip(keys, data)))
567570

netneurotools/freesurfer.py

Lines changed: 103 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -264,8 +264,8 @@ def parcels_to_vertices(data, *, lhannot, rhannot, drop=None):
264264
drop : list, optional
265265
Specifies regions in {lh,rh}annot that are not present in `data`. NaNs
266266
will be inserted in place of the these regions in the returned data. If
267-
not specified, 'unknown' and 'corpuscallosum' are assumed to not be
268-
present. Default: None
267+
not specified, parcels defined in `netneurotools.freesurfer.FSIGNORE`
268+
are assumed to not be present. Default: None
269269
270270
Reurns
271271
------
@@ -274,10 +274,7 @@ def parcels_to_vertices(data, *, lhannot, rhannot, drop=None):
274274
"""
275275

276276
if drop is None:
277-
drop = [
278-
'unknown', 'corpuscallosum', # default FreeSurfer
279-
'Background+FreeSurfer_Defined_Medial_Wall' # common alternative
280-
]
277+
drop = FSIGNORE
281278
drop = _decode_list(drop)
282279

283280
data = np.vstack(data)
@@ -335,8 +332,9 @@ def vertices_to_parcels(data, *, lhannot, rhannot, drop=None):
335332
hemisphere
336333
drop : list, optional
337334
Specifies regions in {lh,rh}annot that should be removed from the
338-
parcellated version of `data`. If not specified, 'unknown' and
339-
'corpuscallosum' will be removed. Default: None
335+
parcellated version of `data`. If not specified, vertices corresponding
336+
to parcels defined in `netneurotools.freesurfer.FSIGNORE` will be
337+
removed. Default: None
340338
341339
Reurns
342340
------
@@ -345,10 +343,7 @@ def vertices_to_parcels(data, *, lhannot, rhannot, drop=None):
345343
"""
346344

347345
if drop is None:
348-
drop = [
349-
'unknown', 'corpuscallosum', # default FreeSurfer
350-
'Background+FreeSurfer_Defined_Medial_Wall' # common alternative
351-
]
346+
drop = FSIGNORE
352347
drop = _decode_list(drop)
353348

354349
data = np.vstack(data)
@@ -439,6 +434,60 @@ def _get_fsaverage_coords(version='fsaverage', surface='sphere'):
439434
return np.row_stack(coords), np.hstack(hemi)
440435

441436

437+
def _get_fsaverage_spins(version='fsaverage', spins=None, n_rotate=1000,
438+
seed=None, verbose=False, return_cost=False):
439+
"""
440+
Generates spatial permutation resamples for fsaverage `version`
441+
442+
If `spins` are provided then performs checks to confirm they are valid
443+
444+
Parameters
445+
----------
446+
version : str, optional
447+
Specifies which version of `fsaverage` for which to generate spins.
448+
Must be one of {'fsaverage', 'fsaverage3', 'fsaverage4', 'fsaverage5',
449+
'fsaverage6'}. Default: 'fsaverage'
450+
spins : array_like, optional
451+
Pre-computed spins to use instead of generating them on the fly. If not
452+
provided will use other provided parameters to create them. Default:
453+
None
454+
n_rotate : int, optional
455+
Number of rotations to generate. Default: 1000
456+
seed : {int, np.random.RandomState instance, None}, optional
457+
Seed for random number generation. Default: None
458+
verbose : bool, optional
459+
Whether to print occasional status messages. Default: False
460+
return_cost : bool, optional
461+
Whether to return cost array (specified as Euclidean distance) for each
462+
coordinate for each rotation. Currently this option is not supported if
463+
pre-computed `spins` are provided. Default: True
464+
465+
Returns
466+
--------
467+
spins : (N, S) numpy.ndarray
468+
Resampling array
469+
"""
470+
471+
if spins is None:
472+
coords, hemiid = _get_fsaverage_coords(version, 'sphere')
473+
spins, cost = gen_spinsamples(coords, hemiid, n_rotate=n_rotate,
474+
seed=seed, verbose=verbose)
475+
if return_cost:
476+
return spins, cost
477+
478+
spins = np.asarray(spins, dtype='int32')
479+
if spins.shape[-1] != n_rotate:
480+
warnings.warn('Shape of provided `spins` array does not match '
481+
'number of rotations requested with `n_rotate`. '
482+
'Ignoring specified `n_rotate` parameter and using '
483+
'all provided `spins`.')
484+
n_rotate = spins.shape[-1]
485+
if return_cost:
486+
raise ValueError('Cannot `return_cost` when `spins` are provided.')
487+
488+
return spins, None
489+
490+
442491
def spin_data(data, *, lhannot, rhannot, version='fsaverage', n_rotate=1000,
443492
spins=None, drop=None, seed=None, verbose=False,
444493
return_cost=False):
@@ -473,8 +522,8 @@ def spin_data(data, *, lhannot, rhannot, version='fsaverage', n_rotate=1000,
473522
drop : list, optional
474523
Specifies regions in {lh,rh}annot that are not present in `data`. NaNs
475524
will be inserted in place of the these regions in the returned data. If
476-
not specified, 'unknown' and 'corpuscallosum' are assumed to not be
477-
present. Default: None
525+
not specified, parcels defined in `netneurotools.freesurfer.FSIGNORE`
526+
are assumed to not be present. Default: None
478527
seed : {int, np.random.RandomState instance, None}, optional
479528
Seed for random number generation. Default: None
480529
verbose : bool, optional
@@ -495,41 +544,23 @@ def spin_data(data, *, lhannot, rhannot, version='fsaverage', n_rotate=1000,
495544
"""
496545

497546
if drop is None:
498-
drop = [
499-
'unknown', 'corpuscallosum', # default FreeSurfer
500-
'Background+FreeSurfer_Defined_Medial_Wall' # common alternative
501-
]
547+
drop = FSIGNORE
502548

503549
# get coordinates and hemisphere designation for spin generation
504550
vertices = parcels_to_vertices(data, lhannot=lhannot, rhannot=rhannot,
505551
drop=drop)
506552

507-
if spins is None:
508-
coords, hemiid = _get_fsaverage_coords(version, 'sphere')
509-
if len(vertices) != len(coords):
510-
raise ValueError('Provided annotation files have a different '
511-
'number of vertices than the specified fsaverage '
512-
'surface.\n ANNOTATION: {} vertices\n '
513-
'FSAVERAGE: {} vertices'
514-
.format(len(vertices), len(coords)))
515-
spins, cost = gen_spinsamples(coords, hemiid, n_rotate=n_rotate,
516-
seed=seed, verbose=verbose)
517-
else:
518-
spins = np.asarray(spins, dtype='int32')
519-
if len(spins) != len(vertices):
520-
raise ValueError('Provided `spins` array has a different number '
521-
'of vertices than the provided annotation files.'
522-
'\n ANNOTATION: {} vertices\n SPINS: '
523-
'{} vertices\n'
524-
.format(len(vertices), len(spins)))
525-
if spins.shape[-1] != n_rotate:
526-
warnings.warn('Shape of provided `spins` array does not match '
527-
'number of rotations requested with `n_rotate`. '
528-
'Ignoring specified `n_rotate` parameter and using '
529-
'all provided `spins`.')
530-
n_rotate = spins.shape[-1]
531-
if return_cost:
532-
raise ValueError('Cannot `return_cost` when `spins` are provided.')
553+
# get spins + cost (if requested)
554+
spins, cost = _get_fsaverage_spins(version=version, spins=spins,
555+
n_rotate=n_rotate,
556+
seed=seed, verbose=verbose,
557+
return_cost=return_cost)
558+
if len(vertices) != len(spins):
559+
raise ValueError('Provided annotation files have a different '
560+
'number of vertices than the specified fsaverage '
561+
'surface.\n ANNOTATION: {} vertices\n '
562+
'FSAVERAGE: {} vertices'
563+
.format(len(vertices), len(spins)))
533564

534565
spun = np.zeros(data.shape + (n_rotate,))
535566
for n in range(n_rotate):
@@ -550,7 +581,8 @@ def spin_data(data, *, lhannot, rhannot, version='fsaverage', n_rotate=1000,
550581

551582

552583
def spin_parcels(*, lhannot, rhannot, version='fsaverage', n_rotate=1000,
553-
drop=None, seed=None, return_cost=False, **kwargs):
584+
spins=None, drop=None, seed=None, verbose=False,
585+
return_cost=False):
554586
"""
555587
Rotates parcels in `{lh,rh}annot` and re-assigns based on maximum overlap
556588
@@ -569,16 +601,22 @@ def spin_parcels(*, lhannot, rhannot, version='fsaverage', n_rotate=1000,
569601
'fsaverage5', 'fsaverage6'}. Default: 'fsaverage'
570602
n_rotate : int, optional
571603
Number of rotations to generate. Default: 1000
604+
spins : array_like, optional
605+
Pre-computed spins to use instead of generating them on the fly. If not
606+
provided will use other provided parameters to create them. Default:
607+
None
572608
drop : list, optional
573609
Specifies regions in {lh,rh}annot that are not present in `data`. NaNs
574610
will be inserted in place of the these regions in the returned data. If
575-
not specified, 'unknown' and 'corpuscallosum' are assumed to not be
576-
present. Default: None
611+
not specified, parcels defined in `netneurotools.freesurfer.FSIGNORE`
612+
are assumed to not be present. Default: None
613+
seed : {int, np.random.RandomState instance, None}, optional
614+
Seed for random number generation. Default: None
615+
verbose : bool, optional
616+
Whether to print occasional status messages. Default: False
577617
return_cost : bool, optional
578618
Whether to return cost array (specified as Euclidean distance) for each
579-
coordinate for each rotation Default: True
580-
kwargs : key-value, optional
581-
Key-value pairs passed to :func:`netneurotools.stats.gen_spinsamples`
619+
coordinate for each rotation. Default: True
582620
583621
Returns
584622
-------
@@ -604,10 +642,7 @@ def overlap(vals):
604642
return -1
605643

606644
if drop is None:
607-
drop = [
608-
'unknown', 'corpuscallosum', # default FreeSurfer
609-
'Background+FreeSurfer_Defined_Medial_Wall' # common alternative
610-
]
645+
drop = FSIGNORE
611646
drop = _decode_list(drop)
612647

613648
# get vertex-level labels (set drop labels to - values)
@@ -625,19 +660,24 @@ def overlap(vals):
625660
labels = np.unique(vertices)
626661
mask = labels > -1
627662

628-
# get coordinates and hemisphere designation for spin generation
629-
coords, hemiid = _get_fsaverage_coords(version, 'sphere')
630-
if len(vertices) != len(coords):
631-
raise ValueError('Provided annotation files have a different number '
632-
'of vertices than the specified fsaverage surface.\n'
633-
' ANNOTATION: {} vertices\n'
634-
' FSAVERAGE: {} vertices'
635-
.format(len(vertices), len(coords)))
663+
# get spins + cost (if requested)
664+
spins, cost = _get_fsaverage_spins(version=version, spins=spins,
665+
n_rotate=n_rotate,
666+
seed=seed, verbose=verbose,
667+
return_cost=return_cost)
668+
if len(vertices) != len(spins):
669+
raise ValueError('Provided annotation files have a different '
670+
'number of vertices than the specified fsaverage '
671+
'surface.\n ANNOTATION: {} vertices\n '
672+
'FSAVERAGE: {} vertices'
673+
.format(len(vertices), len(spins)))
636674

637675
# spin and assign regions based on max overlap
638-
spins, cost = gen_spinsamples(coords, hemiid, n_rotate=n_rotate, **kwargs)
639676
regions = np.zeros((len(labels[mask]), n_rotate), dtype='int32')
640677
for n in range(n_rotate):
678+
if verbose:
679+
msg = f'Calculating parcel overlap: {n:>5}/{n_rotate}'
680+
print(msg, end='\b' * len(msg), flush=True)
641681
regions[:, n] = labeled_comprehension(vertices[spins[:, n]], vertices,
642682
labels, overlap, int, -1)[mask]
643683

netneurotools/stats.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -651,7 +651,7 @@ def gen_spinsamples(coords, hemiid, n_rotate=1000, check_duplicates=True,
651651
for n in range(n_rotate):
652652
count, duplicated = 0, True
653653

654-
if verbose and n % 10 == 0:
654+
if verbose:
655655
msg = 'Generating spin {:>5} of {:>5}'.format(n, n_rotate)
656656
print(msg, end='\r', flush=True)
657657

0 commit comments

Comments
 (0)