package patternRecognition.textures.glzm.glszm;

import arrayTiTi.ArrayOperations;

import dv.DV;
import dv.DVTools;

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

import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
import java.util.Iterator;
import java.util.List;

import mathematics.Maths;
import measures.cclh.ConnectedComponentLabeling;
import measures.cclh.UnionFindCcl;
import morphee.levelings.Leveling;

import processing.filters.Gaussian;

/**
 * <p>Description: This class fills a "Size Zone Matrix".</p>
 * <p>Package(s) required: dv, imageTiTi, mathematics, measures, morphee, processing.</p>
 * <p>Copyright: Copyright (c) 2006-Today.</p>
 * <p>Updates:<br>
 * February 27 2011 => Add methods required for interfaces Computable and PreComputable (for class GlszmFeatures).<br>
 * 11 Avril 2010, 1.3 => Gestion de la reduction des tailles pour le stockage dans la matrice.<br>
 * <dd> => Possibilite de fixer la taille de la matrice ou de la laisser variable avec un pas.</dd><br>
 * <dd> => Simplification par l'utilisation de l'interface ColorReducer.</dd><br>
 * 26 Janvier 2010 => Modifications des methodes FindValues qui s'effectuent desormais en un seul passage.<br> 
 * 19 Janvier 2010 => Ajout de la methode "FillMatrix(DV, int, boolean)", pour le projet Loreal.<br>
 * 29 Decembre 2009, 1.2 => Correction d'un bug pour la gestion de la valeur interdite :
 *  ccl.Label("this."image, 0, EightConnex, null).<br>
 * <dd> => Ajout de la methode FillMatrix(Image, int, int) pour une generalisation dans GLZM.</dd><br>
 * <dd> => Ajout de la methode FillMatrix(List) pour une generalisation dans GLZM.</dd><br>
 * 27 Novembre 2009 => Correction d'un bug du a la gestion de la valeur interdite :
 *  Correspondances[i] = TrouverValeur(Carte, i)"-1" ;<br>
 * 18 Avril 2008, 1.1 => Ajout de l'affinage des niveaux de gris par KMeans.<br>
 * 10 Avril 2008 => Creation.</p>
 * 
 * @author Guillaume THIBAULT
 * @version 1.3
 * <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>
 */

public class GrayLevelSizeZoneMatrix
{

/** Matrix height <=> Number of gray level.*/
protected int nbGrayLevel = -1 ;
/** Number of size of zones to consider. If FixedSize=true it will be the matrix width, else it is the step.*/
protected int nbSizes = -1 ;
/** Matrix width. If FixedSize then Width=nbSizes, else Width=SizeZoneMax/nbSizes+1.*/
protected int Width = -1 ;
/** Is the width fixed? */
protected boolean FixedSize = false ;
/** The array which represents the size zone matrix.*/
protected double[][] matrix = null ;

/** The original image after color reduction.*/
protected BufferedImage Reduced = null ;

/** The original volume after color reduction.*/
protected DV Reduced3D = null ;

/** This class realizes the labeling of connected components.*/
protected ConnectedComponentLabeling ccl = new UnionFindCcl() ;
/** A gaussian filter, usefull to computes the marker of leveling.*/
private Gaussian gauss = new Gaussian(1, 1) ;

/** 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 GrayLevelSizeZoneMatrix()
	{
	}

/** This method realizes the processing.
 * @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 FillMatrix(BufferedImage image, int nbGrayLevel, int nbSizes, boolean FixedSize, ColorReducer reducer,
							Leveling leveling, int ForbiddenValue, boolean EightConnex, int nbCPU)
	{
	if ( !ImageTools.isGrayLevel(image) ) throw new IllegalArgumentException("Only gray level images supported.") ;
	
	if ( nbGrayLevel < 2 )
		throw new IllegalArgumentException("Number of gray level incorrect: " + nbGrayLevel + ", wish [2...2^N[") ;
	
	this.nbGrayLevel = nbGrayLevel ;
	this.nbSizes = nbSizes ;
	this.FixedSize = FixedSize ;
	this.reducer = reducer ;
	this.leveling = leveling ;
	this.ForbiddenValue = ForbiddenValue ;
	this.EightConnex = EightConnex ;
	this.nbCPU = nbCPU ;
	
	if ( !Maths.isPowerOf(nbGrayLevel, 2)  )
		throw new IllegalArgumentException("Number of gray level must be a power of 2: " + nbGrayLevel) ;
	
	if ( FixedSize && ( matrix == null || matrix.length != nbGrayLevel || matrix[0].length != Width) )
		{
		Width = nbSizes ;
		matrix = new double[nbGrayLevel][Width] ;
		}
	
	
	int i, width ;
	Reduced = null ;
	
	if ( leveling == null ) Reduced = reducer.Reduce(image, nbGrayLevel, ForbiddenValue) ;
	else Reduced = reducer.Reduce(leveling.Filter(image, nbCPU, gauss.Filter(image, nbCPU)), nbGrayLevel, ForbiddenValue) ;
	
	ccl.Label(Reduced, 0, EightConnex) ; // On calcule (Žtiquette) les composantes connexes.
	int[][] Labels = ccl.Labels() ;
	int[] Sizes = ccl.Sizes() ; // On rŽcupre la dimension de chaque composante.
	int[] Correspondances = new int[Sizes.length] ;	
	
	FindValues(Labels, Correspondances) ; // Correspondance composante/niveau de gris. La case 0 = ForbiddenValue

	width = 0 ;
	for (i=1 ; i < Sizes.length ; i++) // On trouve la taille de la plus grande zone.
		if ( Correspondances[i] >= 0 && width < Sizes[i] )
			width = Sizes[i] ;	
	
	if ( FixedSize )
		{
		ArrayOperations.Fill(matrix, 0.0) ; // Matrice dŽjˆ allouŽe, donc il suffit de la re-initialiser.
		for (i=1 ; i < Correspondances.length ; i++) // On remplit la matrice
			if ( Correspondances[i] >= 0 )
				matrix[Correspondances[i]][(int)((double)(Sizes[i]-1)/(double)width*(double)nbSizes)]++ ;
		}
	else
		{
		Width = width/nbSizes+1 ;
		matrix = null ;
		matrix = new double[nbGrayLevel][Width] ;
		for (i=1 ; i < Correspondances.length ; i++) // On remplit la matrice
			if ( Correspondances[i] >= 0 )
				matrix[Correspondances[i]][Sizes[i]/nbSizes]++ ;
		}
	}



/** 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 FillMatrix(BufferedImage ImageReduced, ConnectedComponentLabeling Ccl)
	{
	if ( !ImageTools.isGrayLevel(ImageReduced) ) throw new IllegalArgumentException("Only gray level images supported.") ;
	int i, width ;
	int[][] Labels = Ccl.Labels() ;
	int[] Sizes = Ccl.Sizes() ;
	int[] Correspondances = new int[Sizes.length] ;
	
	FindValues(Labels, Correspondances) ; // Correspondance composante/niveau de gris. La case 0 = ForbiddenValue
	
	width = 0 ;
	for (i=1 ; i < Sizes.length ; i++) // On trouve la taille max des composantes qui sera la largeur de la matrice.
		if ( Correspondances[i] >= 0 && width < Sizes[i] )
			width = Sizes[i] ;

	if ( FixedSize )
		{
		ArrayOperations.Fill(matrix, 0.0) ; // Matrice dŽjˆ allouŽe, donc il suffit de la re-initialiser.
		for (i=1 ; i < Correspondances.length ; i++) // On remplit la matrice
			if ( Correspondances[i] >= 0 )
				matrix[Correspondances[i]][(int)((double)(Sizes[i]-1)/(double)width*(double)nbSizes)]++ ;
		}
	else
		{
		Width = width/nbSizes+1 ;
		matrix = null ;
		matrix = new double[nbGrayLevel][Width] ;
		for (i=1 ; i < Correspondances.length ; i++) // On remplit la matrice
			if ( Correspondances[i] >= 0 )
				matrix[Correspondances[i]][Sizes[i]/nbSizes]++ ;
		}
	}



/** This method realizes the processing in case of pre-computations.
 * @param sizes The array which contains all size zones.
 * @param colors The array which contains all color zones.
 * @param used The array which determines if a zone is used during processing (matrix filling).
 * @param nbCPU Number of thread to use for processing.*/
public void FillMatrix(int[] sizes, int[] colors, boolean[] used, int nbCPU)
	{
	int i, Largeur = 0 ;
	
	for (i=1 ; i < sizes.length ; i++)
		if ( used[i] && Largeur < sizes[i] )
			Largeur = sizes[i] ;
	
	if ( FixedSize )
		{
		ArrayOperations.Fill(matrix, 0.0) ; // Matrice dŽjˆ allouŽe, donc il suffit de la re-initialiser.
		for (i=1 ; i < sizes.length ; i++) // On remplit la matrice
			if ( used[i] )
				matrix[colors[i]-1][(int)((double)(sizes[i]-1)/(double)Largeur*(double)nbSizes)]++ ;
		}
	else
		{
		Width = Largeur/nbSizes + 1 ;
		matrix = null ;
		matrix = new double[nbGrayLevel][Width] ;
		for (i=1 ; i < sizes.length ; i++) // On remplit la matrice
			if ( used[i] )
				try	{
				matrix[colors[i]-1][sizes[i]/nbSizes]++ ;
				}
				catch ( Exception E )
					{
					System.err.println(i + " => " + colors[i] + " => " + sizes[i]) ;
					}
		}
	}




/** This method realizes the processing.
 * @param list The list of reduced images: one zone per image!
 * @param nbCPU Number of thread to use for processing.*/
public void FillMatrix(List<BufferedImage> list, int nbCPU)
	{
	int i, x, y, Largeur, width, height, nb = 0 ;
	BufferedImage shape = null ;
	int[] Sizes = new int[list.size()] ;
	Iterator<BufferedImage> iter = list.iterator() ;
	WritableRaster wr = null ; ;
	
	Largeur = 0 ;
	while ( iter.hasNext() )
		{
		shape = iter.next() ;
		wr = shape.getRaster() ;
		if ( !ImageTools.isGrayLevel(shape) ) throw new IllegalArgumentException("Only gray level image required.") ;

		width = shape.getWidth() ;
		height = shape.getHeight() ;
		
		Sizes[nb] = 0 ; // Initialisation de la taille.
		for (y=0 ; y < height ; y++)
			for (x=0 ; x < width ; x++)
				if ( wr.getSample(x, y, 0) > 0 ) Sizes[nb]++ ; // On compte les pixels de la zone.
		
		if ( Largeur < Sizes[nb] ) Largeur = Sizes[nb] ;
		wr = null ;
		shape = null ;
		nb++ ;
		}
	
	if ( FixedSize )
		{
		ArrayOperations.Fill(matrix, 0.0) ; // Matrice dŽjˆ allouŽe, donc il suffit de la re-initialiser.
		for (i=1 ; i < nb ; i++) // On remplit la matrice
			matrix[FindColor(list.get(i))-1][(int)((double)(Sizes[i]-1)/(double)Largeur*(double)nbSizes)]++ ;
		}
	else
		{
		Width = Largeur/nbSizes + 1 ;
		matrix = null ;
		matrix = new double[nbGrayLevel][Width] ;
		for (i=0 ; i < nb ; i++) // On remplit la matrice
			matrix[FindColor(list.get(i))-1][Sizes[i]/nbSizes]++ ;
		}
	}







/** This method realizes the processing. Do not call the method "FillMatrix" (useless because it is automatically called).
 * @param dv 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 FillMatrix(DV dv, int ForbiddenValue, boolean TwentySixConnex)
	{
	int i, width ;
	
	Reduced3D = null ;
	Reduced3D = DVTools.ReduceGrayLevel(dv, nbGrayLevel, ForbiddenValue) ;
	
	ccl.Label(Reduced3D, ForbiddenValue, TwentySixConnex) ;
	
	int[][][] Labels = ccl.Labels3D() ;
	int[] Sizes = ccl.Sizes() ; // On rŽcupre la dimension de chaque composante.
	int[] Correspondances = new int[Sizes.length] ;
	
	FindValues(Labels, Correspondances) ; // Correspondance omposante/niveau de gris. La case 0 = ForbiddenValue 

	width = 0 ;
	for (i=1 ; i < Sizes.length ; i++) // On trouve la taille max des composantes qui sera la largeur de la matrice.
		if ( Correspondances[i] >= 0 && width < Sizes[i] )
			width = Sizes[i] ;
	
	if ( FixedSize )
		{
		ArrayOperations.Fill(matrix, 0.0) ; // Matrice dŽjˆ allouŽe, donc il suffit de la re-initialiser.
		for (i=1 ; i < Correspondances.length ; i++) // On remplit la matrice
			if ( Correspondances[i] >= 0 )
				matrix[Correspondances[i]][(int)((double)(Sizes[i]-1)/(double)width*(double)nbSizes)]++ ;
		}
	else
		{
		Width = width/nbSizes+1 ;
		matrix = null ;
		matrix = new double[nbGrayLevel][Width] ;
		for (i=1 ; i < Correspondances.length ; i++) // On remplit la matrice
			if ( Correspondances[i] >= 0 )
				matrix[Correspondances[i]][Sizes[i]/nbSizes]++ ;
		}
	}





/** 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)
	{
	FillMatrix(image, nbGrayLevel, nbSizes, FixedSize, reducer, leveling, ForbiddenValue, EightConnex, nbCPU) ;
	}



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






















/** Methode qui permet de trouver la couleur de la vignette, car elle est codee avec des 0 et une autre couleur.
 * @param image L'image dont on souhaite trouver la couleur.
 * @return La valeur de la couleur trouvee.*/
private int FindColor(BufferedImage image)
	{
	WritableRaster wr = image.getRaster() ;
	for (int y=0 ; y < image.getHeight() ; y++)
		for (int x=0 ; x < image.getWidth() ; x++)
			if ( wr.getSample(x, y, 0) > 0 )
				return wr.getSample(x, y, 0) ;
	throw new Error("Color not found. This error must not occured.") ;
	}




/** Une methode qui trouve la correspondance entre les composantes connexes et les niveaux de gris
 *  => Affecte la couleur reduite a chaque composante.
 * @param Labels La carte des composantes.
 * @param Correspondances Le tableau des correspondances, celui que l'on doit remplir.*/
private void FindValues(int[][] Labels, int[] Correspondances)
	{
	WritableRaster wr = Reduced.getRaster() ;
	int hauteur = Labels.length ;
	int largeur = Labels[0].length ;
	for (int y=0 ; y < hauteur ; y++)
		for (int x=0 ; x < largeur ; x++)
			if ( Labels[y][x] > 0 )
				Correspondances[Labels[y][x]] = wr.getSample(x, y, 0) - 1 ;
	wr = null ;
	}


/** Une methode qui trouve la correspondance entre les composantes connexes et les niveaux de gris
 *  => Affecte la couleur reduite a chaque composante.
 * @param Labels La carte des composantes.
 * @param Correspondances Le tableau des correspondances, celui que l'on doit remplir.*/
private void FindValues(int[][][] Labels, int[] Correspondances)
	{
	int depth = Labels.length ;
	int height = Labels[0].length ;
	int width = Labels[0][0].length ;
	for (int z=0 ; z < depth ; z++)
		for (int y=0 ; y < height ; y++)
			for (int x=0 ; x < width ; x++)
				if ( Labels[z][y][x] > 0 )
					Correspondances[Labels[z][y][x]] = Reduced3D.Voxel(z, y, x) - 1 ;
	}













/* ----------------------------------------------------- Les getters ----------------------------------------------------- */
/** This method returns the size zone matrix.
 * @return The 'Size Zone Matrix'.*/
public double[][] getMatrix()
	{
	return matrix ;
	}


public String toString()
	{
	StringBuffer sb = new StringBuffer() ;
	for (int j=0 ; j < nbGrayLevel ; j++)
		{
		for (int i=0 ; i < nbSizes ; i++) sb.append(matrix[j][i] + " ") ;
		sb.append("\n") ;
		}
	return sb.toString() ;
	}

}
