diff --git a/lib/matplotlib/testing/compare.py b/lib/matplotlib/testing/compare.py index 5769cb7123c2..3c2c1c7209ec 100644 --- a/lib/matplotlib/testing/compare.py +++ b/lib/matplotlib/testing/compare.py @@ -260,7 +260,16 @@ def calculate_rms(expectedImage, actualImage): return rms -def compare_images(expected, actual, tol, in_decorator=False): +def count_large_differences(expectedImage, actualImage, threshold): + """ + Returns the number of pixel which differ by greater than `threshold` + """ + abs_diff_image = abs(expectedImage - actualImage) + return np.sum(abs_diff_image > threshold) + + +def compare_images(expected, actual, tol, in_decorator=False, + excursion_threshold=None, max_excursion_count=None): """ Compare two "image" files checking differences within a tolerance. @@ -324,17 +333,25 @@ def compare_images(expected, actual, tol, in_decorator=False): actualImage = actualImage.astype(np.int16) rms = calculate_rms(expectedImage, actualImage) + excursion_pass = True # default to true for back + results = {} + if excursion_threshold is not None and max_excursion_count is not None: + excursion_count = count_large_differences(expectedImage, + actualImage, + excursion_threshold) + excursion_pass = excursion_count < excursion_threshold + results['excursion_count'] = excursion_count diff_image = make_test_filename(actual, 'failed-diff') - - if rms <= tol: + if rms <= tol and excursion_pass: if os.path.exists(diff_image): os.unlink(diff_image) + return None save_diff_image(expected, actual, diff_image) - results = dict(rms=rms, expected=str(expected), + results.update(rms=rms, expected=str(expected), actual=str(actual), diff=str(diff_image)) if not in_decorator: diff --git a/lib/matplotlib/testing/decorators.py b/lib/matplotlib/testing/decorators.py index 655fba1b9987..be92d2078a9b 100644 --- a/lib/matplotlib/testing/decorators.py +++ b/lib/matplotlib/testing/decorators.py @@ -176,7 +176,9 @@ def do_test(): figure.savefig(actual_fname, **self._savefig_kwarg) err = compare_images(expected_fname, actual_fname, - self._tol, in_decorator=True) + self._tol, in_decorator=True, + excursion_threshold=self._excursion_threshold, + max_excursion_count=self._max_excursion_count) try: if not os.path.exists(expected_fname): @@ -186,7 +188,7 @@ def do_test(): if err: raise ImageComparisonFailure( 'images not close: %(actual)s vs. %(expected)s ' - '(RMS %(rms).3f)'%err) + '(RMS %(rms).3f) (excursions %(excursion_count)d)'%err) except ImageComparisonFailure: if not check_freetype_version(self._freetype_version): raise KnownFailureTest( @@ -198,7 +200,8 @@ def do_test(): def image_comparison(baseline_images=None, extensions=None, tol=13, freetype_version=None, remove_text=False, - savefig_kwarg=None): + savefig_kwarg=None, excursion_threshold=128, + max_excursion_count=100): """ call signature:: @@ -272,7 +275,9 @@ def compare_images_decorator(func): '_tol': tol, '_freetype_version': freetype_version, '_remove_text': remove_text, - '_savefig_kwarg': savefig_kwarg}) + '_savefig_kwarg': savefig_kwarg, + '_max_excursion_count': max_excursion_count, + '_excursion_threshold': excursion_threshold}) return new_class return compare_images_decorator