Skip to content
Snippets Groups Projects
evaluate.py 3.48 KiB
# SPDX-FileCopyrightText: Copyright © 2023 Idiap Research Institute <contact@idiap.ch>
#
# SPDX-License-Identifier: GPL-3.0-or-later

import pathlib
import typing

import click

from clapper.click import ResourceOption, verbosity_option
from clapper.logging import setup

from ...models.typing import SaliencyMapAlgorithm
from ..click import ConfigCommand

# avoids X11/graphical desktop requirement when creating plots
__import__("matplotlib").use("agg")

logger = setup(__name__.split(".")[0], format="%(levelname)s: %(message)s")


@click.command(
    entry_point_group="ptbench.config",
    cls=ConfigCommand,
    epilog="""Examples:

1. Tabulates and generates plots for two saliency map algorithms:

   .. code:: sh

      ptbench saliency evaluate -vv -e gradcam path/to/gradcam-completeness.json path/to/gradcam-interpretability.json -e gradcam++ path/to/gradcam++-completeness.json path/to/gradcam++-interpretability.json
""",
)
@click.option(
    "--entry",
    "-e",
    required=True,
    multiple=True,
    help=f"ENTRY is a triplet containing the algorithm name, the path to the "
    f"scores issued from the completness analysis (``ptbench "
    f"saliency-completness``) and scores issued from the interpretability "
    f"analysis (``ptbench saliency-interpretability``), both in JSON format. "
    f"Paths to score files must exist before the program is called. Valid values "
    f"for saliency map algorithms are "
    f"{'|'.join(typing.get_args(SaliencyMapAlgorithm))}",
    type=(
        click.Choice(
            typing.get_args(SaliencyMapAlgorithm), case_sensitive=False
        ),
        click.Path(
            exists=True,
            file_okay=True,
            dir_okay=False,
            path_type=pathlib.Path,
        ),
        click.Path(
            exists=True,
            file_okay=True,
            dir_okay=False,
            path_type=pathlib.Path,
        ),
    ),
    cls=ResourceOption,
)
@click.option(
    "--output-folder",
    "-o",
    help="Path where to store the analysis result (created if does not exist)",
    required=False,
    default="results",
    type=click.Path(file_okay=False, dir_okay=True, path_type=pathlib.Path),
    cls=ResourceOption,
)
@verbosity_option(logger=logger, expose_value=False)
def evaluate(
    entry,
    output_folder,
    **_,  # ignored
) -> None:
    """Calculates summary statistics for a saliency map algorithm."""
    import json

    from matplotlib.backends.backend_pdf import PdfPages

    from ...engine.saliency.evaluator import run, summary_table

    summary = {
        algo: run(algo, json.load(complet.open()), json.load(interp.open()))
        for algo, complet, interp in entry
    }
    table = summary_table(summary, "rst")
    click.echo(summary)

    if output_folder is not None:
        output_folder.mkdir(parents=True, exist_ok=True)

        table_path = output_folder / "summary.rst"

        logger.info(f"Saving summary table at `{table_path}`...")
        with table_path.open("w") as f:
            f.write(table)

        figure_path = output_folder / "plots.pdf"
        logger.info(f"Saving figures at `{figure_path}`...")

        with PdfPages(figure_path) as pdf:
            for dataset in summary.keys():
                pdf.savefig(summary[dataset]["aopc-combined"]["plot"])
                pdf.savefig(summary[dataset]["proportional-energy"]["plot"])
                pdf.savefig(
                    summary[dataset]["road-weighted-proportional-energy"][
                        "plot"
                    ]
                )