nimare 0.4.2rc4__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 +635 -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 +240 -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.2rc4.dist-info/LICENSE +21 -0
- nimare-0.4.2rc4.dist-info/METADATA +124 -0
- nimare-0.4.2rc4.dist-info/RECORD +119 -0
- nimare-0.4.2rc4.dist-info/WHEEL +5 -0
- nimare-0.4.2rc4.dist-info/entry_points.txt +2 -0
- nimare-0.4.2rc4.dist-info/top_level.txt +2 -0
nimare/reports/base.py
ADDED
@@ -0,0 +1,664 @@
|
|
1
|
+
# STATEMENT OF CHANGES: This file is derived from sources licensed under the Apache-2.0 terms,
|
2
|
+
# and this file has been changed.
|
3
|
+
# The original file this work derives from is found at:
|
4
|
+
# https://github.com/nipreps/niworkflows/blob/9905f90110879ed4123ea291f512b0a60d7ba207/niworkflows/reports/core.py
|
5
|
+
#
|
6
|
+
# [May 2023] CHANGES:
|
7
|
+
# * Replace BIDSlayout with code that uses the nimare Dataset and MetaResult class.
|
8
|
+
#
|
9
|
+
# ORIGINAL WORK'S ATTRIBUTION NOTICE:
|
10
|
+
#
|
11
|
+
# Copyright 2021 The NiPreps Developers <nipreps@gmail.com>
|
12
|
+
#
|
13
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
14
|
+
# you may not use this file except in compliance with the License.
|
15
|
+
# You may obtain a copy of the License at
|
16
|
+
#
|
17
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
18
|
+
#
|
19
|
+
# Unless required by applicable law or agreed to in writing, software
|
20
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
21
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
22
|
+
# See the License for the specific language governing permissions and
|
23
|
+
# limitations under the License.
|
24
|
+
"""Reports builder for NiMARE's MetaResult object."""
|
25
|
+
import textwrap
|
26
|
+
from glob import glob
|
27
|
+
from pathlib import Path
|
28
|
+
|
29
|
+
try:
|
30
|
+
from importlib.resources import files
|
31
|
+
except ImportError:
|
32
|
+
# Python < 3.9
|
33
|
+
from importlib_resources import files
|
34
|
+
|
35
|
+
import jinja2
|
36
|
+
import numpy as np
|
37
|
+
import pandas as pd
|
38
|
+
|
39
|
+
from nimare.meta.cbma.base import CBMAEstimator, PairwiseCBMAEstimator
|
40
|
+
from nimare.reports.figures import (
|
41
|
+
_plot_dof_map,
|
42
|
+
_plot_relcov_map,
|
43
|
+
_plot_ridgeplot,
|
44
|
+
_plot_sumstats,
|
45
|
+
_plot_true_voxels,
|
46
|
+
gen_table,
|
47
|
+
plot_clusters,
|
48
|
+
plot_coordinates,
|
49
|
+
plot_heatmap,
|
50
|
+
plot_interactive_brain,
|
51
|
+
plot_mask,
|
52
|
+
plot_static_brain,
|
53
|
+
)
|
54
|
+
|
55
|
+
PARAMETERS_DICT = {
|
56
|
+
"kernel_transformer__fwhm": "FWHM",
|
57
|
+
"kernel_transformer__sample_size": "Sample size",
|
58
|
+
"kernel_transformer__r": "Sphere radius (mm)",
|
59
|
+
"kernel_transformer__value": "Value for sphere",
|
60
|
+
"kernel_transformer__memory": "Memory",
|
61
|
+
"kernel_transformer__memory_level": "Memory level",
|
62
|
+
"kernel_transformer__sum_across_studies": "Sum Across Studies",
|
63
|
+
"memory": "Memory",
|
64
|
+
"memory_level": "Memory level",
|
65
|
+
"null_method": "Null method",
|
66
|
+
"n_iters": "Number of iterations",
|
67
|
+
"n_cores": "Number of cores",
|
68
|
+
"fwe": "Family-wise error rate (FWE) correction",
|
69
|
+
"fdr": "False discovery rate (FDR) correction",
|
70
|
+
"method": "Method",
|
71
|
+
"alpha": "Alpha",
|
72
|
+
"prior": "Prior",
|
73
|
+
"tau2": "Between-study variance",
|
74
|
+
"use_sample_size": "Use sample size for weights",
|
75
|
+
"normalize_contrast_weights": "Normalize by the number of contrasts",
|
76
|
+
"two_sided": "Two-sided test",
|
77
|
+
"beta": "Parameter estimate",
|
78
|
+
"se": "Standard error of the parameter estimate",
|
79
|
+
"varcope": "Variance of the parameter estimate",
|
80
|
+
"t": "T-statistic",
|
81
|
+
"z": "Z-statistic",
|
82
|
+
}
|
83
|
+
|
84
|
+
PNG_SNIPPET = """\
|
85
|
+
<img class="png-reportlet" src="./{0}" style="width: 100%" /></div>
|
86
|
+
<div class="elem-filename">
|
87
|
+
Get figure file: <a href="./{0}" target="_blank">{0}</a>
|
88
|
+
</div>
|
89
|
+
"""
|
90
|
+
|
91
|
+
IFRAME_SNIPPET = """\
|
92
|
+
<div class="igraph-container">
|
93
|
+
<iframe class="igraph" src="./{0}"></iframe>
|
94
|
+
</div>
|
95
|
+
"""
|
96
|
+
|
97
|
+
SUMMARY_TEMPLATE = """\
|
98
|
+
<ul class="elem-desc">
|
99
|
+
{meta_text}
|
100
|
+
</ul>
|
101
|
+
<details>
|
102
|
+
<summary>Experiments excluded</summary><br />
|
103
|
+
<p>{exc_ids}</p>
|
104
|
+
</details>
|
105
|
+
"""
|
106
|
+
|
107
|
+
ESTIMATOR_TEMPLATE = """\
|
108
|
+
<ul class="elem-desc">
|
109
|
+
<li>Estimator: {est_name}</li>
|
110
|
+
{ker_text}
|
111
|
+
{est_params_text}
|
112
|
+
</ul>
|
113
|
+
"""
|
114
|
+
|
115
|
+
CORRECTOR_TEMPLATE = """\
|
116
|
+
<ul class="elem-desc">
|
117
|
+
<li> Correction Method: {correction_method}</li>
|
118
|
+
{cor_params_text}
|
119
|
+
<li>Parameters: {ext_params_text}</li>
|
120
|
+
</ul>
|
121
|
+
"""
|
122
|
+
|
123
|
+
DIAGNOSTIC_TEMPLATE = """\
|
124
|
+
<h2 class="sub-report-group">Target image: {target_image}</h2>
|
125
|
+
<ul class="elem-desc">
|
126
|
+
<li>Voxel-level threshold: {voxel_thresh}</li>
|
127
|
+
<li>Cluster size threshold: {cluster_threshold}</li>
|
128
|
+
<li>Number of cores: {n_cores}</li>
|
129
|
+
</ul>
|
130
|
+
"""
|
131
|
+
|
132
|
+
|
133
|
+
def _get_cbma_summary(dset, sel_ids):
|
134
|
+
n_studies = len(dset.coordinates["study_id"].unique())
|
135
|
+
|
136
|
+
mask = dset.masker.mask_img
|
137
|
+
sel_ids = dset.get_studies_by_mask(mask)
|
138
|
+
sel_dset = dset.slice(sel_ids)
|
139
|
+
|
140
|
+
n_foci = dset.coordinates.shape[0]
|
141
|
+
n_foci_sel = sel_dset.coordinates.shape[0]
|
142
|
+
n_foci_nonbrain = n_foci - n_foci_sel
|
143
|
+
|
144
|
+
n_exps = len(dset.ids)
|
145
|
+
n_exps_sel = len(sel_ids)
|
146
|
+
|
147
|
+
cbma_text = [
|
148
|
+
f"<li>Number of studies: {n_studies:d}</li>",
|
149
|
+
f"<li>Number of experiments: {n_exps:d}</li>",
|
150
|
+
f"<li>Number of experiments included: {n_exps_sel:d}</li>",
|
151
|
+
f"<li>Number of foci: {n_foci:d} </li>",
|
152
|
+
f"<li>Number of foci outside the mask: {n_foci_nonbrain:d} </li>",
|
153
|
+
]
|
154
|
+
|
155
|
+
return " ".join(cbma_text)
|
156
|
+
|
157
|
+
|
158
|
+
def _get_ibma_summary(dset, sel_ids):
|
159
|
+
img_df = dset.images
|
160
|
+
n_studies = len(img_df["study_id"].unique())
|
161
|
+
|
162
|
+
ignore_columns = ["id", "study_id", "contrast_id"]
|
163
|
+
map_type = [c for c in img_df if not c.endswith("__relative") and c not in ignore_columns]
|
164
|
+
|
165
|
+
n_imgs = len(dset.ids)
|
166
|
+
n_sel_ids = len(sel_ids)
|
167
|
+
|
168
|
+
ibma_text = [
|
169
|
+
f"<li>Number of studies: {n_studies:d}</li>",
|
170
|
+
f"<li>Number of images: {n_imgs:d}</li>",
|
171
|
+
f"<li>Number of images included: {n_sel_ids:d}</li>",
|
172
|
+
]
|
173
|
+
|
174
|
+
maptype_text = ["<li>Available maps: ", "<ul>"]
|
175
|
+
maptype_text.extend(f"<li>{PARAMETERS_DICT[m]} ({m})</li>" for m in map_type)
|
176
|
+
maptype_text.extend(["</ul>", "</li>"])
|
177
|
+
|
178
|
+
ibma_text.extend(maptype_text)
|
179
|
+
return " ".join(ibma_text)
|
180
|
+
|
181
|
+
|
182
|
+
def _gen_summary(dset, sel_ids, meta_type, out_filename):
|
183
|
+
"""Generate preliminary checks from dataset for the report."""
|
184
|
+
exc_ids = list(set(dset.ids) - set(sel_ids))
|
185
|
+
exc_ids_str = ", ".join(exc_ids)
|
186
|
+
|
187
|
+
meta_text = (
|
188
|
+
_get_cbma_summary(dset, sel_ids)
|
189
|
+
if meta_type == "CBMA"
|
190
|
+
else _get_ibma_summary(dset, sel_ids)
|
191
|
+
)
|
192
|
+
|
193
|
+
summary_text = SUMMARY_TEMPLATE.format(
|
194
|
+
meta_text=meta_text,
|
195
|
+
exc_ids=exc_ids_str,
|
196
|
+
)
|
197
|
+
(out_filename).write_text(summary_text, encoding="UTF-8")
|
198
|
+
|
199
|
+
|
200
|
+
def _get_kernel_summary(params_dict):
|
201
|
+
kernel_transformer = str(params_dict["kernel_transformer"])
|
202
|
+
ker_params = {k: v for k, v in params_dict.items() if k.startswith("kernel_transformer__")}
|
203
|
+
ker_params_text = ["<ul>"]
|
204
|
+
ker_params_text.extend(f"<li>{PARAMETERS_DICT[k]}: {v}</li>" for k, v in ker_params.items())
|
205
|
+
ker_params_text.append("</ul>")
|
206
|
+
ker_params_text = "".join(ker_params_text)
|
207
|
+
|
208
|
+
return f"<li>Kernel Transformer: {kernel_transformer}{ker_params_text}</li>"
|
209
|
+
|
210
|
+
|
211
|
+
def _gen_est_summary(obj, out_filename):
|
212
|
+
"""Generate html with parameter use in obj (e.g., estimator)."""
|
213
|
+
params_dict = obj.get_params()
|
214
|
+
|
215
|
+
# Add kernel transformer parameters to summary if obj is a CBMAEstimator
|
216
|
+
ker_text = _get_kernel_summary(params_dict) if isinstance(obj, CBMAEstimator) else ""
|
217
|
+
|
218
|
+
est_params = {k: v for k, v in params_dict.items() if not k.startswith("kernel_transformer")}
|
219
|
+
est_params_text = [f"<li>{PARAMETERS_DICT[k]}: {v}</li>" for k, v in est_params.items()]
|
220
|
+
est_params_text = "".join(est_params_text)
|
221
|
+
|
222
|
+
est_name = obj.__class__.__name__
|
223
|
+
|
224
|
+
summary_text = ESTIMATOR_TEMPLATE.format(
|
225
|
+
est_name=est_name,
|
226
|
+
ker_text=ker_text,
|
227
|
+
est_params_text=est_params_text,
|
228
|
+
)
|
229
|
+
(out_filename).write_text(summary_text, encoding="UTF-8")
|
230
|
+
|
231
|
+
|
232
|
+
def _gen_cor_summary(obj, out_filename):
|
233
|
+
"""Generate html with parameter use in obj (e.g., corrector)."""
|
234
|
+
params_dict = obj.get_params()
|
235
|
+
|
236
|
+
cor_params_text = [f"<li>{PARAMETERS_DICT[k]}: {v}</li>" for k, v in params_dict.items()]
|
237
|
+
cor_params_text = "".join(cor_params_text)
|
238
|
+
|
239
|
+
ext_params_text = ["<ul>"]
|
240
|
+
ext_params_text.extend(
|
241
|
+
f"<li>{PARAMETERS_DICT[k]}: {v}</li>" for k, v in obj.parameters.items()
|
242
|
+
)
|
243
|
+
ext_params_text.append("</ul>")
|
244
|
+
ext_params_text = "".join(ext_params_text)
|
245
|
+
|
246
|
+
summary_text = CORRECTOR_TEMPLATE.format(
|
247
|
+
correction_method=PARAMETERS_DICT[obj._correction_method],
|
248
|
+
cor_params_text=cor_params_text,
|
249
|
+
ext_params_text=ext_params_text,
|
250
|
+
)
|
251
|
+
(out_filename).write_text(summary_text, encoding="UTF-8")
|
252
|
+
|
253
|
+
|
254
|
+
def _gen_diag_summary(obj, out_filename):
|
255
|
+
"""Generate html with parameter use in obj (e.g., diagnostics)."""
|
256
|
+
diag_dict = obj.get_params()
|
257
|
+
|
258
|
+
summary_text = DIAGNOSTIC_TEMPLATE.format(**diag_dict)
|
259
|
+
(out_filename).write_text(summary_text, encoding="UTF-8")
|
260
|
+
|
261
|
+
|
262
|
+
def _no_clusts_found(out_filename):
|
263
|
+
"""Generate html with single text."""
|
264
|
+
null_text = '<h4 style="color:#A30000">No significant clusters found</h4>'
|
265
|
+
(out_filename).write_text(null_text, encoding="UTF-8")
|
266
|
+
|
267
|
+
|
268
|
+
def _no_maps_found(out_filename):
|
269
|
+
"""Generate html with single text."""
|
270
|
+
null_text = """\
|
271
|
+
<h4 style="color:#A30000">No significant voxels were found above the threshold</h4>
|
272
|
+
"""
|
273
|
+
(out_filename).write_text(null_text, encoding="UTF-8")
|
274
|
+
|
275
|
+
|
276
|
+
def _gen_fig_summary(img_key, threshold, out_filename):
|
277
|
+
summary_text = f"""\
|
278
|
+
<h2 class="sub-report-group">Corrected meta-analytic map: {img_key}</h2>
|
279
|
+
<ul class="elem-desc">
|
280
|
+
<li>Voxel-level threshold: {threshold}</li>
|
281
|
+
</ul>
|
282
|
+
"""
|
283
|
+
(out_filename).write_text(summary_text, encoding="UTF-8")
|
284
|
+
|
285
|
+
|
286
|
+
def _gen_figures(results, img_key, diag_name, threshold, fig_dir):
|
287
|
+
"""Generate html and png objects for the report."""
|
288
|
+
# Plot brain images if not empty
|
289
|
+
if (results.maps[img_key] > threshold).any():
|
290
|
+
img = results.get_map(img_key)
|
291
|
+
plot_interactive_brain(img, fig_dir / "corrector_figure-interactive.html", threshold)
|
292
|
+
plot_static_brain(img, fig_dir / "corrector_figure-static.png", threshold)
|
293
|
+
else:
|
294
|
+
_no_maps_found(fig_dir / "corrector_figure-non.html")
|
295
|
+
|
296
|
+
# Plot clusters table if cluster_table is not empty
|
297
|
+
cluster_table = results.tables[f"{img_key}_tab-clust"]
|
298
|
+
if cluster_table is not None and not cluster_table.empty:
|
299
|
+
gen_table(cluster_table, fig_dir / "diagnostics_tab-clust_table.html")
|
300
|
+
|
301
|
+
# Get label maps and contribution_table
|
302
|
+
contribution_tables = []
|
303
|
+
heatmap_names = []
|
304
|
+
lbl_name = "_".join(img_key.split("_")[1:])
|
305
|
+
lbl_name = f"_{lbl_name}" if lbl_name else lbl_name
|
306
|
+
for tail in ["positive", "negative"]:
|
307
|
+
lbl_key = f"label{lbl_name}_tail-{tail}"
|
308
|
+
if lbl_key in results.maps:
|
309
|
+
label_map = results.get_map(lbl_key)
|
310
|
+
plot_clusters(label_map, fig_dir / f"diagnostics_tail-{tail}_figure.png")
|
311
|
+
|
312
|
+
contribution_table_name = f"{img_key}_diag-{diag_name}_tab-counts_tail-{tail}"
|
313
|
+
if contribution_table_name in results.tables:
|
314
|
+
contribution_table = results.tables[contribution_table_name]
|
315
|
+
if contribution_table is not None and not contribution_table.empty:
|
316
|
+
contribution_table = contribution_table.set_index("id")
|
317
|
+
contribution_tables.append(contribution_table)
|
318
|
+
heatmap_names.append(
|
319
|
+
f"diagnostics_diag-{diag_name}_tab-counts_tail-{tail}_figure.html"
|
320
|
+
)
|
321
|
+
|
322
|
+
# For IBMA plot only one heatmap with both positive and negative tails
|
323
|
+
contribution_table_name = f"{img_key}_diag-{diag_name}_tab-counts"
|
324
|
+
if contribution_table_name in results.tables:
|
325
|
+
contribution_table = results.tables[contribution_table_name]
|
326
|
+
if contribution_table is not None and not contribution_table.empty:
|
327
|
+
contribution_table = contribution_table.set_index("id")
|
328
|
+
contribution_tables.append(contribution_table)
|
329
|
+
heatmap_names.append(f"diagnostics_diag-{diag_name}_tab-counts_figure.html")
|
330
|
+
|
331
|
+
# Plot heatmaps
|
332
|
+
[
|
333
|
+
plot_heatmap(contribution_table, fig_dir / heatmap_name, zmin=0)
|
334
|
+
for heatmap_name, contribution_table in zip(heatmap_names, contribution_tables)
|
335
|
+
]
|
336
|
+
|
337
|
+
else:
|
338
|
+
_no_clusts_found(fig_dir / "diagnostics_tab-clust_table.html")
|
339
|
+
|
340
|
+
|
341
|
+
class Element(object):
|
342
|
+
"""Just a basic component of a report."""
|
343
|
+
|
344
|
+
def __init__(self, name, title=None):
|
345
|
+
self.name = name
|
346
|
+
self.title = title
|
347
|
+
|
348
|
+
|
349
|
+
class Reportlet(Element):
|
350
|
+
"""Reportlet holds the content of a SubReports.
|
351
|
+
|
352
|
+
A reportlet has title, description and a list of components with either an
|
353
|
+
HTML fragment or a path to an SVG file, and possibly a caption. This is a
|
354
|
+
factory class to generate Reportlets reusing the config object from a ``Report``
|
355
|
+
object.
|
356
|
+
"""
|
357
|
+
|
358
|
+
def __init__(self, out_dir, config=None):
|
359
|
+
if not config:
|
360
|
+
raise RuntimeError("Reportlet must have a config object")
|
361
|
+
|
362
|
+
bids_dict = config["bids"]
|
363
|
+
|
364
|
+
# value and suffix are don't need the key, so removing from the bids conform name
|
365
|
+
keys_to_skip = ["value", "suffix"]
|
366
|
+
bids_name = "_".join("%s-%s" % i for i in bids_dict.items() if i[0] not in keys_to_skip)
|
367
|
+
bids_name = f"_{bids_name}" if bids_name else bids_name
|
368
|
+
bids_name = f"{bids_dict['value']}{bids_name}_{bids_dict['suffix']}"
|
369
|
+
|
370
|
+
self.name = config.get("name", bids_name)
|
371
|
+
self.title = config.get("title")
|
372
|
+
self.subtitle = config.get("subtitle")
|
373
|
+
self.subsubtitle = config.get("subsubtitle")
|
374
|
+
self.description = config.get("description")
|
375
|
+
|
376
|
+
files = glob(str(out_dir / "figures" / f"{self.name}.*"))
|
377
|
+
|
378
|
+
self.components = []
|
379
|
+
for file in files:
|
380
|
+
src = Path(file)
|
381
|
+
ext = "".join(src.suffixes)
|
382
|
+
desc_text = config.get("caption")
|
383
|
+
iframe = config.get("iframe", False)
|
384
|
+
dropdown = config.get("dropdown", False)
|
385
|
+
|
386
|
+
contents = None
|
387
|
+
html_anchor = src.relative_to(out_dir)
|
388
|
+
if ext == ".html":
|
389
|
+
contents = IFRAME_SNIPPET.format(html_anchor) if iframe else src.read_text()
|
390
|
+
if dropdown:
|
391
|
+
contents = (
|
392
|
+
f"<details><summary>Advanced ({self.title})</summary>{contents}</details>"
|
393
|
+
)
|
394
|
+
self.title = ""
|
395
|
+
elif ext == ".png":
|
396
|
+
contents = PNG_SNIPPET.format(html_anchor)
|
397
|
+
|
398
|
+
if contents:
|
399
|
+
self.components.append((contents, desc_text))
|
400
|
+
|
401
|
+
def is_empty(self):
|
402
|
+
"""Check if the reportlet has no components."""
|
403
|
+
return len(self.components) == 0
|
404
|
+
|
405
|
+
|
406
|
+
class SubReport(Element):
|
407
|
+
"""SubReports are sections within a Report."""
|
408
|
+
|
409
|
+
def __init__(self, name, isnested=False, reportlets=None, title=""):
|
410
|
+
self.name = name
|
411
|
+
self.title = title
|
412
|
+
self.reportlets = reportlets or []
|
413
|
+
self.isnested = isnested
|
414
|
+
|
415
|
+
|
416
|
+
class Report:
|
417
|
+
"""The full report object.
|
418
|
+
|
419
|
+
.. versionadded:: 0.1.0
|
420
|
+
|
421
|
+
Parameters
|
422
|
+
----------
|
423
|
+
result : :obj:`~nimare.results.MetaResult`
|
424
|
+
A MetaResult produced by a coordinate- or image-based meta-analysis.
|
425
|
+
out_dir : :obj:`str`
|
426
|
+
Output directory in which to save the report.
|
427
|
+
out_filename : :obj:`str`, optional
|
428
|
+
The name of an html file to export the report to.
|
429
|
+
Default is 'report.html'.
|
430
|
+
"""
|
431
|
+
|
432
|
+
def __init__(
|
433
|
+
self,
|
434
|
+
results,
|
435
|
+
out_dir,
|
436
|
+
out_filename="report.html",
|
437
|
+
):
|
438
|
+
self.results = results
|
439
|
+
meta_type = "CBMA" if issubclass(type(self.results.estimator), CBMAEstimator) else "IBMA"
|
440
|
+
self._is_pairwise_estimator = issubclass(
|
441
|
+
type(self.results.estimator), PairwiseCBMAEstimator
|
442
|
+
)
|
443
|
+
|
444
|
+
# Initialize structuring elements
|
445
|
+
self.sections = []
|
446
|
+
self.out_dir = Path(out_dir)
|
447
|
+
self.out_filename = out_filename
|
448
|
+
|
449
|
+
self.fig_dir = self.out_dir / "figures"
|
450
|
+
self.fig_dir.mkdir(parents=True, exist_ok=True)
|
451
|
+
|
452
|
+
if self._is_pairwise_estimator:
|
453
|
+
datasets = [self.results.estimator.dataset1, self.results.estimator.dataset2]
|
454
|
+
sel_ids = [
|
455
|
+
self.results.estimator.inputs_["id1"],
|
456
|
+
self.results.estimator.inputs_["id2"],
|
457
|
+
]
|
458
|
+
else:
|
459
|
+
datasets = [self.results.estimator.dataset]
|
460
|
+
sel_ids = [self.results.estimator.inputs_["id"]]
|
461
|
+
|
462
|
+
for dset_i, (dataset, sel_id) in enumerate(zip(datasets, sel_ids)):
|
463
|
+
# Generate summary text
|
464
|
+
_gen_summary(
|
465
|
+
dataset,
|
466
|
+
sel_id,
|
467
|
+
meta_type,
|
468
|
+
self.fig_dir / f"preliminary_dset-{dset_i+1}_summary.html",
|
469
|
+
)
|
470
|
+
|
471
|
+
# Plot mask
|
472
|
+
plot_mask(
|
473
|
+
dataset.masker.mask_img,
|
474
|
+
self.fig_dir / f"preliminary_dset-{dset_i+1}_figure-mask.png",
|
475
|
+
)
|
476
|
+
|
477
|
+
if meta_type == "CBMA":
|
478
|
+
# Plot coordinates for CBMA estimators
|
479
|
+
plot_coordinates(
|
480
|
+
dataset.coordinates,
|
481
|
+
self.fig_dir / f"preliminary_dset-{dset_i+1}_figure-static.png",
|
482
|
+
self.fig_dir / f"preliminary_dset-{dset_i+1}_figure-interactive.html",
|
483
|
+
self.fig_dir / f"preliminary_dset-{dset_i+1}_figure-legend.png",
|
484
|
+
)
|
485
|
+
elif meta_type == "IBMA":
|
486
|
+
# Use "z_maps", for Fishers, and Stouffers; otherwise use "beta_maps".
|
487
|
+
INPUT_TYPE_LABELS = {"z_maps": "Z", "t_maps": "T", "beta_maps": "Beta"}
|
488
|
+
for key_maps, x_label in INPUT_TYPE_LABELS.items():
|
489
|
+
if key_maps in self.results.estimator.inputs_:
|
490
|
+
break
|
491
|
+
else:
|
492
|
+
key_maps, x_label = "beta_maps", "Beta"
|
493
|
+
|
494
|
+
maps_arr = self.results.estimator.inputs_[key_maps]
|
495
|
+
ids_ = self.results.estimator.inputs_["id"]
|
496
|
+
|
497
|
+
if self.results.estimator.aggressive_mask:
|
498
|
+
_plot_relcov_map(
|
499
|
+
maps_arr,
|
500
|
+
self.results.estimator.masker,
|
501
|
+
self.fig_dir / f"preliminary_dset-{dset_i+1}_figure-relcov.png",
|
502
|
+
)
|
503
|
+
else:
|
504
|
+
dof_map = self.results.get_map("dof")
|
505
|
+
_plot_dof_map(
|
506
|
+
dof_map,
|
507
|
+
self.fig_dir / f"preliminary_dset-{dset_i+1}_figure-dof.png",
|
508
|
+
)
|
509
|
+
|
510
|
+
_plot_true_voxels(
|
511
|
+
maps_arr,
|
512
|
+
ids_,
|
513
|
+
self.fig_dir / f"preliminary_dset-{dset_i+1}_figure-truevoxels.html",
|
514
|
+
)
|
515
|
+
|
516
|
+
_plot_ridgeplot(
|
517
|
+
maps_arr,
|
518
|
+
ids_,
|
519
|
+
x_label,
|
520
|
+
self.fig_dir / f"preliminary_dset-{dset_i+1}_figure-ridgeplot.html",
|
521
|
+
)
|
522
|
+
|
523
|
+
_plot_sumstats(
|
524
|
+
maps_arr,
|
525
|
+
ids_,
|
526
|
+
self.fig_dir / f"preliminary_dset-{dset_i+1}_figure-summarystats.html",
|
527
|
+
)
|
528
|
+
|
529
|
+
# Compute similarity matrix
|
530
|
+
if self.results.estimator.inputs_["corr_matrix"] is None:
|
531
|
+
if self.results.estimator.aggressive_mask:
|
532
|
+
voxel_mask = self.results.estimator.inputs_["aggressive_mask"]
|
533
|
+
corr = np.corrcoef(
|
534
|
+
self.results.estimator.inputs_[key_maps][:, voxel_mask],
|
535
|
+
rowvar=True,
|
536
|
+
)
|
537
|
+
else:
|
538
|
+
corr = np.corrcoef(
|
539
|
+
self.results.estimator.inputs_[key_maps],
|
540
|
+
rowvar=True,
|
541
|
+
)
|
542
|
+
else:
|
543
|
+
corr = self.inputs_["corr_matrix"]
|
544
|
+
|
545
|
+
similarity_table = pd.DataFrame(
|
546
|
+
index=ids_,
|
547
|
+
columns=ids_,
|
548
|
+
data=corr,
|
549
|
+
)
|
550
|
+
|
551
|
+
plot_heatmap(
|
552
|
+
similarity_table,
|
553
|
+
self.fig_dir / f"preliminary_dset-{dset_i+1}_figure-similarity.html",
|
554
|
+
symmetric=True,
|
555
|
+
cmap="RdBu_r",
|
556
|
+
zmin=-1,
|
557
|
+
zmax=1,
|
558
|
+
)
|
559
|
+
|
560
|
+
_gen_est_summary(self.results.estimator, self.fig_dir / "estimator_summary.html")
|
561
|
+
_gen_cor_summary(self.results.corrector, self.fig_dir / "corrector_summary.html")
|
562
|
+
for diagnostic in self.results.diagnostics:
|
563
|
+
img_key = diagnostic.target_image
|
564
|
+
diag_name = diagnostic.__class__.__name__
|
565
|
+
threshold = diagnostic.voxel_thresh
|
566
|
+
|
567
|
+
_gen_fig_summary(img_key, threshold, self.fig_dir / "corrector_figure-summary.html")
|
568
|
+
_gen_diag_summary(diagnostic, self.fig_dir / "diagnostics_summary.html")
|
569
|
+
_gen_figures(self.results, img_key, diag_name, threshold, self.fig_dir)
|
570
|
+
|
571
|
+
# Default template from nimare
|
572
|
+
nimare_path = files("nimare")
|
573
|
+
self.template_path = nimare_path / "reports" / "report.tpl"
|
574
|
+
self._load_config(nimare_path / "reports" / "default.yml")
|
575
|
+
assert self.template_path.exists()
|
576
|
+
|
577
|
+
def _load_config(self, config):
|
578
|
+
from yaml import safe_load as load
|
579
|
+
|
580
|
+
settings = load(config.read_text())
|
581
|
+
self.packagename = settings.get("package", None)
|
582
|
+
|
583
|
+
self.index(settings["sections"])
|
584
|
+
|
585
|
+
def index(self, config):
|
586
|
+
"""Traverse the reports config definition and instantiate reportlets.
|
587
|
+
|
588
|
+
This method also places figures in their final location.
|
589
|
+
"""
|
590
|
+
for subrep_cfg in config:
|
591
|
+
reportlets = [Reportlet(self.out_dir, config=cfg) for cfg in subrep_cfg["reportlets"]]
|
592
|
+
|
593
|
+
if reportlets := [r for r in reportlets if not r.is_empty()]:
|
594
|
+
sub_report = SubReport(
|
595
|
+
subrep_cfg["name"],
|
596
|
+
isnested=False,
|
597
|
+
reportlets=reportlets,
|
598
|
+
title=subrep_cfg.get("title"),
|
599
|
+
)
|
600
|
+
self.sections.append(sub_report)
|
601
|
+
|
602
|
+
def generate_report(self):
|
603
|
+
"""Once the Report has been indexed, the final HTML can be generated."""
|
604
|
+
boilerplate = []
|
605
|
+
boiler_idx = 0
|
606
|
+
|
607
|
+
if hasattr(self.results, "description_"):
|
608
|
+
text = self.results.description_
|
609
|
+
references = self.results.bibtex_
|
610
|
+
text = textwrap.fill(text, 99)
|
611
|
+
|
612
|
+
boilerplate.append(
|
613
|
+
(
|
614
|
+
boiler_idx,
|
615
|
+
"LaTeX",
|
616
|
+
f"""<pre>{text}</pre>
|
617
|
+
<h3>Bibliography</h3>
|
618
|
+
<pre>{references}</pre>
|
619
|
+
""",
|
620
|
+
)
|
621
|
+
)
|
622
|
+
boiler_idx += 1
|
623
|
+
|
624
|
+
env = jinja2.Environment(
|
625
|
+
loader=jinja2.FileSystemLoader(searchpath=str(self.template_path.parent)),
|
626
|
+
trim_blocks=True,
|
627
|
+
lstrip_blocks=True,
|
628
|
+
autoescape=False,
|
629
|
+
)
|
630
|
+
report_tpl = env.get_template(self.template_path.name)
|
631
|
+
report_render = report_tpl.render(sections=self.sections, boilerplate=boilerplate)
|
632
|
+
|
633
|
+
# Write out report
|
634
|
+
self.out_dir.mkdir(parents=True, exist_ok=True)
|
635
|
+
(self.out_dir / self.out_filename).write_text(report_render, encoding="UTF-8")
|
636
|
+
|
637
|
+
|
638
|
+
def run_reports(
|
639
|
+
results,
|
640
|
+
out_dir,
|
641
|
+
):
|
642
|
+
"""Run the reports.
|
643
|
+
|
644
|
+
.. versionchanged:: 0.2.1
|
645
|
+
|
646
|
+
* Add similarity matrix to summary for image-based meta-analyses.
|
647
|
+
|
648
|
+
.. versionchanged:: 0.2.0
|
649
|
+
|
650
|
+
* Support for image-based meta-analyses.
|
651
|
+
|
652
|
+
.. versionadded:: 0.1.0
|
653
|
+
|
654
|
+
Parameters
|
655
|
+
----------
|
656
|
+
result : :obj:`~nimare.results.MetaResult`
|
657
|
+
A MetaResult produced by a coordinate- or image-based meta-analysis.
|
658
|
+
out_dir : :obj:`str`
|
659
|
+
Output directory in which to save the report.
|
660
|
+
"""
|
661
|
+
return Report(
|
662
|
+
results,
|
663
|
+
out_dir,
|
664
|
+
).generate_report()
|