func (store *Store) Query(hash Hash) Matches {
	store.RLock()
	defer store.RUnlock()
	if len(store.candidates) == 0 {
		return nil
	}
	scores := make([]float64, len(store.candidates))
	for index := range scores {
		scores[index] = math.NaN()
	}
	var numMatches int
	for coefIndex, coef := range hash.Coefs {
		if coefIndex == 0 {
			continue
		}
		y := coefIndex / int(hash.Width)
		x := coefIndex % int(hash.Width)
		bin := y
		if x > y {
			bin = x
		}
		if bin > 5 {
			bin = 5
		}
		for colourIndex, colourCoef := range coef {
			if math.Abs(colourCoef) < hash.Thresholds[colourIndex] {
				continue
			}
			sign := 0
			if colourCoef < 0 {
				sign = 1
			}
			location := sign*ImageScale*ImageScale*haar.ColourChannels + coefIndex*haar.ColourChannels + colourIndex
			for _, index := range store.indices[location] {
				if math.IsNaN(scores[index]) {
					score := 0.0
					for colour := range coef {
						score += weights[colour][0] *
							math.Abs(store.candidates[index].scaleCoef[colour]-hash.Coefs[0][colour])
					}
					scores[index] = score
				}
				scores[index] -= weightSums[bin]
			}
		}
	}
	matches := make([]*Match, 0, numMatches)
	for index, score := range scores {
		if !math.IsNaN(score) {
			matches = append(matches, &Match{
				ID:        store.candidates[index].id,
				Score:     score,
				RatioDiff: math.Abs(math.Log(store.candidates[index].ratio) - math.Log(hash.Ratio)),
				DHashDistance: hammingDistance(store.candidates[index].dHash[0], hash.DHash[0]) +
					hammingDistance(store.candidates[index].dHash[1], hash.DHash[1]),
				HistogramDistance: hammingDistance(store.candidates[index].histogram, hash.Histogram),
			})
		}
	}
	return matches
}
Query performs a similarity search on the given image hash and returns
all potential matches. The returned slice will not be sorted but implements
sort.Interface, which will sort it so the match with the best score is its
first element.
package duplo
import (
	"bytes"
	"compress/gzip"
	"encoding/gob"
	"fmt"
	"math"
	"sync"
	"github.com/rivo/duplo/haar"
)
const (
	ImageScale = 128
)
var (
	TopCoefs = 40
	weights = [3][6]float64{
		{5.00, 0.83, 1.01, 0.52, 0.47, 0.30},
		{19.21, 1.26, 0.44, 0.53, 0.28, 0.14},
		{34.37, 0.36, 0.45, 0.14, 0.18, 0.27},
	}
	weightSums = [6]float64{58.58, 2.45, 1.9, 1.19, 0.93, 0.71}
)
type Store struct {
	sync.RWMutex
	candidates []candidate
	ids map[interface{}]uint32
	indices [][]uint32
	modified bool
}
func New() *Store {
	store := new(Store)
	store.ids = make(map[interface{}]uint32)
	store.indices = make([][]uint32, 2*ImageScale*ImageScale*haar.ColourChannels)
	return store
}
func (store *Store) Has(id interface{}) bool {
	store.RLock()
	defer store.RUnlock()
	_, ok := store.ids[id]
	return ok
}
func (store *Store) Add(id interface{}, hash Hash) {
	store.Lock()
	defer store.Unlock()
	_, ok := store.ids[id]
	if ok {
		return
	}
	gob.Register(id)
	index := len(store.candidates)
	store.candidates = append(store.candidates, candidate{
		id,
		hash.Coefs[0],
		hash.Ratio,
		hash.DHash,
		hash.Histogram,
		hash.HistoMax})
	store.ids[id] = uint32(index)
	for coefIndex, coef := range hash.Coefs {
		if coefIndex == 0 {
			continue
		}
		for colourIndex, colourCoef := range coef {
			if math.Abs(colourCoef) < hash.Thresholds[colourIndex] {
				continue
			}
			sign := 0
			if colourCoef < 0 {
				sign = 1
			}
			location := sign*ImageScale*ImageScale*haar.ColourChannels + coefIndex*haar.ColourChannels + colourIndex
			store.indices[location] = append(store.indices[location], uint32(index))
		}
	}
	store.modified = true
}
func (store *Store) IDs() (ids []interface{}) {
	store.Lock()
	defer store.Unlock()
	for id := range store.ids {
		ids = append(ids, id)
	}
	return
}
func (store *Store) Delete(id interface{}) {
	store.Lock()
	defer store.Unlock()
	index, ok := store.ids[id]
	if !ok {
		return  
	}
	store.modified = true
	store.candidates[index].id = nil
	delete(store.ids, id)
	for location, list := range store.indices {
		for indexIndex := range list {
			if list[indexIndex] == index {
				store.indices[location] = append(list[:indexIndex], list[indexIndex+1:]...)
				break
			}
		}
	}
}
func (store *Store) Exchange(oldID, newID interface{}) error {
	store.Lock()
	defer store.Unlock()
	index, ok := store.ids[oldID]
	if !ok {
		return nil  
	}
	if _, ok := store.ids[newID]; ok {
		return fmt.Errorf("Cannot exchange ID, %s already exists", newID)
	}
	delete(store.ids, oldID)
	store.ids[newID] = index
	store.candidates[index].id = newID
	store.modified = true
	return nil
}