jarvisplot 1.3.8__tar.gz → 1.3.9__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/JarvisPLOT.egg-info/PKG-INFO +2 -1
  2. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/PKG-INFO +2 -1
  3. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/README.md +1 -0
  4. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/Figure/figure.py +104 -4
  5. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/pyproject.toml +1 -1
  6. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/tests/test_figure_contracts.py +51 -0
  7. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/JarvisPLOT.egg-info/SOURCES.txt +0 -0
  8. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/JarvisPLOT.egg-info/dependency_links.txt +0 -0
  9. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/JarvisPLOT.egg-info/entry_points.txt +0 -0
  10. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/JarvisPLOT.egg-info/requires.txt +0 -0
  11. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/JarvisPLOT.egg-info/top_level.txt +0 -0
  12. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/Figure/__init__.py +0 -0
  13. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/Figure/adapters_rect.py +0 -0
  14. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/Figure/adapters_ternary.py +0 -0
  15. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/Figure/cards/std_axes_adapter_config.json +0 -0
  16. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/Figure/colorbar_runtime.py +0 -0
  17. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/Figure/config_runtime.py +0 -0
  18. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/Figure/data_pipelines.py +0 -0
  19. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/Figure/dynesty_runtime.py +0 -0
  20. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/Figure/helper.py +0 -0
  21. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/Figure/interp_natural_neighbor.py +0 -0
  22. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/Figure/interp_natural_neighbor_exact.py +0 -0
  23. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/Figure/layer_runtime.py +0 -0
  24. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/Figure/layout_runtime.py +0 -0
  25. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/Figure/method_registry.py +0 -0
  26. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/Figure/preprocessor.py +0 -0
  27. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/Figure/preprocessor_runtime.py +0 -0
  28. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/Figure/profile_runtime.py +0 -0
  29. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/Figure/style_runtime.py +0 -0
  30. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/__init__.py +0 -0
  31. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/cache_store.py +0 -0
  32. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/cards/a4paper/1x1/ternary.json +0 -0
  33. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/cards/a4paper/2x1/dynesty_runplot.json +0 -0
  34. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/cards/a4paper/2x1/rect.json +0 -0
  35. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/cards/a4paper/2x1/rect5x1.json +0 -0
  36. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/cards/a4paper/2x1/rect_cmap.json +0 -0
  37. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/cards/a4paper/2x1/ternary.json +0 -0
  38. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/cards/a4paper/2x1/ternary_cmap.json +0 -0
  39. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/cards/a4paper/4x1/rect.json +0 -0
  40. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/cards/a4paper/4x1/rect_cmap.json +0 -0
  41. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/cards/a4paper/4x1/ternary.json +0 -0
  42. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/cards/a4paper/4x1/ternary_cmap.json +0 -0
  43. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/cards/args.json +0 -0
  44. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/cards/colors/colormaps.json +0 -0
  45. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/cards/default/output.json +0 -0
  46. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/cards/gambit/1x1/ternary.json +0 -0
  47. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/cards/gambit/2x1/rect_cmap.json +0 -0
  48. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/cards/gambit/2x1/ternary.json +0 -0
  49. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/cards/gambit/2x1/ternary_cmap.json +0 -0
  50. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/cards/icons/JarvisHEP.png +0 -0
  51. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/cards/icons/gambit.png +0 -0
  52. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/cards/icons/gambit_small.png +0 -0
  53. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/cards/style_preference.json +0 -0
  54. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/cli.py +0 -0
  55. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/client.py +0 -0
  56. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/config.py +0 -0
  57. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/core.py +0 -0
  58. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/core_assets.py +0 -0
  59. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/core_runtime.py +0 -0
  60. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/data_loader.py +0 -0
  61. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/data_loader_hdf5.py +0 -0
  62. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/data_loader_runtime.py +0 -0
  63. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/data_loader_summary.py +0 -0
  64. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/inner_func.py +0 -0
  65. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/memtrace.py +0 -0
  66. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/utils/__init__.py +0 -0
  67. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/utils/cmaps.py +0 -0
  68. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/utils/dataframes.py +0 -0
  69. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/utils/expression.py +0 -0
  70. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/utils/interpolator.py +0 -0
  71. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/jarvisplot/utils/pathing.py +0 -0
  72. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/setup.cfg +0 -0
  73. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/tests/test_data_loader_contracts.py +0 -0
  74. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/tests/test_docs_status_labels.py +0 -0
  75. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/tests/test_interp_natural_neighbor.py +0 -0
  76. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/tests/test_interpolator_contracts.py +0 -0
  77. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/tests/test_jpcontour_methods.py +0 -0
  78. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/tests/test_parse_data_metadata.py +0 -0
  79. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/tests/test_pathing_contracts.py +0 -0
  80. {jarvisplot-1.3.8 → jarvisplot-1.3.9}/tests/test_template_json.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: JarvisPLOT
3
- Version: 1.3.8
3
+ Version: 1.3.9
4
4
  Summary: Jarvis-PLOT: YAML-driven plotting engine
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -117,6 +117,7 @@ and evidence summary behavior.
117
117
 
118
118
  - Figures are saved automatically to the output paths defined in the YAML configuration.
119
119
  - Common output formats include PNG and PDF (backend-dependent).
120
+ - Saved figures include file metadata such as `Creator: Jarvis-PLOT, powered by Jarvis-HEP` and `Jarvis-PLOT version: X.Y.Z`; the PNG `Description` also includes both fields for macOS Finder compatibility.
120
121
  - Jarvis-PLOT works in headless environments (SSH, batch jobs) without any GUI backend.
121
122
 
122
123
  ---
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: JarvisPLOT
3
- Version: 1.3.8
3
+ Version: 1.3.9
4
4
  Summary: Jarvis-PLOT: YAML-driven plotting engine
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -117,6 +117,7 @@ and evidence summary behavior.
117
117
 
118
118
  - Figures are saved automatically to the output paths defined in the YAML configuration.
119
119
  - Common output formats include PNG and PDF (backend-dependent).
120
+ - Saved figures include file metadata such as `Creator: Jarvis-PLOT, powered by Jarvis-HEP` and `Jarvis-PLOT version: X.Y.Z`; the PNG `Description` also includes both fields for macOS Finder compatibility.
120
121
  - Jarvis-PLOT works in headless environments (SSH, batch jobs) without any GUI backend.
121
122
 
122
123
  ---
@@ -97,6 +97,7 @@ and evidence summary behavior.
97
97
 
98
98
  - Figures are saved automatically to the output paths defined in the YAML configuration.
99
99
  - Common output formats include PNG and PDF (backend-dependent).
100
+ - Saved figures include file metadata such as `Creator: Jarvis-PLOT, powered by Jarvis-HEP` and `Jarvis-PLOT version: X.Y.Z`; the PNG `Description` also includes both fields for macOS Finder compatibility.
100
101
  - Jarvis-PLOT works in headless environments (SSH, batch jobs) without any GUI backend.
101
102
 
102
103
  ---
@@ -47,6 +47,97 @@ except Exception:
47
47
  pl = None
48
48
 
49
49
 
50
+ _JPLOT_VERSION_CACHE = None
51
+
52
+
53
+ def _resolve_jplot_version() -> str:
54
+ """Return the source/package version used for figure metadata."""
55
+ global _JPLOT_VERSION_CACHE
56
+ if _JPLOT_VERSION_CACHE is not None:
57
+ return _JPLOT_VERSION_CACHE
58
+
59
+ pyproject = os.path.abspath(
60
+ os.path.join(os.path.dirname(__file__), "..", "..", "pyproject.toml")
61
+ )
62
+ try:
63
+ with open(pyproject, "r", encoding="utf-8") as handle:
64
+ in_project = False
65
+ for line in handle:
66
+ stripped = line.strip()
67
+ if stripped == "[project]":
68
+ in_project = True
69
+ continue
70
+ if in_project and stripped.startswith("["):
71
+ break
72
+ if in_project and stripped.startswith("version"):
73
+ _JPLOT_VERSION_CACHE = stripped.split("=", 1)[1].strip().strip('"').strip("'")
74
+ return _JPLOT_VERSION_CACHE
75
+ except Exception:
76
+ pass
77
+
78
+ try:
79
+ from importlib.metadata import version as pkg_version
80
+
81
+ for dist_name in ("Jarvis-PLOT", "JarvisPLOT", "jarvisplot"):
82
+ try:
83
+ _JPLOT_VERSION_CACHE = pkg_version(dist_name)
84
+ return _JPLOT_VERSION_CACHE
85
+ except Exception:
86
+ continue
87
+ except Exception:
88
+ pass
89
+
90
+ _JPLOT_VERSION_CACHE = "0.0.0"
91
+ return _JPLOT_VERSION_CACHE
92
+
93
+
94
+ def _savefig_metadata(fmt: str, extra=None) -> dict:
95
+ """Build backend-friendly figure metadata for savefig."""
96
+ version = _resolve_jplot_version()
97
+ creator = "Jarvis-PLOT, powered by Jarvis-HEP"
98
+ version_text = f"Jarvis-PLOT version: {version}"
99
+ provenance_text = f"{creator}; {version_text}"
100
+ fmt_key = str(fmt or "").lower().lstrip(".")
101
+ metadata = {
102
+ "Creator": creator,
103
+ "Title": "Jarvis-PLOT figure",
104
+ "Author": "Jarvis-PLOT",
105
+ }
106
+
107
+ if fmt_key in {"pdf", "ps", "eps"}:
108
+ metadata["Subject"] = provenance_text
109
+ metadata["Keywords"] = f"Jarvis-PLOT; Jarvis-HEP; {version_text}"
110
+ else:
111
+ metadata["Software"] = "Jarvis-PLOT"
112
+ metadata["Description"] = provenance_text
113
+ metadata["Comment"] = provenance_text
114
+ metadata["Jarvis-PLOT version"] = version
115
+
116
+ if isinstance(extra, dict):
117
+ metadata.update(extra)
118
+ return metadata
119
+
120
+
121
+ def _write_png_metadata(path: str, metadata: dict) -> None:
122
+ try:
123
+ from PIL import Image, PngImagePlugin
124
+ except Exception:
125
+ return
126
+ try:
127
+ image = Image.open(path)
128
+ pnginfo = PngImagePlugin.PngInfo()
129
+ for key, value in (metadata or {}).items():
130
+ if value is None:
131
+ continue
132
+ pnginfo.add_text(str(key), str(value))
133
+ save_kwargs = {"pnginfo": pnginfo}
134
+ if "dpi" in image.info:
135
+ save_kwargs["dpi"] = image.info["dpi"]
136
+ image.save(path, **save_kwargs)
137
+ except Exception:
138
+ return
139
+
140
+
50
141
  class Figure:
51
142
  def _is_numbered_ax(self, name: str) -> bool:
52
143
  """Return True iff name matches ax<NUMBER>, e.g. ax1, ax2, ax10."""
@@ -99,6 +190,7 @@ class Figure:
99
190
  self._logger = None
100
191
  self._frame = {}
101
192
  self._outinfo = {}
193
+ self._output_metadata = {}
102
194
  self._yaml_dir = None # directory of the active YAML file (used to resolve relative paths)
103
195
  self.axes = {}
104
196
  self.debug = False
@@ -134,11 +226,13 @@ class Figure:
134
226
 
135
227
  @config.setter
136
228
  def config(self, infos):
137
- self.dir = self.load_path(infos['output'].get("dir", "."), base_dir=self._yaml_dir)
229
+ output = infos.get("output", {})
230
+ self.dir = self.load_path(output.get("dir", "."), base_dir=self._yaml_dir)
138
231
  if not os.path.exists(self.dir):
139
232
  os.makedirs(self.dir)
140
- self.fmts = infos['output'].get("formats", ['png'])
141
- self.dpi = infos['output'].get('dpi', 600)
233
+ self.fmts = output.get("formats", ["png"])
234
+ self.dpi = output.get("dpi", 600)
235
+ self._output_metadata = output.get("metadata", {}) if isinstance(output.get("metadata", {}), dict) else {}
142
236
 
143
237
  @property
144
238
  def jpstyles(self) -> Optional[dict]:
@@ -835,7 +929,13 @@ class Figure:
835
929
  )
836
930
  except Exception:
837
931
  pass
838
- self.fig.savefig(spf, dpi=self.dpi)
932
+ metadata = _savefig_metadata(fmt, self._output_metadata)
933
+ try:
934
+ self.fig.savefig(spf, dpi=self.dpi, metadata=metadata)
935
+ except (TypeError, ValueError):
936
+ self.fig.savefig(spf, dpi=self.dpi)
937
+ if str(fmt).lower().lstrip(".") == "png":
938
+ _write_png_metadata(spf, metadata)
839
939
 
840
940
  def load_axes(self):
841
941
  for ax, kws in self.frame['axes'].items():
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "JarvisPLOT"
7
- version = "1.3.8"
7
+ version = "1.3.9"
8
8
  description = "Jarvis-PLOT: YAML-driven plotting engine"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -84,6 +84,57 @@ def test_style_bundle_can_provide_default_layers(monkeypatch):
84
84
  assert fig._render_queue[0][1]["method"] == "dynesty_runplot"
85
85
 
86
86
 
87
+ def test_savefig_writes_creator_and_version_metadata(tmp_path):
88
+ calls = []
89
+
90
+ class DummyFigure:
91
+ def savefig(self, path, *, dpi=None, metadata=None):
92
+ calls.append((path, dpi, metadata))
93
+
94
+ fig = Figure()
95
+ fig.logger = _logger()
96
+ fig.name = "plot"
97
+ fig.dir = str(tmp_path)
98
+ fig.fmts = ["png", "pdf"]
99
+ fig.dpi = 123
100
+ fig.fig = DummyFigure()
101
+ fig._output_metadata = {}
102
+
103
+ fig.savefig()
104
+
105
+ assert calls[0][2]["Creator"] == "Jarvis-PLOT, powered by Jarvis-HEP"
106
+ assert calls[0][2]["Description"].startswith("Jarvis-PLOT, powered by Jarvis-HEP; Jarvis-PLOT version: ")
107
+ assert calls[0][2]["Comment"] == calls[0][2]["Description"]
108
+ assert calls[0][2]["Jarvis-PLOT version"]
109
+ assert calls[1][2]["Creator"] == "Jarvis-PLOT, powered by Jarvis-HEP"
110
+ assert calls[1][2]["Subject"].startswith("Jarvis-PLOT, powered by Jarvis-HEP; Jarvis-PLOT version: ")
111
+
112
+
113
+ def test_savefig_png_contains_metadata_text_chunks(tmp_path):
114
+ from PIL import Image
115
+
116
+ raw_fig, raw_ax = plt.subplots()
117
+ raw_ax.plot([0.0, 1.0], [0.0, 1.0])
118
+
119
+ fig = Figure()
120
+ fig.logger = _logger()
121
+ fig.name = "plot"
122
+ fig.dir = str(tmp_path)
123
+ fig.fmts = ["png"]
124
+ fig.dpi = 80
125
+ fig.fig = raw_fig
126
+ fig._output_metadata = {}
127
+
128
+ fig.savefig()
129
+ plt.close(raw_fig)
130
+
131
+ info = Image.open(tmp_path / "plot.png").info
132
+ assert info["Creator"] == "Jarvis-PLOT, powered by Jarvis-HEP"
133
+ assert info["Description"].startswith("Jarvis-PLOT, powered by Jarvis-HEP; Jarvis-PLOT version: ")
134
+ assert info["Comment"] == info["Description"]
135
+ assert info["Jarvis-PLOT version"]
136
+
137
+
87
138
  def test_console_record_formatter_escapes_braces():
88
139
  class _Time:
89
140
  def __format__(self, spec):
File without changes
File without changes