package active_contour;

// Import balíkov
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.awt.image.WritableRaster;
import java.awt.image.Raster;
import java.lang.Cloneable;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Iterator;

import org.opencv.core.*;
import org.opencv.highgui.Highgui;
import org.opencv.imgproc.Imgproc;

import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;

/**
 * Táto trieda obsahuje metody pre vypocet metody aktivnych kontur bez vyuzitia gradientu
 * @author Branislav Hesko
 * @version 20.05.2015
 */
public class ActiveContour {
	
	// Inicializacia premennych
	private int numberOfIter;
	private int numberOfPoints;
	private int startPoint;
	private int perimeter;
	private ImageProc img;
	private double[] gradient;
	private double[] origImage;
	private ArrayList curve;
	private static final int addLimit = 4;
	private int surroundSize;
	private Mat image;
	
	/*
	 * Konstruktor metody, ktory obsahuje inicializaciu krivky, vypocet gradientu a priradi premennym triedy vstupne hodnoty 
	 */
	public ActiveContour(int numberOfPoints, int numberOfIter, int surroundSize, int startPoint,int perimeter , ImageProc img) {
		// Inicializacia poctu premennych
		this.numberOfIter = numberOfIter;
		// Inicializacia poctu premennych
		this.numberOfPoints= numberOfPoints;
		// Priradenie originálneho obrazu premennej triedy
		this.img = img;
		//Inicilizacia nulovej matice typu double
		this.image = new Mat(img.getNumRows(),img.getNumCols(), CvType.CV_64F);
		// Konverzia obrazu na typ double
		img.getMat().convertTo(image, CvType.CV_64F);
		
		// Velkost prehladavaneho okolia
		this.surroundSize = surroundSize;
		// Stred kruznice
		this.startPoint= startPoint;
		// Polomer kruznice
		this.perimeter = perimeter;
		// Vytvorenie ArrayListu o dlzke poctu bodov
		curve = new ArrayList<Integer>(numberOfPoints);
		// Privolanie metody inicializacie kruznice
		initialiseCurve();
		// Vytvorenie vektoru gradientu a originalneho obrazu, matica je ulozena vo forme vektoru pre rychle pocitanie
		this.gradient = new double[img.getNumCols()*img.getNumRows()];
		this.origImage = new double[img.getNumCols()*img.getNumRows()];
		// Privolanie metody gradientu a vytvorenie vektoru s gradientom
		getGradient().get(0,0,gradient);
		// Kopirovanie hodnot z matice do jednoho vektoru
		image.get(0, 0, origImage);
		}
	
	/*
	 * Vypocet externej energie
	 */
	public double getExtEnergy(int point) {
		double eExt;
		eExt = Math.abs(gradient[point]);
		return eExt;
	}
	
	/*
	 * Vypotec prve internej energie, reprezentujucej prvu diferenciu
	 */
	public double getIntEnergy(int point,int pointBack) {
		double eInt;
		eInt = Math.pow(getXaxe(point) - getXaxe(pointBack), 2) + Math.pow(getYaxe(point) - getYaxe(pointBack) ,2);
		return eInt;
	}
	
	/* 
	 * Vypocet internej energie v podobe druhej diferencie
	 */
	public double getInt2Energy(int point, int pointBack, int pointForw) {
		double eInt2;
		eInt2 = Math.pow(getXaxe(pointBack) - getXaxe(point) * 2 + getXaxe(pointForw) ,2) + Math.pow(getYaxe(pointBack) - getYaxe(point) * 2 + getYaxe(pointForw) ,2);
		return eInt2;
	}
	
	/*
	 * Hlavna metoda pre vyvoj krivky, iterativny vypocet novych poloh
	 */
	public void evolveCurve() {
		// Inicializacia premennych
		int j,i,k;
		int[] surround = new int[surroundSize*surroundSize];
		double eInt,eExt,eInt2;
		double[] eTotal = new double[surroundSize*surroundSize];
		double minGrad;
		double maxGrad;
		double maxVal;
		double distance,dist;
		//Definovanie a priradenie hodnoty pre jednotlive vahy energii. DOLEZITY PARAMETER!!
		double alfaWeight = 0.33;
		double betaWeight = 0.33;
		double gammaWeight = 13.5;
		
		// Inicializacia iterativneho cyklu
		for(j = 0;j < numberOfIter;j++) {
			for (i = 0; i < curve.size(); i++) {
				
				// Vyber vektoru okolia
				surround = getSurround((int) curve.get(i));
				// Vypocet minimalnej a maximalnej hodnoty gradientu v danom okoli
				minGrad = getMinGradValue(surround);
				maxGrad = getMaxGradValue(surround);
				// Vypocet maximalnej intenzity obrazu v danom okoli
				maxVal = getMaxImgValue(surround);
				// Vzdialenost bodu krivky od stredu kruznice
				distance = Math.sqrt(Math.pow(getXaxe(curve.get(i)) - getXaxe(startPoint),2) + Math.pow(getYaxe(curve.get(i)) - getYaxe(startPoint),2));
				// Vypocet energii pre cele okolie
				for (k = 0; k < surroundSize * surroundSize; k++) {
					// Vypocet vzdialenosti bodu okolia od stredu kruznice
					dist =  Math.sqrt(Math.pow(getXaxe(surround[k]) - getXaxe(startPoint),2) + Math.pow(getYaxe(surround[k]) - getYaxe(startPoint),2));
					// Preferencia rozpinania a jej nastavenie
					if (dist > distance) {
						alfaWeight = 0.07;
						betaWeight = 0.07;
					} else {
						alfaWeight = 0.30;
						betaWeight = 0.30;
					}
					
					// Priradenie jednotlivych energií k premennym, pouzitie volanych metod
					eInt = getIntEnergy(surround[k], (int) curve.get((i - 1 + curve.size()) % curve.size())) / maxVal;
					eExt = Math.abs(getExtEnergy(surround[k]) - minGrad) / (maxGrad - minGrad);
					eInt2 = getInt2Energy(surround[k],(int) curve.get((i - 1 + curve.size()) % curve.size()) , (int) curve.get((i+1)%curve.size()));
					// Vypocet totalnej energie pre k-ty bod okolia
					eTotal[k] = alfaWeight * eInt + betaWeight * eInt2 + gammaWeight * eExt;
				}
				// Vyber novej pozicie
				newPosition(eTotal, i, surround);

			}
			// Odstranenie duplicit bodov
			curve = supressPoints();
			// Pridanie bodov krivke
			curve = addPoints();
		}
		// Finalny vypis vsetkych bodov krivky
		for(i = 0; i< curve.size();i++) {
			System.out.println("Suradnice bodov: X: " + getXaxe((int) curve.get(i)) + " Y: "+ getYaxe((int) curve.get(i)));
		}

	}
	/*
	 * Vyber novej pozicie bodov, ako vyber minimalnej energie okolia
	 */
	public void newPosition(double[] eTotal, int i, int[] surround) {
		int min = 0;
		int j;
			for (j = 1; j<eTotal.length;j++) {
				if (eTotal[j] < eTotal[min]) {
					min = j;
				}
			}
		// Nastavenie novej polohy bodu krivky
		curve.set(i,surround[min]);
	}
	
	/*
	 * Metoda pre inicializaciu krivky
	 */
	public void initialiseCurve() {
		int i;
		for(i = 0;i < numberOfPoints;i++) {
			// Pridavanie bodov krivky
			curve.add(i, (startPoint + (img.getNumCols() * (int) (perimeter * Math.sin(2 * Math.PI * i / numberOfPoints))) + 
					(int) (perimeter * (Math.cos(2 * Math.PI * i / numberOfPoints)))));

		}
		
	}
	
	/*
	 * Metoda odstranujuca duplicitne body
	 */
	public ArrayList supressPoints() {
		ArrayList suppressDuplicates = new ArrayList();
		Iterator iterator = curve.iterator();
		// Prehladanie arraylistu a ulozenie iba neduplicitnych do noveho
		while(iterator.hasNext()) {
			int o = (int) iterator.next();
			if (!suppressDuplicates.contains(o)) {
				suppressDuplicates.add(o);
			}
		}
		return suppressDuplicates;
	}
	
	/*
	 * Metoda pridavajuca body ku krivke, pre zamedzenie riedkej krivky 
	 */
	public ArrayList addPoints() {
		int i;
		int hVar;
		int k = 1;
		ArrayList addPointList  = new ArrayList(curve.size());
		addPointList= (ArrayList) curve.clone();
		for(i = 0;i< curve.size()-1;i++) {
			// Pridavanie bodov ak, vzdialenost je vacsia ako addlimit medzi dvoma bodmi	
			if(Math.abs(getXaxe(curve.get(i)) - getXaxe(curve.get(i + 1))) > addLimit  || Math.abs(getYaxe(curve.get(i)) - 
					getYaxe(curve.get(i + 1))) > addLimit ) {
				hVar =  img.getNumCols() * (int) ((getXaxe(curve.get(i+1)) - getXaxe(curve.get(i)))/2) + 
						(int) ((getYaxe(curve.get(i+1)) - getYaxe(curve.get(i)))/2);
				addPointList.add(i+k , (int) curve.get(i) +hVar);
				k++;

			}
		}
		return addPointList;
	}
	/*
	 * Vrati cislo riadku pre bod vektoru, pretoze matica je zadana ako vektor
	 */
	public int getXaxe(Object o) {
		int var = ((int) o) / img.getNumCols();	
		return var;
	}
	
	/*
	 * Vrati poradove cislo stlpcu, matica je zadana ako vektor, spatny prevod
	 */
	public int getYaxe(Object o) {
		int var = ((int) o) / img.getNumCols();
		var = ((int) o)  - (var*img.getNumCols());
		return var;
	}
	/*
	 * Vrati bod vektoru z x,y suradnic
	 */
	public int getVertex(int x, int y) {
		int var = x + (y* img.getNumCols());
		return var;
	}
	
	/*
	 * Vypocet gradientu
	 */
	public Mat getGradient() {
		Mat grad;
		int kernelSize = 5;
		// Gaussova maska
		Mat kernel3 = new Mat(kernelSize,kernelSize, CvType.CV_64F){
			   {
			     put(0,0,1.0/256.0);
	    	     put(0,1,4.0/256.0);
	    	     put(0,2,7.0/256.0);
				 put(0,3,4.0/256.0);
				 put(0,4,1.0/256.0);

			    	     
	    	     put(1,0,4.0/256.0);
	    	     put(1,1,16.0/256.0);
		   	     put(1,2,26.0/256.0);
				 put(1,3,16.0/256.0);
				 put(1,4,4.0/256.0);

		   	     put(2,0,7.0/256.0);
		   	     put(2,1,26.0/256.0);
		   	     put(2,2,41.0/256.0);
				 put(2,3,26.0/256.0);
				 put(2,4,7.0/256.0);

				   put(3,0,4.0/256.0);
				   put(3,1,16.0/256.0);
				   put(3,2,26.0/256.0);
				   put(3,3,16.0/256.0);
				   put(3,4,4.0/256.0);

				   put(4,0,1.0/256.0);
				   put(4,1,4.0/256.0);
				   put(4,2,7.0/256.0);
				   put(4,3,4.0/256.0);
				   put(4,4,1.0/256.0);
			   }
	   	      };
	   	// Maska laplacianu
		Mat kernel2 =new Mat(3,3,CvType.CV_64F) {
			{
				put(0,0,0);
				put(0,1,-1);
				put(0,2,0);

				put(1,0,-1);
				put(1,1,4);
				put(1,2,-1);

				put(2,0,0);
				put(2,1,-1);
				put(2,2,0);
			}
		};


		grad = new Mat(img.getNumRows(),img.getNumCols(),CvType.CV_64F);
		// Filtracia Gaussovou maskou + maskou laplacianu
		Imgproc.filter2D(image, grad, -1, kernel3);
		Imgproc.filter2D(grad, grad, -1, kernel2);
		
		// Odkomentovanim sa ulozi do suboru gradient, gradientny obraz
		//Highgui.imwrite("gradient.png", grad);
		return grad;
	}
	
	/*
	 * Vrati arraylist s bodmi krivky
	 */
	public ArrayList getCurve() {
		return curve;
	}
	/*
	 * Ziskanie vektoru okolia
	 */
	public int[] getSurround(int point) {
		//System.out.println(point);
		int[] surround = new int[surroundSize*surroundSize];
		int a,b,p0;
		p0 = point - ((surroundSize-1)/2) -((surroundSize-1)/2 * img.getNumCols());

		for(a = 0; a < surroundSize; a++) {
			for(b = 0; b< surroundSize;b++) {
				surround[a*surroundSize + b] = p0 + b + (a*img.getNumCols());
			}
		}

		return surround;
	}
	
	/*
	 * Vrati min gradient okolia
	 */
	public double getMinGradValue(int[] surround){
		int a;
		double min = Double.MAX_VALUE;

		for(a = 0; a < surround.length;a++) {
			//System.out.println(surround[a]);
			if (min > gradient[surround[a]]) {
				min = gradient[surround[a]];
			}
		}
		return min;
	}
	
	/*
	 * Vrati max gradient okolia
	 */
	public double getMaxGradValue(int [] surround) {
		double max = -Double.MAX_VALUE;
		int a;
		for(a = 0; a<surround.length;a++) {
			if (max < gradient[surround[a]]) {
				max = gradient[surround[a]];
			}
		}
		return max;
	}
	
	/*
	 * Vrati maximalnu intenzitu obrazu v okoli definovanym surround metodou
	 */
	public double getMaxImgValue(int [] surround) {
		double maxVal = - Double.MAX_VALUE;
		int a;

		for(a = 0; a < surround.length;a++) {
			 if (maxVal < origImage[surround[a]]) {
				 maxVal = origImage[surround[a]];
			 }
		}
		return maxVal;
	}
}
