brainstat 0.6.0__tar.gz → 0.7.0__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.6.0/brainstat.egg-info → brainstat-0.7.0}/PKG-INFO +7 -5
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/__init__.py +1 -1
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/_utils.py +6 -2
- brainstat-0.7.0/brainstat/context/genetics.py +351 -0
- brainstat-0.7.0/brainstat/reporting/__init__.py +5 -0
- brainstat-0.7.0/brainstat/reporting/_slm.py +316 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/stats/SLM.py +16 -0
- brainstat-0.7.0/brainstat/tests/test_download.py +65 -0
- brainstat-0.7.0/brainstat/tests/test_genetics.py +215 -0
- brainstat-0.7.0/brainstat/tests/test_reporting.py +136 -0
- {brainstat-0.6.0 → brainstat-0.7.0/brainstat.egg-info}/PKG-INFO +7 -5
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat.egg-info/SOURCES.txt +4 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat.egg-info/requires.txt +3 -1
- {brainstat-0.6.0 → brainstat-0.7.0}/setup.py +6 -4
- brainstat-0.6.0/brainstat/context/genetics.py +0 -301
- brainstat-0.6.0/brainstat/tests/test_genetics.py +0 -33
- {brainstat-0.6.0 → brainstat-0.7.0}/LICENSE +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/MANIFEST.in +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/README.rst +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/_typing.py +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/context/__init__.py +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/context/histology.py +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/context/meta_analysis.py +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/context/resting.py +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/data/data_urls.json +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/data/logging.conf +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/data/nn_interp_civet164k.nii.gz +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/data/nn_interp_civet41k.nii.gz +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/data/nn_interp_fsaverage.nii.gz +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/data/nn_interp_fsaverage5.nii.gz +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/data/nn_interp_fslr32k.nii.gz +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/datasets/__init__.py +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/datasets/base.py +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/mesh/__init__.py +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/mesh/data.py +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/mesh/interpolate.py +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/mesh/utils.py +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/stats/__init__.py +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/stats/_linear_model.py +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/stats/_multiple_comparisons.py +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/stats/_t_test.py +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/stats/terms.py +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/stats/utils.py +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/__init__.py +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/conftest.py +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/datagen_f.py +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/datagen_fdr.py +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/datagen_linear_model.py +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/datagen_mesh_edges.py +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/datagen_mesh_smooth.py +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/datagen_peak_clus.py +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/datagen_random_field_theory.py +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/datagen_resels.py +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/datagen_slm.py +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/datagen_stat_threshold.py +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/datagen_t_test.py +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/test_SLM.py +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/test_datasets.py +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/test_f_test.py +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/test_fdr.py +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/test_histology.py +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/test_linear_model.py +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/test_mesh_edges.py +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/test_mesh_smooth.py +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/test_peak_clus.py +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/test_random_field_theory.py +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/test_resels.py +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/test_stat_threshold.py +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/test_t_test.py +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/test_terms.py +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/testutil.py +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tutorial/__init__.py +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tutorial/utils.py +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat.egg-info/dependency_links.txt +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/brainstat.egg-info/top_level.txt +0 -0
- {brainstat-0.6.0 → brainstat-0.7.0}/setup.cfg +0 -0
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: brainstat
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: A toolbox for statistical analysis of neuroimaging data
|
|
5
|
-
Home-page: https://github.com/MICA-
|
|
5
|
+
Home-page: https://github.com/MICA-MNI/BrainStat
|
|
6
6
|
Author: MNI-MICA Lab and MPI-CNG Lab
|
|
7
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
|
-
Project-URL: Bug Reports, https://github.com/MICA-
|
|
11
|
-
Project-URL: Source, https://github.com/MICA-
|
|
10
|
+
Project-URL: Bug Reports, https://github.com/MICA-MNI/BrainStat/issues
|
|
11
|
+
Project-URL: Source, https://github.com/MICA-MNI/BrainStat/
|
|
12
12
|
Classifier: Intended Audience :: Science/Research
|
|
13
13
|
Classifier: Intended Audience :: Developers
|
|
14
14
|
Classifier: Topic :: Software Development :: Build Tools
|
|
@@ -18,13 +18,15 @@ Requires-Python: >=3.10
|
|
|
18
18
|
Description-Content-Type: text/x-rst
|
|
19
19
|
License-File: LICENSE
|
|
20
20
|
Requires-Dist: abagen>=0.1
|
|
21
|
+
Requires-Dist: setuptools<81
|
|
22
|
+
Requires-Dist: statsmodels
|
|
21
23
|
Requires-Dist: brainspace>=0.1.2
|
|
22
24
|
Requires-Dist: h5py
|
|
23
25
|
Requires-Dist: matplotlib>=3.4
|
|
24
26
|
Requires-Dist: netneurotools<0.3.0
|
|
25
27
|
Requires-Dist: neurosynth
|
|
26
28
|
Requires-Dist: nibabel
|
|
27
|
-
Requires-Dist: nilearn>=0.
|
|
29
|
+
Requires-Dist: nilearn>=0.10.4
|
|
28
30
|
Requires-Dist: nimare
|
|
29
31
|
Requires-Dist: numpy<2,>=1.21
|
|
30
32
|
Requires-Dist: pandas<2.1,>=1.3
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""Neuroimaging statistics toolbox."""
|
|
2
|
-
__version__ = "0.
|
|
2
|
+
__version__ = "0.7.0"
|
|
@@ -6,6 +6,7 @@ import shutil
|
|
|
6
6
|
import urllib.request
|
|
7
7
|
import warnings
|
|
8
8
|
from pathlib import Path
|
|
9
|
+
from tempfile import TemporaryDirectory
|
|
9
10
|
from typing import Callable
|
|
10
11
|
|
|
11
12
|
import brainstat
|
|
@@ -179,8 +180,11 @@ def _download_file(
|
|
|
179
180
|
|
|
180
181
|
for attempt in range(max_retries):
|
|
181
182
|
try:
|
|
182
|
-
with
|
|
183
|
-
|
|
183
|
+
with TemporaryDirectory(dir=output_file.parent) as tmpdir:
|
|
184
|
+
temporary_file = Path(tmpdir) / output_file.name
|
|
185
|
+
with urllib.request.urlopen(url, timeout=30) as response, open(temporary_file, "wb") as out_file:
|
|
186
|
+
shutil.copyfileobj(response, out_file)
|
|
187
|
+
temporary_file.replace(output_file)
|
|
184
188
|
return # Success, exit function
|
|
185
189
|
except (RemoteDisconnected, URLError, TimeoutError) as e:
|
|
186
190
|
if attempt < max_retries - 1:
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
"""Genetic decoding using abagen."""
|
|
2
|
+
|
|
3
|
+
import tempfile
|
|
4
|
+
from contextlib import contextmanager
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from threading import RLock
|
|
7
|
+
from typing import Optional, Sequence, Union
|
|
8
|
+
|
|
9
|
+
import abagen.images
|
|
10
|
+
import nibabel as nib
|
|
11
|
+
import numpy as np
|
|
12
|
+
import pandas as pd
|
|
13
|
+
from abagen import check_atlas, get_expression_data
|
|
14
|
+
from brainspace.mesh.mesh_io import read_surface, write_surface
|
|
15
|
+
from nibabel.gifti import GiftiDataArray, GiftiImage
|
|
16
|
+
from sklearn.model_selection import ParameterGrid
|
|
17
|
+
|
|
18
|
+
from brainstat._utils import data_directories, logger
|
|
19
|
+
from brainstat.datasets.base import (
|
|
20
|
+
_fetch_template_surface_files,
|
|
21
|
+
_valid_parcellations,
|
|
22
|
+
fetch_parcellation,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
_COMPAT_LOCK = RLock()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _labeltable_to_df(labels):
|
|
29
|
+
rows = [
|
|
30
|
+
dict(id=key, label=value, hemisphere=hemi, structure="cortex")
|
|
31
|
+
for table, hemi in zip(labels, ("L", "R"))
|
|
32
|
+
for key, value in table.items()
|
|
33
|
+
if key != 0
|
|
34
|
+
]
|
|
35
|
+
if rows:
|
|
36
|
+
return pd.DataFrame(rows).set_index("id").sort_index()
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@contextmanager
|
|
41
|
+
def _abagen_compat():
|
|
42
|
+
"""Scope legacy abagen adapters to a call and always restore originals.
|
|
43
|
+
|
|
44
|
+
The lock serializes BrainStat genetics calls. The pandas adapters are
|
|
45
|
+
process-wide while the call runs; callers using abagen/pandas directly in
|
|
46
|
+
other threads should avoid running them concurrently with this workflow.
|
|
47
|
+
Dependency bounds remain in place until upstream supports newer pandas.
|
|
48
|
+
"""
|
|
49
|
+
with _COMPAT_LOCK:
|
|
50
|
+
original_append = getattr(pd.DataFrame, "append", None)
|
|
51
|
+
original_set_axis = pd.DataFrame.set_axis
|
|
52
|
+
original_labels = abagen.images.labeltable_to_df
|
|
53
|
+
|
|
54
|
+
def append(
|
|
55
|
+
frame, other, ignore_index=False, verify_integrity=False, sort=False
|
|
56
|
+
):
|
|
57
|
+
if isinstance(other, pd.Series):
|
|
58
|
+
other = other.to_frame().T
|
|
59
|
+
objects = [frame] + (
|
|
60
|
+
list(other) if isinstance(other, (list, tuple)) else [other]
|
|
61
|
+
)
|
|
62
|
+
return pd.concat(
|
|
63
|
+
objects,
|
|
64
|
+
ignore_index=ignore_index,
|
|
65
|
+
verify_integrity=verify_integrity,
|
|
66
|
+
sort=sort,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
def set_axis(frame, labels, *args, **kwargs):
|
|
70
|
+
inplace = kwargs.pop("inplace", False)
|
|
71
|
+
if inplace:
|
|
72
|
+
raise ValueError(
|
|
73
|
+
"abagen compatibility supports set_axis(inplace=False) only"
|
|
74
|
+
)
|
|
75
|
+
return original_set_axis(frame, labels, *args, **kwargs)
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
if original_append is None:
|
|
79
|
+
pd.DataFrame.append = append
|
|
80
|
+
pd.DataFrame.set_axis = set_axis
|
|
81
|
+
abagen.images.labeltable_to_df = _labeltable_to_df
|
|
82
|
+
yield
|
|
83
|
+
finally:
|
|
84
|
+
if original_append is None:
|
|
85
|
+
del pd.DataFrame.append
|
|
86
|
+
else:
|
|
87
|
+
pd.DataFrame.append = original_append
|
|
88
|
+
pd.DataFrame.set_axis = original_set_axis
|
|
89
|
+
abagen.images.labeltable_to_df = original_labels
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _surface_size(surface):
|
|
93
|
+
if isinstance(surface, (str, Path)):
|
|
94
|
+
surface = read_surface(str(surface))
|
|
95
|
+
return surface.n_points
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def surface_genetic_expression(
|
|
99
|
+
labels: Union[Sequence[str], np.ndarray],
|
|
100
|
+
surfaces: Optional[Union[str, Sequence[str]]] = None,
|
|
101
|
+
space: Optional[str] = None,
|
|
102
|
+
*,
|
|
103
|
+
atlas_info: Optional[str] = None,
|
|
104
|
+
ibf_threshold: float = 0.5,
|
|
105
|
+
probe_selection: str = "diff_stability",
|
|
106
|
+
donor_probes: str = "aggregate",
|
|
107
|
+
lr_mirror: Optional[bool] = None,
|
|
108
|
+
missing: Optional[str] = None,
|
|
109
|
+
tolerance: float = 2,
|
|
110
|
+
sample_norm: str = "srs",
|
|
111
|
+
gene_norm: str = "srs",
|
|
112
|
+
norm_matched: bool = True,
|
|
113
|
+
norm_structures: bool = False,
|
|
114
|
+
region_agg: str = "donors",
|
|
115
|
+
agg_metric: str = "mean",
|
|
116
|
+
corrected_mni: bool = True,
|
|
117
|
+
reannotated: bool = True,
|
|
118
|
+
return_counts: bool = False,
|
|
119
|
+
return_donors: bool = False,
|
|
120
|
+
return_report: bool = False,
|
|
121
|
+
donors: str = "all",
|
|
122
|
+
data_dir: Optional[str] = None,
|
|
123
|
+
verbose: float = 0,
|
|
124
|
+
n_proc: int = 1,
|
|
125
|
+
) -> Union[pd.DataFrame, dict, tuple]:
|
|
126
|
+
"""Computes genetic expression of surface parcels.
|
|
127
|
+
|
|
128
|
+
Parameters
|
|
129
|
+
----------
|
|
130
|
+
labels : list-of-str or numpy.ndarray
|
|
131
|
+
List of paths to label files for the parcellation, or numpy array
|
|
132
|
+
containing the pre-loaded labels
|
|
133
|
+
surfaces : list-of-image, optional
|
|
134
|
+
List of paths to surface files or preloaded surfaces. If not specified
|
|
135
|
+
assumes that `labels` are on the `fsaverage5` surface. Default: None
|
|
136
|
+
space : {'fsaverage', 'fslr'}
|
|
137
|
+
What template space `surfaces` are aligned to. If not specified assumes
|
|
138
|
+
that `labels` are on the `fsaverage5` surface. Default: None
|
|
139
|
+
|
|
140
|
+
For details of the remaining parameters please consult the
|
|
141
|
+
abagen.get_expression_data() documentation. All its parameters bar "atlas"
|
|
142
|
+
are valid input parameters.
|
|
143
|
+
|
|
144
|
+
Returns
|
|
145
|
+
-------
|
|
146
|
+
pandas.DataFrame, dict or tuple
|
|
147
|
+
Expression data returned unchanged from abagen. The return_counts,
|
|
148
|
+
return_donors and return_report options can change the return type.
|
|
149
|
+
|
|
150
|
+
Examples
|
|
151
|
+
--------
|
|
152
|
+
>>> from brainstat.context.genetics import surface_genetic_expression
|
|
153
|
+
>>> from nilearn import datasets
|
|
154
|
+
>>> import numpy as np
|
|
155
|
+
|
|
156
|
+
>>> destrieux = datasets.fetch_atlas_surf_destrieux()
|
|
157
|
+
>>> labels = np.hstack((destrieux['map_left'], destrieux['map_right']))
|
|
158
|
+
>>> fsaverage = datasets.fetch_surf_fsaverage()
|
|
159
|
+
>>> surfaces = (fsaverage['pial_left'], fsaverage['pial_right'])
|
|
160
|
+
>>> expression = surface_genetic_expression(labels, surfaces,
|
|
161
|
+
... space='fsaverage')
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
if isinstance(surfaces, (str, Path)):
|
|
165
|
+
surfaces = [surfaces]
|
|
166
|
+
elif surfaces is not None:
|
|
167
|
+
surfaces = list(surfaces)
|
|
168
|
+
|
|
169
|
+
if surfaces is not None and len(surfaces) != 2:
|
|
170
|
+
raise ValueError("Provide both left and right hemisphere surfaces.")
|
|
171
|
+
|
|
172
|
+
if isinstance(labels, np.ndarray):
|
|
173
|
+
if (
|
|
174
|
+
labels.ndim != 1
|
|
175
|
+
or labels.size == 0
|
|
176
|
+
or not np.issubdtype(labels.dtype, np.number)
|
|
177
|
+
or np.iscomplexobj(labels)
|
|
178
|
+
or not np.all(np.isfinite(labels))
|
|
179
|
+
or np.any(labels < 0)
|
|
180
|
+
or np.any(labels > np.iinfo(np.int32).max)
|
|
181
|
+
or not np.all(labels == np.floor(labels))
|
|
182
|
+
):
|
|
183
|
+
raise ValueError(
|
|
184
|
+
"labels must be a nonempty 1D array of nonnegative int32 label IDs."
|
|
185
|
+
)
|
|
186
|
+
if surfaces is None:
|
|
187
|
+
if labels.size != 20484:
|
|
188
|
+
raise ValueError(
|
|
189
|
+
"Without surfaces, labels must match fsaverage5 (20484 vertices)."
|
|
190
|
+
)
|
|
191
|
+
surfaces = list(
|
|
192
|
+
_fetch_template_surface_files(
|
|
193
|
+
"fsaverage5",
|
|
194
|
+
data_dir=(
|
|
195
|
+
Path(data_dir)
|
|
196
|
+
if data_dir
|
|
197
|
+
else data_directories["SURFACE_DATA_DIR"]
|
|
198
|
+
),
|
|
199
|
+
)
|
|
200
|
+
)
|
|
201
|
+
if space is None:
|
|
202
|
+
space = "fsaverage"
|
|
203
|
+
sizes = [_surface_size(surface) for surface in surfaces]
|
|
204
|
+
if sum(sizes) != labels.size:
|
|
205
|
+
raise ValueError(
|
|
206
|
+
"Label count does not match the supplied surface geometry."
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
# A directory owns every temporary file, including files whose save fails.
|
|
210
|
+
with tempfile.TemporaryDirectory(prefix="brainstat-genetics-") as tmpdir:
|
|
211
|
+
if isinstance(labels, np.ndarray):
|
|
212
|
+
arrays = np.split(labels.astype(np.int32), [sizes[0]])
|
|
213
|
+
label_files = []
|
|
214
|
+
for index, array in enumerate(arrays):
|
|
215
|
+
path = str(Path(tmpdir) / f"labels-{index}.label.gii")
|
|
216
|
+
image = GiftiImage(
|
|
217
|
+
darrays=[GiftiDataArray(data=array, intent="NIFTI_INTENT_LABEL")]
|
|
218
|
+
)
|
|
219
|
+
nib.save(image, path)
|
|
220
|
+
label_files.append(path)
|
|
221
|
+
labels = tuple(label_files)
|
|
222
|
+
else:
|
|
223
|
+
labels = tuple(str(path) for path in labels)
|
|
224
|
+
|
|
225
|
+
surfaces_gii = None
|
|
226
|
+
if surfaces is not None:
|
|
227
|
+
surfaces_gii = []
|
|
228
|
+
for index, surface in enumerate(surfaces):
|
|
229
|
+
if isinstance(surface, (str, Path)):
|
|
230
|
+
surfaces_gii.append(str(surface))
|
|
231
|
+
else:
|
|
232
|
+
# Cast a copy: converting the surface must not alter caller geometry.
|
|
233
|
+
surface = surface.copy()
|
|
234
|
+
surface.Points = surface.Points.astype(np.float32)
|
|
235
|
+
path = str(Path(tmpdir) / f"surface-{index}.surf.gii")
|
|
236
|
+
write_surface(surface, path, otype="gii")
|
|
237
|
+
surfaces_gii.append(path)
|
|
238
|
+
|
|
239
|
+
# Use abagen to grab expression data.
|
|
240
|
+
logger.info(
|
|
241
|
+
"If you use BrainStat's genetics functionality, please cite abagen (https://abagen.readthedocs.io/en/stable/citing.html)."
|
|
242
|
+
)
|
|
243
|
+
with _abagen_compat():
|
|
244
|
+
atlas = check_atlas(labels, geometry=surfaces_gii, space=space)
|
|
245
|
+
expression = get_expression_data(
|
|
246
|
+
atlas,
|
|
247
|
+
atlas_info=atlas_info,
|
|
248
|
+
ibf_threshold=ibf_threshold,
|
|
249
|
+
probe_selection=probe_selection,
|
|
250
|
+
donor_probes=donor_probes,
|
|
251
|
+
lr_mirror=lr_mirror,
|
|
252
|
+
missing=missing,
|
|
253
|
+
tolerance=tolerance,
|
|
254
|
+
sample_norm=sample_norm,
|
|
255
|
+
gene_norm=gene_norm,
|
|
256
|
+
norm_matched=norm_matched,
|
|
257
|
+
norm_structures=norm_structures,
|
|
258
|
+
region_agg=region_agg,
|
|
259
|
+
agg_metric=agg_metric,
|
|
260
|
+
corrected_mni=corrected_mni,
|
|
261
|
+
reannotated=reannotated,
|
|
262
|
+
return_counts=return_counts,
|
|
263
|
+
return_donors=return_donors,
|
|
264
|
+
return_report=return_report,
|
|
265
|
+
donors=donors,
|
|
266
|
+
data_dir=data_dir,
|
|
267
|
+
verbose=verbose,
|
|
268
|
+
n_proc=n_proc,
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
return expression
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def __create_precomputed(
|
|
275
|
+
data_dir: Optional[Union[str, Path]] = None,
|
|
276
|
+
output_dir: Optional[Union[str, Path]] = None,
|
|
277
|
+
) -> None:
|
|
278
|
+
"""Creates precomputed matrices of genetic expression for standard atlases. These are
|
|
279
|
+
used for the MATLAB implementation."""
|
|
280
|
+
|
|
281
|
+
if output_dir is None:
|
|
282
|
+
output_dir = data_dir = (
|
|
283
|
+
Path(data_dir) if data_dir else data_directories["BRAINSTAT_DATA_DIR"]
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
# Get all parcellations in a format compatible with ParameterGrid.
|
|
287
|
+
parcellations = _valid_parcellations()
|
|
288
|
+
for key in parcellations:
|
|
289
|
+
parcellations[key].update({"atlas": [key]})
|
|
290
|
+
if key == "schaefer":
|
|
291
|
+
parcellations[key].update({"seven_networks": (True, False)})
|
|
292
|
+
else:
|
|
293
|
+
parcellations[key].update({"seven_networks": (True,)})
|
|
294
|
+
|
|
295
|
+
# We only really need to compute this for one template surface.
|
|
296
|
+
# We'll use fsaverage5 for all.
|
|
297
|
+
parcellations[key].update({"surfaces": ["fsaverage5"]})
|
|
298
|
+
|
|
299
|
+
param_grid = ParameterGrid(list(parcellations.values()))
|
|
300
|
+
|
|
301
|
+
# Compute expression for all parcellations.
|
|
302
|
+
for params in param_grid:
|
|
303
|
+
if params["atlas"] == "schaefer":
|
|
304
|
+
network_tag = "7Networks" if params["seven_networks"] else "17Networks"
|
|
305
|
+
else:
|
|
306
|
+
network_tag = ""
|
|
307
|
+
|
|
308
|
+
filename_components = filter(
|
|
309
|
+
None,
|
|
310
|
+
(
|
|
311
|
+
"expression",
|
|
312
|
+
params["atlas"],
|
|
313
|
+
str(params["n_regions"]),
|
|
314
|
+
network_tag,
|
|
315
|
+
),
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
filename = "_".join(filename_components) + ".csv.gz"
|
|
319
|
+
output_file = Path(output_dir) / filename
|
|
320
|
+
if output_file.exists():
|
|
321
|
+
print('Skipping "{}"'.format(output_file))
|
|
322
|
+
continue
|
|
323
|
+
else:
|
|
324
|
+
print('Computing "{}"'.format(output_file))
|
|
325
|
+
|
|
326
|
+
surface_files = _fetch_template_surface_files(
|
|
327
|
+
params["surfaces"], data_dir=data_dir # type: ignore
|
|
328
|
+
)
|
|
329
|
+
space = "fslr" if params["surfaces"] == "fslr32k" else "fsaverage"
|
|
330
|
+
|
|
331
|
+
labels = fetch_parcellation(
|
|
332
|
+
params["surfaces"],
|
|
333
|
+
params["atlas"],
|
|
334
|
+
params["n_regions"],
|
|
335
|
+
seven_networks=params["seven_networks"],
|
|
336
|
+
data_dir=data_dir,
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
surf_lh = tempfile.NamedTemporaryFile(suffix=".surf.gii")
|
|
340
|
+
surf_rh = tempfile.NamedTemporaryFile(suffix=".surf.gii")
|
|
341
|
+
for i, surf in enumerate((surf_lh, surf_rh)):
|
|
342
|
+
__freesurfer_to_surfgii(surface_files[i], surf.name)
|
|
343
|
+
surface_paths = [surf_lh.name, surf_rh.name]
|
|
344
|
+
|
|
345
|
+
expression = surface_genetic_expression(labels, surface_paths, space=space) # type: ignore
|
|
346
|
+
expression.to_csv(output_file) # type: ignore
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def __freesurfer_to_surfgii(freesurfer_file: str, gifti_file: str) -> None:
|
|
350
|
+
surf = read_surface(freesurfer_file, itype="fs")
|
|
351
|
+
write_surface(surf, gifti_file, otype="gii")
|