API integrating the EBRAINS KG with TVB using siibra-python.

Focuses on retrieving and creating TVB Structural and Functional Connectivities

In [ ]:
import siibra

Prerequisites

Note: You can skip this section if you are already familiar with the concepts of atlas, parcellation, cohort and how to retrieve them using siibra.

Siibra makes it possible to retrieve structural and functional connectivities from the EBRAINS Knowledge Graph. But first, we need to select an atlas, a parcellation, a cohort and a subject (or list of subjects) for which we want to obtain this data. For more information about these concepts you can take a look at siibra's documentation page.

So, in this Prerequisites section, we will demonstrate how to obtain information about these concepts.

Atlases

The first concept that we need to look at is the atlas that we are going to use. Siibra makes it easy to retrieve all the atlases present in the KG:

In [ ]:
list(siibra.atlases) # make it a list to see their details

As we can see, there are currently 4 atlases in the KG, each for a different species. In this notebook, because we want to use the data from the KG with TVB, we will focus on the human data, so we will choose the first atlas.

Choosing an atlas can be done in a few different ways. Of course, we could access it using its index in the atlases list:

In [ ]:
atlas = siibra.atlases[0]
atlas

But if we don't know the order of the atlases inside siibra, we can specify its name, which is easier to remember

In [ ]:
atlas = siibra.atlases['Multilevel Human Atlas']
atlas

But what if we can't remember the exact name of the atlas? There is no problem, as siibra, very conveniently, knows how to match the desired atlas even with a partial description. Meaning, we can specify that we want the human atlas, and siibra will retrieve the correct one:

In [ ]:
atlas = siibra.atlases['human']
atlas

Parcellations

Next, we need to look at parcellations. Each atlas has its own list of parcellations and different parcellations of the same atlas will have different data available (connectivities, gene expressions, etc.).

Once again, siibra makes it easy to get a list of all the parcellations available in the KG (pertaining to all atlases):

In [ ]:
list(siibra.parcellations)

Just like for the atlases, the parcellations are tied to a specific species. So, we can infer which atlas each parcellation belongs to. However, it is not necessary to infer this information, as siibra gives us a little help in this regard, making it possible to list all the parcellation pertaining to an atlas:

In [ ]:
list(atlas.parcellations) # remember that we already set the atlas to be the Multilevel Human Atlas

Selecting a parcellation can be done, as for the atlases, in several different ways:

  • By index
  • By full name
  • By partial name

As we can see, there are many parcellations related to the Human Atlas. Some of them are variations of the same parcellation (DiFuMo) and some are different versions of the same parcellartion (Julich-Brain). In this notebook we will look at the Julich parcellation, as that is the one containing connectivity data.

There are multiple Julich parcellations, corresponding to different versions of it. So a question arises, when we want to select a parcellation using only its partial name, how can we do that? Well, one idea would be to include the version number in the partial name:

In [ ]:
parcellation = atlas.parcellations['julich 2.9']
parcellation

We could also use just the version number, like:

parcellation = atlas.parcellations['2.9']

But it is better to specify at least part of the name as well.

But what happens if we specify just the name, without any version identifier? In this case, siibra chooses one for us, usually the most recent version:

In [ ]:
parcellation = atlas.parcellations['julich']
parcellation

Cohorts and features

Each parcellation can have multiple cohorts. Unfortunately, there is no direct way to see all the cohorts related to this parcellation, as this is highly dependent on the kind of data we want to extract from this parcellation.

We want to extract connectivity data (Weights, Tract Lengths and Functional connectivities) from the KG using siibra, so we need to list the connectivity related features in order to see all the cohorts that used our parcellation.

(For the sake of demonstration, we will use v2.9 of the Julich parcellation, as it is the only one containing 2 cohorts.)

In [ ]:
parcellation = atlas.parcellations['julich 2.9']
parcellation

Getting the features (e.g. connectivity weights) is pretty easy with siibra. We just need to specify the parcellation and the kind of feature we wish to retrieve:

In [ ]:
features = siibra.features.get(parcellation, siibra.features.connectivity.StreamlineCounts) # use StreamlineLengths for Tract lengths and FunctionalConnectivity for Functional connectivities
features

We can see that there are 2 groups of features (of Connectivity weights) related to this parcellation. Each group, represented as a CompoundFeature object, corresponds to a different cohort. This information is stored as an attribute of the CompoundFeature object:

In [ ]:
for group in features:
    print(f'{group.name} - Cohort: {group.cohort}')

Note: If we try to extract the Functional Connectivities and expect 2 groups in return, we will actually get 6:

In [ ]:
f_conn_features = siibra.features.get(parcellation, siibra.features.connectivity.FunctionalConnectivity)
f_conn_features

So, why is that? The reason comes from how the HCP cohort experiments were conducted. They had separate scannings noted as REST1-LR, REST1-RL, REST2-LR or REST2-RL.

In [ ]:
for group in f_conn_features:
    print(f'{group.name} - Cohort: {group.cohort}')

One last thing, if you want to read even more about these connectivities and how they were acquired, you can access the dataset description link just as easily:

In [ ]:
for group in f_conn_features:
    print(f'{group.name}: {group.doi_or_url}')

Subjects

If we take a look at the name of the group features above, we will see 200 StreamlineCounts features or 349 FunctionalConnectivity features. This means that there are 200 Structural Connectivities (HCP cohort) and 349 Functional Connectivities (1000BRAINS cohort) respectively. Each connectivity corresponts to a specific subject (although for the 1000BRAINS cohort there are multiple connectivities for the same individual, but this will be explained later).

To see the list of subjects, first we need to take a look at one of the feature groups. The subject ID will be displayed at the end of connectivity's name, but we can also access it separately.

In [ ]:
feature_group = features[0] # select one of the groups
for f in feature_group:
    print(f'Subject ID {f.subject}: {f.name}')

For the 1000BRAINS cohort we will see the subject ID, usually followed by _1 or _2, representing the scanning session. So, the same subject can have 1 or 2 scanning sessions.

In [ ]:
feature_group = features[1] # select second group, corresponding to 1000BRAINS cohort
for f in feature_group:
    print(f'Subject ID {f.subject}: {f.name}')

Connectivity data

Now that we know everything that we need about the information required to access connectivity data (i.e. the Connectivity matrix), we can go ahead and do that.

Selecting the connectivity for just one subject is not a straightforward task and, instead, we need to search through the entire list of connectivities from our feature group to get the desired connectivity.

In [ ]:
# select atlas
atlas = siibra.atlases['human']

# select parcellation
parcellation = siibra.parcellations['julich 3.0']

# get feature group
features = siibra.features.get(parcellation, siibra.features.connectivity.StreamlineCounts)
feature_group = features[0]

# get connectivity for a specific subject
subject_id = '000'
connectivity = [c for c in feature_group if c.subject == subject_id][0]

print(connectivity.name)

Finally, we can access the connectivity matrix, which can later be used for different tasks. The matrix is represented as a pandas.DataFrame object, where the columns and index names are the regions of the connectivity.

In [ ]:
connectivity.data

Using the API

If you went through the prerequisites part, you saw that getting the connectivity data is not that straightforward. Plus, the result is a pandas.DataFrame, which, in order to be used in TVB, needs to be converted to a tvb.datatypes.connectivity.Connectivity object. This is what the API is intended for: to make it easier for users to specify what they want and assure them that they will get as result a connectivity which can be used for plotting, simulations, etc.

With this API, you just need to specify the data you want (from which atlas, parcellation, cohort and for which subjects) and you will get it. And don't worry, default values are also set, in case you don't know where to start.

1. Retrieving both Structural and Functional Connectivities

If you want to know more about the implementation details of this API, you can take a look at our main methods for extracting Structural and Functional connectivities. However, the most important aspects were covered in the Prerequisites part of this notebook.

The results of this method are 2 dictionaries, containing structural and functional connectivities respectively. Each dictionary has as:

- key: the subject id

- value: the Structural/Functional TVB Connectivity for the corresponding subject

In [ ]:
from tvb.adapters.creators.siibra_base import get_connectivities_from_kg
struct_conn_dict, func_conn_dict = get_connectivities_from_kg('human', 'julich 3', 'HCP', '000-001', True)
There is 1 Structural connectivity for each subject:
In [ ]:
print(f'Structural connectivities:')
for sc in struct_conn_dict.items():
    print(sc)
And 5 Functional connectivities for each subject:
In [ ]:
print(f'Functional connectivities:')
for fc in func_conn_dict.items():
    print(fc)

After retrieving the connectivities, we can access and use them as any other connectivity from TVB:

In [ ]:
sc_conn = struct_conn_dict['000']
sc_conn
In [ ]:
from tvb.simulator.plot.tools import plot_connectivity
plot_connectivity(sc_conn)

2. Retrieving just Structural Connectivities

2.1 Using the common API for Structural and Functional Connectivities:

The API from 1. can be used to extract just Structural connectivities, by setting the last flag (mentioning the option to also compute Functional connectivities) to False

In [ ]:
struct_conn_dict, func_conn_dict = get_connectivities_from_kg('human', 'julich 2.9', '1000BRAINS', '0002', False)
Now there are 2(*) Structural connectivities for our subject:
*in this cohort, some subjects had 2 scanning sessions, resulting in 2 Structural connectivities
In [ ]:
print(f'Structural connectivities:')
for sc in struct_conn_dict.items():
    print(sc)
And no Functional connectivities:
In [ ]:
print(f'Functional connectivities: \n {func_conn_dict}')

2.2 Using the API for Structural connectivities:

In [ ]:
from tvb.adapters.creators.siibra_base import get_structural_connectivities_from_kg
struct_conn_dict = get_structural_connectivities_from_kg('human', 'julich 2.9', '1000BRAINS', '0002')
In [ ]:
print(f'Structural connectivities:')
for sc in struct_conn_dict.items():
    print(sc)

Again, the resulted connectivity could be used like any other TVB Connectivity, so let's use it in a simulation this time:

In [ ]:
import matplotlib.pyplot as plt
import numpy
from tvb.simulator.lab import *

connectivity=struct_conn_dict['0002_1']
connectivity.speed=numpy.array([1.0])

sim = simulator.Simulator(
    connectivity=connectivity,
    coupling=coupling.Linear(a=numpy.array([2e-4])),
    integrator=integrators.EulerStochastic(dt=10.0),
    model=models.Linear(gamma=numpy.array([-1e-2])),
    monitors=(monitors.Raw(),),
    simulation_length=1e4
).configure()

(time, data), = sim.run()

print(time.shape)
print(data.shape)

3. Retrieving just Functional Connectivities

Note: Functional Connectivities are represented as ConnectivityMeasures in TVB. This means that each FC will have an associated Structural Connectivity to it. For this reason, to extract any FC from siibra, we must also provide a dictionary of corresponding Structural Connectivities.
In [ ]:
# you can get this dictionary any way you want, but, in the context of this demo, this is the easiest way to do it
struct_conn_dict = get_structural_connectivities_from_kg('human', 'julich 3', 'HCP', '002')
In [ ]:
from tvb.adapters.creators.siibra_base import get_connectivity_measures_from_kg
func_conn_dict = get_connectivity_measures_from_kg('human', 'julich 3', 'HCP', '002', struct_conn_dict)
In [ ]:
print(f'Functional connectivities:')
for fc in func_conn_dict.items():
    print(fc)
In [ ]:
# inspecting a Functional Connectivity
func_conn_dict['002'][0]
In [ ]: