blob: 89ac677cc2bc8ee0fc8040b9626a747e35cdb5a2 [file] [log] [blame]
// digesttools are utility functions for answering questions about digests.
package digesttools
import (
"math"
"go.skia.org/infra/go/sklog"
"go.skia.org/infra/golden/go/diff"
"go.skia.org/infra/golden/go/tally"
"go.skia.org/infra/golden/go/types"
)
// Closest describes one digest that is the closest another digest.
type Closest struct {
Digest string `json:"digest"` // The closest digest, empty if there are no digests to compare to.
Diff float32 `json:"diff"` // A percent value.
DiffPixels float32 `json:"diffPixels"` // A percent value.
MaxRGBA []int `json:"maxRGBA"`
}
func newClosest() *Closest {
return &Closest{
Diff: math.MaxFloat32,
DiffPixels: math.MaxFloat32,
MaxRGBA: []int{},
}
}
// ClosestDigest returns the closest digest of type 'label' to 'digest', or "" if there aren't any positive digests.
//
// If no digest of type 'label' is found then Closest.Digest is the empty string.
func ClosestDigest(test string, digest string, exp types.Expectations, tallies tally.Tally, diffStore diff.DiffStore, label types.Label) *Closest {
ret := newClosest()
unavailableDigests := diffStore.UnavailableDigests()
if _, ok := unavailableDigests[digest]; ok {
return ret
}
selected := []string{}
for d := range tallies {
if _, ok := unavailableDigests[d]; !ok && (exp.Classification(test, d) == label) {
selected = append(selected, d)
}
}
if len(selected) == 0 {
return ret
}
if diffMetrics, err := diffStore.Get(diff.PRIORITY_NOW, digest, selected); err != nil {
sklog.Errorf("ClosestDigest: Failed to get diff: %s", err)
return ret
} else {
for digest, diffs := range diffMetrics {
dm := diffs.(*diff.DiffMetrics)
if delta := combinedDiffMetric(dm.PixelDiffPercent, dm.MaxRGBADiffs); delta < ret.Diff {
ret.Digest = digest
ret.Diff = delta
ret.DiffPixels = dm.PixelDiffPercent
ret.MaxRGBA = dm.MaxRGBADiffs
}
}
return ret
}
}
// ClosestFromDiffMetrics returns an instance of Closest with the values of the
// given diff.DiffMetrics. The Digest field will be left empty.
func ClosestFromDiffMetrics(diff *diff.DiffMetrics) *Closest {
return &Closest{
Diff: combinedDiffMetric(diff.PixelDiffPercent, diff.MaxRGBADiffs),
DiffPixels: diff.PixelDiffPercent,
MaxRGBA: diff.MaxRGBADiffs,
}
}
// combinedDiffMetric returns a value in [0, 1] that represents how large
// the diff is between two images.
func combinedDiffMetric(pixelDiffPercent float32, maxRGBA []int) float32 {
if len(maxRGBA) == 0 {
return 1.0
}
// Turn maxRGBA into a percent by taking the root mean square difference from
// [0, 0, 0, 0].
sum := 0.0
for _, c := range maxRGBA {
sum += float64(c) * float64(c)
}
normalizedRGBA := math.Sqrt(sum/float64(len(maxRGBA))) / 255.0
// We take the sqrt of (pixelDiffPercent * normalizedRGBA) to straigten out
// the curve, i.e. think about what a plot of x^2 would look like in the
// range [0, 1].
return float32(math.Sqrt(float64(pixelDiffPercent) * normalizedRGBA))
}