Skip to content

Commit 83f6aa3

Browse files
Add Python examples: embedding + parcellations
1 parent 11ba046 commit 83f6aa3

File tree

9 files changed

+227
-716
lines changed

9 files changed

+227
-716
lines changed

LICENSE

+29-674
Large diffs are not rendered by default.

brainspace/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from brainspace._version import __version__

brainspace/_version.py

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
"""BrainSpace version"""
2+
3+
__version__ = '0.1.0'

brainspace/gradient/kernels.py

+16-1
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,13 @@
66
# License: BSD 3 clause
77

88

9+
import warnings
10+
911
import numpy as np
1012
from scipy.stats import rankdata
13+
1114
from sklearn.metrics.pairwise import cosine_similarity, rbf_kernel
15+
1216
from .utils import dominant_set
1317

1418

@@ -44,10 +48,15 @@ def compute_affinity(x, kernel=None, sparsity=.9, gamma=None):
4448
Affinity matrix.
4549
"""
4650

47-
if sparsity:
51+
if sparsity is not None:
4852
x = dominant_set(x, k=1-sparsity, is_thresh=False, as_sparse=False)
4953

5054
if kernel is None:
55+
mask_neg = x < 0
56+
if mask_neg.any():
57+
x[mask_neg] = 0
58+
warnings.warn('The matrix contains negative values and will '
59+
'be zeroed-out.')
5160
return x
5261

5362
if kernel in {'pearson', 'spearman'}:
@@ -69,4 +78,10 @@ def compute_affinity(x, kernel=None, sparsity=.9, gamma=None):
6978
else:
7079
raise ValueError("Unknown kernel '{0}'.".format(kernel))
7180

81+
mask_neg = a < 0
82+
if mask_neg.any():
83+
a[mask_neg] = 0
84+
warnings.warn('The matrix contains negative values and will '
85+
'be zeroed-out.')
86+
7287
return a

brainspace/mesh/array_operations.py

+71-25
Original file line numberDiff line numberDiff line change
@@ -533,43 +533,89 @@ def propagate_labeling(surf, labeling, no_label=np.nan, mask=None, alpha=0.99,
533533
return new_labeling
534534

535535

536-
def smooth_array(surf, point_data, n_iter=10, mask=None, include_self=True,
537-
kernel='gaussian', sigma=1):
536+
@append_vtk(to='point')
537+
def smooth_array(surf, point_data, n_iter=5, mask=None, kernel='gaussian',
538+
relax=0.2, sigma=None, append=False, array_name=None):
539+
"""Propagate labeling on surface points.
540+
541+
Parameters
542+
----------
543+
surf : vtkPolyData or BSPolyData
544+
Input surface.
545+
point_data : str, 1D ndarray
546+
Input array to smooth. If str, it must be in the point data
547+
attributes of `surf`. If ndarray, use this array.
548+
n_iter : int, optional
549+
Number of smoothing iterations. Default is 5.
550+
mask : 1D ndarray, optional
551+
Binary mask. If specified, smoothing is only performed on points
552+
within the mask. Default is None.
553+
kernel : {'uniform', 'gaussian', 'inverse_distance'}, optional
554+
Smoothing kernel. Default is 'gaussian'.
555+
relax : float, optional
556+
Relaxation factor, contribution of neighboring points such that
557+
``0 < relax < 1``. Default is 0.2.
558+
sigma : float, optional
559+
Gaussian kernel width. If None, use standard deviation of egde lengths.
560+
Default is None.
561+
append : bool, optional
562+
If True, append array to point data attributes of input surface and
563+
return surface. Otherwise, only return array. Default is False.
564+
array_name : str, optional
565+
Array name to append to surface's point data attributes. Only used if
566+
``append == True``. Default is None.
567+
568+
Returns
569+
-------
570+
output : vtkPolyData, BSPolyData or ndarray
571+
A 1D array with the smoothed data. Return array if
572+
``append == False``. Otherwise, return input surface with the
573+
new array.
574+
575+
"""
576+
577+
if relax <= 0 or relax >= 1:
578+
raise ValueError('Relaxation factor must be between 0 and 1.')
538579

539580
if isinstance(point_data, str):
540-
pd = surf.get_array(name=point_data, at='p')
581+
point_data = surf.get_array(name=point_data, at='p')
582+
583+
if mask is not None:
584+
pd = point_data[mask]
585+
else:
586+
pd = point_data
541587

542-
# adj = me.get_immediate_adjacency(surf)
543-
# d = me.get_immediate_distance(surf)
544-
# d.setdiag(0)
545588
if kernel == 'uniform':
546-
w = me.get_immediate_adjacency(surf, include_self=include_self)
589+
w = me.get_immediate_adjacency(surf, include_self=False, mask=mask,
590+
dtype=np.float)
547591
elif kernel == 'gaussian':
548-
w = me.get_immediate_distance(surf, metric='sqeuclidean')
549-
# w.setdiag(0)
592+
w = me.get_immediate_distance(surf, metric='sqeuclidean', mask=mask)
593+
if sigma is None:
594+
# sigma = w.data.mean() + 3 * w.data.std()
595+
sigma = w.data.std()
550596
w.data *= -.5 / (sigma*sigma)
551-
w.data[:] = np.exp(w.data)
552-
if include_self:
553-
w.setdiag(1)
597+
np.exp(w.data, w.data)
554598
elif kernel == 'inverse_distance':
555-
w = me.get_immediate_distance(surf, metric='euclidean')
599+
w = me.get_immediate_distance(surf, metric='euclidean', mask=mask)
556600
w.data **= -1
557-
if include_self:
558-
w.setdiag(0)
601+
else:
602+
raise ValueError("Unknown kernel: {0}".format(kernel))
559603

560-
lam = 1
561-
mu = lam
562-
norm = w.sum(axis=1)
563-
alpha = np.ones(pd.shape)
564-
alpha[norm > 0] = 1 - lam
604+
w = w.tocoo(copy=False)
605+
ws = w.sum(axis=1).A1
606+
w.data *= relax/ws[w.row]
565607

566-
beta = np.zeros(pd.shape)
567-
beta[norm > 0] = lam / norm[norm > 0]
608+
retain = np.ones(pd.shape)
609+
retain[ws > 0] -= relax
568610

569611
spd = pd.copy()
570612
for i in range(n_iter):
571-
spd = alpha * spd + beta * w.dot(spd)
572-
573-
return spd
613+
wp = w.dot(spd)
614+
spd *= retain
615+
spd += wp
574616

617+
if mask is not None:
618+
spd = map_to_mask(spd, mask=mask)
619+
spd[mask] = point_data[mask]
575620

621+
return spd

brainspace/mesh/mesh_elements.py

+87-1
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,7 @@ def get_edges(surf, mask=None):
343343
344344
See Also
345345
--------
346+
:func:`get_edge_length`
346347
:func:`get_points`
347348
:func:`get_cells`
348349
@@ -355,6 +356,91 @@ def get_edges(surf, mask=None):
355356
return edges
356357

357358

359+
def get_edge_length(surf, metric='euclidean', mask=None):
360+
"""Get surface edge lengths.
361+
362+
Parameters
363+
----------
364+
surf : vtkDataSet or BSDataSet
365+
Input surface.
366+
metric : {'euclidean', 'sqeuclidean'}, optional
367+
Distance metric. Default is 'euclidean'.
368+
mask : 1D ndarray, optional
369+
Binary mask. If specified, only use points within the mask.
370+
Default is None.
371+
372+
Returns
373+
-------
374+
edges : ndarray, shape (n_edges, 2)
375+
Array of edges. Each element is a point id.
376+
377+
See Also
378+
--------
379+
:func:`get_edges`
380+
:func:`get_immediate_distance`
381+
382+
"""
383+
384+
points = get_points(surf, mask=mask)
385+
edges = get_edges(surf, mask=mask)
386+
387+
dif = points[edges[:, 0]] - points[edges[:, 1]]
388+
d = np.einsum('ij,ij->i', dif, dif)
389+
if metric == 'euclidean':
390+
d **= .5
391+
return d
392+
393+
394+
def get_border_cells(surf):
395+
"""Get cells in boundary.
396+
397+
Cells in boundary have one boundary edge.
398+
399+
Parameters
400+
----------
401+
surf : vtkDataSet or BSDataSet
402+
Input surface.
403+
404+
Returns
405+
-------
406+
edges : 1D ndarray
407+
Array of cells in border.
408+
409+
See Also
410+
--------
411+
:func:`get_edges`
412+
:func:`get_immediate_distance`
413+
414+
"""
415+
416+
ce = get_cell_edge_neighbors(surf, include_self=False)
417+
return np.where(ce.getnnz(axis=1) < 3)[0]
418+
419+
420+
def get_border_edges(surf):
421+
"""Get edges in border.
422+
423+
Parameters
424+
----------
425+
surf : vtkDataSet or BSDataSet
426+
Input surface.
427+
428+
Returns
429+
-------
430+
edges : 2D ndarray, shape = (n_edges, 2)
431+
Array of edges in border. Each element is a point id.
432+
433+
See Also
434+
--------
435+
:func:`get_edges`
436+
:func:`get_immediate_distance`
437+
438+
"""
439+
440+
ce = get_cell_edge_neighbors(surf, include_self=False)
441+
return np.where(ce.getnnz(axis=1) < 3)[0]
442+
443+
358444
def get_immediate_distance(surf, metric='euclidean', mask=None,
359445
dtype=np.float32):
360446
"""Get immediate distance matrix.
@@ -389,8 +475,8 @@ def get_immediate_distance(surf, metric='euclidean', mask=None,
389475
390476
"""
391477

392-
n_pts = surf.GetNumberOfPoints() if mask is None else np.count_nonzero(mask)
393478
points = get_points(surf, mask=mask)
479+
n_pts = points.shape[0]
394480
edges = get_edges(surf, mask=mask)
395481

396482
dif = points[edges[:, 0]] - points[edges[:, 1]]

brainspace/plotting/base.py

+8-8
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ def AddRenderer(self, row=None, col=None, renderer=None, **kwargs):
141141
if row is None or isinstance(row, tuple):
142142
row = slice(None) if row is None else slice(*row)
143143
else:
144-
row = slice(row, row+1)
144+
row = slice(row, row + 1)
145145
if col is None or isinstance(col, tuple):
146146
col = slice(None) if col is None else slice(*col)
147147
else:
@@ -214,11 +214,11 @@ def show(self, interactive=True, embed_nb=False, scale=None,
214214
return None
215215

216216
def close(self, *args):
217-
try:
218-
if hasattr(self, 'panel'):
219-
del self.panel
220-
except:
221-
pass
217+
# try:
218+
# if hasattr(self, 'panel'):
219+
# del self.panel
220+
# except:
221+
# pass
222222
self.ren_win.Finalize()
223223
# self.iren.RemoveAllObservers()
224224
self.iren.TerminateApp()
@@ -256,8 +256,8 @@ def screenshot(self, filename=None, scale=None, transparent_bg=True):
256256

257257
def Render(self):
258258
self.ren_win.Render()
259-
if hasattr(self, 'panel'):
260-
self.panel.param.trigger('object')
259+
# if hasattr(self, 'panel'):
260+
# self.panel.param.trigger('object')
261261

262262

263263
def _get_qt_app():

docs/index.rst

+3-4
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,12 @@ Authors
3535
License
3636
-------
3737

38-
The BrainSpace source code is available under the Revised BSD (3-Clause) license
38+
The BrainSpace source code is available under the BSD (3-Clause) license.
3939

4040

4141
Support
4242
-------
4343

4444
If you have problems installing the software or questions about usage,
45-
documentation or something else related to BrainSpace, you can post to our
46-
mailing list Mail.
47-
45+
documentation or something else related to BrainSpace, you can post to the
46+
Issues section of our repository.

setup.py

+9-3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
# Always prefer setuptools over distutils
99
from setuptools import setup, find_packages
1010
from os import path
11+
from io import open as io_open
1112

1213
here = path.abspath(path.dirname(__file__))
1314

@@ -16,6 +17,11 @@
1617
with open(path.join(here, 'README.rst'), encoding='utf-8') as f:
1718
long_description = f.read()
1819

20+
__version__ = None
21+
version_file = path.join(here, 'brainspace/_version.py')
22+
with io_open(version_file, mode='r') as fd:
23+
exec(fd.read())
24+
1925
# Arguments marked as "Required" below must be included for upload to PyPI.
2026
# Fields marked as "Optional" may be commented out.
2127

@@ -39,7 +45,7 @@
3945
# For a discussion on single-sourcing the version across setup.py and the
4046
# project code, see
4147
# https://packaging.python.org/en/latest/single_source_version.html
42-
version='0.1', # Required
48+
version=__version__, # Required
4349

4450
# This is a one-line description or tagline of what your project does. This
4551
# corresponds to the "Summary" metadata field:
@@ -76,7 +82,7 @@
7682

7783
# This should be your name or the name of the organization which owns the
7884
# project.
79-
author='The Python Packaging Authority', # Optional
85+
author='BrainSpace developers', # Optional
8086

8187
# This should be a valid email address corresponding to the author listed
8288
# above.
@@ -101,7 +107,7 @@
101107
'Topic :: Scientific/Engineering',
102108

103109
# Pick your license as you wish
104-
'License :: OSI Approved :: MIT License',
110+
'License :: OSI Approved :: BSD License',
105111

106112
# Specify the Python versions you support here. In particular, ensure
107113
# that you indicate whether you support Python 2, Python 3 or both.

0 commit comments

Comments
 (0)