/*********************************************************************
 * This file is part of the PRAPI library.
 *
 * Copyright (C) 2001 Topi Mäenpää and Jaakko Viertola
 * All rights reserved.
 *
 * This program is free software. You can redistribute and/or modify
 * it under the terms of the free software licence found in the
 * accompanying file "COPYING". The licence terms must always be
 * redistributed with this source file. The above copyright notice
 * must be reproduced in all modified and unmodified copies of this
 * source file.
 *
 * $Revision: 1.16 $
 *********************************************************************/

#ifndef _HISTOGRAM_H
#define _HISTOGRAM_H

#include <Matrix.h>
#include "FeatureExtractor.h"
#include "Quantizer.h"
#include "ImageTransform.h"
#include "VectorQuantizer.h"
#include <Math.h>

using namespace util;

namespace prapi
{
	/**
	 * Produces a histogram from any matrix that holds integers.
	 **/
	class IntegerHistogram : public FeatureExtractor<int,Matrix<int> >
	{
	public:
		/**
		 * Create a new IntegerHistogram feature extractor with the
		 * maximum length of histogram set to <i>length</i>. If the length
		 * is not set, then the length of the produced histograms will
		 * equal to maximum value found in an matrix plus one.
		 **/
		IntegerHistogram(int length = 0) : _iLength(length) {}

		/**
		 * Create the histogram.
		 **/
		List<int> getFeatureVector(const Matrix<int>& mat) throw (FeatureExtractionException&);

		/**
		 * Set the length of the produced histograms. 0 = automatic. If a
		 * value larger than <i>length</i> is found in the matrix, the
		 * length of the histogram will exceed <i>length</i>.
		 **/
		void setLength(int length) { _iLength = length; }
		/**
		 * Get the length of the histograms this extractor produces.
		 **/
		int getLength(void) const { return _iLength; }
		
	private:
		int _iLength;
	};

	
	/**
	 * MultiFeatureHistogram produces many types of histograms out of
	 * layered feature matrices. Each matrix layer represents a quantized
	 * feature, and the features can be combined e.g. by producing a
	 * multi-dimensional histogram.
	 **/
	class MultiFeatureHistogram : public FeatureExtractor<int, List<Matrix<int> > >
	{
	public:
		class LayerCombiner;
		class Merge;
		class Concatenate;
		class MultiDimensional;
		class VQ;

		/**
		 * Create a new MultiFeatureHistogram feature extractor with the
		 * given layer combiner.
		 **/
		MultiFeatureHistogram(const LayerCombiner& combiner) : _layerCombiner(combiner) {}

		/**
		 * Generate the histogram. This methods loops through the pixels
		 * on the first image in <i>lst</i>. It constructs a vector by
		 * collecting all corresponding pixels, i.e. pixels with the same
		 * coordinates, from all images, in the order they appear in
		 * <i>lst</i>. For each constructed vector, the modifyHistogram
		 * method of the internal layer combiner is called. Finally, a
		 * histogram is returned. Note that the images in <i>lst</i> must
		 * be of exactly the same size.
		 **/
		List<int> getFeatureVector(const List<Matrix<int> >& lst) throw (FeatureExtractionException&);

	private:
		const LayerCombiner& _layerCombiner;
	};

	/**
	 * LayerCombiner is an interface for different types of
	 * combination schemes.
	 **/
	class MultiFeatureHistogram::LayerCombiner
	{
	public:
		virtual ~LayerCombiner() {}
		
		/**
		 * Modify the histogram.
		 *
		 * @param pixelValues a list of integers representing the
		 * quantized feature values for each channel.
		 * @param histogram the histogram to modify according to the pixel
		 * values.
		 **/
		virtual void modifyHistogram(const List<int>& pixelValues,
																 List<int>& histogram) const = 0;
		/**
		 * Get the length of the histogram this combiner will produce.
		 **/
		virtual int getHistogramLength() const  = 0;
	};

	/**
	 * The Merge combiner merges histograms so that the pixels in all
	 * layers are collected to a single histogram.
	 **/
	class MultiFeatureHistogram::Merge : public MultiFeatureHistogram::LayerCombiner
	{
	public:
		/**
		 * Create a new Merge combiner with the given histogram length.
		 **/
		Merge(int levels) : _iLevels(levels) {}
		
		virtual void modifyHistogram(const List<int>& pixelValues,
																 List<int>& histogram) const;

		virtual int getHistogramLength() const { return _iLevels; }

	private:
		int _iLevels;
	};

	/**
	 * The Concatenate combiner concatenates histograms built
	 * separately for each layer.
	 **/
	class MultiFeatureHistogram::Concatenate : public MultiFeatureHistogram::LayerCombiner
	{
	public:
		/**
		 * Create a new Concatenate combiner with the given number of
		 * channels and quantization levels for each channel.
		 **/
		Concatenate(int channels, int levels);
		/**
		 * Create a new Concatenate combiner with the given number of
		 * quantization levels for each channel.
		 **/
		Concatenate(List<int> levels);
		
		virtual void modifyHistogram(const List<int>& pixelValues,
																 List<int>& histogram) const;

		virtual int getHistogramLength() const { return _iLength; }

	private:
		List<int> _ilstLevels;
		int _iLength;
	};

	/**
	 * The MultiDimensional combiner builds up a multi-dimensional
	 * histogram using the pixel values in each layer as coordinates
	 * in a multi-dimensional feature space.
	 **/
	class MultiFeatureHistogram::MultiDimensional : public MultiFeatureHistogram::LayerCombiner
	{
	public:
		/**
		 * Create a new MultiDimensional combiner with the given number of
		 * channels and quantization levels for each channel.
		 **/
		MultiDimensional(int channels, int levels);
		/**
		 * Create a new MultiDimensional combiner with the given number of
		 * quantization levels for each channel.
		 **/
		MultiDimensional(List<int> levels);
		
		virtual void modifyHistogram(const List<int>& pixelValues,
																 List<int>& histogram) const;

		virtual int getHistogramLength() const { return _iLength; }

	private:
		List<int> _ilstMultipliers;
		int _iLength;
	};

	/**
	 * The VQ combiner uses a VectorQuantizer to get a bin index for
	 * each multi-feature pixel. In fact, this type of a combiner is not
	 * strictly necessary as one could quantize the layered matrix prior
	 * to making the histogram. It is provided to allow easy
	 * implementation of different types of histograms. Please note that
	 * in most applications, this type of a combiner is a massive
	 * performance hog.
	 **/
	class MultiFeatureHistogram::VQ : public MultiFeatureHistogram::LayerCombiner
	{
	public:
		/**
		 * Create a new VQ combiner with the given vector quantizer.
		 **/
		VQ(const VectorQuantizer& quantizer) : _vectorQuantizer(quantizer) {}
		
		virtual void modifyHistogram(const List<int>& pixelValues,
																 List<int>& histogram) const
		{ histogram[_vectorQuantizer.getBinIndex(pixelValues)]++; }

		virtual int getHistogramLength() const { return _vectorQuantizer.getMaxIndex()+1; }
	private:
		const VectorQuantizer& _vectorQuantizer;
	};
	

	/**
	 * A feature extractor that produces multi-dimensional histogram out
	 * of a list of matrixs. Input matrices are treated as quantized
	 * feature matrixs, and corresponding entries (pixels) are treated as
	 * coordinates in the histogram. That is, if you have two feature
	 * matrices, say LBP and contrast, each pixel is placed into the
	 * histogram using the LBP value as the first coordinate and
	 * contrast value as the second one.
	 *
	 * @deprecated Use MultiFeatureHistogram instead
	 **/
	class MultiDimensionalHistogram : public FeatureExtractor<int, List<Matrix<int> > >
	{
	public:
		/**
		 * Create a new MuldiDimensionalHistogram feature extractor with
		 * the given maximum values for each dimension.
		 *
		 * @param dimensions the maximum value for each histogram
		 * dimension. The minimum value for each dimension is zero. If
		 * values larger than those provided as maximum values are
		 * encountered during feature vector calculation, the result will
		 * be undefined. An error may also occur.
		 **/
		MultiDimensionalHistogram(List<int> dimensions);

		/**
		 * Generate a multi-dimensional histogram using feature values as
		 * coordinates in the histogram. The length of the matrix list must
		 * be equal to the length of the dimensions list given in the
		 * constructor.
		 **/
		List<int> getFeatureVector(const List<Matrix<int> >& lst) throw (FeatureExtractionException&);

	private:
		List<int> _ilstMultipliers;
		int _iLength;
	};

	/**
	 * Produces a histogram from any matrix containing primitive data
	 * types by using a Quantizer.
	 **/
	template <class T> class GeneralHistogram : public FeatureExtractor<int, Matrix<T> >
	{
	public:
		/**
		 * Create an new GeneralHistogram that quantized matrix (image)
		 * data using the given quantizer.
		 **/
		GeneralHistogram(Quantizer* q) { quantizer = q; }

		/**
		 * Calculate the histogram.
		 **/
		List<int> getFeatureVector(const Matrix<T>& mat) throw (FeatureExtractionException&);

	private:
		Quantizer* quantizer;
	};

	/** 
	 * A wrapper class for different types of histogram operations.
	 * Histogram operations transform integer matrices to integer matrices.
	 **/
	class HistogramOperation : public ImageTransform<int,int>
	{
	public:
		class Equalization;
		class ContrastStretching;
		class ZNormalization;
	};

	template <class T> List<int> GeneralHistogram<T>::getFeatureVector(const Matrix<T>& mat) throw (FeatureExtractionException&)
	{
		int levels = quantizer->getLevels();
		List<int> result(levels);
		result.setLength(levels);
		memset(result.getData(),0,levels*sizeof(int));
		
		const T* data = mat.getData();
		for (int i=0;i<mat.getColumns()*mat.getRows();i++,data++)
			{
				int value = quantizer->getBinIndex(*data);
				result[value]++;
			}
		return result;
	}

	/**
	 * Makes the Histogram Equalization for a given Matrix.
	 **/
	class HistogramOperation::Equalization : public HistogramOperation
		{
		public:
			Matrix<int> getTransformedImage(const Matrix<int>& mat) throw (ImageTransformException&);
		};

	/**
	 * Makes the ContrastStreching for a given Matrix.
	 **/
	class HistogramOperation::ContrastStretching : public HistogramOperation
		{
		public:
			/**
			 * ContrastStreching. If constructor don't get any parameters it finds the
			 * range where it could strech the values. If wanted to specify the range give
			 * the new ranges to minValue and maxValue and the old value for oldMinValue
			 * and oldMaxValue.
			 *
			 * @param minValue Minium value of area which wanted to stretch
			 * @param maxValue Maxium value of area which wanted to stretch
			 * @param oldMinValue the Minium value of old histogram which wanted to
			 *                    stretch for value minValue
			 * @param oldMaxValue the Maxium value of old histogram which wanted to
			 *                    stretch for value maxValue
			 **/
			ContrastStretching(int minValue=0, int maxValue=255,int oldMinValue=0,int oldMaxValue=0) :
				_iMin(minValue),_iMax(maxValue),_iOldMin(oldMinValue),_iOldMax(oldMaxValue){}

			Matrix<int> getTransformedImage(const Matrix<int>& mat) throw (ImageTransformException&);
		private:
			int _iMin,_iMax,_iOldMin,_iOldMax;
		};

	/**
	 * Z-normalizes an image. In z-normalization, the mean and standard
	 * deviation of the gray scale distribution are set to predefined
	 * values.
	 **/
	class HistogramOperation::ZNormalization : public HistogramOperation
		{
		public:
			/**
			 * Construct a new ZNormalization.
			 *
			 * @param newMean the new mean value for the gray level distribution.
			 * @param newDev the new standard deviation.
			 **/
			ZNormalization(double newMean, double newDev,int levels = 255) :
				_dNewMean(newMean),_dNewDev(newDev),_iLevels(levels){} 

			Matrix<int> getTransformedImage(const Matrix<int>& mat) throw (ImageTransformException&);

		private:
			double _dNewMean;
			double _dNewDev;
			int _iLevels;
		};
}
#endif
