/* statistical difference
 *
 * Copyright: 1990, N. Dessipris.
 *
 * Author: Nicos Dessipris
 * Written on: 02/05/1990
 * Modified on :
 * 6/8/93 JC
 *	- now works for odd window sizes
 *	- ANSIfication
 * 25/5/95 JC
 *	- new IM_ARRAY() macro
 * 25/1/96 JC
 *	- im_lhisteq() adapted to make new im_stdif()
 *	- now partial, plus rolling window
 *	- 5x faster, amazingly
 *	- works
 * 7/4/04
 *	- now uses im_embed() with edge stretching on the input, not
 *	  the output
 * 25/3/10
 * 	- gtkdoc
 * 	- small cleanups
 * 10/8/13
 * 	- wrapped as a class using hist_local.c
 * 	- many bands
 */

/*

	This file is part of VIPS.

	VIPS is free software; you can redistribute it and/or modify
	it under the terms of the GNU Lesser General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU Lesser General Public License for more details.

	You should have received a copy of the GNU Lesser General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
	02110-1301  USA

 */

/*

	These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk

 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /*HAVE_CONFIG_H*/
#include <glib/gi18n-lib.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#include <vips/vips.h>
#include <vips/internal.h>

typedef struct _VipsStdif {
	VipsOperation parent_instance;

	VipsImage *in;
	VipsImage *out;

	int width;
	int height;

	double a;
	double m0;
	double b;
	double s0;

} VipsStdif;

typedef VipsOperationClass VipsStdifClass;

G_DEFINE_TYPE(VipsStdif, vips_stdif, VIPS_TYPE_OPERATION);

/* How ugly and stupid.
 */
#define MAX_BANDS (100)

static int
vips_stdif_generate(VipsRegion *out_region,
	void *vseq, void *a, void *b, gboolean *stop)
{
	VipsRect *r = &out_region->valid;
	VipsRegion *ir = (VipsRegion *) vseq;
	VipsImage *in = (VipsImage *) a;
	VipsStdif *stdif = (VipsStdif *) b;
	int bands = in->Bands;
	int npel = stdif->width * stdif->width;

	VipsRect irect;
	int y;
	int lsk;
	int centre; /* Offset to move to centre of window */

	/* What part of ir do we need?
	 */
	irect.left = out_region->valid.left;
	irect.top = out_region->valid.top;
	irect.width = out_region->valid.width + stdif->width;
	irect.height = out_region->valid.height + stdif->height;
	if (vips_region_prepare(ir, &irect))
		return -1;

	lsk = VIPS_REGION_LSKIP(ir);
	centre = lsk * (stdif->height / 2) + stdif->width / 2;

	for (y = 0; y < r->height; y++) {
		/* Get input and output pointers for this line.
		 */
		VipsPel *p = VIPS_REGION_ADDR(ir, r->left, r->top + y);
		VipsPel *q = VIPS_REGION_ADDR(out_region, r->left, r->top + y);

		double f1 = stdif->a * stdif->m0;
		double f2 = 1.0 - stdif->a;
		double f3 = stdif->b * stdif->s0;

		VipsPel *p1;
		int x, i, j, b;

		/* We will get int overflow for windows larger than about 256
		 * x 256, sadly.
		 */
		unsigned int sum[MAX_BANDS];
		unsigned int sum2[MAX_BANDS];

		/* Find sum, sum of squares for the start of this line.
		 */
		for (b = 0; b < bands; b++) {
			memset(sum, 0, bands * sizeof(unsigned int));
			memset(sum2, 0, bands * sizeof(unsigned int));
		}
		p1 = p;
		for (j = 0; j < stdif->height; j++) {
			i = 0;
			for (x = 0; x < stdif->width; x++) {
				for (b = 0; b < bands; b++) {
					int t = p1[i++];

					sum[b] += t;
					sum2[b] += t * t;
				}
			}

			p1 += lsk;
		}

		/* Loop for output pels.
		 */
		for (x = 0; x < r->width; x++) {
			for (b = 0; b < bands; b++) {
				/* Find stats.
				 */
				double mean = (double) sum[b] / npel;
				double var = (double) sum2[b] / npel -
					(mean * mean);
				double sig = sqrt(var);

				/* Transform.
				 */
				double res = f1 + f2 * mean +
					((double) p[centre] - mean) *
						(f3 / (stdif->s0 + stdif->b * sig));

				/* And write.
				 */
				if (res < 0.0)
					*q++ = 0;
				else if (res >= 256.0)
					*q++ = 255;
				else
					*q++ = res + 0.5;

				/* Adapt sums - remove the pels from the left
				 * hand column, add in pels for a new
				 * right-hand column.
				 */
				p1 = p;
				for (j = 0; j < stdif->height; j++) {
					int t1 = p1[0];
					int t2 = p1[bands * stdif->width];

					sum[b] -= t1;
					sum2[b] -= t1 * t1;

					sum[b] += t2;
					sum2[b] += t2 * t2;

					p1 += lsk;
				}

				p += 1;
			}
		}
	}

	return 0;
}

static int
vips_stdif_build(VipsObject *object)
{
	VipsObjectClass *class = VIPS_OBJECT_GET_CLASS(object);
	VipsStdif *stdif = (VipsStdif *) object;
	VipsImage **t = (VipsImage **) vips_object_local_array(object, 3);

	VipsImage *in;

	if (VIPS_OBJECT_CLASS(vips_stdif_parent_class)->build(object))
		return -1;

	in = stdif->in;

	if (vips_image_decode(in, &t[0]))
		return -1;
	in = t[0];

	if (vips_check_format(class->nickname, in, VIPS_FORMAT_UCHAR))
		return -1;

	if (stdif->width > in->Xsize ||
		stdif->height > in->Ysize) {
		vips_error(class->nickname, "%s", _("window too large"));
		return -1;
	}
	if (in->Bands > MAX_BANDS) {
		vips_error(class->nickname, "%s", _("too many bands"));
		return -1;
	}

	/* Expand the input.
	 */
	if (vips_embed(in, &t[1],
			stdif->width / 2, stdif->height / 2,
			in->Xsize + stdif->width - 1, in->Ysize + stdif->height - 1,
			"extend", VIPS_EXTEND_COPY,
			NULL))
		return -1;
	in = t[1];

	g_object_set(object, "out", vips_image_new(), NULL);

	/* Set demand hints. FATSTRIP is good for us, as THINSTRIP will cause
	 * too many recalculations on overlaps.
	 */
	if (vips_image_pipelinev(stdif->out,
			VIPS_DEMAND_STYLE_FATSTRIP, in, NULL))
		return -1;
	stdif->out->Xsize -= stdif->width - 1;
	stdif->out->Ysize -= stdif->height - 1;

	if (vips_image_generate(stdif->out,
			vips_start_one,
			vips_stdif_generate,
			vips_stop_one,
			in, stdif))
		return -1;

	stdif->out->Xoffset = 0;
	stdif->out->Yoffset = 0;

	vips_reorder_margin_hint(stdif->out, stdif->width * stdif->height);

	return 0;
}

static void
vips_stdif_class_init(VipsStdifClass *class)
{
	GObjectClass *gobject_class = G_OBJECT_CLASS(class);
	VipsObjectClass *object_class = (VipsObjectClass *) class;

	gobject_class->set_property = vips_object_set_property;
	gobject_class->get_property = vips_object_get_property;

	object_class->nickname = "stdif";
	object_class->description = _("statistical difference");
	object_class->build = vips_stdif_build;

	VIPS_ARG_IMAGE(class, "in", 1,
		_("Input"),
		_("Input image"),
		VIPS_ARGUMENT_REQUIRED_INPUT,
		G_STRUCT_OFFSET(VipsStdif, in));

	VIPS_ARG_IMAGE(class, "out", 2,
		_("Output"),
		_("Output image"),
		VIPS_ARGUMENT_REQUIRED_OUTPUT,
		G_STRUCT_OFFSET(VipsStdif, out));

	/* Windows larger than 256x256 will overflow sum2, see above.
	 */
	VIPS_ARG_INT(class, "width", 4,
		_("Width"),
		_("Window width in pixels"),
		VIPS_ARGUMENT_REQUIRED_INPUT,
		G_STRUCT_OFFSET(VipsStdif, width),
		1, 256, 11);

	VIPS_ARG_INT(class, "height", 5,
		_("Height"),
		_("Window height in pixels"),
		VIPS_ARGUMENT_REQUIRED_INPUT,
		G_STRUCT_OFFSET(VipsStdif, height),
		1, 256, 11);

	VIPS_ARG_DOUBLE(class, "a", 2,
		_("Mean weight"),
		_("Weight of new mean"),
		VIPS_ARGUMENT_OPTIONAL_INPUT,
		G_STRUCT_OFFSET(VipsStdif, a),
		0.0, 1.0, 0.5);

	VIPS_ARG_DOUBLE(class, "m0", 2,
		_("Mean"),
		_("New mean"),
		VIPS_ARGUMENT_OPTIONAL_INPUT,
		G_STRUCT_OFFSET(VipsStdif, m0),
		-INFINITY, INFINITY, 128);

	VIPS_ARG_DOUBLE(class, "b", 2,
		_("Deviation weight"),
		_("Weight of new deviation"),
		VIPS_ARGUMENT_OPTIONAL_INPUT,
		G_STRUCT_OFFSET(VipsStdif, b),
		0.0, 2.0, 0.5);

	VIPS_ARG_DOUBLE(class, "s0", 2,
		_("Deviation"),
		_("New deviation"),
		VIPS_ARGUMENT_OPTIONAL_INPUT,
		G_STRUCT_OFFSET(VipsStdif, s0),
		-INFINITY, INFINITY, 50);
}

static void
vips_stdif_init(VipsStdif *stdif)
{
	stdif->width = 11;
	stdif->height = 11;
	stdif->a = 0.5;
	stdif->m0 = 128.0;
	stdif->b = 0.5;
	stdif->s0 = 50.0;
}

/**
 * vips_stdif: (method)
 * @in: input image
 * @out: (out): output image
 * @width: width of region
 * @height: height of region
 * @...: %NULL-terminated list of optional named arguments
 *
 * Optional arguments:
 *
 * * @a: weight of new mean
 * * @m0: target mean
 * * @b: weight of new deviation
 * * @s0: target deviation
 *
 * vips_stdif() performs statistical differencing according to the formula
 * given in page 45 of the book "An Introduction to Digital Image
 * Processing" by Wayne Niblack. This transformation emphasises the way in
 * which a pel differs statistically from its neighbours. It is useful for
 * enhancing low-contrast images with lots of detail, such as X-ray plates.
 *
 * At point (i,j) the output is given by the equation:
 *
 * |[
 * vout(i,j) = @a * @m0 + (1 - @a) * meanv +
 *       (vin(i,j) - meanv) * (@b * @s0) / (@s0 + @b * stdv)
 * ]|
 *
 * Values @a, @m0, @b and @s0 are entered, while meanv and stdv are the values
 * calculated over a moving window of size @width, @height centred on pixel
 * (i,j). @m0 is the new mean, @a is the weight given to it. @s0 is the new
 * standard deviation, @b is the weight given to it.
 *
 * Try:
 *
 * |[
 * vips stdif $VIPSHOME/pics/huysum.v fred.v 0.5 128 0.5 50 11 11
 * ]|
 *
 * The operation works on one-band uchar images only, and writes a one-band
 * uchar image as its result. The output image has the same size as the
 * input.
 *
 * See also: vips_hist_local().
 *
 * Returns: 0 on success, -1 on error
 */
int
vips_stdif(VipsImage *in, VipsImage **out, int width, int height, ...)
{
	va_list ap;
	int result;

	va_start(ap, height);
	result = vips_call_split("stdif", ap, in, out, width, height);
	va_end(ap);

	return result;
}
