imagepair.py revision 280ea8296ff83663a427b8c9aa0ee98b7839f926
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# Keys used within ImagePair dictionary representations.
15# NOTE: Keep these in sync with static/constants.js
16KEY__DIFFERENCE_DATA = 'differenceData'
17KEY__EXPECTATIONS_DATA = 'expectations'
18KEY__EXTRA_COLUMN_VALUES = 'extraColumns'
19KEY__IMAGE_A_URL = 'imageAUrl'
20KEY__IMAGE_B_URL = 'imageBUrl'
21KEY__IS_DIFFERENT = 'isDifferent'
22
23
24class ImagePair(object):
25  """Describes a pair of images, pixel difference info, and optional metadata.
26  """
27
28  def __init__(self, image_diff_db,
29               base_url, imageA_relative_url, imageB_relative_url,
30               expectations=None, extra_columns=None):
31    """
32    Args:
33      image_diff_db: ImageDiffDB instance we use to generate/store image diffs
34      base_url: base of all image URLs
35      imageA_relative_url: string; URL pointing at an image, relative to
36          base_url; or None, if this image is missing
37      imageB_relative_url: string; URL pointing at an image, relative to
38          base_url; or None, if this image is missing
39      expectations: optional dictionary containing expectations-specific
40          metadata (ignore-failure, bug numbers, etc.)
41      extra_columns: optional dictionary containing more metadata (test name,
42          builder name, etc.)
43    """
44    self.base_url = base_url
45    self.imageA_relative_url = imageA_relative_url
46    self.imageB_relative_url = imageB_relative_url
47    self.expectations_dict = expectations
48    self.extra_columns_dict = extra_columns
49    if not imageA_relative_url or not imageB_relative_url:
50      self._is_different = True
51      self._diff_record = None
52      self._diff_record_set = True
53    elif imageA_relative_url == imageB_relative_url:
54      self._is_different = False
55      self._diff_record = None
56      self._diff_record_set = True
57    else:
58      # Tell image_diff_db to add this ImagePair.
59      # It will do so in a separate thread so as not to block this one;
60      # when you call self.get_diff_record(), it will block until the results
61      # are ready.
62      image_diff_db.add_image_pair_async(
63          expected_image_locator=imageA_relative_url,
64          expected_image_url=posixpath.join(base_url, imageA_relative_url),
65          actual_image_locator=imageB_relative_url,
66          actual_image_url=posixpath.join(base_url, imageB_relative_url))
67      self._image_diff_db = image_diff_db
68      self._diff_record_set = False
69
70  def get_diff_record(self):
71    """Returns the DiffRecord associated with this ImagePair.
72
73    Returns None if the images are identical, or one is missing.
74    This method will block until the DiffRecord is available.
75    """
76    if not self._diff_record_set:
77      self._diff_record = self._image_diff_db.get_diff_record(
78          expected_image_locator=self.imageA_relative_url,
79          actual_image_locator=self.imageB_relative_url)
80      self._image_diff_db = None  # release reference, no longer needed
81      if (self._diff_record and
82          self._diff_record.get_num_pixels_differing() == 0):
83        self._is_different = False
84      else:
85        self._is_different = True
86      self._diff_record_set = True
87    return self._diff_record
88
89  def as_dict(self):
90    """Returns a dictionary describing this ImagePair.
91
92    Uses the KEY__* constants as keys.
93    """
94    asdict = {
95        KEY__IMAGE_A_URL: self.imageA_relative_url,
96        KEY__IMAGE_B_URL: self.imageB_relative_url,
97    }
98    if self.expectations_dict:
99      asdict[KEY__EXPECTATIONS_DATA] = self.expectations_dict
100    if self.extra_columns_dict:
101      asdict[KEY__EXTRA_COLUMN_VALUES] = self.extra_columns_dict
102    diff_record = self.get_diff_record()
103    if diff_record and (diff_record.get_num_pixels_differing() > 0):
104      asdict[KEY__DIFFERENCE_DATA] = diff_record.as_dict()
105    asdict[KEY__IS_DIFFERENT] = self._is_different
106    return asdict
107