irregular6612 Claude Sonnet 4.6 commited on
Commit
09e1c5e
·
1 Parent(s): 9511d23

fix(cp5): close PNG figures on error + force Agg + _title unit tests

Browse files
Files changed (2) hide show
  1. proteus/viz/png.py +9 -7
  2. tests/viz/test_png.py +26 -0
proteus/viz/png.py CHANGED
@@ -36,7 +36,7 @@ def write_pngs(
36
  """Render every frame to `<out_dir>/frame_NNN.png`; return the paths in order."""
37
  import matplotlib
38
 
39
- matplotlib.use("Agg") # headless: no window, no display required
40
  import matplotlib.pyplot as plt
41
 
42
  out_dir = Path(out_dir)
@@ -47,12 +47,14 @@ def write_pngs(
47
  # scale: int = 4, color_map: Optional[dict] = None) -> ndarray
48
  # The first `steps` arg is a display counter (ignored inside the function).
49
  rgb = frame_to_rgb_array(0, step.frame, scale, COLOR_MAP)
50
- fig, ax = plt.subplots(figsize=(4.0, 4.4))
51
- ax.imshow(rgb, interpolation="nearest")
52
- ax.axis("off")
53
- ax.set_title(_title(step), fontsize=9)
54
  path = out_dir / f"frame_{i:03d}.png"
55
- fig.savefig(path, dpi=100, bbox_inches="tight")
56
- plt.close(fig)
 
 
 
 
 
 
57
  paths.append(path)
58
  return paths
 
36
  """Render every frame to `<out_dir>/frame_NNN.png`; return the paths in order."""
37
  import matplotlib
38
 
39
+ matplotlib.use("Agg", force=True) # headless; force switch even if a GUI backend pre-loaded pyplot via the engine
40
  import matplotlib.pyplot as plt
41
 
42
  out_dir = Path(out_dir)
 
47
  # scale: int = 4, color_map: Optional[dict] = None) -> ndarray
48
  # The first `steps` arg is a display counter (ignored inside the function).
49
  rgb = frame_to_rgb_array(0, step.frame, scale, COLOR_MAP)
 
 
 
 
50
  path = out_dir / f"frame_{i:03d}.png"
51
+ fig, ax = plt.subplots(figsize=(4.0, 4.4))
52
+ try:
53
+ ax.imshow(rgb, interpolation="nearest")
54
+ ax.axis("off")
55
+ ax.set_title(_title(step), fontsize=9)
56
+ fig.savefig(path, dpi=100, bbox_inches="tight")
57
+ finally:
58
+ plt.close(fig)
59
  paths.append(path)
60
  return paths
tests/viz/test_png.py CHANGED
@@ -34,3 +34,29 @@ def test_write_pngs_creates_missing_dir(tmp_path):
34
  paths = write_pngs(steps, nested)
35
  assert nested.is_dir()
36
  assert len(paths) == len(steps)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  paths = write_pngs(steps, nested)
35
  assert nested.is_dir()
36
  assert len(paths) == len(steps)
37
+
38
+
39
+ from proteus.viz.png import _title
40
+ from proteus.viz.reconstruct import FrameMeta, FrameStep
41
+ import numpy as np
42
+
43
+
44
+ def _step(meta):
45
+ return FrameStep(np.zeros((2, 2), dtype=int), meta)
46
+
47
+
48
+ def test_title_cut_phase():
49
+ assert _title(_step(FrameMeta(phase="cut", index=1))) == "Cut 1 (pre-roll)"
50
+
51
+
52
+ def test_title_play_congruent_no_terminal():
53
+ meta = FrameMeta(phase="play", index=2, turn_idx=2, action="up", was_congruent=True)
54
+ assert _title(_step(meta)) == "Turn 2: action=up ✓"
55
+
56
+
57
+ def test_title_play_diverged_with_terminal():
58
+ meta = FrameMeta(
59
+ phase="play", index=3, turn_idx=3, action="down",
60
+ was_congruent=False, terminal="survived",
61
+ )
62
+ assert _title(_step(meta)) == "Turn 3: action=down ✗ [survived]"