Skip to content

Commit

Permalink
Add adaptive mask plot to report (#1073)
Browse files Browse the repository at this point in the history
  • Loading branch information
tsalo authored Apr 16, 2024
1 parent 4e091fd commit ee714f3
Show file tree
Hide file tree
Showing 9 changed files with 122 additions and 7 deletions.
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 @@ def tedana_workflow(
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)
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 @@ def tedana_workflow(

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

0 comments on commit ee714f3

Please sign in to comment.