Example #1
0
def yb_compare_images(expected, actual, tol):
    """ Compares a baseline image and test generated actual image
    using a matplotlib's built-in imagine comparison function

    expected : string, imagepath
        The image filepath to the baseline image

    actual : string, imagepath
        The image filepath to the actual test generated image

    tol : float
        The tolerance (a color value difference, where 255 is the
        maximal difference).  The test fails if the average pixel
        difference is greater than this value.
    """
    __tracebackhide__ = True

    if not os.path.exists(expected):
        raise ImageComparisonFailure('image does not exist: %s' % expected)

    # method from matplotlib.testing.compare
    err = compare_images(expected, actual, tol, in_decorator=True)

    if err:
        raise ImageComparisonFailure(
            'images not close (RMS %(rms).3f):\n\t%(actual)s\n\t%(expected)s '
            % err)
Example #2
0
                def do_test():
                    figure = plt.figure(fignum)

                    if self._remove_text:
                        self.remove_text(figure)

                    figure.savefig(actual_fname, **self._savefig_kwarg)

                    plt.close(figure)

                    err = compare_images(expected_fname,
                                         actual_fname,
                                         self._tol,
                                         in_decorator=True)

                    try:
                        if not os.path.exists(expected_fname):
                            raise ImageComparisonFailure(
                                'image does not exist: %s' % expected_fname)

                        if err:
                            raise ImageComparisonFailure(
                                'images not close: %(actual)s vs. %(expected)s'
                                ' (RMS %(rms).3f)' % err)
                    except ImageComparisonFailure:
                        if not check_freetype_version(self._freetype_version):
                            raise KnownFailure(
                                "Mismatched version of freetype.  Test "
                                "requires '%s', you have '%s'" %
                                (self._freetype_version,
                                 ft2font.__freetype_version__))
                        raise
Example #3
0
def _compare_animation(anim, expected, format_, nframes, tol):
    # generate images from the animation
    base_dir, filename = split(join('tests', 'baseline_images', expected))
    out_dir = split(join('tests', 'output_images', expected))[0]

    if not os.path.exists(out_dir):
        os.makedirs(out_dir)

    anim.save(os.path.join(out_dir, (filename + format_)),
              writer=BunchOFiles())

    for i in range(nframes):
        image_name = '%s%d%s' % (filename, i, format_)
        expected_name = os.path.join(base_dir, image_name)
        actual_name = os.path.join(out_dir, image_name)

        err = compare_images(expected_name,
                             actual_name,
                             tol,
                             in_decorator=True)

        if not os.path.exists(expected_name):
            raise ImageComparisonFailure('image does not exist: %s' %
                                         expected_name)

        if err:
            for key in ["actual", "expected"]:
                err[key] = os.path.relpath(err[key])
            raise ImageComparisonFailure(
                'images not close (RMS %(rms).3f):\n\t%(actual)s\n\t%(expected)s '
                % err)
Example #4
0
def save_diff_image(expected, actual, output):
    expectedImage = _png.read_png(expected)
    actualImage = _png.read_png(actual)
    actualImage, expectedImage = crop_to_same(
        actual, actualImage, expected, expectedImage)
    expectedImage = np.array(expectedImage).astype(float)
    actualImage = np.array(actualImage).astype(float)
    if expectedImage.shape != actualImage.shape:
        raise ImageComparisonFailure(
            "Image sizes do not match expected size: {0} "
            "actual size {1}".format(expectedImage.shape, actualImage.shape))
    absDiffImage = np.abs(expectedImage - actualImage)

    # expand differences in luminance domain
    absDiffImage *= 255 * 10
    save_image_np = np.clip(absDiffImage, 0, 255).astype(np.uint8)
    height, width, depth = save_image_np.shape

    # The PDF renderer doesn't produce an alpha channel, but the
    # matplotlib PNG writer requires one, so expand the array
    if depth == 3:
        with_alpha = np.empty((height, width, 4), dtype=np.uint8)
        with_alpha[:, :, 0:3] = save_image_np
        save_image_np = with_alpha

    # Hard-code the alpha channel to fully solid
    save_image_np[:, :, 3] = 255

    _png.write_png(save_image_np, output)
Example #5
0
    def __call__(self, orig, dest):
        if not self._proc:
            self._stdout = TemporaryFile()
            self._proc = subprocess.Popen(
                [
                    mpl._get_executable_info("gs").executable, "-dNOPAUSE",
                    "-sDEVICE=png16m"
                ],
                # As far as I can see, ghostscript never outputs to stderr.
                stdin=subprocess.PIPE,
                stdout=subprocess.PIPE)
            try:
                self._read_until(b"\nGS")
            except _ConverterError:
                raise OSError("Failed to start Ghostscript")

        def encode_and_escape(name):
            return (os.fsencode(name).replace(b"\\", b"\\\\").replace(
                b"(", br"\(").replace(b")", br"\)"))

        self._proc.stdin.write(b"<< /OutputFile (" + encode_and_escape(dest) +
                               b") >> setpagedevice (" +
                               encode_and_escape(orig) + b") run flush\n")
        self._proc.stdin.flush()
        # GS> if nothing left on the stack; GS<n> if n items left on the stack.
        err = self._read_until(b"GS")
        stack = self._read_until(b">")
        if stack or not os.path.exists(dest):
            stack_size = int(stack[1:]) if stack else 0
            self._proc.stdin.write(b"pop\n" * stack_size)
            # Using the systemencoding should at least get the filenames right.
            raise ImageComparisonFailure((err + b"GS" + stack + b">").decode(
                sys.getfilesystemencoding(), "replace"))
Example #6
0
    def __call__(self, orig, dest):
        if (not self._proc  # First run.
                or self._proc.poll() is not None):  # Inkscape terminated.
            env = os.environ.copy()
            # If one passes e.g. a png file to Inkscape, it will try to
            # query the user for conversion options via a GUI (even with
            # `--without-gui`).  Unsetting `DISPLAY` prevents this (and causes
            # GTK to crash and Inkscape to terminate, but that'll just be
            # reported as a regular exception below).
            env.pop("DISPLAY", None)  # May already be unset.
            # Do not load any user options.
            env["INKSCAPE_PROFILE_DIR"] = os.devnull
            # Old versions of Inkscape (0.48.3.1, used on Travis as of now)
            # seem to sometimes deadlock when stderr is redirected to a pipe,
            # so we redirect it to a temporary file instead.  This is not
            # necessary anymore as of Inkscape 0.92.1.
            stderr = TemporaryFile()
            self._proc = subprocess.Popen(
                ["inkscape", "--without-gui", "--shell"],
                stdin=subprocess.PIPE,
                stdout=subprocess.PIPE,
                stderr=stderr,
                env=env)
            # Slight abuse, but makes shutdown handling easier.
            self._proc.stderr = stderr
            try:
                self._read_until(b"\n>")
            except _ConverterError as err:
                raise OSError("Failed to start Inkscape in interactive "
                              "mode") from err

        # Inkscape uses glib's `g_shell_parse_argv`, which has a consistent
        # behavior across platforms, so we can just use `shlex.quote`.
        orig_b, dest_b = map(_shlex_quote_bytes, map(os.fsencode,
                                                     [orig, dest]))
        if b"\n" in orig_b or b"\n" in dest_b:
            # Who knows whether the current folder name has a newline, or if
            # our encoding is even ASCII compatible...  Just fall back on the
            # slow solution (Inkscape uses `fgets` so it will always stop at a
            # newline).
            cbook.warn_deprecated(
                "3.3",
                message="Support for converting files from paths "
                "containing a newline is deprecated since %(since)s and "
                "support will be removed %(removal)s")
            return make_external_conversion_command(
                lambda old, new: ['inkscape', '-z', old, '--export-png', new])(
                    orig, dest)
        self._proc.stdin.write(orig_b + b" --export-png=" + dest_b + b"\n")
        self._proc.stdin.flush()
        try:
            self._read_until(b"\n>")
        except _ConverterError as err:
            # Inkscape's output is not localized but gtk's is, so the output
            # stream probably has a mixed encoding.  Using the filesystem
            # encoding should at least get the filenames right...
            self._proc.stderr.seek(0)
            raise ImageComparisonFailure(self._proc.stderr.read().decode(
                sys.getfilesystemencoding(), "replace")) from err
Example #7
0
def calculate_rms(expectedImage, actualImage):
    "Calculate the per-pixel errors, then compute the root mean square error."
    if expectedImage.shape != actualImage.shape:
        raise ImageComparisonFailure(
            "Image sizes do not match expected size: {0} "
            "actual size {1}".format(expectedImage.shape, actualImage.shape))
    # Convert to float to avoid overflowing finite integer types.
    return np.sqrt(((expectedImage - actualImage).astype(float) ** 2).mean())
Example #8
0
    def __call__(self, orig, dest):
        if (not self._proc  # First run.
                or self._proc.poll() is not None):  # Inkscape terminated.
            env = os.environ.copy()
            # If one passes e.g. a png file to Inkscape, it will try to
            # query the user for conversion options via a GUI (even with
            # `--without-gui`).  Unsetting `DISPLAY` prevents this (and causes
            # GTK to crash and Inkscape to terminate, but that'll just be
            # reported as a regular exception below).
            env.pop("DISPLAY", None)  # May already be unset.
            # Do not load any user options.
            # `os.environ` needs native strings on Py2+Windows.
            env[str("INKSCAPE_PROFILE_DIR")] = os.devnull
            # Old versions of Inkscape (0.48.3.1, used on Travis as of now)
            # seem to sometimes deadlock when stderr is redirected to a pipe,
            # so we redirect it to a temporary file instead.  This is not
            # necessary anymore as of Inkscape 0.92.1.
            self._stderr = TemporaryFile()
            self._proc = subprocess.Popen(
                [str("inkscape"), "--without-gui", "--shell"],
                stdin=subprocess.PIPE,
                stdout=subprocess.PIPE,
                stderr=self._stderr,
                env=env)
            if not self._read_to_prompt():
                raise OSError("Failed to start Inkscape")

        try:
            fsencode = os.fsencode
        except AttributeError:  # Py2.

            def fsencode(s):
                return s.encode(sys.getfilesystemencoding())

        # Inkscape uses glib's `g_shell_parse_argv`, which has a consistent
        # behavior across platforms, so we can just use `shlex.quote`.
        orig_b, dest_b = map(_shlex_quote_bytes, map(fsencode, [orig, dest]))
        if b"\n" in orig_b or b"\n" in dest_b:
            # Who knows whether the current folder name has a newline, or if
            # our encoding is even ASCII compatible...  Just fall back on the
            # slow solution (Inkscape uses `fgets` so it will always stop at a
            # newline).
            return make_external_conversion_command(
                lambda old, new:
                [str('inkscape'), '-z', old, '--export-png', new])(orig, dest)
        self._proc.stdin.write(orig_b + b" --export-png=" + dest_b + b"\n")
        self._proc.stdin.flush()
        if not self._read_to_prompt():
            # Inkscape's output is not localized but gtk's is, so the
            # output stream probably has a mixed encoding.  Using
            # `getfilesystemencoding` should at least get the filenames
            # right...
            self._stderr.seek(0)
            raise ImageComparisonFailure(self._stderr.read().decode(
                sys.getfilesystemencoding(), "replace"))
Example #9
0
def calculate_rms(expectedImage, actualImage):
    "Calculate the per-pixel errors, then compute the root mean square error."
    if expectedImage.shape != actualImage.shape:
        raise ImageComparisonFailure(
            "Image sizes do not match expected size: {0} "
            "actual size {1}".format(expectedImage.shape, actualImage.shape))
    num_values = expectedImage.size
    abs_diff_image = abs(expectedImage - actualImage)
    histogram = np.bincount(abs_diff_image.ravel(), minlength=256)
    sum_of_squares = np.sum(histogram * np.arange(len(histogram))**2)
    rms = np.sqrt(float(sum_of_squares) / num_values)
    return rms
Example #10
0
def save_diff_image(expected, actual, output):
    '''
    Parameters
    ----------
    expected : str
        File path of expected image.
    actual : str
        File path of actual image.
    output : str
        File path to save difference image to.
    '''
    # Drop alpha channels, similarly to compare_images.
    from matplotlib import _png
    with open(expected, "rb") as expected_file:
        expected_image = _png.read_png(expected_file)[..., :3]
    with open(actual, "rb") as actual_file:
        actual_image = _png.read_png(actual_file)[..., :3]
    actual_image, expected_image = crop_to_same(
        actual, actual_image, expected, expected_image)
    expected_image = np.array(expected_image).astype(float)
    actual_image = np.array(actual_image).astype(float)
    if expected_image.shape != actual_image.shape:
        raise ImageComparisonFailure(
            "Image sizes do not match expected size: {} "
            "actual size {}".format(expected_image.shape, actual_image.shape))
    abs_diff_image = np.abs(expected_image - actual_image)

    # expand differences in luminance domain
    abs_diff_image *= 255 * 10
    save_image_np = np.clip(abs_diff_image, 0, 255).astype(np.uint8)
    height, width, depth = save_image_np.shape

    # The PDF renderer doesn't produce an alpha channel, but the
    # matplotlib PNG writer requires one, so expand the array
    if depth == 3:
        with_alpha = np.empty((height, width, 4), dtype=np.uint8)
        with_alpha[:, :, 0:3] = save_image_np
        save_image_np = with_alpha

    # Hard-code the alpha channel to fully solid
    save_image_np[:, :, 3] = 255

    with open(output, "wb") as output_file:
        _png.write_png(save_image_np, output_file)
Example #11
0
def convert(filename, cache):
    """
    Convert the named file into a png file.  Returns the name of the
    created file.

    If *cache* is True, the result of the conversion is cached in
    `matplotlib._get_cachedir() + '/test_cache/'`.  The caching is based
    on a hash of the exact contents of the input file.  The is no limit
    on the size of the cache, so it may need to be manually cleared
    periodically.

    """
    base, extension = filename.rsplit('.', 1)
    if extension not in converter:
        raise ImageComparisonFailure(
            "Don't know how to convert %s files to png" % extension)
    newname = base + '_' + extension + '.png'
    if not os.path.exists(filename):
        raise IOError("'%s' does not exist" % filename)

    # Only convert the file if the destination doesn't already exist or
    # is out of date.
    if (not os.path.exists(newname)
            or os.stat(newname).st_mtime < os.stat(filename).st_mtime):
        if cache:
            cache_dir = get_cache_dir()
        else:
            cache_dir = None

        if cache_dir is not None:
            hash_value = get_file_hash(filename)
            new_ext = os.path.splitext(newname)[1]
            cached_file = os.path.join(cache_dir, hash_value + new_ext)
            if os.path.exists(cached_file):
                shutil.copyfile(cached_file, newname)
                return newname

        converter[extension](filename, newname)

        if cache_dir is not None:
            shutil.copyfile(newname, cached_file)

    return newname
Example #12
0
def save_diff_image(expected, actual, output):
    """
    Parameters
    ----------
    expected : str
        File path of expected image.
    actual : str
        File path of actual image.
    output : str
        File path to save difference image to.
    """
    # Drop alpha channels, similarly to compare_images.
    expected_image = np.asarray(Image.open(expected).convert("RGB"))
    actual_image = np.asarray(Image.open(actual).convert("RGB"))
    actual_image, expected_image = crop_to_same(actual, actual_image, expected,
                                                expected_image)
    expected_image = np.array(expected_image).astype(float)
    actual_image = np.array(actual_image).astype(float)
    if expected_image.shape != actual_image.shape:
        raise ImageComparisonFailure(
            "Image sizes do not match expected size: {} "
            "actual size {}".format(expected_image.shape, actual_image.shape))
    abs_diff_image = np.abs(expected_image - actual_image)

    # expand differences in luminance domain
    abs_diff_image *= 255 * 10
    save_image_np = np.clip(abs_diff_image, 0, 255).astype(np.uint8)
    height, width, depth = save_image_np.shape

    # The PDF renderer doesn't produce an alpha channel, but the
    # matplotlib PNG writer requires one, so expand the array
    if depth == 3:
        with_alpha = np.empty((height, width, 4), dtype=np.uint8)
        with_alpha[:, :, 0:3] = save_image_np
        save_image_np = with_alpha

    # Hard-code the alpha channel to fully solid
    save_image_np[:, :, 3] = 255

    Image.fromarray(save_image_np).save(output, format="png")
Example #13
0
def calculate_rms(expectedImage, actualImage):
    "Calculate the per-pixel errors, then compute the root mean square error."
    if expectedImage.shape != actualImage.shape:
        raise ImageComparisonFailure(
            "image sizes do not match expected size: {0} "
            "actual size {1}".format(expectedImage.shape, actualImage.shape))

    num_values = np.prod(expectedImage.shape)
    abs_diff_image = abs(expectedImage - actualImage)

    # On Numpy 1.6, we can use bincount with minlength, which is much
    # faster than using histogram
    expected_version = version.LooseVersion("1.6")
    found_version = version.LooseVersion(np.__version__)
    if found_version >= expected_version:
        histogram = np.bincount(abs_diff_image.ravel(), minlength=256)
    else:
        histogram = np.histogram(abs_diff_image, bins=np.arange(257))[0]

    sum_of_squares = np.sum(histogram * np.arange(len(histogram))**2)
    rms = np.sqrt(float(sum_of_squares) / num_values)

    return rms
Example #14
0
    def __call__(self, orig, dest):
        old_inkscape = mpl._get_executable_info("inkscape").version < "1"
        terminator = b"\n>" if old_inkscape else b"> "
        if not hasattr(self, "_tmpdir"):
            self._tmpdir = TemporaryDirectory()
        if (not self._proc  # First run.
                or self._proc.poll() is not None):  # Inkscape terminated.
            env = {
                **os.environ,
                # If one passes e.g. a png file to Inkscape, it will try to
                # query the user for conversion options via a GUI (even with
                # `--without-gui`).  Unsetting `DISPLAY` prevents this (and
                # causes GTK to crash and Inkscape to terminate, but that'll
                # just be reported as a regular exception below).
                "DISPLAY":
                "",
                # Do not load any user options.
                "INKSCAPE_PROFILE_DIR":
                os.devnull,
            }
            # Old versions of Inkscape (e.g. 0.48.3.1) seem to sometimes
            # deadlock when stderr is redirected to a pipe, so we redirect it
            # to a temporary file instead.  This is not necessary anymore as of
            # Inkscape 0.92.1.
            stderr = TemporaryFile()
            self._proc = subprocess.Popen(
                ["inkscape", "--without-gui", "--shell"]
                if old_inkscape else ["inkscape", "--shell"],
                stdin=subprocess.PIPE,
                stdout=subprocess.PIPE,
                stderr=stderr,
                env=env,
                cwd=self._tmpdir.name)
            # Slight abuse, but makes shutdown handling easier.
            self._proc.stderr = stderr
            try:
                self._read_until(terminator)
            except _ConverterError as err:
                raise OSError("Failed to start Inkscape in interactive "
                              "mode") from err

        # Inkscape's shell mode does not support escaping metacharacters in the
        # filename ("\n", and ":;" for inkscape>=1).  Avoid any problems by
        # running from a temporary directory and using fixed filenames.
        inkscape_orig = Path(self._tmpdir.name, os.fsdecode(b"f.svg"))
        inkscape_dest = Path(self._tmpdir.name, os.fsdecode(b"f.png"))
        try:
            inkscape_orig.symlink_to(Path(orig).resolve())
        except OSError:
            shutil.copyfile(orig, inkscape_orig)
        self._proc.stdin.write(
            b"f.svg --export-png=f.png\n" if old_inkscape else
            b"file-open:f.svg;export-filename:f.png;export-do;file-close\n")
        self._proc.stdin.flush()
        try:
            self._read_until(terminator)
        except _ConverterError as err:
            # Inkscape's output is not localized but gtk's is, so the output
            # stream probably has a mixed encoding.  Using the filesystem
            # encoding should at least get the filenames right...
            self._proc.stderr.seek(0)
            raise ImageComparisonFailure(self._proc.stderr.read().decode(
                sys.getfilesystemencoding(), "replace")) from err
        os.remove(inkscape_orig)
        shutil.move(inkscape_dest, dest)