From 2e923f2991d4070d86bd5d63a5434f30ce570fd3 Mon Sep 17 00:00:00 2001 From: Nick White Date: Tue, 4 Aug 2020 13:29:06 +0100 Subject: Rename package from integralimg to integral --- README | 6 +- example_test.go | 26 +++---- go.mod | 2 +- integral.go | 213 ++++++++++++++++++++++++++++++++++++++++++++++++++++ integral_test.go | 185 +++++++++++++++++++++++++++++++++++++++++++++ integralimg.go | 213 ---------------------------------------------------- integralimg_test.go | 185 --------------------------------------------- 7 files changed, 415 insertions(+), 415 deletions(-) create mode 100644 integral.go create mode 100644 integral_test.go delete mode 100644 integralimg.go delete mode 100644 integralimg_test.go diff --git a/README b/README index 329fb00..ed682ad 100644 --- a/README +++ b/README @@ -1,4 +1,4 @@ -# rescribe.xyz/integralimg package +# rescribe.xyz/integral package This package contains methods and structures for dealing with Integral Images, aka Summed Area Tables. These are structures @@ -7,9 +7,9 @@ pixel, which can make several common image processing operations much faster. This is a Go package, and can be installed in the standard go way, -by running `go get rescribe.xyz/integralimg` and documentation +by running `go get rescribe.xyz/integral` and documentation can be read with the `go doc` command or online at -. +. ## Contributions diff --git a/example_test.go b/example_test.go index 8958557..542fbf1 100644 --- a/example_test.go +++ b/example_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by the GPLv3 // license that can be found in the LICENSE file. -package integralimg_test +package integral_test import ( "fmt" @@ -12,7 +12,7 @@ import ( "log" "os" - "rescribe.xyz/integralimg" + "rescribe.xyz/integral" ) func ExampleImage_Sum() { @@ -26,9 +26,9 @@ func ExampleImage_Sum() { log.Fatal(err) } b := img.Bounds() - integral := integralimg.NewImage(b) - draw.Draw(integral, b, img, b.Min, draw.Src) - fmt.Printf("Sum: %d\n", integral.Sum(b)) + in := integral.NewImage(b) + draw.Draw(in, b, img, b.Min, draw.Src) + fmt.Printf("Sum: %d\n", in.Sum(b)) // Output: // Sum: 601340165 } @@ -44,9 +44,9 @@ func ExampleImage_Mean() { log.Fatal(err) } b := img.Bounds() - integral := integralimg.NewImage(b) - draw.Draw(integral, b, img, b.Min, draw.Src) - fmt.Printf("Mean: %f\n", integral.Mean(b)) + in := integral.NewImage(b) + draw.Draw(in, b, img, b.Min, draw.Src) + fmt.Printf("Mean: %f\n", in.Mean(b)) // Output: // Mean: 54677.229042 } @@ -62,11 +62,11 @@ func ExampleMeanStdDev() { log.Fatal(err) } b := img.Bounds() - integral := integralimg.NewImage(b) - sqIntegral := integralimg.NewSqImage(b) - draw.Draw(integral, b, img, b.Min, draw.Src) - draw.Draw(sqIntegral, b, img, b.Min, draw.Src) - mean, stddev := integralimg.MeanStdDev(*integral, *sqIntegral, b) + in := integral.NewImage(b) + sq := integral.NewSqImage(b) + draw.Draw(in, b, img, b.Min, draw.Src) + draw.Draw(sq, b, img, b.Min, draw.Src) + mean, stddev := integral.MeanStdDev(*in, *sq, b) fmt.Printf("Mean: %f, Standard Deviation: %f\n", mean, stddev) // Output: // Mean: 54677.229042, Standard Deviation: 21643.721672 diff --git a/go.mod b/go.mod index 9e06b2d..78b4623 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ -module rescribe.xyz/integralimg +module rescribe.xyz/integral go 1.14 diff --git a/integral.go b/integral.go new file mode 100644 index 0000000..e3d09bb --- /dev/null +++ b/integral.go @@ -0,0 +1,213 @@ +// Copyright 2020 Nick White. +// Use of this source code is governed by the GPLv3 +// license that can be found in the LICENSE file. + +// integral is a package for processing integral images, aka +// summed area tables. These are structures which precompute the +// sum of pixels to the left and above each pixel, which can make +// several common image processing operations much faster. +// +// A lot of image processing operations rely on many calculations +// of the sum or mean of a set of pixels. As these have been +// precalculated for an integral image, these calculations are +// much faster. Image.Sum() and Image.Mean() functions are provided +// by this package to take advantage of this. +// +// Another common requirement is standard deviation over an area +// of an image. This can be calculated by creating an integral +// image and squared integral image (SqImage) for a base image, and +// passing them to the MeanStdDev() function provided. +package integral + +import ( + "image" + "image/color" + "math" +) + +// Image is an integral image +type Image [][]uint64 + +// SqImage is a Square integral image. +// A squared integral image is an integral image for which the square of +// each pixel is saved; this is useful for efficiently calculating +// Standard Deviation. +type SqImage [][]uint64 + +func (i Image) ColorModel() color.Model { return color.Gray16Model } + +func (i Image) Bounds() image.Rectangle { + return image.Rect(0, 0, len(i[0]), len(i)) +} + +// at64 is used to return the raw uint64 for a given pixel. Accessing +// this separately to a (potentially lossy) conversion to a Gray16 is +// necessary for SqImage to function accurately. +func (i Image) at64(x, y int) uint64 { + if !(image.Point{x, y}.In(i.Bounds())) { + return 0 + } + + var prevx, prevy, prevxy uint64 + prevx, prevy, prevxy = 0, 0, 0 + if x > 0 { + prevx = i[y][x-1] + } + if y > 0 { + prevy = i[y-1][x] + } + if x > 0 && y > 0 { + prevxy = i[y-1][x-1] + } + orig := i[y][x] + prevxy - prevx - prevy + return orig +} + +func (i Image) At(x, y int) color.Color { + c := i.at64(x, y) + return color.Gray16{uint16(c)} +} + +func (i Image) set64(x, y int, c uint64) { + var prevx, prevy, prevxy uint64 + prevx, prevy, prevxy = 0, 0, 0 + if x > 0 { + prevx = i[y][x-1] + } + if y > 0 { + prevy = i[y-1][x] + } + if x > 0 && y > 0 { + prevxy = i[y-1][x-1] + } + final := c + prevx + prevy - prevxy + i[y][x] = final +} + +func (i Image) Set(x, y int, c color.Color) { + gray := color.Gray16Model.Convert(c).(color.Gray16).Y + i.set64(x, y, uint64(gray)) +} + +// NewImage returns a new integral image with the given bounds. +func NewImage(r image.Rectangle) *Image { + w, h := r.Dx(), r.Dy() + var rows Image + for i := 0; i < h; i++ { + col := make([]uint64, w) + rows = append(rows, col) + } + return &rows +} + +func (i SqImage) ColorModel() color.Model { return Image(i).ColorModel() } + +func (i SqImage) Bounds() image.Rectangle { + return Image(i).Bounds() +} + +func (i SqImage) At(x, y int) color.Color { + c := Image(i).at64(x, y) + rt := math.Sqrt(float64(c)) + return color.Gray16{uint16(rt)} +} + +func (i SqImage) Set(x, y int, c color.Color) { + gray := uint64(color.Gray16Model.Convert(c).(color.Gray16).Y) + Image(i).set64(x, y, gray*gray) +} + +// NewSqImage returns a new squared integral image with the given bounds. +func NewSqImage(r image.Rectangle) *SqImage { + i := NewImage(r) + s := SqImage(*i) + return &s +} + +func lowest(a, b int) int { + if a < b { + return a + } + return b +} + +func highest(a, b int) int { + if a > b { + return a + } + return b +} + +func (i Image) topLeft(r image.Rectangle) uint64 { + b := i.Bounds() + x := r.Min.X - 1 + y := r.Min.Y - 1 + x = lowest(x, b.Max.X-1) + y = lowest(y, b.Max.Y-1) + if x < 0 || y < 0 { + return 0 + } + return i[y][x] +} + +func (i Image) topRight(r image.Rectangle) uint64 { + b := i.Bounds() + x := lowest(r.Max.X-1, b.Max.X-1) + y := r.Min.Y - 1 + y = lowest(y, b.Max.Y-1) + if x < 0 || y < 0 { + return 0 + } + return i[y][x] +} + +func (i Image) bottomLeft(r image.Rectangle) uint64 { + b := i.Bounds() + x := r.Min.X - 1 + x = lowest(x, b.Max.X-1) + y := lowest(r.Max.Y-1, b.Max.Y-1) + if x < 0 || y < 0 { + return 0 + } + return i[y][x] +} + +func (i Image) bottomRight(r image.Rectangle) uint64 { + b := i.Bounds() + x := lowest(r.Max.X-1, b.Max.X-1) + y := lowest(r.Max.Y-1, b.Max.Y-1) + return i[y][x] +} + +// Sum returns the sum of all pixels in a section of an image +func (i Image) Sum(r image.Rectangle) uint64 { + return i.bottomRight(r) + i.topLeft(r) - i.topRight(r) - i.bottomLeft(r) +} + +// Mean returns the average value of pixels in a section of an image +func (i Image) Mean(r image.Rectangle) float64 { + in := r.Intersect(i.Bounds()) + return float64(i.Sum(r)) / float64(in.Dx()*in.Dy()) +} + +// Sum returns the sum of all pixels in a section of an image +func (i SqImage) Sum(r image.Rectangle) uint64 { + return Image(i).Sum(r) +} + +// Mean returns the average value of pixels in a section of an image +func (i SqImage) Mean(r image.Rectangle) float64 { + return Image(i).Mean(r) +} + +// MeanStdDev calculates the mean and standard deviation of a +// section of an image, using the corresponding regular and square +// integral images. +func MeanStdDev(i Image, sq SqImage, r image.Rectangle) (float64, float64) { + imean := i.Mean(r) + smean := sq.Mean(r) + + variance := smean - (imean * imean) + + return imean, math.Sqrt(variance) +} diff --git a/integral_test.go b/integral_test.go new file mode 100644 index 0000000..513c7c4 --- /dev/null +++ b/integral_test.go @@ -0,0 +1,185 @@ +// Copyright 2020 Nick White. +// Use of this source code is governed by the GPLv3 +// license that can be found in the LICENSE file. + +package integral + +import ( + "image" + "image/draw" + _ "image/png" + "os" + "testing" +) + +func TestFromPNG(t *testing.T) { + f, err := os.Open("testdata/in.png") + if err != nil { + t.Fatalf("Could not open file %s: %v\n", "testdata/in.png", err) + } + defer f.Close() + img, _, err := image.Decode(f) + if err != nil { + t.Fatalf("Could not decode image: %v\n", err) + } + b := img.Bounds() + + integral := NewImage(b) + draw.Draw(integral, b, img, b.Min, draw.Src) + + if !imgsequal(img, integral) { + t.Errorf("Read png image differs to integral image\n") + } +} + +func TestSqFromPNG(t *testing.T) { + f, err := os.Open("testdata/in.png") + if err != nil { + t.Fatalf("Could not open file %s: %v\n", "testdata/in.png", err) + } + defer f.Close() + img, _, err := image.Decode(f) + if err != nil { + t.Fatalf("Could not decode image: %v\n", err) + } + b := img.Bounds() + + integral := NewSqImage(b) + draw.Draw(integral, b, img, b.Min, draw.Src) + + if !imgsequal(img, integral) { + t.Errorf("Read png image differs to square integral image\n") + } +} + +func TestSum(t *testing.T) { + f, err := os.Open("testdata/in.png") + if err != nil { + t.Fatalf("Could not open file %s: %v\n", "testdata/in.png", err) + } + defer f.Close() + img, _, err := image.Decode(f) + if err != nil { + t.Fatalf("Could not decode image: %v\n", err) + } + b := img.Bounds() + + imgplus := newGray16Plus(b) + integral := NewImage(b) + + draw.Draw(imgplus, b, img, b.Min, draw.Src) + draw.Draw(integral, b, img, b.Min, draw.Src) + + cases := []struct { + name string + r image.Rectangle + }{ + {"fullimage", b}, + {"small", image.Rect(1, 1, 5, 5)}, + {"toobig", image.Rect(0, 0, 2000, b.Dy())}, + {"toosmall", image.Rect(-1, -1, 4, 5)}, + {"small2", image.Rect(0, 0, 4, 4)}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + sumimg := imgplus.sum(c.r) + sumint := integral.Sum(c.r) + if sumimg != sumint { + t.Errorf("Sum of integral image differs to regular image: regular: %d, integral: %d\n", sumimg, sumint) + } + }) + } +} + +func TestMean(t *testing.T) { + f, err := os.Open("testdata/in.png") + if err != nil { + t.Fatalf("Could not open file %s: %v\n", "testdata/in.png", err) + } + defer f.Close() + img, _, err := image.Decode(f) + if err != nil { + t.Fatalf("Could not decode image: %v\n", err) + } + b := img.Bounds() + + imgplus := newGray16Plus(b) + integral := NewImage(b) + + draw.Draw(imgplus, b, img, b.Min, draw.Src) + draw.Draw(integral, b, img, b.Min, draw.Src) + + cases := []struct { + name string + r image.Rectangle + }{ + {"fullimage", b}, + {"small", image.Rect(1, 1, 5, 5)}, + {"toobig", image.Rect(0, 0, 2000, b.Dy())}, + {"toosmall", image.Rect(-1, -1, 4, 5)}, + {"small2", image.Rect(0, 0, 4, 4)}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + meanimg := imgplus.mean(c.r) + meanint := integral.Mean(c.r) + if meanimg != meanint { + t.Errorf("Mean of integral image differs to regular image: regular: %f, integral: %f\n", meanimg, meanint) + } + }) + } +} + +func imgsequal(img1, img2 image.Image) bool { + b := img1.Bounds() + if !b.Eq(img2.Bounds()) { + return false + } + for y := b.Min.Y; y < b.Max.Y; y++ { + for x := b.Min.X; x < b.Max.X; x++ { + r0, g0, b0, a0 := img1.At(x, y).RGBA() + r1, g1, b1, a1 := img2.At(x, y).RGBA() + if r0 != r1 { + return false + } + if g0 != g1 { + return false + } + if b0 != b1 { + return false + } + if a0 != a1 { + return false + } + } + } + return true +} + +type grayPlus struct { + image.Gray16 +} + +func newGray16Plus(r image.Rectangle) *grayPlus { + var g grayPlus + g.Gray16 = *image.NewGray16(r) + return &g +} + +func (i grayPlus) sum(r image.Rectangle) uint64 { + var sum uint64 + for y := r.Min.Y; y < r.Max.Y; y++ { + for x := r.Min.X; x < r.Max.X; x++ { + c := i.Gray16At(x, y).Y + sum += uint64(c) + } + } + return sum +} + +func (i grayPlus) mean(r image.Rectangle) float64 { + in := r.Intersect(i.Bounds()) + return float64(i.sum(r)) / float64(in.Dx()*in.Dy()) +} diff --git a/integralimg.go b/integralimg.go deleted file mode 100644 index d4d25f1..0000000 --- a/integralimg.go +++ /dev/null @@ -1,213 +0,0 @@ -// Copyright 2020 Nick White. -// Use of this source code is governed by the GPLv3 -// license that can be found in the LICENSE file. - -// integralimg is a package for processing integral images, aka -// summed area tables. These are structures which precompute the -// sum of pixels to the left and above each pixel, which can make -// several common image processing operations much faster. -// -// A lot of image processing operations rely on many calculations -// of the sum or mean of a set of pixels. As these have been -// precalculated for an integral Image, these calculations are -// much faster. Image.Sum() and Image.Mean() functions are provided -// by this package to take advantage of this. -// -// Another common requirement is standard deviation over an area -// of an image. This can be calculated by creating an integral -// Image and squared integral Image (SqImage) for a base image, and -// passing them to the MeanStdDev() function provided. -package integralimg - -import ( - "image" - "image/color" - "math" -) - -// Image is an integral Image -type Image [][]uint64 - -// SqImage is a Square integral Image. -// A squared integral image is an integral image for which the square of -// each pixel is saved; this is useful for efficiently calculating -// Standard Deviation. -type SqImage [][]uint64 - -func (i Image) ColorModel() color.Model { return color.Gray16Model } - -func (i Image) Bounds() image.Rectangle { - return image.Rect(0, 0, len(i[0]), len(i)) -} - -// at64 is used to return the raw uint64 for a given pixel. Accessing -// this separately to a (potentially lossy) conversion to a Gray16 is -// necessary for SqImage to function accurately. -func (i Image) at64(x, y int) uint64 { - if !(image.Point{x, y}.In(i.Bounds())) { - return 0 - } - - var prevx, prevy, prevxy uint64 - prevx, prevy, prevxy = 0, 0, 0 - if x > 0 { - prevx = i[y][x-1] - } - if y > 0 { - prevy = i[y-1][x] - } - if x > 0 && y > 0 { - prevxy = i[y-1][x-1] - } - orig := i[y][x] + prevxy - prevx - prevy - return orig -} - -func (i Image) At(x, y int) color.Color { - c := i.at64(x, y) - return color.Gray16{uint16(c)} -} - -func (i Image) set64(x, y int, c uint64) { - var prevx, prevy, prevxy uint64 - prevx, prevy, prevxy = 0, 0, 0 - if x > 0 { - prevx = i[y][x-1] - } - if y > 0 { - prevy = i[y-1][x] - } - if x > 0 && y > 0 { - prevxy = i[y-1][x-1] - } - final := c + prevx + prevy - prevxy - i[y][x] = final -} - -func (i Image) Set(x, y int, c color.Color) { - gray := color.Gray16Model.Convert(c).(color.Gray16).Y - i.set64(x, y, uint64(gray)) -} - -// NewImage returns a new integral Image with the given bounds. -func NewImage(r image.Rectangle) *Image { - w, h := r.Dx(), r.Dy() - var rows Image - for i := 0; i < h; i++ { - col := make([]uint64, w) - rows = append(rows, col) - } - return &rows -} - -func (i SqImage) ColorModel() color.Model { return Image(i).ColorModel() } - -func (i SqImage) Bounds() image.Rectangle { - return Image(i).Bounds() -} - -func (i SqImage) At(x, y int) color.Color { - c := Image(i).at64(x, y) - rt := math.Sqrt(float64(c)) - return color.Gray16{uint16(rt)} -} - -func (i SqImage) Set(x, y int, c color.Color) { - gray := uint64(color.Gray16Model.Convert(c).(color.Gray16).Y) - Image(i).set64(x, y, gray*gray) -} - -// NewSqImage returns a new squared integral Image with the given bounds. -func NewSqImage(r image.Rectangle) *SqImage { - i := NewImage(r) - s := SqImage(*i) - return &s -} - -func lowest(a, b int) int { - if a < b { - return a - } - return b -} - -func highest(a, b int) int { - if a > b { - return a - } - return b -} - -func (i Image) topLeft(r image.Rectangle) uint64 { - b := i.Bounds() - x := r.Min.X - 1 - y := r.Min.Y - 1 - x = lowest(x, b.Max.X-1) - y = lowest(y, b.Max.Y-1) - if x < 0 || y < 0 { - return 0 - } - return i[y][x] -} - -func (i Image) topRight(r image.Rectangle) uint64 { - b := i.Bounds() - x := lowest(r.Max.X-1, b.Max.X-1) - y := r.Min.Y - 1 - y = lowest(y, b.Max.Y-1) - if x < 0 || y < 0 { - return 0 - } - return i[y][x] -} - -func (i Image) bottomLeft(r image.Rectangle) uint64 { - b := i.Bounds() - x := r.Min.X - 1 - x = lowest(x, b.Max.X-1) - y := lowest(r.Max.Y-1, b.Max.Y-1) - if x < 0 || y < 0 { - return 0 - } - return i[y][x] -} - -func (i Image) bottomRight(r image.Rectangle) uint64 { - b := i.Bounds() - x := lowest(r.Max.X-1, b.Max.X-1) - y := lowest(r.Max.Y-1, b.Max.Y-1) - return i[y][x] -} - -// Sum returns the sum of all pixels in a section of an image -func (i Image) Sum(r image.Rectangle) uint64 { - return i.bottomRight(r) + i.topLeft(r) - i.topRight(r) - i.bottomLeft(r) -} - -// Mean returns the average value of pixels in a section of an image -func (i Image) Mean(r image.Rectangle) float64 { - in := r.Intersect(i.Bounds()) - return float64(i.Sum(r)) / float64(in.Dx()*in.Dy()) -} - -// Sum returns the sum of all pixels in a section of an image -func (i SqImage) Sum(r image.Rectangle) uint64 { - return Image(i).Sum(r) -} - -// Mean returns the average value of pixels in a section of an image -func (i SqImage) Mean(r image.Rectangle) float64 { - return Image(i).Mean(r) -} - -// MeanStdDev calculates the mean and standard deviation of a -// section of an image, using the corresponding regular and square -// integral images. -func MeanStdDev(i Image, sq SqImage, r image.Rectangle) (float64, float64) { - imean := i.Mean(r) - smean := sq.Mean(r) - - variance := smean - (imean * imean) - - return imean, math.Sqrt(variance) -} diff --git a/integralimg_test.go b/integralimg_test.go deleted file mode 100644 index 885ff61..0000000 --- a/integralimg_test.go +++ /dev/null @@ -1,185 +0,0 @@ -// Copyright 2020 Nick White. -// Use of this source code is governed by the GPLv3 -// license that can be found in the LICENSE file. - -package integralimg - -import ( - "image" - "image/draw" - _ "image/png" - "os" - "testing" -) - -func TestFromPNG(t *testing.T) { - f, err := os.Open("testdata/in.png") - if err != nil { - t.Fatalf("Could not open file %s: %v\n", "testdata/in.png", err) - } - defer f.Close() - img, _, err := image.Decode(f) - if err != nil { - t.Fatalf("Could not decode image: %v\n", err) - } - b := img.Bounds() - - integral := NewImage(b) - draw.Draw(integral, b, img, b.Min, draw.Src) - - if !imgsequal(img, integral) { - t.Errorf("Read png image differs to integral image\n") - } -} - -func TestSqFromPNG(t *testing.T) { - f, err := os.Open("testdata/in.png") - if err != nil { - t.Fatalf("Could not open file %s: %v\n", "testdata/in.png", err) - } - defer f.Close() - img, _, err := image.Decode(f) - if err != nil { - t.Fatalf("Could not decode image: %v\n", err) - } - b := img.Bounds() - - integral := NewSqImage(b) - draw.Draw(integral, b, img, b.Min, draw.Src) - - if !imgsequal(img, integral) { - t.Errorf("Read png image differs to square integral image\n") - } -} - -func TestSum(t *testing.T) { - f, err := os.Open("testdata/in.png") - if err != nil { - t.Fatalf("Could not open file %s: %v\n", "testdata/in.png", err) - } - defer f.Close() - img, _, err := image.Decode(f) - if err != nil { - t.Fatalf("Could not decode image: %v\n", err) - } - b := img.Bounds() - - imgplus := newGray16Plus(b) - integral := NewImage(b) - - draw.Draw(imgplus, b, img, b.Min, draw.Src) - draw.Draw(integral, b, img, b.Min, draw.Src) - - cases := []struct { - name string - r image.Rectangle - }{ - {"fullimage", b}, - {"small", image.Rect(1, 1, 5, 5)}, - {"toobig", image.Rect(0, 0, 2000, b.Dy())}, - {"toosmall", image.Rect(-1, -1, 4, 5)}, - {"small2", image.Rect(0, 0, 4, 4)}, - } - - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - sumimg := imgplus.sum(c.r) - sumint := integral.Sum(c.r) - if sumimg != sumint { - t.Errorf("Sum of integral image differs to regular image: regular: %d, integral: %d\n", sumimg, sumint) - } - }) - } -} - -func TestMean(t *testing.T) { - f, err := os.Open("testdata/in.png") - if err != nil { - t.Fatalf("Could not open file %s: %v\n", "testdata/in.png", err) - } - defer f.Close() - img, _, err := image.Decode(f) - if err != nil { - t.Fatalf("Could not decode image: %v\n", err) - } - b := img.Bounds() - - imgplus := newGray16Plus(b) - integral := NewImage(b) - - draw.Draw(imgplus, b, img, b.Min, draw.Src) - draw.Draw(integral, b, img, b.Min, draw.Src) - - cases := []struct { - name string - r image.Rectangle - }{ - {"fullimage", b}, - {"small", image.Rect(1, 1, 5, 5)}, - {"toobig", image.Rect(0, 0, 2000, b.Dy())}, - {"toosmall", image.Rect(-1, -1, 4, 5)}, - {"small2", image.Rect(0, 0, 4, 4)}, - } - - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - meanimg := imgplus.mean(c.r) - meanint := integral.Mean(c.r) - if meanimg != meanint { - t.Errorf("Mean of integral image differs to regular image: regular: %f, integral: %f\n", meanimg, meanint) - } - }) - } -} - -func imgsequal(img1, img2 image.Image) bool { - b := img1.Bounds() - if !b.Eq(img2.Bounds()) { - return false - } - for y := b.Min.Y; y < b.Max.Y; y++ { - for x := b.Min.X; x < b.Max.X; x++ { - r0, g0, b0, a0 := img1.At(x, y).RGBA() - r1, g1, b1, a1 := img2.At(x, y).RGBA() - if r0 != r1 { - return false - } - if g0 != g1 { - return false - } - if b0 != b1 { - return false - } - if a0 != a1 { - return false - } - } - } - return true -} - -type grayPlus struct { - image.Gray16 -} - -func newGray16Plus(r image.Rectangle) *grayPlus { - var g grayPlus - g.Gray16 = *image.NewGray16(r) - return &g -} - -func (i grayPlus) sum(r image.Rectangle) uint64 { - var sum uint64 - for y := r.Min.Y; y < r.Max.Y; y++ { - for x := r.Min.X; x < r.Max.X; x++ { - c := i.Gray16At(x, y).Y - sum += uint64(c) - } - } - return sum -} - -func (i grayPlus) mean(r image.Rectangle) float64 { - in := r.Intersect(i.Bounds()) - return float64(i.sum(r)) / float64(in.Dx()*in.Dy()) -} -- cgit v1.2.1-24-ge1ad