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

#ifndef _CALBP_H
#define _CALBP_H

#include "LBP.h"

namespace prapi { namespace texture {

	/**
	 * This class contains an implementation of the Cellular Automaton
	 * LBP. Texture features are extracted by calculating a number of
	 * cocentric LBP codes for each pixel. The one-dimensional cellular
	 * automaton rule that best encodes the binarized neighborhood of a
	 * pixel is derived. The distribution of the automaton rules is used
	 * as a texture feature.
	 **/
	template <class T> class CALBP : public FeatureExtractor<int, util::Matrix<T> >,
																	 public ImageTransform<int, T>
	{
	public:
		/**
		 * Create a new CALBP instance.
		 *
		 * @param scales the number of successive scales (neighborhood
		 * radii) to use in calculating the cocentric LBP codes. In many
		 * applications, 10 is a reasonable value. Must be larger than 1.
		 *
		 * @param samples the number of samples on each scale. For example
		 * 8. This value is limited by the number of bits in an integer
		 * (usually 32).
		 *
		 * @param startScale the radius of the innermost neighborhood. 
		 * This is 1 by default, but 2 may be a good choice if
		 * <i>samples</i> is larger than eight.
		 **/
		CALBP(unsigned int scales, unsigned int samples=8, unsigned int startScale=1) :
			_uiScales(scales), _uiSamples(samples), _uiStartScale(startScale), _bReduce(false) {}
		virtual ~CALBP() {}

		/**
		 * The CALBP operator works in two modes: reduced and full. In
		 * reduced mode, only the 32 most common rules are used. The rest
		 * are collected into one additional entry, resulting in
		 * 33-dimensional feature vectors. In full mode, all 256 ca rules
		 * are considered.
		 *
		 * @param reduced if false (the default), all ca rules are used,
		 * and feature vectors have 256 entries. If true, only the most
		 * common rules are used, and feature vectors have 33 entries.
		 **/
		void setReductionMode(bool reduce) { _bReduce = reduce; }

		/**
		 * Get the reduction mode.
		 **/
		bool getReductionMode() const { return _bReduce; }

		/**
		 * Fetch a distribution of cellular automaton rules from a
		 * gray-scale image.
		 *
		 * @return depending on the reduction mode, either a 256- or a
		 * 33-dimensional vector is returned
		 **/
		util::List<int> getFeatureVector(const util::Matrix<T>& image) throw (FeatureExtractionException&);

		/**
		 * Calculate the cellular automation rule number for each pixel in
		 * an image. Due to border effects, the returned image is smaller
		 * than the original.
		 *
		 * @param image the input image
		 *
		 * @return an image in which each pixel stores the cellular
		 * automaton code calcualted from its neighborhood
		 **/
		util::Matrix<int> getTransformedImage(const util::Matrix<T>& image) throw (ImageTransformException&);
	private:
		unsigned int _uiScales, _uiSamples, _uiStartScale;
		bool _bReduce;

		/**
		 * Given two binary numbers (val1 and val2), find out the
		 * occurrences of each cellular automaton rule, and place votes to
		 * the given list.
		 **/
		inline void vote(int val1, int val2, util::List<int>& votes);
	};

	/**
	 * A look-up table for pruning "useless" CA codes. Experience shows
	 * that about 95% of all image pixels can be covered by only 32
	 * different ca rules. The rest can be used as a single entry.
	 **/
	extern const int iaCALBPLookup[];

	template <class T> util::List<int> CALBP<T>::getFeatureVector(const util::Matrix<T>& image)
		throw (FeatureExtractionException&)
	{
		GeneralLBP<T> lbp(_uiSamples);
		util::List<util::Matrix<int> > images(_uiScales);
		for (unsigned int i=_uiStartScale; i<_uiScales + _uiStartScale; i++)
			{
				lbp.setPredicate(i);
				images += lbp.getTransformedImage(image);
			}

		int histogramLength = _bReduce ? 33 : 256;
		util::List<int> result(histogramLength);
		result.setLength(histogramLength, 0);

		int r,c;
		util::List<int> votes(8);
		votes.setLength(8);

		//Go through all pixels in the last image (smallest, due to
		//largest neighborhood radius)
		AllItems(r,c,images[_uiScales-1])
			{
				votes = 0;
				//Each LBP code is used to determine the CA that could create
				//the next binary pattern given the previous one.
				for (unsigned int i=1; i<_uiScales; i++)
					{
						int diff1 = _uiScales - i;
						int diff2 = _uiScales - i - 1;
						vote(images[i-1](r+diff1,c+diff1), images[i](r+diff2,c+diff2), votes);
					}

				int caCode = 0;
				//Negative vote means 0, positive 1. Shift the sign.
				for (int i=0; i<8; i++)
					caCode |= (votes[i] & 0x80000000) >> (31-i);

				if (_bReduce)
					result[iaCALBPLookup[caCode]]++;
				else
					result[caCode]++;
			}
		return result;
	}

	template <class T> util::Matrix<int> CALBP<T>::getTransformedImage(const util::Matrix<T>& image)
		throw (ImageTransformException&)
	{
		GeneralLBP<T> lbp(_uiSamples);
		util::List<util::Matrix<int> > images(_uiScales);
		for (unsigned int i=_uiStartScale; i<_uiScales + _uiStartScale; i++)
			{
				lbp.setPredicate(i);
				images += lbp.getTransformedImage(image);
			}

		int r,c;
		util::List<int> votes(8);
		votes.setLength(8);

		Matrix<int> result(images[_uiScales-1].getRows(),
											 images[_uiScales-1].getColumns());

		//Go through all pixels in the last image (smallest, due to
		//largest neighborhood radius)
		AllItems(r,c,images[_uiScales-1])
			{
				votes = 0;
				//Each LBP code is used to determine the CA that could create
				//the next binary pattern given the previous one.
				for (unsigned int i=1; i<_uiScales; i++)
					{
						int diff1 = _uiScales - i;
						int diff2 = _uiScales - i - 1;
						vote(images[i-1](r+diff1,c+diff1), images[i](r+diff2,c+diff2), votes);
					}

				int caCode = 0;
				//Negative vote means 0, positive 1. Shift the sign.
				for (int i=0; i<8; i++)
					caCode |= (votes[i] & 0x80000000) >> (31-i);

				if (_bReduce)
					result(r,c) = iaCALBPLookup[caCode];
				else
					result(r,c) = caCode;
			}
		return result;
	}

	template <class T> inline void CALBP<T>::vote(int val1, int val2, util::List<int>& votes)
	{
		//Initialize the value from which three-bit rules are to be found
		//by rotating val1 once to the left. This way, the three LSBs of
		//value present the neighborhood of the LSB in val2.
		unsigned int value = BitOperation::rol((unsigned int)val1, 1, _uiSamples);

		//Go through all bits
		for (unsigned int i=0; i<_uiSamples; i++)
			{
				//Take three LSBs as a vote list index, and either increment
				//or decrement the vote count depending on the corresponding
				//bit in val2.
				votes[value & 7] += (val2 & (1 << i)) ? -1 : 1;

				//Rotate the binary code so that 3 LSBs can be used in the
				//next round
				value = BitOperation::ror(value, 1, _uiSamples);
			}
	}
}}
		

#endif
