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,651 @@
|
|
1
|
+
"""Plot figures for report."""
|
2
|
+
|
3
|
+
import matplotlib.colors as mcolors
|
4
|
+
import matplotlib.patches as mpatches
|
5
|
+
import matplotlib.pyplot as plt
|
6
|
+
import numpy as np
|
7
|
+
import pandas as pd
|
8
|
+
import plotly.express as px
|
9
|
+
from nilearn import datasets
|
10
|
+
from nilearn.plotting import (
|
11
|
+
plot_connectome,
|
12
|
+
plot_img,
|
13
|
+
plot_roi,
|
14
|
+
plot_stat_map,
|
15
|
+
view_connectome,
|
16
|
+
view_img,
|
17
|
+
)
|
18
|
+
from ridgeplot import ridgeplot
|
19
|
+
from scipy import stats
|
20
|
+
from scipy.cluster.hierarchy import leaves_list, linkage, optimal_leaf_ordering
|
21
|
+
|
22
|
+
TABLE_STYLE = [
|
23
|
+
dict(
|
24
|
+
selector="th, td",
|
25
|
+
props=[
|
26
|
+
("text-align", "center"),
|
27
|
+
("font-family", "monospace"),
|
28
|
+
("font-size", "15px"),
|
29
|
+
("padding", "5px 3px"),
|
30
|
+
("margin", "0px 3px"),
|
31
|
+
("border", "1px solid #ddd"),
|
32
|
+
],
|
33
|
+
),
|
34
|
+
]
|
35
|
+
|
36
|
+
|
37
|
+
PXS_PER_STD = 30 # Number of pixels per study, control the size (height) of Plotly figures
|
38
|
+
MAX_CHARS = 20 # Maximum number of characters for labels
|
39
|
+
|
40
|
+
|
41
|
+
def _check_extention(filename, exts):
|
42
|
+
if filename.suffix not in exts:
|
43
|
+
raise ValueError(
|
44
|
+
f'The "out_filename" provided has extension {filename.suffix}. '
|
45
|
+
f'Valid extensions are {", ".join(exts)}.'
|
46
|
+
)
|
47
|
+
|
48
|
+
|
49
|
+
def _reorder_matrix(mat, row_labels, col_labels, symmetric=False, reorder="single"):
|
50
|
+
"""Reorder a matrix.
|
51
|
+
|
52
|
+
This function reorders the provided matrix. It was adaptes from
|
53
|
+
nilearn.plotting.plot_matrix._reorder_matrix to reorder non-square matrices.
|
54
|
+
|
55
|
+
License
|
56
|
+
-------
|
57
|
+
New BSD License
|
58
|
+
Copyright (c) 2007 - 2023 The nilearn developers.
|
59
|
+
Redistribution and use in source and binary forms, with or without
|
60
|
+
modification, are permitted provided that the following conditions are met:
|
61
|
+
a. Redistributions of source code must retain the above copyright notice,
|
62
|
+
this list of conditions and the following disclaimer.
|
63
|
+
b. Redistributions in binary form must reproduce the above copyright
|
64
|
+
notice, this list of conditions and the following disclaimer in the
|
65
|
+
documentation and/or other materials provided with the distribution.
|
66
|
+
c. Neither the name of the nilearn developers nor the names of
|
67
|
+
its contributors may be used to endorse or promote products
|
68
|
+
derived from this software without specific prior written
|
69
|
+
permission.
|
70
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
71
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
72
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
73
|
+
ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
|
74
|
+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
75
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
76
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
77
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
78
|
+
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
79
|
+
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
80
|
+
DAMAGE.
|
81
|
+
"""
|
82
|
+
if not row_labels or not col_labels:
|
83
|
+
raise ValueError("Labels are needed to show the reordering.")
|
84
|
+
|
85
|
+
# Order rows
|
86
|
+
row_linkage_matrix = linkage(mat, method=reorder)
|
87
|
+
row_ordered_linkage = optimal_leaf_ordering(row_linkage_matrix, mat)
|
88
|
+
row_index = leaves_list(row_ordered_linkage)
|
89
|
+
|
90
|
+
# Make sure labels is an ndarray and copy it
|
91
|
+
row_labels = np.array(row_labels).copy()
|
92
|
+
|
93
|
+
if not symmetric:
|
94
|
+
# Order columns
|
95
|
+
col_linkage_matrix = linkage(mat.T, method=reorder)
|
96
|
+
col_ordered_linkage = optimal_leaf_ordering(col_linkage_matrix, mat.T)
|
97
|
+
col_index = leaves_list(col_ordered_linkage)
|
98
|
+
|
99
|
+
col_labels = np.array(col_labels).copy()
|
100
|
+
else:
|
101
|
+
col_index = row_index
|
102
|
+
col_labels = row_labels
|
103
|
+
|
104
|
+
mat = mat.copy()
|
105
|
+
|
106
|
+
# and reorder labels and matrix
|
107
|
+
row_labels = row_labels[row_index].tolist()
|
108
|
+
col_labels = col_labels[col_index].tolist()
|
109
|
+
mat = mat[row_index, :][:, col_index]
|
110
|
+
|
111
|
+
return mat, row_labels, col_labels
|
112
|
+
|
113
|
+
|
114
|
+
def plot_static_brain(img, out_filename, threshold=1e-06):
|
115
|
+
"""Plot static brain image.
|
116
|
+
|
117
|
+
.. versionadded:: 0.1.0
|
118
|
+
|
119
|
+
Parameters
|
120
|
+
----------
|
121
|
+
img : :obj:`~nibabel.nifti1.Nifti1Image`
|
122
|
+
Stat image to plot.
|
123
|
+
out_filename : :obj:`pathlib.Path`
|
124
|
+
The name of an image file to export the plot to.
|
125
|
+
Valid extensions are '.png', '.pdf', '.svg'.
|
126
|
+
threshold: a number, None, or 'auto', optional
|
127
|
+
If None is given, the image is not thresholded. If a number is given, it is
|
128
|
+
used to threshold the image: values below the threshold (in absolute value)
|
129
|
+
are plotted as transparent. If 'auto' is given, the threshold is determined
|
130
|
+
magically by analysis of the image. Default=1e-6.
|
131
|
+
"""
|
132
|
+
_check_extention(out_filename, [".png", ".pdf", ".svg"])
|
133
|
+
|
134
|
+
template = datasets.load_mni152_template(resolution=1)
|
135
|
+
fig = plot_stat_map(
|
136
|
+
img,
|
137
|
+
bg_img=template,
|
138
|
+
black_bg=False,
|
139
|
+
draw_cross=False,
|
140
|
+
threshold=threshold,
|
141
|
+
display_mode="mosaic",
|
142
|
+
symmetric_cbar=True,
|
143
|
+
)
|
144
|
+
fig.savefig(out_filename, dpi=300)
|
145
|
+
fig.close()
|
146
|
+
|
147
|
+
|
148
|
+
def plot_mask(mask, out_filename):
|
149
|
+
"""Plot mask.
|
150
|
+
|
151
|
+
.. versionadded:: 0.1.0
|
152
|
+
|
153
|
+
Parameters
|
154
|
+
----------
|
155
|
+
img : :obj:`~nibabel.nifti1.Nifti1Image`
|
156
|
+
Mask image to plot.
|
157
|
+
out_filename : :obj:`pathlib.Path`
|
158
|
+
The name of an image file to export the plot to.
|
159
|
+
Valid extensions are '.png', '.pdf', '.svg'.
|
160
|
+
"""
|
161
|
+
_check_extention(out_filename, [".png", ".pdf", ".svg"])
|
162
|
+
|
163
|
+
template = datasets.load_mni152_template(resolution=1)
|
164
|
+
|
165
|
+
fig = plot_roi(
|
166
|
+
mask,
|
167
|
+
bg_img=template,
|
168
|
+
black_bg=False,
|
169
|
+
draw_cross=False,
|
170
|
+
cmap="Blues",
|
171
|
+
vmin=0,
|
172
|
+
vmax=1,
|
173
|
+
alpha=0.7,
|
174
|
+
display_mode="mosaic",
|
175
|
+
)
|
176
|
+
fig.savefig(out_filename, dpi=300)
|
177
|
+
fig.close()
|
178
|
+
|
179
|
+
|
180
|
+
def plot_coordinates(
|
181
|
+
coordinates_df,
|
182
|
+
out_static_filename,
|
183
|
+
out_interactive_filename,
|
184
|
+
out_legend_filename,
|
185
|
+
):
|
186
|
+
"""Plot static and interactive coordinates.
|
187
|
+
|
188
|
+
.. versionadded:: 0.1.0
|
189
|
+
|
190
|
+
Parameters
|
191
|
+
----------
|
192
|
+
coordinates_df : :obj:`pandas.DataFrame`
|
193
|
+
A DataFrame with the coordinates in the dataset.
|
194
|
+
out_static_filename : :obj:`pathlib.Path`
|
195
|
+
The name of an image file to export the static plot to.
|
196
|
+
Valid extensions are '.png', '.pdf', '.svg'.
|
197
|
+
out_interactive_filename : :obj:`pathlib.Path`
|
198
|
+
The name of an image file to export the interactive plot to.
|
199
|
+
Valid extension is '.html'.
|
200
|
+
out_legend_filename : :obj:`pathlib.Path`
|
201
|
+
The name of an image file to export the legend plot to.
|
202
|
+
Valid extensions are '.png', '.pdf', '.svg'.
|
203
|
+
"""
|
204
|
+
_check_extention(out_static_filename, [".png", ".pdf", ".svg"])
|
205
|
+
_check_extention(out_interactive_filename, [".html"])
|
206
|
+
_check_extention(out_legend_filename, [".png", ".pdf", ".svg"])
|
207
|
+
|
208
|
+
node_coords = coordinates_df[["x", "y", "z"]].to_numpy()
|
209
|
+
n_coords = len(node_coords)
|
210
|
+
adjacency_matrix = np.zeros((n_coords, n_coords))
|
211
|
+
|
212
|
+
# Generate dictionary and array of colors for each unique ID
|
213
|
+
ids = coordinates_df["study_id"].to_list()
|
214
|
+
unq_ids = np.unique(ids)
|
215
|
+
cmap = plt.colormaps["tab20"].resampled(len(unq_ids))
|
216
|
+
colors_dict = {unq_id: mcolors.to_hex(cmap(i)) for i, unq_id in enumerate(unq_ids)}
|
217
|
+
colors = [colors_dict[id_] for id_ in ids]
|
218
|
+
|
219
|
+
fig = plot_connectome(adjacency_matrix, node_coords, node_color=colors)
|
220
|
+
fig.savefig(out_static_filename, dpi=300)
|
221
|
+
fig.close()
|
222
|
+
|
223
|
+
# Generate legend
|
224
|
+
patches_lst = [
|
225
|
+
mpatches.Patch(color=color, label=label) for label, color in colors_dict.items()
|
226
|
+
]
|
227
|
+
|
228
|
+
# Plot legeng
|
229
|
+
max_len_per_page = 200
|
230
|
+
max_legend_len = max(len(id_) for id_ in unq_ids)
|
231
|
+
ncol = 1 if max_legend_len > max_len_per_page else int(max_len_per_page / max_legend_len)
|
232
|
+
labl_fig, ax = plt.subplots(1, 1)
|
233
|
+
labl_fig.legend(
|
234
|
+
handles=patches_lst,
|
235
|
+
ncol=ncol,
|
236
|
+
fontsize=10,
|
237
|
+
loc="center",
|
238
|
+
)
|
239
|
+
ax.axis("off")
|
240
|
+
labl_fig.savefig(out_legend_filename, bbox_inches="tight", dpi=300)
|
241
|
+
plt.close()
|
242
|
+
|
243
|
+
# Plot interactive connectome
|
244
|
+
html_view = view_connectome(
|
245
|
+
adjacency_matrix,
|
246
|
+
node_coords,
|
247
|
+
node_size=10,
|
248
|
+
colorbar=False,
|
249
|
+
node_color=colors,
|
250
|
+
)
|
251
|
+
html_view.save_as_html(out_interactive_filename)
|
252
|
+
|
253
|
+
|
254
|
+
def plot_interactive_brain(img, out_filename, threshold=1e-06):
|
255
|
+
"""Plot interactive brain image.
|
256
|
+
|
257
|
+
.. versionadded:: 0.1.0
|
258
|
+
|
259
|
+
Parameters
|
260
|
+
----------
|
261
|
+
img : :obj:`~nibabel.nifti1.Nifti1Image`
|
262
|
+
Stat image to plot.
|
263
|
+
out_filename : :obj:`pathlib.Path`
|
264
|
+
The name of an image file to export the plot to. Valid extension is '.html'.
|
265
|
+
threshold: a number, None, or 'auto', optional
|
266
|
+
If None is given, the image is not thresholded. If a number is given, it is
|
267
|
+
used to threshold the image: values below the threshold (in absolute value)
|
268
|
+
are plotted as transparent. If 'auto' is given, the threshold is determined
|
269
|
+
magically by analysis of the image. Default=1e-6.
|
270
|
+
"""
|
271
|
+
_check_extention(out_filename, [".html"])
|
272
|
+
|
273
|
+
template = datasets.load_mni152_template(resolution=1)
|
274
|
+
html_view = view_img(
|
275
|
+
img,
|
276
|
+
bg_img=template,
|
277
|
+
black_bg=False,
|
278
|
+
threshold=threshold,
|
279
|
+
symmetric_cmap=True,
|
280
|
+
)
|
281
|
+
html_view.save_as_html(out_filename)
|
282
|
+
|
283
|
+
|
284
|
+
def plot_heatmap(
|
285
|
+
data_df,
|
286
|
+
out_filename,
|
287
|
+
symmetric=False,
|
288
|
+
reorder="single",
|
289
|
+
cmap="Reds",
|
290
|
+
zmin=None,
|
291
|
+
zmax=None,
|
292
|
+
):
|
293
|
+
"""Plot heatmap.
|
294
|
+
|
295
|
+
.. versionadded:: 0.1.0
|
296
|
+
|
297
|
+
Parameters
|
298
|
+
----------
|
299
|
+
data_df : :obj:`pandas.DataFrame`
|
300
|
+
A DataFrame with the data for the heatmap. It could be a correlation matrix or
|
301
|
+
a contribution matrix with information about the relative contributions of
|
302
|
+
each experiment to each cluster in the thresholded map.
|
303
|
+
out_filename : :obj:`pathlib.Path`
|
304
|
+
The name of an image file to export the plot to.
|
305
|
+
Valid extension is '.html'.
|
306
|
+
symmetric : :obj:`bool`, optional
|
307
|
+
Whether to reorder the matrix symmetrically. Use True if using a correlation matrix.
|
308
|
+
Default is False.
|
309
|
+
reorder : :obj:`str`, optional
|
310
|
+
The method to use for reordering the matrix. Default is 'average'.
|
311
|
+
cmap : :obj:`str`, optional
|
312
|
+
The colormap to use. Default is 'Reds'.
|
313
|
+
zmin : :obj:`float`, optional
|
314
|
+
The minimum value to use for the colormap. Default is None.
|
315
|
+
zmax : :obj:`float`, optional
|
316
|
+
The maximum value to use for the colormap. Default is None.
|
317
|
+
"""
|
318
|
+
_check_extention(out_filename, [".html"])
|
319
|
+
|
320
|
+
n_studies, n_clusters = data_df.shape
|
321
|
+
if (n_studies > 2) and (n_clusters > 2):
|
322
|
+
# Reorder matrix only if more than 1 cluster/experiment
|
323
|
+
mat = data_df.to_numpy()
|
324
|
+
row_labels, col_labels = (
|
325
|
+
data_df.index.to_list(),
|
326
|
+
data_df.columns.to_list(),
|
327
|
+
)
|
328
|
+
new_mat, new_row_labels, new_col_labels = _reorder_matrix(
|
329
|
+
mat,
|
330
|
+
row_labels,
|
331
|
+
col_labels,
|
332
|
+
symmetric=symmetric,
|
333
|
+
reorder=reorder,
|
334
|
+
)
|
335
|
+
|
336
|
+
# Truncate labels to MAX_CHARS characters
|
337
|
+
x_labels = [label[:MAX_CHARS] for label in new_col_labels]
|
338
|
+
y_labels = [label[:MAX_CHARS] for label in new_row_labels]
|
339
|
+
data_df = pd.DataFrame(new_mat, columns=x_labels, index=y_labels)
|
340
|
+
|
341
|
+
fig = px.imshow(data_df, color_continuous_scale=cmap, zmin=zmin, zmax=zmax, aspect="equal")
|
342
|
+
|
343
|
+
height = n_studies * PXS_PER_STD
|
344
|
+
fig.update_layout(autosize=True, height=height)
|
345
|
+
fig.write_html(out_filename, full_html=True, include_plotlyjs=True)
|
346
|
+
|
347
|
+
|
348
|
+
def gen_table(clusters_table, out_filename):
|
349
|
+
"""Generate table.
|
350
|
+
|
351
|
+
.. versionadded:: 0.1.0
|
352
|
+
|
353
|
+
Parameters
|
354
|
+
----------
|
355
|
+
clusters_table : :obj:`pandas.DataFrame`
|
356
|
+
A DataFrame with information about each cluster.
|
357
|
+
out_filename : :obj:`pathlib.Path`
|
358
|
+
The name of an image file to export the plot to.
|
359
|
+
Valid extension is '.html'.
|
360
|
+
"""
|
361
|
+
_check_extention(out_filename, [".html"])
|
362
|
+
|
363
|
+
clust_ids = clusters_table["Cluster ID"].to_list()
|
364
|
+
clusters_table = clusters_table.drop(columns=["Cluster ID"])
|
365
|
+
|
366
|
+
tail = [c_id.split(" ")[0].split("Tail")[0] for c_id in clust_ids]
|
367
|
+
ids = [c_id.split(" ")[1] for c_id in clust_ids]
|
368
|
+
tuples = list(zip(*[tail, ids]))
|
369
|
+
row = pd.MultiIndex.from_tuples(tuples)
|
370
|
+
clusters_table.index = row
|
371
|
+
clusters_table.index = clusters_table.index.rename(["Tail", "Cluster ID"])
|
372
|
+
|
373
|
+
styled_df = clusters_table.style.format(precision=2).set_table_styles(TABLE_STYLE)
|
374
|
+
styled_df.to_html(out_filename)
|
375
|
+
|
376
|
+
|
377
|
+
def plot_clusters(img, out_filename):
|
378
|
+
"""Plot clusters.
|
379
|
+
|
380
|
+
.. versionadded:: 0.1.0
|
381
|
+
|
382
|
+
Parameters
|
383
|
+
----------
|
384
|
+
img : :obj:`~nibabel.nifti1.Nifti1Image`
|
385
|
+
Label image to plot.
|
386
|
+
out_filename : :obj:`pathlib.Path`
|
387
|
+
The name of an image file to export the plot to.
|
388
|
+
Valid extensions are '.png', '.pdf', '.svg'.
|
389
|
+
"""
|
390
|
+
_check_extention(out_filename, [".png", ".pdf", ".svg"])
|
391
|
+
|
392
|
+
template = datasets.load_mni152_template(resolution=1)
|
393
|
+
|
394
|
+
# Define cmap depending on the number of clusters
|
395
|
+
clust_ids = list(np.unique(img.get_fdata())[1:])
|
396
|
+
cmap = plt.colormaps["tab20"].resampled(len(clust_ids))
|
397
|
+
|
398
|
+
fig = plot_roi(
|
399
|
+
img,
|
400
|
+
bg_img=template,
|
401
|
+
black_bg=False,
|
402
|
+
draw_cross=False,
|
403
|
+
cmap=cmap,
|
404
|
+
alpha=0.8,
|
405
|
+
colorbar=True,
|
406
|
+
display_mode="mosaic",
|
407
|
+
)
|
408
|
+
fig.savefig(out_filename, dpi=300)
|
409
|
+
fig.close()
|
410
|
+
|
411
|
+
|
412
|
+
def _plot_true_voxels(maps_arr, ids_, out_filename):
|
413
|
+
"""Plot percentage of valid voxels.
|
414
|
+
|
415
|
+
.. versionadded:: 0.2.2
|
416
|
+
|
417
|
+
"""
|
418
|
+
n_studies, n_voxels = maps_arr.shape
|
419
|
+
mask = ~np.isnan(maps_arr) & (maps_arr != 0)
|
420
|
+
|
421
|
+
x_label, y_label = "Voxels Included", "ID"
|
422
|
+
perc_voxs = mask.sum(axis=1) / n_voxels
|
423
|
+
valid_df = pd.DataFrame({y_label: ids_, x_label: perc_voxs})
|
424
|
+
valid_sorted_df = valid_df.sort_values(x_label, ascending=True)
|
425
|
+
|
426
|
+
fig = px.bar(
|
427
|
+
valid_sorted_df,
|
428
|
+
x=x_label,
|
429
|
+
y=y_label,
|
430
|
+
orientation="h",
|
431
|
+
color=x_label,
|
432
|
+
color_continuous_scale="blues",
|
433
|
+
range_color=(0, 1),
|
434
|
+
)
|
435
|
+
|
436
|
+
fig.update_xaxes(
|
437
|
+
showline=True,
|
438
|
+
linewidth=2,
|
439
|
+
linecolor="black",
|
440
|
+
visible=True,
|
441
|
+
showticklabels=False,
|
442
|
+
title=None,
|
443
|
+
)
|
444
|
+
fig.update_yaxes(
|
445
|
+
showline=True,
|
446
|
+
linewidth=2,
|
447
|
+
linecolor="black",
|
448
|
+
visible=True,
|
449
|
+
ticktext=valid_sorted_df[y_label].str.slice(0, MAX_CHARS).tolist(),
|
450
|
+
)
|
451
|
+
|
452
|
+
height = n_studies * PXS_PER_STD
|
453
|
+
fig.update_layout(
|
454
|
+
height=height,
|
455
|
+
autosize=True,
|
456
|
+
font_size=14,
|
457
|
+
plot_bgcolor="white",
|
458
|
+
xaxis_gridcolor="white",
|
459
|
+
yaxis_gridcolor="white",
|
460
|
+
xaxis_gridwidth=2,
|
461
|
+
showlegend=False,
|
462
|
+
)
|
463
|
+
fig.write_html(out_filename, full_html=True, include_plotlyjs=True)
|
464
|
+
|
465
|
+
|
466
|
+
def _plot_ridgeplot(maps_arr, ids_, x_label, out_filename):
|
467
|
+
"""Plot histograms of the images.
|
468
|
+
|
469
|
+
.. versionadded:: 0.2.0
|
470
|
+
|
471
|
+
"""
|
472
|
+
n_studies = len(ids_)
|
473
|
+
labels = [id_[:MAX_CHARS] for id_ in ids_] # Truncate labels to MAX_CHARS characters
|
474
|
+
|
475
|
+
mask = ~np.isnan(maps_arr) & (maps_arr != 0)
|
476
|
+
maps_lst = [maps_arr[i][mask[i]] for i in range(n_studies)]
|
477
|
+
|
478
|
+
N_KDE_POINTS = 100
|
479
|
+
max_val = 8 if x_label == "Z" else 1
|
480
|
+
kde_points = np.linspace(-max_val, max_val, N_KDE_POINTS)
|
481
|
+
bandwidth = 0.5 if x_label == "Z" else 0.1
|
482
|
+
|
483
|
+
fig = ridgeplot(
|
484
|
+
samples=maps_lst,
|
485
|
+
labels=labels,
|
486
|
+
coloralpha=0.98,
|
487
|
+
bandwidth=bandwidth,
|
488
|
+
kde_points=kde_points,
|
489
|
+
colorscale="Bluered",
|
490
|
+
colormode="mean-means",
|
491
|
+
spacing=PXS_PER_STD / 100,
|
492
|
+
linewidth=2,
|
493
|
+
)
|
494
|
+
|
495
|
+
height = n_studies * PXS_PER_STD
|
496
|
+
fig.update_layout(
|
497
|
+
height=height,
|
498
|
+
autosize=True,
|
499
|
+
font_size=14,
|
500
|
+
plot_bgcolor="white",
|
501
|
+
xaxis_gridcolor="white",
|
502
|
+
yaxis_gridcolor="white",
|
503
|
+
xaxis_gridwidth=2,
|
504
|
+
xaxis_title=x_label,
|
505
|
+
showlegend=False,
|
506
|
+
)
|
507
|
+
fig.write_html(out_filename, full_html=True, include_plotlyjs=True)
|
508
|
+
|
509
|
+
|
510
|
+
def _plot_sumstats(maps_arr, ids_, out_filename):
|
511
|
+
"""Plot summary statistics of the images.
|
512
|
+
|
513
|
+
.. versionadded:: 0.2.2
|
514
|
+
|
515
|
+
"""
|
516
|
+
n_studies = len(ids_)
|
517
|
+
mask = ~np.isnan(maps_arr) & (maps_arr != 0)
|
518
|
+
maps_lst = [maps_arr[i][mask[i]] for i in range(n_studies)]
|
519
|
+
|
520
|
+
stats_lbls = [
|
521
|
+
"Mean",
|
522
|
+
"STD",
|
523
|
+
"Var",
|
524
|
+
"Median",
|
525
|
+
"Mode",
|
526
|
+
"Min",
|
527
|
+
"Max",
|
528
|
+
"Skew",
|
529
|
+
"Kurtosis",
|
530
|
+
"Range",
|
531
|
+
"Moment",
|
532
|
+
"IQR",
|
533
|
+
]
|
534
|
+
scores, id_lst = [], []
|
535
|
+
for id_, map_ in zip(ids_, maps_lst):
|
536
|
+
scores.extend(
|
537
|
+
[
|
538
|
+
np.mean(map_),
|
539
|
+
np.std(map_),
|
540
|
+
np.var(map_),
|
541
|
+
np.median(map_),
|
542
|
+
stats.mode(map_)[0],
|
543
|
+
np.min(map_),
|
544
|
+
np.max(map_),
|
545
|
+
stats.skew(map_),
|
546
|
+
stats.kurtosis(map_),
|
547
|
+
np.max(map_) - np.min(map_),
|
548
|
+
stats.moment(map_, moment=4),
|
549
|
+
stats.iqr(map_),
|
550
|
+
]
|
551
|
+
)
|
552
|
+
id_lst.extend([id_] * len(stats_lbls))
|
553
|
+
|
554
|
+
stats_labels = stats_lbls * n_studies
|
555
|
+
data_df = pd.DataFrame({"ID": id_lst, "Score": scores, "Stat": stats_labels})
|
556
|
+
|
557
|
+
fig = px.strip(
|
558
|
+
data_df,
|
559
|
+
y="Score",
|
560
|
+
color="ID",
|
561
|
+
facet_col="Stat",
|
562
|
+
stripmode="group",
|
563
|
+
facet_col_wrap=4,
|
564
|
+
facet_col_spacing=0.08,
|
565
|
+
)
|
566
|
+
|
567
|
+
fig.update_xaxes(showline=True, linewidth=2, linecolor="black", mirror=True)
|
568
|
+
fig.update_yaxes(
|
569
|
+
constrain="domain",
|
570
|
+
matches=None,
|
571
|
+
showline=True,
|
572
|
+
linewidth=2,
|
573
|
+
linecolor="black",
|
574
|
+
mirror=True,
|
575
|
+
title=None,
|
576
|
+
)
|
577
|
+
fig.update_layout(
|
578
|
+
height=900,
|
579
|
+
autosize=True,
|
580
|
+
font_size=14,
|
581
|
+
plot_bgcolor="white",
|
582
|
+
xaxis_gridcolor="white",
|
583
|
+
yaxis_gridcolor="white",
|
584
|
+
xaxis_gridwidth=2,
|
585
|
+
showlegend=False,
|
586
|
+
)
|
587
|
+
fig.for_each_yaxis(lambda yaxis: yaxis.update(showticklabels=True))
|
588
|
+
fig.write_html(out_filename, full_html=True, include_plotlyjs=True)
|
589
|
+
|
590
|
+
|
591
|
+
def _plot_relcov_map(maps_arr, masker, out_filename):
|
592
|
+
"""Plot relative coverage map.
|
593
|
+
|
594
|
+
.. versionadded:: 0.2.0
|
595
|
+
|
596
|
+
"""
|
597
|
+
_check_extention(out_filename, [".png", ".pdf", ".svg"])
|
598
|
+
|
599
|
+
epsilon = 1e-05
|
600
|
+
|
601
|
+
# Binaries maps and create relative coverage map
|
602
|
+
binary_maps_arr = np.where((-epsilon > maps_arr) | (maps_arr > epsilon), 1, 0)
|
603
|
+
coverage_arr = np.sum(binary_maps_arr, axis=0) / binary_maps_arr.shape[0]
|
604
|
+
|
605
|
+
coverage_img = masker.inverse_transform(coverage_arr)
|
606
|
+
|
607
|
+
# Plot coverage map
|
608
|
+
template = datasets.load_mni152_template(resolution=1)
|
609
|
+
fig = plot_img(
|
610
|
+
coverage_img,
|
611
|
+
bg_img=template,
|
612
|
+
black_bg=False,
|
613
|
+
draw_cross=False,
|
614
|
+
threshold=epsilon,
|
615
|
+
alpha=0.7,
|
616
|
+
colorbar=True,
|
617
|
+
cmap="Blues",
|
618
|
+
vmin=0,
|
619
|
+
vmax=1,
|
620
|
+
display_mode="mosaic",
|
621
|
+
)
|
622
|
+
fig.savefig(out_filename, dpi=300)
|
623
|
+
fig.close()
|
624
|
+
|
625
|
+
|
626
|
+
def _plot_dof_map(dof_map, out_filename):
|
627
|
+
"""Plot DoF map.
|
628
|
+
|
629
|
+
.. versionadded:: 0.2.1
|
630
|
+
|
631
|
+
"""
|
632
|
+
_check_extention(out_filename, [".png", ".pdf", ".svg"])
|
633
|
+
|
634
|
+
epsilon = 1e-05
|
635
|
+
|
636
|
+
# Plot coverage map
|
637
|
+
template = datasets.load_mni152_template(resolution=1)
|
638
|
+
fig = plot_img(
|
639
|
+
dof_map,
|
640
|
+
bg_img=template,
|
641
|
+
black_bg=False,
|
642
|
+
draw_cross=False,
|
643
|
+
threshold=epsilon,
|
644
|
+
alpha=0.7,
|
645
|
+
colorbar=True,
|
646
|
+
cmap="YlOrRd",
|
647
|
+
vmin=0,
|
648
|
+
display_mode="mosaic",
|
649
|
+
)
|
650
|
+
fig.savefig(out_filename, dpi=300)
|
651
|
+
fig.close()
|