Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/gh-pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ jobs:
run: src/python/build_site.py
- name: Create overview
run: bmp-create-overview --html-file=_site/index.html
- name: Create reports
run: python3 vis/generate_petab_reports.py --output-dir=_site/reports
- name: Setup Pages
uses: actions/configure-pages@v5
- name: Upload artifact
Expand Down
2 changes: 1 addition & 1 deletion src/python/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ dependencies = [

[project.optional-dependencies]
dev = ["pre-commit", "pytest", "ruff"]
site = ["bokeh>=3.7.3"]
site = ["bokeh>=3.7.3", "petab[vis]", "Jinja2>=3.0.3"]

[project.scripts]
bmp-petablint = "benchmark_models_petab.check_petablint:main"
Expand Down
97 changes: 97 additions & 0 deletions vis/generate_petab_reports.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import argparse
import base64
import io
from pathlib import Path

import matplotlib.pyplot as plt
import pandas as pd
import petab.v1
import petab.v1.visualize.plotter
from jinja2 import Environment, FileSystemLoader
from petab.visualize import plot_problem

import benchmark_models_petab as bmp

# plot simulation line without markers
petab.v1.visualize.plotter.simulation_line_kwargs["marker"] = ""
# plot measurements with markers only
petab.v1.visualize.plotter.measurement_line_kwargs["linestyle"] = "None"


def _plot_problem(problem: petab.v1.Problem, sim_df: pd.DataFrame) -> str:
"""Plot measurement points and simulation line for one observable and return base64 png."""
plot_problem(problem, simulations_df=sim_df)
fig = plt.gcf()

buf = io.BytesIO()
fig.savefig(buf, format="png", dpi=150)
plt.close(fig)
buf.seek(0)
img_b64 = base64.b64encode(buf.read()).decode("ascii")
return img_b64


def generate_report_for_model(problem_id: str, template_env: Environment, out_dir: Path):
problem = bmp.get_problem(problem_id)
if problem is None:
return

sim_df = bmp.get_simulation_df(problem_id)

images: list[dict[str, str]] = []
img = _plot_problem(problem, sim_df)
images.append({"id": problem_id, "img": img})

template = template_env.get_template("problem_report.html.jinja")
rendered = template.render(model_name=problem_id, images=images)

out_dir.mkdir(parents=True, exist_ok=True)
out_path = out_dir / f"{problem_id}.html"
with open(out_path, "w", encoding="utf-8") as fh:
fh.write(rendered)
print(f"Wrote {out_path}")


def generate_index(problem_id_list: list[str], template_env: Environment, out_dir: Path):
"""Render an index page with links to per-problem reports."""
template = template_env.get_template("index.html.jinja")
rendered = template.render(models=problem_id_list, count=len(problem_id_list))

out_dir.mkdir(parents=True, exist_ok=True)
out_path = out_dir / "index.html"
with open(out_path, "w", encoding="utf-8") as fh:
fh.write(rendered)
print(f"Wrote {out_path}")


def main(output_dir: Path = None):
if output_dir is None:
output_dir = Path(__file__).parent / "reports"
templates_dir = Path(__file__).parent / "templates"

env = Environment(loader=FileSystemLoader(templates_dir), autoescape=True)
problem_id_list = list(bmp.MODELS)

for problem_id in problem_id_list:
try:
generate_report_for_model(problem_id, env, output_dir)
except Exception as e:
print(f"Skipping {problem_id} due to error: {e}")

try:
generate_index(problem_id_list, env, output_dir)
except Exception as e:
print(f"Failed to write index: {e}")

if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Generate PETab problem reports")
parser.add_argument(
"--output-dir",
"-o",
type=Path,
default=None,
help="Directory to write reports to (default: vis/reports)",
)
args = parser.parse_args()

main(args.output_dir)
26 changes: 26 additions & 0 deletions vis/templates/index.html.jinja
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>PEtab problems overview</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
ul { list-style: none; padding: 0; }
li { margin: 6px 0; }
a { text-decoration: none; color: #0366d6; }
a:hover { text-decoration: underline; }
</style>
</head>
<body>
<h1>PEtab problems ({{ count }})</h1>
{% if models %}
<ul>
{% for m in models %}
<li><a href="{{ m }}.html">{{ m }}</a></li>
{% endfor %}
</ul>
{% else %}
<p>No problems found.</p>
{% endif %}
</body>
</html>
38 changes: 38 additions & 0 deletions vis/templates/problem_report.html.jinja
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<!-- html -->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>{{ model_name }} - PEtab report</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.figure { margin-bottom: 24px; }
img { max-width: 100%; height: auto; border: 1px solid #ccc; }
.top-nav { margin-bottom: 12px; }
.top-nav a { text-decoration: none; color: #0366d6; margin-right: 12px; }
.top-nav a:hover { text-decoration: underline; }
</style>
</head>
<body>
<div class="top-nav">
<a href="index.html">&larr; Back to index</a>
<a href="https://github.com/Benchmarking-Initiative/Benchmark-Models-PEtab/tree/master/Benchmark-Models/{{ model_name|urlencode }}" target="_blank" rel="noopener noreferrer">
View PEtab files on GitHub
</a>
</div>

<h1>{{ model_name }}</h1>
{% if images %}
{% for img in images %}
<div class="figure">
{% if images|length > 1 %}
<h3>{{ img.id }}</h3>
{% endif %}
<img src="data:image/png;base64,{{ img.img }}" alt="{{ img.id }}">
</div>
{% endfor %}
{% else %}
<p>No observables or data available for this problem.</p>
{% endif %}
</body>
</html>