blob: 2ee32e3e1b81a51b0244046f6cc4585fbbad63b0 [file] [log] [blame]
// digesttools are utility functions for answering questions about digests.
package digesttools
import (
"context"
"math"
"go.skia.org/infra/go/skerr"
"go.skia.org/infra/golden/go/diff"
"go.skia.org/infra/golden/go/digest_counter"
"go.skia.org/infra/golden/go/expectations"
"go.skia.org/infra/golden/go/types"
)
// ClosestDiffFinder is able to query a diffstore for digests
// that are the closest negative or closest positive to
// a given digest. Implementations should be considered
// not-thread-safe.
type ClosestDiffFinder interface {
// Precompute allows the implementation to warm any caches.
// Call before doing a batch of ClosestDigest calls.
Precompute(ctx context.Context) error
// ClosestDigest returns the closest digest of type 'label' to 'digest',
// or NoDigestFound if there aren't any positive digests.
//
// If no digest of type 'label' is found then Closest.Digest is the empty string.
ClosestDigest(ctx context.Context, test types.TestName, digest types.Digest, label expectations.Label) (*Closest, error)
}
// Closest describes one digest that is the closest another digest.
type Closest struct {
// The closest digest, empty if there are no digests to compare to.
Digest types.Digest `json:"digest"`
Diff float32 `json:"diff"` // A percent value.
DiffPixels float32 `json:"diffPixels"` // A percent value.
MaxRGBA [4]int `json:"maxRGBA"`
}
const NoDigestFound = types.Digest("")
// newClosest returns an initialized Closest struct, defaulting to
// NoDigestFound and related values.
func newClosest() *Closest {
return &Closest{
Digest: NoDigestFound,
Diff: math.MaxFloat32,
DiffPixels: math.MaxFloat32,
MaxRGBA: [4]int{},
}
}
// Impl implements the ClosestDiffFinder interface
type Impl struct {
expectations expectations.ReadOnly
dCounter digest_counter.DigestCounter
diffStore diff.DiffStore
}
// NewClosestDiffFinder returns a *Impl loaded with the given data sources.
func NewClosestDiffFinder(exp expectations.ReadOnly, dCounter digest_counter.DigestCounter, diffStore diff.DiffStore) *Impl {
return &Impl{
expectations: exp,
dCounter: dCounter,
diffStore: diffStore,
}
}
// Precompute implements the ClosestDiffFinder interface.
func (i *Impl) Precompute(ctx context.Context) error {
return nil
}
// ClosestDigest implements the ClosestDiffFinder interface.
func (i *Impl) ClosestDigest(ctx context.Context, test types.TestName, digest types.Digest, label expectations.Label) (*Closest, error) {
ret := newClosest()
// Locate all digests that this test produces and match the given label.
selected := types.DigestSlice{}
testDigests := i.dCounter.ByTest()[test]
for d := range testDigests {
if i.expectations.Classification(test, d) == label {
selected = append(selected, d)
}
}
if len(selected) == 0 {
return ret, nil
}
if diffMetrics, err := i.diffStore.Get(ctx, digest, selected); err != nil {
return nil, skerr.Wrapf(err, "getting diffs for %s and %d comparisons", digest, len(selected))
} else {
for digest, dm := range diffMetrics {
// We warm based on CombinedMetric because that's the default setting.
if dm.CombinedMetric < ret.Diff {
ret.Digest = digest
ret.Diff = dm.CombinedMetric
ret.DiffPixels = dm.PixelDiffPercent
ret.MaxRGBA = dm.MaxRGBADiffs
}
}
return ret, nil
}
}
// Make sure Impl fulfills the ClosestDiffFinder interface
var _ ClosestDiffFinder = (*Impl)(nil)