/*                                                                            *
 *   This file is part of the ESO SINFO Pipeline                              *
 *   Copyright (C) 2004,2005 European Southern Observatory                    *
 *                                                                            *
 *   This library is free software; you can redistribute it and/or modify     *
 *   it under the terms of the GNU 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 General Public License for more details.                             *
 *                                                                            *
 *   You should have received a copy of the GNU General Public License        *
 *   along with this program; if not, write to the Free Software              *
 *   Foundation, 51 Franklin St, Fifth Floor, Boston, MA  02111-1307  USA     *
 *                                                                           */
 /*
 * $Author: amodigli $
 * $Date: 2013-08-02 14:23:25 $
 * $Revision: 1.11 $
 * $Name: not supported by cvs2svn $
  */
/*
 * set of libraries for testing kappa-sigma clipping
 */
#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif
#include <stdio.h>
#include <cpl.h>
#include <cpl_test.h>
#include <sinfo_globals.h>
#include <sinfo_pro_save.h>
#include <sinfo_error.h>
#include <sinfo_msg.h>
#include <sinfo_utils_wrappers.h>
#include <sinfo_new_cube_ops.h>


const int BUF_ARRAY_SIZE = 10;
const double EPSILON = 1E-5;
const char* KAPPA_SIGMA_BUG_REPORT = "amodigli@eso.org";

const double GAUSS_NORM = 1E7;
const int GAUSS_FWHM = 30;
const double K = 2.35482;//1. / (2. * sqrt(2.* ln(2.)));

const double DEFECT_LEVEL = 500;

struct _KS_Array_Data
{
	/*input parameters*/
	double buf[10];
	double kappa;
	/*output parameters*/
	int nInvalidPoints;
	double dResult;

};
typedef struct _KS_Array_Data KS_Array_Data;

// forward declarations
cpl_image* prepare_plane_gauss(int sizeX, int sizeY, double noiseLevel,
                               double signal_level, int GAUSS_CENTER_X,
                               int GAUSS_CENTER_Y, double gNORM, double gSIGMA);
cpl_image* prepare_plane_noise(int sizeX, int sizeY, double SIGNAL_LEVEL,
                               double SIGNAL_NOISE_LEVEL);
cpl_image* prepare_plane_square_shape();
void set_defect(cpl_image* pImage, int defectX, int defectY, double value);
///////////

int check_arr_data(KS_Array_Data* pData)
{
	int z = 0;
	const double exptimes [] = {0,0,0,0,0,0,0,0,0,0};

	cpl_array* pArray = cpl_array_new(BUF_ARRAY_SIZE, CPL_TYPE_DOUBLE);

	for (z = 0; z < BUF_ARRAY_SIZE; z++)
	{
		double value = pData->buf[z];
		if (!isnan(value))
			cpl_array_set(pArray, z, value);
	}
	double result = sinfo_kappa_sigma_array_with_mask(pArray, BUF_ARRAY_SIZE,
	                pData->kappa,0, exptimes, 0, 0,0);
	cpl_test_abs(result,pData->dResult,EPSILON);
	int resInvalidPoints = cpl_array_count_invalid(pArray);
	cpl_test_eq(resInvalidPoints, pData->nInvalidPoints);
	cpl_array_delete(pArray);
	return 0;
}


int kappa_sigma_array_test(void)
{
	int retval = 0;

	KS_Array_Data DataToCheck[] = {
			{
				{1,	2,	3,	4,	5,	6,	7,	8,	9,	1000},
				3,
				1,
				5,
			},
			{
				{4,	2,	3,	5,	5,	6,	7,	8,	1001,	1000},
				2,
				2,
				5,
			},
			{
				{1,	2,	3,	5,	5,	6,	7,	8,	4,	15},
				2,
				1,
				4.55556,
			},
			{
				{1,	2,	3,	5,	5,	6,	7,	8,	4,	15},
				3,
				0,
				5.6,
			},
			{
				{10,20,	23,	25,	25,	26,	27,	22,	21,	30},
				1.5,
				5,
				25.2,
			},
/*			{
				{nan(""),20,	22,	nan(""),20,	22,	nan(""),20,	22,	nan("")},
				3,
				0,
				21,
			},*/
			{
				{ZERO,20,	21, 22,	ZERO,20,	22,	ZERO,20,	22},
				3,
				3,
				21,
			},
/*			{
				{50,100,	50,	100,	50,	100,	50,	100,	50,	100},
				1.5,
				0,
				75,
			},
			{
				{50,100,	50,	100,	50,	100,	50,	100,	50,	100},
				3,
				0,
				75,
			},
			*/
	};

	int sz = sizeof(DataToCheck) / sizeof(DataToCheck[0]);
	int z = 0;
	for (z = 0; z < sz; z++)
	{
		retval += check_arr_data(&DataToCheck[z]);
	}
	return retval;
}

int kappa_sigma_chessboard_test()
{
	/* create a 2*N images with chessboard with 1 pixel shift for each
	 * coaddition should produce an image with the same values for the
     * each pixel (in the overlayed area)
     */
	const int sizeX = 24;
	const int sizeY = 24;
	const int nCubes = 2; // should be even
	double exptimes[nCubes];
	const double dBlackValue = 100;
	const double dWhiteValue = 50;
	const int shiftX = 3; // should be odd
	const int shiftY = 2; // should be even

	// coords of the overlapping image
	const int xminOver = (nCubes-1) * shiftX + 1;
	const int yminOver = (nCubes-1) * shiftY + 1;
	const int xmaxOver = sizeX;
	const int ymaxOver = sizeY;


	const double kappa = 3;
	cpl_imagelist* ppCubes[nCubes];

	int resSizeX = sizeX + (nCubes-1) * shiftX;
	int resSizeY = sizeY + (nCubes-1) * shiftY;
	int pOffsetX[nCubes];
	int pOffsetY[nCubes];

	cpl_imagelist* ilResult = cpl_imagelist_new();
	cpl_assert(ilResult);
	int z = 0;
	for (z = 0; z < nCubes; z++)
	{
		cpl_imagelist* pCube = cpl_imagelist_new();
		cpl_image* pImage = cpl_image_new(sizeX, sizeY, CPL_TYPE_DOUBLE);
		int x = 0;
		for (x = 1; x <= sizeX; x++)
		{
			int y = 0;
			for (y = 1; y <= sizeY; y++)
			{
				double value = ((x+y) & 1)? dBlackValue : dWhiteValue;
				check_nomsg(cpl_image_set(pImage, x,y,value));
			}
		}
		check_nomsg(cpl_imagelist_set(pCube, pImage, 0));
		ppCubes[z] = pCube;
		pOffsetX[z] = z * shiftX;
		pOffsetY[z] = z * shiftY;
	}

	cpl_image* imResult = 0;
	check_nomsg(imResult = cpl_image_new(resSizeX, resSizeY, CPL_TYPE_DOUBLE));
	check_nomsg(cpl_imagelist_set(ilResult, imResult ,0));
	cpl_imagelist* ilMask = cpl_imagelist_new();
	cpl_imagelist_set(ilMask, cpl_image_new(resSizeX, resSizeY, CPL_TYPE_DOUBLE), 0);
	sinfo_coadd_with_ks_clip_optimized(
				0,
				1,
				nCubes,
				kappa,
				pOffsetX,
				pOffsetY,
	            exptimes,
				ilMask,
				ilResult,
				ppCubes);

	check_nomsg( imResult = cpl_imagelist_get(ilResult, 0));
	cpl_assert(imResult);
	double dExpectedValue = (dBlackValue + dWhiteValue) / 2;

	// check result in the overlapping region
	int x = 0;
	for (x = xminOver; x <= xmaxOver; x++)
	{
		int y = 0;
		for (y = yminOver; y <= ymaxOver; y++)
		{
			int px = 0;
			double value = 0;
			cpl_assert(x <= resSizeX );
			cpl_assert(y <= resSizeY );
			check_nomsg(value = cpl_image_get(imResult, x,y, &px));
			cpl_test_abs(value,dExpectedValue,EPSILON);
		}
	}
	cpl_imagelist_delete(ilMask);
	cpl_imagelist_delete(ilResult);
	for (z = 0; z < nCubes; z++)
	{
		cpl_imagelist_delete(ppCubes[z]);
	}
	return 0;
	cleanup:
	cpl_assert(!"error in cpl function");
	cpl_imagelist_delete(ilResult);
	return 1;
}

enum _TEST_IMAGE_TYPE
{
	IT_NOISE,
	IT_GAUSS,
	IT_SQUARE,
};
typedef enum _TEST_IMAGE_TYPE TEST_IMAGE_TYPE;

int prepare_cube(cpl_imagelist** ppCubes, TEST_IMAGE_TYPE type, int sizeX, int sizeY, int nCubes, double noiseLevel, double signal_level, int defectX, int defectY)
{
	int z = 0;
	for (z = 0; z < nCubes; z++)
	{
		cpl_imagelist* pCube = cpl_imagelist_new();
		cpl_image* pImage;
		ppCubes[z] = pCube;
		// create a plane
		switch(type)
		{
			case  IT_NOISE:
				pImage = prepare_plane_noise(sizeY, sizeY, signal_level, noiseLevel);
				break;
			case IT_GAUSS:
				{
					double GAUSS_SIGMA = GAUSS_FWHM / K;
					pImage = prepare_plane_gauss(sizeX, sizeY,noiseLevel, signal_level, sizeX / 2, sizeY / 2, GAUSS_NORM, GAUSS_SIGMA);
				}
				break;
			case IT_SQUARE:
				cpl_assert(!"not implemented");
				pImage = prepare_plane_square_shape();
				break;
			default:
				cpl_assert(!"unknown plane type");
		}
		cpl_assert(pImage);
		if (z == 0 && (defectX * defectY > 0))
		{
			// put a defect only to the first cube
			set_defect(pImage, defectX, defectY, DEFECT_LEVEL);
		};
		check_nomsg(cpl_imagelist_set(pCube, pImage, 0));
	}
	return 0;
	cleanup:
	cpl_assert(!"error in prepare_cube");
	return 1;
}

cpl_image* prepare_plane_gauss(int sizeX, int sizeY, double noiseLevel, double signal_level, int GAUSS_CENTER_X, int GAUSS_CENTER_Y, double gNORM, double gSIGMA)
{
	cpl_image *pRet = NULL;
	cpl_image* pNoise = cpl_image_new(sizeX, sizeY, CPL_TYPE_DOUBLE);

	check_nomsg(cpl_image_fill_noise_uniform(pNoise, signal_level - noiseLevel, signal_level + noiseLevel));
	cpl_image* imGauss = cpl_image_new(sizeX, sizeY, CPL_TYPE_DOUBLE);
	check_nomsg(cpl_image_fill_gaussian(imGauss, GAUSS_CENTER_X, GAUSS_CENTER_Y, gNORM, gSIGMA, gSIGMA));
	check_nomsg(pRet = cpl_image_add_create(imGauss, pNoise));
	cpl_image_delete(imGauss);
	cpl_image_delete(pNoise);
	return pRet;
	cleanup:
	sinfo_msg_error("error in prepare_plane gauss");
	return NULL;
}

cpl_image* prepare_plane_noise(int sizeX, int sizeY, double SIGNAL_LEVEL, double SIGNAL_NOISE_LEVEL)
{
	// plane with noise
	cpl_image* pRet = cpl_image_new(sizeX, sizeY, CPL_TYPE_DOUBLE);
	check_nomsg(cpl_image_fill_noise_uniform(pRet, SIGNAL_LEVEL - SIGNAL_NOISE_LEVEL, SIGNAL_LEVEL + SIGNAL_NOISE_LEVEL));
	return pRet;
	cleanup:
	sinfo_msg_error("error in prepare_plain_noise");
	return NULL;
}

cpl_image* prepare_plane_square_shape()
{
	// TODO
	cpl_image* pRet = 0;

	return pRet;
}

void set_defect(cpl_image* pImage, int defectX, int defectY, double value)
{
	cpl_image_set(pImage, defectX-1, defectY+1, value);
	cpl_image_set(pImage, defectX+1, defectY+1, value);
	cpl_image_set(pImage, defectX-1, defectY-1, value);
	cpl_image_set(pImage, defectX+1, defectY-1, value);
	// NAN point to the center
	cpl_image_set(pImage, defectX, defectY,ZERO);
}

double image_get_max(cpl_image* imResult, int sizeX, int sizeY)
{
	double retval = 0;
	int x = 0;
	int y = 0;
	for (x = 1; x <= sizeX; x++)
	{
		for (y = 1; y <= sizeY; y++)
		{
			int px = 0;
			double value = cpl_image_get(imResult, x, y, &px);
			if (!isnan(value))
			{
				if (value > retval)
				{
					retval = value;
				}
			}
		}
	}
	return retval;
}

int kappa_sigma_gauss_narrow_test()
{

	const int sizeX = 64;
	const int sizeY = 64;
	const int defectPointX = 55;
	const int defectPointY = 55;
	const int nCubes = 10;
	double exptimes[nCubes];
	const double SIGNAL_LEVEL = 50;
	const double SIGNAL_NOISE_LEVEL = 5;
	const double kappa = 3;
	int pOffsetX[nCubes];
	int pOffsetY[nCubes];
	int z = 0;
	memset(&pOffsetX[0], 0, nCubes * sizeof(pOffsetX[0]));
	memset(&pOffsetY[0], 0, nCubes * sizeof(pOffsetY[0]));
	cpl_imagelist* ppCubes[nCubes];

	cpl_imagelist* ilResult = cpl_imagelist_new();
	cpl_assert(ilResult);
	cpl_image* imResult = 0;
	check_nomsg(imResult = cpl_image_new(sizeX, sizeY, CPL_TYPE_DOUBLE));
	check_nomsg(cpl_imagelist_set(ilResult, imResult ,0));

	prepare_cube(ppCubes, IT_GAUSS, sizeX,sizeY, nCubes, SIGNAL_NOISE_LEVEL,SIGNAL_LEVEL, defectPointX, defectPointY);

	cpl_imagelist* ilMask = cpl_imagelist_new();
	cpl_imagelist_set(ilMask, cpl_image_new(sizeX, sizeY, CPL_TYPE_DOUBLE), 0);
	sinfo_coadd_with_ks_clip_optimized(
				0,
				1,
				nCubes,
				kappa,
				pOffsetX,
				pOffsetY,
	            exptimes,
				ilMask,
				ilResult,
				ppCubes);

	check_nomsg( imResult = cpl_imagelist_get(ilResult, 0));

	///////////////////   checking the result

	// check the gauss maximum point
	int gaussX = sizeX / 2;
	int gaussY = sizeY / 2;
	int px = 0;
	// the peak on the result image
	double gauss_value = cpl_image_get(imResult, gaussX, gaussY, &px);

	double gauss_expected_max_value = 0;
	for (z = 0; z < nCubes; z++) // through all input images
	{
		gauss_expected_max_value += image_get_max(cpl_imagelist_get(ppCubes[z],0), sizeX, sizeY);
	}
	gauss_expected_max_value /= nCubes;
	cpl_test_abs(gauss_value, gauss_expected_max_value, SIGNAL_NOISE_LEVEL / 2);

	// 3. check FWHM
	double half_width_value = (gauss_expected_max_value -  SIGNAL_NOISE_LEVEL / 2) / 2;
	int x_max = 1;
	int y_stripe = sizeY / 2;
	double curr_value  =0;
	do
	{
		check_nomsg (curr_value = cpl_image_get(imResult, x_max, y_stripe, &px));
		if (curr_value > half_width_value )
		{
			break;
		}
		x_max++;
	}
	while(x_max < sizeX / 2);
	if (curr_value > 0)
	{
		x_max = (sizeX / 2 - x_max) * 2;
		// check the expected value
		cpl_test_abs(GAUSS_FWHM, x_max, 1);
	}
	else
	{
		cpl_assert(!"FWHM is not reached");
	}


	///////////////////////////////

	/*
	 *  Cleanup
	 */
	cpl_imagelist_delete(ilResult);
	cpl_imagelist_delete(ilMask);

	for (z = 0; z < nCubes; z++)
	{
		cpl_imagelist_delete(ppCubes[z]);
	}
	return 0;
	cleanup:
	cpl_assert(!"error in cpl function");
	cpl_imagelist_delete(ilResult);
	return 1;
}

static int kappa_sigma_round_shift_test()
{
	const int sizeX = 8;
	const int sizeY = 8;
	const int globalSizeX = 12;
	const int globalSizeY = 12;
	const int shift_index_X[] = {0, 0, 1, 1};
	const int shift_index_Y[] = {0, 1, 1, 0};
	const int nCubes = 4;
	double exptimes[nCubes];
	int pOffsetX[nCubes];
	int pOffsetY[nCubes];
	const double signal_levels[] = {11, 23, 37, 43};
	int minOver = 5;
	int maxOver = 8;
	double kappa = 3;
	double EXPOSITION_DEFAULT = 100;

	cpl_imagelist* ppCubes[nCubes];
	cpl_imagelist* ilResult = cpl_imagelist_new();
	cpl_image* imResult = 0;
	cpl_imagelist* ilMask = cpl_imagelist_new();
	cpl_image* imMask = cpl_image_new(globalSizeX, globalSizeY, CPL_TYPE_DOUBLE);
	int x = 0;
	int z = 0;
        int y = 0;
	check_nomsg(imResult = cpl_image_new(globalSizeX, globalSizeY, CPL_TYPE_DOUBLE));
	check_nomsg(cpl_imagelist_set(ilResult, imResult ,0));
	//1. prepare the cubes

	for (z = 0; z < nCubes; z++)
	{

		cpl_imagelist* pImList = cpl_imagelist_new();
		cpl_image* pImage = cpl_image_new(sizeX, sizeY, CPL_TYPE_DOUBLE);
		x = 0;
		y = 0;
		int value_index = 0;
		for (x = 1; x <= sizeX; x++)
		{
			for(y = 1; y <= sizeY; y++)
			{
				int index = value_index % nCubes;
				double value = (index == z) ? signal_levels[index] : ZERO ;
				check_nomsg(cpl_image_set(pImage, x, y, value));
				value_index++;
			}
			value_index--;
		}
		check_nomsg(cpl_imagelist_set(pImList, pImage, 0));
		ppCubes[z] = pImList;
		pOffsetX[z] = nCubes * shift_index_X[z];
		pOffsetY[z] = nCubes * shift_index_Y[z];
	}
	// prepare the mask
	for (x = 1; x <= sizeX; x++)
	{
		int y = 0;
		for (y = 1; y <= sizeY; y++)
		{
			check_nomsg(cpl_image_set(imMask, x, y, EXPOSITION_DEFAULT));
		}
	}
	// setup exposition values
	for (x = 0; x < nCubes; x++)
	{
		exptimes[x] = EXPOSITION_DEFAULT;
	}
	check_nomsg(cpl_imagelist_set(ilMask, imMask, 0));
	sinfo_coadd_with_ks_clip_optimized(
				0,
				1,
				nCubes,
				kappa,
				pOffsetX,
				pOffsetY,
	            exptimes,
				ilMask,
				ilResult,
				ppCubes);

	check_nomsg( imResult = cpl_imagelist_get(ilResult, 0));
	// check the result
	// values in the overlapping region should come from the same rule as during calculating input,
	// but without holes (NAN values);

	int index = 0;
	for (x = minOver; x <= maxOver; x++)
	{
		int y = 0;
		for (y = minOver; y <= maxOver; y++)
		{
			int px = 0;
			double value_expected = signal_levels[index % nCubes];
			double value = cpl_image_get(imResult, x, y, &px);
			cpl_test_abs(value, value_expected, EPSILON);
			index ++;
		}
		index--;
	}
	// cleanup
	cpl_imagelist_delete(ilResult);
	cpl_imagelist_delete(ilMask);
	for (z = 0; z < nCubes; z++)
	{
		cpl_imagelist_delete(ppCubes[z]);
	}
	return 0;
	cleanup:
	cpl_imagelist_delete(ilResult);
	cpl_imagelist_delete(ilMask);
	cpl_assert(!"error in cpl function");
	return 0;
}

static int kappa_sigma_outlier_test()
{
	// make a defect on the image with noise. kappa-sigma should remove the defect, defect should appear
	// then on the mask image
	const int sizeX = 10;
	const int sizeY = 10;

	const int nCubes = 10;
	double exptimes[nCubes];
	const double SIGNAL_LEVEL = 250;
	const double SIGNAL_NOISE_LEVEL = 10;
	const double kappa = 3;
	const double EXPOSITION_DEFAULT = 100;
	const int DEFECT_X = 5;
	const int DEFECT_Y = 5;

	int pOffsetX[nCubes];
	int pOffsetY[nCubes];
	memset(&pOffsetX[0], 0, nCubes * sizeof(pOffsetX[0]));
	memset(&pOffsetY[0], 0, nCubes * sizeof(pOffsetY[0]));



	cpl_imagelist* ppCubes[nCubes];
	cpl_imagelist* ilResult = cpl_imagelist_new();
	cpl_assert(ilResult);
	cpl_image* imResult = 0;
	check_nomsg(imResult = cpl_image_new(sizeX, sizeY, CPL_TYPE_DOUBLE));
	check_nomsg(cpl_imagelist_set(ilResult, imResult ,0));

	prepare_cube(ppCubes, IT_NOISE, sizeX,sizeY, nCubes, SIGNAL_NOISE_LEVEL,SIGNAL_LEVEL, DEFECT_X, DEFECT_Y);

	cpl_imagelist* ilMask = cpl_imagelist_new();
	cpl_image* imMask = cpl_image_new(sizeX, sizeY, CPL_TYPE_DOUBLE);
	int x = 0;
	for (x = 1; x <= sizeX; x++)
	{
		int y = 0;
		for (y = 1; y <= sizeY; y++)
		{
			cpl_image_set(imMask, x, y, EXPOSITION_DEFAULT);
		}
	}
	// setup exposition values
	for (x = 0; x < nCubes; x++)
	{
		exptimes[x] = EXPOSITION_DEFAULT;
	}
	cpl_imagelist_set(ilMask, imMask, 0);
	sinfo_coadd_with_ks_clip_optimized(
				0,
				1,
				nCubes,
				kappa,
				pOffsetX,
				pOffsetY,
	            exptimes,
				ilMask,
				ilResult,
				ppCubes);

	check_nomsg( imResult = cpl_imagelist_get(ilResult, 0));
	//

///////////////// check the result //////////////////////////////
	/*
	 * all points should have a value of signal +- noise - all defect pixels should be removed
	 * */

	for (x = 1; x <= sizeX; x++)
	{
		int y = 0;
		for (y = 1;y <= sizeY; y++)
		{
			int px = 0;
			double result_value = 0;
			result_value = cpl_image_get(imResult, x,y, &px);
			cpl_test_abs(result_value,SIGNAL_LEVEL,SIGNAL_NOISE_LEVEL);
		}
	}
	/*
	 * check the mask image - pixels on the defect places should be 0
	 * */
	double mask_value = 0;
	imMask = cpl_imagelist_get(ilMask, 0);
	cpl_assert(imMask);

	int px = 0;

	// value for the NAN point
	check_nomsg(mask_value = cpl_image_get(imMask, DEFECT_X, DEFECT_Y, &px));
	cpl_test_abs(mask_value, 0, EPSILON);
	// values for defect points (defect_level)
	check_nomsg(mask_value = cpl_image_get(imMask, DEFECT_X + 1, DEFECT_Y + 1, &px));
	cpl_test_abs(mask_value, 0, EPSILON);
	check_nomsg(mask_value = cpl_image_get(imMask, DEFECT_X - 1, DEFECT_Y - 1, &px));
	cpl_test_abs(mask_value, 0, EPSILON);
	check_nomsg(mask_value = cpl_image_get(imMask, DEFECT_X + 1, DEFECT_Y - 1, &px));
	cpl_test_abs(mask_value, 0, EPSILON);
	check_nomsg(mask_value = cpl_image_get(imMask, DEFECT_X - 1, DEFECT_Y + 1, &px));
	cpl_test_abs(mask_value, 0, EPSILON);

////////////////////////////////////////
	// cleanup
	cpl_imagelist_delete(ilResult);
	cpl_imagelist_delete(ilMask);
	int z = 0;
	for (z = 0; z < nCubes; z++)
	{
		cpl_imagelist_delete(ppCubes[z]);
	}
	return 0;
	cleanup:
	cpl_imagelist_delete(ilResult);
	cpl_assert(!"error in cpl function");
	return 0;
}

int main(void)
{
	cpl_test_init(KAPPA_SIGMA_BUG_REPORT, CPL_MSG_WARNING);
	kappa_sigma_array_test();
	kappa_sigma_chessboard_test();
	kappa_sigma_outlier_test();
	kappa_sigma_gauss_narrow_test();
	kappa_sigma_round_shift_test();
	return cpl_test_end(0);
}

/**@}*/
