77
88import six
99
10+ import atexit
11+ import functools
1012import hashlib
13+ import itertools
1114import os
1215import shutil
16+ import sys
1317
1418import numpy as np
1519
@@ -128,6 +132,59 @@ def convert(old, new):
128132 return convert
129133
130134
135+ class _SVGConverter (object ):
136+ def __init__ (self ):
137+ self ._proc = None
138+ # We cannot rely on the GC to trigger `__del__` at exit because
139+ # other modules (e.g. `subprocess`) may already have their globals
140+ # set to `None`, which make `proc.communicate` or `proc.terminate`
141+ # fail. By relying on `atexit` we ensure the destructor runs before
142+ # `None`-setting occurs.
143+ atexit .register (self .__del__ )
144+
145+ def _read_to_terminator (self ):
146+ stream = iter (functools .partial (self ._proc .stdout .read , 1 ), b"" )
147+ terminator = (b"\n " , b">" )
148+ n = len (terminator )
149+ its = itertools .tee (stream , n )
150+ for i , it in enumerate (its ):
151+ next (itertools .islice (it , i , i ), None ) # Advance `it` by `i`.
152+ while True :
153+ window = tuple (map (next , its ))
154+ if len (window ) == n :
155+ if window == terminator :
156+ break
157+ else :
158+ continue
159+ else :
160+ # One of the `next(it)` raised StopIteration, so the tuple is
161+ # shorter.
162+ break
163+
164+ def __call__ (self , orig , dest ):
165+ if not self ._proc :
166+ self ._proc = subprocess .Popen ([str ("inkscape" ), "--shell" ],
167+ stdin = subprocess .PIPE ,
168+ stdout = subprocess .PIPE )
169+ self ._read_to_terminator ()
170+ try :
171+ fsencode = os .fsencode
172+ except AttributeError : # Py2.
173+ fsencode = lambda s : s .encode (sys .getfilesystemencoding ())
174+ self ._proc .stdin .write (
175+ fsencode ("{} --export-png={}\n " .format (orig , dest )))
176+ self ._proc .stdin .flush ()
177+ self ._read_to_terminator ()
178+
179+ def __del__ (self ):
180+ if self ._proc :
181+ if self ._proc .poll () is None : # Not exited yet.
182+ self ._proc .communicate (b"quit\n " )
183+ self ._proc .wait ()
184+ self ._proc .stdin .close ()
185+ self ._proc .stdout .close ()
186+
187+
131188def _update_converter ():
132189 gs , gs_v = matplotlib .checkdep_ghostscript ()
133190 if gs_v is not None :
@@ -138,9 +195,7 @@ def cmd(old, new):
138195 converter ['eps' ] = make_external_conversion_command (cmd )
139196
140197 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 )
198+ converter ['svg' ] = _SVGConverter ()
144199
145200
146201#: A dictionary that maps filename extensions to functions which
@@ -363,9 +418,8 @@ def save_diff_image(expected, actual, output):
363418 actual , actualImage , expected , expectedImage )
364419 expectedImage = np .array (expectedImage ).astype (float )
365420 actualImage = np .array (actualImage ).astype (float )
366- assert expectedImage .ndim == actualImage .ndim
367- assert expectedImage .shape == actualImage .shape
368- absDiffImage = abs (expectedImage - actualImage )
421+ assert expectedImage .shape [:2 ] == actualImage .shape [:2 ]
422+ absDiffImage = np .abs (expectedImage [:, :, :3 ] - actualImage [:, :, :3 ])
369423
370424 # expand differences in luminance domain
371425 absDiffImage *= 255 * 10
0 commit comments