Skip to content

Commit a0d0182

Browse files
committed
Add function for generating connectivity links + tests
1 parent da8fcb7 commit a0d0182

5 files changed

Lines changed: 171 additions & 4 deletions

File tree

frites/conn/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,5 @@
2323
# connectivity utility functions
2424
from .conn_fcd_corr import conn_fcd_corr # noqa
2525
from .conn_sliding_windows import define_windows, plot_windows # noqa
26-
from .conn_utils import (conn_get_pairs, conn_reshape_undirected, # noqa
26+
from .conn_utils import (conn_get_pairs, conn_links, conn_reshape_undirected, # noqa
2727
conn_reshape_directed, conn_ravel_directed, conn_net)

frites/conn/conn_ccf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def conn_ccf(data, times=None, roi=None, normalized=True, n_jobs=1,
4242
name of the ROI dimension can be provided
4343
normalized : bool | True
4444
Z-score normalization of the data. By default, it set to true.
45-
times_as_sample : bool | False
45+
times_as_sample : bool | True
4646
Specify whether the time dimension of the cross-correlation output
4747
should be described using the time unit of the input data or in
4848
samples. By default, samples are used to describe lags between

frites/conn/conn_utils.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,107 @@ def conn_get_pairs(roi, directed=False, nb_min_suj=-np.inf, verbose=None):
142142
return df_conn, df_suj
143143

144144

145+
def conn_links(roi, directed=False, net=False, within_roi=True, sep='auto',
146+
nb_min_links=None, pairs=None, verbose=None):
147+
"""Construct pairwise links for functional connectivity.
148+
149+
This function can be used for defining the pairwise links for computing
150+
either undirected or directed FC on M/EEG or intracranial EEG.
151+
152+
Parameters
153+
----------
154+
roi : array_like
155+
List of roi (or contacts) names.
156+
directed : bool | False
157+
Specify whether the links should be for undirected (False) or directed
158+
(True) FC
159+
net : bool | False
160+
Specify whether it is for net directed FC (True) or not (False)
161+
within_roi : bool | True
162+
Specify whether links within a brain region (e.g. V1-V1) should be
163+
keept (True) or removed (False).
164+
sep : string | 'auto'
165+
If 'auto', '-' are used to linked brain region names for undirected FC
166+
or '->' for directed FC. Alternatively, you can provide a custom
167+
separator (e.g. sep='/')
168+
nb_min_links : int | None
169+
Threshold for defining a minimum number of links between two brain
170+
regions (e.g. iEEG)
171+
pairs : array_like | None
172+
Force to use certain pairs of brain regions. Should be an array of
173+
shape (n_pairs, 2) where the first column refer to sources and the
174+
second to targets
175+
176+
Returns
177+
-------
178+
indices : tuple
179+
Remaining indices for (sources, targets)
180+
roi_st : list
181+
Name of the pairs of brain regions
182+
"""
183+
set_log_level(verbose)
184+
assert isinstance(roi, (np.ndarray, list, tuple))
185+
if not directed:
186+
assert not net, ("Net computations not supported for undirected "
187+
"connectivity")
188+
roi = np.asarray(roi)
189+
n_roi = len(roi)
190+
logger.info(f"Defining links (n_roi={n_roi}; directed={directed}; "
191+
f"net={net}, nb_min_links={nb_min_links})")
192+
193+
# build separator name
194+
if sep == 'auto':
195+
sep = '->' if directed and not net else '-'
196+
else:
197+
assert isinstance(sep, str)
198+
199+
# get (un)directed pairs
200+
if isinstance(pairs, np.ndarray) and (pairs.shape[1] == 2):
201+
x_s, x_t = pairs[:, 0], pairs[:, 1]
202+
else:
203+
if directed and not net:
204+
x_s, x_t = np.where(~np.eye(n_roi, dtype=bool))
205+
elif (not directed) or (directed and net):
206+
x_s, x_t = np.triu_indices(n_roi, k=1)
207+
208+
# drop within roi links
209+
if not within_roi:
210+
logger.info(" Dropping within-roi links")
211+
roi_s, roi_t = roi[x_s], roi[x_t]
212+
keep = [s != t for s, t in zip(roi_s, roi_t)]
213+
x_s, x_t = x_s[keep], x_t[keep]
214+
215+
# change roi order for undirected and net directed
216+
if (not directed) or (directed and net):
217+
logger.info(" Sorting roi names")
218+
roi_low = np.asarray([np.char.lower(r.astype(str)) for r in roi])
219+
_xs, _xt = x_s.copy(), x_t.copy()
220+
x_s, x_t = [], []
221+
for s, t in zip(_xs, _xt):
222+
_pair = np.array([roi_low[s], roi_low[t]])
223+
if np.all(_pair == np.sort(_pair)):
224+
x_s.append(s)
225+
x_t.append(t)
226+
else:
227+
x_s.append(t)
228+
x_t.append(s)
229+
x_s, x_t = np.asarray(x_s), np.asarray(x_t)
230+
231+
# keep pairs with a minimum number of links inside
232+
if isinstance(nb_min_links, int):
233+
logger.info(" Thresholding number of links")
234+
roi_st = [f"{s}{sep}{t}" for s, t in zip(roi[x_s], roi[x_t])]
235+
df = pd.DataFrame({'pairs': roi_st})
236+
df = df.groupby('pairs').size()
237+
keep = [df.loc[r] >= nb_min_links for r in roi_st]
238+
x_s, x_t = x_s[keep], x_t[keep]
239+
240+
# build pairs of brain region names
241+
roi_st = np.asarray([f"{s}{sep}{t}" for s, t in zip(roi[x_s], roi[x_t])])
242+
243+
return (x_s, x_t), roi_st
244+
245+
145246
###############################################################################
146247
###############################################################################
147248
# CONN RESHAPING

frites/conn/tests/test_conn_utils.py

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44

55
from frites.conn import (conn_reshape_undirected, conn_reshape_directed,
66
conn_ravel_directed, define_windows, plot_windows,
7-
conn_dfc, conn_covgc, conn_get_pairs, conn_net)
7+
conn_dfc, conn_covgc, conn_get_pairs, conn_net,
8+
conn_links)
89

910

1011
class TestConnUtils(object):
@@ -222,3 +223,61 @@ def test_conn_net(self):
222223
# test order
223224
net = conn_net(conn, roi='space', sep='->', order=['z', 'x', 'y'])
224225
np.testing.assert_array_equal(net['space'], ['z-x', 'z-y', 'x-y'])
226+
227+
def test_conn_links(self):
228+
"""Test function conn_links."""
229+
roi = ['dlPFC', 'aINS', 'dlPFC', 'vmPFC']
230+
# overall testing
231+
(x_s, x_t), roi_st = conn_links(roi, verbose=False)
232+
assert len(x_s) == len(x_t) == len(roi_st)
233+
assert all([f"{roi_st[s]}-{roi_st[t]}" for s, t in zip(x_s, x_t)])
234+
235+
# test direction
236+
_, roi_st = conn_links(roi)
237+
np.testing.assert_array_equal(roi_st,
238+
['aINS-dlPFC', 'dlPFC-dlPFC', 'dlPFC-vmPFC', 'aINS-dlPFC',
239+
'aINS-vmPFC', 'dlPFC-vmPFC'])
240+
_, roi_st = conn_links(roi, directed=True, net=False)
241+
np.testing.assert_array_equal(roi_st,
242+
['dlPFC->aINS', 'dlPFC->dlPFC', 'dlPFC->vmPFC', 'aINS->dlPFC',
243+
'aINS->dlPFC', 'aINS->vmPFC', 'dlPFC->dlPFC', 'dlPFC->aINS',
244+
'dlPFC->vmPFC', 'vmPFC->dlPFC', 'vmPFC->aINS', 'vmPFC->dlPFC'])
245+
_, roi_st = conn_links(roi, directed=True, net=True)
246+
np.testing.assert_array_equal(roi_st,
247+
['aINS-dlPFC', 'dlPFC-dlPFC', 'dlPFC-vmPFC', 'aINS-dlPFC',
248+
'aINS-vmPFC', 'dlPFC-vmPFC'])
249+
250+
# testing removing within roi connections
251+
_, roi_st = conn_links(roi, within_roi=False)
252+
assert 'dlPFC-dlPFC' not in roi_st
253+
_, roi_st = conn_links(roi, directed=True, net=False, within_roi=False)
254+
assert 'dlPFC->dlPFC' not in roi_st
255+
_, roi_st = conn_links(roi, directed=True, net=True, within_roi=False)
256+
assert 'dlPFC-dlPFC' not in roi_st
257+
258+
# remove links without a minimum number of conections
259+
_, roi_st = conn_links(roi, nb_min_links=2)
260+
np.testing.assert_array_equal(roi_st,
261+
['aINS-dlPFC', 'dlPFC-vmPFC', 'aINS-dlPFC', 'dlPFC-vmPFC'])
262+
_, roi_st = conn_links(roi, directed=True, nb_min_links=2)
263+
np.testing.assert_array_equal(roi_st,
264+
['dlPFC->aINS', 'dlPFC->dlPFC', 'dlPFC->vmPFC', 'aINS->dlPFC',
265+
'aINS->dlPFC', 'dlPFC->dlPFC', 'dlPFC->aINS', 'dlPFC->vmPFC',
266+
'vmPFC->dlPFC', 'vmPFC->dlPFC'])
267+
_, roi_st = conn_links(roi, directed=True, nb_min_links=2, net=True)
268+
np.testing.assert_array_equal(roi_st,
269+
['aINS-dlPFC', 'dlPFC-vmPFC', 'aINS-dlPFC', 'dlPFC-vmPFC'])
270+
271+
# testing string separator
272+
for direction in [True, False]:
273+
_, roi_st = conn_links(roi, sep='/', directed=True)
274+
assert all(['/' in r for r in roi_st])
275+
276+
# testing pairs
277+
p_1, p_2 = np.array([0, 2]), np.array([1, 3])
278+
_, roi_st = conn_links(roi, pairs=np.c_[p_1, p_2])
279+
np.testing.assert_array_equal(roi_st,
280+
['aINS-dlPFC', 'dlPFC-vmPFC'])
281+
_, roi_st = conn_links(roi, pairs=np.c_[p_1, p_2], directed=True)
282+
np.testing.assert_array_equal(roi_st,
283+
['dlPFC->aINS', 'dlPFC->vmPFC'])

frites/data/frites.mplstyle

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ axes.edgecolor: white
1313
axes.linewidth: 1
1414
axes.grid: True
1515
axes.titlesize: xx-large
16+
axes.titlecolor: 444444
1617
axes.labelsize: xx-large
17-
axes.labelcolor: 555555
18+
axes.labelcolor: 444444
1819
axes.axisbelow: True # grid/ticks are below elements (e.g., lines, text)
1920

2021
axes.prop_cycle: cycler('color', ['E24A33', '348ABD', '988ED5', '777777', 'FBC15E', '8EBA42', 'FFB5B8'])
@@ -38,6 +39,12 @@ grid.color: lightgray
3839
grid.linestyle: - # solid line
3940

4041
legend.fontsize: 18
42+
legend.labelcolor: 444444
43+
44+
text.color: 444444
4145

4246
figure.facecolor: white
4347
figure.edgecolor: 0.50
48+
49+
savefig.dpi: 300
50+
savefig.bbox: tight

0 commit comments

Comments
 (0)