"""Functions to do automatic visualization of Niimg-like objects.

See http://nilearn.github.io/stable/manipulating_images/input_output.html

Only matplotlib is required.
"""

import collections.abc
import functools
import inspect
import warnings

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from matplotlib import __version__ as mpl_version
from matplotlib import get_backend
from matplotlib.colors import LinearSegmentedColormap, Normalize
from matplotlib.gridspec import GridSpecFromSubplotSpec
from matplotlib.ticker import MaxNLocator

from nilearn import DEFAULT_DIVERGING_CMAP
from nilearn._utils import logger
from nilearn._utils.docs import fill_doc
from nilearn._utils.extmath import fast_abs_percentile
from nilearn._utils.logger import find_stack_level
from nilearn._utils.niimg import safe_get_data
from nilearn._utils.numpy_conversions import as_ndarray
from nilearn._utils.param_validation import (
    check_parameter_in_allowed,
    check_params,
    check_threshold,
)
from nilearn._utils.versions import compare_version
from nilearn.image import (
    check_niimg_3d,
    check_niimg_4d,
    get_data,
    iter_img,
    math_img,
    new_img_like,
    resample_to_img,
)
from nilearn.maskers import NiftiMasker
from nilearn.masking import apply_mask, compute_epi_mask
from nilearn.plotting import cm
from nilearn.plotting._engine_utils import create_colormap_from_lut
from nilearn.plotting._utils import (
    DEFAULT_TICK_FORMAT,
    check_threshold_not_negative,
    get_colorbar_and_data_ranges,
)
from nilearn.plotting.displays import get_projector, get_slicer
from nilearn.plotting.displays._slicers import save_figure_if_needed
from nilearn.plotting.image.utils import MNI152TEMPLATE, load_anat
from nilearn.signal import clean


def show() -> None:
    """Show all the figures generated by nilearn and/or matplotlib.

    This function is equivalent to :func:`matplotlib.pyplot.show`,
    but is skipped on the 'Agg' backend where it has no effect other
    than to emit a warning.

    """
    if get_backend().lower() != "agg":  # avoid warnings
        plt.show()
    else:
        warnings.warn(
            (
                f"You are using the '{get_backend().lower()}' "
                "matplotlib backend that is non-interactive."
                "\nNo figure will be plotted when calling "
                "matplotlib.pyplot.show() or nilearn.plotting.show()."
                "\nYou can fix this by installing a different backend: "
                "for example via\n\tpip install PyQt6"
            ),
            stacklevel=find_stack_level(),
        )


###############################################################################
# Core, usage-agnostic functions


@fill_doc
def _plot_img_with_bg(
    img,
    bg_img=None,
    cut_coords=None,
    output_file=None,
    display_mode="ortho",
    colorbar=False,
    figure=None,
    axes=None,
    title=None,
    threshold=None,
    annotate=True,
    draw_cross=True,
    black_bg=False,
    vmin=None,
    vmax=None,
    bg_vmin=None,
    bg_vmax=None,
    interpolation="nearest",
    display_factory=get_slicer,
    cbar_vmin=None,
    cbar_vmax=None,
    cbar_tick_format=DEFAULT_TICK_FORMAT,
    brain_color=(0.5, 0.5, 0.5),
    decimals=False,
    radiological=False,
    transparency=None,
    transparency_range=None,
    **kwargs,
):
    """Refer to the docstring of plot_img for parameters not listed below.

    Parameters
    ----------
    %(img)s
        Image to plot.

    %(bg_img)s
        If nothing is specified, no background image is plotted.
        Default=None.

    %(cut_coords)s

    %(output_file)s

    %(display_mode)s

    %(colorbar)s
        Default=False.

    %(figure)s

    %(axes)s

    %(title)s

    %(threshold)s

    %(annotate)s

    %(draw_cross)s

    %(black_bg)s
        Default=False.

    %(vmin)s

    %(vmax)s

    bg_vmin : :obj:`float` or None, default=None
        vmin for `bg_img`.

    bg_vmax : :obj:`float` or None, default=None
        vmax for `bg_img`.

    interpolation : :obj:`str`, default='nearest'
        Passed to the add_overlay calls.

    display_factory : function, default=get_slicer
        Takes a display_mode argument and return a display class.

    cbar_tick_format : :obj:`str`, default="%%.2g" (scientific notation)
        Controls how to format the tick labels of the colorbar.
        Ex: use "%%i" to display as integers.

    decimals : :obj:`int` or :obj:`bool`, default=False
        Number of decimal places on slice position annotation.
        If False,
        the slice position is integer without decimal point.

    %(radiological)s

    %(transparency)s

    %(transparency_range)s

    kwargs :  extra keyword arguments, optional
        Extra keyword arguments passed
        to the display.add_overlay method (see below).
        Ultimately passed to `matplotlib.pyplot.imshow` via
        :meth:`~nilearn.plotting.displays.BaseSlicer.add_overlay`.

    Returns
    -------
    display : :class:`~nilearn.plotting.displays.OrthoSlicer` or \
        :class:`~nilearn.plotting.displays.OrthoProjector` or None
        An instance of the OrthoSlicer or OrthoProjector class depending on the
        function defined in ``display_factory``. If ``output_file`` is defined,
        None is returned.

    Raises
    ------
    ValueError
        if the specified threshold is a negative number
    """
    check_params(locals())
    check_threshold_not_negative(threshold)

    show_nan_msg = False
    if vmax is not None and np.isnan(vmax):
        vmax = None
        show_nan_msg = True
    if vmin is not None and np.isnan(vmin):
        vmin = None
        show_nan_msg = True
    if show_nan_msg:
        nan_msg = (
            "NaN is not permitted for the vmax and vmin arguments.\n"
            "Tip: Use np.nanmax() instead of np.max()."
        )
        warnings.warn(nan_msg, stacklevel=find_stack_level())

    if img is not False and img is not None:
        img = check_niimg_3d(img, dtype="auto")
        data = safe_get_data(img, ensure_finite=True)
        affine = img.affine

        if np.isnan(np.sum(data)):
            data = np.nan_to_num(data)

        # Deal with automatic settings of plot parameters
        if threshold == "auto":
            # Threshold epsilon below a percentile value, to be sure that some
            # voxels pass the threshold
            threshold = float(fast_abs_percentile(data)) - 1e-5

        img = new_img_like(img, as_ndarray(data), affine)

    display = display_factory(display_mode)(
        img,
        threshold=threshold,
        cut_coords=cut_coords,
        figure=figure,
        axes=axes,
        black_bg=black_bg,
        colorbar=colorbar,
        brain_color=brain_color,
        radiological=radiological,
    )
    if bg_img is not None:
        bg_img = check_niimg_3d(bg_img)
        display.add_overlay(
            bg_img,
            vmin=bg_vmin,
            vmax=bg_vmax,
            cmap="gray",
            interpolation=interpolation,
        )

    if img is not None and img is not False:
        display.add_overlay(
            new_img_like(img, data, affine),
            threshold=threshold,
            interpolation=interpolation,
            colorbar=colorbar,
            vmin=vmin,
            vmax=vmax,
            cbar_vmin=cbar_vmin,
            cbar_vmax=cbar_vmax,
            cbar_tick_format=cbar_tick_format,
            transparency=transparency,
            transparency_range=transparency_range,
            **kwargs,
        )
    if radiological:
        for display_axis in display.axes.values():
            display_axis.ax.invert_xaxis()
    if annotate:
        display.annotate(decimals=decimals)
    if draw_cross:
        display.draw_cross()
    if title is not None and title != "":
        display.title(title)

    return save_figure_if_needed(display, output_file)


@fill_doc
def plot_img(
    img,
    cut_coords=None,
    output_file=None,
    display_mode="ortho",
    figure=None,
    axes=None,
    title=None,
    threshold=None,
    annotate=True,
    draw_cross=True,
    black_bg=False,
    colorbar=True,
    cbar_tick_format=DEFAULT_TICK_FORMAT,
    resampling_interpolation="continuous",
    bg_img=None,
    vmin=None,
    vmax=None,
    radiological=False,
    decimals=False,
    cmap="gray",
    transparency=None,
    transparency_range=None,
    **kwargs,
):
    """Plot cuts of a given image.

    By default Frontal, Axial, and Lateral.

    Parameters
    ----------
    %(img)s

    %(cut_coords)s

    %(output_file)s

    %(display_mode)s

    %(figure)s

    %(axes)s

    %(title)s

    %(threshold)s

    %(annotate)s

    %(draw_cross)s

    %(black_bg)s
        Default=False.

    %(colorbar)s
        Default=True.

    cbar_tick_format : :obj:`str`, default="%%.2g" (scientific notation)
        Controls how to format the tick labels of the colorbar.
        Ex: use "%%i" to display as integers.

    %(resampling_interpolation)s
        Default='continuous'.

    %(bg_img)s
        If nothing is specified, no background image is plotted.
        Default=None.

    %(vmin)s

    %(vmax)s

    %(radiological)s

    decimals : :obj:`int` or :obj:`bool`, default=False
        Number of decimal places on slice position annotation.
        If False,
        the slice position is integer without decimal point.

    %(cmap)s
        default="gray"

    %(transparency)s

    %(transparency_range)s

    kwargs : extra keyword arguments, optional
        Extra keyword arguments
        ultimately passed to `matplotlib.pyplot.imshow` via
        :meth:`~nilearn.plotting.displays.BaseSlicer.add_overlay`.

    Returns
    -------
    display : :class:`~nilearn.plotting.displays.OrthoSlicer` or None
        An instance of the OrthoSlicer class. If ``output_file`` is defined,
        None is returned.

    Raises
    ------
    ValueError
        if the specified threshold is a negative number

    .. note::

        This is a low-level function. For most use cases, other plotting
        functions might be more appropriate and easier to use.

    .. seealso::

        :func:`~nilearn.plotting.plot_anat`
            To simply plot anatomical images
        :func:`~nilearn.plotting.plot_epi`
            To simply plot raw EPI images
        :func:`~nilearn.plotting.plot_roi`
            To simply plot max-prob atlases (3D images)
        :func:`~nilearn.plotting.plot_prob_atlas`
            To simply plot probabilistic atlases (4D images)
        :mod:`nilearn.plotting`
            See API reference for other options
    """
    check_params(locals())
    check_threshold_not_negative(threshold)
    display = _plot_img_with_bg(
        img,
        cut_coords=cut_coords,
        output_file=output_file,
        display_mode=display_mode,
        figure=figure,
        axes=axes,
        title=title,
        threshold=threshold,
        annotate=annotate,
        draw_cross=draw_cross,
        resampling_interpolation=resampling_interpolation,
        black_bg=black_bg,
        colorbar=colorbar,
        cbar_tick_format=cbar_tick_format,
        bg_img=bg_img,
        vmin=vmin,
        vmax=vmax,
        radiological=radiological,
        decimals=decimals,
        cmap=cmap,
        transparency=transparency,
        transparency_range=transparency_range,
        **kwargs,
    )

    return display


###############################################################################
# Usage-specific functions


@fill_doc
def plot_anat(
    anat_img=MNI152TEMPLATE,
    cut_coords=None,
    output_file=None,
    display_mode="ortho",
    figure=None,
    axes=None,
    title=None,
    annotate=True,
    threshold=None,
    draw_cross=True,
    black_bg="auto",
    dim="auto",
    cmap="gray",
    colorbar=True,
    cbar_tick_format=DEFAULT_TICK_FORMAT,
    radiological=False,
    vmin=None,
    vmax=None,
    **kwargs,
):
    """Plot cuts of an anatomical image.

    By default 3 cuts: Frontal, Axial, and Lateral.

    Parameters
    ----------
    anat_img : Niimg-like object, default=MNI152TEMPLATE
        See :ref:`extracting_data`.
        The anatomical image to be used as a background. If None is
        given, nilearn tries to find a T1 template.

    %(cut_coords)s

    %(output_file)s

    %(display_mode)s

    %(figure)s

    %(axes)s

    %(title)s

    %(annotate)s

    %(threshold)s

    %(draw_cross)s

    %(black_bg)s
        Default='auto'.

    %(dim)s
        Default='auto'.

    %(cmap)s
        Default=`gray`.

    %(colorbar)s
        Default=True

    cbar_tick_format : :obj:`str`, default="%%.2g" (scientific notation)
        Controls how to format the tick labels of the colorbar.
        Ex: use "%%i" to display as integers.

    %(radiological)s

    %(vmin)s

    %(vmax)s

    kwargs : extra keyword arguments, optional
        Extra keyword arguments
        ultimately passed to `matplotlib.pyplot.imshow` via
        :meth:`~nilearn.plotting.displays.BaseSlicer.add_overlay`.

    Returns
    -------
    display : :class:`~nilearn.plotting.displays.OrthoSlicer` or None
        An instance of the OrthoSlicer class. If ``output_file`` is defined,
        None is returned.

    Raises
    ------
    ValueError
        if the specified threshold is a negative number

    Notes
    -----
    Arrays should be passed in numpy convention: (x, y, z) ordered.

    For visualization, non-finite values found in passed 'anat_img'
    are set to zero.

    """
    check_params(locals())
    check_threshold_not_negative(threshold)

    anat_img, black_bg, anat_vmin, anat_vmax = load_anat(
        anat_img, dim=dim, black_bg=black_bg
    )

    if vmin is None:
        vmin = anat_vmin
    if vmax is None:
        vmax = anat_vmax

    display = plot_img(
        anat_img,
        cut_coords=cut_coords,
        output_file=output_file,
        display_mode=display_mode,
        figure=figure,
        axes=axes,
        title=title,
        threshold=threshold,
        annotate=annotate,
        draw_cross=draw_cross,
        black_bg=black_bg,
        colorbar=colorbar,
        cbar_tick_format=cbar_tick_format,
        vmin=vmin,
        vmax=vmax,
        cmap=cmap,
        radiological=radiological,
        **kwargs,
    )
    return display


@fill_doc
def plot_epi(
    epi_img=None,
    cut_coords=None,
    output_file=None,
    display_mode="ortho",
    figure=None,
    axes=None,
    title=None,
    annotate=True,
    draw_cross=True,
    black_bg=True,
    colorbar=True,
    cbar_tick_format=DEFAULT_TICK_FORMAT,
    cmap="gray",
    vmin=None,
    vmax=None,
    radiological=False,
    **kwargs,
):
    """Plot cuts of an :term:`EPI` image.

    By default 3 cuts: Frontal, Axial, and Lateral.

    Parameters
    ----------
    epi_img : a Niimg-like object or None, default=None
        The :term:`EPI` (T2*) image.

    %(cut_coords)s

    %(output_file)s

    %(display_mode)s

    %(figure)s

    %(axes)s

    %(title)s

    %(annotate)s

    %(draw_cross)s

    %(black_bg)s
        Default=True.

    %(colorbar)s
        Default=True

    cbar_tick_format : :obj:`str`, default="%%.2g" (scientific notation)
        Controls how to format the tick labels of the colorbar.
        Ex: use "%%i" to display as integers.

    %(cmap)s
        Default=`gray`.

    %(vmin)s

    %(vmax)s

    %(radiological)s

    kwargs : extra keyword arguments, optional
        Extra keyword arguments
        ultimately passed to `matplotlib.pyplot.imshow` via
        :meth:`~nilearn.plotting.displays.BaseSlicer.add_overlay`.

    Returns
    -------
    display : :class:`~nilearn.plotting.displays.OrthoSlicer` or None
        An instance of the OrthoSlicer class. If ``output_file`` is defined,
        None is returned.

    Notes
    -----
    Arrays should be passed in numpy convention: (x, y, z) ordered.
    """
    check_params(locals())
    display = plot_img(
        epi_img,
        cut_coords=cut_coords,
        output_file=output_file,
        display_mode=display_mode,
        figure=figure,
        axes=axes,
        title=title,
        threshold=None,
        annotate=annotate,
        draw_cross=draw_cross,
        black_bg=black_bg,
        colorbar=colorbar,
        cbar_tick_format=cbar_tick_format,
        cmap=cmap,
        vmin=vmin,
        vmax=vmax,
        radiological=radiological,
        **kwargs,
    )
    return display


def _plot_roi_contours(display, roi_img, cmap, alpha, linewidths):
    """Help for plotting regions of interest ROIs in contours.

    Parameters
    ----------
    display : :class:`~nilearn.plotting.displays.OrthoSlicer`, object
        An object with background image on which contours are shown.

    roi_img : Niimg-like object
        See :ref:`extracting_data`.
        The ROI/mask image, it could be binary mask or an atlas or ROIs
        with integer values.

    %(cmap)s

    alpha : :obj:`float` between 0 and 1
        Alpha sets the transparency of the color inside the filled
        contours.

    linewidths : :obj:`float`
        This option can be used to set the boundary thickness of the
        contours.

    Returns
    -------
    display : :class:`~nilearn.plotting.displays.OrthoSlicer`, object
        Contours displayed on the background image.

    """
    roi_img = check_niimg_3d(roi_img)
    roi_data = get_data(roi_img)
    labels = np.unique(roi_data)
    cmap = plt.get_cmap(cmap)
    color_list = cmap(np.linspace(0, 1, len(labels)))
    for idx, label in enumerate(labels):
        if label == 0:
            continue
        data = roi_data == label
        data = data.astype(np.int32)
        img = new_img_like(roi_img, data, affine=roi_img.affine)
        display.add_contours(
            img,
            levels=[0.5],
            colors=[color_list[idx - 1]],
            transparency=alpha,
            linewidths=linewidths,
            linestyles="solid",
        )
    return display


@fill_doc
def plot_roi(
    roi_img,
    bg_img=MNI152TEMPLATE,
    cut_coords=None,
    output_file=None,
    display_mode="ortho",
    figure=None,
    axes=None,
    title=None,
    annotate=True,
    draw_cross=True,
    black_bg="auto",
    threshold=0.5,
    alpha=0.7,
    cmap="gist_ncar",
    dim="auto",
    colorbar=True,
    cbar_tick_format=DEFAULT_TICK_FORMAT,
    vmin=None,
    vmax=None,
    resampling_interpolation="nearest",
    view_type="continuous",
    linewidths=2.5,
    radiological=False,
    **kwargs,
):
    """Plot cuts of an ROI/mask image.

    By default 3 cuts: Frontal, Axial, and Lateral.

    Parameters
    ----------
    roi_img : Niimg-like object
        See :ref:`extracting_data`.
        The ROI/mask image, it could be binary mask or an atlas or ROIs
        with integer values.

    %(bg_img)s
        If nothing is specified, the MNI152 template will be used.
        To turn off background image, just pass "bg_img=None".
        Default=MNI152TEMPLATE.

    %(cut_coords)s

    %(output_file)s

    %(display_mode)s

    %(figure)s

    %(axes)s

    %(title)s

    %(annotate)s

    %(draw_cross)s

    %(black_bg)s
        Default='auto'.

    %(threshold)s
        Default=0.5.

    alpha : :obj:`float` between 0 and 1, default=0.7
        Alpha sets the transparency of the color inside the filled
        contours.

    %(cmap_lut)s
        Default=`gist_ncar`.

    %(dim)s
        Default='auto'.

    %(colorbar)s
        Default=True

    cbar_tick_format : :obj:`str`, default="%%i"
        Controls how to format the tick labels of the colorbar.
        Ex: use "%%.2g" to use scientific notation.

    %(vmin)s

    %(vmax)s

    %(resampling_interpolation)s
        Default='nearest'.

    view_type : {'continuous', 'contours'}, default='continuous'
        By default view_type == 'continuous',
        rois are shown as continuous colors.
        If view_type == 'contours', maps are shown as contours.
        For this type, label
        denoted as 0 is considered as background and not shown.

    %(linewidths)s
        Default=2.5.

    %(radiological)s

    kwargs : extra keyword arguments, optional
        Extra keyword arguments
        ultimately passed to `matplotlib.pyplot.imshow` via
        :meth:`~nilearn.plotting.displays.BaseSlicer.add_overlay`.

    Returns
    -------
    display : :class:`~nilearn.plotting.displays.OrthoSlicer` or None
        An instance of the OrthoSlicer class. If ``output_file`` is defined,
        None is returned.

    Raises
    ------
    ValueError
        if the specified threshold is a negative number

    Notes
    -----
    A small threshold is applied by default to eliminate numerical
    background noise.

    For visualization, non-finite values found in passed 'roi_img' or
    'bg_img' are set to zero.

    See Also
    --------
    nilearn.plotting.plot_prob_atlas : To simply plot probabilistic atlases
        (4D images)
    """
    check_params(locals())
    check_threshold_not_negative(threshold)

    valid_view_types = ["continuous", "contours"]
    check_parameter_in_allowed(view_type, valid_view_types, "view_type")
    if view_type == "contours":
        img = roi_img
        roi_img = None

    bg_img, black_bg, bg_vmin, bg_vmax = load_anat(
        bg_img, dim=dim, black_bg=black_bg
    )

    if isinstance(cmap, pd.DataFrame):
        cmap = create_colormap_from_lut(cmap)

    transparency = alpha

    display = _plot_img_with_bg(
        img=roi_img,
        bg_img=bg_img,
        cut_coords=cut_coords,
        output_file=output_file,
        display_mode=display_mode,
        figure=figure,
        axes=axes,
        title=title,
        annotate=annotate,
        draw_cross=draw_cross,
        black_bg=black_bg,
        threshold=threshold,
        bg_vmin=bg_vmin,
        bg_vmax=bg_vmax,
        resampling_interpolation=resampling_interpolation,
        colorbar=colorbar,
        cbar_tick_format=cbar_tick_format,
        transparency=transparency,
        cmap=cmap,
        vmin=vmin,
        vmax=vmax,
        radiological=radiological,
        **kwargs,
    )

    if view_type == "contours":
        display = _plot_roi_contours(
            display, img, cmap=cmap, alpha=alpha, linewidths=linewidths
        )

    return display


@fill_doc
def plot_prob_atlas(
    maps_img,
    bg_img=MNI152TEMPLATE,
    view_type="auto",
    threshold="auto",
    linewidths=2.5,
    cut_coords=None,
    output_file=None,
    display_mode="ortho",
    figure=None,
    axes=None,
    title=None,
    annotate=True,
    draw_cross=True,
    black_bg="auto",
    dim="auto",
    colorbar=True,
    cmap="gist_rainbow",
    vmin=None,
    vmax=None,
    alpha=0.7,
    radiological=False,
    **kwargs,
):
    """Plot a :term:`Probabilistic atlas` onto the anatomical image \
    by default :term:`MNI` template.

    Parameters
    ----------
    maps_img : Niimg-like object or the filename
        4D image of the :term:`Probabilistic atlas` maps.

    %(bg_img)s
        If nothing is specified, the MNI152 template will be used.
        To turn off background image, just pass "bg_img=False".
        Default=MNI152TEMPLATE.

        .. nilearn_versionadded:: 0.4.0

    view_type : {'auto', 'contours', 'filled_contours', 'continuous'}, \
                default='auto'
        If view_type == 'auto', it means maps will be displayed
        automatically using any one of the three view types. The automatic
        selection of view type depends on the total number of maps.
        If view_type == 'contours', maps are overlaid as contours
        If view_type == 'filled_contours', maps are overlaid as contours
        along with color fillings inside the contours.
        If view_type == 'continuous', maps are overlaid as continuous
        colors irrespective of the number maps.

    threshold : a :obj:`int` or :obj:`float` or :obj:`str` or \
              :obj:`list` of :obj:`int` or :obj:`float` or \
              :obj:`str`, default='auto'
        This parameter is optional and is used to threshold the maps image
        using the given value or automatically selected value. The values
        in the image (in absolute value) above the threshold level will be
        visualized.
        The default strategy, computes a threshold level that seeks to
        minimize (yet not eliminate completely) the overlap between several
        maps for a better visualization.
        The threshold can also be expressed as a percentile over the values
        of the whole atlas. In that case, the value must be specified as
        string finishing with a percent sign, e.g., "25.3%%".
        If a single string is provided, the same percentile will be applied
        over the whole atlas. Otherwise, if a list of percentiles is
        provided, each 3D map is thresholded with certain percentile
        sequentially. Length of percentiles given should match the number
        of 3D map in time (4th) dimension.
        If a number or a list of numbers, the numbers should be
        non-negative. The given value will be used directly to threshold the
        maps without any percentile calculation.
        If None, a very small threshold is applied to remove numerical
        noise from the maps background.

    %(linewidths)s
        Default=2.5.

    %(cut_coords)s

    %(output_file)s

    %(display_mode)s

    %(figure)s

    %(axes)s

    %(title)s

    %(annotate)s

    %(draw_cross)s

    %(black_bg)s
        Default='auto'.

    %(dim)s
        Default='auto'.

    %(cmap)s
        Default=`gist_rainbow`.

    %(colorbar)s
        Default=True.

    %(vmin)s

    %(vmax)s

    alpha : :obj:`float` between 0 and 1, default=0.7
        Alpha sets the transparency of the color inside the filled contours.
    %(radiological)s

    kwargs : extra keyword arguments, optional
        Extra keyword arguments
        ultimately passed to `matplotlib.pyplot.imshow` via
        :meth:`~nilearn.plotting.displays.BaseSlicer.add_overlay`.

    Returns
    -------
    display : :class:`~nilearn.plotting.displays.OrthoSlicer` or None
        An instance of the OrthoSlicer class. If ``output_file`` is defined,
        None is returned.

    Raises
    ------
    ValueError
        if the specified threshold is a negative number

    See Also
    --------
    nilearn.plotting.plot_roi : To simply plot max-prob atlases (3D images)
    """
    check_params(locals())
    check_threshold_not_negative(threshold)

    display = plot_anat(
        bg_img,
        cut_coords=cut_coords,
        display_mode=display_mode,
        figure=figure,
        axes=axes,
        title=title,
        annotate=annotate,
        draw_cross=draw_cross,
        black_bg=black_bg,
        dim=dim,
        radiological=radiological,
        vmin=vmin,
        vmax=vmax,
        **kwargs,
    )

    maps_img = check_niimg_4d(maps_img)
    n_maps = maps_img.shape[3]

    valid_view_types = ["auto", "contours", "filled_contours", "continuous"]
    check_parameter_in_allowed(view_type, valid_view_types, "view_type")

    cmap = plt.get_cmap(cmap)
    color_list = cmap(np.linspace(0, 1, n_maps))

    if view_type == "auto":
        if n_maps > 20:
            view_type = "contours"
        elif n_maps > 10:
            view_type = "filled_contours"
        else:
            view_type = "continuous"

    if threshold is None:
        threshold = 1e-6
    elif threshold == "auto":
        # it will use default percentage,
        # strategy is to avoid maximum overlaps as possible
        if view_type == "contours":
            correction_factor = 1
        elif view_type == "filled_contours":
            correction_factor = 0.8
        else:
            correction_factor = 0.5
        threshold = f"{100 * (1 - 0.2 * correction_factor / n_maps):f}%"

    if isinstance(threshold, collections.abc.Iterable) and not isinstance(
        threshold, str
    ):
        threshold = list(threshold)
        if len(threshold) != n_maps:
            raise TypeError(
                "The list of values to threshold "
                "should be equal to number of maps"
            )
    else:
        threshold = [threshold] * n_maps

    filled = view_type.startswith("filled")
    tmp = dict(**inspect.signature(plot_prob_atlas).parameters)
    kwargs_contour = {}
    if not filled:
        kwargs_contour = {"linewidths": linewidths}
    elif linewidths != tmp["linewidths"].default:
        # only throw warning if the user has changed
        # from the default linewidths
        # otherwise this function will always
        # throw a warning any time the user tries to plot filled contours
        warnings.warn(
            f"'linewidths' is not supported by {view_type}=",
            UserWarning,
            stacklevel=find_stack_level(),
        )

    transparency = alpha
    for map_img, color, thr in zip(
        iter_img(maps_img), color_list, threshold, strict=False
    ):
        data = get_data(map_img)
        # To threshold or choose the level of the contours
        thr = check_threshold(
            thr, data, percentile_func=fast_abs_percentile, name="threshold"
        )
        # Get rid of background values in all cases
        thr = max(thr, 1e-6)

        if view_type == "continuous":
            display.add_overlay(
                map_img,
                threshold=thr,
                cmap=cm.alpha_cmap(color),
                transparency=transparency,
            )
        else:
            display.add_contours(
                map_img,
                levels=[thr],
                colors=[color],
                filled=filled,
                transparency=transparency,
                linestyles="solid",
                **kwargs_contour,
            )
    if colorbar:
        display._colorbar = True
        # Create a colormap from color list to feed display
        cmap = LinearSegmentedColormap.from_list(
            "segmented colors", color_list, n_maps + 1
        )
        display._show_colorbar(cmap, Normalize(1, n_maps + 1))
        tick_locator = MaxNLocator(nbins=10)
        display.locator = tick_locator
        display._cbar.update_ticks()
        tick_location = np.round(
            np.linspace(1, n_maps, min(n_maps, 10))
        ).astype("int")
        display._cbar.set_ticks(tick_location + 0.5)
        display._cbar.set_ticklabels(tick_location)
        (
            left,
            bottom,
            width,
            height,
        ) = display._colorbar_ax.get_position().bounds
        display._colorbar_ax.set_position([left, bottom, width, height * 0.95])
        display._colorbar_ax.annotate(
            "Map #",
            xy=(1, 1.03),
            ha="right",
            va="bottom",
            xycoords="axes fraction",
        )

    return save_figure_if_needed(display, output_file)


@fill_doc
def plot_stat_map(
    stat_map_img,
    bg_img=MNI152TEMPLATE,
    cut_coords=None,
    output_file=None,
    display_mode="ortho",
    colorbar=True,
    cbar_tick_format=DEFAULT_TICK_FORMAT,
    figure=None,
    axes=None,
    title=None,
    threshold=1e-6,
    annotate=True,
    draw_cross=True,
    black_bg="auto",
    cmap=DEFAULT_DIVERGING_CMAP,
    symmetric_cbar="auto",
    dim="auto",
    vmin=None,
    vmax=None,
    radiological=False,
    resampling_interpolation="continuous",
    transparency=None,
    transparency_range=None,
    **kwargs,
):
    """Plot cuts of an ROI/mask image.

    By default 3 cuts: Frontal, Axial, and Lateral.

    Parameters
    ----------
    stat_map_img : Niimg-like object
        See :ref:`extracting_data`.
        The statistical map image

    %(bg_img)s
        If nothing is specified, the MNI152 template will be used.
        To turn off background image, just pass "bg_img=None".
        Default=MNI152TEMPLATE.

    %(cut_coords)s

    %(output_file)s

    %(display_mode)s

    %(colorbar)s
        Default=True.

    cbar_tick_format : :obj:`str`, default="%%.2g" (scientific notation)
        Controls how to format the tick labels of the colorbar.
        Ex: use "%%i" to display as integers.

    %(figure)s

    %(axes)s

    %(title)s

    %(threshold)s
        Default=1e-6.

    %(annotate)s

    %(draw_cross)s

    %(black_bg)s
        Default='auto'.

    %(cmap)s

        Default=default="RdBu_r".

    %(symmetric_cbar)s

    %(dim)s
        Default='auto'.

    %(vmin)s

    %(vmax)s

    %(resampling_interpolation)s
        Default='continuous'.

    %(radiological)s

    %(transparency)s

    %(transparency_range)s

    kwargs : extra keyword arguments, optional
        Extra keyword arguments
        ultimately passed to `matplotlib.pyplot.imshow` via
        :meth:`~nilearn.plotting.displays.BaseSlicer.add_overlay`.

    Returns
    -------
    display : :class:`~nilearn.plotting.displays.OrthoSlicer` or None
        An instance of the OrthoSlicer class. If ``output_file`` is defined,
        None is returned.

    Raises
    ------
    ValueError
        if the specified threshold is a negative number

    Notes
    -----
    Arrays should be passed in numpy convention: (x, y, z) ordered.

    For visualization, non-finite values found in passed 'stat_map_img' or
    'bg_img' are set to zero.

    See Also
    --------
    nilearn.plotting.plot_anat : To simply plot anatomical images
    nilearn.plotting.plot_epi : To simply plot raw EPI images
    nilearn.plotting.plot_glass_brain : To plot maps in a glass brain
    """
    check_params(locals())
    check_threshold_not_negative(threshold)

    # dim the background
    bg_img, black_bg, bg_vmin, bg_vmax = load_anat(
        bg_img, dim=dim, black_bg=black_bg
    )

    stat_map_img = check_niimg_3d(stat_map_img, dtype="auto")

    cbar_vmin, cbar_vmax, vmin, vmax = get_colorbar_and_data_ranges(
        safe_get_data(stat_map_img, ensure_finite=True),
        vmin=vmin,
        vmax=vmax,
        symmetric_cbar=symmetric_cbar,
    )

    display = _plot_img_with_bg(
        img=stat_map_img,
        bg_img=bg_img,
        cut_coords=cut_coords,
        output_file=output_file,
        display_mode=display_mode,
        figure=figure,
        axes=axes,
        title=title,
        annotate=annotate,
        draw_cross=draw_cross,
        black_bg=black_bg,
        threshold=threshold,
        bg_vmin=bg_vmin,
        bg_vmax=bg_vmax,
        cmap=cmap,
        vmin=vmin,
        vmax=vmax,
        colorbar=colorbar,
        cbar_tick_format=cbar_tick_format,
        cbar_vmin=cbar_vmin,
        cbar_vmax=cbar_vmax,
        resampling_interpolation=resampling_interpolation,
        radiological=radiological,
        transparency=transparency,
        transparency_range=transparency_range,
        **kwargs,
    )

    return display


@fill_doc
def plot_glass_brain(
    stat_map_img,
    output_file=None,
    display_mode="ortho",
    colorbar=True,
    cbar_tick_format=DEFAULT_TICK_FORMAT,
    figure=None,
    axes=None,
    title=None,
    threshold="auto",
    annotate=True,
    black_bg=False,
    cmap=None,
    alpha=0.7,
    vmin=None,
    vmax=None,
    plot_abs=True,
    symmetric_cbar="auto",
    resampling_interpolation="continuous",
    radiological=False,
    transparency=None,
    **kwargs,
):
    """Plot 2d projections of an ROI/mask image (by default 3 projections:
    Frontal, Axial, and Lateral). The brain glass schematics
    are added on top of the image.

    The plotted image should be in :term:`MNI` space for this function to work
    properly.

    Only glass brain can be plotted by switching stat_map_img to None.

    Parameters
    ----------
    stat_map_img : Niimg-like object
        See :ref:`extracting_data`.
        The statistical map image.
        It needs to be in :term:`MNI` space
        in order to align with the brain schematics.

    %(output_file)s

    display_mode : :obj:`str`, default='ortho'
        Choose the direction of the cuts: 'x' - sagittal, 'y' - coronal,
        'z' - axial, 'l' - sagittal left hemisphere only,
        'r' - sagittal right hemisphere only, 'ortho' - three cuts are
        performed in orthogonal directions. Possible values are: 'ortho',
        'x', 'y', 'z', 'xz', 'yx', 'yz', 'l', 'r', 'lr', 'lzr', 'lyr',
        'lzry', 'lyrz'.

    %(colorbar)s
        Default=True.

    cbar_tick_format : :obj:`str`, default="%%.2g" (scientific notation)
        Controls how to format the tick labels of the colorbar.
        Ex: use "%%i" to display as integers.

    %(figure)s

    %(axes)s

    %(title)s

    %(threshold)s
        Default='auto'.

    %(annotate)s

    %(black_bg)s
        Default=False.

    %(cmap)s
        Default=None.

    alpha : :obj:`float` between 0 and 1, default=0.7
        Alpha transparency for the brain schematics.

    %(vmin)s

    %(vmax)s

    plot_abs : :obj:`bool`, default=True
        If set to True maximum intensity projection of the
        absolute value will be used (rendering positive and negative
        values in the same manner). If set to false the sign of the
        maximum intensity will be represented with different colors.
        See
        :ref:`sphx_glr_auto_examples_01_plotting_plot_demo_glass_brain_extensive.py`
        for examples.

    %(symmetric_cbar)s

    %(resampling_interpolation)s
        Default='continuous'.

    %(radiological)s

    %(transparency)s

    %(transparency_range)s

    kwargs : extra keyword arguments, optional
        Extra keyword arguments
        ultimately passed to `matplotlib.pyplot.imshow` via
        :meth:`~nilearn.plotting.displays.BaseSlicer.add_overlay`.

    Returns
    -------
    display : :class:`~nilearn.plotting.displays.OrthoProjector` or None
        An instance of the OrthoProjector class. If ``output_file`` is defined,
        None is returned.

    Raises
    ------
    ValueError
        if the specified threshold is a negative number

    Notes
    -----
    Arrays should be passed in numpy convention: (x, y, z) ordered.
    """
    check_params(locals())
    check_threshold_not_negative(threshold)

    if cmap is None:
        cmap = cm.cold_white_hot
        if black_bg:
            cmap = cm.cold_hot
        if not plot_abs:
            cmap = plt.cm.RdBu_r
        # use only positive half of colormap if plotting absolute values
        if plot_abs:
            cmap = LinearSegmentedColormap.from_list(
                "cmap_pos",
                cmap(np.linspace(0.5, 1, 256)),
            )

    if stat_map_img:
        stat_map_img = check_niimg_3d(stat_map_img, dtype="auto")
        if plot_abs:
            if vmin is not None and vmin < 0:
                warnings.warn(
                    "vmin is negative but plot_abs is True",
                    category=UserWarning,
                    stacklevel=find_stack_level(),
                )
            force_min_stat_map_value = 0
        else:
            force_min_stat_map_value = None

        cbar_vmin, cbar_vmax, vmin, vmax = get_colorbar_and_data_ranges(
            safe_get_data(stat_map_img, ensure_finite=True),
            vmin=vmin,
            vmax=vmax,
            symmetric_cbar=symmetric_cbar,
            force_min_stat_map_value=force_min_stat_map_value,
        )
    else:
        cbar_vmin, cbar_vmax = None, None

    def display_factory(display_mode):
        return functools.partial(
            get_projector(display_mode), alpha=alpha, plot_abs=plot_abs
        )

    display = _plot_img_with_bg(
        img=stat_map_img,
        output_file=output_file,
        display_mode=display_mode,
        figure=figure,
        axes=axes,
        title=title,
        annotate=annotate,
        black_bg=black_bg,
        threshold=threshold,
        cmap=cmap,
        colorbar=colorbar,
        cbar_tick_format=cbar_tick_format,
        display_factory=display_factory,
        vmin=vmin,
        vmax=vmax,
        cbar_vmin=cbar_vmin,
        cbar_vmax=cbar_vmax,
        resampling_interpolation=resampling_interpolation,
        radiological=radiological,
        transparency=transparency,
        **kwargs,
    )

    if stat_map_img is None and "l" in display.axes:
        display.axes["l"].ax.invert_xaxis()

    return display


@fill_doc
def plot_connectome(
    adjacency_matrix,
    node_coords,
    node_color="auto",
    node_size=50,
    edge_cmap=DEFAULT_DIVERGING_CMAP,
    edge_vmin=None,
    edge_vmax=None,
    edge_threshold=None,
    output_file=None,
    display_mode="ortho",
    figure=None,
    axes=None,
    title=None,
    annotate=True,
    black_bg=False,
    alpha=0.7,
    edge_kwargs=None,
    node_kwargs=None,
    colorbar=True,
    radiological=False,
):
    """Plot connectome on top of the brain glass schematics.

    The plotted image should be in :term:`MNI` space for this function to work
    properly.

    In the case of 'l' and 'r' directions (for hemispheric projections),
    markers in the coordinate x == 0 are included in both hemispheres.

    Parameters
    ----------
    adjacency_matrix : numpy array of shape (n, n)
        Represents the link strengths of the graph. The matrix can be
        symmetric which will result in an undirected graph, or not
        symmetric which will result in a directed graph.

    node_coords : numpy array_like of shape (n, 3)
        3d coordinates of the graph nodes in world space.

    node_color : color or sequence of colors or 'auto', default='auto'
        Color(s) of the nodes. If string is given, all nodes
        are plotted with same color given in string.

    node_size : scalar or array_like, default=50
        Size(s) of the nodes in points^2.

    edge_cmap : colormap, default="RdBu_r"
        Colormap used for representing the strength of the edges.

    edge_vmin, edge_vmax : :obj:`float` or None, Default=None
        If not None, either or both of these values will be used to
        as the minimum and maximum values to color edges. If None are
        supplied the maximum absolute value within the given threshold
        will be used as minimum (multiplied by -1) and maximum
        coloring levels.

    edge_threshold : :obj:`str`, number or None, Default=None
        If it is a number only the edges with a value greater than
        edge_threshold will be shown.
        If it is a string it must finish with a percent sign,
        e.g. "25.3%%", and only the edges with a abs(value) above
        the given percentile will be shown.
    %(output_file)s
    display_mode : :obj:`str`, default='ortho'
        Choose the direction of the cuts: 'x' - sagittal, 'y' - coronal,
        'z' - axial, 'l' - sagittal left hemisphere only,
        'r' - sagittal right hemisphere only, 'ortho' - three cuts are
        performed in orthogonal directions. Possible values are: 'ortho',
        'x', 'y', 'z', 'xz', 'yx', 'yz', 'l', 'r', 'lr', 'lzr', 'lyr',
        'lzry', 'lyrz'.
    %(figure)s
    %(axes)s
    %(title)s
    %(annotate)s
    %(black_bg)s
        Default=False.
    alpha : :obj:`float` between 0 and 1, default=0.7
        Alpha transparency for the brain schematics.

    edge_kwargs : :obj:`dict` or None, default=None
        Will be passed as kwargs for each edge matlotlib Line2D.

    node_kwargs : :obj:`dict` or None, default=None
        Will be passed as kwargs to the plt.scatter call that plots all
        the nodes in one go.

    %(colorbar)s
        Default=True.

    %(radiological)s

    Returns
    -------
    display : :class:`~nilearn.plotting.displays.OrthoProjector` or None
        An instance of the OrthoProjector class. If ``output_file`` is defined,
        None is returned.

    See Also
    --------
    nilearn.plotting.find_parcellation_cut_coords : Extraction of node
        coords on brain parcellations.
    nilearn.plotting.find_probabilistic_atlas_cut_coords : Extraction of
        node coords on brain probabilistic atlases.

    """
    check_params(locals())

    display = plot_glass_brain(
        None,
        display_mode=display_mode,
        figure=figure,
        axes=axes,
        title=title,
        annotate=annotate,
        black_bg=black_bg,
        alpha=alpha,
        radiological=radiological,
    )

    display.add_graph(
        adjacency_matrix,
        node_coords,
        node_color=node_color,
        node_size=node_size,
        edge_cmap=edge_cmap,
        edge_vmin=edge_vmin,
        edge_vmax=edge_vmax,
        edge_threshold=edge_threshold,
        edge_kwargs=edge_kwargs,
        node_kwargs=node_kwargs,
        colorbar=colorbar,
    )

    return save_figure_if_needed(display, output_file)


@fill_doc
def plot_markers(
    node_values,
    node_coords,
    node_size="auto",
    node_cmap="gray",
    node_vmin=None,
    node_vmax=None,
    node_threshold=None,
    alpha=0.7,
    output_file=None,
    display_mode="ortho",
    figure=None,
    axes=None,
    title=None,
    annotate=True,
    black_bg=False,
    node_kwargs=None,
    colorbar=True,
    radiological=False,
):
    """Plot network nodes (markers) on top of the brain glass schematics.

    Nodes are color coded according to provided nodal measure. Nodal measure
    usually represents some notion of node importance.

    Parameters
    ----------
    node_values : array_like of length n
        Vector containing nodal importance measure. Each node will be colored
        according to corresponding node value.

    node_coords : numpy array_like of shape (n, 3)
        3d coordinates of the graph nodes in world space.

    node_size : 'auto' or scalar or array-like, default='auto'
        Size(s) of the nodes in points^2. By default the size of the node is
        inversely proportional to the number of nodes.

    node_cmap : :obj:`str` or colormap, default="gray".
        Colormap used to represent the node measure.

    node_vmin : :obj:`float` or None, default=None
        Lower bound of the colormap. If `None`, the min of the node_values is
        used.

    node_vmax : :obj:`float` or None, default=None
        Upper bound of the colormap. If `None`, the min of the node_values is
        used.

    node_threshold : :obj:`float` or None, default=None
        If provided only the nodes with a value greater than node_threshold
        will be shown.

    alpha : :obj:`float` between 0 and 1, default=0.7
        Alpha transparency for markers.
    %(output_file)s

    display_mode : :obj:`str`, default='ortho'
        Choose the direction of the cuts: 'x' - sagittal, 'y' - coronal,
        'z' - axial, 'l' - sagittal left hemisphere only,
        'r' - sagittal right hemisphere only, 'ortho' - three cuts are
        performed in orthogonal directions. Possible values are: 'ortho',
        'x', 'y', 'z', 'xz', 'yx', 'yz', 'l', 'r', 'lr', 'lzr', 'lyr',
        'lzry', 'lyrz'.
    %(figure)s
    %(axes)s
    %(title)s
    %(annotate)s
    %(black_bg)s
        Default=False.
    node_kwargs : :obj:`dict` or None, default=None
        will be passed as kwargs to the plt.scatter call that plots all
        the nodes in one go
    %(colorbar)s
        Default=True.
    %(radiological)s

    Returns
    -------
    display : :class:`~nilearn.plotting.displays.OrthoProjector` or None
        An instance of the OrthoProjector class. If ``output_file`` is defined,
        None is returned.
    """
    check_params(locals())

    node_values = np.array(node_values).flatten()
    node_coords = np.array(node_coords)

    # Validate node_values
    if node_values.shape != (node_coords.shape[0],):
        msg = (
            "Dimension mismatch: 'node_values' should be vector of length "
            f"{len(node_coords)}, "
            f"but current shape is {node_values.shape} "
            f"instead of {(node_coords.shape[0],)}"
        )
        raise ValueError(msg)

    display = plot_glass_brain(
        None,
        display_mode=display_mode,
        figure=figure,
        axes=axes,
        title=title,
        annotate=annotate,
        black_bg=black_bg,
        radiological=radiological,
    )

    if isinstance(node_size, str) and node_size == "auto":
        node_size = min(1e4 / len(node_coords), 100)

    # Filter out nodes with node values below threshold
    if node_threshold is not None:
        if node_threshold > np.max(node_values):
            msg = (
                f"Provided 'node_threshold' value: {node_threshold} "
                "should not exceed "
                f"highest node value: {np.max(node_values)}"
            )
            raise ValueError(msg)

        retained_nodes = node_values > node_threshold
        node_values = node_values[retained_nodes]
        node_coords = node_coords[retained_nodes]
        if isinstance(node_size, collections.abc.Iterable):
            node_size = [
                size
                for ok_retain, size in zip(
                    retained_nodes, node_size, strict=False
                )
                if ok_retain
            ]

    # Calculate node colors based on value
    node_vmin = np.min(node_values) if node_vmin is None else node_vmin
    node_vmax = np.max(node_values) if node_vmax is None else node_vmax
    if node_vmin == node_vmax:
        node_vmin = 0.9 * node_vmin
        node_vmax = 1.1 * node_vmax
    norm = Normalize(vmin=node_vmin, vmax=node_vmax)
    node_cmap = (
        plt.get_cmap(node_cmap) if isinstance(node_cmap, str) else node_cmap
    )
    node_color = [node_cmap(norm(node_value)) for node_value in node_values]

    # Prepare additional parameters for plt.scatter
    node_kwargs = {} if node_kwargs is None else node_kwargs
    node_kwargs.update([("alpha", alpha)])

    display.add_markers(
        marker_coords=node_coords,
        marker_color=node_color,
        marker_size=node_size,
        **node_kwargs,
    )

    if colorbar:
        display._colorbar = True
        display._show_colorbar(cmap=node_cmap, norm=norm)

    return save_figure_if_needed(display, output_file)


@fill_doc
def plot_carpet(
    img,
    mask_img=None,
    mask_labels=None,
    t_r=None,
    detrend=True,
    output_file=None,
    figure=None,
    axes=None,
    vmin=None,
    vmax=None,
    title=None,
    cmap="gray",
    cmap_labels="gist_ncar",
    standardize=True,
):
    """Plot an image representation of :term:`voxel` intensities across time.

    This figure is also known as a "grayplot" or "Power plot".

    Parameters
    ----------
    %(img)s
        4D image.

    mask_img : Niimg-like object or None, default=None
        Limit plotted voxels to those inside the provided mask.
        If a 3D atlas is provided, voxels will be grouped by atlas
        value and a colorbar will be added to the left side of the figure
        with atlas labels.
        If not specified, a new mask will be derived from data.
        See :ref:`extracting_data`.

    mask_labels : :obj:`dict` or None, default=None
        If ``mask_img`` corresponds to an atlas, then this dictionary maps
        values from the ``mask_img`` to labels. Dictionary keys are labels
        and values are values within the atlas.
    %(t_r)s

        .. note::
            If ``t_r`` is not provided, it will be inferred from ``img``'s
            header (``img.header.get_zooms()[-1]``).

        .. nilearn_versionadded:: 0.9.1
            Prior to this, ``t_r`` would be inferred from ``img`` without
            user input.

    detrend : :obj:`bool`, default=True
        Detrend and z-score the data prior to plotting.
    %(output_file)s
    %(figure)s
    %(axes)s
    %(vmin)s
    %(vmax)s
    %(title)s
    %(cmap)s
        Default=`gray`.

    cmap_labels : :class:`matplotlib.colors.Colormap`, or :obj:`str`, \
                  default=`gist_ncar`
        If ``mask_img`` corresponds to an atlas, then cmap_labels
        can be used to define the colormap for coloring the labels placed
        on the side of the carpet plot.

    %(standardize_true)s

        .. note::

            Added to control passing value to `standardize` of ``signal.clean``
            to call new behavior since passing "zscore" or True (default) is
            deprecated.
            This parameter will be changed to "zscore_sample"
            in version 0.14 and removed in version 0.15.

    Returns
    -------
    figure : :class:`matplotlib.figure.Figure`
        Figure object with carpet plot.

    Notes
    -----
    This figure was originally developed in :footcite:t:`Power2017`.

    In cases of long acquisitions (>800 volumes), the data will be downsampled
    to have fewer than 800 volumes before being plotted.

    References
    ----------
    .. footbibliography::

    """
    check_params(locals())
    img = check_niimg_4d(img, dtype="auto")

    # Define TR and number of frames
    t_r = t_r or float(img.header.get_zooms()[-1])
    n_tsteps = img.shape[-1]

    if mask_img is None:
        mask_img = compute_epi_mask(img)
    else:
        mask_img = check_niimg_3d(mask_img, dtype="auto")

    is_atlas = len(np.unique(mask_img.get_fdata())) > 2
    if is_atlas:
        background_label = 0

        atlas_img_res = resample_to_img(mask_img, img, interpolation="nearest")
        atlas_bin = math_img(
            f"img != {background_label}",
            img=atlas_img_res,
        )
        masker = NiftiMasker(atlas_bin, target_affine=img.affine)

        data = masker.fit_transform(img)
        atlas_values = masker.transform(atlas_img_res)
        atlas_values = np.squeeze(atlas_values)

        if mask_labels:
            label_dtype = next(iter(mask_labels.values()))
            if label_dtype != atlas_values.dtype:
                logger.log(
                    "Coercing atlas_values to "
                    f"{label_dtype.__class__.__name__}",
                    verbose=1,
                )
                atlas_values = atlas_values.astype(type(label_dtype))

        # Sort data and atlas by atlas values
        order = np.argsort(atlas_values)
        order = np.squeeze(order)
        atlas_values = atlas_values[order]
        data = data[:, order]
    else:
        data = apply_mask(img, mask_img)

    # Detrend and standardize data
    if detrend:
        data = clean(data, t_r=t_r, detrend=True, standardize=standardize)

    if figure is None:
        if not axes:
            figsize = (10, 5)
            figure = plt.figure(figsize=figsize)
        else:
            figure = axes.figure

    if axes is None:
        axes = figure.add_subplot(1, 1, 1)
    else:
        assert axes.figure is figure, "The axes passed are not in the figure"

    # Determine vmin and vmax based on the full data
    std = np.mean(data.std(axis=0))
    default_vmin = data.mean() - (2 * std)
    default_vmax = data.mean() + (2 * std)

    # Avoid segmentation faults for long acquisitions by decimating the data
    LONG_CUTOFF = 800
    # Get smallest power of 2 greater than the number of volumes divided by the
    # cutoff, to determine how much to decimate (downsample) the data.
    n_decimations = int(np.ceil(np.log2(np.ceil(n_tsteps / LONG_CUTOFF))))
    data = data[:: 2**n_decimations, :]

    if is_atlas:
        # Define nested GridSpec
        legend = False
        wratios = [2, 100, 20]
        gs = GridSpecFromSubplotSpec(
            1,
            2 + int(legend),
            subplot_spec=axes.get_subplotspec(),
            width_ratios=wratios[: 2 + int(legend)],
            wspace=0.0,
        )

        ax0 = plt.subplot(gs[0])
        ax0.set_xticks([])
        ax0.imshow(
            atlas_values[:, np.newaxis],
            interpolation="none",
            aspect="auto",
            cmap=cmap_labels,
        )
        if mask_labels:
            # Add labels to middle of each associated band
            mask_labels_inv = {v: k for k, v in mask_labels.items()}
            ytick_locs = [
                np.mean(np.where(atlas_values == i)[0])
                for i in np.unique(atlas_values)
            ]
            ax0.set_yticks(ytick_locs)
            ax0.set_yticklabels(
                [mask_labels_inv[i] for i in np.unique(atlas_values)]
            )
        else:
            ax0.set_yticks([])

        # Carpet plot
        if compare_version(mpl_version, ">=", "3.8.0rc1"):
            axes.remove()  # remove axes for newer versions of mpl
        axes = plt.subplot(gs[1])  # overwrites axes with older versions of mpl
        axes.imshow(
            data.T,
            interpolation="nearest",
            aspect="auto",
            cmap=cmap,
            vmin=vmin or default_vmin,
            vmax=vmax or default_vmax,
        )
        ax0.tick_params(axis="both", which="both", length=0)
    else:
        axes.imshow(
            data.T,
            interpolation="nearest",
            aspect="auto",
            cmap=cmap,
            vmin=vmin or default_vmin,
            vmax=vmax or default_vmax,
        )

    axes.grid(False)
    axes.set_yticks([])
    axes.set_yticklabels([])

    # Set 10 frame markers in X axis
    interval = max(
        (int(data.shape[0] + 1) // 10, int(data.shape[0] + 1) // 5, 1)
    )
    xticks = list(range(data.shape[0])[::interval])
    axes.set_xticks(xticks)
    axes.set_xlabel("time (s)")

    if title:
        axes.set_title(title)

    labels = t_r * (np.array(xticks))
    labels *= 2**n_decimations
    axes.set_xticklabels([f"{t:.02f}" for t in labels.tolist()])

    # Remove and redefine spines
    for side in ["top", "right"]:
        # Toggle the spine objects
        axes.spines[side].set_color("none")
        axes.spines[side].set_visible(False)

    axes.xaxis.set_ticks_position("bottom")
    axes.spines["bottom"].set_position(("outward", 10))

    if not mask_labels:
        axes.yaxis.set_ticks_position("left")
        buffer = 20 if is_atlas else 10
        axes.spines["left"].set_position(("outward", buffer))
        axes.set_ylabel("voxels")

    return save_figure_if_needed(figure, output_file)


def plot_img_comparison(
    ref_imgs,
    src_imgs,
    masker=None,
    plot_hist=True,
    log=True,
    ref_label="image set 1",
    src_label="image set 2",
    output_dir=None,
    axes=None,
):
    """Redirect to plot_img_comparison."""
    from nilearn.plotting.img_comparison import plot_img_comparison

    # TODO (nilearn >= 0.13.1)
    warnings.warn(
        (
            "The 'plot_img_comparison' has been moved to  "
            "'nilearn.plotting.img_comparison'.\n"
            "It will be removed from 'nilearn.plotting.img_plotting' "
            "in version >= 0.13.1.\n"
            "Import 'plot_img_comparison' "
            "from 'nilearn.plotting.img_comparison' "
            "to silence this warning."
        ),
        FutureWarning,
        stacklevel=find_stack_level(),
    )

    plot_img_comparison(
        ref_imgs,
        src_imgs,
        masker,
        plot_hist=plot_hist,
        log=log,
        ref_label=ref_label,
        src_label=src_label,
        output_dir=output_dir,
        axes=axes,
    )
