nimare 0.4.2__py3-none-any.whl
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.
- benchmarks/__init__.py +0 -0
- benchmarks/bench_cbma.py +57 -0
- nimare/__init__.py +45 -0
- nimare/_version.py +21 -0
- nimare/annotate/__init__.py +21 -0
- nimare/annotate/cogat.py +213 -0
- nimare/annotate/gclda.py +924 -0
- nimare/annotate/lda.py +147 -0
- nimare/annotate/text.py +75 -0
- nimare/annotate/utils.py +87 -0
- nimare/base.py +217 -0
- nimare/cli.py +124 -0
- nimare/correct.py +462 -0
- nimare/dataset.py +685 -0
- nimare/decode/__init__.py +33 -0
- nimare/decode/base.py +115 -0
- nimare/decode/continuous.py +462 -0
- nimare/decode/discrete.py +753 -0
- nimare/decode/encode.py +110 -0
- nimare/decode/utils.py +44 -0
- nimare/diagnostics.py +510 -0
- nimare/estimator.py +139 -0
- nimare/extract/__init__.py +19 -0
- nimare/extract/extract.py +466 -0
- nimare/extract/utils.py +295 -0
- nimare/generate.py +331 -0
- nimare/io.py +667 -0
- nimare/meta/__init__.py +39 -0
- nimare/meta/cbma/__init__.py +6 -0
- nimare/meta/cbma/ale.py +951 -0
- nimare/meta/cbma/base.py +947 -0
- nimare/meta/cbma/mkda.py +1361 -0
- nimare/meta/cbmr.py +970 -0
- nimare/meta/ibma.py +1683 -0
- nimare/meta/kernel.py +501 -0
- nimare/meta/models.py +1199 -0
- nimare/meta/utils.py +494 -0
- nimare/nimads.py +492 -0
- nimare/reports/__init__.py +24 -0
- nimare/reports/base.py +664 -0
- nimare/reports/default.yml +123 -0
- nimare/reports/figures.py +651 -0
- nimare/reports/report.tpl +160 -0
- nimare/resources/__init__.py +1 -0
- nimare/resources/atlases/Harvard-Oxford-LICENSE +93 -0
- nimare/resources/atlases/HarvardOxford-cort-maxprob-thr25-2mm.nii.gz +0 -0
- nimare/resources/database_file_manifest.json +142 -0
- nimare/resources/english_spellings.csv +1738 -0
- nimare/resources/filenames.json +32 -0
- nimare/resources/neurosynth_laird_studies.json +58773 -0
- nimare/resources/neurosynth_stoplist.txt +396 -0
- nimare/resources/nidm_pain_dset.json +1349 -0
- nimare/resources/references.bib +541 -0
- nimare/resources/semantic_knowledge_children.txt +325 -0
- nimare/resources/semantic_relatedness_children.txt +249 -0
- nimare/resources/templates/MNI152_2x2x2_brainmask.nii.gz +0 -0
- nimare/resources/templates/tpl-MNI152NLin6Asym_res-01_T1w.nii.gz +0 -0
- nimare/resources/templates/tpl-MNI152NLin6Asym_res-01_desc-brain_mask.nii.gz +0 -0
- nimare/resources/templates/tpl-MNI152NLin6Asym_res-02_T1w.nii.gz +0 -0
- nimare/resources/templates/tpl-MNI152NLin6Asym_res-02_desc-brain_mask.nii.gz +0 -0
- nimare/results.py +225 -0
- nimare/stats.py +276 -0
- nimare/tests/__init__.py +1 -0
- nimare/tests/conftest.py +229 -0
- nimare/tests/data/amygdala_roi.nii.gz +0 -0
- nimare/tests/data/data-neurosynth_version-7_coordinates.tsv.gz +0 -0
- nimare/tests/data/data-neurosynth_version-7_metadata.tsv.gz +0 -0
- nimare/tests/data/data-neurosynth_version-7_vocab-terms_source-abstract_type-tfidf_features.npz +0 -0
- nimare/tests/data/data-neurosynth_version-7_vocab-terms_vocabulary.txt +100 -0
- nimare/tests/data/neurosynth_dset.json +2868 -0
- nimare/tests/data/neurosynth_laird_studies.json +58773 -0
- nimare/tests/data/nidm_pain_dset.json +1349 -0
- nimare/tests/data/nimads_annotation.json +1 -0
- nimare/tests/data/nimads_studyset.json +1 -0
- nimare/tests/data/test_baseline.txt +2 -0
- nimare/tests/data/test_pain_dataset.json +1278 -0
- nimare/tests/data/test_pain_dataset_multiple_contrasts.json +1242 -0
- nimare/tests/data/test_sleuth_file.txt +18 -0
- nimare/tests/data/test_sleuth_file2.txt +10 -0
- nimare/tests/data/test_sleuth_file3.txt +5 -0
- nimare/tests/data/test_sleuth_file4.txt +5 -0
- nimare/tests/data/test_sleuth_file5.txt +5 -0
- nimare/tests/test_annotate_cogat.py +32 -0
- nimare/tests/test_annotate_gclda.py +86 -0
- nimare/tests/test_annotate_lda.py +27 -0
- nimare/tests/test_dataset.py +99 -0
- nimare/tests/test_decode_continuous.py +132 -0
- nimare/tests/test_decode_discrete.py +92 -0
- nimare/tests/test_diagnostics.py +168 -0
- nimare/tests/test_estimator_performance.py +385 -0
- nimare/tests/test_extract.py +46 -0
- nimare/tests/test_generate.py +247 -0
- nimare/tests/test_io.py +294 -0
- nimare/tests/test_meta_ale.py +298 -0
- nimare/tests/test_meta_cbmr.py +295 -0
- nimare/tests/test_meta_ibma.py +240 -0
- nimare/tests/test_meta_kernel.py +209 -0
- nimare/tests/test_meta_mkda.py +234 -0
- nimare/tests/test_nimads.py +21 -0
- nimare/tests/test_reports.py +110 -0
- nimare/tests/test_stats.py +101 -0
- nimare/tests/test_transforms.py +272 -0
- nimare/tests/test_utils.py +200 -0
- nimare/tests/test_workflows.py +221 -0
- nimare/tests/utils.py +126 -0
- nimare/transforms.py +907 -0
- nimare/utils.py +1367 -0
- nimare/workflows/__init__.py +14 -0
- nimare/workflows/base.py +189 -0
- nimare/workflows/cbma.py +165 -0
- nimare/workflows/ibma.py +108 -0
- nimare/workflows/macm.py +77 -0
- nimare/workflows/misc.py +65 -0
- nimare-0.4.2.dist-info/LICENSE +21 -0
- nimare-0.4.2.dist-info/METADATA +124 -0
- nimare-0.4.2.dist-info/RECORD +119 -0
- nimare-0.4.2.dist-info/WHEEL +5 -0
- nimare-0.4.2.dist-info/entry_points.txt +2 -0
- nimare-0.4.2.dist-info/top_level.txt +2 -0
@@ -0,0 +1,298 @@
|
|
1
|
+
"""Test nimare.meta.ale (ALE/SCALE meta-analytic algorithms)."""
|
2
|
+
|
3
|
+
import os
|
4
|
+
import pickle
|
5
|
+
|
6
|
+
import nibabel as nib
|
7
|
+
import numpy as np
|
8
|
+
import pytest
|
9
|
+
from nilearn.input_data import NiftiLabelsMasker
|
10
|
+
|
11
|
+
import nimare
|
12
|
+
from nimare.correct import FDRCorrector, FWECorrector
|
13
|
+
from nimare.meta import ale
|
14
|
+
from nimare.results import MetaResult
|
15
|
+
from nimare.tests.utils import get_test_data_path
|
16
|
+
from nimare.utils import vox2mm
|
17
|
+
|
18
|
+
|
19
|
+
def test_ALE_approximate_null_unit(testdata_cbma, tmp_path_factory):
|
20
|
+
"""Unit test for ALE with approximate null_method."""
|
21
|
+
tmpdir = tmp_path_factory.mktemp("test_ALE_approximate_null_unit")
|
22
|
+
est_out_file = os.path.join(tmpdir, "est_file.pkl.gz")
|
23
|
+
res_out_file = os.path.join(tmpdir, "res_file.pkl.gz")
|
24
|
+
|
25
|
+
meta = ale.ALE(null_method="approximate")
|
26
|
+
results = meta.fit(testdata_cbma)
|
27
|
+
assert "stat" in results.maps.keys()
|
28
|
+
assert "p" in results.maps.keys()
|
29
|
+
assert "z" in results.maps.keys()
|
30
|
+
assert isinstance(results, nimare.results.MetaResult)
|
31
|
+
assert isinstance(results.get_map("z", return_type="image"), nib.Nifti1Image)
|
32
|
+
assert isinstance(results.get_map("z", return_type="array"), np.ndarray)
|
33
|
+
results_copy = results.copy()
|
34
|
+
assert results_copy != results
|
35
|
+
assert isinstance(results, nimare.results.MetaResult)
|
36
|
+
|
37
|
+
# Test saving/loading estimator
|
38
|
+
for compress in [True, False]:
|
39
|
+
meta.save(est_out_file, compress=compress)
|
40
|
+
assert os.path.isfile(est_out_file)
|
41
|
+
meta2 = ale.ALE.load(est_out_file, compressed=compress)
|
42
|
+
assert isinstance(meta2, ale.ALE)
|
43
|
+
if compress:
|
44
|
+
with pytest.raises(pickle.UnpicklingError):
|
45
|
+
ale.ALE.load(est_out_file, compressed=(not compress))
|
46
|
+
else:
|
47
|
+
with pytest.raises(OSError):
|
48
|
+
ale.ALE.load(est_out_file, compressed=(not compress))
|
49
|
+
|
50
|
+
# Test saving/loading MetaResult object
|
51
|
+
for compress in [True, False]:
|
52
|
+
results.save(res_out_file, compress=compress)
|
53
|
+
assert os.path.isfile(res_out_file)
|
54
|
+
res2 = MetaResult.load(res_out_file, compressed=compress)
|
55
|
+
assert isinstance(res2, MetaResult)
|
56
|
+
if compress:
|
57
|
+
with pytest.raises(pickle.UnpicklingError):
|
58
|
+
MetaResult.load(res_out_file, compressed=(not compress))
|
59
|
+
else:
|
60
|
+
with pytest.raises(OSError):
|
61
|
+
MetaResult.load(res_out_file, compressed=(not compress))
|
62
|
+
|
63
|
+
# Test MCC methods
|
64
|
+
# Monte Carlo FWE
|
65
|
+
corr = FWECorrector(method="montecarlo", voxel_thresh=0.001, n_iters=5, n_cores=-1)
|
66
|
+
corr_results = corr.transform(results)
|
67
|
+
assert isinstance(corr_results, nimare.results.MetaResult)
|
68
|
+
assert isinstance(corr_results.description_, str)
|
69
|
+
assert "z_desc-size_level-cluster_corr-FWE_method-montecarlo" in corr_results.maps.keys()
|
70
|
+
assert "z_desc-mass_level-cluster_corr-FWE_method-montecarlo" in corr_results.maps.keys()
|
71
|
+
assert "z_level-voxel_corr-FWE_method-montecarlo" in corr_results.maps.keys()
|
72
|
+
assert "logp_desc-size_level-cluster_corr-FWE_method-montecarlo" in corr_results.maps.keys()
|
73
|
+
assert "logp_desc-mass_level-cluster_corr-FWE_method-montecarlo" in corr_results.maps.keys()
|
74
|
+
assert "logp_level-voxel_corr-FWE_method-montecarlo" in corr_results.maps.keys()
|
75
|
+
assert isinstance(
|
76
|
+
corr_results.get_map(
|
77
|
+
"z_desc-size_level-cluster_corr-FWE_method-montecarlo", return_type="image"
|
78
|
+
),
|
79
|
+
nib.Nifti1Image,
|
80
|
+
)
|
81
|
+
assert isinstance(
|
82
|
+
corr_results.get_map(
|
83
|
+
"z_desc-size_level-cluster_corr-FWE_method-montecarlo", return_type="array"
|
84
|
+
),
|
85
|
+
np.ndarray,
|
86
|
+
)
|
87
|
+
assert isinstance(
|
88
|
+
corr_results.get_map(
|
89
|
+
"z_desc-mass_level-cluster_corr-FWE_method-montecarlo", return_type="image"
|
90
|
+
),
|
91
|
+
nib.Nifti1Image,
|
92
|
+
)
|
93
|
+
assert isinstance(
|
94
|
+
corr_results.get_map(
|
95
|
+
"z_desc-mass_level-cluster_corr-FWE_method-montecarlo", return_type="array"
|
96
|
+
),
|
97
|
+
np.ndarray,
|
98
|
+
)
|
99
|
+
|
100
|
+
# Bonferroni FWE
|
101
|
+
corr = FWECorrector(method="bonferroni")
|
102
|
+
corr_results = corr.transform(results)
|
103
|
+
assert isinstance(corr_results, nimare.results.MetaResult)
|
104
|
+
assert isinstance(corr_results.description_, str)
|
105
|
+
assert isinstance(
|
106
|
+
corr_results.get_map("z_corr-FWE_method-bonferroni", return_type="image"), nib.Nifti1Image
|
107
|
+
)
|
108
|
+
assert isinstance(
|
109
|
+
corr_results.get_map("z_corr-FWE_method-bonferroni", return_type="array"), np.ndarray
|
110
|
+
)
|
111
|
+
|
112
|
+
# FDR
|
113
|
+
corr = FDRCorrector(method="indep", alpha=0.05)
|
114
|
+
corr_results = corr.transform(results)
|
115
|
+
assert isinstance(corr_results, nimare.results.MetaResult)
|
116
|
+
assert isinstance(corr_results.description_, str)
|
117
|
+
assert isinstance(
|
118
|
+
corr_results.get_map("z_corr-FDR_method-indep", return_type="image"), nib.Nifti1Image
|
119
|
+
)
|
120
|
+
assert isinstance(
|
121
|
+
corr_results.get_map("z_corr-FDR_method-indep", return_type="array"), np.ndarray
|
122
|
+
)
|
123
|
+
|
124
|
+
|
125
|
+
def test_ALE_montecarlo_null_unit(testdata_cbma, tmp_path_factory):
|
126
|
+
"""Unit test for ALE with an montecarlo null_method.
|
127
|
+
|
128
|
+
This test is run with low-memory kernel transformation as well.
|
129
|
+
"""
|
130
|
+
tmpdir = tmp_path_factory.mktemp("test_ALE_montecarlo_null_unit")
|
131
|
+
out_file = os.path.join(tmpdir, "file.pkl.gz")
|
132
|
+
|
133
|
+
meta = ale.ALE(null_method="montecarlo", n_iters=10)
|
134
|
+
results = meta.fit(testdata_cbma)
|
135
|
+
assert isinstance(results.description_, str)
|
136
|
+
assert "stat" in results.maps.keys()
|
137
|
+
assert "p" in results.maps.keys()
|
138
|
+
assert "z" in results.maps.keys()
|
139
|
+
assert isinstance(results, nimare.results.MetaResult)
|
140
|
+
assert isinstance(results.get_map("z", return_type="image"), nib.Nifti1Image)
|
141
|
+
assert isinstance(results.get_map("z", return_type="array"), np.ndarray)
|
142
|
+
results_copy = results.copy()
|
143
|
+
assert results_copy != results
|
144
|
+
assert isinstance(results, nimare.results.MetaResult)
|
145
|
+
|
146
|
+
# Test saving/loading
|
147
|
+
meta.save(out_file, compress=True)
|
148
|
+
assert os.path.isfile(out_file)
|
149
|
+
meta2 = ale.ALE.load(out_file, compressed=True)
|
150
|
+
assert isinstance(meta2, ale.ALE)
|
151
|
+
with pytest.raises(pickle.UnpicklingError):
|
152
|
+
ale.ALE.load(out_file, compressed=False)
|
153
|
+
|
154
|
+
meta.save(out_file, compress=False)
|
155
|
+
assert os.path.isfile(out_file)
|
156
|
+
meta2 = ale.ALE.load(out_file, compressed=False)
|
157
|
+
assert isinstance(meta2, ale.ALE)
|
158
|
+
with pytest.raises(OSError):
|
159
|
+
ale.ALE.load(out_file, compressed=True)
|
160
|
+
|
161
|
+
# Test MCC methods
|
162
|
+
# Monte Carlo FWE
|
163
|
+
corr = FWECorrector(method="montecarlo", voxel_thresh=0.001, n_iters=5, n_cores=-1)
|
164
|
+
corr_results = corr.transform(results)
|
165
|
+
assert isinstance(corr_results, nimare.results.MetaResult)
|
166
|
+
assert isinstance(corr_results.description_, str)
|
167
|
+
assert "z_desc-size_level-cluster_corr-FWE_method-montecarlo" in corr_results.maps.keys()
|
168
|
+
assert "z_desc-mass_level-cluster_corr-FWE_method-montecarlo" in corr_results.maps.keys()
|
169
|
+
assert "z_level-voxel_corr-FWE_method-montecarlo" in corr_results.maps.keys()
|
170
|
+
assert "logp_desc-size_level-cluster_corr-FWE_method-montecarlo" in corr_results.maps.keys()
|
171
|
+
assert "logp_desc-mass_level-cluster_corr-FWE_method-montecarlo" in corr_results.maps.keys()
|
172
|
+
assert "logp_level-voxel_corr-FWE_method-montecarlo" in corr_results.maps.keys()
|
173
|
+
assert isinstance(
|
174
|
+
corr_results.get_map(
|
175
|
+
"z_desc-size_level-cluster_corr-FWE_method-montecarlo", return_type="image"
|
176
|
+
),
|
177
|
+
nib.Nifti1Image,
|
178
|
+
)
|
179
|
+
assert isinstance(
|
180
|
+
corr_results.get_map(
|
181
|
+
"z_desc-size_level-cluster_corr-FWE_method-montecarlo", return_type="array"
|
182
|
+
),
|
183
|
+
np.ndarray,
|
184
|
+
)
|
185
|
+
assert isinstance(
|
186
|
+
corr_results.get_map(
|
187
|
+
"z_desc-mass_level-cluster_corr-FWE_method-montecarlo", return_type="image"
|
188
|
+
),
|
189
|
+
nib.Nifti1Image,
|
190
|
+
)
|
191
|
+
assert isinstance(
|
192
|
+
corr_results.get_map(
|
193
|
+
"z_desc-mass_level-cluster_corr-FWE_method-montecarlo", return_type="array"
|
194
|
+
),
|
195
|
+
np.ndarray,
|
196
|
+
)
|
197
|
+
|
198
|
+
# Check that the updated null distribution is in the corrected MetaResult's Estimator.
|
199
|
+
assert (
|
200
|
+
"values_desc-mass_level-cluster_corr-fwe_method-montecarlo"
|
201
|
+
in corr_results.estimator.null_distributions_.keys()
|
202
|
+
)
|
203
|
+
# The updated null distribution should *not* be in the original Estimator, nor in the
|
204
|
+
# uncorrected MetaResult's Estimator.
|
205
|
+
assert (
|
206
|
+
"values_desc-mass_level-cluster_corr-fwe_method-montecarlo"
|
207
|
+
not in meta.null_distributions_.keys()
|
208
|
+
)
|
209
|
+
assert (
|
210
|
+
"values_desc-mass_level-cluster_corr-fwe_method-montecarlo"
|
211
|
+
not in results.estimator.null_distributions_.keys()
|
212
|
+
)
|
213
|
+
|
214
|
+
# Bonferroni FWE
|
215
|
+
corr = FWECorrector(method="bonferroni")
|
216
|
+
corr_results = corr.transform(results)
|
217
|
+
assert isinstance(corr_results, nimare.results.MetaResult)
|
218
|
+
assert isinstance(
|
219
|
+
corr_results.get_map("z_corr-FWE_method-bonferroni", return_type="image"), nib.Nifti1Image
|
220
|
+
)
|
221
|
+
assert isinstance(
|
222
|
+
corr_results.get_map("z_corr-FWE_method-bonferroni", return_type="array"), np.ndarray
|
223
|
+
)
|
224
|
+
|
225
|
+
# FDR
|
226
|
+
corr = FDRCorrector(method="indep", alpha=0.05)
|
227
|
+
corr_results = corr.transform(results)
|
228
|
+
assert isinstance(corr_results, nimare.results.MetaResult)
|
229
|
+
assert isinstance(
|
230
|
+
corr_results.get_map("z_corr-FDR_method-indep", return_type="image"), nib.Nifti1Image
|
231
|
+
)
|
232
|
+
assert isinstance(
|
233
|
+
corr_results.get_map("z_corr-FDR_method-indep", return_type="array"), np.ndarray
|
234
|
+
)
|
235
|
+
|
236
|
+
|
237
|
+
def test_ALESubtraction_smoke(testdata_cbma, tmp_path_factory):
|
238
|
+
"""Smoke test for ALESubtraction."""
|
239
|
+
tmpdir = tmp_path_factory.mktemp("test_ALESubtraction_smoke")
|
240
|
+
out_file = os.path.join(tmpdir, "file.pkl.gz")
|
241
|
+
|
242
|
+
sub_meta = ale.ALESubtraction(n_iters=10, n_cores=2)
|
243
|
+
results = sub_meta.fit(testdata_cbma, testdata_cbma)
|
244
|
+
assert isinstance(results, nimare.results.MetaResult)
|
245
|
+
assert isinstance(results.description_, str)
|
246
|
+
assert "z_desc-group1MinusGroup2" in results.maps.keys()
|
247
|
+
assert isinstance(
|
248
|
+
results.get_map("z_desc-group1MinusGroup2", return_type="image"), nib.Nifti1Image
|
249
|
+
)
|
250
|
+
assert isinstance(results.get_map("z_desc-group1MinusGroup2", return_type="array"), np.ndarray)
|
251
|
+
|
252
|
+
sub_meta.save(out_file)
|
253
|
+
assert os.path.isfile(out_file)
|
254
|
+
|
255
|
+
|
256
|
+
def test_SCALE_smoke(testdata_cbma, tmp_path_factory):
|
257
|
+
"""Smoke test for SCALE."""
|
258
|
+
tmpdir = tmp_path_factory.mktemp("test_SCALE_smoke")
|
259
|
+
out_file = os.path.join(tmpdir, "file.pkl.gz")
|
260
|
+
dset = testdata_cbma.slice(testdata_cbma.ids[:3])
|
261
|
+
|
262
|
+
with pytest.raises(TypeError):
|
263
|
+
ale.SCALE(xyz="dog", n_iters=5, n_cores=1)
|
264
|
+
|
265
|
+
with pytest.raises(ValueError):
|
266
|
+
ale.SCALE(xyz=np.random.random((5, 3, 1)), n_iters=5, n_cores=1)
|
267
|
+
|
268
|
+
with pytest.raises(ValueError):
|
269
|
+
ale.SCALE(xyz=np.random.random((3, 10)), n_iters=5, n_cores=1)
|
270
|
+
|
271
|
+
xyz = vox2mm(
|
272
|
+
np.vstack(np.where(testdata_cbma.masker.mask_img.get_fdata())).T,
|
273
|
+
testdata_cbma.masker.mask_img.affine,
|
274
|
+
)
|
275
|
+
xyz = xyz[:20, :]
|
276
|
+
meta = ale.SCALE(xyz, n_iters=5, n_cores=1)
|
277
|
+
results = meta.fit(dset)
|
278
|
+
assert isinstance(results, nimare.results.MetaResult)
|
279
|
+
assert isinstance(results.description_, str)
|
280
|
+
assert "z" in results.maps.keys()
|
281
|
+
assert isinstance(results.get_map("z", return_type="image"), nib.Nifti1Image)
|
282
|
+
assert isinstance(results.get_map("z", return_type="array"), np.ndarray)
|
283
|
+
|
284
|
+
meta.save(out_file)
|
285
|
+
assert os.path.isfile(out_file)
|
286
|
+
|
287
|
+
|
288
|
+
def test_ALE_non_nifti_masker(testdata_cbma):
|
289
|
+
"""Unit test for ALE with non-NiftiMasker.
|
290
|
+
|
291
|
+
CBMA estimators don't work with non-NiftiMasker (e.g., a NiftiLabelsMasker).
|
292
|
+
"""
|
293
|
+
atlas = os.path.join(get_test_data_path(), "test_pain_dataset", "atlas.nii.gz")
|
294
|
+
masker = NiftiLabelsMasker(atlas)
|
295
|
+
meta = ale.ALE(mask=masker, n_iters=10)
|
296
|
+
|
297
|
+
with pytest.raises(ValueError):
|
298
|
+
meta.fit(testdata_cbma)
|
@@ -0,0 +1,295 @@
|
|
1
|
+
"""Tests for CBMR meta-analytic methods."""
|
2
|
+
|
3
|
+
import logging
|
4
|
+
import warnings
|
5
|
+
|
6
|
+
import pytest
|
7
|
+
|
8
|
+
try:
|
9
|
+
import torch
|
10
|
+
except ImportError:
|
11
|
+
warnings.warn("Torch not installed. CBMR tests will be skipped.")
|
12
|
+
TORCH_INSTALLED = False
|
13
|
+
else:
|
14
|
+
TORCH_INSTALLED = True
|
15
|
+
from nimare.meta import models
|
16
|
+
from nimare.meta.cbmr import CBMREstimator, CBMRInference
|
17
|
+
|
18
|
+
import nimare
|
19
|
+
from nimare.correct import FDRCorrector, FWECorrector
|
20
|
+
from nimare.transforms import StandardizeField
|
21
|
+
|
22
|
+
# numba has a lot of debug messages that are not useful for testing
|
23
|
+
logging.getLogger("numba").setLevel(logging.WARNING)
|
24
|
+
# indexed_gzip has a few debug messages that are not useful for testing
|
25
|
+
logging.getLogger("indexed_gzip").setLevel(logging.WARNING)
|
26
|
+
|
27
|
+
|
28
|
+
if TORCH_INSTALLED:
|
29
|
+
|
30
|
+
@pytest.fixture(
|
31
|
+
scope="session",
|
32
|
+
params=[
|
33
|
+
pytest.param(models.PoissonEstimator, id="Poisson"),
|
34
|
+
pytest.param(models.NegativeBinomialEstimator, id="NegativeBinomial"),
|
35
|
+
pytest.param(
|
36
|
+
models.ClusteredNegativeBinomialEstimator, id="ClusteredNegativeBinomial"
|
37
|
+
),
|
38
|
+
],
|
39
|
+
)
|
40
|
+
def model(request):
|
41
|
+
"""CBMR models."""
|
42
|
+
return request.param
|
43
|
+
|
44
|
+
else:
|
45
|
+
model = None
|
46
|
+
|
47
|
+
|
48
|
+
@pytest.fixture(scope="session")
|
49
|
+
def cbmr_result(testdata_cbmr_simulated, model):
|
50
|
+
"""Test CBMR estimator."""
|
51
|
+
dset = StandardizeField(fields=["sample_sizes", "avg_age", "schizophrenia_subtype"]).transform(
|
52
|
+
testdata_cbmr_simulated
|
53
|
+
)
|
54
|
+
|
55
|
+
cbmr = CBMREstimator(
|
56
|
+
group_categories=["diagnosis", "drug_status"],
|
57
|
+
moderators=["standardized_sample_sizes", "standardized_avg_age", "schizophrenia_subtype"],
|
58
|
+
spline_spacing=100,
|
59
|
+
model=model,
|
60
|
+
penalty=False,
|
61
|
+
n_iter=1000,
|
62
|
+
lr=1,
|
63
|
+
tol=1e4,
|
64
|
+
device="cpu",
|
65
|
+
)
|
66
|
+
res = cbmr.fit(dataset=dset)
|
67
|
+
assert isinstance(res, nimare.results.MetaResult)
|
68
|
+
assert isinstance(res.description_, str)
|
69
|
+
|
70
|
+
return res
|
71
|
+
|
72
|
+
|
73
|
+
@pytest.fixture(scope="session")
|
74
|
+
def inference_results(testdata_cbmr_simulated, cbmr_result):
|
75
|
+
"""Test inference results for CBMR estimator."""
|
76
|
+
inference = CBMRInference(device="cpu")
|
77
|
+
inference.fit(cbmr_result)
|
78
|
+
t_con_groups = inference.create_contrast(
|
79
|
+
[
|
80
|
+
"DepressionYes-DepressionNo",
|
81
|
+
],
|
82
|
+
source="groups",
|
83
|
+
)
|
84
|
+
t_con_moderators = inference.create_contrast(
|
85
|
+
["standardized_sample_sizes-standardized_avg_age"],
|
86
|
+
source="moderators",
|
87
|
+
)
|
88
|
+
contrast_result = inference.transform(
|
89
|
+
t_con_groups=t_con_groups, t_con_moderators=t_con_moderators
|
90
|
+
)
|
91
|
+
|
92
|
+
return contrast_result
|
93
|
+
|
94
|
+
|
95
|
+
@pytest.fixture(
|
96
|
+
scope="session",
|
97
|
+
params=[
|
98
|
+
pytest.param(FWECorrector(method="bonferroni"), id="bonferroni"),
|
99
|
+
pytest.param(FDRCorrector(method="indep"), id="indep"),
|
100
|
+
pytest.param(FDRCorrector(method="negcorr"), id="negcorr"),
|
101
|
+
],
|
102
|
+
)
|
103
|
+
def corrector(request):
|
104
|
+
"""Corrector classes."""
|
105
|
+
return request.param
|
106
|
+
|
107
|
+
|
108
|
+
def test_cbmr_estimator(cbmr_result):
|
109
|
+
"""Unit test for CBMR estimator."""
|
110
|
+
assert isinstance(cbmr_result, nimare.results.MetaResult)
|
111
|
+
|
112
|
+
|
113
|
+
def test_cbmr_inference(inference_results):
|
114
|
+
"""Unit test for CBMR inference."""
|
115
|
+
assert isinstance(inference_results, nimare.results.MetaResult)
|
116
|
+
|
117
|
+
|
118
|
+
def test_cbmr_correctors(inference_results, corrector):
|
119
|
+
"""Unit test for Correctors that work with CBMR."""
|
120
|
+
corrected_results = corrector.transform(inference_results)
|
121
|
+
assert isinstance(corrected_results, nimare.results.MetaResult)
|
122
|
+
|
123
|
+
|
124
|
+
def test_firth_penalty(testdata_cbmr_simulated):
|
125
|
+
"""Unit test for Firth penalty."""
|
126
|
+
dset = StandardizeField(fields=["sample_sizes", "avg_age", "schizophrenia_subtype"]).transform(
|
127
|
+
testdata_cbmr_simulated
|
128
|
+
)
|
129
|
+
cbmr = CBMREstimator(
|
130
|
+
group_categories=["diagnosis", "drug_status"],
|
131
|
+
moderators=["standardized_sample_sizes", "standardized_avg_age", "schizophrenia_subtype"],
|
132
|
+
spline_spacing=100,
|
133
|
+
model=models.PoissonEstimator,
|
134
|
+
penalty=True,
|
135
|
+
lr=1,
|
136
|
+
tol=1e4,
|
137
|
+
device="cpu",
|
138
|
+
)
|
139
|
+
res = cbmr.fit(dataset=dset)
|
140
|
+
assert isinstance(res, nimare.results.MetaResult)
|
141
|
+
|
142
|
+
|
143
|
+
def test_moderators_none(testdata_cbmr_simulated):
|
144
|
+
"""Unit test for Firth penalty."""
|
145
|
+
dset = StandardizeField(fields=["sample_sizes", "avg_age", "schizophrenia_subtype"]).transform(
|
146
|
+
testdata_cbmr_simulated
|
147
|
+
)
|
148
|
+
cbmr = CBMREstimator(
|
149
|
+
group_categories=["diagnosis", "drug_status"],
|
150
|
+
moderators=None,
|
151
|
+
spline_spacing=100,
|
152
|
+
model=models.PoissonEstimator,
|
153
|
+
penalty=False,
|
154
|
+
lr=1,
|
155
|
+
tol=1e4,
|
156
|
+
device="cpu",
|
157
|
+
)
|
158
|
+
res = cbmr.fit(dataset=dset)
|
159
|
+
assert isinstance(res, nimare.results.MetaResult)
|
160
|
+
inference = CBMRInference(device="cpu")
|
161
|
+
inference.fit(res)
|
162
|
+
|
163
|
+
t_con_groups = inference.create_contrast(
|
164
|
+
[
|
165
|
+
"DepressionYes",
|
166
|
+
],
|
167
|
+
source="groups",
|
168
|
+
)
|
169
|
+
inference_results = inference.transform(t_con_groups=t_con_groups)
|
170
|
+
|
171
|
+
assert isinstance(inference_results, nimare.results.MetaResult)
|
172
|
+
|
173
|
+
|
174
|
+
def test_CBMREstimator_update(testdata_cbmr_simulated):
|
175
|
+
"""Unit test for CBMR estimator update function."""
|
176
|
+
testdata_cbmr_simulated = StandardizeField(
|
177
|
+
fields=["sample_sizes", "avg_age", "schizophrenia_subtype"]
|
178
|
+
).transform(testdata_cbmr_simulated)
|
179
|
+
cbmr = CBMREstimator(
|
180
|
+
moderators=["standardized_sample_sizes", "standardized_avg_age", "schizophrenia_subtype"],
|
181
|
+
model=models.PoissonEstimator,
|
182
|
+
lr=1,
|
183
|
+
)
|
184
|
+
|
185
|
+
cbmr._collect_inputs(testdata_cbmr_simulated, drop_invalid=True)
|
186
|
+
cbmr._preprocess_input(testdata_cbmr_simulated)
|
187
|
+
|
188
|
+
# fit the model
|
189
|
+
init_weight_kwargs = {
|
190
|
+
"groups": cbmr.groups,
|
191
|
+
"moderators": cbmr.moderators,
|
192
|
+
"spatial_coef_dim": cbmr.inputs_["coef_spline_bases"].shape[1],
|
193
|
+
"moderators_coef_dim": len(cbmr.moderators) if cbmr.moderators else None,
|
194
|
+
}
|
195
|
+
|
196
|
+
cbmr.model.init_weights(**init_weight_kwargs)
|
197
|
+
optimizer = torch.optim.LBFGS(cbmr.model.parameters(), cbmr.lr)
|
198
|
+
# load dataset info to torch.tensor
|
199
|
+
if cbmr.moderators:
|
200
|
+
moderators_by_group_tensor = dict()
|
201
|
+
for group in cbmr.model.groups:
|
202
|
+
moderators_tensor = torch.tensor(
|
203
|
+
cbmr.inputs_["moderators_by_group"][group],
|
204
|
+
dtype=torch.float64,
|
205
|
+
device=cbmr.device,
|
206
|
+
)
|
207
|
+
moderators_by_group_tensor[group] = moderators_tensor
|
208
|
+
else:
|
209
|
+
moderators_by_group_tensor = None
|
210
|
+
foci_per_voxel_tensor, foci_per_study_tensor = dict(), dict()
|
211
|
+
for group in cbmr.model.groups:
|
212
|
+
group_foci_per_voxel_tensor = torch.tensor(
|
213
|
+
cbmr.inputs_["foci_per_voxel"][group], dtype=torch.float64, device=cbmr.device
|
214
|
+
)
|
215
|
+
group_foci_per_study_tensor = torch.tensor(
|
216
|
+
cbmr.inputs_["foci_per_study"][group], dtype=torch.float64, device=cbmr.device
|
217
|
+
)
|
218
|
+
foci_per_voxel_tensor[group] = group_foci_per_voxel_tensor
|
219
|
+
foci_per_study_tensor[group] = group_foci_per_study_tensor
|
220
|
+
|
221
|
+
if cbmr.iter == 0:
|
222
|
+
prev_loss = torch.tensor(float("inf")) # initialization loss difference
|
223
|
+
|
224
|
+
cbmr.model._update(
|
225
|
+
optimizer,
|
226
|
+
torch.tensor(cbmr.inputs_["coef_spline_bases"], dtype=torch.float64, device=cbmr.device),
|
227
|
+
moderators_by_group_tensor,
|
228
|
+
foci_per_voxel_tensor,
|
229
|
+
foci_per_study_tensor,
|
230
|
+
prev_loss,
|
231
|
+
)
|
232
|
+
# deliberately set the first spatial coefficient to nan
|
233
|
+
for group in cbmr.model.groups:
|
234
|
+
nan_coef = torch.tensor(cbmr.model.spatial_coef_linears[group].weight)
|
235
|
+
nan_coef[:, 0] = float("nan")
|
236
|
+
cbmr.model.spatial_coef_linears[group].weight = torch.nn.Parameter(nan_coef)
|
237
|
+
|
238
|
+
# Expect exceptions when one of the spatial coefficients is nan.
|
239
|
+
with pytest.raises(ValueError):
|
240
|
+
cbmr.model._update(
|
241
|
+
optimizer,
|
242
|
+
torch.tensor(
|
243
|
+
cbmr.inputs_["coef_spline_bases"], dtype=torch.float64, device=cbmr.device
|
244
|
+
),
|
245
|
+
moderators_by_group_tensor,
|
246
|
+
foci_per_voxel_tensor,
|
247
|
+
foci_per_study_tensor,
|
248
|
+
prev_loss,
|
249
|
+
)
|
250
|
+
|
251
|
+
|
252
|
+
def test_StandardizeField(testdata_cbmr_simulated):
|
253
|
+
"""Unit test for StandardizeField."""
|
254
|
+
dset = StandardizeField(fields=["sample_sizes", "avg_age"]).transform(testdata_cbmr_simulated)
|
255
|
+
assert isinstance(dset, nimare.dataset.Dataset)
|
256
|
+
assert "standardized_sample_sizes" in dset.annotations
|
257
|
+
assert "standardized_avg_age" in dset.annotations
|
258
|
+
assert dset.annotations["standardized_sample_sizes"].mean() == pytest.approx(0.0, abs=1e-3)
|
259
|
+
assert dset.annotations["standardized_sample_sizes"].std() == pytest.approx(1.0, abs=1e-3)
|
260
|
+
assert dset.annotations["standardized_avg_age"].mean() == pytest.approx(0.0, abs=1e-3)
|
261
|
+
assert dset.annotations["standardized_avg_age"].std() == pytest.approx(1.0, abs=1e-3)
|
262
|
+
|
263
|
+
|
264
|
+
@pytest.mark.cbmr_importerror
|
265
|
+
def test_cbmr_importerror():
|
266
|
+
"""Test that ImportErrors are raised when torch is not installed."""
|
267
|
+
with pytest.raises(ImportError):
|
268
|
+
from nimare.meta.cbmr import CBMREstimator
|
269
|
+
|
270
|
+
CBMREstimator()
|
271
|
+
|
272
|
+
with pytest.raises(ImportError):
|
273
|
+
from nimare.meta.cbmr import CBMRInference
|
274
|
+
|
275
|
+
CBMRInference()
|
276
|
+
|
277
|
+
with pytest.raises(ImportError):
|
278
|
+
from nimare.meta.models import GeneralLinearModelEstimator
|
279
|
+
|
280
|
+
GeneralLinearModelEstimator()
|
281
|
+
|
282
|
+
with pytest.raises(ImportError):
|
283
|
+
from nimare.meta.models import PoissonEstimator
|
284
|
+
|
285
|
+
PoissonEstimator()
|
286
|
+
|
287
|
+
with pytest.raises(ImportError):
|
288
|
+
from nimare.meta.models import NegativeBinomialEstimator
|
289
|
+
|
290
|
+
NegativeBinomialEstimator()
|
291
|
+
|
292
|
+
with pytest.raises(ImportError):
|
293
|
+
from nimare.meta.models import ClusteredNegativeBinomialEstimator
|
294
|
+
|
295
|
+
ClusteredNegativeBinomialEstimator()
|