brainstat 0.4.2__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.4.2/brainstat.egg-info → brainstat-0.5.2}/PKG-INFO +38 -7
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/__init__.py +1 -1
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/_utils.py +28 -3
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/context/genetics.py +131 -47
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/context/histology.py +23 -2
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/datasets/base.py +114 -3
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/mesh/interpolate.py +1 -1
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/stats/_linear_model.py +3 -1
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/stats/_multiple_comparisons.py +2 -2
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/stats/_t_test.py +1 -1
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/stats/terms.py +16 -7
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/stats/utils.py +4 -2
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tests/test_SLM.py +31 -0
- {brainstat-0.4.2 → 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.4.2 → brainstat-0.5.2}/brainstat/tests/test_mesh_edges.py +1 -1
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tests/test_terms.py +26 -2
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tutorial/utils.py +1 -1
- {brainstat-0.4.2 → brainstat-0.5.2/brainstat.egg-info}/PKG-INFO +38 -7
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat.egg-info/SOURCES.txt +1 -0
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat.egg-info/requires.txt +6 -5
- {brainstat-0.4.2 → brainstat-0.5.2}/setup.cfg +1 -1
- {brainstat-0.4.2 → brainstat-0.5.2}/setup.py +8 -9
- brainstat-0.4.2/brainstat/tests/test_histology.py +0 -21
- {brainstat-0.4.2 → brainstat-0.5.2}/LICENSE +0 -0
- {brainstat-0.4.2 → brainstat-0.5.2}/MANIFEST.in +0 -0
- {brainstat-0.4.2 → brainstat-0.5.2}/README.rst +0 -0
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/_typing.py +0 -0
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/context/__init__.py +0 -0
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/context/meta_analysis.py +0 -0
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/context/resting.py +0 -0
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/data/data_urls.json +0 -0
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/data/logging.conf +0 -0
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/data/nn_interp_civet164k.nii.gz +0 -0
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/data/nn_interp_civet41k.nii.gz +0 -0
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/data/nn_interp_fsaverage.nii.gz +0 -0
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/data/nn_interp_fsaverage5.nii.gz +0 -0
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/data/nn_interp_fslr32k.nii.gz +0 -0
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/datasets/__init__.py +0 -0
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/mesh/__init__.py +0 -0
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/mesh/data.py +0 -0
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/mesh/utils.py +0 -0
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/stats/SLM.py +0 -0
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/stats/__init__.py +0 -0
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tests/__init__.py +0 -0
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tests/conftest.py +0 -0
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tests/datagen_f.py +0 -0
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tests/datagen_fdr.py +0 -0
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tests/datagen_linear_model.py +0 -0
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tests/datagen_mesh_edges.py +0 -0
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tests/datagen_mesh_smooth.py +0 -0
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tests/datagen_peak_clus.py +0 -0
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tests/datagen_random_field_theory.py +0 -0
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tests/datagen_resels.py +0 -0
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tests/datagen_slm.py +0 -0
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tests/datagen_stat_threshold.py +0 -0
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tests/datagen_t_test.py +0 -0
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tests/test_f_test.py +0 -0
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tests/test_fdr.py +0 -0
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tests/test_linear_model.py +0 -0
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tests/test_mesh_smooth.py +0 -0
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tests/test_peak_clus.py +0 -0
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tests/test_random_field_theory.py +0 -0
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tests/test_resels.py +0 -0
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tests/test_stat_threshold.py +0 -0
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tests/test_t_test.py +0 -0
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tests/testutil.py +0 -0
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tutorial/__init__.py +0 -0
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat.egg-info/dependency_links.txt +0 -0
- {brainstat-0.4.2 → brainstat-0.5.2}/brainstat.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
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
|
|
@@ -13,13 +13,44 @@ Classifier: Intended Audience :: Science/Research
|
|
|
13
13
|
Classifier: Intended Audience :: Developers
|
|
14
14
|
Classifier: Topic :: Software Development :: Build Tools
|
|
15
15
|
Classifier: Topic :: Scientific/Engineering
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.
|
|
17
|
-
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
-
Requires-Python: >=3.7.*
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Requires-Python: >=3.10
|
|
20
18
|
Description-Content-Type: text/x-rst
|
|
21
|
-
Provides-Extra: dev
|
|
22
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
|
|
23
54
|
|
|
24
55
|
BrainStat : A toolbox for statistical analysis of neuroimaging data
|
|
25
56
|
===================================================================
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""Neuroimaging statistics toolbox."""
|
|
2
|
-
__version__ = "0.
|
|
2
|
+
__version__ = "0.5.2"
|
|
@@ -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__":
|
|
@@ -2,13 +2,59 @@
|
|
|
2
2
|
import tempfile
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
from typing import Optional, Sequence, Union
|
|
5
|
-
|
|
5
|
+
import os
|
|
6
6
|
import nibabel as nib
|
|
7
7
|
import numpy as np
|
|
8
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
|
|
9
54
|
from abagen import check_atlas, get_expression_data
|
|
10
55
|
from brainspace.mesh.mesh_io import read_surface, write_surface
|
|
11
56
|
from sklearn.model_selection import ParameterGrid
|
|
57
|
+
from nibabel.gifti import GiftiImage, GiftiDataArray
|
|
12
58
|
|
|
13
59
|
from brainstat._utils import data_directories, logger
|
|
14
60
|
from brainstat.datasets.base import (
|
|
@@ -23,7 +69,7 @@ def surface_genetic_expression(
|
|
|
23
69
|
surfaces: Optional[Union[str, Sequence[str]]] = None,
|
|
24
70
|
space: Optional[str] = None,
|
|
25
71
|
*,
|
|
26
|
-
atlas_info: str = None,
|
|
72
|
+
atlas_info: Optional[str] = None,
|
|
27
73
|
ibf_threshold: float = 0.5,
|
|
28
74
|
probe_selection: str = "diff_stability",
|
|
29
75
|
donor_probes: str = "aggregate",
|
|
@@ -89,52 +135,90 @@ def surface_genetic_expression(
|
|
|
89
135
|
elif surfaces is None:
|
|
90
136
|
surfaces = []
|
|
91
137
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if
|
|
95
|
-
#
|
|
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:
|
|
96
218
|
try:
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
surfaces_gii.append(nib.load(name))
|
|
101
|
-
finally:
|
|
102
|
-
Path(name).unlink()
|
|
103
|
-
else:
|
|
104
|
-
surfaces_gii.append(nib.load(surface))
|
|
105
|
-
|
|
106
|
-
# Use abagen to grab expression data.
|
|
107
|
-
logger.info(
|
|
108
|
-
"If you use BrainStat's genetics functionality, please cite abagen (https://abagen.readthedocs.io/en/stable/citing.html)."
|
|
109
|
-
)
|
|
110
|
-
atlas = check_atlas(labels, geometry=surfaces_gii, space=space)
|
|
111
|
-
expression = get_expression_data(
|
|
112
|
-
atlas,
|
|
113
|
-
atlas_info=atlas_info,
|
|
114
|
-
ibf_threshold=ibf_threshold,
|
|
115
|
-
probe_selection=probe_selection,
|
|
116
|
-
donor_probes=donor_probes,
|
|
117
|
-
lr_mirror=lr_mirror,
|
|
118
|
-
missing=missing,
|
|
119
|
-
tolerance=tolerance,
|
|
120
|
-
sample_norm=sample_norm,
|
|
121
|
-
gene_norm=gene_norm,
|
|
122
|
-
norm_matched=norm_matched,
|
|
123
|
-
norm_structures=norm_structures,
|
|
124
|
-
region_agg=region_agg,
|
|
125
|
-
agg_metric=agg_metric,
|
|
126
|
-
corrected_mni=corrected_mni,
|
|
127
|
-
reannotated=reannotated,
|
|
128
|
-
return_counts=return_counts,
|
|
129
|
-
return_donors=return_donors,
|
|
130
|
-
return_report=return_report,
|
|
131
|
-
donors=donors,
|
|
132
|
-
data_dir=data_dir,
|
|
133
|
-
verbose=verbose,
|
|
134
|
-
n_proc=n_proc,
|
|
135
|
-
)
|
|
136
|
-
|
|
137
|
-
return expression
|
|
219
|
+
Path(f).unlink()
|
|
220
|
+
except FileNotFoundError:
|
|
221
|
+
pass
|
|
138
222
|
|
|
139
223
|
|
|
140
224
|
def __create_precomputed(
|
|
@@ -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
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
""" Load external datasets. """
|
|
2
|
+
import json
|
|
2
3
|
import tempfile
|
|
3
4
|
import zipfile
|
|
5
|
+
from collections import namedtuple
|
|
4
6
|
from pathlib import Path
|
|
5
7
|
from typing import List, Optional, Tuple, Union
|
|
6
8
|
|
|
@@ -11,9 +13,19 @@ from brainspace.mesh.mesh_io import read_surface
|
|
|
11
13
|
from brainspace.mesh.mesh_operations import combine_surfaces
|
|
12
14
|
from brainspace.vtk_interface.wrappers.data_object import BSPolyData
|
|
13
15
|
from netneurotools import datasets as nnt_datasets
|
|
14
|
-
from netneurotools.civet import read_civet
|
|
15
16
|
from nibabel import load as nib_load
|
|
16
17
|
from nibabel.freesurfer.io import read_annot, read_geometry
|
|
18
|
+
from sklearn.utils import Bunch
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
from netneurotools.datasets.utils import _get_data_dir, _get_dataset_info
|
|
22
|
+
except ImportError:
|
|
23
|
+
from netneurotools.datasets.datasets_utils import _get_data_dir, _get_dataset_info
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
from nilearn.datasets._utils import fetch_files as _fetch_files
|
|
27
|
+
except ImportError:
|
|
28
|
+
from nilearn.datasets.utils import fetch_files as _fetch_files
|
|
17
29
|
|
|
18
30
|
from brainstat._utils import (
|
|
19
31
|
_download_file,
|
|
@@ -24,6 +36,65 @@ from brainstat._utils import (
|
|
|
24
36
|
from brainstat.mesh.interpolate import _surf2surf
|
|
25
37
|
|
|
26
38
|
|
|
39
|
+
def read_civet(fname: Union[str, Path]) -> Tuple[np.ndarray, np.ndarray]:
|
|
40
|
+
"""
|
|
41
|
+
Reads a CIVET surface file (MNI OBJ format).
|
|
42
|
+
|
|
43
|
+
Parameters
|
|
44
|
+
----------
|
|
45
|
+
fname : str or Path
|
|
46
|
+
Path to the CIVET surface file.
|
|
47
|
+
|
|
48
|
+
Returns
|
|
49
|
+
-------
|
|
50
|
+
vertices : numpy.ndarray
|
|
51
|
+
(N, 3) array of vertex coordinates.
|
|
52
|
+
faces : numpy.ndarray
|
|
53
|
+
(M, 3) array of face indices.
|
|
54
|
+
"""
|
|
55
|
+
fname = str(fname)
|
|
56
|
+
with open(fname, "rb") as f:
|
|
57
|
+
first_char = f.read(1)
|
|
58
|
+
|
|
59
|
+
if first_char == b"P":
|
|
60
|
+
with open(fname, "r") as f:
|
|
61
|
+
content = f.read().split()
|
|
62
|
+
|
|
63
|
+
n_vertices = int(content[6])
|
|
64
|
+
|
|
65
|
+
idx = 7
|
|
66
|
+
# Vertices
|
|
67
|
+
vertices = np.array(content[idx : idx + n_vertices * 3], dtype=float).reshape(
|
|
68
|
+
-1, 3
|
|
69
|
+
)
|
|
70
|
+
idx += n_vertices * 3
|
|
71
|
+
|
|
72
|
+
# Normals
|
|
73
|
+
idx += n_vertices * 3
|
|
74
|
+
|
|
75
|
+
n_triangles = int(content[idx])
|
|
76
|
+
idx += 1
|
|
77
|
+
|
|
78
|
+
color_flag = int(content[idx])
|
|
79
|
+
idx += 1
|
|
80
|
+
|
|
81
|
+
if color_flag == 0:
|
|
82
|
+
idx += 4
|
|
83
|
+
else:
|
|
84
|
+
idx += n_vertices * 4
|
|
85
|
+
|
|
86
|
+
# Check remaining items to decide if we need to skip
|
|
87
|
+
remaining = len(content) - idx
|
|
88
|
+
if remaining == n_triangles * 3 + n_triangles:
|
|
89
|
+
idx += n_triangles
|
|
90
|
+
|
|
91
|
+
triangles = np.array(content[idx:], dtype=int).reshape(-1, 3)
|
|
92
|
+
|
|
93
|
+
return vertices, triangles
|
|
94
|
+
else:
|
|
95
|
+
raise NotImplementedError("Binary CIVET files are not supported yet.")
|
|
96
|
+
|
|
97
|
+
|
|
27
98
|
def fetch_parcellation(
|
|
28
99
|
template: str,
|
|
29
100
|
atlas: str,
|
|
@@ -147,7 +218,7 @@ def fetch_template_surface(
|
|
|
147
218
|
surfaces_fs = [read_geometry(file) for file in surface_files]
|
|
148
219
|
surfaces = [build_polydata(surface[0], surface[1]) for surface in surfaces_fs]
|
|
149
220
|
elif template == "fslr32k":
|
|
150
|
-
surfaces = [read_surface(file) for file in surface_files]
|
|
221
|
+
surfaces = [read_surface(str(file)) for file in surface_files]
|
|
151
222
|
else:
|
|
152
223
|
surfaces_obj = [read_civet(file) for file in surface_files]
|
|
153
224
|
surfaces = [build_polydata(surface[0], surface[1]) for surface in surfaces_obj]
|
|
@@ -384,7 +455,7 @@ def _fetch_template_surface_files(
|
|
|
384
455
|
|
|
385
456
|
if template == "fslr32k":
|
|
386
457
|
layer = layer if layer else "midthickness"
|
|
387
|
-
bunch =
|
|
458
|
+
bunch = _fetch_conte69_fixed(data_dir=str(data_dir))
|
|
388
459
|
elif template == "civet41k" or template == "civet164k":
|
|
389
460
|
layer = layer if layer else "mid"
|
|
390
461
|
if layer == "sphere":
|
|
@@ -515,3 +586,43 @@ def _fetch_civet_spheres(template: str, data_dir: Path) -> Tuple[str, str]:
|
|
|
515
586
|
|
|
516
587
|
# Return two filenames to conform to other left/right hemisphere functions.
|
|
517
588
|
return (str(filename), str(filename))
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
SURFACE = namedtuple('Surface', ('lh', 'rh'))
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
def _fetch_conte69_fixed(data_dir=None, url=None, resume=True, verbose=1):
|
|
595
|
+
"""
|
|
596
|
+
Download files for Van Essen et al., 2012 Conte69 template.
|
|
597
|
+
Fixed version to handle encoding on Windows.
|
|
598
|
+
"""
|
|
599
|
+
dataset_name = 'tpl-conte69'
|
|
600
|
+
keys = ['midthickness', 'inflated', 'vinflated']
|
|
601
|
+
|
|
602
|
+
data_dir = _get_data_dir(data_dir=data_dir)
|
|
603
|
+
info = _get_dataset_info(dataset_name)
|
|
604
|
+
if url is None:
|
|
605
|
+
url = info['url']
|
|
606
|
+
|
|
607
|
+
opts = {
|
|
608
|
+
'uncompress': True,
|
|
609
|
+
'md5sum': info['md5'],
|
|
610
|
+
'move': '{}.tar.gz'.format(dataset_name)
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
filenames = [
|
|
614
|
+
'tpl-conte69/tpl-conte69_space-MNI305_variant-fsLR32k_{}.{}.surf.gii'
|
|
615
|
+
.format(res, hemi) for res in keys for hemi in ['L', 'R']
|
|
616
|
+
] + ['tpl-conte69/template_description.json']
|
|
617
|
+
|
|
618
|
+
data = _fetch_files(data_dir, files=[(f, url, opts) for f in filenames],
|
|
619
|
+
resume=resume, verbose=verbose)
|
|
620
|
+
|
|
621
|
+
# Fix: Specify encoding='utf-8'
|
|
622
|
+
with open(data[-1], 'r', encoding='utf-8') as src:
|
|
623
|
+
data[-1] = json.load(src)
|
|
624
|
+
|
|
625
|
+
# bundle hemispheres together
|
|
626
|
+
data = [SURFACE(*data[:-1][i:i + 2]) for i in range(0, 6, 2)] + [data[-1]]
|
|
627
|
+
|
|
628
|
+
return Bunch(**dict(zip(keys + ['info'], data)))
|
|
@@ -13,7 +13,7 @@ from brainspace.mesh.mesh_elements import get_cells, get_points
|
|
|
13
13
|
from brainspace.mesh.mesh_io import read_surface
|
|
14
14
|
from brainspace.vtk_interface.wrappers.data_object import BSPolyData
|
|
15
15
|
from nilearn.datasets import load_mni152_brain_mask
|
|
16
|
-
from scipy.interpolate
|
|
16
|
+
from scipy.interpolate import LinearNDInterpolator, NearestNDInterpolator
|
|
17
17
|
from scipy.spatial import cKDTree
|
|
18
18
|
|
|
19
19
|
from brainstat._typing import ArrayLike
|
|
@@ -442,7 +442,9 @@ def _compute_resls(self, Y: np.ndarray) -> Tuple[np.ndarray, dict]:
|
|
|
442
442
|
for j in range(Y.shape[2]):
|
|
443
443
|
normr = np.sqrt(self.SSE[((j + 1) * (j + 2) // 2) - 1])
|
|
444
444
|
for i in range(Y.shape[0]):
|
|
445
|
-
u =
|
|
445
|
+
u = np.divide(
|
|
446
|
+
Y[i, :, j], normr, out=np.zeros_like(Y[i, :, j]), where=normr != 0
|
|
447
|
+
)
|
|
446
448
|
resl[:, j] += np.diff(u[edges], axis=1).ravel() ** 2
|
|
447
449
|
|
|
448
450
|
return resl, mesh_connections
|
|
@@ -951,7 +951,7 @@ def peak_clus(
|
|
|
951
951
|
cluster_val = cluster_val + 1
|
|
952
952
|
|
|
953
953
|
vox = np.argwhere(excurset) + 1
|
|
954
|
-
ivox = np.argwhere(np.
|
|
954
|
+
ivox = np.argwhere(np.isin(vox.flatten(), lmvox)) + 1
|
|
955
955
|
clmid = nf[ivox - 1]
|
|
956
956
|
uclmid, iclmid, jclmid = np.unique(clmid, return_index=True, return_inverse=True)
|
|
957
957
|
iclmid = iclmid + 1
|
|
@@ -959,7 +959,7 @@ def peak_clus(
|
|
|
959
959
|
ucid = np.unique(nf)
|
|
960
960
|
nclus = len(ucid)
|
|
961
961
|
# implementing matlab's histc function ###
|
|
962
|
-
bin_edges = np.r_[-np.
|
|
962
|
+
bin_edges = np.r_[-np.inf, 0.5 * (ucid[:-1] + ucid[1:]), np.inf]
|
|
963
963
|
ucvol, ucvol_edges = np.histogram(nf, bin_edges)
|
|
964
964
|
|
|
965
965
|
if reselspvert is None:
|
|
@@ -173,6 +173,6 @@ def _t_test(self) -> None:
|
|
|
173
173
|
M[M_ef] = ef_duplicate[:, i]
|
|
174
174
|
M[M_sse] = sse_matrix.flatten()
|
|
175
175
|
|
|
176
|
-
self.t[i] = np.sqrt(-np.linalg.det(M) / det_sse / vf)
|
|
176
|
+
self.t[i] = float(np.sqrt(-np.linalg.det(M) / det_sse / vf))
|
|
177
177
|
|
|
178
178
|
self.t = np.atleast_2d(self.t)
|
|
@@ -240,7 +240,7 @@ def remove_duplicate_columns(df: pd.DataFrame, tol: float = 1e-8) -> List[str]:
|
|
|
240
240
|
df = df / df.abs().sum(0)
|
|
241
241
|
df *= 1 / tol
|
|
242
242
|
# keep = df.round().T.drop_duplicates(keep="last").T.columns # Slow!!
|
|
243
|
-
idx = np.unique(df.round().
|
|
243
|
+
idx = np.unique(df.round().to_numpy(dtype=float), axis=1, return_index=True)[-1]
|
|
244
244
|
keep = df.columns[sorted(idx)]
|
|
245
245
|
return keep
|
|
246
246
|
|
|
@@ -365,9 +365,14 @@ class FixedEffect(object):
|
|
|
365
365
|
return self._add(t)
|
|
366
366
|
|
|
367
367
|
def __radd__(
|
|
368
|
-
self, t: Union[ArrayLike, "FixedEffect", "MixedEffect"]
|
|
368
|
+
self, t: Union[int, ArrayLike, "FixedEffect", "MixedEffect"]
|
|
369
369
|
) -> "FixedEffect":
|
|
370
|
-
|
|
370
|
+
if isinstance(t, int) and t == 0:
|
|
371
|
+
# When t is 0, return self without adding an extra intercept
|
|
372
|
+
return self
|
|
373
|
+
else:
|
|
374
|
+
# For other cases, proceed with the addition
|
|
375
|
+
return self._add(t, side="right")
|
|
371
376
|
|
|
372
377
|
def __sub__(self, t: Union[ArrayLike, "FixedEffect"]) -> "FixedEffect":
|
|
373
378
|
if self.empty:
|
|
@@ -377,12 +382,16 @@ class FixedEffect(object):
|
|
|
377
382
|
return self
|
|
378
383
|
df /= df.abs().sum(0)
|
|
379
384
|
df.index = self.m.index
|
|
380
|
-
|
|
381
385
|
m = self.m / self.m.abs().sum(0)
|
|
382
|
-
|
|
383
|
-
|
|
386
|
+
|
|
387
|
+
m_normalized_T = m.T
|
|
388
|
+
df_normalized_T = df.T
|
|
389
|
+
|
|
390
|
+
merged = m_normalized_T.merge(df_normalized_T, how="outer", indicator=True)
|
|
391
|
+
merged_index = m_normalized_T.iloc[:, [1]].reset_index().merge(merged, how="left")
|
|
392
|
+
mask = merged_index.loc[merged_index['_merge'] == 'left_only','index']
|
|
384
393
|
return FixedEffect(
|
|
385
|
-
self.m[
|
|
394
|
+
self.m[mask], add_intercept=False, _check_categorical=False
|
|
386
395
|
)
|
|
387
396
|
|
|
388
397
|
def _mul(
|
|
@@ -101,7 +101,7 @@ def ismember(
|
|
|
101
101
|
|
|
102
102
|
else:
|
|
103
103
|
# Get values of A that are in B.
|
|
104
|
-
bool_vector = np.
|
|
104
|
+
bool_vector = np.isin(A, B)
|
|
105
105
|
bool_array = np.reshape(bool_vector, A.shape)
|
|
106
106
|
|
|
107
107
|
# Get location of elements in B. Transpose B and A to get MATLAB behavior (i.e. column first)
|
|
@@ -110,7 +110,9 @@ def ismember(
|
|
|
110
110
|
locations = np.zeros(A.size) + np.nan
|
|
111
111
|
Aflat = A.T.flat
|
|
112
112
|
for i in range(0, idx.size):
|
|
113
|
-
|
|
113
|
+
matches = np.argwhere(val == Aflat[idx[i]])
|
|
114
|
+
if len(matches) > 0:
|
|
115
|
+
locations[idx[i]] = locB[matches[0, 0]]
|
|
114
116
|
locations = np.reshape(locations, A.shape)
|
|
115
117
|
return bool_array, locations
|
|
116
118
|
|
|
@@ -7,10 +7,12 @@ import numpy as np
|
|
|
7
7
|
import pandas as pd
|
|
8
8
|
import pytest
|
|
9
9
|
import templateflow.api as tflow
|
|
10
|
+
from sklearn.linear_model import LinearRegression
|
|
10
11
|
|
|
11
12
|
from brainstat.stats.SLM import SLM, _onetailed_to_twotailed
|
|
12
13
|
from brainstat.stats.terms import FixedEffect, MixedEffect
|
|
13
14
|
from brainstat.tests.testutil import datadir
|
|
15
|
+
from brainstat.tutorial.utils import fetch_mics_data
|
|
14
16
|
|
|
15
17
|
|
|
16
18
|
def recursive_comparison(X1, X2):
|
|
@@ -123,6 +125,31 @@ def dummy_test(infile, expfile):
|
|
|
123
125
|
assert all(flag == True for (flag) in testout)
|
|
124
126
|
|
|
125
127
|
|
|
128
|
+
def dummy_test2():
|
|
129
|
+
|
|
130
|
+
# BrainStat
|
|
131
|
+
thickness, demographics = fetch_mics_data()
|
|
132
|
+
term_age = FixedEffect(demographics.AGE_AT_SCAN)
|
|
133
|
+
model = term_age
|
|
134
|
+
slm_age = SLM(
|
|
135
|
+
model,
|
|
136
|
+
np.ones(thickness.shape[0]),
|
|
137
|
+
surf="fsaverage5",
|
|
138
|
+
)
|
|
139
|
+
slm_age.fit(thickness)
|
|
140
|
+
|
|
141
|
+
# Scikit Learn
|
|
142
|
+
model_age = LinearRegression()
|
|
143
|
+
model_age.fit(demographics.AGE_AT_SCAN.to_numpy().reshape(-1, 1), thickness)
|
|
144
|
+
|
|
145
|
+
# Compare
|
|
146
|
+
comp2 = np.allclose(
|
|
147
|
+
np.squeeze(model_age.coef_), slm_age.coef[1, :], rtol=1e-05, equal_nan=True
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
assert comp2 == True
|
|
151
|
+
|
|
152
|
+
|
|
126
153
|
expected_number_of_tests = 22
|
|
127
154
|
parametrize = pytest.mark.parametrize
|
|
128
155
|
|
|
@@ -134,6 +161,10 @@ def test_run_all(test_number):
|
|
|
134
161
|
dummy_test(infile, expfile)
|
|
135
162
|
|
|
136
163
|
|
|
164
|
+
# Test against scikit learn
|
|
165
|
+
dummy_test2()
|
|
166
|
+
|
|
167
|
+
|
|
137
168
|
@pytest.mark.skipif(
|
|
138
169
|
sys.platform.startswith("win"), reason="Template flow has issues on windows."
|
|
139
170
|
)
|
|
@@ -19,6 +19,7 @@ parametrize = pytest.mark.parametrize
|
|
|
19
19
|
def test_load_surfaces(template):
|
|
20
20
|
"""Test loading surface templates. For now this only tests whether it runs
|
|
21
21
|
without error and whether the output type is correct."""
|
|
22
|
+
|
|
22
23
|
surface = fetch_template_surface(template, join=True)
|
|
23
24
|
assert isinstance(surface, BSPolyData)
|
|
24
25
|
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from brainstat.context.genetics import surface_genetic_expression
|
|
2
|
+
from brainstat.datasets import fetch_parcellation, fetch_template_surface, fetch_parcellation
|
|
3
|
+
import numpy as np
|
|
4
|
+
from nilearn import datasets
|
|
5
|
+
import pytest
|
|
6
|
+
from urllib.error import HTTPError
|
|
7
|
+
|
|
8
|
+
# Get Schaefer-100 genetic expression.
|
|
9
|
+
# def test_surface_genetic_expression():
|
|
10
|
+
# schaefer_100_fs5 = fetch_parcellation("fsaverage5", "schaefer", 100)
|
|
11
|
+
# surfaces = fetch_template_surface("fsaverage5", join=False)
|
|
12
|
+
# expression = surface_genetic_expression(schaefer_100_fs5, surfaces, space="fsaverage")
|
|
13
|
+
# assert expression is not None
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_surface_genetic_expression2():
|
|
17
|
+
destrieux = datasets.fetch_atlas_surf_destrieux()
|
|
18
|
+
labels = np.hstack((destrieux['map_left'], destrieux['map_right']))
|
|
19
|
+
fsaverage = datasets.fetch_surf_fsaverage()
|
|
20
|
+
surfaces = (fsaverage['pial_left'], fsaverage['pial_right'])
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
expression = surface_genetic_expression(labels, surfaces, space='fsaverage')
|
|
24
|
+
assert expression is not None
|
|
25
|
+
except HTTPError as e:
|
|
26
|
+
if e.code == 503:
|
|
27
|
+
pytest.skip(f"Allen Brain Institute server unavailable (503): {e}")
|
|
28
|
+
else:
|
|
29
|
+
raise
|
|
30
|
+
|
|
31
|
+
if __name__ == "__main__":
|
|
32
|
+
test_surface_genetic_expression2()
|
|
33
|
+
# test_surface_genetic_expression()
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Unit tests for the histology module."""
|
|
2
|
+
import pytest
|
|
3
|
+
import requests
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
from brainstat._utils import read_data_fetcher_json
|
|
7
|
+
from brainstat.context.histology import read_histology_profile
|
|
8
|
+
parametrize = pytest.mark.parametrize
|
|
9
|
+
json = read_data_fetcher_json()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@parametrize("template", list(json["bigbrain_profiles"].keys()))
|
|
13
|
+
def test_urls(template):
|
|
14
|
+
"""Tests whether the histology files can be downloaded.
|
|
15
|
+
|
|
16
|
+
Parameters
|
|
17
|
+
----------
|
|
18
|
+
template : list
|
|
19
|
+
Template names.
|
|
20
|
+
"""
|
|
21
|
+
try:
|
|
22
|
+
r = requests.get(json["bigbrain_profiles"][template]["url"], timeout=10, stream=True)
|
|
23
|
+
assert r.status_code == 200
|
|
24
|
+
except (requests.exceptions.ConnectionError, requests.exceptions.Timeout):
|
|
25
|
+
pytest.skip(f"Network connection issue when testing URL for {template}")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_histology_profiles_is_ndarray():
|
|
29
|
+
histology_profiles = read_histology_profile(template="fslr32k")
|
|
30
|
+
|
|
31
|
+
# Assert that histology_profiles is an ndarray
|
|
32
|
+
assert isinstance(histology_profiles, np.ndarray), "histology_profiles should be a NumPy ndarray"
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import numpy as np
|
|
4
4
|
import pandas as pd
|
|
5
|
-
|
|
5
|
+
from functools import reduce
|
|
6
|
+
from brainstat.tutorial.utils import fetch_mics_data
|
|
6
7
|
from brainstat.stats.terms import FixedEffect, MixedEffect
|
|
7
8
|
|
|
8
9
|
|
|
@@ -29,8 +30,10 @@ def test_fixed_init():
|
|
|
29
30
|
|
|
30
31
|
def test_fixed_overload():
|
|
31
32
|
"""Tests the overloads of the FixedEffect class."""
|
|
33
|
+
np.random.seed(2)
|
|
32
34
|
random_data = np.random.random_sample((10, 3))
|
|
33
35
|
fix01 = FixedEffect(random_data[:, :2], ["x0", "x1"], add_intercept=False)
|
|
36
|
+
fix012 = FixedEffect(random_data, ["x0", "x1", "x2"], add_intercept=False)
|
|
34
37
|
fix12 = FixedEffect(random_data[:, 1:], ["x2", "x3"], add_intercept=False)
|
|
35
38
|
fix2 = FixedEffect(random_data[:, 2], ["x2"], add_intercept=False)
|
|
36
39
|
fixi0 = FixedEffect(random_data[:, 0], ["x0"], add_intercept=True)
|
|
@@ -47,7 +50,20 @@ def test_fixed_overload():
|
|
|
47
50
|
assert np.array_equal(fix_add_intercept.m, expected)
|
|
48
51
|
|
|
49
52
|
fix_sub = fix01 - fix12
|
|
50
|
-
|
|
53
|
+
np.testing.assert_allclose(
|
|
54
|
+
fix_sub.m,
|
|
55
|
+
random_data[:, 0][:, None],
|
|
56
|
+
atol=1e-8,
|
|
57
|
+
rtol=1e-5,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
fix_sub2 = fix012 - fix2
|
|
61
|
+
np.testing.assert_allclose(
|
|
62
|
+
fix_sub2.m,
|
|
63
|
+
random_data[:, :2],
|
|
64
|
+
atol=1e-8,
|
|
65
|
+
rtol=1e-5,
|
|
66
|
+
)
|
|
51
67
|
|
|
52
68
|
fix_mul = fix01 * fix2
|
|
53
69
|
assert np.array_equal(fix_mul.m, random_data[:, :2] * random_data[:, 2][:, None])
|
|
@@ -129,3 +145,11 @@ def test_identity_detection():
|
|
|
129
145
|
def as_variance(M):
|
|
130
146
|
var = [np.reshape(x[:, None] @ x[None, :], (-1, 1)) for x in M.T]
|
|
131
147
|
return np.squeeze(var, axis=2).T
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def test_sum():
|
|
151
|
+
_, demographics = fetch_mics_data()
|
|
152
|
+
model = []
|
|
153
|
+
term_age = FixedEffect(demographics.AGE_AT_SCAN)
|
|
154
|
+
model.append(term_age)
|
|
155
|
+
assert reduce(lambda x, y: x + y, model) == sum(model)
|
|
@@ -1,6 +1,6 @@
|
|
|
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
|
|
@@ -13,13 +13,44 @@ Classifier: Intended Audience :: Science/Research
|
|
|
13
13
|
Classifier: Intended Audience :: Developers
|
|
14
14
|
Classifier: Topic :: Software Development :: Build Tools
|
|
15
15
|
Classifier: Topic :: Scientific/Engineering
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.
|
|
17
|
-
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
-
Requires-Python: >=3.7.*
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Requires-Python: >=3.10
|
|
20
18
|
Description-Content-Type: text/x-rst
|
|
21
|
-
Provides-Extra: dev
|
|
22
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
|
|
23
54
|
|
|
24
55
|
BrainStat : A toolbox for statistical analysis of neuroimaging data
|
|
25
56
|
===================================================================
|
|
@@ -53,6 +53,7 @@ brainstat/tests/test_SLM.py
|
|
|
53
53
|
brainstat/tests/test_datasets.py
|
|
54
54
|
brainstat/tests/test_f_test.py
|
|
55
55
|
brainstat/tests/test_fdr.py
|
|
56
|
+
brainstat/tests/test_genetics.py
|
|
56
57
|
brainstat/tests/test_histology.py
|
|
57
58
|
brainstat/tests/test_linear_model.py
|
|
58
59
|
brainstat/tests/test_mesh_edges.py
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
abagen>=0.1
|
|
2
2
|
brainspace>=0.1.2
|
|
3
3
|
h5py
|
|
4
|
-
|
|
4
|
+
matplotlib>=3.4
|
|
5
|
+
netneurotools<0.3.0
|
|
5
6
|
neurosynth
|
|
6
7
|
nibabel
|
|
7
8
|
nilearn>=0.7.0
|
|
8
9
|
nimare
|
|
9
|
-
numpy>=1.
|
|
10
|
-
pandas
|
|
11
|
-
scikit_learn
|
|
12
|
-
scipy>=1.
|
|
10
|
+
numpy>=1.21
|
|
11
|
+
pandas>=1.3
|
|
12
|
+
scikit_learn>=1.0
|
|
13
|
+
scipy>=1.7
|
|
13
14
|
templateflow
|
|
14
15
|
trimesh
|
|
15
16
|
|
|
@@ -31,24 +31,23 @@ setuptools.setup(
|
|
|
31
31
|
"Intended Audience :: Developers",
|
|
32
32
|
"Topic :: Software Development :: Build Tools",
|
|
33
33
|
"Topic :: Scientific/Engineering",
|
|
34
|
-
"Programming Language :: Python :: 3.
|
|
35
|
-
"Programming Language :: Python :: 3.8",
|
|
36
|
-
"Programming Language :: Python :: 3.9",
|
|
34
|
+
"Programming Language :: Python :: 3.10",
|
|
37
35
|
],
|
|
38
|
-
python_requires=">=3.
|
|
36
|
+
python_requires=">=3.10",
|
|
39
37
|
install_requires=[
|
|
40
38
|
"abagen>=0.1",
|
|
41
39
|
"brainspace>=0.1.2",
|
|
42
40
|
"h5py",
|
|
43
|
-
"
|
|
41
|
+
"matplotlib>=3.4",
|
|
42
|
+
"netneurotools<0.3.0",
|
|
44
43
|
"neurosynth",
|
|
45
44
|
"nibabel",
|
|
46
45
|
"nilearn>=0.7.0",
|
|
47
46
|
"nimare",
|
|
48
|
-
"numpy>=1.
|
|
49
|
-
"pandas",
|
|
50
|
-
"scikit_learn",
|
|
51
|
-
"scipy>=1.
|
|
47
|
+
"numpy>=1.21",
|
|
48
|
+
"pandas>=1.3",
|
|
49
|
+
"scikit_learn>=1.0",
|
|
50
|
+
"scipy>=1.7",
|
|
52
51
|
"templateflow",
|
|
53
52
|
"trimesh",
|
|
54
53
|
],
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
"""Unit tests for the histology module."""
|
|
2
|
-
import pytest
|
|
3
|
-
import requests
|
|
4
|
-
|
|
5
|
-
from brainstat._utils import read_data_fetcher_json
|
|
6
|
-
|
|
7
|
-
parametrize = pytest.mark.parametrize
|
|
8
|
-
json = read_data_fetcher_json()
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
@parametrize("template", list(json["bigbrain_profiles"].keys()))
|
|
12
|
-
def test_urls(template):
|
|
13
|
-
"""Tests whether the histology files can be downloaded.
|
|
14
|
-
|
|
15
|
-
Parameters
|
|
16
|
-
----------
|
|
17
|
-
template : list
|
|
18
|
-
Template names.
|
|
19
|
-
"""
|
|
20
|
-
r = requests.head(json["bigbrain_profiles"][template]["url"])
|
|
21
|
-
assert r.status_code == 200
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|