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.
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/PKG-INFO +1 -1
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/_version.py +2 -2
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/plot/neb.py +43 -30
- chemparseplot-1.9.16/tests/plot/test_neb_renderers.py +146 -0
- chemparseplot-1.9.14/tests/plot/test_neb_renderers.py +0 -86
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/.gitignore +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/LICENSE +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/__init__.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/api.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/__init__.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/chemgp_hdf5.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/chemgp_jsonl.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/converter.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/eon/__init__.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/eon/_trajectory_common.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/eon/con_io.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/eon/dimer_trajectory.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/eon/frame_series.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/eon/gprd.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/eon/min_trajectory.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/eon/minimization.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/eon/neb.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/eon/saddle_search.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/eon/stitch.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/file_.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/grammar/__init__.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/grammar/_deps.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/grammar/orca_text.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/grammar/xyz.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/neb_utils.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/orca/__init__.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/orca/_opi.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/orca/geomscan.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/orca/neb/__init__.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/orca/neb/interp.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/orca/neb/opi_parser.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/patterns.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/plumed.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/projection.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/sella/saddle_search.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/trajectory/__init__.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/trajectory/hdf5.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/trajectory/neb.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/parse/types.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/plot/__init__.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/plot/chemgp.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/plot/geomscan.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/plot/optimization.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/plot/plumed.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/plot/structs.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/plot/theme.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/units.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/chemparseplot/util.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/pyproject.toml +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/readme.md +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/__init__.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/_optional_imports.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/conftest.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/fixtures/h2mol.xyz +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/fixtures/orca_energy_coords_snippet.out +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/fixtures_geomscan_snippet.txt +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/__init__.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/_con_fixtures.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/grammar/__init__.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/grammar/test_grammar_deps.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/grammar/test_orca_text_grammar.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/grammar/test_xyz_grammar.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/orca/test_geomscan.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/orca/test_interp.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/test_chemgp_hdf5.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/test_chemgp_jsonl_types.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/test_con_io.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/test_converter.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/test_dimer_trajectory.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/test_eon_neb_types.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/test_eon_trajectory_common.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/test_frame_series.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/test_min_trajectory.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/test_minimization_result.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/test_neb_stitch.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/test_neb_utils.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/test_orca_public_api.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/test_parser_types.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/test_patterns.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/test_plumed.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/test_projection.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/test_readcon_boundary.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/test_saddle_results.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/test_sella_saddle.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/test_trajectory_hdf5.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/parse/test_trajectory_neb.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/plot/__init__.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/plot/test_chemgp_utils.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/plot/test_neb_helpers.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/plot/test_optimization.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/plot/test_orca_neb_payload.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/plot/test_projection_refactor.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/plot/test_strip_rendering.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/plot/test_structs_units.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/plot/test_surface_auto_thin.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/test_coverage_batch.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/test_full_coverage.py +0 -0
- {chemparseplot-1.9.14 → chemparseplot-1.9.16}/tests/test_public_api.py +0 -0
- {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.
|
|
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.
|
|
22
|
-
__version_tuple__ = version_tuple = (1, 9,
|
|
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
|
|
226
|
+
"""Verify xyzrender is importable (package or ensure_import cache).
|
|
203
227
|
|
|
204
228
|
Raises
|
|
205
229
|
------
|
|
206
230
|
RuntimeError
|
|
207
|
-
If xyzrender
|
|
231
|
+
If xyzrender cannot be imported, with install instructions.
|
|
208
232
|
"""
|
|
209
|
-
|
|
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
|
|
271
|
-
|
|
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
|
|
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
|
-
|
|
309
|
-
|
|
328
|
+
# Python API: same knobs as CLI --hy -t -S --config
|
|
329
|
+
xr.render(
|
|
310
330
|
xyz_path,
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|