Home | History | Annotate | Download | only in image_comparison
      1 # Copyright 2015 The Chromium OS Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 import errno
      6 import Image
      7 import logging
      8 import subprocess
      9 import tempfile
     10 
     11 from autotest_lib.client.cros.image_comparison import comparison_result
     12 from autotest_lib.client.cros.video import method_logger
     13 
     14 
     15 class PdiffImageComparer(object):
     16     """
     17     Compares two images using ChromeOS' perceptualdiff binary.
     18 
     19     """
     20 
     21     @method_logger.log
     22     def compare(self, golden_img_path, test_img_path, box=None):
     23         """
     24         Compares a test image against the specified golden image using the
     25         terminal tool 'perceptualdiff'.
     26 
     27         @param golden_img_path: path, complete path to a golden image.
     28         @param test_img_path: path, complete path to a test image.
     29         @param box: int tuple, left, upper, right, lower pixel coordinates.
     30                     Defines the rectangular boundary within which to compare.
     31         @return: int, number of pixels that are different.
     32         @raise : Whatever _pdiff_compare raises.
     33 
     34         """
     35         if not box:
     36             return self._pdiff_compare(golden_img_path, test_img_path)
     37 
     38         ext = '.png'
     39         tmp_golden_img_file = tempfile.NamedTemporaryFile(suffix=ext)
     40         tmp_test_img_file = tempfile.NamedTemporaryFile(suffix=ext)
     41 
     42         with tmp_golden_img_file, tmp_test_img_file:
     43             tmp_golden_img_path = tmp_golden_img_file.name
     44             tmp_test_img_path = tmp_test_img_file.name
     45 
     46             Image.open(golden_img_path).crop(box).save(tmp_golden_img_path)
     47             Image.open(test_img_path).crop(box).save(tmp_test_img_path)
     48 
     49             return self._pdiff_compare(tmp_golden_img_path, tmp_test_img_path)
     50 
     51 
     52     def _pdiff_compare(self, golden_img_path, test_img_path):
     53         """
     54         Invokes perceptualdiff using subprocess tools.
     55 
     56         @param golden_img_path: path, complete path to a golden image.
     57         @param test_img_path: path, complete path to a test image.
     58         @return: int, number of pixels that are different.
     59         @raise ValueError if image dimensions are not the same.
     60         @raise OSError: if file does not exist or can not be opened.
     61 
     62         """
     63 
     64         # TODO mussa: Could downsampling the images be good for us?
     65 
     66         tmp_diff_file = tempfile.NamedTemporaryFile(suffix='.png', delete=False)
     67         args = ['perceptualdiff', golden_img_path, test_img_path, '-output',
     68                 tmp_diff_file.name]
     69 
     70         logging.debug("Start process with args : " + str(args))
     71 
     72         p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
     73 
     74         output = p.communicate()
     75         logging.debug('output of perceptual diff command is')
     76         logging.debug(output)
     77 
     78         stdoutdata = output[0]
     79 
     80         mismatch_error = "Image dimensions do not match"
     81         diff_message = "Images are visibly different"
     82         filenotfound_message = "Cannot open"
     83 
     84         #TODO(dhaddock): Check for image not created
     85         if p.returncode == 0:
     86             # pdiff exited with 0, images were the same
     87             return comparison_result.ComparisonResult(0, '', None)
     88 
     89         if mismatch_error in stdoutdata:
     90             raise ValueError("pdiff says: " + stdoutdata)
     91 
     92         if diff_message in stdoutdata:
     93             diff_pixels = [int(s) for s in stdoutdata.split() if s.isdigit()][0]
     94             return comparison_result.ComparisonResult(int(diff_pixels), '',
     95                                                       tmp_diff_file.name)
     96 
     97         if filenotfound_message in stdoutdata:
     98             raise OSError(errno.ENOENT, "pdiff says: " + stdoutdata)
     99 
    100         raise RuntimeError("Unknown result from pdiff: "
    101                            "Output : " + stdoutdata)
    102 
    103     def __enter__(self):
    104         return self
    105 
    106     def __exit__(self, exc_type, exc_val, exc_tb):
    107         pass