Fix def files so that the implementation agnostic interface definition has no non-standards defined entry points, and change the eglrefimpl specific implementation to place its private entry points high up in the ordinal order space in the implementation region, not the standards based entrypoints region.
# Copyright (c) 2008-2009 Nokia Corporation and/or its subsidiary(-ies).
# All rights reserved.
# This component and the accompanying materials are made available
# under the terms of "Eclipse Public License v1.0"
# which accompanies this distribution, and is available
# at the URL "http://www.eclipse.org/legal/epl-v10.html".
#
# Initial Contributors:
# Nokia Corporation - initial contribution.
#
# Contributors:
#
# Description:
#
"""
Reference Image
Class representing test images and results of comparing it against reference images.
"""
import os
import os.path
from string import *
from PIL import Image, ImageChops, ImageOps, ImageStat, ImageFilter
from sets import Set
import shutil
# Relative path for reference images
KRefPath = "\\ref\\"
# Relative path for test images
KTestPath = "\\test\\"
# Compare test with reference images by pixel and pyramid difference; generate diff images
class RefImage:
# Change the value to tune the passing limit for pyramid diff
PYRAMID_PASS_LIMIT = 10
# Change the value to tune the passing limit for pixel diff
PIXEL_DIFF_PASS_LIMIT = 2
# These are the types of differences that can be tested.
PIXEL_DIFF = 1
DIFF_SCORE = 2
PYRAMID_DIFF = 3
# @param aRefFile The reference images
# @param aTestFile The test images
# @param aBaseDir The base directory of reference and test images
# @param aSource The distinctive part of the expected diff image name
def __init__(self, aRefFile, aTestFile, aBaseDir, aSource):
self.source = aSource
self.refFile = aRefFile
self.testFile = aTestFile
self.baseDir = aBaseDir
self.targetImage = os.path.basename(aRefFile)
self.maxDiff = -1
self.diffScore = -1
self.pyramidDiffs = None
self.refImageCache = None
self.testFileCache = None
self.cachedTestFile = None
self.cachedDiff = None
self.diffImages = None
self.diffsInUse = Set([self.PIXEL_DIFF,self.PYRAMID_DIFF])
# Read in reference images
def _getImage(self):
if not self.refImageCache:
self.refImageCache = Image.open(self.baseDir + KRefPath + self.refFile)
print "ref image: ", self.refFile
return self.refImageCache
# Read in test images
def _getTestee(self):
if not self.testFileCache:
self.testFileCache = Image.open(self.baseDir + KTestPath + self.testFile)
print "test image: ", self.testFile
return self.testFileCache
# Get absolute value of the difference between test and reference images
def _getDiff(self):
self.cachedDiff = ImageChops.difference(self._getImage(), self._getTestee())
return self.cachedDiff
# Get pyramid levels of an image.
# Returns a set of successively low-pass filtered images, resized by 1/2, 1/4, 1/8 respectivly.
# @param aImage The image as the source to get pyramid levels
# @return A set of 3 images scaled at 1/2, 1/4, and 1/8
def _genPyramidLevels(self, aImage):
# Create a 3X3 convolution kernel.
# Gaussian image smoothing kernel, approximated by 3x3 convolution filter.
# A convolution is a weighted average of all the pixels in some neighborhood of a given pixel.
# The convolution kernel values are the weights for the average.
kernel = ImageFilter.Kernel((3, 3), [.75, .9, .75, .9, 1, .9, .75, .9, .75])
source = aImage
res = []
while len(res) < 3:
srcSize = source.size
# Mirror borders.
temp = Image.new("RGBA", (srcSize[0]+2, srcSize[1]+2))
temp.paste(source, (1, 1))
# .crop returns a rectangular region from the current image. Passed: left, upper, right, and lower pixel coordinate.
# .paste to upper-left corner.
# left, top, right, bottom
# Add a one pixel border around the image, so the center of the 3x3 convolution filter starts on the corner pixel of the image.
temp.paste(source.crop((1, 0, 1, srcSize[1]-1)), (0, 1))
temp.paste(source.crop((0, 1, srcSize[1]-1, 1)), (1, 0))
temp.paste(source.crop((srcSize[0]-2, 0, srcSize[0]-2, srcSize[1]-1)), (srcSize[0]+1, 1))
temp.paste(source.crop((0, srcSize[1]-2, srcSize[1]-1, srcSize[1]-2)), (1, srcSize[1]+1))
# Resize the filtered image to 0.5 size, via. 2x2 linear interpolation.
filtered = temp.filter(kernel).crop((1, 1, srcSize[0], srcSize[1])).resize((srcSize[0]/2, srcSize[1]/2), Image.BILINEAR)
source = filtered
res.append(filtered)
return res
# Compute difference values between test and reference images
#
# - Generate mask image (3x3 max/min differences)
# - Generate pyramid reference images (1/2, 1/4, 1/8 low-pass filtered and scaled).
# - Generate pyramid test images (1/2, 1/4, 1/8 low-pass filtered and scaled).
# - Generate pyramid mask images (1/2, 1/4, 1/8 low-pass filtered and scaled).
# - Weight the mask according to level.
# - For each level:
# - Get absolute difference image between reference and test.
# - Multiply absolute difference with inverted mask at that level
# - Take maximum pixel value at each level as the pyramid difference.
#
# See: http://www.pythonware.com/library/pil/handbook/index.htm
#
def compPyramidDiff(self):
ref = self._getImage()
testee = self._getTestee()
#if testee.size != ref.size:
# file.write("WARNING: The reference image has different dimension from the testee image")
# maskImage is the difference between min and max pixels within a 3x3 pixel environment in the reference image.
maskImage = ImageChops.difference(ref.filter(ImageFilter.MinFilter(3)), ref.filter(ImageFilter.MaxFilter(3)))
# generate low-pass filtered pyramid images.
refLevels = self._genPyramidLevels(ref)
refL1 = refLevels[0]
refL2 = refLevels[1]
refL3 = refLevels[2]
testLevels = self._genPyramidLevels(testee)
testL1 = testLevels[0]
testL2 = testLevels[1]
testL3 = testLevels[2]
maskLevels = self._genPyramidLevels(maskImage)
# Apply weighting factor to masks at levels 1, 2, and 3.
maskL1 = Image.eval(maskLevels[0], lambda x: 5*x)
maskL2 = Image.eval(maskLevels[1], lambda x: 3*x)
maskL3 = Image.eval(maskLevels[2], lambda x: 2*x)
# Generate a pixel difference image between reference and test.
# Multiply the difference image with the inverse of the mask.
# Mask inverse (out = MAX - image):
# So, areas of regional (3x3) similarity thend to MAX and differences tend to 0x00.
# Multiply (out = image1 * image2 / MAX:
# Superimposes two images on top of each other. If you multiply an image with a solid black image,
# the result is black. If you multiply with a solid white image, the image is unaffected.
# This has the effect of accentuating any test/reference differences where there is a small
# regional difference in the reference image.
diffL1 = ImageChops.difference(refL1, testL1)
diffL1 = ImageChops.multiply(diffL1, ImageChops.invert(maskL1))
diffL2 = ImageChops.difference(refL2, testL2)
diffL2 = ImageChops.multiply(diffL2, ImageChops.invert(maskL2))
diffL3 = ImageChops.difference(refL3, testL3)
diffL3 = ImageChops.multiply(diffL3, ImageChops.invert(maskL3))
# So now the difference images are a grey-scale image that are brighter where differences
# between the reference and test images were detected in regions where there was little
# variability in the reference image.
# Get maxima for all bands at each pyramid level, and take the maximum value as the pyramid value.
# stat.extrema (Get min/max values for each band in the image).
self.pyramidDiffs = [
max(map(lambda (x): x[1], ImageStat.Stat(diffL1).extrema)),
max(map(lambda (x): x[1], ImageStat.Stat(diffL2).extrema)),
max(map(lambda (x): x[1], ImageStat.Stat(diffL3).extrema))
]
print "self.pyramidDiffs = ", self.pyramidDiffs
# Compute max diff of pixel difference
def compMaxDiff(self):
self.maxDiff = max(map(lambda (x): x[1], ImageStat.Stat(self._getDiff()).extrema))
# Compute diff score
# @param file A log file to store error messages
def compDiffScore(self, file):
self.diffScore = 0
ref = self._getImage()
testee = self._getTestee()
if testee.size != ref.size:
file.write("WARNING: Reference image from source has different dimension than the testee image")
#raise ValueError("Reference image from source has different dimension than the testee image")
# If a difference exists...
if self.maxDiff != 0:
# Filter images for min and max pixel (dark and light) values within 5x5 environment.
refMin = ref.filter(ImageFilter.MinFilter(5))
refMax = ref.filter(ImageFilter.MaxFilter(5))
testMin = testee.filter(ImageFilter.MinFilter(5))
testMax = testee.filter(ImageFilter.MaxFilter(5))
# make the min and max filter images a bit darker and lighter, respectively.
refMin = Image.eval(refMin, lambda x: x - 4)
refMax = Image.eval(refMax, lambda x: x + 4)
testMin = Image.eval(testMin, lambda x: x - 4)
testMax = Image.eval(testMax, lambda x: x + 4)
refRefHist = ref.histogram()
testRefHist = testee.histogram()
# Calculate difference score.
# Check for darkness in reference image.
# Generate an image of the darkest pixels when comparing the 5x5 max filtered and lightened reference image against the test image.
# If the pixel colour histogram of the generated image is different from the test image histogram, increase the difference score.
if (ImageChops.darker(refMax, testee).histogram() != testRefHist):
self.diffScore += 1
# Check for lightness in reference image.
if (ImageChops.lighter(refMin, testee).histogram() != testRefHist):
self.diffScore += 1
# Check for darkness in test image.
if (ImageChops.darker(testMax, ref).histogram() != refRefHist):
self.diffScore += 1
# Check for lightness in test image.
if (ImageChops.lighter(testMin, ref).histogram() != refRefHist):
self.diffScore += 1
print "self.diffScore: ", self.diffScore
# Generate test results
# @param file A log file to store error messages
def pyramidValue (self):
return self.pyramidDiffs[2]
def passed(self, file, aThresholdValue):
if aThresholdValue == -1:
aThresholdValue = self.PYRAMID_PASS_LIMIT
if self.pyramidDiffs:
return self.pyramidValue() <= aThresholdValue
elif self.maxDiff >= 0:
return self.maxDiff <= self.PIXEL_DIFF_PASS_LIMIT
elif self.maxDiff < 0:
warningMsg = "WARNING: Differences were not computed for the test image " + self.testFile + " against its reference image<br>"
print warningMsg;
if file: file.write(warningMsg);
return True
else:
assert False
return False
# Make diff images
# @param aDestDir
def makeDiffImages(self, aDestDir):
diffBands = list(self._getDiff().split())
assert (len(diffBands) == 3 or len(diffBands) == 1)
diffs = {}
baseDiffName = "Diff_" + self.source + "_" + self.targetImage
# Invert the diffs.
for i in range(len(diffBands)):
#for i in range(4):
diffBands[i] = ImageChops.invert(diffBands[i])
temp = ["R", "G", "B"]
for i in range(len(diffBands)):
name = temp[i] + baseDiffName
# Highlight the differing pixels
if not self.PYRAMID_DIFF in self.diffsInUse and not self.DIFF_SCORE in self.diffsInUse:
diffBands[i] = Image.eval(diffBands[i], lambda x: (x / (255 - self.PIXEL_DIFF_PASS_LIMIT)) * 255)
# Following line commented as we don't need to save bitmaps for the separate R,G or B channels.
#diffBands[i].save(aDestDir + name, "BMP")
diffs[temp[i]] = name
if len(diffBands) == 3:
rgbDiff = ImageChops.darker(diffBands[0], ImageChops.darker(diffBands[1], diffBands[2]))
else:
rgbDiff = diffBands[0]
rgbDiffName = "RGB" + baseDiffName
rgbDiff.save(aDestDir + rgbDiffName, "BMP")
diffs["RGB"] = rgbDiffName
self.diffImages = diffs
return diffs
# Print test results to command line
# @param file A log file to store error messages
def printResult(self, file, aThresholdValue):
print "test result: ", self.passed(file, aThresholdValue), "maxDiff: ", self.maxDiff
# Get test results
# @param file A log file to store error messages
def getResult(self, file, aThresholdValue):
return self.passed(file, aThresholdValue);
# Get current puramid result value.
def getPyramidResultValue(self):
if self.pyramidDiffs:
return self.pyramidValue()
return 255
# Get diff images
def getDiffImages(self):
assert self.diffImages != None
return self.diffImages
# Disable a diff test
# @param diff Holds either self.PIXEL_DIFF,self.PYRAMID_DIFF or both when the tester wants to disable either or both of the diff tests
def disableDiff(self, diff):
self.diffsInUse.discard(diff)
# Enabld a diff test
# @param diff Holds either self.PIXEL_DIFF,self.PYRAMID_DIFF or both when the tester wants to enable either or both of the diff tests
def enableDiff(self, diff):
self.diffsInUse.add(diff)
# Set diffs
# @param diffs Either self.PIXEL_DIFF,self.PYRAMID_DIFF or both when the tester wants to set either or both of the diff tests
def setDiffs(self, diffs):
self.diffsInUse = (diffs)
# Compute difference according to the values in self.diffsInUse
# @param file A log file to store error messages
def computeDifferences(self, file):
if self.PIXEL_DIFF in self.diffsInUse:
self.compMaxDiff()
if self.DIFF_SCORE in self.diffsInUse:
self.compDiffScore(file)
if self.PYRAMID_DIFF in self.diffsInUse:
self.compPyramidDiff()