Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 2a25286

Browse filesBrowse files
committed
Inkscape shell mode.
1 parent 8a77cfb commit 2a25286
Copy full SHA for 2a25286

File tree

Expand file treeCollapse file tree

1 file changed

+68
-5
lines changed
Filter options
Expand file treeCollapse file tree

1 file changed

+68
-5
lines changed

‎lib/matplotlib/testing/compare.py

Copy file name to clipboardExpand all lines: lib/matplotlib/testing/compare.py
+68-5Lines changed: 68 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,14 @@
77

88
import six
99

10+
import atexit
11+
import functools
1012
import hashlib
13+
import itertools
1114
import os
15+
import shlex
1216
import shutil
17+
import sys
1318

1419
import numpy as np
1520

@@ -128,6 +133,67 @@ def convert(old, new):
128133
return convert
129134

130135

136+
class _SVGConverter(object):
137+
def __init__(self):
138+
self._proc = None
139+
# We cannot rely on the GC to trigger `__del__` at exit because
140+
# other modules (e.g. `subprocess`) may already have their globals
141+
# set to `None`, which make `proc.communicate` or `proc.terminate`
142+
# fail. By relying on `atexit` we ensure the destructor runs before
143+
# `None`-setting occurs.
144+
atexit.register(self.__del__)
145+
146+
def _read_to_terminator(self):
147+
stream = iter(functools.partial(self._proc.stdout.read, 1), "")
148+
terminator = tuple("\n>")
149+
n = len(terminator)
150+
its = itertools.tee(stream, n)
151+
for i, it in enumerate(its):
152+
next(itertools.islice(it, i, i), None) # Advance `it` by `i`.
153+
while True:
154+
window = tuple(map(next, its))
155+
if (len(window) != n or window == terminator):
156+
# First case is to running out of data -- one of the `next(it)`
157+
# raises StopIteration, so the tuple is shorter.
158+
# Second case is to successful reading until terminator.
159+
break
160+
161+
def __call__(self, orig, dest):
162+
if (not self._proc # First run.
163+
or self._proc.poll()): # Inkscape terminated, e.g. crashed.
164+
env = os.environ.copy()
165+
# If one passes e.g. a png file to Inkscape, it will try to
166+
# query the user for conversion options via a GUI (even with
167+
# `--without-gui`). Unsetting `DISPLAY` prevents this (and causes
168+
# GTK to crash and Inkscape to terminate, but that'll just be
169+
# reported as a regular exception below).
170+
env["DISPLAY"] = ""
171+
# Do not load any user options.
172+
env["INKSCAPE_PROFILE_DIR"] = os.devnull
173+
self._proc = subprocess.Popen(
174+
[str("inkscape"), "--without-gui", "--shell"],
175+
env=env, stdin=subprocess.PIPE,
176+
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
177+
universal_newlines=True)
178+
self._read_to_terminator()
179+
# Inkscape uses glib's `g_shell_parse_argv`, which has a consistent
180+
# behavior across platforms.
181+
self._proc.stdin.write(
182+
"{} --export-png={}\n".format(*map(shlex.quote, [orig, dest])))
183+
self._proc.stdin.flush()
184+
self._read_to_terminator() # Ensure that the conversion is done.
185+
if self._proc.poll(): # Inkscape exits on error.
186+
raise ImageComparisonFailure(self._proc.stderr.read())
187+
188+
def __del__(self):
189+
if self._proc:
190+
if self._proc.poll() is None: # Not exited yet.
191+
self._proc.communicate("quit\n")
192+
self._proc.wait()
193+
self._proc.stdin.close()
194+
self._proc.stdout.close()
195+
196+
131197
def _update_converter():
132198
gs, gs_v = matplotlib.checkdep_ghostscript()
133199
if gs_v is not None:
@@ -138,9 +204,7 @@ def cmd(old, new):
138204
converter['eps'] = make_external_conversion_command(cmd)
139205

140206
if matplotlib.checkdep_inkscape() is not None:
141-
def cmd(old, new):
142-
return [str('inkscape'), '-z', old, '--export-png', new]
143-
converter['svg'] = make_external_conversion_command(cmd)
207+
converter['svg'] = _SVGConverter()
144208

145209

146210
#: A dictionary that maps filename extensions to functions which
@@ -363,9 +427,8 @@ def save_diff_image(expected, actual, output):
363427
actual, actualImage, expected, expectedImage)
364428
expectedImage = np.array(expectedImage).astype(float)
365429
actualImage = np.array(actualImage).astype(float)
366-
assert expectedImage.ndim == actualImage.ndim
367430
assert expectedImage.shape == actualImage.shape
368-
absDiffImage = abs(expectedImage - actualImage)
431+
absDiffImage = np.abs(expectedImage - actualImage)
369432

370433
# expand differences in luminance domain
371434
absDiffImage *= 255 * 10

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.