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 9276eea

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

File tree

Expand file treeCollapse file tree

1 file changed

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

1 file changed

+78
-5
lines changed

‎lib/matplotlib/testing/compare.py

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

88
import six
99

10+
import atexit
11+
import functools
1012
import hashlib
13+
import itertools
1114
import os
1215
import shutil
16+
import sys
17+
try:
18+
from shlex import quote as _shlex_quote
19+
except ImportError:
20+
from pipes import quote as _shlex_quote
1321

1422
import numpy as np
1523

@@ -128,6 +136,74 @@ def convert(old, new):
128136
return convert
129137

130138

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

140216
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)
217+
converter['svg'] = _SVGConverter()
144218

145219

146220
#: A dictionary that maps filename extensions to functions which
@@ -363,9 +437,8 @@ def save_diff_image(expected, actual, output):
363437
actual, actualImage, expected, expectedImage)
364438
expectedImage = np.array(expectedImage).astype(float)
365439
actualImage = np.array(actualImage).astype(float)
366-
assert expectedImage.ndim == actualImage.ndim
367440
assert expectedImage.shape == actualImage.shape
368-
absDiffImage = abs(expectedImage - actualImage)
441+
absDiffImage = np.abs(expectedImage - actualImage)
369442

370443
# expand differences in luminance domain
371444
absDiffImage *= 255 * 10

0 commit comments

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