Skip to content

Commit

Permalink
Add support to some histogram functions (#395)
Browse files Browse the repository at this point in the history
* Add support to vips_stats

* Add support for some histogram functions
  • Loading branch information
rafaelsierra authored Jan 21, 2024
1 parent 9339542 commit 19be7f9
Show file tree
Hide file tree
Showing 5 changed files with 211 additions and 0 deletions.
20 changes: 20 additions & 0 deletions vips/arithmetic.c
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,23 @@ int find_trim(VipsImage *in, int *left, int *top, int *width, int *height,
int getpoint(VipsImage *in, double **vector, int n, int x, int y) {
return vips_getpoint(in, vector, &n, x, y, NULL);
}

int stats(VipsImage *in, VipsImage **out) {
return vips_stats(in, out, NULL);
}

int hist_find(VipsImage *in, VipsImage **out) {
return vips_hist_find(in, out, NULL);
}

int hist_cum(VipsImage *in, VipsImage **out) {
return vips_hist_cum(in, out, NULL);
}

int hist_norm(VipsImage *in, VipsImage **out) {
return vips_hist_norm(in, out, NULL);
}

int hist_entropy(VipsImage *in, double *out) {
return vips_hist_entropy(in, out, NULL);
}
60 changes: 60 additions & 0 deletions vips/arithmetic.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,63 @@ func vipsGetPoint(in *C.VipsImage, n int, x int, y int) ([]float64, error) {
// maximum n is 4
return (*[4]float64)(unsafe.Pointer(out))[:n:n], nil
}

// https://www.libvips.org/API/current/libvips-arithmetic.html#vips-stats
func vipsStats(in *C.VipsImage) (*C.VipsImage, error) {
incOpCounter("stats")
var out *C.VipsImage

if err := C.stats(in, &out); err != 0 {
return nil, handleImageError(out)
}

return out, nil
}

// https://www.libvips.org/API/current/libvips-arithmetic.html#vips-hist-find
func vipsHistFind(in *C.VipsImage) (*C.VipsImage, error) {
incOpCounter("histFind")
var out *C.VipsImage

if err := C.hist_find(in, &out); err != 0 {
return nil, handleImageError(out)
}

return out, nil
}

// https://www.libvips.org/API/current/libvips-histogram.html#vips-hist-norm
func vipsHistNorm(in *C.VipsImage) (*C.VipsImage, error) {
incOpCounter("histNorm")
var out *C.VipsImage

if err := C.hist_norm(in, &out); err != 0 {
return nil, handleImageError(out)
}

return out, nil
}

// https://www.libvips.org/API/current/libvips-histogram.html#vips-hist-cum
func vipsHistCum(in *C.VipsImage) (*C.VipsImage, error) {
incOpCounter("histCum")
var out *C.VipsImage

if err := C.hist_cum(in, &out); err != 0 {
return nil, handleImageError(out)
}

return out, nil
}

// https://www.libvips.org/API/current/libvips-histogram.html#vips-hist-entropy
func vipsHistEntropy(in *C.VipsImage) (float64, error) {
incOpCounter("histEntropy")
var out C.double

if err := C.hist_entropy(in, &out); err != 0 {
return 0, handleVipsError()
}

return float64(out), nil
}
5 changes: 5 additions & 0 deletions vips/arithmetic.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,8 @@ int average(VipsImage *in, double *out);
int find_trim(VipsImage *in, int *left, int *top, int *width, int *height,
double threshold, double r, double g, double b);
int getpoint(VipsImage *in, double **vector, int n, int x, int y);
int stats(VipsImage *in, VipsImage **out);
int hist_find(VipsImage *in, VipsImage **out);
int hist_cum(VipsImage *in, VipsImage **out);
int hist_norm(VipsImage *in, VipsImage **out);
int hist_entropy(VipsImage *in, double *out);
59 changes: 59 additions & 0 deletions vips/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -1655,6 +1655,65 @@ func (r *ImageRef) GetPoint(x int, y int) ([]float64, error) {
return vipsGetPoint(r.image, n, x, y)
}

// Stats find many image statistics in a single pass through the data. Image is changed into a one-band
// `BandFormatDouble` image of at least 10 columns by n + 1 (where n is number of bands in image in)
// rows. Columns are statistics, and are, in order: minimum, maximum, sum, sum of squares, mean,
// standard deviation, x coordinate of minimum, y coordinate of minimum, x coordinate of maximum,
// y coordinate of maximum.
//
// Row 0 has statistics for all bands together, row 1 has stats for band 1, and so on.
//
// If there is more than one maxima or minima, one of them will be chosen at random.
func (r *ImageRef) Stats() error {
out, err := vipsStats(r.image)
if err != nil {
return err
}
r.setImage(out)
return nil
}

// HistogramFind find the histogram the image.
// Find the histogram for all bands (producing a one-band histogram).
// char and uchar images are cast to uchar before histogramming, all other image types are cast to ushort.
func (r *ImageRef) HistogramFind() error {
out, err := vipsHistFind(r.image)
if err != nil {
return err
}
r.setImage(out)
return nil
}

// HistogramCumulative form cumulative histogram.
func (r *ImageRef) HistogramCumulative() error {
out, err := vipsHistCum(r.image)
if err != nil {
return err
}
r.setImage(out)
return nil
}

// HistogramNormalise
// The maximum of each band becomes equal to the maximum index, so for example the max for a uchar
// image becomes 255. Normalise each band separately.
func (r *ImageRef) HistogramNormalise() error {
out, err := vipsHistNorm(r.image)
if err != nil {
return err
}
r.setImage(out)
return nil
}

// HistogramEntropy estimate image entropy from a histogram. Entropy is calculated as:
// `-sum(p * log2(p))`
// where p is histogram-value / sum-of-histogram-values.
func (r *ImageRef) HistogramEntropy() (float64, error) {
return vipsHistEntropy(r.image)
}

// DrawRect draws an (optionally filled) rectangle with a single colour
func (r *ImageRef) DrawRect(ink ColorRGBA, left int, top int, width int, height int, fill bool) error {
err := vipsDrawRect(r.image, ink, left, top, width, height, fill)
Expand Down
67 changes: 67 additions & 0 deletions vips/image_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1072,6 +1072,73 @@ func TestImageRef_CorruptedJPEG(t *testing.T) {
assert.Error(t, err, "VipsJpeg: Corrupt JPEG data: bad Huffman code")
}

func TestImageRef_Stats(t *testing.T) {
Startup(nil)

image, err := NewImageFromFile(resources + "png-24bit.png")
require.NoError(t, err)
bands := image.Bands()

err = image.Stats()
require.NoError(t, err)

// May need updating if `vips_stats` adds more columns
require.Equal(t, 10, image.Width())
require.Equal(t, bands+1, image.Height())
}

func TestImageRef_HistogramFind(t *testing.T) {
Startup(nil)

image, err := NewImageFromFile(resources + "png-24bit.png")
require.NoError(t, err)

err = image.HistogramFind()
require.NoError(t, err)
require.Equal(t, 256, image.Width())
require.Equal(t, 1, image.Height())
}

func TestImageRef_HistogramNormalize(t *testing.T) {
Startup(nil)

image, err := NewImageFromFile(resources + "png-24bit.png")
require.NoError(t, err)

err = image.HistogramFind()
require.NoError(t, err)

err = image.HistogramNormalise()
require.NoError(t, err)
}

func TestImageRef_HistogramCumulative(t *testing.T) {
Startup(nil)

image, err := NewImageFromFile(resources + "png-24bit.png")
require.NoError(t, err)

err = image.HistogramFind()
require.NoError(t, err)

err = image.HistogramCumulative()
require.NoError(t, err)
}

func TestImageRef_HistogramEntropy(t *testing.T) {
Startup(nil)

image, err := NewImageFromFile(resources + "png-24bit.png")
require.NoError(t, err)

err = image.HistogramFind()
require.NoError(t, err)

e, err := image.HistogramEntropy()
require.NoError(t, err)
require.True(t, e > 0)
}

// TODO unit tests to cover:
// NewImageFromReader failing test
// NewImageFromFile failing test
Expand Down

0 comments on commit 19be7f9

Please sign in to comment.