/*--- formatted by Jindent 2.1, (www.c-lab.de/~jindent) ---*/

/*
 * The package jm.cepstrum is part of the Renal Function Project
 * for analysis of dynamic contrast medium evalutions MRT-Images
 * of the Kidneys.
 * 
 * Copyright (C) 1999 / 2000 Jens Martin
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License 2
 * as published by the Free Software Foundation.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

package jm.cepstrum;

import java.awt.*;
import java.lang.Math.*;
import java.awt.image.*;

import jm.jigl.*;

import ij.*;
import ij.process.*;

import jm.util.*;						// 2000.7.31 tha: Fuer GridBagConstraints2


/**
 * Fuehrt die eigentliche Berechnung der Kepstrum-Filterung durch.
 * Wird von CepstrumAnalysis benoetigt. *
 * @see jm.kidney.AnalysisDialog
 * @see jm.cepstrum.CepstrumAnalysis
 * @version  3.1, 14/02/2000
 * @author   Jens Martin
 */
public class Cepstrum extends Frame {
	private int							img_width;
	private int							img_height;
	private int							all_width;
	private int							all_height;
	private java.awt.Point	img1_pos;
	private java.awt.Point	img2_pos;
	private boolean					destroy_when_ready;
	private boolean					imgs_docked;
	private boolean					do_windowing;
	private boolean					do_sobel;
	private boolean					check_result;
	private boolean					verbose = true;
	private boolean					writeLog = true;
	private int							translation_x;
	private int							translation_y;

	private ImagePlus				orgImage = null;
	private ImagePlus				fftImage = null;
	private ImagePlus				psImage = null;
	private ImagePlus				fft2Image = null;
	private ImagePlus				ps2Image = null;

	public ImageStack				orgStack = null;
	public ImageStack				fftStack = null;
	public ImageStack				psStack = null;
	public ImageStack				fft2Stack = null;
	public ImageStack				ps2Stack = null;
	public boolean					showImages = true;

	public double						peakQuality = 0.0;
	public double						peakAboveAverage = 0.0;

	private GrayImage				input1;
	private GrayImage				input2;

	// private GrayImage	imgOrg;
	private GrayImage				img1;
	private RealGrayImage		img2;
	private RealGrayImage		img3;
	private GrayImage				img4;

	// private GrayImage	img5;
	// private ImageCanvas	cnvImg0;
	// private ImageCanvas	cnvImg1;
	// private ImageCanvas	cnvImg2;
	// private ImageCanvas	cnvImg3;
	// private ImageCanvas	cnvImg4;
	// private ImageCanvas	cnvImg5;
	FlowLayout							flowLayout1 = new FlowLayout();


	/**
	 * Der Konstruktor. Erzeugt ein Klassen-Objekt.
	 * Die Eingabebilder werden in der Methode calculateTranslation uebergeben.
	 */
	public Cepstrum() {
		super();
		try {
			check_result = true;
			destroy_when_ready = true;
			do_sobel = true;
			do_windowing = true;
			imgs_docked = true;
			translation_x = -1;
			translation_y = -1;
			jbInit();
		} catch (Exception e) {
			e.printStackTrace();
		} 
	}


	/**
	 * Steuert die textuelle Ausgabe von Zwischenergebnissen im ImageJ-Hauptfenster
	 * @param    v      Boolean-Flag fuer Ausgabe
	 */
	public void setVerbose(boolean v) {
		verbose = v;
	} 


	/**
	 * Steuert die Ausgabe von Zwischenergebnissen in die Protokolldatei (LogFile)
	 * @param    log      Boolean-Flag fuer Ausgabe
	 */
	public void setWriteLog(boolean log) {
		writeLog = log;
	} 


	/**
	 * Haupt-Berechnungsmethode dieser Klasse. Die Berechnung der
	 * Verschiebung findet hier statt.
	 * @param    in1      Referenzbild, Datentyp: java.awt.Image
	 * @param    in2      Folgebild, Datentyp: java.awt.Image
	 * @param    shift    Enthaelt den Betrag der Verschiebung
	 * @return   boolean  true, falls die Berechnung erfolgreich war.
	 */
	public boolean calculateTranslation(java.awt.Image in1, java.awt.Image in2, java.awt.Point shift) {
		boolean result = false;

		try {
			shift.x = -1;
			shift.y = -1;

			// jigl-Grauwertbilder erzeugen
			input1 = new GrayImage(in1);
			input2 = new GrayImage(in2);

			// Bilddimensionen pruefen
			if ((input1.X() != input2.X()) && (input1.Y() != input2.Y())) {
				return result;
			} 
			img_width = input1.X();
			img_height = input1.Y();

			// Zeropadding fuer FFT: Bilderpositionen im Kepstrum-Fenster berechnen
			int offset_x = offsetToPowerOf2(img_width);
			int offset_y = offsetToPowerOf2(img_height);
			int xydiff = ((img_width + offset_x) * 2) - (img_height + offset_y);

			if (xydiff != 0) {
				if (xydiff > 0) {
					offset_y = offset_y + Math.abs(xydiff);
				} else {
					offset_x = offset_x + (xydiff / 2);
				} 
			} 
			all_width = (img_width + offset_x) * 2 * 2;
			all_height = (img_height + offset_y) * 2;
			img1_pos = new java.awt.Point((all_width / 4) + (offset_x / 2), (all_height / 4) + (offset_y / 2));
			img2_pos = new java.awt.Point((all_width / 2) + (offset_x / 2), (all_height / 4) + (offset_y / 2));

			// Dies ist das Kepstrum-Fenster
			img1 = new GrayImage(all_width, all_height);

			// IJ.write("p1:" + img1_pos.x + "/" + img1_pos.y + "   p2:" + img2_pos.x + "/" + img2_pos.y);
			// Sobel-Filter durchfuehren
			if (verbose) {
				IJ.write("Sobel-Operator...");
			} 
			if (do_sobel) {
				input1 = filterSobel(input1);
				input2 = filterSobel(input2);
			} 

			// Fensterung durchfuehren: Kaiser-Bessel-Fenster
			if (verbose) {
				IJ.write("Applying Window...");
			} 
			if (do_windowing) {
				input1 = filterKBWindow(input1, true);
				input2 = filterKBWindow(input2, true);

				// applyWindow(img1);
			} 

			// Bilder in das Kepstrum-Fenster kopieren
			int x, y;

			for (x = 0; x < img_width; x++) {
				for (y = 0; y < img_height; y++) {
					img1.set(img1_pos.x + x, img1_pos.y + y, input1.get(x, y));
					img1.set(img2_pos.x + x, img2_pos.y + y, input2.get(x, y));
				} 
			} 

			// Hier Anzeige von Zwischenergebnissen zur Debugzwecken moeglich
			if (orgStack == null && showImages == true) {
				ShortProcessor	sp = new ShortProcessor(10, 10, true);
				ColorModel			cm = sp.getColorModel();

				orgStack = new ImageStack(all_width, all_height, cm);
				fftStack = new ImageStack(all_width, all_height, cm);
				psStack = new ImageStack(all_width, all_height, cm);
				fft2Stack = new ImageStack(all_width, all_height, cm);
				ps2Stack = new ImageStack(all_width, all_height, cm);
			} 

			if (showImages) {
				orgStack.addSlice(" ", this.getGrayArray(img1));

				// LogFile schreiben
			} 
			if (writeLog) {
				ResultLog logFile = new ResultLog("cepstrum.log");

				logFile.append("" + do_sobel + "|" + do_windowing + "|" + all_width + "|" + all_height + "|");
			} 

			// Hilfsklasse zur Durchfuehrung der Berechnungen aufrufen
			result = doCepstrum();

			// Verschiebungsvektor fuellen
			shift.x = translation_x;
			shift.y = translation_y;
		} catch (Exception e) {
			e.printStackTrace();
			result = false;
		} 
		return result;
	} 


	/**
	 * Berechnet die Differenz(Offset) der ganzzahligen Eingabe zur naechsten Zweierpotenz.
	 * Bsp: Eingabe 23 -> naechste Potenz 32 -> Ergebnis 9
	 * @param size ganzzahliger Eingabewert
	 * @return int der berechnete Offset
	 */
	private int offsetToPowerOf2(int size) {
		int offset = 0;
		int power = 2;

		while (power < size) {
			power = power * 2;
			if (power == size) {
				return 0;
			} 
			offset = power - size;
		} 
		return offset;
	} 


	/**
	 * Hilfsmethode von calcualteTranslation. Fuehrt die Berechnung des Kepstrum-Filters druch.
	 * Alle noetigen Eingaben und Initialisierungen werden durch calculateTranslation vorgenommen.
	 * @return boolean true, wenn die Berechnung erfolgreich war
	 * @throws Exception Zum Abfangen von Fehlern
	 */
	private boolean doCepstrum() throws Exception {
		boolean result = false;

		try {

			// Fensterung des Kepstrum-Fensters !
			if (verbose) {
				IJ.write("Applying Window...");
			} 
			if (do_windowing) {
				applyWindow(img1);
			} 

			// Fourier-Transformation
			if (verbose) {
				IJ.write("Fourier-Transformation...");
			} 
			ComplexImage	imgcc = FFT.doFFT(img1, true);

			// Zentrieren des komplexwertigen Bildes -> Fensterkreuz muss fuer die
			// weiteren Berechnungen in der Mitte liegen
			if (verbose) {
				IJ.write("Centering FFT-Image...");
			} 
			{
				Shift					shift = new Shift(imgcc.X() / 2, imgcc.Y() / 2, 1);
				RealGrayImage imgr = (RealGrayImage) shift.apply(imgcc.real());
				RealGrayImage imgi = (RealGrayImage) shift.apply(imgcc.imag());

				imgcc.setImag(imgi);
				imgcc.setReal(imgr);
			} 

			// Augabe der Zwischenergebnisse fuer Debugzwecke
			if (showImages) {
				fftStack.addSlice(" ", this.getGrayArray(imgcc));

				// Power-Sektrum bilden
			} 
			if (verbose) {
				IJ.write("Building Power-Spectrum...");
			} 
			img2 = filterPower(imgcc);

			// imgcc.dispose();
			// Logarithmieren
			if (verbose) {
				IJ.write("calculating Log[] of Image...");
			} 
			img2 = filterLog(img2);

			// Fensterung des Zwischenergebnisses
			if (verbose) {
				IJ.write("Applying Window...");
			} 
			if (do_windowing) {
				img2 = filterKBWindow(img2, false);
			} 

			// Augabe der Zwischenergebnisse fuer Debugzwecke
			if (showImages) {
				psStack.addSlice(" ", this.getGrayArray(img2));

				// zweite Fourier-Transformation
			} 
			if (verbose) {
				IJ.write("Fourier-Transformation...");
			} 
			ComplexImage	imgcc2 = FFT.doFFT(img2, true);

			// Zentrieren des komplexwertigen Bildes -> Fensterkreuz muss fuer die
			// weiteren Berechnungen in der Mitte liegen
			if (verbose) {
				IJ.write("Centering FFT-Image...");
			} 
			{
				Shift					shift = new Shift(imgcc2.X() / 2, imgcc2.Y() / 2, 1);
				RealGrayImage imgr = (RealGrayImage) shift.apply(imgcc2.real());
				RealGrayImage imgi = (RealGrayImage) shift.apply(imgcc2.imag());

				imgcc2.setImag(imgi);
				imgcc2.setReal(imgr);
			} 

			// Nun ist die KEsptrum-Berechnung fertig !

			// Augabe der Zwischenergebnisse fuer Debugzwecke
			if (showImages) {
				fft2Stack.addSlice(" ", this.getGrayArray(imgcc2));

			} 
			if (verbose) {
				IJ.write("Building Power-Spectrum...");
			} 
			img3 = filterPower(imgcc2);
			if (showImages) {
				ps2Stack.addSlice(" ", this.getGrayArray(img3));

			} 
			if (verbose) {
				IJ.write("Searching for local maxima...");
			} 
			img4 = findLocalMax(imgcc2);

			// Augabe der Zwischenergebnisse fuer Debugzwecke,
			// hier komplett auskommeniert. Kann sehr hilfreich sein !

			/*
			 * if (input1 != null) cnvImg0 = new ImageCanvas( input1);
			 * if (input2 != null) cnvImg1 = new ImageCanvas( input2);
			 * cnvImg2 = new ImageCanvas( img1);
			 * cnvImg4 = new ImageCanvas( img4);
			 * //img3 = new RealGrayImage(imgcc2.X(),imgcc2.Y());
			 * //img3 = getMagScaleFromComplexImage(imgcc2);
			 * //img5 = ConvertImage.toGray(img1.copy());
			 * //img5.median(1);
			 * 
			 * //cnvImg5 = new ImageCanvas( getFFTImageForDisplay( img1));
			 * cnvImg5 = new ImageCanvas( getMagScaleFromComplexImage( imgcc2));
			 * //cnvImg5 = new ImageCanvas( ConvertImage.toGray(img1));
			 * //cnvImg3 = new ImageCanvas( (getMagScaleFromComplexImage( imgcc2)));
			 * //cnvImg3 = new ImageCanvas( (getFFTImageForDisplay( imgcc2)));
			 * //cnvImg5 = new ImageCanvas( img2);
			 * //cnvImg4 = new gui.ImageCanvas( ( ConvertImage.toGray( imgpw)));
			 * //cnvImg4 = new gui.ImageCanvas( ConvertImage.toGray(img3));
			 * //cnvImg4 = new ImageCanvas( img5);
			 * 
			 * // --
			 * 
			 * setLayout ( new FlowLayout());
			 * add( cnvImg2);
			 * add( cnvImg4);
			 * add( cnvImg5);
			 * add( cnvImg0);
			 * add( cnvImg1);
			 */
			result = true;
		} catch (Exception e) {
			e.printStackTrace();
			result = false;
		} 
		return result;
	} 


	/**
	 * Liefert zu einem Grauwertbild die Fouriertransformierte als
	 * Grauwertbild( Mag, 8bit skaliert und geshiftet).
	 * Dieses ist fuer die Anzeige am Schirm nachberarbeitet.
	 * Wurde nur fuer Debugzwecke gebraucht.
	 * @deprecated Wird nur noch fuer Debug-Zwecke gebraucht
	 * @param img jigl-Grauwertbild
	 * @return GrayImage Das Ergebnisbild.
	 */
	private GrayImage getFFTImageForDisplay(GrayImage img) {

		// ffting
		ComplexImage	imgcc = FFT.doFFT(img, true);

		// centering..
		Shift					shift = new Shift(imgcc.X() / 2, imgcc.Y() / 2, 1);
		RealGrayImage imgr = (RealGrayImage) shift.apply(imgcc.real());
		RealGrayImage imgi = (RealGrayImage) shift.apply(imgcc.imag());

		imgcc.setImag(imgi);
		imgcc.setReal(imgr);
		return getMagScaleFromComplexImage(imgcc);
	} 


	/**
	 * Fuehrt auf einem Grauwertbild eine Sobelfilterung durch.
	 * @param img jigl-Grauwertbild
	 * @return GrayImage Das Ergebnisbild.
	 */
	private GrayImage filterSobel(GrayImage img) {
		GrayImage imgres = new GrayImage(img.X(), img.Y());

		imgres.byteSize();
		imgres.clear();
		img.byteSize();
		int xx;
		int xxw = img.X();
		int yy;
		int yyw = img.Y();
		int p1 = 0, p2 = 0, p3 = 0, p4 = 0, p5 = 0, p6 = 0, p7 = 0, p8 = 0, p9 = 0;
		int result = 0;
		int result_x = 0;
		int result_y = 0;

		for (yy = 1; yy < yyw - 1; yy++) {
			for (xx = 1; xx < xxw - 1; xx++) {
				if (xx == 1 /* first_row */) {
					p1 = img.get(xx - 1, yy - 1);
					p2 = img.get(xx, yy - 1);
					p3 = img.get(xx + 1, yy - 1);
					p4 = img.get(xx - 1, yy);
					p5 = img.get(xx, yy);
					p6 = img.get(xx + 1, yy);
					p7 = img.get(xx - 1, yy + 1);
					p8 = img.get(xx, yy + 1);
					p9 = img.get(xx + 1, yy + 1);
				} else {
					p1 = p2;
					p2 = p3;
					p3 = img.get(xx + 1, yy - 1);
					p4 = p5;
					p5 = p6;
					p6 = img.get(xx + 1, yy);
					p7 = p8;
					p8 = p9;
					p9 = img.get(xx + 1, yy + 1);
				} 

				// Calculate horizontal Sobel
				result_x = p7 + 2 * p8 + p9 - p1 - 2 * p2 - p3;

				// Calculate vertical Sobel
				result_y = p3 + 2 * p6 + p9 - p1 - 2 * p4 - p7;

				// Correct Overflows
				result_x = Math.abs(result_x);
				result_y = Math.abs(result_y);

				// Put to resulting Image
				result = (result_x + result_y) / 4;		// / 2 / 4;
				if (result > 255) {
					result = 255;

				} 
				imgres.set(xx, yy, result);
			} 
		} 
		return imgres;
	} 


	/**
	 * Logarithmiert ein reell-wertiges Grauwertbild. Wird zur Kepstrum-Berechnung benoetigt.
	 * @param imgReal jigl-Grauwertbild (reellwertig, Datentyp RealGrawImage)
	 * @return RealGrayImage Das Ergebnisbild.
	 */
	public RealGrayImage filterLog(RealGrayImage imgReal) {
		int						x, y;
		float					a;
		int						width = imgReal.X();
		int						height = imgReal.Y();
		RealGrayImage imgLog = new RealGrayImage(width, height);

		for (x = 0; x < width; x++) {
			for (y = 0; y < height; y++) {

				// a = (float)(1.0 / Math.log(10)) * (float)Math.log((double)imgReal.get(x,y));
				a = (float) Math.log(imgReal.get(x, y));
				imgLog.set(x, y, a);
			} 
		} 
		return imgLog;
	} 


	/**
	 * Bildet das Power-Spektrum eines komplexwertigen Eingabebildes.
	 * Diese enthaelt das Ergebnis einer FFT. Wird zur Kepstrum-Berechnung benoetigt.
	 * @param imgcc komplexes Eingabebild, Datentyp ComplexImage)
	 * @return RealGrayImage Das Ergebnisbild.
	 */
	private RealGrayImage filterPower(ComplexImage imgcc) {
		int						x, y;
		float					a;
		int						width = imgcc.X();
		int						height = imgcc.Y();
		RealGrayImage imgPower = new RealGrayImage(width, height);

		for (x = 0; x < width; x++) {
			for (y = 0; y < height; y++) {
				a = (imgcc.getReal(x, y) * imgcc.getReal(x, y)) + (imgcc.getImag(x, y) * imgcc.getImag(x, y));
				imgPower.set(x, y, a);
			} 
		} 
		return imgPower;
	} 


	/**
	 * Hilfsfunktion zur Umwandlung eines komplexwertigen Bildes in ein short-Array.
	 * Ueberladene Methode.
	 * @param imgcc komplexes Eingabebild, Datentyp ComplexImage)
	 * @return short[] Array mit dem Ergebnisbild.
	 */
	private short[] getGrayArray(ComplexImage imgcc) {
		int			w = imgcc.X();
		int			h = imgcc.Y();
		short[] pix = new short[w * h];
		int			x, y;

		double	a = 0.0;
		double	max = Double.MIN_VALUE;
		double	c = 1.0;

		for (x = 0; x < w; x++) {
			for (y = 0; y < h; y++) {
				a = Math.sqrt(((double) imgcc.getReal(x, y) * (double) imgcc.getReal(x, y)) + ((double) imgcc.getImag(x, y) * (double) imgcc.getImag(x, y)));
				a = Math.log(1.0 + a);
				if (a > max) {
					max = a;
				} 
			} 
		} 
		c = max / 16380.0;

		for (x = 0; x < w; x++) {
			for (y = 0; y < h; y++) {
				a = Math.sqrt(((double) imgcc.getReal(x, y) * (double) imgcc.getReal(x, y)) + ((double) imgcc.getImag(x, y) * (double) imgcc.getImag(x, y)));
				a = Math.log(1.0 + a);
				a = a / c;
				pix[(y * w) + x] = (short) a;
			} 
		} 
		return pix;
	} 


	/**
	 * Hilfsfunktion zur Umwandlung eines reellwertigen Bildes in ein short-Array.
	 * Ueberladene Methode.
	 * @param imgcc reelles Eingabebild, Datentyp RealGrayImage)
	 * @return short[] Array mit dem Ergebnisbild.
	 */
	private short[] getGrayArray(RealGrayImage imgcc) {
		int			w = imgcc.X();
		int			h = imgcc.Y();
		short[] pix = new short[w * h];
		int			x, y;

		double	a = 0.0;
		double	max = Double.MIN_VALUE;
		double	c = 1.0;

		for (x = 0; x < w; x++) {
			for (y = 0; y < h; y++) {
				a = (double) imgcc.get(x, y);
				if (a > max && a < 16384) {
					max = a;
				} 
			} 
		} 
		c = max / 16384;
		for (x = 0; x < w; x++) {
			for (y = 0; y < h; y++) {

				// a = Math.sqrt(((double)imgcc.getReal(x,y)*(double)imgcc.getReal(x,y)) +
				// ((double)imgcc.getImag(x,y)*(double)imgcc.getImag(x,y)) );

				a = (double) imgcc.get(x, y);
				a = a / c;
				pix[(y * w) + x] = (short) a;
			} 
		} 
		return pix;
	} 


	/**
	 * Hilfsfunktion zur Umwandlung eines jigl-Grauwertbildes in ein short-Array.
	 * Ueberladene Methode.
	 * @param imgcc Eingabebild, Datentyp GrayImage)
	 * @return short[] Array mit dem Ergebnisbild.
	 */
	private short[] getGrayArray(GrayImage imgcc) {
		int			w = imgcc.X();
		int			h = imgcc.Y();
		short[] pix = new short[w * h];
		int			x, y;

		double	a = 0.0;
		double	max = Double.MIN_VALUE;
		double	c = 1.0;

		for (x = 0; x < w; x++) {
			for (y = 0; y < h; y++) {
				a = (double) imgcc.get(x, y);
				if (a > max && a < 16384) {
					max = a;
				} 
			} 
		} 
		c = max / 16384;

		for (x = 0; x < w; x++) {
			for (y = 0; y < h; y++) {

				// a = Math.sqrt(((double)imgcc.getReal(x,y)*(double)imgcc.getReal(x,y)) +
				// ((double)imgcc.getImag(x,y)*(double)imgcc.getImag(x,y)) );

				a = (double) imgcc.get(x, y);
				a = a / c;
				pix[(y * w) + x] = (short) a;
			} 
		} 
		return pix;
	} 


	/**
	 * FUehrt die Fensterung aus. Berechnet auf dem Eingabebild ein kaiser-Bessel-Fenster,
	 * so dass der Rand nach scharz ausgeblendet wird.
	 * Ueberladene Methode.
	 * @param img     Eingabebild, Datentyp GrayImage)
	 * @param inverse wenn true, dann wird der Bereich in der Bildmitte nach schwarz ausgeblendet.
	 * @return GrayImage Das Ergebnisbild.
	 */
	private GrayImage filterKBWindow(GrayImage img, boolean inverse) {

		int				w = img.X();
		int				h = img.Y();
		GrayImage imgWin = new GrayImage(w, h);
		int				x, y;
		double		twoPiDivWidth = (2.0 * Math.PI) / (double) w;
		double		twoPiDivHeight = (2.0 * Math.PI) / (double) h;
		double		kb, val;

		for (y = 0; y < h; y++) {
			for (x = 0; x < w; x++) {
				val = (double) img.get(x, y);
				kb = 0.40243 - (0.49804 * Math.cos(twoPiDivWidth * (double) x)) + (0.09831 * Math.cos(twoPiDivWidth * 2.0 * (double) x)) - (0.00122 * Math.cos(twoPiDivWidth * 3.0 * (double) x));
				if (inverse) {
					kb = 1.0 - kb;
				} 
				imgWin.set(x, y, (int) (kb * val));
			} 
		} 
		for (x = 0; x < w; x++) {
			for (y = 0; y < h; y++) {
				val = (double) imgWin.get(x, y);
				kb = 0.40243 - (0.49804 * Math.cos(twoPiDivHeight * (double) y)) + (0.09831 * Math.cos(twoPiDivHeight * 2.0 * (double) y)) - (0.00122 * Math.cos(twoPiDivHeight * 3.0 * (double) y));
				if (inverse) {
					kb = 1.0 - kb;
				} 
				imgWin.set(x, y, (int) (kb * val));
			} 
		} 
		return imgWin;
	} 


	/**
	 * FUehrt die Fensterung aus. Berechnet auf dem Eingabebild ein Kaiser-Bessel-Fenster,
	 * so dass der Rand nach scharz ausgeblendet wird.
	 * Ueberladene Methode.
	 * @param img     Eingabebild, Datentyp RealGrayImage)
	 * @param inverse wenn true, dann wird der Bereich in der Bildmitte nach schwarz ausgeblendet.
	 * @return RealGrayImage Das Ergebnisbild.
	 */
	private RealGrayImage filterKBWindow(RealGrayImage img, boolean inverse) {
		int						w = img.X();
		int						h = img.Y();
		RealGrayImage imgWin = new RealGrayImage(w, h);
		int						x, y;
		double				twoPiDivWidth = (2.0 * Math.PI) / (double) w;
		double				twoPiDivHeight = (2.0 * Math.PI) / (double) h;
		double				kb, val;

		for (y = 0; y < h; y++) {
			for (x = 0; x < w; x++) {
				val = (double) img.get(x, y);
				kb = 0.40243 - (0.49804 * Math.cos(twoPiDivWidth * (double) x)) + (0.09831 * Math.cos(twoPiDivWidth * 2.0 * (double) x)) - (0.00122 * Math.cos(twoPiDivWidth * 3.0 * (double) x));
				if (inverse) {
					kb = 1.0 - kb;
				} 
				imgWin.set(x, y, (float) (kb * val));
			} 
		} 
		for (x = 0; x < w; x++) {
			for (y = 0; y < h; y++) {
				val = (double) imgWin.get(x, y);
				kb = 0.40243 - (0.49804 * Math.cos(twoPiDivHeight * (double) y)) + (0.09831 * Math.cos(twoPiDivHeight * 2.0 * (double) y)) - (0.00122 * Math.cos(twoPiDivHeight * 3.0 * (double) y));
				if (inverse) {
					kb = 1.0 - kb;
				} 
				imgWin.set(x, y, (int) (kb * val));
			} 
		} 
		return imgWin;
	} 


	/**
	 * Alte Fensterfunktion. Wird nicht mehr benoetigt.
	 * Das Eingabebild wird dabei modifiziert.
	 * Nur noch fuer Debugzwecke in der Klasse.
	 * @deprecated Wird nur noch fuer Debug-Zwecke gebraucht
	 * @param img     Eingabebild, Datentyp GrayImage)
	 */
	private void applyWindow(GrayImage img) {
		int			x;
		int			y;
		int			bw = 8;
		double	a = 3;
		double	valorg = 0.0;
		double	valnew = 0.0;
		double	exponent = 0.0;

		for (x = img1_pos.x; x < (img2_pos.x + img_width); x++) {
			for (y = 0; y < (img_height / bw); y++) {
				valorg = (double) img.get(x, (img1_pos.y + y));
				exponent = Math.pow((a * (double) (y) / (double) (img_height / bw)), 2.0) * (-1.0);
				valnew = valorg * (1.0 - Math.pow(Math.E, exponent));
				img.set(x, (img1_pos.y + y), (int) (valnew));
			} 
			for (y = 0; y < (img_height / bw); y++) {
				valorg = (double) img.get(x, (img1_pos.y + img_height - 1 - y));
				exponent = Math.pow((a * (double) (y) / (double) (img_height / bw)), 2.0) * (-1.0);
				valnew = valorg * (1.0 - Math.pow(Math.E, exponent));
				img.set(x, (img1_pos.y + img_height - 1 - y), (int) (valnew));
			} 
		} 
		for (y = img1_pos.y; y < (img1_pos.y + img_height); y++) {
			for (x = 0; x < (img_width / bw); x++) {
				valorg = (double) img.get(img1_pos.x + x, y);
				exponent = Math.pow((a * (double) (x) / (double) (img_width / bw)), 2.0) * (-1.0);
				valnew = valorg * (1.0 - Math.pow(Math.E, exponent));
				img.set(img1_pos.x + x, y, (int) (valnew));
			} 
			for (x = 0; x < (img_width / bw); x++) {
				valorg = (double) img.get(img2_pos.x + img_width - 1 - x, y);
				exponent = Math.pow((a * (double) (x) / (double) (img_width / bw)), 2.0) * (-1.0);
				valnew = valorg * (1.0 - Math.pow(Math.E, exponent));
				img.set(img2_pos.x + img_width - 1 - x, y, (int) (valnew));
			} 
		} 
		if (imgs_docked) {
			for (y = img1_pos.y; y < (img1_pos.y + img_height); y++) {
				for (x = 0; x < (img_width / bw); x++) {
					valnew = (double) img.get(img2_pos.x - x, y);
					valnew = valnew + (double) img.get(img2_pos.x - 1 - x, y);
					img.set(img2_pos.x - 1 - x, y, (((int) valnew) / 2));
				} 
			} 
			for (y = img1_pos.y; y < (img1_pos.y + img_height); y++) {
				for (x = 0; x < (img_width / bw); x++) {
					valnew = (double) img.get(img2_pos.x - 1 + x, y);
					valnew = valnew + (double) img.get(img2_pos.x + x, y);
					img.set(img2_pos.x + x, y, (((int) valnew) / 2));
				} 
			} 
		} else {
			for (y = img1_pos.y; y < (img1_pos.y + img_height); y++) {
				for (x = 0; x < (img_width / bw); x++) {
					valorg = (double) img.get(img2_pos.x + x, y);
					exponent = Math.pow((a * (double) (x) / (double) (img_width / bw)), 2.0) * (-1.0);
					valnew = valorg * (1.0 - Math.pow(Math.E, exponent));
					img.set(img2_pos.x + x, y, (int) (valnew));
				} 
				for (x = 0; x < (img_width / bw); x++) {
					valorg = (double) img.get(img1_pos.x + img_width - 1 - x, y);
					exponent = Math.pow((a * (double) (x) / (double) (img_width / bw)), 2.0) * (-1.0);
					valnew = valorg * (1.0 - Math.pow(Math.E, exponent));
					img.set(img1_pos.x + img_width - 1 - x, y, (int) (valnew));
				} 
			} 
		} 

	} 


	/**
	 * Hilfsmethode. Legt fest, ob die als Zwischenergebnisse erzeugten
	 * Bilder schon zur Laufzeit verworfen werden sollen.
	 * @param destroy     true, wenn die Referenzen geloescht werden sollen
	 */
	public void setDestroyInputImages(boolean destroy) {
		destroy_when_ready = destroy;
	} 


	/**
	 * Hilfsmethode. Legt fest, ob eine Fensterung (Kaiser-Bessel-Fenster)
	 * durchgefuehrt werden soll.
	 * @param windowing     true, wenn Fensterung durchgefuehrt werden soll
	 */
	public void setApplyWindow(boolean windowing) {
		do_windowing = windowing;
	} 


	/**
	 * Hilfsmethode. Legt fest, ob eine Sobelfilterung als Vorverarbeitung fuer die
	 * Kepstrum-Filterung durchgefuehrt werden soll.
	 * @param sobel     true, wenn Sobel-Filter durchgefuehrt werden soll
	 */
	public void setApplySobel(boolean sobel) {
		do_sobel = sobel;
	} 


	/**
	 * Hilfsmethode. Liest aus, ob ein Zerstoeren der Zwischenergebnisse
	 * stattfinden soll.
	 * @return boolean true, wenn die Referenzen geloescht werden sollen
	 */
	public boolean getDestroyInputImages() {
		return destroy_when_ready;
	} 


	/**
	 * Hilfsmethode. Liest aus, ob eine Fensterung (Kaiser-Bessel-Fenster)
	 * durchgefuehrt werden soll.
	 * @return boolean true, wenn Fensterung durchgefuehrt werden soll
	 */
	public boolean getApplyWindow() {
		return do_windowing;
	} 


	/**
	 * Hilfsmethode. Liest aus, ob eine Sobelfilterung als Vorverarbeitung fuer die
	 * Kepstrum-Filterung durchgefuehrt werden soll.
	 * @return boolean true, wenn Sobel-Filter durchgefuehrt werden soll
	 */
	public boolean getApplySobel() {
		return do_sobel;
	} 


	/**
	 * Liefert zu einem komplexwertigen Bild ein
	 * fuer die Bildschirmanzeige optimiertes Power-Spektrum als Grauwertbild.
	 * Wurde nur fuer Debugzwecke gebraucht.
	 * @deprecated Wird nur noch fuer Debug-Zwecke gebraucht
	 * @param imgcc komplexwertiges Einagbebild, Datentyp  ComplexImage
	 * @return GrayImage Das Ergebnisbild.
	 */
	private GrayImage getMagScaleFromComplexImage(ComplexImage imgcc) {

		// mag image
		GrayImage result = new GrayImage(imgcc.X(), imgcc.Y());

		// scale to 8 bit
		int				xx;
		int				xxw = imgcc.X();
		int				yy;
		int				yyw = imgcc.Y();

		for (yy = 0; yy < yyw; yy++) {
			for (xx = 0; xx < xxw; xx++) {
				int i = (int) Math.log(1.0 + Math.sqrt(Math.pow((double) imgcc.getReal(xx, yy), 2.0) + Math.pow((double) imgcc.getImag(xx, yy), 2.0)));

				if (i < 0) {
					i = i * (-1);
				} 
				if (i > 255) {
					i = 255;
				} 
				result.set(xx, yy, i);
			} 
		} 
		return result;
	} 


	/**
	 * Erzeugt aus einem Eingabebild ein verschobenes Bild.
	 * Die Verschiebung wird mit uebergeben.
	 * Wurde nur fuer Testzwecke gebraucht.
	 * @deprecated Wird nur noch fuer Testzwecke gebraucht
	 * @param posx x-Position innerhalb des Bildes
	 * @param posy y-Position innerhalb des Bildes
	 * @param shiftx zu erzeugende x-Verschiebung
	 * @param shifty zu erzeugende y-Verschiebung
	 * @return GrayImage Das Ergebnisbild.
	 */
	public GrayImage createTranslation(GrayImage img, int posx, int posy, int shiftx, int shifty) {
		GrayImage result = new GrayImage(256, 256);

		int				xx;
		int				xxw = result.X();
		int				yy;
		int				yyw = result.Y();

		for (yy = yyw / 4; yy < yyw / 4 * 3; yy++) {
			for (xx = xxw / 4; xx < xxw / 2; xx++) {
				result.set(xx, yy, img.get(posx + xx, posy + yy));
				result.set(xx + xxw / 4, yy, img.get(posx + shiftx + xx, posy + shifty + yy));
			} 
		} 
		return result;
	} 


	/**
	 * Initialisierung der Objekte des Fensters.
	 * Von JBuilder automatisch eingefuegt.
	 */
	private void jbInit() throws Exception {
		this.setSize(new Dimension(638, 524));
		this.setLayout(flowLayout1);
	} 


	/**
	 * Sucht dein lokales Maximum im Suchbereich der Kepstralebene.
	 * Wird zur Berechnung des Kepstrums benoetigt.
	 * @param imgcc komplexwertiges Eingabebild (die Kepstral-Ebene)
	 * @return GrayImage Den bearbeitenen Suchbereich als Grauwertbild. Nur fuer Testzwecke.
	 */
	public GrayImage findLocalMax(ComplexImage imgcc) {
		int				width = imgcc.X();
		int				height = imgcc.Y();
		GrayImage result = new GrayImage(width, height);
		int				i, pos;
		int				max = 5;
		int[]			px = new int[max];
		int[]			py = new int[max];
		long[]		val = new long[max];
		int				x_start_search = width / 8;
		int				x_end_search = width / 8 * 3;
		int				y_start_search = 0;
		int				y_end_search = height;
		int				xx, yy;
		double		a;
		long			b;
		double		average = 0.0;
		int				counter = 0;

		for (i = 0; i < max; i++) {
			px[i] = 0;
			py[i] = 0;
			val[i] = 0;
		} 

		// es werden die 'max' hellsten Punkte im Bild gesucht
		for (xx = x_start_search; xx < x_end_search; xx++) {
			for (yy = y_start_search; yy < y_end_search; yy++) {
				a = Math.pow((double) imgcc.getReal(xx, yy), 2.0) + Math.pow((double) imgcc.getImag(xx, yy), 2.0);
				b = (long) a;
				average = average + (a / 1000.0);
				counter++;
				pos = 0;
				while (pos < max) {
					if (b > val[pos]) {

						// IJ.write( "Found new max Val: " + b + " --- Shifting Points...");
						i = max - 1;
						for (i = max - 1; i > pos; i--) {
							px[i] = px[i - 1];
							py[i] = py[i - 1];
							val[i] = val[i - 1];
						} 
						val[pos] = b;
						px[pos] = xx;
						py[pos] = yy;
						pos = max;
					} 
					pos++;
				} 
			} 
		} 

		// Punkte in das Ergebnisbild einzeichnen
		// Nur fuer Testzwecke.
		for (i = 0; i < max; i++) {
			result.set(px[i], py[i], 255);
		} 

		// Fuer Fuzzy-Guete-Bewertung: Abstaende aller Punkte addieren:
		double	fuzzy = 0;

		for (i = 1; i < max; i++) {
			fuzzy = fuzzy + Math.sqrt(Math.pow((double) Math.abs(px[0] - px[i]), 2.0) + Math.pow((double) Math.abs(py[0] - py[i]), 2.0));

		} 
		if (verbose) {
			IJ.write("Quality of Cepstrum-Maximum: " + fuzzy);
		} 
		this.peakQuality = fuzzy;

		// Logfile schreiben
		if (writeLog) {
			ResultLog logFile = new ResultLog("cepstrum.log");

			logFile.append("" + fuzzy + "|");
		} 

		// Druchschnittlichen Grauwert des Power-Kepstrums:
		average = average / (double) counter;
		average = average * 1000.0;
		double	percentAboveAverage = Math.pow((double) imgcc.getReal(px[0], py[0]), 2.0) + Math.pow((double) imgcc.getImag(px[0], py[0]), 2.0);

		percentAboveAverage = percentAboveAverage / average;
		if (verbose) {
			IJ.write("Cepstrum-Maximum is above Average (in %): " + percentAboveAverage);
		} 
		this.peakAboveAverage = percentAboveAverage;

		// Logfile schreiben
		if (writeLog) {
			ResultLog logFile = new ResultLog("cepstrum.log");

			logFile.append("" + percentAboveAverage + "|");
		} 

		// Verschiebung berechnen:
		int shiftx = 0;
		int shifty = 0;
		int midx = x_start_search + (x_end_search - x_start_search) / 2;
		int midy = y_start_search + (y_end_search - y_start_search) / 2;

		// IJ.write("Found Middle at " + midx + " / " + midy);
		shiftx = (midx - px[0]) * (-1);
		shifty = ((midy - py[0]) * (-1));		// + 1;
		if (verbose) {
			IJ.write("Cepstrum calculated Translation: " + shiftx + " / " + shifty);
		} 
		translation_x = shiftx;
		translation_y = shifty;

		// translation_y--;//??? Scheint besser zu sein !!!
		return result;
	} 

}




/*--- formatting done in "My Own Convention" style on 08-04-2000 ---*/

