pdqhash-go

git clone git://archive.git.mtrnord.blog/MTRNord/pdqhash-go.git
Log | Files | Refs | LICENSE

commit 2290c5881ff302dc009ac741b92a8682a39c1a38
parent 4907828757ec9a80c6358bf6b60ffebc15fca17f
Author: MTRNord <mtrnord1@gmail.com>
Date:   Sun, 25 Feb 2024 12:30:40 +0100

Fix hashes

Diffstat:
Mcmd/scanner.go | 34++++++++++++++++++++++++++++------
Mgo.mod | 1+
Mgo.sum | 2++
Mhelpers/helpers.go | 6+++---
Mhelpers/helpers_test.go | 2+-
Mpdq_hasher.go | 45++++++++++++++++++++++++---------------------
Mpdq_hasher_test.go | 4+---
Aq0004.prof | 0
8 files changed, 60 insertions(+), 34 deletions(-)

diff --git a/cmd/scanner.go b/cmd/scanner.go @@ -5,6 +5,7 @@ import ( "log" "os" "path/filepath" + "runtime/pprof" pdq "github.com/MTRNord/pdqhash-go" "github.com/MTRNord/pdqhash-go/types" @@ -14,7 +15,7 @@ import ( // This is an example. It is not meant to be run in prod. -func processFile(filename string, detailed bool) { +func processFolder(filename string, detailed bool) { pdqhasher := pdq.NewPDQHasher() numPDQHash := 0 @@ -49,23 +50,41 @@ func processFile(filename string, detailed bool) { }) } +func processFile(filename string, detailed bool) { + pdqhasher := pdq.NewPDQHasher() + + hashAndQuality := pdqhasher.FromFile(filename) + delta := 0 + if detailed { + log.Printf("hash=%s,norm=%d,delta=%d,quality=%d,filename=%s", hashAndQuality.Hash.String(), hashAndQuality.Hash.HammingNorm(), delta, hashAndQuality.Quality, filename) + } else { + log.Printf("%s,%d,%s", hashAndQuality.Hash.String(), hashAndQuality.Quality, filename) + } +} + func main() { var folder string var detailedOutput bool flag.StringVar(&folder, "folder", "", "Folder to scan") flag.BoolVar(&detailedOutput, "detailed", false, "Detailed output") + var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file") flag.Parse() + if *cpuprofile != "" { + f, err := os.Create(*cpuprofile) + if err != nil { + log.Fatal(err) + } + pprof.StartCPUProfile(f) + defer pprof.StopCPUProfile() + } // Check if folder exists and is a folder fileInfo, err := os.Stat(folder) if err != nil { log.Fatal(err) } - if !fileInfo.IsDir() { - log.Fatalf("'%s' is not a folder", folder) - } vips.LoggingSettings(nil, vips.LogLevelMessage) vips.Startup(&vips.Config{ @@ -78,6 +97,9 @@ func main() { CollectStats: false, }) defer vips.Shutdown() - - processFile(folder, detailedOutput) + if !fileInfo.IsDir() { + processFile(folder, detailedOutput) + } else { + processFolder(folder, detailedOutput) + } } diff --git a/go.mod b/go.mod @@ -10,6 +10,7 @@ require ( ) require ( + github.com/chewxy/math32 v1.10.1 github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/stretchr/objx v0.5.0 // indirect diff --git a/go.sum b/go.sum @@ -1,3 +1,5 @@ +github.com/chewxy/math32 v1.10.1 h1:LFpeY0SLJXeaiej/eIp2L40VYfscTvKh/FSEZ68uMkU= +github.com/chewxy/math32 v1.10.1/go.mod h1:dOB2rcuFrCn6UHrze36WSLVPKtzPMRAQvBvUwkSsLqs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/helpers/helpers.go b/helpers/helpers.go @@ -17,9 +17,9 @@ func Torben(m [][]float64, numRows, numCols int) float64 { equal := 0 min := m[0][0] max := m[0][0] - guess := 0.0 - maxltguess := 0.0 - mingtguess := 0.0 + guess := float64(0.0) + maxltguess := float64(0.0) + mingtguess := float64(0.0) for i := 0; i < numRows; i++ { for j := 0; j < numCols; j++ { v := m[i][j] diff --git a/helpers/helpers_test.go b/helpers/helpers_test.go @@ -17,5 +17,5 @@ func TestTorben(t *testing.T) { } } - assert.Equal(t, 1.07, Torben(m, numRows, numCols), "The Torben function should produce 1.07 for 3 rows and 8 cols") + assert.Equal(t, float64(1.07), Torben(m, numRows, numCols), "The Torben function should produce 1.07 for 3 rows and 8 cols") } diff --git a/pdq_hasher.go b/pdq_hasher.go @@ -4,6 +4,7 @@ package pdq import ( "log" + "math" "github.com/MTRNord/pdqhash-go/helpers" @@ -11,7 +12,6 @@ import ( "github.com/davidbyttow/govips/v2/vips" _ "image/jpeg" - _ "image/png" ) // From Wikipedia: standard RGB to luminance (the 'Y' in 'YUV'). @@ -88,9 +88,9 @@ func NewPDQHasher() *PDQHasher { func ComputeDCTMatrix() [][]float64 { d := make([][]float64, 16) - for i := 0; i < 16; i++ { + for i := 0; i < len(d); i++ { di := make([]float64, 64) - for j := 0; j < 64; j++ { + for j := 0; j < len(di); j++ { di[j] = DCT_MATRIX_SCALE_FACTOR() * math.Cos((math.Pi/2.0/64.0)*(float64(i)+1.0)*(2.0*float64(j)+1.0)) } d[i] = di @@ -121,7 +121,7 @@ func (p *PDQHasher) FromFile(filename string) HashAndQuality { } // resizing the image proportionally to max 512px width and max 512px height - err = image.Thumbnail(512, 512, vips.InterestingNone) + err = image.ThumbnailWithSize(512, 512, vips.InterestingNone, vips.SizeDown) if err != nil { log.Fatalf("Error resizing image: %v", err) } @@ -154,15 +154,17 @@ func (p *PDQHasher) fillFloatLumaFromBufferImage(image *vips.ImageRef, luma *[]f if err != nil { log.Fatalf("Error converting to RGB: %v", err) } - goImage, err := image.ToImage(nil) - if err != nil { - log.Fatalf("Error converting to Go image: %v", err) - } for i := 0; i < numRows; i++ { for j := 0; j < numCols; j++ { - colorArray := goImage.At(j, i) - r, g, b, _ := colorArray.RGBA() + colorArray, err := image.GetPoint(j, i) + if err != nil { + log.Fatalf("Error getting pixel: %v", err) + } + r := colorArray[0] + g := colorArray[1] + b := colorArray[2] + log.Println(r, g, b) (*luma)[i*numCols+j] = LUMA_FROM_R_COEFF*float64(r) + LUMA_FROM_G_COEFF*float64(g) + LUMA_FROM_B_COEFF*float64(b) } } @@ -273,9 +275,9 @@ func (p *PDQHasher) pdqHash256esFromFloatLuma(fullBuffer1, fullBuffer2 []float64 // numRows x numCols in row-major order func (p *PDQHasher) decimateFloat(in *[]float64, inNumRows, inNumCols int, out *[][]float64) { for i := 0; i < 64; i++ { - ini := int(((float64(i) + 0.5) * float64(inNumRows)) / 64) + ini := int(((float64(i) + 0.5) * float64(inNumRows)) / 64.0) for j := 0; j < 64; j++ { - inj := int(((float64(j) + 0.5) * float64(inNumCols)) / 64) + inj := int(((float64(j) + 0.5) * float64(inNumCols)) / 64.0) (*out)[i][j] = (*in)[ini*inNumCols+inj] } } @@ -293,7 +295,7 @@ func (p *PDQHasher) computePDQImageDomainQualityMetric(buffer64x64 [][]float64) for j := 0; j < 64; j++ { u := buffer64x64[i][j] v := buffer64x64[i+1][j] - d := int(((u - v) * 100) / 255) + d := int(((u - v) * 100.0) / 255.0) gradientSum += int(helpers.Abs(d)) } } @@ -301,15 +303,15 @@ func (p *PDQHasher) computePDQImageDomainQualityMetric(buffer64x64 [][]float64) for j := 0; j < 63; j++ { u := buffer64x64[i][j] v := buffer64x64[i][j+1] - d := int(((u - v) * 100) / 255) + d := int(((u - v) * 100.0) / 255.0) gradientSum += int(helpers.Abs(d)) } } - quality := int(gradientSum / 90) + quality := float64(gradientSum) / 90.0 if quality > 100 { quality = 100 } - return quality + return int(quality) } /** @@ -329,7 +331,7 @@ func (p *PDQHasher) dct64To16(A, T, B *([][]float64)) { ti := make([]float64, 64) for j := 0; j < 64; j++ { - tij := 0.0 + tij := float64(0.0) for k := 0; k < 64; k++ { tij += D[i][k] * (*A)[k][j] } @@ -340,7 +342,7 @@ func (p *PDQHasher) dct64To16(A, T, B *([][]float64)) { for i := 0; i < 16; i++ { for j := 0; j < 16; j++ { - sumk := 0.0 + sumk := float64(0.0) for k := 0; k < 64; k++ { sumk += (*T)[i][k] * D[j][k] } @@ -466,7 +468,8 @@ func (p *PDQHasher) pdqBuffer16x16ToBits(dctOutput16x16 [][]float64) *types.Hash // Round up. func (p *PDQHasher) computeJaroszWindowSize(dimension int) int { - return (dimension + PDQ_JAROSZ_WINDOW_SIZE_DIVISOR - 1) / PDQ_JAROSZ_WINDOW_SIZE_DIVISOR + result := (float64(dimension) + float64(PDQ_JAROSZ_WINDOW_SIZE_DIVISOR) - 1.0) / float64(PDQ_JAROSZ_WINDOW_SIZE_DIVISOR) + return int(result) } func (p *PDQHasher) jaroszFilterFloat(buffer1, buffer2 *[]float64, numRows, numCols, windowSizeAlongRows, windowSizeAlongCols, nreps int) { @@ -477,7 +480,7 @@ func (p *PDQHasher) jaroszFilterFloat(buffer1, buffer2 *[]float64, numRows, numC } func (p *PDQHasher) box1DFloat(invec *[]float64, inStartOffset int, outvec *[]float64, outStartOffset, vectorLength, stride, fullWindowSize int) { - halfWindowSize := int((fullWindowSize + 2) / 2) + halfWindowSize := int(((float64(fullWindowSize) + 2.0) / 2.0)) phase_1_nreps := int(halfWindowSize - 1) phase_2_nreps := int(fullWindowSize - halfWindowSize + 1) phase_3_nreps := int(vectorLength - fullWindowSize) @@ -485,7 +488,7 @@ func (p *PDQHasher) box1DFloat(invec *[]float64, inStartOffset int, outvec *[]fl li := 0 // Index of left edge of read window, for subtracts ri := 0 // Index of right edge of read windows, for adds oi := 0 // Index of output vector - sum := 0.0 + sum := float64(0.0) currentWindowSize := 0 // PHASE 1: ACCUMULATE FIRST SUM NO WRITES diff --git a/pdq_hasher_test.go b/pdq_hasher_test.go @@ -1,7 +1,6 @@ package pdq import ( - "log" "testing" "github.com/MTRNord/pdqhash-go/types" @@ -120,7 +119,6 @@ func TestPDQHasher(t *testing.T) { computedHash := hash.Hash hammingDistance := computedHash.HammingDistance(expectedHashObj) - log.Printf("Image: %s Hamming distance: %d\n", imagePath, hammingDistance) - assert.True(t, hammingDistance <= hammingTolerance) + assert.LessOrEqualf(t, hammingDistance, hammingTolerance, "Hamming distance is too high: %s", imagePath) } } diff --git a/q0004.prof b/q0004.prof Binary files differ.