Home | History | Annotate | Download | only in rebaseline_server
      1 #!/usr/bin/python
      2 
      3 """
      4 Copyright 2014 Google Inc.
      5 
      6 Use of this source code is governed by a BSD-style license that can be
      7 found in the LICENSE file.
      8 
      9 ImagePair class (see class docstring for details)
     10 """
     11 
     12 import posixpath
     13 
     14 
     15 # Keys used within ImagePair dictionary representations.
     16 # NOTE: Keep these in sync with static/constants.js
     17 KEY__IMAGEPAIRS__DIFFERENCES = 'differenceData'
     18 KEY__IMAGEPAIRS__EXPECTATIONS = 'expectations'
     19 KEY__IMAGEPAIRS__EXTRACOLUMNS = 'extraColumns'
     20 KEY__IMAGEPAIRS__IMAGE_A_URL = 'imageAUrl'
     21 KEY__IMAGEPAIRS__IMAGE_B_URL = 'imageBUrl'
     22 KEY__IMAGEPAIRS__IS_DIFFERENT = 'isDifferent'
     23 KEY__IMAGEPAIRS__SOURCE_JSON_FILE = 'sourceJsonFile'
     24 
     25 # If self._diff_record is set to this, we haven't asked ImageDiffDB for the
     26 # image diff details yet.
     27 _DIFF_RECORD_STILL_LOADING = 'still_loading'
     28 
     29 
     30 class ImagePair(object):
     31   """Describes a pair of images, pixel difference info, and optional metadata.
     32   """
     33 
     34   def __init__(self, image_diff_db,
     35                imageA_base_url, imageB_base_url,
     36                imageA_relative_url, imageB_relative_url,
     37                expectations=None, extra_columns=None, source_json_file=None,
     38                download_all_images=False):
     39     """
     40     Args:
     41       image_diff_db: ImageDiffDB instance we use to generate/store image diffs
     42       imageA_base_url: string; base URL for image A
     43       imageB_base_url: string; base URL for image B
     44       imageA_relative_url: string; URL pointing at an image, relative to
     45           imageA_base_url; or None, if this image is missing
     46       imageB_relative_url: string; URL pointing at an image, relative to
     47           imageB_base_url; or None, if this image is missing
     48       expectations: optional dictionary containing expectations-specific
     49           metadata (ignore-failure, bug numbers, etc.)
     50       extra_columns: optional dictionary containing more metadata (test name,
     51           builder name, etc.)
     52       source_json_file: relative path of the JSON file where each image came
     53           from; this will be the same for both imageA and imageB, within their
     54           respective directories
     55       download_all_images: if True, download any images associated with this
     56           image pair, even if we don't need them to generate diffs
     57           (imageA == imageB, or one of them is missing)
     58     """
     59     self._image_diff_db = image_diff_db
     60     self.imageA_base_url = imageA_base_url
     61     self.imageB_base_url = imageB_base_url
     62     self.imageA_relative_url = imageA_relative_url
     63     self.imageB_relative_url = imageB_relative_url
     64     self.expectations_dict = expectations
     65     self.extra_columns_dict = extra_columns
     66     self.source_json_file = source_json_file
     67     if not imageA_relative_url or not imageB_relative_url:
     68       self._is_different = True
     69       self._diff_record = None
     70     elif imageA_relative_url == imageB_relative_url:
     71       self._is_different = False
     72       self._diff_record = None
     73     else:
     74       # Tell image_diff_db to add an entry for this diff asynchronously.
     75       # Later on, we will call image_diff_db.get_diff_record() to find it.
     76       self._is_different = True
     77       self._diff_record = _DIFF_RECORD_STILL_LOADING
     78 
     79     if self._diff_record != None or download_all_images:
     80       image_diff_db.add_image_pair(
     81           expected_image_locator=imageA_relative_url,
     82           expected_image_url=self.posixpath_join(imageA_base_url,
     83                                                  imageA_relative_url),
     84           actual_image_locator=imageB_relative_url,
     85           actual_image_url=self.posixpath_join(imageB_base_url,
     86                                                imageB_relative_url))
     87 
     88   def as_dict(self):
     89     """Returns a dictionary describing this ImagePair.
     90 
     91     Uses the KEY__IMAGEPAIRS__* constants as keys.
     92     """
     93     asdict = {
     94         KEY__IMAGEPAIRS__IMAGE_A_URL: self.imageA_relative_url,
     95         KEY__IMAGEPAIRS__IMAGE_B_URL: self.imageB_relative_url,
     96     }
     97     asdict[KEY__IMAGEPAIRS__IS_DIFFERENT] = self._is_different
     98     if self.expectations_dict:
     99       asdict[KEY__IMAGEPAIRS__EXPECTATIONS] = self.expectations_dict
    100     if self.extra_columns_dict:
    101       asdict[KEY__IMAGEPAIRS__EXTRACOLUMNS] = self.extra_columns_dict
    102     if self.source_json_file:
    103       asdict[KEY__IMAGEPAIRS__SOURCE_JSON_FILE] = self.source_json_file
    104     if self._diff_record is _DIFF_RECORD_STILL_LOADING:
    105       # We have waited as long as we can to ask ImageDiffDB for details of
    106       # this image diff.  Now we must block until ImageDiffDB can provide
    107       # those details.
    108       #
    109       # TODO(epoger): Is it wasteful for every imagepair to have its own
    110       # reference to image_diff_db?  If so, we could pass an image_diff_db
    111       # reference into this method call instead...
    112       self._diff_record = self._image_diff_db.get_diff_record(
    113           expected_image_locator=self.imageA_relative_url,
    114           actual_image_locator=self.imageB_relative_url)
    115     if self._diff_record != None:
    116       asdict[KEY__IMAGEPAIRS__DIFFERENCES] = self._diff_record.as_dict()
    117     return asdict
    118 
    119   @staticmethod
    120   def posixpath_join(*args):
    121     """Wrapper around posixpath.join().
    122 
    123     Returns posixpath.join(*args), or None if any arg is None.
    124     """
    125     for arg in args:
    126       if arg == None:
    127         return None
    128     return posixpath.join(*args)
    129