File: test_utils.py

package info (click to toggle)
pymol 3.1.0%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 74,084 kB
  • sloc: cpp: 482,660; python: 89,328; ansic: 29,512; javascript: 6,792; sh: 84; makefile: 25
file content (133 lines) | stat: -rw-r--r-- 3,738 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
import tempfile
from PIL import Image
import numpy
from pathlib import Path
import pytest

from pymol import cmd


def datafile(filename: str) -> Path:
    """
    Return path to filename, the current directory and the data
    directory are searched for filename.
    :filename: filename to search for
    """
    if Path(filename).exists():
        return filename
    PYMOL_TESTING_ROOT = 2
    # pymol-testing root
    pymol_test_dir = Path(__file__).parents[PYMOL_TESTING_ROOT]
    return pymol_test_dir.joinpath('data', filename)


class mktemp(object):
    """
    Context manager which returns a temporary filename and
    deletes the file in the end, if it exists.
    """

    def __init__(self, suffix: str = ''):
        self.file = tempfile.NamedTemporaryFile(suffix=suffix, delete=False)
        self.file.close()  # Allow other processes to access the file
        self.filename = self.file.name

    def __enter__(self):
        return self.filename

    def __exit__(self, exc_type, exc_value, traceback):
        if Path(self.filename).exists():
            Path(self.filename).unlink()


def ambientOnly(cmd) -> None:
    """
    Set up a scene with only ambient lighting.
    """
    cmd.set('ambient', 1)
    cmd.set('antialias', 0)
    cmd.set('light_count', 1)
    cmd.set('depth_cue', 0)
    # needed for open-source
    cmd.set('reflect', 0)
    cmd.set('direct', 0)


def get_imagearray(cmd, **kwargs) -> numpy.array:
    """
    Return the image as a numpy array.
    :kwargs: keyword arguments passed to cmd.png
    """

    with mktemp('.png') as filename:
        cmd.png(filename, **kwargs)
        img = Image.open(filename)

        return numpy.array(img.getdata(),
                           numpy.uint8).reshape((img.size[1], img.size[0], -1))


def _imageHasColor(cmd, color, img, delta=0) -> bool:
    """
    Return True if the image contains the given color.
    :color: color to search for
    :img: image as numpy array
    :delta: maximum difference between color and image color
    """
    if isinstance(color, str):
        color = [int(v*255) for v in cmd.get_color_tuple(color)]
    else:
        color = list(color)
    dim = img.shape[-1]
    if dim == len(color) + 1:
        dim -= 1
        img = img[..., :dim]
    diff = abs(img.reshape((-1, dim)) - color)
    return (diff - delta <= 0).prod(1).sum()


def imageHasColor(cmd, color: str, delta: float = 0) -> bool:
    """
    Return True if the image contains the given color.
    :color: color to search for
    :delta: maximum difference between color and image color
    """
    img = get_imagearray(cmd)
    return _imageHasColor(cmd, color, img, delta)


def assert_in_names_undo(cmd, name: str) -> None:
    """
    Assert that the object is in the names list and undoing the
    command removes it from the list.
    :name: name to search for
    """
    assert name in cmd.get_names()
    cmd.undo()
    assert name not in cmd.get_names()
    cmd.redo()
    assert name in cmd.get_names()


def compatible_with(version: str) -> bool:
    def tupleize_version(str_: str):
        return tuple(int(x) for x in str_.split('.') if x.isdigit())

    PYMOL_VERSION = cmd.get_version()
    PYMOL_VERSION_TUPLE = tupleize_version(PYMOL_VERSION[0])

    if isinstance(version, int):
        return version <= PYMOL_VERSION[2]
    elif isinstance(version, float):
        return version <= PYMOL_VERSION[1]
    else:
        return tupleize_version(version) <= PYMOL_VERSION_TUPLE


def requires_version(version, reason=None):
    def decorator(test_func):
        return pytest.mark.skipif(
            not compatible_with(version),
            reason=reason or f"Requires PyMOL {version}"
        )(test_func)
    return decorator