• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

neurospin-deepinsight / brainprep / 21867385651

10 Feb 2026 01:45PM UTC coverage: 77.455% (-1.1%) from 78.514%
21867385651

push

github

AGrigis
.github/workflows/testing: add FS env.

1412 of 1823 relevant lines covered (77.45%)

0.77 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

47.87
/brainprep/interfaces/plotting.py
1
##########################################################################
2
# NSAp - Copyright (C) CEA, 2021 - 2025
3
# Distributed under the terms of the CeCILL-B license, as published by
4
# the CEA-CNRS-INRIA. Refer to the LICENSE file or to
5
# http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
6
# for details.
7
##########################################################################
8

9
"""
10
Plotting functions.
11
"""
12

13
import itertools
1✔
14

15
import matplotlib.pyplot as plt
1✔
16
import nibabel
1✔
17
import numpy as np
1✔
18
import pandas as pd
1✔
19
import seaborn as sns
1✔
20
from nilearn import plotting
1✔
21

22
from ..reporting import log_runtime
1✔
23
from ..typing import (
1✔
24
    Directory,
25
    File,
26
)
27
from ..utils import (
1✔
28
    coerceparams,
29
    outputdir,
30
)
31
from ..wrappers import pywrapper
1✔
32

33

34
@coerceparams
1✔
35
@outputdir(
1✔
36
    plotting=True)
37
@log_runtime(
1✔
38
    bunched=False)
39
@pywrapper
1✔
40
def plot_defacing_mosaic(
1✔
41
        im_file: File,
42
        mask_file: File,
43
        output_dir: Directory,
44
        entities: dict,
45
        dryrun: bool = False) -> tuple[File]:
46
    """
47
    Generates a defacing mosaic image by overlaying a mask on an anatomical
48
    image.
49

50
    Parameters
51
    ----------
52
    im_file : File
53
        Path to the anatomical image.
54
    mask_file : File
55
        Path to the defacing mask.
56
    output_dir : Directory
57
        Directory where the mosaic image will be saved.
58
    entities : dict
59
        A dictionary of parsed BIDS entities including modality.
60
    dryrun : bool
61
        If True, skip actual computation and file writing. Default False.
62

63
    Returns
64
    -------
65
    mosaic_file : File
66
        Path to the saved mosaic image.
67
    """
68
    basename = "sub-{sub}_ses-{ses}_run-{run}_mod-T1w_deface".format(
1✔
69
        **entities)
70
    mosaic_file = output_dir / f"{basename}mosaic.png"
1✔
71

72
    if not dryrun:
1✔
73
        plotting.plot_roi(
×
74
            mask_file,
75
            bg_img=im_file,
76
            display_mode="z",
77
            cut_coords=25,
78
            black_bg=True,
79
            alpha=0.6,
80
            colorbar=False,
81
            output_file=mosaic_file
82
        )
83
        arr = plt.imread(mosaic_file)
×
84
        cut = int(arr.shape[1] / 5)
×
85
        plt.figure()
×
86
        arr = np.concatenate(
×
87
            [arr[:, idx * cut: (idx + 1) * cut] for idx in range(5)], axis=0)
88
        plt.imshow(arr)
×
89
        plt.axis("off")
×
90
        plt.savefig(mosaic_file)
×
91

92
    return (mosaic_file, )
1✔
93

94

95
@coerceparams
1✔
96
@outputdir(
1✔
97
    plotting=True)
98
@log_runtime(
1✔
99
    bunched=False)
100
@pywrapper
1✔
101
def plot_histogram(
1✔
102
        table_file: File,
103
        col_name: str,
104
        output_dir: Directory,
105
        bar_coords: list[float] | None = None,
106
        dryrun: bool = False) -> tuple[File]:
107
    """
108
    Generates a histogram image with optional vertical bars.
109

110
    Parameters
111
    ----------
112
    table_file : File
113
        TSV table containing the data to be displayed.
114
    col_name : str
115
        Name of the column containing the histogram data.
116
    output_dir : Directory
117
        Directory where the image with the histogram will be saved.
118
    bar_coords: list[float] | None
119
        Coordianates of vertical lines to be displayed in red. Default None.
120
    dryrun : bool
121
        If True, skip actual computation and file writing. Default False.
122

123
    Returns
124
    -------
125
    histogram_file : File
126
        Generated image with the histogram.
127
    """
128
    histogram_file = output_dir / f"histogram_{col_name}.png"
1✔
129

130
    if not dryrun:
1✔
131

132
        data = pd.read_csv(
×
133
            table_file,
134
            sep="\t",
135
        )
136
        arr = data[col_name].astype(float)
×
137
        arr = arr[~np.isnan(arr)]
×
138
        arr = arr[~np.isinf(arr)]
×
139

140
        _, ax = plt.subplots()
×
141
        sns.histplot(
×
142
            arr,
143
            color="gray",
144
            alpha=0.6,
145
            ax=ax,
146
            kde=True,
147
            stat="density",
148
            label=col_name,
149
        )
150
        for x_coord in bar_coords or []:
×
151
            ax.axvline(x=x_coord, color="red")
×
152
        ax.spines["right"].set_visible(False)
×
153
        ax.spines["top"].set_visible(False)
×
154
        ax.legend()
×
155

156
        plt.savefig(histogram_file)
×
157

158
    return (histogram_file, )
1✔
159

160

161
@coerceparams
1✔
162
@outputdir(
1✔
163
    plotting=True)
164
@log_runtime(
1✔
165
    bunched=False)
166
@pywrapper
1✔
167
def plot_brainparc(
1✔
168
        wm_mask_file: File,
169
        gm_mask_file: File,
170
        csf_mask_file: File,
171
        brain_mask_file: File,
172
        output_dir: Directory,
173
        entities: dict,
174
        dryrun: bool = False) -> tuple[File]:
175
    """
176

177
    Parameters
178
    ----------
179
    wm_mask_file : File
180
        Binary mask of white matter regions.
181
    gm_mask_file : File
182
        Binary mask of gray matter regions.
183
    csf_mask_file : File
184
        Binary mask of cerebrospinal fluid regions.
185
    brain_mask_file : File
186
        Binary brain mask file.
187
    output_dir : Directory
188
        FreeSurfer working directory containing all the subjects.
189
    entities : dict
190
        A dictionary of parsed BIDS entities including modality.
191
    dryrun : bool
192
        If True, skip actual computation and file writing. Default False.
193

194
    Returns
195
    -------
196
    brainparc_image_file : File
197
        Image of the GM mask and GM, WM, CSF tissues histograms.
198
    """
199
    basename = "sub-{sub}_ses-{ses}_run-{run}_brainparc".format(
1✔
200
        **entities)
201
    brainparc_image_file = output_dir / f"{basename}.png"
1✔
202

203
    if not dryrun:
1✔
204

205
        subject = f"run-{entities['run']}"
×
206
        anat_file = output_dir.parent / subject / "mri" / "norm.mgz"
×
207

208
        fig, axs = plt.subplots(2)
×
209
        plotting.plot_roi(
×
210
            roi_img=gm_mask_file,
211
            bg_img=anat_file,
212
            alpha=0.3,
213
            figure=fig,
214
            axes=axs[0],
215
        )
216

217
        anat_arr = nibabel.load(anat_file).get_fdata()
×
218
        mask_arr = nibabel.load(brain_mask_file).get_fdata()
×
219
        bins = np.histogram_bin_edges(
×
220
            anat_arr[mask_arr.astype(bool)],
221
            bins="auto",
222
        )
223
        palette = itertools.cycle(sns.color_palette("Set1"))
×
224
        for name, path in [("WM", wm_mask_file),
×
225
                           ("GM", gm_mask_file),
226
                           ("CSF", csf_mask_file)]:
227
            mask = nibabel.load(path).get_fdata()
×
228
            sns.histplot(
×
229
                anat_arr[mask.astype(bool)],
230
                bins=bins,
231
                color=next(palette),
232
                alpha=0.6,
233
                ax=axs[1],
234
                kde=True,
235
                stat="density",
236
                label=name,
237
            )
238
        axs[1].spines["right"].set_visible(False)
×
239
        axs[1].spines["top"].set_visible(False)
×
240
        axs[1].legend()
×
241

242
        plt.subplots_adjust(wspace=0, hspace=0, top=0.9, bottom=0.1)
×
243
        plt.savefig(brainparc_image_file)
×
244

245
    return (brainparc_image_file, )
1✔
246

247

248
@coerceparams
1✔
249
@outputdir(
1✔
250
    plotting=True)
251
@log_runtime(
1✔
252
    bunched=False)
253
@pywrapper
1✔
254
def plot_pca(
1✔
255
        pca_file: File,
256
        output_dir: Directory,
257
        dryrun: bool = False) -> tuple[File]:
258
    """
259
    Plot the two first PCA components.
260

261
    Parameters
262
    ----------
263
    pca_file : File
264
        TSV file containing PCA two first components as two columns named
265
        ``pc1`` and ``pc2``, as well as BIDS ``participant_id``, ``session``,
266
        and ``run``.
267
    output_dir : Directory
268
        Directory where the result image will be saved.
269
    dryrun : bool
270
        If True, skip actual computation and file writing. Default False.
271

272
    Returns
273
    -------
274
    pca_image_file : File
275
        Generated image with the two first PCA components.
276
    """
277
    pca_image_file = output_dir / f"pca.png"
1✔
278

279
    if not dryrun:
1✔
280

281
        df = pd.read_csv(pca_file, sep="\t")
×
282

283
        fig, ax = plt.subplots(figsize=(20, 10))
×
284
        ax.scatter(df.pc1, df.pc2)
×
285
        for idx in range(len(df)):
×
286
            ax.annotate(
×
287
                f"{df.participant_id[idx]}-{df.session[idx]}-{df.run[idx]}",
288
                xy=(df.pc1[idx], df.pc2[idx]),
289
                xytext=(4, 4),
290
                textcoords="offset pixels"
291
            )
292
        plt.xlabel(f"PC1 (var={df.explained_variance_ratio_pc1[0]:.2f})")
×
293
        plt.ylabel(f"PC2 (var={df.explained_variance_ratio_pc2[1]:.2f})")
×
294
        plt.axis("equal")
×
295
        ax.spines["right"].set_visible(False)
×
296
        ax.spines["top"].set_visible(False)
×
297
        plt.tight_layout()
×
298
        plt.savefig(pca_image_file)
×
299
        plt.close(fig)
×
300

301
    return (pca_image_file, )
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc