chemparseplot 1.9.14__tar.gz → 1.9.16__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 (104) hide show
  1. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/PKG-INFO +1 -1
  2. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/_version.py +2 -2
  3. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/plot/neb.py +43 -30
  4. chemparseplot-1.9.16/tests/plot/test_neb_renderers.py +146 -0
  5. chemparseplot-1.9.14/tests/plot/test_neb_renderers.py +0 -86
  6. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/.gitignore +0 -0
  7. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/LICENSE +0 -0
  8. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/__init__.py +0 -0
  9. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/api.py +0 -0
  10. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/__init__.py +0 -0
  11. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/chemgp_hdf5.py +0 -0
  12. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/chemgp_jsonl.py +0 -0
  13. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/converter.py +0 -0
  14. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/eon/__init__.py +0 -0
  15. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/eon/_trajectory_common.py +0 -0
  16. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/eon/con_io.py +0 -0
  17. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/eon/dimer_trajectory.py +0 -0
  18. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/eon/frame_series.py +0 -0
  19. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/eon/gprd.py +0 -0
  20. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/eon/min_trajectory.py +0 -0
  21. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/eon/minimization.py +0 -0
  22. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/eon/neb.py +0 -0
  23. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/eon/saddle_search.py +0 -0
  24. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/eon/stitch.py +0 -0
  25. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/file_.py +0 -0
  26. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/grammar/__init__.py +0 -0
  27. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/grammar/_deps.py +0 -0
  28. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/grammar/orca_text.py +0 -0
  29. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/grammar/xyz.py +0 -0
  30. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/neb_utils.py +0 -0
  31. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/orca/__init__.py +0 -0
  32. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/orca/_opi.py +0 -0
  33. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/orca/geomscan.py +0 -0
  34. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/orca/neb/__init__.py +0 -0
  35. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/orca/neb/interp.py +0 -0
  36. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/orca/neb/opi_parser.py +0 -0
  37. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/patterns.py +0 -0
  38. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/plumed.py +0 -0
  39. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/projection.py +0 -0
  40. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/sella/saddle_search.py +0 -0
  41. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/trajectory/__init__.py +0 -0
  42. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/trajectory/hdf5.py +0 -0
  43. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/trajectory/neb.py +0 -0
  44. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/types.py +0 -0
  45. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/plot/__init__.py +0 -0
  46. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/plot/chemgp.py +0 -0
  47. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/plot/geomscan.py +0 -0
  48. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/plot/optimization.py +0 -0
  49. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/plot/plumed.py +0 -0
  50. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/plot/structs.py +0 -0
  51. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/plot/theme.py +0 -0
  52. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/units.py +0 -0
  53. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/util.py +0 -0
  54. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/pyproject.toml +0 -0
  55. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/readme.md +0 -0
  56. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/__init__.py +0 -0
  57. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/_optional_imports.py +0 -0
  58. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/conftest.py +0 -0
  59. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/fixtures/h2mol.xyz +0 -0
  60. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/fixtures/orca_energy_coords_snippet.out +0 -0
  61. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/fixtures_geomscan_snippet.txt +0 -0
  62. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/__init__.py +0 -0
  63. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/_con_fixtures.py +0 -0
  64. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/grammar/__init__.py +0 -0
  65. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/grammar/test_grammar_deps.py +0 -0
  66. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/grammar/test_orca_text_grammar.py +0 -0
  67. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/grammar/test_xyz_grammar.py +0 -0
  68. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/orca/test_geomscan.py +0 -0
  69. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/orca/test_interp.py +0 -0
  70. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/test_chemgp_hdf5.py +0 -0
  71. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/test_chemgp_jsonl_types.py +0 -0
  72. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/test_con_io.py +0 -0
  73. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/test_converter.py +0 -0
  74. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/test_dimer_trajectory.py +0 -0
  75. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/test_eon_neb_types.py +0 -0
  76. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/test_eon_trajectory_common.py +0 -0
  77. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/test_frame_series.py +0 -0
  78. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/test_min_trajectory.py +0 -0
  79. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/test_minimization_result.py +0 -0
  80. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/test_neb_stitch.py +0 -0
  81. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/test_neb_utils.py +0 -0
  82. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/test_orca_public_api.py +0 -0
  83. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/test_parser_types.py +0 -0
  84. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/test_patterns.py +0 -0
  85. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/test_plumed.py +0 -0
  86. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/test_projection.py +0 -0
  87. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/test_readcon_boundary.py +0 -0
  88. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/test_saddle_results.py +0 -0
  89. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/test_sella_saddle.py +0 -0
  90. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/test_trajectory_hdf5.py +0 -0
  91. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/test_trajectory_neb.py +0 -0
  92. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/plot/__init__.py +0 -0
  93. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/plot/test_chemgp_utils.py +0 -0
  94. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/plot/test_neb_helpers.py +0 -0
  95. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/plot/test_optimization.py +0 -0
  96. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/plot/test_orca_neb_payload.py +0 -0
  97. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/plot/test_projection_refactor.py +0 -0
  98. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/plot/test_strip_rendering.py +0 -0
  99. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/plot/test_structs_units.py +0 -0
  100. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/plot/test_surface_auto_thin.py +0 -0
  101. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/test_coverage_batch.py +0 -0
  102. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/test_full_coverage.py +0 -0
  103. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/test_public_api.py +0 -0
  104. {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/tutorials/test_chemparseplot.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chemparseplot
3
- Version: 1.9.14
3
+ Version: 1.9.16
4
4
  Summary: Parsers and plotting tools for computational chemistry
5
5
  Project-URL: Documentation, https://chemparseplot.rgoswami.me
6
6
  Project-URL: Issues, https://github.com/HaoZeke/chemparseplot/issues
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '1.9.14'
22
- __version_tuple__ = version_tuple = (1, 9, 14)
21
+ __version__ = version = '1.9.16'
22
+ __version_tuple__ = version_tuple = (1, 9, 16)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -3,7 +3,6 @@ from __future__ import annotations
3
3
  import io
4
4
  import logging
5
5
  import shutil
6
- import subprocess
7
6
  import tempfile
8
7
  from dataclasses import dataclass
9
8
  from pathlib import Path
@@ -198,20 +197,40 @@ def render_structure_to_image(atoms, zoom, rotation): # noqa: ARG001
198
197
  return img_data
199
198
 
200
199
 
200
+ def _import_xyzrender():
201
+ """Import xyzrender via ensure_import (AUTO_DEPS) or a normal import.
202
+
203
+ Prefer the Python API over a PATH binary. Matches the suite lazy-dep model
204
+ (rgpycrumbs ``ensure_import`` / ``RGPYCRUMBS_AUTO_DEPS``).
205
+ """
206
+ try:
207
+ from rgpycrumbs._aux import enable_library_auto_deps, ensure_import
208
+
209
+ enable_library_auto_deps()
210
+ return ensure_import("xyzrender")
211
+ except ImportError:
212
+ pass
213
+ try:
214
+ import xyzrender
215
+ except ImportError as exc:
216
+ msg = (
217
+ "xyzrender is required for strip_renderer='xyzrender'. "
218
+ "Install with: pip install 'xyzrender>=0.1.3' "
219
+ "(or set RGPYCRUMBS_AUTO_DEPS=1 so ensure_import can stage it)"
220
+ )
221
+ raise RuntimeError(msg) from exc
222
+ return xyzrender
223
+
224
+
201
225
  def _check_xyzrender():
202
- """Verify that the ``xyzrender`` binary is on PATH.
226
+ """Verify xyzrender is importable (package or ensure_import cache).
203
227
 
204
228
  Raises
205
229
  ------
206
230
  RuntimeError
207
- If xyzrender is not found, with install instructions.
231
+ If xyzrender cannot be imported, with install instructions.
208
232
  """
209
- if shutil.which("xyzrender") is None:
210
- msg = (
211
- "xyzrender binary not found on PATH. "
212
- "Install with: pip install 'xyzrender>=0.1.3'"
213
- )
214
- raise RuntimeError(msg)
233
+ _import_xyzrender()
215
234
 
216
235
 
217
236
  def _apply_perspective_tilt(atoms, tilt_deg=8.0):
@@ -267,9 +286,8 @@ def _parse_rotation_angles(rotation_str):
267
286
  def _render_xyzrender(atoms, rotation="auto", canvas_size=400, config="paton"):
268
287
  """Render an ASE Atoms object to a numpy RGBA array via xyzrender.
269
288
 
270
- Uses the specified config preset (default: ``paton`` for ball-and-stick).
271
- Other useful presets: ``bubble`` (space-filling, good for surfaces),
272
- ``flat``, ``tube``, ``wire``, ``skeletal``.
289
+ Uses the **Python API** (``xyzrender.render``), staged through
290
+ ``ensure_import`` when ``RGPYCRUMBS_AUTO_DEPS=1`` — not a PATH binary.
273
291
 
274
292
  Parameters
275
293
  ----------
@@ -280,15 +298,18 @@ def _render_xyzrender(atoms, rotation="auto", canvas_size=400, config="paton"):
280
298
  Any ASE-style string (e.g. ``"0x,90y,0z"``) disables auto-orient
281
299
  and pre-rotates the atoms.
282
300
  canvas_size : int
283
- Output image width/height in pixels (passed as ``-S``).
301
+ Output image width/height in pixels.
284
302
 
285
303
  Returns
286
304
  -------
287
305
  numpy.ndarray
288
306
  RGBA image array with shape ``(H, W, 4)`` and float dtype.
289
307
  """
308
+ import os
309
+
290
310
  from ase.io import write as _ase_write
291
311
 
312
+ xr = _import_xyzrender()
292
313
  with tempfile.NamedTemporaryFile(suffix=".xyz", delete=False) as xyz_fh:
293
314
  xyz_path = xyz_fh.name
294
315
  png_path = xyz_path.rsplit(".", 1)[0] + ".png"
@@ -299,31 +320,23 @@ def _render_xyzrender(atoms, rotation="auto", canvas_size=400, config="paton"):
299
320
 
300
321
  if has_custom_rotation:
301
322
  rx, ry, rz = _parse_rotation_angles(rotation)
302
- # Pre-rotate and disable auto-orient
303
323
  atoms.rotate(rx, "x", center="COP")
304
324
  atoms.rotate(ry, "y", center="COP")
305
325
  atoms.rotate(rz, "z", center="COP")
306
326
 
307
327
  _ase_write(xyz_path, atoms, format="xyz")
308
- cmd = [
309
- "xyzrender",
328
+ # Python API: same knobs as CLI --hy -t -S --config
329
+ xr.render(
310
330
  xyz_path,
311
- "-o",
312
- png_path,
313
- "-S",
314
- str(canvas_size),
315
- "--config",
316
- config,
317
- "--hy",
318
- "-t",
319
- ]
320
- if has_custom_rotation:
321
- cmd.append("--no-orient")
322
- subprocess.run(cmd, check=True, capture_output=True) # noqa: S603
331
+ config=config,
332
+ canvas_size=canvas_size,
333
+ hy=True,
334
+ transparent=True,
335
+ orient=not has_custom_rotation,
336
+ output=png_path,
337
+ )
323
338
  img_data = plt.imread(png_path)
324
339
  finally:
325
- import os
326
-
327
340
  for p in (xyz_path, png_path):
328
341
  try:
329
342
  os.unlink(p)
@@ -0,0 +1,146 @@
1
+ # SPDX-FileCopyrightText: 2023-present Rohit Goswami <rog32@hi.is>
2
+ #
3
+ # SPDX-License-Identifier: MIT
4
+ import shutil
5
+ from unittest.mock import patch
6
+
7
+ import numpy as np
8
+ import pytest
9
+
10
+ from tests.conftest import skip_if_not_env
11
+
12
+ skip_if_not_env("neb")
13
+
14
+ import matplotlib.pyplot as plt
15
+ from ase import Atoms
16
+
17
+ from chemparseplot.plot.neb import (
18
+ _check_xyzrender,
19
+ _render_xyzrender,
20
+ plot_structure_strip,
21
+ render_structure_to_image,
22
+ )
23
+
24
+ pytestmark = pytest.mark.neb
25
+
26
+
27
+ @pytest.fixture()
28
+ def water():
29
+ return Atoms(
30
+ "H2O",
31
+ positions=[[0, 0, 0], [0.76, 0.59, 0], [-0.76, 0.59, 0]],
32
+ )
33
+
34
+
35
+ class TestRenderStructureToImage:
36
+ """ASE renderer contract."""
37
+
38
+ def test_returns_rgba(self, water):
39
+ img = render_structure_to_image(water, zoom=0.3, rotation="0x,90y,0z")
40
+ assert isinstance(img, np.ndarray)
41
+ assert img.ndim == 3
42
+ assert img.shape[2] == 4
43
+ assert img.dtype in (np.float32, np.float64)
44
+
45
+
46
+ class TestCheckXyzrender:
47
+ """Package importability (ensure_import / site-packages), not PATH binary."""
48
+
49
+ def test_missing_package_raises(self):
50
+ import builtins
51
+ real_import = builtins.__import__
52
+
53
+ def fake_import(name, *a, **k):
54
+ if name == "xyzrender" or name.startswith("xyzrender."):
55
+ raise ImportError("blocked")
56
+ if name == "rgpycrumbs" or name.startswith("rgpycrumbs."):
57
+ raise ImportError("blocked")
58
+ return real_import(name, *a, **k)
59
+
60
+ with patch.object(builtins, "__import__", side_effect=fake_import):
61
+ with pytest.raises(RuntimeError, match="xyzrender is required"):
62
+ _check_xyzrender()
63
+
64
+ def test_present_package_passes(self):
65
+ pytest.importorskip("xyzrender")
66
+ _check_xyzrender()
67
+
68
+
69
+ class TestRenderXyzrender:
70
+ """xyzrender Python API renderer."""
71
+
72
+ def test_produces_image(self, water):
73
+ pytest.importorskip("xyzrender")
74
+ img = _render_xyzrender(water, canvas_size=200)
75
+ assert isinstance(img, np.ndarray)
76
+ assert img.ndim == 3
77
+ assert img.shape[2] in (3, 4)
78
+
79
+
80
+ class TestPlotStructureStripRendererParam:
81
+ """Renderer dispatch in plot_structure_strip."""
82
+
83
+ def test_ase_renderer_called(self, water):
84
+ _, ax = plt.subplots()
85
+ atoms_list = [water, water]
86
+ labels = ["A", "B"]
87
+
88
+ with patch("chemparseplot.plot.neb.render_structure_to_image") as mock_render:
89
+ mock_render.return_value = np.zeros((10, 10, 4), dtype=np.float32)
90
+ plot_structure_strip(ax, atoms_list, labels, renderer="ase")
91
+ assert mock_render.call_count == len(atoms_list)
92
+
93
+ plt.close("all")
94
+
95
+
96
+ class TestXyzrenderCliApiParity:
97
+ """CLI and Python API must match for the same xyzrender version."""
98
+
99
+ def test_cli_python_api_parity(self, water, tmp_path):
100
+ import importlib.util
101
+ import subprocess
102
+
103
+ pytest.importorskip("xyzrender")
104
+ cli = shutil.which("xyzrender")
105
+ if cli is None:
106
+ pytest.skip("xyzrender console script not on PATH")
107
+ # same package as importable xyzrender
108
+ import xyzrender
109
+
110
+ # write shared geometry
111
+ from ase.io import write as ase_write
112
+
113
+ xyz = tmp_path / "w.xyz"
114
+ png_cli = tmp_path / "cli.png"
115
+ ase_write(str(xyz), water, format="xyz")
116
+ subprocess.run(
117
+ [
118
+ cli,
119
+ str(xyz),
120
+ "-o",
121
+ str(png_cli),
122
+ "-S",
123
+ "200",
124
+ "--config",
125
+ "paton",
126
+ "--hy",
127
+ "-t",
128
+ ],
129
+ check=True,
130
+ capture_output=True,
131
+ )
132
+ img_cli = plt.imread(png_cli)
133
+ img_api = _render_xyzrender(water, canvas_size=200, config="paton")
134
+ a = np.asarray(img_cli, dtype=float)
135
+ b = np.asarray(img_api, dtype=float)
136
+ if a.max() > 1.5:
137
+ a = a / 255.0
138
+ if b.max() > 1.5:
139
+ b = b / 255.0
140
+ if a.shape[-1] == 3:
141
+ a = np.concatenate([a, np.ones(a.shape[:2] + (1,))], axis=-1)
142
+ if b.shape[-1] == 3:
143
+ b = np.concatenate([b, np.ones(b.shape[:2] + (1,))], axis=-1)
144
+ assert a.shape == b.shape, (a.shape, b.shape)
145
+ # bit-identical for same xyzrender version
146
+ np.testing.assert_allclose(a, b, atol=1e-5, rtol=0)
@@ -1,86 +0,0 @@
1
- # SPDX-FileCopyrightText: 2023-present Rohit Goswami <rog32@hi.is>
2
- #
3
- # SPDX-License-Identifier: MIT
4
- import shutil
5
- from unittest.mock import patch
6
-
7
- import numpy as np
8
- import pytest
9
-
10
- from tests.conftest import skip_if_not_env
11
-
12
- skip_if_not_env("neb")
13
-
14
- import matplotlib.pyplot as plt
15
- from ase import Atoms
16
-
17
- from chemparseplot.plot.neb import (
18
- _check_xyzrender,
19
- _render_xyzrender,
20
- plot_structure_strip,
21
- render_structure_to_image,
22
- )
23
-
24
- pytestmark = pytest.mark.neb
25
-
26
-
27
- @pytest.fixture()
28
- def water():
29
- return Atoms(
30
- "H2O",
31
- positions=[[0, 0, 0], [0.76, 0.59, 0], [-0.76, 0.59, 0]],
32
- )
33
-
34
-
35
- class TestRenderStructureToImage:
36
- """ASE renderer contract."""
37
-
38
- def test_returns_rgba(self, water):
39
- img = render_structure_to_image(water, zoom=0.3, rotation="0x,90y,0z")
40
- assert isinstance(img, np.ndarray)
41
- assert img.ndim == 3
42
- assert img.shape[2] == 4
43
- assert img.dtype in (np.float32, np.float64)
44
-
45
-
46
- class TestCheckXyzrender:
47
- """Binary availability check."""
48
-
49
- def test_missing_binary_raises(self):
50
- with patch.object(shutil, "which", return_value=None):
51
- with pytest.raises(RuntimeError, match="xyzrender binary not found"):
52
- _check_xyzrender()
53
-
54
- def test_present_binary_passes(self):
55
- with patch.object(shutil, "which", return_value="/usr/bin/xyzrender"):
56
- _check_xyzrender()
57
-
58
-
59
- class TestRenderXyzrender:
60
- """xyzrender subprocess renderer."""
61
-
62
- @pytest.mark.skipif(
63
- shutil.which("xyzrender") is None,
64
- reason="xyzrender not installed",
65
- )
66
- def test_produces_image(self, water):
67
- img = _render_xyzrender(water, canvas_size=200)
68
- assert isinstance(img, np.ndarray)
69
- assert img.ndim == 3
70
- assert img.shape[2] in (3, 4)
71
-
72
-
73
- class TestPlotStructureStripRendererParam:
74
- """Renderer dispatch in plot_structure_strip."""
75
-
76
- def test_ase_renderer_called(self, water):
77
- _, ax = plt.subplots()
78
- atoms_list = [water, water]
79
- labels = ["A", "B"]
80
-
81
- with patch("chemparseplot.plot.neb.render_structure_to_image") as mock_render:
82
- mock_render.return_value = np.zeros((10, 10, 4), dtype=np.float32)
83
- plot_structure_strip(ax, atoms_list, labels, renderer="ase")
84
- assert mock_render.call_count == len(atoms_list)
85
-
86
- plt.close("all")
File without changes
File without changes