/************************************************************************
  			Blob.h
  			
FUNCIONALITAT: Definició de la classe CBlob
AUTOR: Inspecta S.L.
MODIFICACIONS (Modificació, Autor, Data):

FUNCTIONALITY: Definition of the CBlob class and some helper classes to perform
			   some calculations on it
AUTHOR: Inspecta S.L.
MODIFICATIONS (Modification, Author, Date):

**************************************************************************/

//! Disable warnings referred to 255 character truncation for the std:map
#pragma warning( disable : 4786 ) 

#ifndef CBLOB_INSPECTA_INCLUDED
#define CBLOB_INSPECTA_INCLUDED

#include <OpenCV/OpenCV.h>
#include "BlobLibraryConfiguration.h"
#include <functional>
#include <vector>
#include <algorithm>


#ifdef BLOB_OBJECT_FACTORY
	//! Object factory pattern implementation
	#include "..\inspecta\DesignPatterns\ObjectFactory.h"
#endif


//! Factor de conversió de graus a radians
#define DEGREE2RAD		(CV_PI / 180.0)
/**
	Classe que representa un blob, entés com un conjunt de pixels del 
	mateix color contigus en una imatge binaritzada.

	Class to represent a blob, a group of connected pixels in a binary image
*/
class CBlob
{
public:
	//! Constructor estàndard
	//! Standard constructor
	CBlob();
	//! Constructor de còpia
	//! Copy constructor
	CBlob( const CBlob &src );
	CBlob( const CBlob *src );

	//! Destructor estàndard
	//! Standard Destructor
	~CBlob();
	
	//! Operador d'assignació
	//! Assigment operator
	CBlob& operator=(const CBlob &src );

	//! Indica si el blob està buit ( no té cap info associada )
	//! Shows if the blob has associated information
	bool IsEmpty() const
	{
		return (area == 0.0 && perimeter == 0.0 );
	};

	//! Neteja les cantonades del blob
	//! Clears the edges of the blob
	void ClearEdges();
	//! Copia les cantonades del blob a un altre (les afegeix al destí)
	//! Adds the blob edges to another blob
	void CopyEdges( CBlob &destination ) const;
	//! Retorna el poligon convex del blob
	//! Calculates the convex hull of the blob
	bool GetConvexHull( CvSeq **dst ) const;
	//! Calcula l'elipse que s'adapta als vèrtexs del blob
	//! Fits an ellipse to the blob edges
	CvBox2D GetEllipse() const;

	//! Pinta l'interior d'un blob d'un color determinat
	//! Paints the blob in an image
	void FillBlob( IplImage *imatge, CvScalar color, int offsetX = 0, int offsetY = 0 ) const;

	//! Funcions GET sobre els valors dels blobs
	//! Get functions

	inline int Label() const	{ return etiqueta; }
	inline int Parent() const	{ return parent; }
	inline double Area() const { return area; }
	inline double Perimeter() const { return perimeter; }
	inline double ExternPerimeter() const { return externPerimeter; }
	inline int	  Exterior() const { return exterior; }
	inline double Mean() const { return mean; }
	inline double StdDev() const { return stddev; }
	inline double MinX() const { return minx; }
	inline double MinY() const { return miny; }
	inline double MaxX() const { return maxx; }
	inline double MaxY() const { return maxy; }
	inline CvSeq *Edges() const { return edges; }
	inline double SumX() const { return sumx; }
	inline double SumY() const { return sumy; }
	inline double SumXX() const { return sumxx; }
	inline double SumYY() const { return sumyy; }
	inline double SumXY() const { return sumxy; }
	
	//! etiqueta del blob
	//! label of the blob
	int etiqueta;
	//! flag per indicar si es exterior o no
	//! true for extern blobs
	int exterior;
	//! area del blob
	//! Blob area
	double area;
	//! perimetre del blob
	//! Blob perimeter
	double perimeter;
	//! quantitat de perimetre del blob extern
	//! amount of blob perimeter which is exterior
	double externPerimeter;
	//! etiqueta del blob pare
	//! label of the parent blob
	int parent;
	//! moments
	double sumx;
	double sumy;
	double sumxx;
	double sumyy;
	double sumxy;
	//! Bounding rect
	double minx;
	double maxx;
	double miny;
	double maxy;
	
	//! mitjana
	//! mean of the grey scale values of the blob pixels
	double mean;
	//! desviació standard
	//! standard deviation of the grey scale values of the blob pixels
	double stddev;

	//! àrea de memòria on es desaran els punts de contorn del blob
	//! storage which contains the edges of the blob
	CvMemStorage *m_storage;
	//!	Sequència de punts del contorn del blob
	//! Sequence with the edges of the blob
	CvSeq *edges;
	

	//! Point datatype for plotting (FillBlob)
	typedef std::vector<CvPoint> vectorPunts;

	//! Helper class to compare two CvPoints (for sorting in FillBlob)
	struct comparaCvPoint : public std::binary_function<CvPoint, CvPoint, bool> 
	{
		//! Definim que un punt és menor com més amunt a la dreta estigui
		bool operator()(CvPoint a, CvPoint b) 
		{ 
			if( a.y == b.y ) 
				return a.x < b.x;
			else 
				return a.y < b.y; 
		}
	};
};



/**************************************************************************
		Definició de les classes per a fer operacions sobre els blobs

		Helper classes to perform operations on blobs
**************************************************************************/


//! Classe d'on derivarem totes les operacions sobre els blobs
//! Interface to derive all blob operations
class COperadorBlob
{
public:
	virtual ~COperadorBlob(){};

	//! Aplica l'operació al blob
	virtual double operator()(const CBlob &blob) const = 0;
	//! Obté el nom de l'operador
	virtual const char *GetNom() const = 0;

	operator COperadorBlob*() const
	{
		return (COperadorBlob*)this;
	}
};

typedef COperadorBlob funcio_calculBlob;

#ifdef BLOB_OBJECT_FACTORY
	/**
		Funció per comparar dos identificadors dins de la fàbrica de COperadorBlobs
	*/
	struct functorComparacioIdOperador
	{
	  bool operator()(const char* s1, const char* s2) const
	  {
		return strcmp(s1, s2) < 0;
	  }
	};

	//! Definition of Object factory type for COperadorBlob objects
	typedef ObjectFactory<COperadorBlob, const char *, functorComparacioIdOperador > t_OperadorBlobFactory;

	//! Funció global per a registrar tots els operadors definits a blob.h
	void RegistraTotsOperadors( t_OperadorBlobFactory &fabricaOperadorsBlob );

#endif

//! Classe per calcular l'àrea d'un blob
//! Class to get the area of a blob
class CBlobGetArea : public COperadorBlob
{
public:
    double operator()(const CBlob &blob) const
	{ 
		return blob.Area(); 
	}
	const char *GetNom() const
	{
		return "CBlobGetArea";
	}
};

//! Classe per calcular el perimetre d'un blob
//! Class to get the perimeter of a blob
class CBlobGetPerimeter: public COperadorBlob
{
public:
    double operator()(const CBlob &blob) const
	{ 
		return blob.Perimeter(); 
	}
	const char *GetNom() const
	{
		return "CBlobGetPerimeter";
	}
};

//! Classe que diu si un blob és extern o no
//! Class to get the extern flag of a blob
class CBlobGetExterior: public COperadorBlob
{
public:
    double operator()(const CBlob &blob) const
	{ 
		return blob.Exterior(); 
	}
	const char *GetNom() const
	{
		return "CBlobGetExterior";
	}
};

//! Classe per calcular la mitjana de nivells de gris d'un blob
//! Class to get the mean grey level of a blob
class CBlobGetMean: public COperadorBlob
{
public:
    double operator()(const CBlob &blob) const
	{ 
		return blob.Mean(); 
	}
	const char *GetNom() const
	{
		return "CBlobGetMean";
	}
};

//! Classe per calcular la desviació estàndard dels nivells de gris d'un blob
//! Class to get the standard deviation of the grey level values of a blob
class CBlobGetStdDev: public COperadorBlob
{
public:
    double operator()(const CBlob &blob) const
	{ 
		return blob.StdDev(); 
	}
	const char *GetNom() const
	{
		return "CBlobGetStdDev";
	}
};

//! Classe per calcular la compacitat d'un blob
//! Class to calculate the compactness of a blob
class CBlobGetCompactness: public COperadorBlob
{
public:
    double operator()(const CBlob &blob) const;
	const char *GetNom() const
	{
		return "CBlobGetCompactness";
	}
};

//! Classe per calcular la longitud d'un blob
//! Class to calculate the length of a blob
class CBlobGetLength: public COperadorBlob
{
public:
    double operator()(const CBlob &blob) const;
	const char *GetNom() const
	{
		return "CBlobGetLength";
	}
};

//! Classe per calcular l'amplada d'un blob
//! Class to calculate the breadth of a blob
class CBlobGetBreadth: public COperadorBlob
{
public:
    double operator()(const CBlob &blob) const;
	const char *GetNom() const
	{
		return "CBlobGetBreadth";
	}
};

//! Classe per calcular la diferència en X del blob
class CBlobGetDiffX: public COperadorBlob
{
public:
    double operator()(const CBlob &blob) const
	{
		return blob.maxx - blob.minx;
	}
	const char *GetNom() const
	{
		return "CBlobGetDiffX";
	}
};

//! Classe per calcular la diferència en X del blob
class CBlobGetDiffY: public COperadorBlob
{
public:
    double operator()(const CBlob &blob) const
	{
		return blob.maxy - blob.miny;
	}
	const char *GetNom() const
	{
		return "CBlobGetDiffY";
	}
};

//! Classe per calcular el moment PQ del blob
//! Class to calculate the P,Q moment of a blob
class CBlobGetMoment: public COperadorBlob
{
public:
	//! Constructor estàndard
	//! Standard constructor (gets the 00 moment)
	CBlobGetMoment()
	{
		m_p = m_q = 0;
	}
	//! Constructor: indiquem el moment p,q a calcular
	//! Constructor: gets the PQ moment
	CBlobGetMoment( int p, int q )
	{
		m_p = p;
		m_q = q;
	};
	double operator()(const CBlob &blob) const;
	const char *GetNom() const
	{
		return "CBlobGetMoment";
	}

private:
	//! moment que volem calcular
	int m_p, m_q;
};

//! Classe per calcular el perimetre del poligon convex d'un blob
//! Class to calculate the convex hull perimeter of a blob
class CBlobGetHullPerimeter: public COperadorBlob
{
public:
    double operator()(const CBlob &blob) const;
	const char *GetNom() const
	{
		return "CBlobGetHullPerimeter";
	}
};

//! Classe per calcular l'àrea del poligon convex d'un blob
//! Class to calculate the convex hull area of a blob
class CBlobGetHullArea: public COperadorBlob
{
public:
    double operator()(const CBlob &blob) const;
	const char *GetNom() const
	{
		return "CBlobGetHullArea";
	}
};

//! Classe per calcular la x minima en la y minima
//! Class to calculate the minimum x on the minimum y
class CBlobGetMinXatMinY: public COperadorBlob
{
public:
    double operator()(const CBlob &blob) const;
	const char *GetNom() const
	{
		return "CBlobGetMinXatMinY";
	}
};

//! Classe per calcular la y minima en la x maxima
//! Class to calculate the minimum y on the maximum x
class CBlobGetMinYatMaxX: public COperadorBlob
{
public:
    double operator()(const CBlob &blob) const;
	const char *GetNom() const
	{
		return "CBlobGetMinYatMaxX";
	}
};

//! Classe per calcular la x maxima en la y maxima
//! Class to calculate the maximum x on the maximum y
class CBlobGetMaxXatMaxY: public COperadorBlob
{
public:
    double operator()(const CBlob &blob) const;
	const char *GetNom() const
	{
		return "CBlobGetMaxXatMaxY";
	}
};

//! Classe per calcular la y maxima en la x minima
//! Class to calculate the maximum y on the minimum y
class CBlobGetMaxYatMinX: public COperadorBlob
{
public:
    double operator()(const CBlob &blob) const;
	const char *GetNom() const
	{
		return "CBlobGetMaxYatMinX";
	}
};

//! Classe per a calcular la x mínima
//! Class to get the minimum x
class CBlobGetMinX: public COperadorBlob
{
public:
    double operator()(const CBlob &blob) const
	{
		return blob.MinX();
	}
	const char *GetNom() const
	{
		return "CBlobGetMinX";
	}
};

//! Classe per a calcular la x màxima
//! Class to get the maximum x
class CBlobGetMaxX: public COperadorBlob
{
public:
    double operator()(const CBlob &blob) const
	{
		return blob.MaxX();
	}
	const char *GetNom() const
	{
		return "CBlobGetMaxX";
	}
};

//! Classe per a calcular la y mínima
//! Class to get the minimum y
class CBlobGetMinY: public COperadorBlob
{
public:
    double operator()(const CBlob &blob) const
	{
		return blob.MinY();
	}
	const char *GetNom() const
	{
		return "CBlobGetMinY";
	}
};

//! Classe per a calcular la y màxima
//! Class to get the maximum y
class CBlobGetMaxY: public COperadorBlob
{
public:
    double operator()(const CBlob &blob) const
	{
		return blob.MaxY();
	}
	const char *GetNom() const
	{
		return "CBlobGetMax";
	}
};


//! Classe per calcular l'elongacio d'un blob
//! Class to calculate the elongation of the blob
class CBlobGetElongation: public COperadorBlob
{
public:
    double operator()(const CBlob &blob) const;
	const char *GetNom() const
	{
		return "CBlobGetElongation";
	}
};

//! Classe per calcular la rugositat d'un blob
//! Class to calculate the roughness of the blob
class CBlobGetRoughness: public COperadorBlob
{
public:
    double operator()(const CBlob &blob) const;
	const char *GetNom() const
	{
		return "CBlobGetRoughness";
	}
};

//! Classe per calcular la distància entre el centre del blob i un punt donat
//! Class to calculate the euclidean distance between the center of a blob and a given point
class CBlobGetDistanceFromPoint: public COperadorBlob
{
public:
	//! Standard constructor (distance to point 0,0)
	CBlobGetDistanceFromPoint()
	{
		m_x = m_y = 0.0;
	}
	//! Constructor (distance to point x,y)
	CBlobGetDistanceFromPoint( const double x, const double y )
	{
		m_x = x;
		m_y = y;
	}

    double operator()(const CBlob &blob) const;
	const char *GetNom() const
	{
		return "CBlobGetDistanceFromPoint";
	}

private:
	// coordenades del punt on volem calcular la distància
	double m_x, m_y;
};

//! Classe per calcular el nombre de pixels externs d'un blob
//! Class to get the number of extern pixels of a blob
class CBlobGetExternPerimeter: public COperadorBlob
{
public:
    double operator()(const CBlob &blob) const
	{
		return blob.ExternPerimeter();
	}
	const char *GetNom() const
	{
		return "CBlobGetExternPerimeter";
	}
};

//! Classe per calcular el ratio entre el perimetre i nombre pixels externs
//! valors propers a 0 indiquen que la majoria del blob és intern
//! valors propers a 1 indiquen que la majoria del blob és extern
//! Class to calculate the ratio between the perimeter and the number of extern pixels
class CBlobGetExternPerimeterRatio: public COperadorBlob
{
public:
    double operator()(const CBlob &blob) const
	{
		if( blob.Perimeter() != 0 )
			return blob.ExternPerimeter() / blob.Perimeter();
		else
			return blob.ExternPerimeter();
	}
	const char *GetNom() const
	{
		return "CBlobGetExternPerimeterRatio";
	}
};

//! Classe per calcular el ratio entre el perimetre convex i nombre pixels externs
//! valors propers a 0 indiquen que la majoria del blob és intern
//! valors propers a 1 indiquen que la majoria del blob és extern
//! Class to calculate the ratio between the perimeter and the number of extern pixels
class CBlobGetExternHullPerimeterRatio: public COperadorBlob
{
public:
    double operator()(const CBlob &blob) const
	{
		CBlobGetHullPerimeter getHullPerimeter;
		double hullPerimeter;

		if( (hullPerimeter = getHullPerimeter( blob ) ) != 0 )
			return blob.ExternPerimeter() / hullPerimeter;
		else
			return blob.ExternPerimeter();
	}
	const char *GetNom() const
	{
		return "CBlobGetExternHullPerimeterRatio";
	}
};

//! Classe per calcular el centre en el eix X d'un blob
//! Class to calculate the center in the X direction
class CBlobGetXCenter: public COperadorBlob
{
public:
    double operator()(const CBlob &blob) const
	{
		return blob.MinX() + (( blob.MaxX() - blob.MinX() ) / 2.0);
	}
	const char *GetNom() const
	{
		return "CBlobGetXCenter";
	}
};

//! Classe per calcular el centre en el eix Y d'un blob
//! Class to calculate the center in the Y direction
class CBlobGetYCenter: public COperadorBlob
{
public:
    double operator()(const CBlob &blob) const
	{
		return blob.MinY() + (( blob.MaxY() - blob.MinY() ) / 2.0);
	}
	const char *GetNom() const
	{
		return "CBlobGetYCenter";
	}
};

//! Classe per calcular la longitud de l'eix major d'un blob
//! Class to calculate the length of the major axis of the ellipse that fits the blob edges
class CBlobGetMajorAxisLength: public COperadorBlob
{
public:
    double operator()(const CBlob &blob) const
	{
		CvBox2D elipse = blob.GetEllipse();

		return elipse.size.width;
	}
	const char *GetNom() const
	{
		return "CBlobGetMajorAxisLength";
	}
};

//! Classe per calcular el ratio entre l'area de la elipse i la de la taca
//! Class 
class CBlobGetAreaElipseRatio: public COperadorBlob
{
public:
    double operator()(const CBlob &blob) const
	{
		if( blob.Area()==0.0 ) return 0.0;

		CvBox2D elipse = blob.GetEllipse();
		double ratioAreaElipseAreaTaca = ( (elipse.size.width/2.0)
										   *
										   (elipse.size.height/2.0)
							               *CV_PI
						                 )
									     /
									     blob.Area();

		return ratioAreaElipseAreaTaca;
	}
	const char *GetNom() const
	{
		return "CBlobGetAreaElipseRatio";
	}
};

//! Classe per calcular la longitud de l'eix menor d'un blob
//! Class to calculate the length of the minor axis of the ellipse that fits the blob edges
class CBlobGetMinorAxisLength: public COperadorBlob
{
public:
    double operator()(const CBlob &blob) const
	{
		CvBox2D elipse = blob.GetEllipse();

		return elipse.size.height;
	}
	const char *GetNom() const
	{
		return "CBlobGetMinorAxisLength";
	}
};

//! Classe per calcular l'orientació de l'ellipse del blob en radians
//! Class to calculate the orientation of the ellipse that fits the blob edges in radians
class CBlobGetOrientation: public COperadorBlob
{
public:
    double operator()(const CBlob &blob) const
	{
		CvBox2D elipse = blob.GetEllipse();

		if( elipse.angle > 180.0 )
			return (( elipse.angle - 180.0 )* DEGREE2RAD);
		else
			return ( elipse.angle * DEGREE2RAD);

	}
	const char *GetNom() const
	{
		return "CBlobGetOrientation";
	}
};

//! Classe per calcular el cosinus de l'orientació de l'ellipse del blob
//! Class to calculate the cosinus of the orientation of the ellipse that fits the blob edges
class CBlobGetOrientationCos: public COperadorBlob
{
public:
    double operator()(const CBlob &blob) const
	{
		CBlobGetOrientation getOrientation;
		return fabs( cos( getOrientation(blob) ));
	}
	const char *GetNom() const
	{
		return "CBlobGetOrientationCos";
	}
};


//! Classe per calcular el ratio entre l'eix major i menor de la el·lipse
//! Class to calculate the ratio between both axes of the ellipse
class CBlobGetAxisRatio: public COperadorBlob
{
public:
    double operator()(const CBlob &blob) const
	{
		CvBox2D elipse = blob.GetEllipse();

		return elipse.size.height / elipse.size.width;
	}
	const char *GetNom() const
	{
		return "CBlobGetAxisRatio";
	}
};


//! Classe per calcular si un punt cau dins del blob
//! Class to calculate whether a point is inside a blob
class CBlobGetXYInside: public COperadorBlob
{
public:
	//! Constructor estàndard
	//! Standard constructor
	CBlobGetXYInside()
	{
		m_p = cvPoint(0,0);
	}
	//! Constructor: indiquem el punt
	//! Constructor: sets the point
	CBlobGetXYInside( CvPoint p )
	{
		m_p = p;
	};
	double operator()(const CBlob &blob) const;
	const char *GetNom() const
	{
		return "CBlobGetXYInside";
	}

private:
	//! punt que considerem
	//! point to be considered
	CvPoint m_p;
};

#endif //CBLOB_INSPECTA_INCLUDED

