package patternRecognition.textures.glzm.glszm;

import arrayTiTi.ArrayOperations;

import dv.DV;

import imageTiTi.ImageTools;
import imageTiTi.reducer.ColorReducer;

import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;

import measures.cclh.ConnectedComponentLabeling;
import morphee.levelings.Leveling;

import patternRecognition.PreComputable;


/**
 * <p>Description: this class features on the "Size Zone Matrix".<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 => Barycenter on gray level.<br>
 *  - F12 => Barycenter on size zones.<br>
 *  - F13 => Weighted variance on gray level.<br>
 *  - F14 => Weighted variance on size zones.<br>
 *  - F15 => Orientation.<br>
 * </p>
 * <p>Package(s) required: dv, imageTiTi, measures, morphee.</p>
 * <p>Copyright: Copyright (c) 2006-Today.</p>
 * <p>Updates: <br>
 * February 27 2011, 1.1 => Add interfaces PreComputable and Computable.<br>
 * 20 Avril 2010 => Ajout des indices F13 a F15 et correction/traduction des noms.<br>
 * 07 Fevrier 2010 => Suppression des caracteristiques Haralick.<br>
 * 21 Janvier 2010 => Ajout de la methode "Compute(DV, int, boolean) pour effectuer le calcul sur des volumes.<br>
 * 19 Novembre 2009 => Ajout de la methode ComputeFromStack qui permet de faire les calculs sur une pile d'images
 *  (projet Loreal, CMM/ENSMP).<br>
 * 10 Avril 2008 => Creation.</p>
 * 
 * <p>
 * <!-- technical-bibtex-start -->
 * BibTeX:
 * <pre>
 * &#64;conference{Thibault09,
 * 		address = {Minsk, Belarus},
 *		author = {Guillaume Thibault and Bernard Fertil and Claire Navarro and Sandrine Pereira and Pierre Cau
 *					and Nicolas Levy and Jean Sequeira and Jean-Luc Mari},
 *		booktitle = {Pattern Recognition and Information Processing (PRIP)},
 *		pages = {140--145},
 *		title = {Texture Indexes and Gray Level Size Zone Matrix. Application to Cell Nuclei Classification},
 *		year = {2009}
 *		}
 * </pre>
 * <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.1
 */

public class GlszmFeatures extends GrayLevelSizeZoneMatrix implements PreComputable
{

/** 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"} ;

/** This array will contain the list of size zones after the call of the method PreCompute.*/
private int[] sizes ;
/** This array will contain the color of zones after reduction.*/
private int[] colors ;
/** This array will contain the list of connected component used in the window (framework).*/
private boolean[] used ;





public GlszmFeatures()
	{
	super() ;
	}





/** This method realizes the processing. Do not call the method "FillMatrix" (useless because it is automatically called).
 * @param image The image to characterize.
 * @param nbGrayLevel The number of gray level after reduction (matrix height).
 * @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 nbGrayLevel, int nbSizes, boolean FixedSize, ColorReducer reducer,
						Leveling leveling, int ForbiddenValue, boolean EightConnex, int nbCPU)
	{
	FillMatrix(image, nbGrayLevel, nbSizes, FixedSize, reducer, leveling, ForbiddenValue, EightConnex, nbCPU) ; // On remplit :)
	
	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, nbGrayLevel, nbSizes, FixedSize, reducer, leveling, ForbiddenValue, EightConnex, nbCPU) ;
	
	ComputeFeatures() ;
	}



/** This method realizes the processing.
 * @param ImageReduced The original image after color reduction.
 * @param Ccl The class which realizes labeling of connected components.*/
public void Compute(BufferedImage ImageReduced, ConnectedComponentLabeling Ccl)
	{
	if ( !ImageTools.isGrayLevel(ImageReduced) ) throw new IllegalArgumentException("Only gray level images supported.") ;
	
	FillMatrix(ImageReduced, Ccl) ; // On remplit la matrice :)
	
	ComputeFeatures() ;
	}



/** This method realizes the processing. Do not call the method "FillMatrix" (useless because it is automatically called).
 * @param volume The volume to characterize.
 * @param ForbiddenValue The background value or value to not take into account during processing. If negative, so the entire
 *  3D texture is processed.
 * @param TwentySixConnex Use 26-connexity for labeling?*/
public void Compute(DV volume, int ForbiddenValue, boolean TwentySixConnex)
	{	
	FillMatrix(volume, ForbiddenValue, TwentySixConnex) ; // On remplit la matrice :)
	
	ComputeFeatures() ;
	}









/* ------------------------------------------------- Interface PreComputable ------------------------------------------------- */
/** This method realizes the preprocessings.
 * @param image The image to process.
 * @param nbCPU Number of thread to use for processing.*/
public void PreCompute(BufferedImage image, int nbCPU)
	{
	if ( nbCPU < 0 ) throw new Error("Parameters must be set before the call to this method.") ;
	
	int x, y ;
	int height = image.getHeight() ;
	int width = image.getWidth() ;
	BufferedImage reduced = reducer.Reduce(image, nbGrayLevel, ForbiddenValue) ;
	ccl.Label(reduced, ForbiddenValue, EightConnex) ;
	int counter = ccl.NumberOfConnectedComponent() ;
	int[][] labels = ccl.Labels() ;
	WritableRaster wrred = reduced.getRaster() ;
	
	if ( sizes == null || sizes.length != counter+1 )
		{
		colors = null ;
		colors = new int[counter+1] ;
		sizes = null ;
		sizes = new int[counter+1] ;
		used = null ;
		used = new boolean[counter+1] ;
		}
	
	if ( FixedSize && ( matrix == null || matrix.length != nbGrayLevel || matrix[0].length != Width) )
		{
		Width = nbSizes ;
		matrix = new double[nbGrayLevel][Width] ;
		}
	
	ArrayOperations.Fill(colors, 0) ;
	ArrayOperations.Fill(sizes, 0) ;
	ArrayOperations.Fill(used, false) ;
	
	for (y=0 ; y < height ; y++)
		for (x=0 ; x < width ; x++)
			if ( wrred.getSample(x, y, 0) > 0 )
				{
				sizes[labels[y][x]]++ ;
				colors[labels[y][x]] = wrred.getSample(x, y, 0) ;
				}
	
	wrred = null ;
	labels = null ;
	}



/** This method computes characteristics according preprocessings (pre-computation).
 * @param startx The X coordinate of the starting point (corner bottom left).
 * @param starty The Y coordinate of the starting point (corner bottom left).
 * @param endx The X coordinate of the ending point (corner top right).
 * @param endy The Y coordinate of the ending point (corner top right).*/
public void FastCompute(int startx, int starty, int endx, int endy)
	{
	if ( sizes == null ) throw new Error("Method PreCompute must be called before this one.") ;
	
	int[][] labels = ccl.Labels() ;
	ArrayOperations.Fill(used, false) ;
	
	for (int y=starty ; y <= endy ; y++)
		for (int x=startx ; x <= endx ; x++)
			used[labels[y][x]] = true ;
	used[0] = false ;
	
	FillMatrix(sizes, colors, used, nbCPU) ;
	
	ComputeFeatures() ;
	}













/* -------------------------------------------------- Features -------------------------------------------------- */
/** This method handles the computation of features.*/
private void ComputeFeatures()
	{
	Sum = 0.0 ;
	for (int y=0 ; y < nbGrayLevel ; 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]) ;
	}




/** This method realizes processing for a stack of images; processing of each image and average on features.
 * @param images The image to process.
 * @param nbGrayLevel The number of gray level after reduction (matrix height).
 * @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 nbGrayLevel, 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], nbGrayLevel, 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 ;
	}
















/* -------------------------------------------- Calcul des caractŽristiques -------------------------------------------- */
/** F0 - Small Zone Emphasis, SZE.
 * @return SZE.*/
private double SZE()
	{
	double val = 0.0 ;
	for (int n=0 ; n < nbGrayLevel ; 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 < nbGrayLevel ; 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 < nbGrayLevel ; 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 < nbGrayLevel ; 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 < nbGrayLevel ; 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 < nbGrayLevel ; 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 < nbGrayLevel ; 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 < nbGrayLevel ; 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 < nbGrayLevel ; 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 < nbGrayLevel ; n++)
			v += matrix[n][s] ;
		val += v*v ;
		}
	return val / Sum ;
	}

/** F10 - Zone Percentage, ZPC. Equality of iso-sizes.
 * @return ZPC.*/
private double ZPC()
	{
	double val = 0.0 ;
	for (int n=0 ; n < nbGrayLevel ; 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 < nbGrayLevel ; 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 < nbGrayLevel ; 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 < nbGrayLevel ; 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 < nbGrayLevel ; 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 < nbGrayLevel ; 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 ;
	}












/* ---------------------------------------------------- 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 ;
	}

}
