diff --git a/docs/_static/adaptive_mask.png b/docs/_static/adaptive_mask.png
new file mode 100644
index 000000000..32a09df7a
Binary files /dev/null and b/docs/_static/adaptive_mask.png differ
diff --git a/docs/outputs.rst b/docs/outputs.rst
index 55369191b..e1f1a82ff 100644
--- a/docs/outputs.rst
+++ b/docs/outputs.rst
@@ -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.
diff --git a/tedana/reporting/data/html/report_body_template.html b/tedana/reporting/data/html/report_body_template.html
index 008713715..9328c815a 100644
--- a/tedana/reporting/data/html/report_body_template.html
+++ b/tedana/reporting/data/html/report_body_template.html
@@ -180,21 +180,25 @@
Carpet plots
+
+
Adaptive mask
+
+
T2* and S0
T2*
-
+
-
+
S0
-
+
-
+
diff --git a/tedana/reporting/html_report.py b/tedana/reporting/html_report.py
index 519aefe50..8f01263f5 100644
--- a/tedana/reporting/html_report.py
+++ b/tedana/reporting/html_report.py
@@ -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"
@@ -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,
diff --git a/tedana/reporting/static_figures.py b/tedana/reporting/static_figures.py
index d9b9abdaa..8d155e155 100644
--- a/tedana/reporting/static_figures.py
+++ b/tedana/reporting/static_figures.py
@@ -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))
diff --git a/tedana/tests/data/cornell_three_echo_outputs.txt b/tedana/tests/data/cornell_three_echo_outputs.txt
index 44a6e660f..5c1d538dd 100644
--- a/tedana/tests/data/cornell_three_echo_outputs.txt
+++ b/tedana/tests/data/cornell_three_echo_outputs.txt
@@ -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
diff --git a/tedana/tests/data/fiu_four_echo_outputs.txt b/tedana/tests/data/fiu_four_echo_outputs.txt
index 221bc66e7..0da67ba15 100644
--- a/tedana/tests/data/fiu_four_echo_outputs.txt
+++ b/tedana/tests/data/fiu_four_echo_outputs.txt
@@ -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
diff --git a/tedana/tests/data/nih_five_echo_outputs_verbose.txt b/tedana/tests/data/nih_five_echo_outputs_verbose.txt
index 9c86dee80..613690c77 100644
--- a/tedana/tests/data/nih_five_echo_outputs_verbose.txt
+++ b/tedana/tests/data/nih_five_echo_outputs_verbose.txt
@@ -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
diff --git a/tedana/workflows/tedana.py b/tedana/workflows/tedana.py
index 79f61ff13..3a315c97f 100644
--- a/tedana/workflows/tedana.py
+++ b/tedana/workflows/tedana.py
@@ -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."
@@ -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,