1#!/usr/bin/python
2
3"""
4Copyright 2014 Google Inc.
5
6Use of this source code is governed by a BSD-style license that can be
7found in the LICENSE file.
8
9ImagePair class (see class docstring for details)
10"""
11
12import posixpath
13
14
15# Keys used within ImagePair dictionary representations.
16# NOTE: Keep these in sync with static/constants.js
17KEY__IMAGEPAIRS__DIFFERENCES = 'differenceData'
18KEY__IMAGEPAIRS__EXPECTATIONS = 'expectations'
19KEY__IMAGEPAIRS__EXTRACOLUMNS = 'extraColumns'
20KEY__IMAGEPAIRS__IMAGE_A_URL = 'imageAUrl'
21KEY__IMAGEPAIRS__IMAGE_B_URL = 'imageBUrl'
22KEY__IMAGEPAIRS__IS_DIFFERENT = 'isDifferent'
23
24
25class ImagePair(object):
26  """Describes a pair of images, pixel difference info, and optional metadata.
27  """
28
29  def __init__(self, image_diff_db,
30               base_url, imageA_relative_url, imageB_relative_url,
31               expectations=None, extra_columns=None):
32    """
33    Args:
34      image_diff_db: ImageDiffDB instance we use to generate/store image diffs
35      base_url: base of all image URLs
36      imageA_relative_url: string; URL pointing at an image, relative to
37          base_url; or None, if this image is missing
38      imageB_relative_url: string; URL pointing at an image, relative to
39          base_url; or None, if this image is missing
40      expectations: optional dictionary containing expectations-specific
41          metadata (ignore-failure, bug numbers, etc.)
42      extra_columns: optional dictionary containing more metadata (test name,
43          builder name, etc.)
44    """
45    self.base_url = base_url
46    self.imageA_relative_url = imageA_relative_url
47    self.imageB_relative_url = imageB_relative_url
48    self.expectations_dict = expectations
49    self.extra_columns_dict = extra_columns
50    if not imageA_relative_url or not imageB_relative_url:
51      self._is_different = True
52      self.diff_record = None
53    elif imageA_relative_url == imageB_relative_url:
54      self._is_different = False
55      self.diff_record = None
56    else:
57      # TODO(epoger): Rather than blocking until image_diff_db can read in
58      # the image pair and generate diffs, it would be better to do it
59      # asynchronously: tell image_diff_db to download a bunch of file pairs,
60      # and only block later if we're still waiting for diff_records to come
61      # back.
62      self._is_different = True
63      image_diff_db.add_image_pair(
64          expected_image_locator=imageA_relative_url,
65          expected_image_url=posixpath.join(base_url, imageA_relative_url),
66          actual_image_locator=imageB_relative_url,
67          actual_image_url=posixpath.join(base_url, imageB_relative_url))
68      self.diff_record = image_diff_db.get_diff_record(
69          expected_image_locator=imageA_relative_url,
70          actual_image_locator=imageB_relative_url)
71      if self.diff_record and self.diff_record.get_num_pixels_differing() == 0:
72        self._is_different = False
73
74  def as_dict(self):
75    """Returns a dictionary describing this ImagePair.
76
77    Uses the KEY__IMAGEPAIRS__* constants as keys.
78    """
79    asdict = {
80        KEY__IMAGEPAIRS__IMAGE_A_URL: self.imageA_relative_url,
81        KEY__IMAGEPAIRS__IMAGE_B_URL: self.imageB_relative_url,
82    }
83    asdict[KEY__IMAGEPAIRS__IS_DIFFERENT] = self._is_different
84    if self.expectations_dict:
85      asdict[KEY__IMAGEPAIRS__EXPECTATIONS] = self.expectations_dict
86    if self.extra_columns_dict:
87      asdict[KEY__IMAGEPAIRS__EXTRACOLUMNS] = self.extra_columns_dict
88    if self.diff_record and (self.diff_record.get_num_pixels_differing() > 0):
89      asdict[KEY__IMAGEPAIRS__DIFFERENCES] = self.diff_record.as_dict()
90    return asdict
91