Render ranking exports and save the figure owning the supplied axes

This commit is contained in:
Andras Schmelczer 2026-10-27 20:03:45 +00:00
commit 85355bce11
2 changed files with 40 additions and 6 deletions

View file

@ -1,9 +1,10 @@
from pathlib import Path from pathlib import Path
from typing import Dict, List, Optional, TypeVar from typing import Dict, List, Optional, TypeVar, cast
import matplotlib import matplotlib
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
import numpy as np import numpy as np
from matplotlib.figure import Figure
from sklearn.metrics import average_precision_score, precision_recall_curve from sklearn.metrics import average_precision_score, precision_recall_curve
from ..unique import unique from ..unique import unique
@ -45,7 +46,8 @@ def evaluate_ranking(
assert 0 <= target_recall <= 1 assert 0 <= target_recall <= 1
if plot and axes is None: render = plot or output_svg is not None
if render and axes is None:
fig = plt.figure(figsize=(10, 10)) fig = plt.figure(figsize=(10, 10))
fig.patch.set_facecolor("white") fig.patch.set_facecolor("white")
ax = plt.axes() ax = plt.axes()
@ -56,7 +58,7 @@ def evaluate_ranking(
str_classes = [str(c) for c in classes] str_classes = [str(c) for c in classes]
with matplotlib.rc_context({"font.size": 20}): with matplotlib.rc_context({"font.size": 20}):
if plot: if render:
ax.set_xmargin(0) ax.set_xmargin(0)
draw_f1_iso_lines(axes=ax) draw_f1_iso_lines(axes=ax)
@ -87,14 +89,14 @@ def evaluate_ranking(
) )
results[classes[i]] = precision_at_closest_recall results[classes[i]] = precision_at_closest_recall
if plot: if render:
ax.plot( ax.plot(
recall, recall,
precision, precision,
label=f"{'|'.join(str_classes[:i + 1])} ↔ {'|'.join(str_classes[i+1:])} (P@{target_recall:.2f}={precision_at_closest_recall:.2f}, AP={average_precision:.2f})", label=f"{'|'.join(str_classes[:i + 1])} ↔ {'|'.join(str_classes[i+1:])} (P@{target_recall:.2f}={precision_at_closest_recall:.2f}, AP={average_precision:.2f})",
) )
if plot: if render:
ax.legend(loc="upper right") ax.legend(loc="upper right")
ax.axvline(x=target_recall, linestyle="--", color="#55c6bb", linewidth=2.0) ax.axvline(x=target_recall, linestyle="--", color="#55c6bb", linewidth=2.0)
@ -112,6 +114,8 @@ def evaluate_ranking(
if axes is None: if axes is None:
plt.show() plt.show()
elif output_svg: elif output_svg:
plt.savefig(output_svg, format="svg") cast(Figure, ax.get_figure(root=True)).savefig(output_svg, format="svg")
if not plot and axes is None:
plt.close(ax.get_figure(root=True))
return results return results

View file

@ -1,6 +1,7 @@
from pathlib import Path from pathlib import Path
import matplotlib import matplotlib
import matplotlib.pyplot as plt
import pytest import pytest
matplotlib.use("Agg") # don't show a window for each test matplotlib.use("Agg") # don't show a window for each test
@ -108,3 +109,32 @@ def test_save() -> None:
assert path.exists() assert path.exists()
path.unlink() path.unlink()
def test_export_saves_the_supplied_axes_figure(tmp_path: Path) -> None:
intended, axes = plt.subplots()
unrelated, other = plt.subplots()
other.set_title("unrelated figure")
output = tmp_path / "ranking.svg"
try:
evaluate_ranking(
[0, 1],
[0.1, 0.9],
target_recall=1,
axes=axes,
output_svg=output,
title="intended ranking",
)
content = output.read_text()
assert "intended ranking" in content
assert "unrelated figure" not in content
finally:
plt.close(intended)
plt.close(unrelated)
def test_export_without_display_still_renders(tmp_path: Path) -> None:
output = tmp_path / "ranking.svg"
evaluate_ranking([0, 1], [0.1, 0.9], target_recall=1, plot=False, output_svg=output)
assert "Precision" in output.read_text()
assert "Recall" in output.read_text()