Plotting#

VIBE uses Strategies for generating plots from validation data. This allows you to control:

  • How datasets are combined in plots (overlaid, separate, per-component)

  • Which rendering backend to use (matplotlib, ROOT)

  • How plots are styled and formatted

Overview of Strategies#

A plotting strategy defines how plots are generated from your parquet data files. VIBE provides three built-in strategies:

  1. PerDatasetPlottingStrategy: Generates separate plots for each dataset

  2. OverlayDatasetsPlottingStrategy: Overlays all datasets on a single plot

  3. OverlayDatasetsPerComponentPlottingStrategy: Creates overlay plots separately for each histogram component

The plotting design separates the plotting logic (strategy) from the rendering backend (renderer), allowing you to mix and match different combinations.

Built-in Plotting Strategies#

Per-Dataset Strategy#

This strategy generates one plot per dataset. Each histogram defined in your validation mode will produce multiple output files - one for each dataset in your configuration.

Use case: When you want to analyze each dataset individually and compare them side-by-side.

Output: For 3 datasets and 5 histograms, you get 15 separate plots.

Overlay Datasets Strategy#

This strategy overlays all datasets on the same plot. All histogram components from all datasets are shown together with distinct colors/styles.

Use case: When you want to directly compare all datasets on a single plot.

Output: For 3 datasets and 5 histograms, you get 5 plots, each containing all datasets.

Overlay Datasets Per-Component Strategy#

This strategy creates separate overlay plots for each histogram component. All datasets are overlaid, but only for a single component at a time.

Use case: When your histogram has multiple components (e.g., signal/background) and you want to compare how each component varies across datasets.

Output: For 3 datasets, 5 histograms, and 2 components per histogram, you get 10 plots.

Custom Strategies In This Repo#

In addition to the built-in strategies, VIBE includes custom plotting strategies that are already registered and can be used directly in plotting_strategies.

Multi-Comparison Binned Strategy#

Strategy name: "multi_comparison_binned"

This strategy compares one baseline dataset against one or more datasets in user-defined bins.

Use case: When you want binned ratio/efficiency/asymmetry-style comparisons across datasets.

Output: One plot per histogram/component (and per bin-config set), with a main histogram panel and an optional comparison panel.

Key configuration:

These settings are now read from the mode TOML file under [plotting_config.multi_comparison_binned]. Validation mode classes may still define the old attributes, but those are only used as fallback for backward compatibility.

  • multi_comparison_bin_config (Required): Defines the specific bins or categories for the multi-comparison plots (e.g., binning in momentum or angles). Use an empty string for the no-cut bin, for example { "": "" }.

  • multi_comparison_type (Optional, default "ratio"): Specifies the type of mathematical comparison to perform between datasets, such as ratio, difference, or asymmetry.

  • multi_comparison_range (Optional): Sets the exact y-axis plotting range for the comparison panel to keep visual scales consistent.

  • multi_comparison_show_comparison_panel (Optional): A boolean flag to toggle the display of the bottom comparison panel on or off.

  • multi_comparison_normalize_unity (Optional): If True, scales all histograms to a total area of 1 before performing the comparison.

  • correlated_datasets (Optional): Indicates whether statistical uncertainties should account for correlation between the datasets being compared (this is done via bootstrapping).

  • multi_comparison_binning_config (Optional): Provides alternative structural configurations for histogram bins specific to the comparison plots.

Example TOML configuration:

[plotting_config]
    [plotting_config.multi_comparison_binned]
        multi_comparison_type = "ratio"
        multi_comparison_range = [0.0, 2.0]
        multi_comparison_show_comparison_panel = true
        multi_comparison_normalize_unity = false
        correlated_datasets = true
        multi_comparison_bin_config = [{ "" = "" }]

Particle List Efficiency Strategy#

Strategy name: "particle_list_efficiency"

This strategy computes efficiency-style comparisons between two particle lists inside each dataset (for example gamma:selection vs gamma:gen).

Use case: When you want reconstruction-vs-reference efficiency as a function of a histogram variable, with optional all-dataset overlay.

Output: Per-dataset efficiency plots and ratio-only overlays, plus an all-datasets ratio-only comparison when multiple datasets are configured.

Key configuration:

These settings are now read from [plotting_config.particle_list_efficiency] in the mode TOML file. The strategy reuses the same multi_comparison_* options as "multi_comparison_binned" to control the binning and formatting of the efficiency ratio plots.

  • particle_list_vars_for_efficiency (Optional): Explicitly defines which particle lists to compare as the numerator and denominator; if unset, they are auto-detected.

Example TOML configuration:

[plotting_config]
    [plotting_config.particle_list_efficiency]
        multi_comparison_type = "efficiency"
        multi_comparison_range = [0.0, 1.3]
        multi_comparison_show_comparison_panel = true
        multi_comparison_normalize_unity = false
        correlated_datasets = true
        multi_comparison_bin_config = { "E < 0.025 GeV" = "gamma_mcE >= 0.025 and gamma_mcE < 0.045" }

Resolution Fitting Strategy#

Strategy name: "resolution_fitting"

This strategy performs a fit scan of relative resolution in bins of the configured histogram variable and extracts width metrics.

Use case: When you want per-bin fitted resolution performance instead of direct histogram overlays.

Output: Per-dataset fit-scan summary plots, optional diagnostic fit plots per bin, and a comparison plot when exactly two datasets are configured.

Key configuration:

These settings are now read from [plotting_config.resolution_fitting] in the mode TOML file. Validation mode classes may still define the old attributes, but TOML values take precedence and the class attributes are only used as fallback defaults.

  • resolution_pred_col (default "E"): The column name in the dataset representing the reconstructed or predicted value.

  • resolution_truth_col (default "mcE"): The column name representing the truth or reference value used to compute the resolution.

  • resolution_selection: A boolean selection string applied to the dataset before performing the fit.

  • resolution_x_label: The label text applied to the x-axis describing the quantity being fitted.

  • resolution_min_data_points (default 200): The minimum number of entries required in a bin to attempt a resolution fit.

  • resolution_max_bins: Limits the total number of bins that will be processed during the fit scan.

  • resolution_max_fit_events (default 50000): Ceiling on the maximum number of events to use for each fit to ensure reasonable processing times.

  • resolution_remove_nans (default False): If True, automatically filters out invalid/NaN entries from the dataset before fitting.

Example TOML configuration:

[plotting_config]
    [plotting_config.resolution_fitting]
        resolution_pred_col = "pi0_E"
        resolution_truth_col = "pi0_mcE"
        resolution_selection = "pi0_isSignal == 1.0"
        resolution_x_label = "$E_{\pi^0}$"
        resolution_min_data_points = 200
        resolution_remove_nans = true

Example mode setup:

class MyResolutionMode(ValidationModeBaseClass):
    plotting_strategies = ["resolution_fitting"]

    # Prefer placing these in [plotting_config.resolution_fitting] in the mode TOML.
    # Class attributes are still supported as a fallback for older modes.
    resolution_pred_col = "pi0_E"
    resolution_truth_col = "pi0_mcE"
    resolution_selection = "pi0_isSignal == 1.0"
    resolution_x_label = r"$E_{\pi^0}$"
    resolution_min_data_points = 200
    resolution_remove_nans = True

Configuring Strategies in Your Mode#

Default Strategies#

By default, all three built-in strategies are available for any validation mode. You can control which strategies to use by overriding the plotting_strategies property:

from vibe.core.validation_mode import ValidationModeBaseClass

class MyValidationMode(ValidationModeBaseClass):
    name = "MyMode"

    @property
    def plotting_strategies(self):
        # Only use per-dataset and overlay strategies
        return ["per_dataset", "overlay_datasets"]

    # ... rest of your mode implementation

Available strategy names:

  • "per_dataset"

  • "overlay_datasets"

  • "overlay_datasets_per_component"

Project-specific custom strategy names:

  • "multi_comparison_binned"

  • "particle_list_efficiency"

  • "resolution_fitting"

Using Custom Strategies#

If you’ve registered a custom plotting strategy (see below), you can include it in the list:

@property
def plotting_strategies(self):
    return ["per_dataset", "my_custom_strategy"]

Renderers and Output Formats#

VIBE supports multiple renderers for different output formats:

Matplotlib Renderer (MatplotlibHistogramRenderer)
  • Generates matplotlib-style plots

  • Default renderer for all built-in strategies

ROOT Renderer (ROOTHistogramRenderer)
  • Generates ROOT-compatible plots

  • Uses PyROOT for rendering

Creating a Custom Plotting Strategy#

Step 1: Inherit from PlottingStrategy#

Create a new Python file in vibe/core/plotting/strategies/custom/ and define your strategy class:

# vibe/core/plotting/strategies/custom/my_custom_strategy.py
from vibe.core.plotting.strategies.plotting_strategy import PlottingStrategy
from vibe.core.plotting.renderers.renderer_config import (
    RendererConfigFactory,
    ConfigurableRenderer
)
from vibe.core.helper.root_helper import makeROOTCompatible
from vibe.core.helper.settings import get_setting
from vibe.core.helper.parquet_helper import read_parquet
import os
import numpy as np


class MyCustomPlottingStrategy(PlottingStrategy):
    """Custom plotting strategy with specialized behavior."""

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

Step 2: Implement the generate_plots Method#

The generate_plots method is where all the plotting logic goes. It receives:

  • input_file_paths: Dictionary mapping dataset keys to parquet file paths

  • mode: The validation mode instance containing configuration

  • dry_run: Boolean flag for testing without actually generating plots

def generate_plots(self, input_file_paths, mode, dry_run=False):
    """Generate plots according to custom strategy.

    Args:
        input_file_paths (dict): Mapping of keys to parquet file paths
        mode: Validation mode configuration
        dry_run (bool): If True, only notify observers without rendering
    """
    if self._histogram_renderer is None:
        raise ValueError("Histogram renderer is not set.")

    # Iterate through histograms
    histograms_by_particle_list = mode.validation_class(
        base_path=get_setting(key="base_path")
    ).get_analysis_validation_histograms_by_particle_list()

    for particle_list, histograms in histograms_by_particle_list.items():
        for histogram in histograms:
            self._generate_single_plot(
                histogram=histogram,
                particle_list=particle_list,
                input_file_paths=input_file_paths,
                mode=mode,
                dry_run=dry_run
            )

Here you would implement your custom plotting logic inside the _generate_single_plot. In principle, this can be any logic you want, for example generating ratio plots, normalized overlays, etc.

Step 4: Register Your Strategy#

Register your custom strategy with the PlottingStrategiesProvider so it can be used in validation modes:

# vibe/core/plotting/strategies/plotting_strategies_provider.py
from vibe.core.plotting.strategies.custom.my_custom_strategy import (
    MyCustomPlottingStrategy
)
from vibe.core.plotting.renderers.matplotlib_histogram_renderer import (
    MatplotlibHistogramRenderer
)
from vibe.core.plotting.renderers.root_histogram_renderer import (
    ROOTHistogramRenderer
)

# Register with matplotlib renderer
PlottingStrategiesProvider.register(
    "my_custom_strategy",
    MyCustomPlottingStrategy(
        histogram_renderer=MatplotlibHistogramRenderer()
    )
)

# Or register with ROOT renderer
PlottingStrategiesProvider.register(
    "my_custom_strategy_root",
    MyCustomPlottingStrategy(
        histogram_renderer=ROOTHistogramRenderer()
    )
)

Step 5: Use in Your Validation Mode#

Now you can use your custom strategy in any validation mode:

from vibe.core.validation_mode import ValidationModeBaseClass

class MyMode(ValidationModeBaseClass):
    name = "MyMode"

    @property
    def plotting_strategies(self):
        return ["my_custom_strategy", "per_dataset"]

    # ... rest of mode implementation

Notifying Observers#

You need to remember to notify observers before generating each plot, as it is a way to tell VIBE what plots are being created. When a plot is about to be generated, the strategy notifies observers with the output key and path:

def generate_plots(self, input_file_paths, mode, dry_run=False):
    # ... setup code ...

    for histogram in histograms:
        output_key = "MyMode_dataset1_histogram1"
        output_path = "/path/to/output.pdf"

        # Notify observers before generating
        self.notify_observers(output_key, output_path)

        # Generate the actual plot
        if not dry_run:
            self._histogram_renderer.render(...)