Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add adaptive mask plot to report #1073

Merged
merged 18 commits into from
Apr 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added docs/_static/adaptive_mask.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 20 additions & 1 deletion docs/outputs.rst
Original file line number Diff line number Diff line change
Expand Up @@ -528,11 +528,30 @@ should not overly focus on carpet plots and should examine these results in cont
:height: 400px


**************************
Adaptive Mask Summary Plot
**************************

Below the carpet plots is a summary plot of the adaptive mask.

This figure overlays contours reflecting the boundaries of the following masks onto the mean optimally combined data:

- **Base**: The base mask, either provided by the user or generated automatically using ``compute_epi_mask``.
- **Optimal combination**: The mask used for optimal combination and denoising.
This corresponds to values greater than or equal to 1 (at least 1 good echo) in the adaptive mask.
- **Classification**: The mask used for the decomposition and component classification steps.
This corresponds to values greather than or equal to 3 (at least 3 good echoes) in the adaptive mask.

.. image:: /_static/adaptive_mask.png
:align: center
:height: 400px


************************
T2* and S0 Summary Plots
************************

Below the carpet plots are summary plots for the T2* and S0 maps.
Below the adaptive mask plot are summary plots for the T2* and S0 maps.
Each map has two figures: a spatial map of the values and a histogram of the voxelwise values.
The T2* map should look similar to T2 maps and be brightest in the ventricles and darkest in areas of largest susceptibility.
The S0 map should roughly follow the signal-to-noise ratio and will be brightest near the surface near RF coils.
Expand Down
12 changes: 8 additions & 4 deletions tedana/reporting/data/html/report_body_template.html
Original file line number Diff line number Diff line change
Expand Up @@ -180,21 +180,25 @@ <h1>Carpet plots</h1>
</div>
</div>
</div>
<div class="carpet-plots">
<h1>Adaptive mask</h1>
<img id="adaptiveMask" src="$adaptiveMask" style="height: 500px" />
</div>
<div class="carpet-plots">
<h1>T2* and S0</h1>
<h2>T2*</h2>
<div class="carpet-plots-image">
<img id="t2starBrainPlot" src="$t2starBrainPlot" class="brainplot" />
<img id="t2starBrainPlot" src="$t2starBrainPlot" style="height: 500px" />
</div>
<div class="carpet-plots-image">
<img id="t2starHistogram" src="$t2starHistogram" />
<img id="t2starHistogram" src="$t2starHistogram" style="height: 500px" />
</div>
<h2>S0</h2>
<div class="carpet-plots-image">
<img id="s0BrainPlot" src="$s0BrainPlot" class="brainplot" />
<img id="s0BrainPlot" src="$s0BrainPlot" style="height: 500px" />
</div>
<div class="carpet-plots-image">
<img id="s0Histogram" src="$s0Histogram" />
<img id="s0Histogram" src="$s0Histogram" style="height: 500px" />
</div>
</div>
</div>
Expand Down
4 changes: 4 additions & 0 deletions tedana/reporting/html_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,9 @@ def _update_template_bokeh(bokeh_id, info_table, about, prefix, references, boke
# Initial carpet plot (default one)
initial_carpet = f"./figures/{prefix}carpet_optcom.svg"

# Adaptive mask image
adaptive_mask = f"./figures/{prefix}adaptive_mask.svg"

# T2* and S0 images
t2star_brain = f"./figures/{prefix}t2star_brain.svg"
t2star_histogram = f"./figures/{prefix}t2star_histogram.svg"
Expand All @@ -165,6 +168,7 @@ def _update_template_bokeh(bokeh_id, info_table, about, prefix, references, boke
about=about,
prefix=prefix,
initialCarpet=initial_carpet,
adaptiveMask=adaptive_mask,
t2starBrainPlot=t2star_brain,
t2starHistogram=t2star_histogram,
s0BrainPlot=s0_brain,
Expand Down
79 changes: 79 additions & 0 deletions tedana/reporting/static_figures.py
Original file line number Diff line number Diff line change
Expand Up @@ -616,3 +616,82 @@ def plot_t2star_and_s0(
annotate=False,
output_file=os.path.join(io_generator.out_dir, "figures", s0_plot),
)


def plot_adaptive_mask(
*,
optcom: np.ndarray,
base_mask: np.ndarray,
io_generator: io.OutputGenerator,
):
"""Create a figure to show the adaptive mask.

This figure shows the base mask, the adaptive mask thresholded for denoising (threshold >= 1),
and the adaptive mask thresholded for classification (threshold >= 3),
overlaid on the mean optimal combination image.

Parameters
----------
optcom : (S x T) :obj:`numpy.ndarray`
Optimal combination of components.
The mean image over time is used as the underlay for the figure.
base_mask : (S,) :obj:`numpy.ndarray`
Base mask used in tedana.
This is the original mask either provided by the user or generated with `compute_epi_mask`.
io_generator : :obj:`~tedana.io.OutputGenerator`
The output generator for this workflow.
"""
from matplotlib.lines import Line2D
from nilearn import image

adaptive_mask_img = io_generator.get_name("adaptive mask img")
mean_optcom_img = io.new_nii_like(io_generator.reference_img, np.mean(optcom, axis=1))

# Concatenate the three masks used in tedana to treat as a probabilistic atlas
base_mask = io.new_nii_like(io_generator.reference_img, base_mask)
mask_denoise = image.math_img("(img >= 1).astype(np.uint8)", img=adaptive_mask_img)
mask_clf = image.math_img("(img >= 3).astype(np.uint8)", img=adaptive_mask_img)
all_masks = image.concat_imgs((base_mask, mask_denoise, mask_clf))
# Set values to 0.5 for probabilistic atlas plotting
all_masks = image.math_img("img * 0.5", img=all_masks)

cmap = plt.cm.gist_rainbow
discrete_cmap = cmap.resampled(3) # colors matching the mask lines in the image
color_dict = {
"Base": discrete_cmap(0),
"Optimal combination": discrete_cmap(0.4),
"Classification": discrete_cmap(0.9),
}

ob = plotting.plot_prob_atlas(
maps_img=all_masks,
bg_img=mean_optcom_img,
view_type="contours",
threshold=0.2,
annotate=False,
draw_cross=False,
cmap=cmap,
display_mode="mosaic",
cut_coords=4,
)

legend_elements = []
for k, v in color_dict.items():
line = Line2D([0], [0], color=v, label=k, markersize=10)
legend_elements.append(line)

fig = ob.frame_axes.get_figure()
width = fig.get_size_inches()[0]

ob.frame_axes.set_zorder(100)
ob.frame_axes.legend(
handles=legend_elements,
facecolor="white",
ncols=3,
loc="lower center",
fancybox=True,
shadow=True,
fontsize=width,
)
adaptive_mask_plot = f"{io_generator.prefix}adaptive_mask.svg"
fig.savefig(os.path.join(io_generator.out_dir, "figures", adaptive_mask_plot))
1 change: 1 addition & 0 deletions tedana/tests/data/cornell_three_echo_outputs.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ desc-adaptiveGoodSignal_mask.nii.gz
desc-denoised_bold.nii.gz
desc-optcom_bold.nii.gz
figures
figures/adaptive_mask.svg
figures/carpet_optcom.svg
figures/carpet_denoised.svg
figures/carpet_accepted.svg
Expand Down
1 change: 1 addition & 0 deletions tedana/tests/data/fiu_four_echo_outputs.txt
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ sub-01_echo-4_desc-Rejected_bold.nii.gz
sub-01_report.txt
sub-01_tedana_report.html
figures
figures/sub-01_adaptive_mask.svg
figures/sub-01_carpet_optcom.svg
figures/sub-01_carpet_denoised.svg
figures/sub-01_carpet_accepted.svg
Expand Down
1 change: 1 addition & 0 deletions tedana/tests/data/nih_five_echo_outputs_verbose.txt
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ sub-01_references.bib
sub-01_report.txt
sub-01_tedana_report.html
figures
figures/sub-01_adaptive_mask.svg
figures/sub-01_carpet_optcom.svg
figures/sub-01_carpet_denoised.svg
figures/sub-01_carpet_accepted.svg
Expand Down
10 changes: 8 additions & 2 deletions tedana/workflows/tedana.py
Original file line number Diff line number Diff line change
Expand Up @@ -595,12 +595,13 @@
t2s_limited_sec = utils.reshape_niimg(t2smap)
t2s_limited = utils.sec2millisec(t2s_limited_sec)
t2s_full = t2s_limited.copy()
mask = utils.reshape_niimg(mask)
mask = utils.reshape_niimg(mask).astype(int)

Check warning on line 598 in tedana/workflows/tedana.py

View check run for this annotation

Codecov / codecov/patch

tedana/workflows/tedana.py#L598

Added line #L598 was not covered by tests
mask[t2s_limited == 0] = 0 # reduce mask based on T2* map
else:
LGR.info("Computing EPI mask from first echo")
first_echo_img = io.new_nii_like(io_generator.reference_img, catd[:, 0, :])
mask = compute_epi_mask(first_echo_img)
mask = compute_epi_mask(first_echo_img).get_fdata()
mask = utils.reshape_niimg(mask).astype(int)
RepLGR.info(
"An initial mask was generated from the first echo using "
"nilearn's compute_epi_mask function."
Expand Down Expand Up @@ -899,6 +900,11 @@

dn_ts, hikts, lowkts = io.denoise_ts(data_oc, mmix, mask_denoise, comptable)

reporting.static_figures.plot_adaptive_mask(
optcom=data_oc,
base_mask=mask,
io_generator=io_generator,
)
reporting.static_figures.carpet_plot(
optcom_ts=data_oc,
denoised_ts=dn_ts,
Expand Down