package patternRecognition.textures.glzm.glszm;

import imageTiTi.reducer.ColorReducer;

import java.awt.image.BufferedImage;

import arrayTiTi.ArrayNew;
import arrayTiTi.ArrayOperations;

import morphee.levelings.Leveling;

import patternRecognition.ComputableFeatures;


/**
 * <p>Description: This class fill the Multiple Gray Level Size Zone Matrix (MGLSZM) and its features.<br>
 * List of features (same than Run Length Matrix). They are moments (orders -2 a 2) and variances.<br>
 *  - F0 => Small Zone Emphasis, SZE.<br>
 *  - F1 => Large Zone Emphasis, LZE.<br>
 *  - F2 => Low Gray level Zone Emphasis, LGZE.<br>
 *  - F3 => High Gray level Zone Emphasis, HGZE.<br>
 *  - F4 => Small Zone Low Gray level Emphasis, SZLGE.<br>
 *  - F5 => Small Zone High Gray level Emphasis, SZHGE.<br>
 *  - F6 => Large Zone Low Gray level Emphasis, LZLGE.<br>
 *  - F7 => Large Zone High Gray Level Emphasis, LZHGE.<br>
 *  - F8 => Gray Level Non Uniformity, GLNU.<br>
 *  - F9 => Size Zone Non Uniformity, SZNU.<br>
 *  - F10 => Zone Percentage, ZPC.<br>
 *  - F11 => Barycentre sur les niveaux de gris.<br>
 *  - F12 => Barycentre sur les tailles de zones.<br>
 *  - F13 => Variance ponderee sur les niveaux de gris.<br>
 *  - F14 => Variance ponderee sur tailles des zones.<br>
 *  - F15 => Orientation.<br>
 * </p>
 * <p>Package(s) required: imageTiTi, morphee, utils.</p>
 * <p>Copyright: Copyright (c) 2006-Today.</p>
 * <p>Updates:<br>
 * June 24 2010 => Creation.</p>
 * 
 * <p>
 * <!-- technical-bibtex-start -->
 * BibTeX:
 * <pre>
 * &#64;conference{ThibaultICIP11,
 * 		address = {Brussel, Belgium},
 *		author = {Guillaume Thibault and Jesus Angulo and Fernand Meyer},
 *		booktitle = {IEEE International Conference on Image Processing (ICIP)},
 *		pages = {53--56},
 *		title = {Advanced Statistical Matrices for Texture Characterization: 
 *				Application to DNA Chromatin and Microtubule Network Classification},
 *		year = {2011}
 *		}
 * </pre>
 * <!-- technical-bibtex-end -->
 * </p>
 * 
 * @author Guillaume THIBAULT
 * @version 1.0
 */

public class MGlszm implements ComputableFeatures
{

/** La classe qui calcule la matrice de tailles de zones.*/
protected GrayLevelSizeZoneMatrix glszm = new GrayLevelSizeZoneMatrix() ;
/** Nombre de niveaux de gris de l'image.*/
protected int GrayLevelMax = -1 ;
/** Nombre de tailles de zones a prendre en compte. Si FixedSize=true ce sera la largeur de la matrice, sinon c'est le pas.*/
protected int nbSizes = -1 ;
/** Largeur de la matrice. Si FixedSize Alors Width=nbSizes, Sinon Width=TailleZoneMax/nbSizes+1.*/
protected int Width = -1 ;
/** La largeur doit elle etre fixe ? */
protected boolean FixedSize = false ;
/** Tableau representant la size zone matrix.*/
protected double[][] matrix = null ;
/** Tableau de coefficients qui permet de ponderer les differents niveaux de gris pour le calcul de la matrice finale.*/
protected double[] Coefficients = null ;


/** The array which contains features.*/
private double[] Features = new double[16] ;
/** The sum of all elements in the matrix.*/
private double Sum = 0.0 ;
/** Features names.*/
private String[] FeaturesNames = new String[]{"SZE", "LZE", "LGZE", "HGZE", "SZLGE", "SZHGE", "LZLGE", "LZHGE",
											"GLNU", "SZNU", "ZPC", "BARYGL", "BARYS", "VARGL", "VARS", "ORIE"} ;



/** The color reducer to use.*/
protected ColorReducer reducer = null ;
/** The leveling to use.*/
protected Leveling leveling = null ;
/** The forbidden value during processing.*/
protected int ForbiddenValue = -1 ;
/** Is the 8-connexty used?*/
protected boolean EightConnex = true ;
/** The number of thread to use.*/
protected int nbCPU = 1 ;





public MGlszm()
	{
	}




/** This method realizes the processing. Do not call the method "FillMatrix" (useless because it is automatically called).
 * @param image The image to characterize.
 * @param nbSizes Number of size of zones. If FixedSize=true it will be the matrix width, else it will be the step.
 * @param FixedSize Is the width fixed?
 * @param reducer This class reduce the number of gray level.
 * @param leveling The leveling to use before the color reduction. If null, any leveling realized.
 * @param ForbiddenValue The backgorund value or value to not take into account during processing. If negative, so the entire
 *  texture is processed.
 * @param EightConnex Use 8-connexity for labeling?
 * @param nbCPU Number of thread to use for processing.*/
public void Compute(BufferedImage image, int nbSizes, boolean FixedSize, ColorReducer reducer,
						Leveling leveling, int ForbiddenValue, boolean EightConnex, int nbCPU)
	{
	int i, x, y, n, nb, width, step, nbCoefs ;
	double[][] m = null ;
	
	switch ( image.getType() )
		{
		case BufferedImage.TYPE_BYTE_GRAY : GrayLevelMax = 256 ;
			if ( Coefficients == null || Coefficients.length != 8 ) AllocCoefficients(8) ;
			nbCoefs = Coefficients.length ;
			if ( nbCoefs != 8 ) throw new Error("Wrong number of coefficients (8 waited for BYTE_GRAY images): " + nbCoefs) ;
			break ;
		case BufferedImage.TYPE_USHORT_GRAY : GrayLevelMax = 65536 ;
			if ( Coefficients == null || Coefficients.length != 16 ) AllocCoefficients(16) ;
			nbCoefs = Coefficients.length ;
			if ( nbCoefs != 16 ) throw new Error("Wrong number of coefficients (16 waited for USHORT_GRAY images): "+nbCoefs) ;
			break ;
		default : throw new IllegalArgumentException("Only gray level images supported.") ;
		}
	
	matrix = null ;
	this.nbSizes = nbSizes ;
	this.FixedSize = FixedSize ;
	
	for (n=2, nb=0 ; n <= GrayLevelMax ; n*=2, nb++)
		{
		glszm.FillMatrix(image, n, nbSizes, FixedSize, reducer, leveling, ForbiddenValue, EightConnex, nbCPU) ;
		m = glszm.matrix ;
		
		if ( matrix == null ) // Le niveau de gris le plus bas produit les plus grandes zones, donc on initialise le premier.
			{
			Width = m[0].length ;
			matrix = new double[GrayLevelMax][Width] ;
			ArrayOperations.Fill(matrix, 0.0) ;
			}
		
		step = GrayLevelMax / n ;
		width = m[0].length ;
		for (x=0 ; x < width ; x++) // On parcours la matrice courante (pour n niveaux de gris)
			for (y=0 ; y < n ; y++)
				for (i=0 ; i < step ; i++) // On Žtale ses valeurs dans la matrice finale.
					matrix[step*y+i][x] += Coefficients[nb] * m[y][x] ;
		
		m = null ;
		}
	
	ComputeFeatures() ;
	}



/** This method realizes the processing.<br>
 * CAUTION: parameters must be set (method parameters) or the method FillMatrix (with all parameters) must be called beforehand.
 * @param image The image to characterize.
 * @param nbCPU Number of thread to use for processing.*/
public void Compute(BufferedImage image, int nbCPU)
	{
	Compute(image, nbSizes, FixedSize, reducer, leveling, ForbiddenValue, EightConnex, nbCPU) ;
	}




/** This method realizes processing for a stack of images; processing of each image and average on features.
 * @param images The image to process.
 * @param nbSizes Number of size of zones. If FixedSize=true it will be the matrix width, else it will be the step.
 * @param FixedSize Is the width fixed?
 * @param reducer This class reduce the number of gray level.
 * @param leveling The leveling to use before the color reduction. If null, any leveling realized.
 * @param ForbiddenValue The background value or value to not take into account during processing. If negative, so the entire
 *  texture is processed.
 * @param EightConnex Use 8-connexity for labeling?
 * @param nbCPU Number of thread to use for processing.*/
public void ComputeFromStack(BufferedImage[] images, int nbSizes, boolean FixedSize, ColorReducer reducer,
								Leveling leveling, int ForbiddenValue, boolean EightConnex, int nbCPU)
	{
	int i, j ;
	int[] nbNanF1 = new int[Features.length] ;
	double[] F1 = new double[Features.length] ; // Tableaux temporaires contenant la somme des caractŽristiques.

	for (i=0 ; i < images.length ; i++)
		{
		Compute(images[i], nbSizes, FixedSize, reducer, leveling, ForbiddenValue, EightConnex, nbCPU) ;
		
		for (j=0 ; j < F1.length ; j++)
			if ( Double.isNaN(Features[j])) nbNanF1[j]++ ; // On compte le nombre de Nan pour ajuster les calculs.
			else F1[j] += Features[j] ; // On sauvegarde les rŽsultats.
		}
	
	for (j=0 ; j < F1.length ; j++) Features[j] = F1[j] / (double)(images.length-nbNanF1[j]) ;
	F1 = null ;
	nbNanF1 = null ;
	}






/** Methode qui permet d'allouer le tableau et d'affecter les coefficients par un appel AllocCoefficients(size, 2.0, 0.0).
 * @param size La taille du tableau.*/
private void AllocCoefficients(int size)
	{
	AllocCoefficients(size, 2.0, 0.0) ;
	}


/** Methode qui permet d'allouer le tableau de coefficients et d'affecter des valeurs (gaussienne).
 * @param size La taille du tableau.
 * @param sigma L'ecart type a utiliser pour la calcul de la gaussienne.
 * @param decalage Le decalage des valeurs => On decale le centre.
 *  Cela permet de ponderer de maniere asymetrique les niveaux de gris.*/
private void AllocCoefficients(int size, double sigma, double decalage)
	{
	int x ;
	double sum, d2, val ;
	double middle = (double)(size-1) / 2.0 + decalage ;
	
	if ( Coefficients == null || Coefficients.length != size)
		{
		Coefficients = null ;
		Coefficients = new double[size] ;
		}
	
	sum = 0 ;
	for (x=0 ; x < size ; x++)
		{
		d2 = Math.pow(middle-(double)x, 2.0) / (2.0*Math.pow(sigma, 2.0)) ;
		val = Math.exp(-d2) ;
		Coefficients[x] = val  ;
		sum += val ;
		}
	
	for (x=0 ; x < size ; x++) // On normalise pour que la somme des valeurs soit 1.
		Coefficients[x] /= sum ;
	}




/** This method sets parameters.
 * @param parameters int nbSizes, boolean FixedSize, ColorReducer reducer, Leveling leveling, int ForbiddenValue,
 *  boolean EightConnex, int nbCPU.*/
public void Parameters(Object... parameters)
	{
	if ( parameters.length != 7 ) throw new IllegalArgumentException("7 parameters required.") ;
	nbSizes = ((Integer)parameters[0]).intValue() ;
	FixedSize = ((Boolean)parameters[1]).booleanValue() ;
	reducer = (ColorReducer)parameters[2] ;
	leveling = (Leveling)parameters[3] ;
	ForbiddenValue = ((Integer)parameters[4]).intValue() ;
	EightConnex = ((Boolean)parameters[5]).booleanValue() ;
	nbCPU = ((Integer)parameters[6]).intValue() ;
	}














/* -------------------------------------------- Calcul des caractŽristiques -------------------------------------------- */
/** This method handles the computation of features.*/
private void ComputeFeatures()
	{
	Sum = 0.0 ;
	for (int y=0 ; y < GrayLevelMax ; y++)
		for (int x=0 ; x < Width ; x++)
			Sum += matrix[y][x] ;
	
	Features[0] = SZE() ;
	Features[1] = LZE() ;
	Features[2] = LGZE() ;
	Features[3] = HGZE() ;
	Features[4] = SZLGE() ;
	Features[5] = SZHGE() ;
	Features[6] = LZLGE() ;
	Features[7] = LZHGE() ;
	Features[8] = GLNU() ;
	Features[9] = SZNU() ;
	Features[10] = ZPC() ;
	Features[11] = BARYGL() ;
	Features[12] = BARYS() ;
	Features[13] = VARGL(Features[11]) ;
	Features[14] = VARS(Features[12]) ;
	Features[15] = ORIE(Features[11], Features[12]) ;
	}


/** F0 - Small Zone Emphasis, SZE.
 * @return SZE.*/
private double SZE()
	{
	double val = 0.0 ;
	for (int n=0 ; n < GrayLevelMax ; n++)
		for (int s=0 ; s < Width ; s++)
			val += matrix[n][s] / Math.pow(s+1, 2.0) ;
	return val / Sum ;
	}

/** F1 - Large Zone Emphasis, LZE.
 * @return LZE.*/
private double LZE()
	{
	double val = 0.0 ;
	for (int n=0 ; n < GrayLevelMax ; n++)
		for (int s=0 ; s < Width ; s++)
			val += matrix[n][s] * Math.pow(s+1, 2.0) ;
	return val / Sum ;
	}

/** F2 - Low Gray level Zone Emphasis, LGZE.
 * @return LGZE.*/
private double LGZE()
	{
	double val = 0.0 ;
	for (int n=0 ; n < GrayLevelMax ; n++)
		for (int s=0 ; s < Width ; s++)
			val += matrix[n][s] / Math.pow(n+1, 2.0) ;
	return val / Sum ;
	}

/** F3 - High Gray level Zone Emphasis, HGZE.
 * @return HGZE.*/
private double HGZE()
	{
	double val = 0.0 ;
	for (int n=0 ; n < GrayLevelMax ; n++)
		for (int s=0 ; s < Width ; s++)
			val += matrix[n][s] * Math.pow(n+1, 2.0) ;
	return val / Sum ;
	}

/** F4 - Small Zone Low Gray level Emphasis, SZLGE.
 * @return SZLGE.*/
private double SZLGE()
	{
	double val = 0.0 ;
	for (int n=0 ; n < GrayLevelMax ; n++)
		for (int s=0 ; s < Width ; s++)
			val += matrix[n][s] / (Math.pow(n+1, 2.0) * Math.pow(s+1, 2.0)) ;
	return val / Sum ;
	}

/** F5 - Small Zone High Gray level Emphasis, SZHGE.
 * @return SZHGE.*/
private double SZHGE()
	{
	double val = 0.0 ;
	for (int n=0 ; n < GrayLevelMax ; n++)
		for (int s=0 ; s < Width ; s++)
			val += matrix[n][s] * Math.pow((double)(n+1)/(double)(s+1), 2.0) ;
	return val / Sum ;
	}

/** F6 - Large Zone Low Gray level Emphasis, LZLGE.
 * @return LSLGLE.*/
private double LZLGE()
	{
	double val = 0.0 ;
	for (int n=0 ; n < GrayLevelMax ; n++)
		for (int s=0 ; s < Width ; s++)
			val += matrix[n][s] * Math.pow((double)(s+1)/(double)(n+1), 2.0) ;
	return val / Sum ;
	}

/** F7 - Large Zone High Gray level Emphasis, LZHGE.
 * @return LZHGE.*/
private double LZHGE()
	{
	double val = 0.0 ;
	for (int n=0 ; n < GrayLevelMax ; n++)
		for (int s=0 ; s < Width ; s++)
			val += matrix[n][s] * Math.pow(n+1, 2.0) * Math.pow(s+1, 2.0) ;
	return val / Sum ;
	}

/** F8 - Gray Level Non Uniform, GLNU. Spectral homogeneity.
 * @return GLNU.*/
private double GLNU()
	{
	double v, val = 0.0 ;
	for (int n=0 ; n < GrayLevelMax ; n++)
		{
		v = 0.0 ;
		for (int s=0 ; s < Width ; s++)
			v += matrix[n][s] ;
		val += v*v ;
		}
	return val / Sum ;
	}

/** F9 - Size Zone Non Uniform, SZNU. Uniformity.
 * @return SZNU.*/
private double SZNU()
	{
	double v, val = 0.0 ;
	for (int s=0 ; s < Width ; s++)
		{
		v = 0.0 ;
		for (int n=0 ; n < GrayLevelMax ; n++)
			v += matrix[n][s] ;
		val += v*v ;
		}
	return val / Sum ;
	}

/** F10 - Zone Percentage, ZPC. Iso-size equality.
 * @return ZPC.*/
private double ZPC()
	{
	double val = 0.0 ;
	for (int n=0 ; n < GrayLevelMax ; n++)
		for (int s=0 ; s < Width ; s++)
			val += (double)(s+1) * matrix[n][s] ;
	return Sum / val ;
	}

/** F11 - The barycenter on gray level.
 * @return The barycenter on gray level.*/
private double BARYGL()
	{
	double mean = 0.0 ;
	for (int n=0 ; n < GrayLevelMax ; n++)
		for (int s=0 ; s < Width ; s++)
			mean += (double)(n+1) * matrix[n][s] ;
	return mean / Sum ;
	}

/** F12 - The barycenter on sizes.
 * @return The barycenter on sizes.*/
private double BARYS()
	{
	double mean = 0.0 ;
	for (int n=0 ; n < GrayLevelMax ; n++)
		for (int s=0 ; s < Width ; s++)
			mean += (double)(s+1) * matrix[n][s] ;
	return mean / Sum ; 
	}

/** F13 - This method computes the variance on gray level.
 * @param mean The average on gray level.
 * @return The variance.*/
private double VARGL(double mean)
	{
	double val = 0.0 ;
	for (int n=0 ; n < GrayLevelMax ; n++)
		for (int s=0 ; s < Width ; s++)
			val += Math.pow((double)(n+1) * matrix[n][s] - mean, 2.0) ;
	return Math.sqrt(val / Sum) ;
	}

/** F14 - This method computes the variance on sizes.
 * @param mean The average on sizes.
 * @return The variance.*/
private double VARS(double mean)
	{
	double val = 0.0 ;
	for (int n=0 ; n < GrayLevelMax ; n++)
		for (int s=0 ; s < Width ; s++)
			val += Math.pow((double)(s+1) * matrix[n][s] - mean, 2.0) ;
	return Math.sqrt(val / Sum) ;
	}

/** F15 - This method computes the orientation of non null values of the matrix.
 * @param bgl Barycenter on gray level.
 * @param bs Barycenter on sizes.
 * @return The orientation [-Pi/2, Pi/2].*/
private double ORIE(double bgl, double bs)
	{
	double a, b, c, m ;
	double m11 = 0.0, m02 = 0.0, m20 = 0.0 ;
	
	for (int n=0 ; n < GrayLevelMax ; n++)
		for (int s=0 ; s < Width ; s++)
			{
			m = matrix[n][s] ;
			m11 += (double)(n+1) * (double)(s+1) * m ;
			m02 = Math.pow(n+1, 2.0) * m ;
			m20 = Math.pow(s+1, 2.0) * m ;
			}
	
	a = m20/Sum - bs*bs ;
	b = 2.0 * (m11/Sum - bgl*bs) ;
	c = m02/Sum - bgl*bgl ;
	return Math.atan(b/(a-c))/2.0 ;
	}












/* ---------------------------------------------------- Les getters ---------------------------------------------------- */
/** This method returns the features.
 * @return An array containing features.*/
public double[] Features()
	{
	return Features ;
	}

/** This method returns one feature.
 * @param i The feature number.
 * @return The feature value.*/
public double Feature(int i)
	{
	return Features[i] ;
	}

/** This method returns an array which contain features names.
 * @return An array of String.*/
public String[] FeaturesNames()
	{
	return FeaturesNames ;
	}

/** This method sets coefficients/weights for the matrix fusion.
 * @param Coefficients The weights array (will be cloned).*/
public void Coefficients(double[] Coefficients)
	{
	this.Coefficients = null ;
	this.Coefficients = ArrayNew.Clone(Coefficients) ;
	}

/** This method returns the array of coefficients.
 * @return Array of coefficients.*/
public double[] Coefficients()
	{
	return Coefficients ;
	}

}
