/*********************************************************************
 * This file is part of the PRAPI library.
 *
 * Copyright (C) 2001-2003 Topi Mäenpää
 * Copyright (C) 2001 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.21 $
 *********************************************************************/

#ifndef _LBP_H
#define _LBP_H

#include <math.h>
#include <List.h>
#include <BitOperation.h>
#include <Matrix.h>
#include "CircularLocalSampler.h"
#include "../FeatureExtractor.h"
#include "../ImageTransform.h"
#include "../Indexer.h"

#define compab_mask_inc(ptr,shift) { value |= ((unsigned int)(cntr - *ptr) & 0x80000000) >> (31-shift); ptr++; }
#define compab_mask(val,shift) { value |= ((unsigned int)(cntr - (val)) & 0x80000000) >> (31-shift); }
#define compeq_mask_inc(ptr,shift) { if (*center == *ptr) value |= 1 << shift; ptr++; }
#define compeq_mask(val,shift) { if (*center == (val)) value |= 1 << shift; }

namespace prapi { namespace texture {

	/**
	 * A common superclass for all types of LBP feature extractors.
	 **/
	template <class T>
	class LBPExtractor : public CircularLocalSampler<int,T>, public FeatureExtractor<int, util::Matrix<T> >
	{
	public:
		/**
		 * Create a new LBPExtractor instance.
		 *
		 * @param samples the number of samples in the circular neighborhood
		 * @param predicate the sampling radius
		 * @param interpolated if true, bilinear interpolation is in use.
		 * Otherwise, nearest neighbor is used.
		 **/
		LBPExtractor(unsigned int samples = 8u, unsigned int predicate = 1u, bool interpolate=true) :
			CircularLocalSampler<int,T>(samples,predicate,interpolate) {}
		/**
		 * Create a new LBPExtractor instance.
		 *
		 * @param samples the number of samples in the circular neighborhood
		 * @param predicate the sampling radius
		 * @param interpolated if true, bilinear interpolation is in use.
		 * Otherwise, nearest neighbor is used.
		 **/
		LBPExtractor(unsigned int samples, double predicate) :
			CircularLocalSampler<int,T>(samples,predicate) {}
	};


	/**
	 * A class for creating mappings for the LBP operator.
	 **/
	class LBPMapping
	{
	public:
		/**
		 * Predefined mapping types.
		 * <ul>
		 * <li>MAP_UNIFORM: use only patterns that have at most two
		 * 1-to-0 or 0-to-1 transitions. Junk the rest in one value.</li>
		 * <li>MAP_ROTMIN: rotate patterns to their minimum values.</li>
		 * <li>MAP_UNIFORM_ROTMIN: use only uniform patterns and rotate
		 * them to their minimum values.</li>
		 * <li>MAP_NONE: no mapping</li>
		 * </ul>
		 **/
		enum MappingType { MAP_UNIFORM, MAP_ROTMIN, MAP_UNIFORM_ROTMIN, MAP_NONE };

		/**
		 * Get a mapping look-up table for the given mapping type. The
		 * returned value is a newly allocated array of integers in which
		 * each item represents the index the particular LBP code should
		 * be mapped to. The returned array will take up
		 * 2<sup>samples</sup>*sizeof(int) bytes of memory.
		 *
		 * @param samples the number of neighborhood samples
		 * @param type the type of mapping
		 * @return a look-up table
		 **/
		static int* getMapping(int samples, MappingType type);

		/**
		 * Get the number of distinct values the given mapping type can
		 * produce. This is useful in determining the length of the
		 * resulting feature vector when the mapping is in use.
		 *
		 * @param samples the number of neighborhood samples
		 * @param type the type of mapping
		 * @return the maximum value of the mapping (plus one, if type != MAP_NONE)
		 **/
		static int getSize(int samples, MappingType type);
	};
	
	/**
	 * A general implementation of the LBP texture operator. The
	 * template can be used with any bit count and predicate.
	 **/
	template <class T = int> class GeneralLBP : public LBPExtractor<T>
	{
	public:
		/**
		 * Predefined mapping types.
		 *
		 * @deprecated Use LBPMapping::MappingType instead
		 **/
		enum MappingType { MAP_UNIFORM, MAP_ROTMIN, MAP_UNIFORM_ROTMIN };

		/**
		 * Create a new GeneralLBP instance. Example:
		 * <pre>
		 * new GeneralLBP<>(16u,2u);
		 * </pre>
		 *
		 * @param samples the number of samples in the circular neighborhood
		 * @param predicate the sampling radius
		 * @param interpolated if true, bilinear interpolation is in use.
		 * Otherwise, nearest neighbor is used.
		 **/
		GeneralLBP(unsigned int samples = 8u, unsigned int predicate = 1u, bool interpolate = true);
		/**
		 * Create a new GeneralLBP instance. Example:
		 * <pre>
		 * new GeneralLBP<>(24u, 5.3);
		 * </pre>
		 *
		 * @param samples the number of samples in the circular neighborhood
		 * @param predicate the sampling radius
		 **/
		GeneralLBP(unsigned int samples, double predicate);
		virtual ~GeneralLBP();
		
		util::List<int> getFeatureVector(const util::Matrix<T>& mat) throw (FeatureExtractionException&);
		util::Matrix<int> getTransformedImage(const util::Matrix<T>& mat) throw (ImageTransformException&);

		/**
		 * Set the look-up table for pattern value mapping. Use
		 * setMapping(NULL,-1) to disable the mapping.
		 *
		 * @param mapping the look-up table. The size of this table must
		 * be equal to 2^samples integers. GeneralLBP takes up the
		 * ownership of the pointer, and deletes it upon destruction. If
		 * you want to keep a copy, make it explicitly.
		 *
		 * @param maxVal the maximum value this mapping will produce plus
		 * one
		 **/
		void setMapping(int* mapping, int maxVal);

		/**
		 * Set the mapping to one of the predefined types.
		 *
		 * @param type the mapping type
		 **/
		void setMapping(LBPMapping::MappingType type);
			
		void setMapping(MappingType type);

		/**
		 * Get the maximum value (+1) this LBP extractor will produce as a
		 * feature. This value is determined either by the mapping or by
		 * the number of bits. The length of a feature vector produced by
		 * getFeatureVector(const Matrix&) equals to the returned value.
		 *
		 * @return the maximum value for a feature +1.
		 **/
		int getMaxValue() const { return _ipPatternMap? _iMaxMappingValue : (1<<_uiSamples); }

		/**
		 * Get the currently active mapping.
		 *
		 * @return the currently active mapping (2<sup>bits</sup> values)
		 * or NULL, if there is no mapping defined
		 **/
		const int* getMapping() const { return _ipPatternMap; }
		/**
		 * Get the currently active mapping.
		 *
		 * @return the currently active mapping (2<sup>bits</sup> values)
		 * or NULL, if there is no mapping defined
		 **/
		int* getMapping() { return _ipPatternMap; }
		
	private:
		int* _ipPatternMap;
		int _iMaxMappingValue;
	};


	/**
	 * An optimized implementation of the 8-bit LBP operator for
	 * integer-valued matrices. This version can only be used with
	 * integer-valued neighborhood radii.
	 **/
	class LBP8 : public LBPExtractor<int>
	{
	public:
		/**
		 * Create a new LBP8 feature extractor.
		 **/
		LBP8(unsigned int predicate = 1u, bool interpolate=true) : LBPExtractor<int>(8,predicate,interpolate) {}

		/**
		 * Do nothing. Prevent the user from setting the number of
		 * samples.
		 **/
		void setSamples(unsigned int samples) {}

		util::List<int> getFeatureVector(const util::Matrix<int>& mat) throw (FeatureExtractionException&);
		util::Matrix<int> getTransformedImage(const util::Matrix<int>& mat) throw (ImageTransformException&);
	};


	template <class T> GeneralLBP<T>::GeneralLBP(unsigned int samples, unsigned int predicate, bool interpolate) :
		LBPExtractor<T>(samples, predicate, interpolate), _ipPatternMap(NULL), _iMaxMappingValue(-1) {}
	template <class T> GeneralLBP<T>::GeneralLBP(unsigned int samples, double predicate) :
		LBPExtractor<T>(samples, predicate), _ipPatternMap(NULL), _iMaxMappingValue(-1) {}

	template <class T> GeneralLBP<T>::~GeneralLBP()
	{
		delete[] _ipPatternMap;
	}

	template <class T> void GeneralLBP<T>::setMapping(int* mapping, int maxVal)
	{
		if (mapping != _ipPatternMap)
			{
				delete[] _ipPatternMap;
				_ipPatternMap = mapping;

				_iMaxMappingValue = maxVal;
			}
	}

	template <class T> void GeneralLBP<T>::setMapping(LBPMapping::MappingType type)
	{
		setMapping(LBPMapping::getMapping(_uiSamples, type),
							 LBPMapping::getSize(_uiSamples, type));
	}

	template <class T> void GeneralLBP<T>::setMapping(MappingType type)
	{
		setMapping(LBPMapping::getMapping(_uiSamples, (LBPMapping::MappingType)type),
							 LBPMapping::getSize(_uiSamples, (LBPMapping::MappingType)type));
	}

	template <class T> util::List<int> GeneralLBP<T>::getFeatureVector(const util::Matrix<T>& mat) throw (FeatureExtractionException&)
	{
		int resultSize = (_ipPatternMap != NULL)? _iMaxMappingValue : (1<<_uiSamples);
		util::IntegerList result(resultSize);
		result.setLength(resultSize);
		memset(result.getData(),0,sizeof(int)*resultSize);
		
		int rows = mat.getRows(), columns = mat.getColumns();
		const T* center = mat.getData() + _uiPredicate * (1 + columns);
		const T* ptr[_uiSamples];
		
		unsigned int value,base;
		int pred2 = _uiPredicate << 1;
		if (!_bInterpolated)
			{
				for (unsigned int i=0;i<_uiSamples;i++)
					{
						int xc = round(_pPoints[i].x + _pOffsets[i].x);
						int yc = round(_pPoints[i].y + _pOffsets[i].y);
						ptr[i] = center + xc + yc * columns;
					}

				for (int r=0;r<rows-pred2;r++)
					{
						for (int c=0;c<columns-pred2;c++)
							{
								value = 0;
								base = 1;
								for (unsigned int i=0;i<_uiSamples;i++)
									{
										if (*ptr[i] >= *center)
											value |= base;
										base <<= 1;
										ptr[i]++;
									}
									
								center++;

								if (_ipPatternMap == NULL)
									result[value]++;
								else
									result[_ipPatternMap[value]]++;
							}
						for (unsigned int i=0;i<_uiSamples;i++)
							ptr[i] += pred2;
						center += pred2;
					}
			}
		else
			{
				for (unsigned int i=0;i<_uiSamples;i++)
					ptr[i] = center + _pPoints[i].x + _pPoints[i].y * columns;
				
				for (int r=0;r<rows-pred2;r++)
					{
						for (int c=0;c<columns-pred2;c++)
							{
								value = 0;
								base = 1;
								for (unsigned int i=0;i<_uiSamples;i++)
									{
										if (interpolateAtPtr(ptr[i],i,columns) >= *center)
											value |= base;
										base <<= 1;
										ptr[i]++;
									}
									
								center++;

								if (_ipPatternMap == NULL)
									result[value]++;
								else
									result[_ipPatternMap[value]]++;
							}
						for (unsigned int i=0;i<_uiSamples;i++)
							ptr[i] += pred2;
						center += pred2;
					}
			}
		return result;
	}

	
	template <class T> util::Matrix<int> GeneralLBP<T>::getTransformedImage(const util::Matrix<T>& mat) throw (ImageTransformException&)
	{
		int rows = mat.getRows(), columns = mat.getColumns();
		const T* center = mat.getData() + _uiPredicate * (1 + columns);
		const T* ptr[_uiSamples];

		int pred2 = _uiPredicate << 1;

		util::Matrix<int> result(rows-pred2,columns-pred2);
		int *resultPtr = result.getData();
		
		unsigned int value,base;
		if (!_bInterpolated)
			{
				for (unsigned int i=0;i<_uiSamples;i++)
					{
						int xc = round(_pPoints[i].x + _pOffsets[i].x);
						int yc = round(_pPoints[i].y + _pOffsets[i].y);
						ptr[i] = center + xc + yc * columns;
					}
				
				for (int r=0;r<rows-pred2;r++)
					{
						for (int c=0;c<columns-pred2;c++)
							{
								value = 0;
								base = 1;
								for (unsigned int i=0;i<_uiSamples;i++)
									{
										if (*ptr[i] >= *center)
											value |= base;
										base <<= 1;
										ptr[i]++;
									}
									
								center++;

								if (_ipPatternMap == NULL)
									*resultPtr = value;
								else
									*resultPtr = _ipPatternMap[value];
								resultPtr++;
							}
						for (unsigned int i=0;i<_uiSamples;i++)
							ptr[i] += pred2;
						center += pred2;
					}
			}
		else
			{
				for (unsigned int i=0;i<_uiSamples;i++)
					ptr[i] = center + _pPoints[i].x + _pPoints[i].y * columns;
				for (int r=0;r<rows-pred2;r++)
					{
						for (int c=0;c<columns-pred2;c++)
							{
								value = 0;
								base = 1;
								//cerr << "Center: " << *center << endl;
								for (unsigned int i=0;i<_uiSamples;i++)
									{
										//cerr << "*ptr[" << i << "]: " << *ptr[i] << " -> "; // << interpolateAtPtr(ptr[i],i,columns) << endl;
										//fprintf(stderr,"%30.28f\n",interpolateAtPtr(ptr[i],i,columns));
										if (interpolateAtPtr(ptr[i],i,columns) >= *center)
											value |= base;
										base <<= 1;
										ptr[i]++;
									}
									
								center++;

								if (_ipPatternMap == NULL)
									*resultPtr = value;
								else
									*resultPtr = _ipPatternMap[value];
								resultPtr++;
							}
						for (unsigned int i=0;i<_uiSamples;i++)
							ptr[i] += pred2;
						center += pred2;
					}
			}
		return result;
	}
}}

#endif
