brainstat 0.3.6__tar.gz → 0.5.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {brainstat-0.3.6/brainstat.egg-info → brainstat-0.5.2}/PKG-INFO +42 -13
- {brainstat-0.3.6 → brainstat-0.5.2}/README.rst +3 -2
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/__init__.py +1 -1
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/_utils.py +30 -5
- brainstat-0.5.2/brainstat/context/genetics.py +301 -0
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/context/histology.py +23 -2
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/context/meta_analysis.py +97 -4
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/data/data_urls.json +2 -2
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/datasets/base.py +115 -4
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/mesh/interpolate.py +1 -1
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/mesh/utils.py +0 -2
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/stats/SLM.py +74 -0
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/stats/_linear_model.py +3 -1
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/stats/_multiple_comparisons.py +4 -4
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/stats/_t_test.py +1 -1
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/stats/terms.py +16 -7
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/stats/utils.py +4 -2
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/tests/test_SLM.py +31 -0
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/tests/test_datasets.py +1 -0
- brainstat-0.5.2/brainstat/tests/test_genetics.py +33 -0
- brainstat-0.5.2/brainstat/tests/test_histology.py +32 -0
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/tests/test_mesh_edges.py +1 -1
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/tests/test_terms.py +26 -2
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/tutorial/utils.py +1 -1
- {brainstat-0.3.6 → brainstat-0.5.2/brainstat.egg-info}/PKG-INFO +42 -13
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat.egg-info/SOURCES.txt +1 -0
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat.egg-info/requires.txt +6 -5
- {brainstat-0.3.6 → brainstat-0.5.2}/setup.cfg +1 -1
- {brainstat-0.3.6 → brainstat-0.5.2}/setup.py +9 -10
- brainstat-0.3.6/brainstat/context/genetics.py +0 -210
- brainstat-0.3.6/brainstat/tests/test_histology.py +0 -21
- {brainstat-0.3.6 → brainstat-0.5.2}/LICENSE +0 -0
- {brainstat-0.3.6 → brainstat-0.5.2}/MANIFEST.in +0 -0
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/_typing.py +0 -0
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/context/__init__.py +0 -0
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/context/resting.py +0 -0
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/data/logging.conf +0 -0
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/data/nn_interp_civet164k.nii.gz +0 -0
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/data/nn_interp_civet41k.nii.gz +0 -0
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/data/nn_interp_fsaverage.nii.gz +0 -0
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/data/nn_interp_fsaverage5.nii.gz +0 -0
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/data/nn_interp_fslr32k.nii.gz +0 -0
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/datasets/__init__.py +0 -0
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/mesh/__init__.py +0 -0
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/mesh/data.py +0 -0
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/stats/__init__.py +0 -0
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/tests/__init__.py +0 -0
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/tests/conftest.py +0 -0
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/tests/datagen_f.py +0 -0
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/tests/datagen_fdr.py +0 -0
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/tests/datagen_linear_model.py +0 -0
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/tests/datagen_mesh_edges.py +0 -0
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/tests/datagen_mesh_smooth.py +0 -0
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/tests/datagen_peak_clus.py +0 -0
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/tests/datagen_random_field_theory.py +0 -0
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/tests/datagen_resels.py +0 -0
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/tests/datagen_slm.py +0 -0
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/tests/datagen_stat_threshold.py +0 -0
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/tests/datagen_t_test.py +0 -0
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/tests/test_f_test.py +0 -0
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/tests/test_fdr.py +0 -0
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/tests/test_linear_model.py +0 -0
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/tests/test_mesh_smooth.py +0 -0
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/tests/test_peak_clus.py +0 -0
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/tests/test_random_field_theory.py +0 -0
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/tests/test_resels.py +0 -0
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/tests/test_stat_threshold.py +0 -0
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/tests/test_t_test.py +0 -0
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/tests/testutil.py +0 -0
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat/tutorial/__init__.py +0 -0
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat.egg-info/dependency_links.txt +0 -0
- {brainstat-0.3.6 → brainstat-0.5.2}/brainstat.egg-info/top_level.txt +0 -0
|
@@ -1,26 +1,56 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: brainstat
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.2
|
|
4
4
|
Summary: A toolbox for statistical analysis of neuroimaging data
|
|
5
5
|
Home-page: https://github.com/MICA-LAB/BrainStat
|
|
6
6
|
Author: MNI-MICA Lab and MPI-CNG Lab
|
|
7
|
-
Author-email:
|
|
7
|
+
Author-email: saratheriver@gmail.com, sheymaba@gmail.com
|
|
8
8
|
License: BSD 3-Clause License
|
|
9
9
|
Project-URL: Documentation, https://brainstat.readthedocs.io
|
|
10
10
|
Project-URL: Bug Reports, https://github.com/MICA-LAB/BrainStat/issues
|
|
11
11
|
Project-URL: Source, https://github.com/MICA-LAB/BrainStat/
|
|
12
|
-
Platform: UNKNOWN
|
|
13
12
|
Classifier: Intended Audience :: Science/Research
|
|
14
13
|
Classifier: Intended Audience :: Developers
|
|
15
14
|
Classifier: Topic :: Software Development :: Build Tools
|
|
16
15
|
Classifier: Topic :: Scientific/Engineering
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.
|
|
18
|
-
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
-
Requires-Python: >=3.7.*
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Requires-Python: >=3.10
|
|
21
18
|
Description-Content-Type: text/x-rst
|
|
22
|
-
Provides-Extra: dev
|
|
23
19
|
License-File: LICENSE
|
|
20
|
+
Requires-Dist: abagen>=0.1
|
|
21
|
+
Requires-Dist: brainspace>=0.1.2
|
|
22
|
+
Requires-Dist: h5py
|
|
23
|
+
Requires-Dist: matplotlib>=3.4
|
|
24
|
+
Requires-Dist: netneurotools<0.3.0
|
|
25
|
+
Requires-Dist: neurosynth
|
|
26
|
+
Requires-Dist: nibabel
|
|
27
|
+
Requires-Dist: nilearn>=0.7.0
|
|
28
|
+
Requires-Dist: nimare
|
|
29
|
+
Requires-Dist: numpy>=1.21
|
|
30
|
+
Requires-Dist: pandas>=1.3
|
|
31
|
+
Requires-Dist: scikit_learn>=1.0
|
|
32
|
+
Requires-Dist: scipy>=1.7
|
|
33
|
+
Requires-Dist: templateflow
|
|
34
|
+
Requires-Dist: trimesh
|
|
35
|
+
Provides-Extra: dev
|
|
36
|
+
Requires-Dist: gitpython; extra == "dev"
|
|
37
|
+
Requires-Dist: hcp-utils; extra == "dev"
|
|
38
|
+
Requires-Dist: mypy; extra == "dev"
|
|
39
|
+
Requires-Dist: plotly; extra == "dev"
|
|
40
|
+
Requires-Dist: pytest; extra == "dev"
|
|
41
|
+
Dynamic: author
|
|
42
|
+
Dynamic: author-email
|
|
43
|
+
Dynamic: classifier
|
|
44
|
+
Dynamic: description
|
|
45
|
+
Dynamic: description-content-type
|
|
46
|
+
Dynamic: home-page
|
|
47
|
+
Dynamic: license
|
|
48
|
+
Dynamic: license-file
|
|
49
|
+
Dynamic: project-url
|
|
50
|
+
Dynamic: provides-extra
|
|
51
|
+
Dynamic: requires-dist
|
|
52
|
+
Dynamic: requires-python
|
|
53
|
+
Dynamic: summary
|
|
24
54
|
|
|
25
55
|
BrainStat : A toolbox for statistical analysis of neuroimaging data
|
|
26
56
|
===================================================================
|
|
@@ -60,11 +90,12 @@ Developers
|
|
|
60
90
|
----------
|
|
61
91
|
.. |seyma_s| unicode:: U+015E .. CEDILLA S
|
|
62
92
|
|
|
63
|
-
-
|
|
93
|
+
- Sara Lariviere - MICA Lab, Montreal Neurological Institute
|
|
64
94
|
- |seyma_s|\ eyma Bayrak - Max Planck Institute for Human Cognitive and Brain Sciences
|
|
95
|
+
- Reinder Vos de Wael - MICA Lab, Montreal Neurological Institute
|
|
65
96
|
- Oualid Benkarim - MICA Lab, Montreal Neurological Institute
|
|
66
|
-
- Sara Lariviere - MICA Lab, Montreal Neurological Institute
|
|
67
97
|
- Raul Cruces - MICA Lab, Montreal Neurological Institute
|
|
98
|
+
- Jessica Royer - MICA Lab, Montreal Neurological Institute
|
|
68
99
|
- Peer Herholz - Montreal Neurological Institute
|
|
69
100
|
- Seok-Jun Hong - Sungkyunkwan University
|
|
70
101
|
- Sofie Valk - Max Planck Institute for Human Cognitive and Brain Sciences
|
|
@@ -84,5 +115,3 @@ Support
|
|
|
84
115
|
If you have problems installing the software or questions about usage and
|
|
85
116
|
documentation, or something else related to BrainStat, you can post to the
|
|
86
117
|
`Issues <https://github.com/MICA-MNI/BrainStat/issues>`_ section of our repository.
|
|
87
|
-
|
|
88
|
-
|
|
@@ -36,11 +36,12 @@ Developers
|
|
|
36
36
|
----------
|
|
37
37
|
.. |seyma_s| unicode:: U+015E .. CEDILLA S
|
|
38
38
|
|
|
39
|
-
-
|
|
39
|
+
- Sara Lariviere - MICA Lab, Montreal Neurological Institute
|
|
40
40
|
- |seyma_s|\ eyma Bayrak - Max Planck Institute for Human Cognitive and Brain Sciences
|
|
41
|
+
- Reinder Vos de Wael - MICA Lab, Montreal Neurological Institute
|
|
41
42
|
- Oualid Benkarim - MICA Lab, Montreal Neurological Institute
|
|
42
|
-
- Sara Lariviere - MICA Lab, Montreal Neurological Institute
|
|
43
43
|
- Raul Cruces - MICA Lab, Montreal Neurological Institute
|
|
44
|
+
- Jessica Royer - MICA Lab, Montreal Neurological Institute
|
|
44
45
|
- Peer Herholz - Montreal Neurological Institute
|
|
45
46
|
- Seok-Jun Hong - Sungkyunkwan University
|
|
46
47
|
- Sofie Valk - Max Planck Institute for Human Cognitive and Brain Sciences
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""Neuroimaging statistics toolbox."""
|
|
2
|
-
__version__ = "0.
|
|
2
|
+
__version__ = "0.5.2"
|
|
@@ -56,8 +56,8 @@ def generate_data_fetcher_json() -> None:
|
|
|
56
56
|
"glasser": {
|
|
57
57
|
"fsaverage": {
|
|
58
58
|
"url": (
|
|
59
|
-
"https://box.bic.mni.mcgill.ca/s/
|
|
60
|
-
"https://box.bic.mni.mcgill.ca/s/
|
|
59
|
+
"https://box.bic.mni.mcgill.ca/s/j4nfMA4D7jSx3QZ/download",
|
|
60
|
+
"https://box.bic.mni.mcgill.ca/s/qZTplkH4A4exOnF/download",
|
|
61
61
|
),
|
|
62
62
|
},
|
|
63
63
|
"fsaverage5": {
|
|
@@ -150,7 +150,7 @@ def deprecated(message: str) -> Callable:
|
|
|
150
150
|
def _download_file(
|
|
151
151
|
url: str, output_file: Path, overwrite: bool = False, verbose=True
|
|
152
152
|
) -> None:
|
|
153
|
-
"""Downloads a file.
|
|
153
|
+
"""Downloads a file with retry logic for network failures.
|
|
154
154
|
|
|
155
155
|
Parameters
|
|
156
156
|
----------
|
|
@@ -163,14 +163,39 @@ def _download_file(
|
|
|
163
163
|
verbose : bool
|
|
164
164
|
If true, print a download message, defaults to True.
|
|
165
165
|
"""
|
|
166
|
+
import time
|
|
167
|
+
from http.client import RemoteDisconnected
|
|
168
|
+
from urllib.error import URLError
|
|
166
169
|
|
|
167
170
|
if output_file.exists() and not overwrite:
|
|
168
171
|
return
|
|
169
172
|
|
|
170
173
|
if verbose:
|
|
171
174
|
logger.info("Downloading " + str(output_file) + " from " + url + ".")
|
|
172
|
-
|
|
173
|
-
|
|
175
|
+
|
|
176
|
+
# Retry logic for intermittent network failures
|
|
177
|
+
max_retries = 3
|
|
178
|
+
retry_delay = 2 # seconds
|
|
179
|
+
|
|
180
|
+
for attempt in range(max_retries):
|
|
181
|
+
try:
|
|
182
|
+
with urllib.request.urlopen(url, timeout=30) as response, open(output_file, "wb") as out_file:
|
|
183
|
+
shutil.copyfileobj(response, out_file)
|
|
184
|
+
return # Success, exit function
|
|
185
|
+
except (RemoteDisconnected, URLError, TimeoutError) as e:
|
|
186
|
+
if attempt < max_retries - 1:
|
|
187
|
+
logger.warning(
|
|
188
|
+
f"Download attempt {attempt + 1}/{max_retries} failed: {e}. "
|
|
189
|
+
f"Retrying in {retry_delay} seconds..."
|
|
190
|
+
)
|
|
191
|
+
time.sleep(retry_delay)
|
|
192
|
+
retry_delay *= 2 # Exponential backoff
|
|
193
|
+
else:
|
|
194
|
+
logger.error(f"Download failed after {max_retries} attempts.")
|
|
195
|
+
raise # Re-raise the exception after all retries fail
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
|
|
174
199
|
|
|
175
200
|
|
|
176
201
|
if __name__ == "__main__":
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
"""Genetic decoding using abagen."""
|
|
2
|
+
import tempfile
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Optional, Sequence, Union
|
|
5
|
+
import os
|
|
6
|
+
import nibabel as nib
|
|
7
|
+
import numpy as np
|
|
8
|
+
import pandas as pd
|
|
9
|
+
|
|
10
|
+
# Monkeypatch for abagen compatibility with pandas 2.0+
|
|
11
|
+
if not hasattr(pd.DataFrame, 'append'):
|
|
12
|
+
def _append(self, other, ignore_index=False, verify_integrity=False, sort=False):
|
|
13
|
+
from pandas import concat
|
|
14
|
+
if isinstance(other, (list, tuple)):
|
|
15
|
+
to_concat = [self] + list(other)
|
|
16
|
+
else:
|
|
17
|
+
to_concat = [self, other]
|
|
18
|
+
return concat(to_concat, ignore_index=ignore_index, verify_integrity=verify_integrity, sort=sort)
|
|
19
|
+
pd.DataFrame.append = _append
|
|
20
|
+
|
|
21
|
+
# Monkeypatch for abagen compatibility with pandas 2.0+ (set_axis inplace)
|
|
22
|
+
_original_set_axis = pd.DataFrame.set_axis
|
|
23
|
+
def _set_axis_patched(self, labels, *args, **kwargs):
|
|
24
|
+
if 'inplace' in kwargs:
|
|
25
|
+
del kwargs['inplace']
|
|
26
|
+
return _original_set_axis(self, labels, *args, **kwargs)
|
|
27
|
+
pd.DataFrame.set_axis = _set_axis_patched
|
|
28
|
+
|
|
29
|
+
import abagen.images
|
|
30
|
+
|
|
31
|
+
def _labeltable_to_df_patched(labels):
|
|
32
|
+
"""
|
|
33
|
+
Patched version of abagen.utils.labeltable_to_df to handle missing 0 index
|
|
34
|
+
and use pd.concat instead of append.
|
|
35
|
+
"""
|
|
36
|
+
info = pd.DataFrame(columns=['id', 'label', 'hemisphere', 'structure'])
|
|
37
|
+
for table, hemi in zip(labels, ('L', 'R')):
|
|
38
|
+
if len(table) == 0:
|
|
39
|
+
continue
|
|
40
|
+
ids, label = zip(*table.items())
|
|
41
|
+
new_df = pd.DataFrame(dict(id=ids, label=label, hemisphere=hemi, structure='cortex'))
|
|
42
|
+
info = pd.concat([info, new_df], ignore_index=True)
|
|
43
|
+
|
|
44
|
+
# Use errors='ignore' to handle missing 0
|
|
45
|
+
info = info.set_index('id').drop([0], axis=0, errors='ignore').sort_index()
|
|
46
|
+
|
|
47
|
+
if len(info) != 0:
|
|
48
|
+
return info
|
|
49
|
+
|
|
50
|
+
abagen.images.labeltable_to_df = _labeltable_to_df_patched
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
import collections
|
|
54
|
+
from abagen import check_atlas, get_expression_data
|
|
55
|
+
from brainspace.mesh.mesh_io import read_surface, write_surface
|
|
56
|
+
from sklearn.model_selection import ParameterGrid
|
|
57
|
+
from nibabel.gifti import GiftiImage, GiftiDataArray
|
|
58
|
+
|
|
59
|
+
from brainstat._utils import data_directories, logger
|
|
60
|
+
from brainstat.datasets.base import (
|
|
61
|
+
_fetch_template_surface_files,
|
|
62
|
+
_valid_parcellations,
|
|
63
|
+
fetch_parcellation,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def surface_genetic_expression(
|
|
68
|
+
labels: Union[Sequence[str], np.ndarray],
|
|
69
|
+
surfaces: Optional[Union[str, Sequence[str]]] = None,
|
|
70
|
+
space: Optional[str] = None,
|
|
71
|
+
*,
|
|
72
|
+
atlas_info: Optional[str] = None,
|
|
73
|
+
ibf_threshold: float = 0.5,
|
|
74
|
+
probe_selection: str = "diff_stability",
|
|
75
|
+
donor_probes: str = "aggregate",
|
|
76
|
+
lr_mirror: Optional[bool] = None,
|
|
77
|
+
missing: Optional[str] = None,
|
|
78
|
+
tolerance: float = 2,
|
|
79
|
+
sample_norm: str = "srs",
|
|
80
|
+
gene_norm: str = "srs",
|
|
81
|
+
norm_matched: bool = True,
|
|
82
|
+
norm_structures: bool = False,
|
|
83
|
+
region_agg: str = "donors",
|
|
84
|
+
agg_metric: str = "mean",
|
|
85
|
+
corrected_mni: bool = True,
|
|
86
|
+
reannotated: bool = True,
|
|
87
|
+
return_counts: bool = False,
|
|
88
|
+
return_donors: bool = False,
|
|
89
|
+
return_report: bool = False,
|
|
90
|
+
donors: str = "all",
|
|
91
|
+
data_dir: Optional[str] = None,
|
|
92
|
+
verbose: float = 0,
|
|
93
|
+
n_proc: int = 1
|
|
94
|
+
) -> pd.DataFrame:
|
|
95
|
+
"""Computes genetic expression of surface parcels.
|
|
96
|
+
|
|
97
|
+
Parameters
|
|
98
|
+
----------
|
|
99
|
+
labels : list-of-str or numpy.ndarray
|
|
100
|
+
List of paths to label files for the parcellation, or numpy array
|
|
101
|
+
containing the pre-loaded labels
|
|
102
|
+
surfaces : list-of-image, optional
|
|
103
|
+
List of paths to surface files or preloaded surfaces. If not specified
|
|
104
|
+
assumes that `labels` are on the `fsaverage5` surface. Default: None
|
|
105
|
+
space : {'fsaverage', 'fslr'}
|
|
106
|
+
What template space `surfaces` are aligned to. If not specified assumes
|
|
107
|
+
that `labels` are on the `fsaverage5` surface. Default: None
|
|
108
|
+
|
|
109
|
+
For details of the remaining parameters please consult the
|
|
110
|
+
abagen.get_expression_data() documentation. All its parameters bar "atlas"
|
|
111
|
+
are valid input parameters.
|
|
112
|
+
|
|
113
|
+
Returns
|
|
114
|
+
-------
|
|
115
|
+
pandas.DataFrame
|
|
116
|
+
Dataframe containing the expression of each gene within each region.
|
|
117
|
+
|
|
118
|
+
Examples
|
|
119
|
+
--------
|
|
120
|
+
>>> from brainstat.context.genetics import surface_genetic_expression
|
|
121
|
+
>>> from nilearn import datasets
|
|
122
|
+
>>> import numpy as np
|
|
123
|
+
|
|
124
|
+
>>> destrieux = datasets.fetch_atlas_surf_destrieux()
|
|
125
|
+
>>> labels = np.hstack((destrieux['map_left'], destrieux['map_right']))
|
|
126
|
+
>>> fsaverage = datasets.fetch_surf_fsaverage()
|
|
127
|
+
>>> surfaces = (fsaverage['pial_left'], fsaverage['pial_right'])
|
|
128
|
+
>>> expression = surface_genetic_expression(labels, surfaces,
|
|
129
|
+
... space='fsaverage')
|
|
130
|
+
"""
|
|
131
|
+
|
|
132
|
+
# Deal with the input parameters.
|
|
133
|
+
if isinstance(surfaces, str):
|
|
134
|
+
surfaces = [surfaces]
|
|
135
|
+
elif surfaces is None:
|
|
136
|
+
surfaces = []
|
|
137
|
+
|
|
138
|
+
temp_files = []
|
|
139
|
+
try:
|
|
140
|
+
if isinstance(labels, np.ndarray):
|
|
141
|
+
# Assuming 'labels' is a 1D NumPy array of length 20484
|
|
142
|
+
num_vertices = len(labels) # Should be 20484
|
|
143
|
+
half_size = num_vertices // 2 # Half of 20484, which is 10242
|
|
144
|
+
|
|
145
|
+
# Split the array into two halves
|
|
146
|
+
labels_left = labels[:half_size] # First half for the left hemisphere
|
|
147
|
+
labels_right = labels[half_size:] # Second half for the right hemisphere
|
|
148
|
+
|
|
149
|
+
# Create GiftiDataArrays for each hemisphere
|
|
150
|
+
data_array_left = GiftiDataArray(data=labels_left)
|
|
151
|
+
data_array_right = GiftiDataArray(data=labels_right)
|
|
152
|
+
|
|
153
|
+
# Create separate GiftiImages for each hemisphere
|
|
154
|
+
labels_left_gii = GiftiImage(darrays=[data_array_left])
|
|
155
|
+
labels_right_gii = GiftiImage(darrays=[data_array_right])
|
|
156
|
+
|
|
157
|
+
# Save to temporary files for abagen compatibility
|
|
158
|
+
labels_files = []
|
|
159
|
+
for img in [labels_left_gii, labels_right_gii]:
|
|
160
|
+
f = tempfile.NamedTemporaryFile(suffix=".gii", delete=False)
|
|
161
|
+
f.close()
|
|
162
|
+
nib.save(img, f.name)
|
|
163
|
+
labels_files.append(f.name)
|
|
164
|
+
temp_files.append(f.name)
|
|
165
|
+
labels = tuple(labels_files)
|
|
166
|
+
|
|
167
|
+
surfaces_gii = []
|
|
168
|
+
for surface in surfaces:
|
|
169
|
+
if not isinstance(surface, str) and not isinstance(surface, Path):
|
|
170
|
+
# Cast surface data to float32 to comply with GIFTI standard
|
|
171
|
+
# GIFTI only supports uint8, int32, and float32 datatypes
|
|
172
|
+
if hasattr(surface, 'Points') and surface.Points.dtype != np.float32:
|
|
173
|
+
surface = surface.copy()
|
|
174
|
+
surface.Points = surface.Points.astype(np.float32)
|
|
175
|
+
|
|
176
|
+
f = tempfile.NamedTemporaryFile(suffix=".gii", delete=False)
|
|
177
|
+
f.close()
|
|
178
|
+
write_surface(surface, f.name, otype="gii")
|
|
179
|
+
surfaces_gii.append(f.name)
|
|
180
|
+
temp_files.append(f.name)
|
|
181
|
+
else:
|
|
182
|
+
surfaces_gii.append(surface)
|
|
183
|
+
|
|
184
|
+
# Use abagen to grab expression data.
|
|
185
|
+
logger.info(
|
|
186
|
+
"If you use BrainStat's genetics functionality, please cite abagen (https://abagen.readthedocs.io/en/stable/citing.html)."
|
|
187
|
+
)
|
|
188
|
+
atlas = check_atlas(labels, geometry=surfaces_gii, space=space)
|
|
189
|
+
expression = get_expression_data(
|
|
190
|
+
atlas,
|
|
191
|
+
atlas_info=atlas_info,
|
|
192
|
+
ibf_threshold=ibf_threshold,
|
|
193
|
+
probe_selection=probe_selection,
|
|
194
|
+
donor_probes=donor_probes,
|
|
195
|
+
lr_mirror=lr_mirror,
|
|
196
|
+
missing=missing,
|
|
197
|
+
tolerance=tolerance,
|
|
198
|
+
sample_norm=sample_norm,
|
|
199
|
+
gene_norm=gene_norm,
|
|
200
|
+
norm_matched=norm_matched,
|
|
201
|
+
norm_structures=norm_structures,
|
|
202
|
+
region_agg=region_agg,
|
|
203
|
+
agg_metric=agg_metric,
|
|
204
|
+
corrected_mni=corrected_mni,
|
|
205
|
+
reannotated=reannotated,
|
|
206
|
+
return_counts=return_counts,
|
|
207
|
+
return_donors=return_donors,
|
|
208
|
+
return_report=return_report,
|
|
209
|
+
donors=donors,
|
|
210
|
+
data_dir=data_dir,
|
|
211
|
+
verbose=verbose,
|
|
212
|
+
n_proc=n_proc,
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
return expression
|
|
216
|
+
finally:
|
|
217
|
+
for f in temp_files:
|
|
218
|
+
try:
|
|
219
|
+
Path(f).unlink()
|
|
220
|
+
except FileNotFoundError:
|
|
221
|
+
pass
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def __create_precomputed(
|
|
225
|
+
data_dir: Optional[Union[str, Path]] = None,
|
|
226
|
+
output_dir: Optional[Union[str, Path]] = None,
|
|
227
|
+
) -> None:
|
|
228
|
+
"""Creates precomputed matrices of genetic expression for standard atlases. These are
|
|
229
|
+
used for the MATLAB implementation."""
|
|
230
|
+
|
|
231
|
+
if output_dir is None:
|
|
232
|
+
output_dir = data_dir = (
|
|
233
|
+
Path(data_dir) if data_dir else data_directories["BRAINSTAT_DATA_DIR"]
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
# Get all parcellations in a format compatible with ParameterGrid.
|
|
237
|
+
parcellations = _valid_parcellations()
|
|
238
|
+
for key in parcellations:
|
|
239
|
+
parcellations[key].update({"atlas": [key]})
|
|
240
|
+
if key == "schaefer":
|
|
241
|
+
parcellations[key].update({"seven_networks": (True, False)})
|
|
242
|
+
else:
|
|
243
|
+
parcellations[key].update({"seven_networks": (True,)})
|
|
244
|
+
|
|
245
|
+
# We only really need to compute this for one template surface.
|
|
246
|
+
# We'll use fsaverage5 for all.
|
|
247
|
+
parcellations[key].update({"surfaces": ["fsaverage5"]})
|
|
248
|
+
|
|
249
|
+
param_grid = ParameterGrid(list(parcellations.values()))
|
|
250
|
+
|
|
251
|
+
# Compute expression for all parcellations.
|
|
252
|
+
for params in param_grid:
|
|
253
|
+
if params["atlas"] == "schaefer":
|
|
254
|
+
network_tag = "7Networks" if params["seven_networks"] else "17Networks"
|
|
255
|
+
else:
|
|
256
|
+
network_tag = ""
|
|
257
|
+
|
|
258
|
+
filename_components = filter(
|
|
259
|
+
None,
|
|
260
|
+
(
|
|
261
|
+
"expression",
|
|
262
|
+
params["atlas"],
|
|
263
|
+
str(params["n_regions"]),
|
|
264
|
+
network_tag,
|
|
265
|
+
),
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
filename = "_".join(filename_components) + ".csv.gz"
|
|
269
|
+
output_file = Path(output_dir) / filename
|
|
270
|
+
if output_file.exists():
|
|
271
|
+
print('Skipping "{}"'.format(output_file))
|
|
272
|
+
continue
|
|
273
|
+
else:
|
|
274
|
+
print('Computing "{}"'.format(output_file))
|
|
275
|
+
|
|
276
|
+
surface_files = _fetch_template_surface_files(
|
|
277
|
+
params["surfaces"], data_dir=data_dir # type: ignore
|
|
278
|
+
)
|
|
279
|
+
space = "fslr" if params["surfaces"] == "fslr32k" else "fsaverage"
|
|
280
|
+
|
|
281
|
+
labels = fetch_parcellation(
|
|
282
|
+
params["surfaces"],
|
|
283
|
+
params["atlas"],
|
|
284
|
+
params["n_regions"],
|
|
285
|
+
seven_networks=params["seven_networks"],
|
|
286
|
+
data_dir=data_dir,
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
surf_lh = tempfile.NamedTemporaryFile(suffix=".surf.gii")
|
|
290
|
+
surf_rh = tempfile.NamedTemporaryFile(suffix=".surf.gii")
|
|
291
|
+
for i, surf in enumerate((surf_lh, surf_rh)):
|
|
292
|
+
__freesurfer_to_surfgii(surface_files[i], surf.name)
|
|
293
|
+
surface_paths = [surf_lh.name, surf_rh.name]
|
|
294
|
+
|
|
295
|
+
expression = surface_genetic_expression(labels, surface_paths, space=space) # type: ignore
|
|
296
|
+
expression.to_csv(output_file) # type: ignore
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def __freesurfer_to_surfgii(freesurfer_file: str, gifti_file: str) -> None:
|
|
300
|
+
surf = read_surface(freesurfer_file, itype="fs")
|
|
301
|
+
write_surface(surf, gifti_file, otype="gii")
|
|
@@ -156,7 +156,19 @@ def read_histology_profile(
|
|
|
156
156
|
)
|
|
157
157
|
|
|
158
158
|
with h5py.File(histology_file, "r") as h5_file:
|
|
159
|
-
|
|
159
|
+
if template == "fslr32k":
|
|
160
|
+
# Known issue #369: The fslr32k data file on the server actually contains
|
|
161
|
+
# fs_LR_64k resolution data (64984 vertices) instead of fs_LR_32k (32492 vertices).
|
|
162
|
+
# This is a data hosting issue. Users expecting 32k resolution data should be aware
|
|
163
|
+
# that they will receive 64k resolution data until this is fixed on the server.
|
|
164
|
+
logger.warning(
|
|
165
|
+
"Known issue: The fslr32k histology profile data currently contains "
|
|
166
|
+
"fs_LR_64k resolution (64984 vertices) instead of fs_LR_32k (32492 vertices). "
|
|
167
|
+
"See https://github.com/MICA-MNI/BrainStat/issues/369 for details."
|
|
168
|
+
)
|
|
169
|
+
profiles = h5_file.get("fs_LR_64k")[...]
|
|
170
|
+
else:
|
|
171
|
+
profiles = h5_file.get(template)[...]
|
|
160
172
|
if civet_template:
|
|
161
173
|
fsaverage_surface = fetch_template_surface("fsaverage")
|
|
162
174
|
civet_surface = fetch_template_surface(civet_template)
|
|
@@ -191,7 +203,16 @@ def download_histology_profiles(
|
|
|
191
203
|
|
|
192
204
|
data_dir = Path(data_dir) if data_dir else data_directories["BIGBRAIN_DATA_DIR"]
|
|
193
205
|
data_dir.mkdir(parents=True, exist_ok=True)
|
|
194
|
-
|
|
206
|
+
if template == "fslr32k":
|
|
207
|
+
output_file = data_dir / "histology_fslr32k.h5"
|
|
208
|
+
# Known issue #369: warn users about data resolution mismatch
|
|
209
|
+
logger.warning(
|
|
210
|
+
"Note: The fslr32k histology profile data currently contains "
|
|
211
|
+
"fs_LR_64k resolution (64984 vertices) instead of fs_LR_32k. "
|
|
212
|
+
"See https://github.com/MICA-MNI/BrainStat/issues/369 for details."
|
|
213
|
+
)
|
|
214
|
+
else:
|
|
215
|
+
output_file = data_dir / ("histology_" + template + ".h5")
|
|
195
216
|
|
|
196
217
|
url = read_data_fetcher_json()["bigbrain_profiles"][template]["url"]
|
|
197
218
|
|
|
@@ -11,7 +11,9 @@ import numpy as np
|
|
|
11
11
|
import pandas as pd
|
|
12
12
|
import templateflow.api as tflow
|
|
13
13
|
from brainspace.vtk_interface.wrappers.data_object import BSPolyData
|
|
14
|
-
from scipy.stats.stats import pearsonr
|
|
14
|
+
from scipy.stats.stats import pearsonr, spearmanr
|
|
15
|
+
from math import pi
|
|
16
|
+
import matplotlib.pyplot as plt
|
|
15
17
|
|
|
16
18
|
from brainstat._utils import (
|
|
17
19
|
data_directories,
|
|
@@ -25,6 +27,7 @@ from brainstat.mesh.interpolate import _surf2vol, multi_surface_to_volume
|
|
|
25
27
|
def meta_analytic_decoder(
|
|
26
28
|
template: str,
|
|
27
29
|
stat_labels: np.ndarray,
|
|
30
|
+
corrtype: Optional[str] = "pearson",
|
|
28
31
|
data_dir: Optional[Union[str, Path]] = None,
|
|
29
32
|
):
|
|
30
33
|
"""Meta-analytic decoding of surface maps using NeuroSynth or NeuroQuery.
|
|
@@ -36,6 +39,8 @@ def meta_analytic_decoder(
|
|
|
36
39
|
stat_labels : str, numpy.ndarray, sequence of str or numpy.ndarray
|
|
37
40
|
Path to a label file for the surfaces, numpy array containing the
|
|
38
41
|
labels, or a list containing multiple of the aforementioned.
|
|
42
|
+
corrtype : str, optional
|
|
43
|
+
Correlation type {'pearson', 'spearman'}. Default is 'pearson'.
|
|
39
44
|
data_dir : str, optional
|
|
40
45
|
The directory of the dataset. If none exists, a new dataset will
|
|
41
46
|
be downloaded and saved to this path. If None, the directory defaults to
|
|
@@ -74,9 +79,14 @@ def meta_analytic_decoder(
|
|
|
74
79
|
| np.isnan(stat_vector)
|
|
75
80
|
| np.isinf(stat_vector)
|
|
76
81
|
)
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
82
|
+
if corrtype == "pearson":
|
|
83
|
+
correlations[i], _ = pearsonr(stat_vector[keep], feature_data[keep])
|
|
84
|
+
colname = "Pearson's r"
|
|
85
|
+
elif corrtype == "spearman":
|
|
86
|
+
correlations[i], _ = spearmanr(stat_vector[keep], feature_data[keep])
|
|
87
|
+
colname = "Spearman's r"
|
|
88
|
+
|
|
89
|
+
df = pd.DataFrame(correlations, index=feature_names, columns=[colname])
|
|
80
90
|
return df.sort_values(by="Pearson's r", ascending=False)
|
|
81
91
|
|
|
82
92
|
|
|
@@ -224,3 +234,86 @@ def _fetch_precomputed_neurosynth(data_dir: Path) -> Generator[Path, None, None]
|
|
|
224
234
|
(Path(name)).unlink()
|
|
225
235
|
|
|
226
236
|
return data_dir.glob("Neurosynth_TFIDF__*z_desc-consistency.nii.gz")
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def radar_plot(data=None, title="", axis_range=None, label=None, color=(0, 0, 0)):
|
|
240
|
+
"""Visualize data in radar plot (author: @saratheriver)
|
|
241
|
+
Parameters
|
|
242
|
+
----------
|
|
243
|
+
data : ndarray, shape = (n_val,)
|
|
244
|
+
Data.
|
|
245
|
+
title : string, optional
|
|
246
|
+
Title of spider plot. Default is empty.
|
|
247
|
+
axis_range : tuple, optional
|
|
248
|
+
Range of spider plot axes. Default is (min, max).
|
|
249
|
+
label : list, optional
|
|
250
|
+
List of axis labels. Length = same as data.shape[0]. Default is empty.
|
|
251
|
+
color : tuple, optional
|
|
252
|
+
Color of line. Default is (0, 0, 0).
|
|
253
|
+
|
|
254
|
+
Returns
|
|
255
|
+
-------
|
|
256
|
+
class_mean : ndarray, shape = (data.shape[0],)
|
|
257
|
+
Values for each branch.
|
|
258
|
+
figure
|
|
259
|
+
Spider plot.
|
|
260
|
+
"""
|
|
261
|
+
# Data check
|
|
262
|
+
if data is None:
|
|
263
|
+
print("Need data to plot")
|
|
264
|
+
|
|
265
|
+
# Create dataframe
|
|
266
|
+
class_mean = pd.DataFrame(data=data.T, columns=label)
|
|
267
|
+
|
|
268
|
+
# Number of variable
|
|
269
|
+
categories = list(class_mean)
|
|
270
|
+
N = len(categories)
|
|
271
|
+
|
|
272
|
+
# We are going to plot the first line of the data frame.
|
|
273
|
+
# But we need to repeat the first value to close the circular graph:
|
|
274
|
+
values = class_mean.loc[0].values.flatten().tolist()
|
|
275
|
+
values += values[:1]
|
|
276
|
+
|
|
277
|
+
# What will be the angle of each axis in the plot? (we divide the plot / number of variable)
|
|
278
|
+
angles = [n / float(N) * 2 * pi for n in range(N)]
|
|
279
|
+
angles += angles[:1]
|
|
280
|
+
|
|
281
|
+
# Initialise the spider plot
|
|
282
|
+
plt.figure(figsize=(8, 4))
|
|
283
|
+
ax = plt.subplot(111, polar=True)
|
|
284
|
+
ax.spines["polar"].set_visible(False)
|
|
285
|
+
|
|
286
|
+
# Draw one axe per variable + add labels labels yet
|
|
287
|
+
if label is None:
|
|
288
|
+
label = list(class_mean)
|
|
289
|
+
|
|
290
|
+
plt.xticks(angles[:-1], label, color="black", size=12)
|
|
291
|
+
ax.xaxis.get_majorticklabels()[0].set_horizontalalignment("left")
|
|
292
|
+
ax.xaxis.get_majorticklabels()[2].set_verticalalignment("bottom")
|
|
293
|
+
ax.xaxis.get_majorticklabels()[3].set_verticalalignment("top")
|
|
294
|
+
|
|
295
|
+
# Draw ylabels
|
|
296
|
+
ax.set_rlabel_position(0)
|
|
297
|
+
if axis_range is None:
|
|
298
|
+
axis_range = (np.min(values), np.max(values))
|
|
299
|
+
|
|
300
|
+
inc = (axis_range[1] - axis_range[0]) / 4
|
|
301
|
+
newinc = [
|
|
302
|
+
axis_range[0] + inc,
|
|
303
|
+
axis_range[0] + (inc * 2),
|
|
304
|
+
axis_range[0] + (inc * 3),
|
|
305
|
+
axis_range[0],
|
|
306
|
+
]
|
|
307
|
+
plt.yticks(
|
|
308
|
+
newinc, [str("{:.2f}".format(elem)) for elem in newinc], color="grey", size=10
|
|
309
|
+
)
|
|
310
|
+
plt.ylim(axis_range)
|
|
311
|
+
|
|
312
|
+
# add title
|
|
313
|
+
if title:
|
|
314
|
+
plt.title(title)
|
|
315
|
+
|
|
316
|
+
# Plot data
|
|
317
|
+
ax.plot(angles, values, linewidth=3, linestyle="solid", color=color)
|
|
318
|
+
|
|
319
|
+
return class_mean
|
|
@@ -53,8 +53,8 @@
|
|
|
53
53
|
"glasser": {
|
|
54
54
|
"fsaverage": {
|
|
55
55
|
"url": [
|
|
56
|
-
"https://box.bic.mni.mcgill.ca/s/
|
|
57
|
-
"https://box.bic.mni.mcgill.ca/s/
|
|
56
|
+
"https://box.bic.mni.mcgill.ca/s/j4nfMA4D7jSx3QZ/download",
|
|
57
|
+
"https://box.bic.mni.mcgill.ca/s/qZTplkH4A4exOnF/download"
|
|
58
58
|
]
|
|
59
59
|
},
|
|
60
60
|
"fsaverage5": {
|