| #!/usr/bin/python | 
 |  | 
 | """ | 
 | Copyright 2014 Google Inc. | 
 |  | 
 | Use of this source code is governed by a BSD-style license that can be | 
 | found in the LICENSE file. | 
 |  | 
 | ImagePair class (see class docstring for details) | 
 | """ | 
 |  | 
 | import posixpath | 
 |  | 
 |  | 
 | # Keys used within ImagePair dictionary representations. | 
 | # NOTE: Keep these in sync with static/constants.js | 
 | KEY__IMAGEPAIRS__DIFFERENCES = 'differenceData' | 
 | KEY__IMAGEPAIRS__EXPECTATIONS = 'expectations' | 
 | KEY__IMAGEPAIRS__EXTRACOLUMNS = 'extraColumns' | 
 | KEY__IMAGEPAIRS__IMAGE_A_URL = 'imageAUrl' | 
 | KEY__IMAGEPAIRS__IMAGE_B_URL = 'imageBUrl' | 
 | KEY__IMAGEPAIRS__IS_DIFFERENT = 'isDifferent' | 
 |  | 
 | # If self._diff_record is set to this, we haven't asked ImageDiffDB for the | 
 | # image diff details yet. | 
 | _DIFF_RECORD_STILL_LOADING = 'still_loading' | 
 |  | 
 |  | 
 | class ImagePair(object): | 
 |   """Describes a pair of images, pixel difference info, and optional metadata. | 
 |   """ | 
 |  | 
 |   def __init__(self, image_diff_db, | 
 |                base_url, imageA_relative_url, imageB_relative_url, | 
 |                expectations=None, extra_columns=None, | 
 |                download_all_images=False): | 
 |     """ | 
 |     Args: | 
 |       image_diff_db: ImageDiffDB instance we use to generate/store image diffs | 
 |       base_url: base of all image URLs | 
 |       imageA_relative_url: string; URL pointing at an image, relative to | 
 |           base_url; or None, if this image is missing | 
 |       imageB_relative_url: string; URL pointing at an image, relative to | 
 |           base_url; or None, if this image is missing | 
 |       expectations: optional dictionary containing expectations-specific | 
 |           metadata (ignore-failure, bug numbers, etc.) | 
 |       extra_columns: optional dictionary containing more metadata (test name, | 
 |           builder name, etc.) | 
 |       download_all_images: if True, download any images associated with this | 
 |           image pair, even if we don't need them to generate diffs | 
 |           (imageA == imageB, or one of them is missing) | 
 |     """ | 
 |     self._image_diff_db = image_diff_db | 
 |     self.base_url = base_url | 
 |     self.imageA_relative_url = imageA_relative_url | 
 |     self.imageB_relative_url = imageB_relative_url | 
 |     self.expectations_dict = expectations | 
 |     self.extra_columns_dict = extra_columns | 
 |     if not imageA_relative_url or not imageB_relative_url: | 
 |       self._is_different = True | 
 |       self._diff_record = None | 
 |     elif imageA_relative_url == imageB_relative_url: | 
 |       self._is_different = False | 
 |       self._diff_record = None | 
 |     else: | 
 |       # Tell image_diff_db to add an entry for this diff asynchronously. | 
 |       # Later on, we will call image_diff_db.get_diff_record() to find it. | 
 |       self._is_different = True | 
 |       self._diff_record = _DIFF_RECORD_STILL_LOADING | 
 |  | 
 |     if self._diff_record != None or download_all_images: | 
 |       image_diff_db.add_image_pair( | 
 |           expected_image_locator=imageA_relative_url, | 
 |           expected_image_url=self.posixpath_join(base_url, imageA_relative_url), | 
 |           actual_image_locator=imageB_relative_url, | 
 |           actual_image_url=self.posixpath_join(base_url, imageB_relative_url)) | 
 |  | 
 |   def as_dict(self): | 
 |     """Returns a dictionary describing this ImagePair. | 
 |  | 
 |     Uses the KEY__IMAGEPAIRS__* constants as keys. | 
 |     """ | 
 |     asdict = { | 
 |         KEY__IMAGEPAIRS__IMAGE_A_URL: self.imageA_relative_url, | 
 |         KEY__IMAGEPAIRS__IMAGE_B_URL: self.imageB_relative_url, | 
 |     } | 
 |     asdict[KEY__IMAGEPAIRS__IS_DIFFERENT] = self._is_different | 
 |     if self.expectations_dict: | 
 |       asdict[KEY__IMAGEPAIRS__EXPECTATIONS] = self.expectations_dict | 
 |     if self.extra_columns_dict: | 
 |       asdict[KEY__IMAGEPAIRS__EXTRACOLUMNS] = self.extra_columns_dict | 
 |     if self._diff_record is _DIFF_RECORD_STILL_LOADING: | 
 |       # We have waited as long as we can to ask ImageDiffDB for details of | 
 |       # this image diff.  Now we must block until ImageDiffDB can provide | 
 |       # those details. | 
 |       # | 
 |       # TODO(epoger): Is it wasteful for every imagepair to have its own | 
 |       # reference to image_diff_db?  If so, we could pass an image_diff_db | 
 |       # reference into this method call instead... | 
 |       self._diff_record = self._image_diff_db.get_diff_record( | 
 |           expected_image_locator=self.imageA_relative_url, | 
 |           actual_image_locator=self.imageB_relative_url) | 
 |     if self._diff_record != None: | 
 |       asdict[KEY__IMAGEPAIRS__DIFFERENCES] = self._diff_record.as_dict() | 
 |     return asdict | 
 |  | 
 |   @staticmethod | 
 |   def posixpath_join(*args): | 
 |     """Wrapper around posixpath.join(). | 
 |  | 
 |     Returns posixpath.join(*args), or None if any arg is None. | 
 |     """ | 
 |     for arg in args: | 
 |       if arg == None: | 
 |         return None | 
 |     return posixpath.join(*args) |