/*********************************************************************
 * This file is part of the PRAPI library.
 *
 * Copyright (C) 2002 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.1 $
 *********************************************************************/

#include "../ImageTransform.h"
#include <MatrixUtils.h>
#include <Math.h>

namespace prapi { namespace binary {

	/**
	 * A class for "adaptive binarization". Instead of the global
	 * thresholding performed for example by
	 * util::MatrixUtils::compare(), this class is able of adaptively
	 * changing the binarization threshold. The threshold is presented
	 * as a percentage of a local average. That is, if you set the
	 * threshold to 0.1, pixel values less than 10% of the local average
	 * are set to zero. If the threshold is one, everything below
	 * average is reset to zero.<p>
	 *
	 * The thresholding works in two modes: "pixel" and "block". In
	 * pixel mode, each pixel is thresholded based on the neighborhood
	 * around it. In block mode, each neighborhood is thresholded
	 * block-wise.
	 **/
	class AdaptiveThresholding
	{
	public:
		/**
		 * Adaptively threshold an image.
		 *
		 * @param threshold the threshold
		 * @param pixelMode if true, thresholding is made pixel-wise
		 * @param neighborhoodSize the size of the neighborhood to
		 * consider in calculation
		 * @param setToOne if true, pixels that exceed the threshold are
		 * set to one. If false, they are left intact.
		 **/
		template <class T> static util::Matrix<T> threshold(const util::Matrix<T>& mat,
																												double threshold=1,
																												bool pixelMode=false,
																												int neighborhoodSize=16,
																												bool setToOne=false);

		/**
		 * An ImageTransform for the adaptive thresholding.
		 **/
		template <class T> class Transform;
	};


	template <class T> class AdaptiveThresholding::Transform : public ImageTransform<T, T>
	{
	public:
		/**
		 * Create a new ImageTransform object that performs the adaptive
		 * thresholding operation.
		 *
		 * @param threshold the threshold
		 * @param pixelMode if true, thresholding is made pixel-wise
		 * @param neighborhoodSize the size of the neighborhood to
		 * consider in calculation (NxN)
		 * @param setToOne if true, pixels that exceed the threshold are
		 * set to one. If false, they are left intact.
		 **/
		Transform(double threshold=1, bool pixelMode=false, int neighborhoodSize=16, bool setToOne=false) :
			_dThreshold(threshold), _bPixelMode(pixelMode), _iNeighborhood(neighborhoodSize), _bSetToOne(setToOne) {}

		util::Matrix<T> getTransformedImage(const util::Matrix<T>& mat) throw (ImageTransformException&)
		{
			return threshold(mat, _bPixelMode, _dThreshold, _iNeighborhood, _bSetToOne);
		}

	private:
		double _dThreshold;
		bool _bPixelMode;
		int _iNeighborhood;
		bool _bSetToOne;
	};


	template <class T> util::Matrix<T> AdaptiveThresholding::threshold(const util::Matrix<T>& mat,
																																		 double threshold,
																																		 bool pixelMode,
																																		 int size,
																																		 bool setToOne)
	{
		using namespace util;

		if (pixelMode)
			{
				Matrix<T> extended(MatrixUtils::extend(mat,size/2,MatrixUtils::EXTEND_SYMMETRIC));
				Matrix<T> neighborhood(size, size, false);
				Matrix<T> result(mat.getRows(), mat.getColumns(), false);
				
				for (int r=0; r<mat.getRows(); r++)
					for (int c=0; c<mat.getColumns(); c++)
						{
							neighborhood = extended(r,c,size,size);
							T val(mat(r,c));
							if (val < T(threshold*Math::mean(neighborhood)))
								result(r,c) = T(0);
							else if (setToOne)
								result(r,c) = T(1);
							else
								result(r,c) = val;
						}
				return result;
			}
		else
			{
				int diffR = mat.getRows() % size, diffC = mat.getColumns() % size;
				
				Matrix<T> extended(MatrixUtils::extend(MatrixUtils::extend(mat, diffR, MatrixUtils::EXTEND_SYMMETRIC, MatrixUtils::BOTTOM),
																							 diffC, MatrixUtils::EXTEND_SYMMETRIC, MatrixUtils::RIGHT));
				Matrix<T> neighborhood(size,size,false);
				for (int r=0; r<mat.getRows(); r+=size)
					for (int c=0; c<mat.getColumns(); c+=size)
						{
							neighborhood = extended(r,c,size,size);
							neighborhood = MatrixUtils::compare<std::greater<T> >(neighborhood, T(threshold*Math::mean(neighborhood)), setToOne);
							MatrixUtils::copy(neighborhood, extended, r, c);
						}

				return extended(0,0,mat.getRows(),mat.getColumns());
			}
	}
}}
