/* im_stats.c
 *
(C) Kirk Martinez 1993
23/4/93 J.Cupitt
	- adapted to partial images
	- special struct abandoned; now returns DOUBLEMASK.
1/7/93 JC
	- adapted for partial v2
	- ANSIfied
27/7/93 JC
	- init of mask changed to use actual values, not IM_MAXDOUBLE and
	  (-IM_MAXDOUBLE). These caused problems when assigned to floats.
	  funny business with offset==42, yuk!
31/8/93 JC
	- forgot to init global max/min properly! sorry.
21/6/95 JC
	- still did not init max and min correctly --- now fixed for good

 * 13/1/05
 *	- use 64 bit arithmetic 
 * 1/9/09
 *	- argh nope min/max was broken again for >1CPU in short pipelines on 
 *  	  some architectures
 * 7/9/09
 * 	- rework based on new im__wrapscan() / im_max() ideas for a proper fix
 * 	- gtkdoc comment
 */

/*

    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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

 */

/*

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

 */

/*
#define DEBUG
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /*HAVE_CONFIG_H*/
#include <vips/intl.h>

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

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

#ifdef WITH_DMALLOC
#include <dmalloc.h>
#endif /*WITH_DMALLOC*/

/* Track min/max/sum/sum-of-squares for each thread during a scan.
 */
static void *
stats_start( IMAGE *im, void *a, void *b )
{
	double *global_stats = (double *) b;

	double *stats;
	int i;

	if( !(stats = IM_ARRAY( NULL, 4 * im->Bands, double )) )
		return( NULL );

	for( i = 0; i < 4 * im->Bands; i++ )
		stats[i] = global_stats[i];

	return( stats );
}

/* Merge a thread's output back into the global stats.
 */
static int
stats_stop( void *seq, void *a, void *b )
{
	const IMAGE *im = (IMAGE *) a;
	double *global_stats = (double *) b;
	double *stats = (double *) seq;

	int i;

	for( i = 0; i < 4 * im->Bands; i += 4 ) {
		global_stats[0] = IM_MIN( global_stats[0], stats[0] );
		global_stats[1] = IM_MAX( global_stats[1], stats[1] );
		global_stats[2] += stats[2];
		global_stats[3] += stats[3];

		global_stats += 4;
		stats += 4;
	}

	im_free( seq );

	return( 0 );
}

/* We scan lines bands times to avoid repeating band loops.
 * Use temp variables of same type for min/max for faster comparisons.
 */
#define LOOP( TYPE ) { \
	for( z = 0; z < im->Bands; z++ ) { \
		TYPE *q = (TYPE *) in + z; \
		double *row = stats + z * 4; \
		TYPE small, big; \
		double sum, sum2; \
		\
		small = row[0]; \
		big = row[1]; \
		sum = row[2]; \
		sum2 = row[3]; \
		\
		for( x = 0; x < n; x++ ) { \
			TYPE value = *q; \
			\
			sum += value;\
			sum2 += (double) value * (double) value;\
			if( value > big ) \
				big = value; \
			else if( value < small ) \
				small = value;\
			\
			q += im->Bands; \
		} \
		\
		row[0] = small; \
		row[1] = big; \
		row[2] = sum; \
		row[3] = sum2; \
	} \
} 

/* Loop over region, adding to seq.
 */
static int
stats_scan( void *in, int n, void *seq, void *a, void *b )
{
	const IMAGE *im = (IMAGE *) a;

	double *stats = (double *) seq;

	int x, z;

	/* Now generate code for all types. 
	 */
	switch( im->BandFmt ) {
	case IM_BANDFMT_UCHAR:	LOOP( unsigned char ); break;
	case IM_BANDFMT_CHAR:	LOOP( signed char ); break; 
	case IM_BANDFMT_USHORT:	LOOP( unsigned short ); break; 
	case IM_BANDFMT_SHORT:	LOOP( signed short ); break; 
	case IM_BANDFMT_UINT:	LOOP( unsigned int ); break; 
	case IM_BANDFMT_INT:	LOOP( signed int ); break; 
	case IM_BANDFMT_DOUBLE:	LOOP( double ); break;
	case IM_BANDFMT_FLOAT:	LOOP( float ); break; 

	default: 
		g_assert( 0 );
	}

	return( 0 );
}

/**
 * im_stats:
 * @in: image to scan
 *
 * Find many image statistics in a single pass through the data. Returns a
 * #DOUBLEMASK of 6 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. Row 0 has statistics for all 
 * bands together, row 1 has stats for band 1, and so on.
 *
 * See also: im_maxpos(), im_min(), im_deviate().
 *
 * Returns: 0 on success, -1 on error
 */
DOUBLEMASK *
im_stats( IMAGE *im )
{	
	DOUBLEMASK *out;
	double *row;
	gint64 pels, vals;
	double *global_stats;
	int i, j;
	double value;

	if( im_pincheck( im ) ||
		im_check_noncomplex( "im_stats", im ) ||
		im_check_uncoded( "im_stats", im ) )
		return( NULL );

	if( !(global_stats = IM_ARRAY( im, 4 * im->Bands, double )) )
		return( NULL );
	if( im__value( im, &value ) )
		return( NULL );
	for( i = 0; i < 4 * im->Bands; i += 4 ) {
		global_stats[i + 0] = value;
		global_stats[i + 1] = value;
		global_stats[i + 2] = 0.0;
		global_stats[i + 3] = 0.0;
	}

	/* Loop over input, calculating min, max, sum, sum^2 for each band
	 * separately.
	 */
	if( im__wrapscan( im, stats_start, stats_scan, stats_stop, 
		im, global_stats ) ) 
		return( NULL );

	/* Calculate mean, deviation, plus overall stats.
	 */
	if( !(out = im_create_dmask( "stats", 6, im->Bands + 1 )) )
		return( NULL );

	/* Init global max/min/sum/sum2.
	 */
	out->coeff[0] = value;
	out->coeff[1] = value;
	out->coeff[2] = 0.0;
	out->coeff[3] = 0.0;

	pels = (gint64) im->Xsize * im->Ysize;
	vals = pels * im->Bands;

	for( i = 0; i < im->Bands; i++ ) {
		row = out->coeff + (i + 1) * 6;
		for( j = 0; j < 4; j++ )
			row[j] = global_stats[i * 4 + j];

		out->coeff[0] = IM_MIN( out->coeff[0], row[0] );
		out->coeff[1] = IM_MAX( out->coeff[1], row[1] );
		out->coeff[2] += row[2];
		out->coeff[3] += row[3];
		row[4] = row[2] / pels;
		row[5] = sqrt( fabs( row[3] - (row[2] * row[2] / pels) ) / 
			(pels - 1) );
	} 
	out->coeff[4] = out->coeff[2] / vals;
	out->coeff[5] = sqrt( fabs( out->coeff[3] - 
		(out->coeff[2] * out->coeff[2] / vals) ) / (vals - 1) );

#ifdef DEBUG
	printf( "im_stats:\n" );
	im_print_dmask( out );
#endif /*DEBUG*/

	return( out );
}
