Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit fdf5fae

Browse files
committed
Merge pull request #98 from mdboom/faster_image_comparison_decorator.
Faster image comparison decorator
2 parents c5c9f69 + bffb15d commit fdf5fae

17 files changed

+882
-181
lines changed

lib/matplotlib/_pylab_helpers.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,12 @@ def destroy_fig(fig):
7878
@staticmethod
7979
def destroy_all():
8080
for manager in Gcf.figs.values():
81-
Gcf.destroy(manager.num)
81+
manager.canvas.mpl_disconnect(manager._cidgcf)
82+
manager.destroy()
8283

84+
Gcf._activeQue = []
85+
Gcf.figs.clear()
86+
gc.collect()
8387

8488
@staticmethod
8589
def has_fignum(num):

lib/matplotlib/testing/compare.py

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,22 @@ def compare_float( expected, actual, relTol = None, absTol = None ):
8383
# convert files with that extension to png format.
8484
converter = { }
8585

86+
def make_external_conversion_command(cmd):
87+
def convert(*args):
88+
cmdline = cmd(*args)
89+
oldname, newname = args
90+
pipe = subprocess.Popen(cmdline, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
91+
stdout, stderr = pipe.communicate()
92+
errcode = pipe.wait()
93+
if not os.path.exists(newname) or errcode:
94+
msg = "Conversion command failed:\n%s\n" % ' '.join(cmd)
95+
if stdout:
96+
msg += "Standard output:\n%s\n" % stdout
97+
if stderr:
98+
msg += "Standard error:\n%s\n" % stderr
99+
raise IOError, msg
100+
return convert
101+
86102
if matplotlib.checkdep_ghostscript() is not None:
87103
# FIXME: make checkdep_ghostscript return the command
88104
if sys.platform == 'win32':
@@ -92,13 +108,13 @@ def compare_float( expected, actual, relTol = None, absTol = None ):
92108
cmd = lambda old, new: \
93109
[gs, '-q', '-sDEVICE=png16m', '-dNOPAUSE', '-dBATCH',
94110
'-sOutputFile=' + new, old]
95-
converter['pdf'] = cmd
96-
converter['eps'] = cmd
111+
converter['pdf'] = make_external_conversion_command(cmd)
112+
converter['eps'] = make_external_conversion_command(cmd)
97113

98114
if matplotlib.checkdep_inkscape() is not None:
99115
cmd = lambda old, new: \
100-
['inkscape', old, '--export-png=' + new]
101-
converter['svg'] = cmd
116+
['inkscape', '-z', old, '--export-png', new]
117+
converter['svg'] = make_external_conversion_command(cmd)
102118

103119
def comparable_formats():
104120
'''Returns the list of file formats that compare_images can compare
@@ -116,17 +132,7 @@ def convert(filename):
116132
newname = base + '_' + extension + '.png'
117133
if not os.path.exists(filename):
118134
raise IOError, "'%s' does not exist" % filename
119-
cmd = converter[extension](filename, newname)
120-
pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
121-
stdout, stderr = pipe.communicate()
122-
errcode = pipe.wait()
123-
if not os.path.exists(newname) or errcode:
124-
msg = "Conversion command failed:\n%s\n" % ' '.join(cmd)
125-
if stdout:
126-
msg += "Standard output:\n%s\n" % stdout
127-
if stderr:
128-
msg += "Standard error:\n%s\n" % stderr
129-
raise IOError, msg
135+
converter[extension](filename, newname)
130136
return newname
131137

132138
verifiers = { }

lib/matplotlib/testing/decorators.py

Lines changed: 105 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
from matplotlib.testing.noseclasses import KnownFailureTest, \
22
KnownFailureDidNotFailTest, ImageComparisonFailure
3-
import os, sys, shutil
3+
import os, sys, shutil, new
44
import nose
55
import matplotlib
66
import matplotlib.tests
7+
import matplotlib.units
8+
from matplotlib import pyplot as plt
79
import numpy as np
810
from matplotlib.testing.compare import comparable_formats, compare_images
911

@@ -46,7 +48,81 @@ def failer(*args, **kwargs):
4648
return nose.tools.make_decorator(f)(failer)
4749
return known_fail_decorator
4850

49-
def image_comparison(baseline_images=None,extensions=None,tol=1e-3):
51+
class CleanupTest:
52+
@classmethod
53+
def setup_class(cls):
54+
cls.original_units_registry = matplotlib.units.registry.copy()
55+
56+
@classmethod
57+
def teardown_class(cls):
58+
plt.close('all')
59+
60+
matplotlib.tests.setup()
61+
62+
matplotlib.units.registry.clear()
63+
matplotlib.units.registry.update(cls.original_units_registry)
64+
65+
def test(self):
66+
self._func()
67+
68+
def cleanup(func):
69+
name = func.__name__
70+
func = staticmethod(func)
71+
func.__get__(1).__name__ = '_private'
72+
new_class = new.classobj(
73+
name,
74+
(CleanupTest,),
75+
{'_func': func})
76+
return new_class
77+
78+
class ImageComparisonTest(CleanupTest):
79+
@classmethod
80+
def setup_class(cls):
81+
CleanupTest.setup_class()
82+
83+
cls._func()
84+
85+
def test(self):
86+
baseline_dir, result_dir = _image_directories(self._func)
87+
88+
for fignum, baseline in zip(plt.get_fignums(), self._baseline_images):
89+
figure = plt.figure(fignum)
90+
91+
for extension in self._extensions:
92+
will_fail = not extension in comparable_formats()
93+
if will_fail:
94+
fail_msg = 'Cannot compare %s files on this system' % extension
95+
else:
96+
fail_msg = 'No failure expected'
97+
98+
orig_expected_fname = os.path.join(baseline_dir, baseline) + '.' + extension
99+
expected_fname = os.path.join(result_dir, 'expected-' + baseline) + '.' + extension
100+
actual_fname = os.path.join(result_dir, baseline) + '.' + extension
101+
if os.path.exists(orig_expected_fname):
102+
shutil.copyfile(orig_expected_fname, expected_fname)
103+
else:
104+
will_fail = True
105+
fail_msg = 'Do not have baseline image %s' % expected_fname
106+
107+
@knownfailureif(
108+
will_fail, fail_msg,
109+
known_exception_class=ImageComparisonFailure)
110+
def do_test():
111+
figure.savefig(actual_fname)
112+
113+
if not os.path.exists(expected_fname):
114+
raise ImageComparisonFailure(
115+
'image does not exist: %s' % expected_fname)
116+
117+
err = compare_images(expected_fname, actual_fname, self._tol, in_decorator=True)
118+
if err:
119+
raise ImageComparisonFailure(
120+
'images not close: %(actual)s vs. %(expected)s '
121+
'(RMS %(rms).3f)'%err)
122+
123+
yield (do_test,)
124+
125+
def image_comparison(baseline_images=None, extensions=None, tol=1e-3):
50126
"""
51127
call signature::
52128
@@ -76,56 +152,29 @@ def image_comparison(baseline_images=None,extensions=None,tol=1e-3):
76152
# default extensions to test
77153
extensions = ['png', 'pdf', 'svg']
78154

79-
# The multiple layers of defs are required because of how
80-
# parameterized decorators work, and because we want to turn the
81-
# single test_foo function to a generator that generates a
82-
# separate test case for each file format.
83155
def compare_images_decorator(func):
84-
baseline_dir, result_dir = _image_directories(func)
85-
86-
def compare_images_generator():
87-
for extension in extensions:
88-
orig_expected_fnames = [os.path.join(baseline_dir,fname) + '.' + extension for fname in baseline_images]
89-
expected_fnames = [os.path.join(result_dir,'expected-'+fname) + '.' + extension for fname in baseline_images]
90-
actual_fnames = [os.path.join(result_dir, fname) + '.' + extension for fname in baseline_images]
91-
have_baseline_images = [os.path.exists(expected) for expected in orig_expected_fnames]
92-
have_baseline_image = np.all(have_baseline_images)
93-
is_comparable = extension in comparable_formats()
94-
if not is_comparable:
95-
fail_msg = 'Cannot compare %s files on this system' % extension
96-
elif not have_baseline_image:
97-
fail_msg = 'Do not have baseline images %s' % expected_fnames
98-
else:
99-
fail_msg = 'No failure expected'
100-
will_fail = not (is_comparable and have_baseline_image)
101-
@knownfailureif(will_fail, fail_msg,
102-
known_exception_class=ImageComparisonFailure )
103-
def decorated_compare_images():
104-
# set the default format of savefig
105-
matplotlib.rc('savefig', extension=extension)
106-
# change to the result directory for the duration of the test
107-
old_dir = os.getcwd()
108-
os.chdir(result_dir)
109-
try:
110-
result = func() # actually call the test function
111-
finally:
112-
os.chdir(old_dir)
113-
for original, expected in zip(orig_expected_fnames, expected_fnames):
114-
if not os.path.exists(original):
115-
raise ImageComparisonFailure(
116-
'image does not exist: %s'%original)
117-
shutil.copyfile(original, expected)
118-
for actual,expected in zip(actual_fnames,expected_fnames):
119-
# compare the images
120-
err = compare_images( expected, actual, tol,
121-
in_decorator=True )
122-
if err:
123-
raise ImageComparisonFailure(
124-
'images not close: %(actual)s vs. %(expected)s '
125-
'(RMS %(rms).3f)'%err)
126-
return result
127-
yield (decorated_compare_images,)
128-
return nose.tools.make_decorator(func)(compare_images_generator)
156+
# We want to run the setup function (the actual test function
157+
# that generates the figure objects) only once for each type
158+
# of output file. The only way to achieve this with nose
159+
# appears to be to create a test class with "setup_class" and
160+
# "teardown_class" methods. Creating a class instance doesn't
161+
# work, so we use new.classobj to actually create a class and
162+
# fill it with the appropriate methods.
163+
name = func.__name__
164+
# For nose 1.0, we need to rename the test function to
165+
# something without the word "test", or it will be run as
166+
# well, outside of the context of our image comparison test
167+
# generator.
168+
func = staticmethod(func)
169+
func.__get__(1).__name__ = '_private'
170+
new_class = new.classobj(
171+
name,
172+
(ImageComparisonTest,),
173+
{'_func': func,
174+
'_baseline_images': baseline_images,
175+
'_extensions': extensions,
176+
'_tol': tol})
177+
return new_class
129178
return compare_images_decorator
130179

131180
def _image_directories(func):
@@ -134,7 +183,7 @@ def _image_directories(func):
134183
Create the result directory if it doesn't exist.
135184
"""
136185
module_name = func.__module__
137-
if module_name=='__main__':
186+
if module_name == '__main__':
138187
# FIXME: this won't work for nested packages in matplotlib.tests
139188
import warnings
140189
warnings.warn('test module run as script. guessing baseline image locations')
@@ -143,13 +192,13 @@ def _image_directories(func):
143192
subdir = os.path.splitext(os.path.split(script_name)[1])[0]
144193
else:
145194
mods = module_name.split('.')
146-
assert mods.pop(0)=='matplotlib'
147-
assert mods.pop(0)=='tests'
195+
assert mods.pop(0) == 'matplotlib'
196+
assert mods.pop(0) == 'tests'
148197
subdir = os.path.join(*mods)
149198
basedir = os.path.dirname(matplotlib.tests.__file__)
150199

151-
baseline_dir = os.path.join(basedir,'baseline_images',subdir)
152-
result_dir = os.path.abspath(os.path.join('result_images',subdir))
200+
baseline_dir = os.path.join(basedir, 'baseline_images', subdir)
201+
result_dir = os.path.abspath(os.path.join('result_images', subdir))
153202

154203
if not os.path.exists(result_dir):
155204
os.makedirs(result_dir)
Binary file not shown.

0 commit comments

Comments
 (0)