summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNick White <git@njw.name>2020-07-20 18:29:30 +0100
committerNick White <git@njw.name>2020-07-20 18:29:30 +0100
commit9071b7fc18595c8d19178f6053545b44010ae6a4 (patch)
tree8e4a295fb53e5832a64b8632a0494a8bb6fb6776
parent43c2f3e5fa1894c49f875242edf7590f47d18ff4 (diff)
Add pggraph tool
-rw-r--r--README2
-rw-r--r--cmd/pggraph/main.go207
-rw-r--r--go.mod10
-rw-r--r--go.sum18
-rw-r--r--wipesides.go2
5 files changed, 237 insertions, 2 deletions
diff --git a/README b/README
index a1bb516..8938e43 100644
--- a/README
+++ b/README
@@ -17,6 +17,8 @@ in their own right as well as serving as examples of using the
package.
- binarize : binarises an image using the sauvola algorithm
+ - pggraph : creates a graph showing the proportion of black
+ pixels for slices through an image
- preproc : binarises and wipes an image
- preprocmulti : binarises and wipes an image with multiple
binarisation ksize values
diff --git a/cmd/pggraph/main.go b/cmd/pggraph/main.go
new file mode 100644
index 0000000..9d9d26c
--- /dev/null
+++ b/cmd/pggraph/main.go
@@ -0,0 +1,207 @@
+// Copyright 2020 Nick White.
+// Use of this source code is governed by the GPLv3
+// license that can be found in the LICENSE file.
+
+// pggraph creates a graph showing the proportion of black pixels
+// for slices through an image. This is useful to determine
+// appropriate parameters to pass to the wipe functions in this
+// module.
+package main
+
+import (
+ "flag"
+ "fmt"
+ "image"
+ "image/draw"
+ _ "image/png"
+ _ "image/jpeg"
+ "io"
+ "log"
+ "os"
+ "sort"
+
+ "rescribe.xyz/integralimg"
+ chart "github.com/wcharczuk/go-chart"
+)
+
+const usage = `Usage: pggraph [-vertical] [-width] inimg graphname
+
+Creates a graph showing the proportion of black pixels
+for slices through an image. This is useful to determine
+appropriate parameters to pass to the wipe functions in
+this module.
+`
+
+const middlePercent = 20
+const tickEvery = 30
+
+// sideways flips an image sideways
+func sideways(img *image.Gray) *image.Gray {
+ b := img.Bounds()
+ newb := image.Rect(b.Min.Y, b.Min.X, b.Max.Y, b.Max.X)
+ new := image.NewGray(newb)
+ for x := b.Min.X; x < b.Max.X; x++ {
+ for y := b.Min.Y; y < b.Max.Y; y++ {
+ new.SetGray(y, x, img.GrayAt(x, y))
+ }
+ }
+ return new
+}
+
+func graph(title string, points map[int]float64, w io.Writer) error {
+ var xvals, yvals []float64
+ var xs []int
+ var midxvals, midyvals []float64
+ var midxs []int
+
+ if len(points) < 2 {
+ return fmt.Errorf("Not enough points to graph, only %d\n", len(points))
+ }
+
+ for x, _ := range points {
+ xs = append(xs, x)
+ }
+ sort.Ints(xs)
+
+ for _, x := range xs {
+ xvals = append(xvals, float64(x))
+ yvals = append(yvals, points[x])
+ }
+
+ mainSeries := chart.ContinuousSeries{
+ Style: chart.Style{
+ StrokeColor: chart.ColorBlue,
+ FillColor: chart.ColorAlternateBlue,
+ },
+ XValues: xvals,
+ YValues: yvals,
+ }
+
+ numAroundMiddle := int(middlePercent / 2 * float64(len(xs)) / float64(100))
+ if numAroundMiddle > 1 {
+ for i := (len(xs) / 2) - numAroundMiddle; i < (len(xs) / 2) + numAroundMiddle; i++ {
+ midxs = append(midxs, xs[i])
+ }
+ } else {
+ midxs = xs
+ }
+
+ for _, x := range midxs {
+ midxvals = append(midxvals, float64(x))
+ midyvals = append(midyvals, points[x])
+ }
+
+ middleSeries := chart.ContinuousSeries{
+ XValues: midxvals,
+ YValues: midyvals,
+ }
+
+ minSeries := &chart.MinSeries{
+ Style: chart.Style{
+ StrokeColor: chart.ColorAlternateGray,
+ StrokeDashArray: []float64{5.0, 5.0},
+ },
+ InnerSeries: middleSeries,
+ }
+
+ maxSeries := &chart.MaxSeries{
+ Style: chart.Style{
+ StrokeColor: chart.ColorAlternateGray,
+ StrokeDashArray: []float64{5.0, 5.0},
+ },
+ InnerSeries: middleSeries,
+ }
+
+ width := xs[1]
+ var ticks []chart.Tick
+ // if width is larger than tickEvery, just have a tick for each point,
+ // otherwise have a tick every tickEvery period
+ if width > tickEvery {
+ for _, v := range xs {
+ ticks = append(ticks, chart.Tick{float64(v), fmt.Sprintf("%d", v)})
+ }
+ } else {
+ for i := 0; i < len(xs) - 1; i += tickEvery {
+ ticks = append(ticks, chart.Tick{float64(xs[i]), fmt.Sprintf("%d", xs[i])})
+ }
+ if len(ticks) > 1 {
+ lastx := xs[len(xs) - 1]
+ ticks[len(ticks) - 1] = chart.Tick{float64(lastx), fmt.Sprintf("%d", lastx)}
+ }
+ }
+
+ graph := chart.Chart{
+ Title: title,
+ Width: 1920,
+ Height: 800,
+ XAxis: chart.XAxis{
+ Name: "Pixel number",
+ Ticks: ticks,
+ },
+ YAxis: chart.YAxis{
+ Name: "Proportion of black pixels",
+ },
+ Series: []chart.Series{
+ mainSeries,
+ minSeries,
+ maxSeries,
+ chart.LastValueAnnotationSeries(minSeries),
+ chart.LastValueAnnotationSeries(maxSeries),
+ },
+ }
+
+ return graph.Render(chart.PNG, w)
+}
+
+func main() {
+ flag.Usage = func() {
+ fmt.Fprintf(flag.CommandLine.Output(), usage)
+ flag.PrintDefaults()
+ }
+ vertical := flag.Bool("vertical", false, "Slice image vertically (from top to bottom) rather than horizontally")
+ width := flag.Int("width", 5, "Width of slice in pixels (height if in vertical mode)")
+ flag.Parse()
+ if flag.NArg() < 2 {
+ flag.Usage()
+ os.Exit(1)
+ }
+
+ f, err := os.Open(flag.Arg(0))
+ defer f.Close()
+ if err != nil {
+ log.Fatalf("Could not open file %s: %v\n", flag.Arg(0), err)
+ }
+ img, _, err := image.Decode(f)
+ if err != nil {
+ log.Fatalf("Could not decode image: %v\n", err)
+ }
+ b := img.Bounds()
+ gray := image.NewGray(image.Rect(0, 0, b.Dx(), b.Dy()))
+ draw.Draw(gray, b, img, b.Min, draw.Src)
+ if *vertical {
+ gray = sideways(gray)
+ }
+ integral := integralimg.ToIntegralImg(gray)
+
+ points := make(map[int]float64)
+ maxx := len(integral[0]) -1
+ for x := 0; x + *width < maxx; x += *width {
+ w := integral.GetVerticalWindow(x, *width)
+ points[x] = w.Proportion()
+ }
+
+ f, err = os.Create(flag.Arg(1))
+ defer f.Close()
+ if err != nil {
+ log.Fatalf("Could not create file %s: %v\n", flag.Arg(1), err)
+ }
+
+ title := fmt.Sprintf("Proportion of black pixels over %s (width %d)", flag.Arg(0), *width)
+ if *vertical {
+ title += " (vertical)"
+ }
+ err = graph(title, points, f)
+ if err != nil {
+ log.Fatalf("Could not create graph: %v\n", err)
+ }
+}
diff --git a/go.mod b/go.mod
index 0237e41..cd9d133 100644
--- a/go.mod
+++ b/go.mod
@@ -1,3 +1,11 @@
module rescribe.xyz/preproc
-require rescribe.xyz/integralimg v0.1.1
+go 1.14
+
+require (
+ github.com/blend/go-sdk v1.1.1 // indirect
+ github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
+ github.com/wcharczuk/go-chart v2.0.2-0.20191206192251-962b9abdec2b+incompatible
+ golang.org/x/image v0.0.0-20200618115811-c13761719519 // indirect
+ rescribe.xyz/integralimg v0.1.1
+)
diff --git a/go.sum b/go.sum
index afa027d..7b10a68 100644
--- a/go.sum
+++ b/go.sum
@@ -1,2 +1,20 @@
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+github.com/DataDog/datadog-go v0.0.0-20180822151419-281ae9f2d895/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
+github.com/airbrake/gobrake v3.6.1+incompatible/go.mod h1:wM4gu3Cn0W0K7GUuVWnlXZU11AGBXMILnrdOU8Kn00o=
+github.com/blend/go-sdk v1.1.1 h1:R7PcwuIxYvrGc/r9TLLfMpajIboTjqs/HyQouzgJ7mQ=
+github.com/blend/go-sdk v1.1.1/go.mod h1:IP1XHXFveOXHRnojRJO7XvqWGqyzevtXND9AdSztAe8=
+github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
+github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
+github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
+github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/wcharczuk/go-chart v2.0.2-0.20191206192251-962b9abdec2b+incompatible h1:ahpaSRefPekV3gcXot2AOgngIV8WYqzvDyFe3i7W24w=
+github.com/wcharczuk/go-chart v2.0.2-0.20191206192251-962b9abdec2b+incompatible/go.mod h1:PF5tmL4EIx/7Wf+hEkpCqYi5He4u90sw+0+6FhrryuE=
+golang.org/x/image v0.0.0-20200618115811-c13761719519 h1:1e2ufUJNM3lCHEY5jIgac/7UTjd6cgJNdatjPdFWf34=
+golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/tools v0.0.0-20181205014116-22934f0fdb62/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
rescribe.xyz/integralimg v0.1.1 h1:riLayPKKM5bs/ZyIcYhbUs4qP8wQOWc+tMxOF3vuscI=
rescribe.xyz/integralimg v0.1.1/go.mod h1:2euyPigpyIixTWO6JtFEhDp/3YKA6yy+d8g17oL0L0s=
diff --git a/wipesides.go b/wipesides.go
index ebfb24f..26b7d25 100644
--- a/wipesides.go
+++ b/wipesides.go
@@ -149,7 +149,7 @@ func toonarrow(img *image.Gray, lowedge int, highedge int, min int) bool {
return false
}
-// func sideways flips an image by sideways
+// sideways flips an image sideways
func sideways(img *image.Gray) *image.Gray {
b := img.Bounds()
newb := image.Rect(b.Min.Y, b.Min.X, b.Max.Y, b.Max.X)