Skip to content

Redesign plotting code: pure-Python PlotKit (mplhep/cmsstyle, standalone, bkg scaling)#1

Merged
kandrosov merged 5 commits into
mainfrom
issue-171
Jun 29, 2026
Merged

Redesign plotting code: pure-Python PlotKit (mplhep/cmsstyle, standalone, bkg scaling)#1
kandrosov merged 5 commits into
mainfrom
issue-171

Conversation

@kandrosov

@kandrosov kandrosov commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Summary

From-scratch redesign of the FLAF plotting code (for cms-flaf/FLAF#171)
into a small, layered, pure-Python package. It renders CMS plots with matplotlib + mplhep by
default and optionally through ROOT + cmsstyle, replacing the legacy ROOT-C++ renderer
(StackedPlotDescriptor.h / PdfPrinter.h / … driven via gInterpreter).

It is a transparent drop-in for FLAF — the legacy Plotter API and the analysis
config/plot/*.yaml files are unchanged, so only the FLAF submodule pointer moves — and it also
runs standalone outside FLAF.

What's included

  • Layered package (relative imports, so it works as both FLAF.PlotKit and standalone PlotKit):
    histogram.py (Hist1D + TH1/uproot/numpy adapters), rootcompat.py (ROOT colours / TLatex /
    fonts / "n|lo:hi" binning → matplotlib), config.py (loads the legacy YAML), spec.py
    (engine-neutral StackSpec), backends.py (MplhepBackend default + CmsstyleBackend, lazy ROOT
    import with fallback), plotters/ (StackedPlotter + STYLES registry + abstract BasePlotter
    extension points), Plotter.py (legacy-compatible facade), cli.py (standalone).
  • Pure Python — no C++. The legacy include/ headers are not carried over.
  • Standalone CLI renders straight from a ROOT file via uproot (no FLAF/ROOT): an explicit
    inputs.yaml process map, or auto-discovery of a real FLAF merged file
    (--path channel/region/category + --data-name / --signal-regex / --signal-select).
  • New capability beyond the old renderer: --signal-scale bkg / signal_plot_scale: bkg
    normalises each signal's integral to the summed background (shape comparison), alongside the
    existing fixed-factor scaling; the legend then reads … (norm. to bkg).

Testing

  • pytest 15/15: ROOT-compat parsing (colours / TLatex incl. a mathtext parse check / binning),
    config merge, smoke renders to PDF/PNG for both backends, CLI nested-file navigation +
    auto-discovery, and signal→background normalisation.
  • Verified end-to-end on real HH_bbWW all_plots histograms (Run3_2023) through both the FLAF
    law-task plotting path (HistPlotter.py + Run3_Model) and the standalone CLI — faithful stacked
    plots (CMS/lumi/channel/category/region labels, divide_by_bin_width, stacked backgrounds +
    bkg-uncertainty band + scaled signals + data + Obs/Bkg ratio).

Companion PRs

  • FLAF issue-171-plotkit-redesign: .gitmodules URL → cms-flaf/PlotKit, submodule pointer,
    mk_flaf_env.sh (+cmsstyle), docs. HistPlotter.py unchanged.
  • HH_bbWW plotkit-signal-scale-to-bkg: signal_plot_scale: bkg.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a redesigned, pure-Python PlotKit package that preserves the legacy FLAF Plotter API and YAML configuration vocabulary while adding a standalone workflow. It provides a default matplotlib/mplhep renderer with an optional ROOT/cmsstyle backend, plus new signal scaling behavior (including “norm to bkg”) and accompanying tests/examples.

Changes:

  • Added engine-neutral plotting spec + layered architecture (config/hist/spec/plotters/backends) with a legacy-compatible Plotter facade.
  • Implemented standalone CLI that can render directly from ROOT files via uproot, including auto-discovery of processes.
  • Added pytest coverage for ROOT-compat translation, config merging, CLI discovery/rendering, and signal→background normalization.

Reviewed changes

Copilot reviewed 21 out of 23 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/test_smoke.py Smoke tests for PDF/PNG rendering, signal scaling-to-bkg behavior, and optional cmsstyle backend.
tests/test_rootcompat.py Unit tests for ROOT color/TLatex/font/bin parsing and mathtext-parse safety.
tests/test_config.py Tests for legacy config merge behavior and backend selection via env var.
tests/test_cli.py Tests CLI auto-discovery classification/selection and end-to-end rendering from nested files.
tests/conftest.py Adjusts sys.path so PlotKit imports work from a checkout during tests.
spec.py Defines engine-neutral plot specification dataclasses (StackSpec, entries, uncertainty band).
rootcompat.py Translates ROOT-flavored config vocabulary (colors, TLatex, styles, bins) to matplotlib equivalents.
requirements.txt Declares core runtime dependencies for the default (mplhep) backend.
README.md Documents installation, CLI usage (explicit map + auto-discovery), API usage, and architecture.
pyproject.toml Defines package metadata, dependencies, optional extras, console script, and packaging layout.
plotters/stacked.py Implements the backward-compatible stacked plot style and signal scaling options (incl. bkg).
plotters/base.py Base plotter class wiring config + backend selection.
plotters/__init__.py Plot-style registry and style lookup helper.
Plotter.py Legacy-compatible Plotter facade matching FLAF’s import/usage patterns.
histogram.py Introduces Hist1D and adapters from ROOT TH1, uproot histograms, and numpy.
examples/demo_standalone.py Minimal runnable example rendering synthetic histograms without FLAF/ROOT.
examples/config/era_Run3_2022.yaml Example era override config (lumi/channel/region labels).
examples/config/cms_stacked.yaml Example legacy-style stacked plotting configuration.
config.py Loads/merges legacy YAML configs and provides accessors + backend selection.
cli.py Standalone CLI entry point and histogram discovery/loading logic.
backends.py Implements mplhep backend and optional cmsstyle backend with fallback behavior.
.gitignore Adds common Python/build artifacts and plot outputs to ignore list.
__init__.py Package metadata and curated exports while preserving legacy Plotter submodule semantics.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread plotters/base.py Outdated
Comment on lines +20 to +22
def __init__(self, config: PlotConfig, backend: Optional[StyleBackend] = None):
self.config = config
self.backend = backend or get_backend(config.backend_name())
Comment thread cli.py Outdated
Comment on lines +73 to +87
with open(inputs_cfg, "r") as f:
inputs = yaml.safe_load(f)

prefix = f"{path.strip('/')}/" if path else ""
histograms = {}
with uproot.open(input_file) as f:
for entry in inputs:
name = entry["name"]
# Accept a nested "<path>/<name>", a "<name>/<hist_name>" layout, or a flat object.
for candidate in (
f"{prefix}{name}",
f"{name}/{hist_name}",
name,
hist_name,
):
Comment thread spec.py
Comment on lines +17 to +40

@dataclasses.dataclass
class HistEntry:
"""One histogram to draw, with its already-resolved (engine-neutral) style."""

hist: Hist1D
label: str
color: Tuple[float, float, float] = (0.5, 0.5, 0.5)
filled: bool = True
line_style: str = "-"
line_width: float = 1.0
line_color: Optional[Tuple[float, float, float]] = None
marker: Optional[str] = None
marker_size: float = 6.0
legend: bool = True


@dataclasses.dataclass
class UncBand:
"""Style for the background-uncertainty band (drawn from the stack total)."""

hatch: Optional[str] = "///"
color: Tuple[float, float, float] = (0.4, 0.6, 0.6)
label: str = "Bkg. uncertainty"
…cess hist_name fallback; widen color type hints
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants