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:
PerDatasetPlottingStrategy: Generates separate plots for each dataset
OverlayDatasetsPlottingStrategy: Overlays all datasets on a single plot
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(default200): 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(default50000): Ceiling on the maximum number of events to use for each fit to ensure reasonable processing times.resolution_remove_nans(defaultFalse): 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 pathsmode: The validation mode instance containing configurationdry_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(...)