package epfl;
/*=====================================================================
| Version: April 6, 2014
\=====================================================================*/

import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;

/*=====================================================================
| Philippe Thevenaz
| EPFL/STI/IMT-LS/LIB/BM.4.137
| Station 17
| CH-1015 Lausanne VD
| Switzerland
|
| phone (CET): +41(21)693.51.61
| fax: +41(21)693.68.10
| RFC-822: philippe.thevenaz@epfl.ch
| X-400: /C=ch/A=400net/P=switch/O=epfl/S=thevenaz/G=philippe/
| URL: http://bigwww.epfl.ch/
\=====================================================================*/

/*===================================================================
| This work is based on the following paper:
|
| Michael Unser
| Splines: A Perfect Fit for Signal and Image Processing
| IEEE Signal Processing Magazine
| vol. 16, no. 6, pp. 22-38, November 1999
|
| This paper is available on-line at
| http://bigwww.epfl.ch/publications/unser9902.html
|
| Other relevant on-line publications are available at
| http://bigwww.epfl.ch/publications/
 \==================================================================*/

/*====================================================================
| See the companion file "Differentials_.html" for additional help
|
| You'll be free to use this software for research purposes, but you should not
| redistribute it without our consent. In addition, we expect you to include a
| citation or acknowlegment whenever you present or publish results that are
| based on it.
\===================================================================*/

import ij.IJ;
import ij.ImagePlus;
import ij.ImageStack;
import ij.Undo;
import ij.WindowManager;
import ij.gui.GenericDialog;
import ij.gui.ImageWindow;
import ij.gui.ProgressBar;
import ij.plugin.PlugIn;
import ij.process.ImageConverter;
import ij.process.ImageProcessor;
import ij.process.StackConverter;

/*====================================================================
|	class Differentials_
\===================================================================*/
/*------------------------------------------------------------------*/
public class Differentials_
	implements
		PlugIn

{ /* begin class Differentials_ */

/*....................................................................
	public variables
....................................................................*/
public final static int GRADIENT_DIRECTION = 1;
public final static int GRADIENT_MAGNITUDE = 0;
public final static int HESSIAN_ORIENTATION = 5;
public final static int LAPLACIAN = 2;
public final static int LARGEST_HESSIAN = 3;
public final static int SMALLEST_HESSIAN = 4;
public final static int GRADIENT_X = 6;
public final static int GRADIENT_Y = 7;
public final static int GRADIENT_XY = 8;
public final static int NORMALIZE_LMAX = 9;
public final static int LOCAL_MIN = 10;   // local minimum within radius
public final static int LOCAL_MAX = 11;   // local maximum within radius
public final static int LOCAL_DIFF = 12;  // local maximum - local minimum within radius
public final static int LOCAL_ISMAX = 13; // 1.0 if it is a local max, 0 - otherwise


private final static int THREADS_MAX = 100;

/*....................................................................
	private variables
....................................................................*/
private ImagePlus imp = null;
private ProgressBar progressBar = IJ.getInstance().getProgressBar();
private int completed = 1;
private int processDuration = 1;
private int stackSize = 1;
private final double FLT_EPSILON =
	(double)Float.intBitsToFloat((int)0x33FFFFFF);
private long lastTime = System.currentTimeMillis();
private static int operation = LAPLACIAN;
private static double max_norm_rad = 5;
private static float dir_x = 0;   // direction for derivative
private static float dir_y = 1.0f;
private static String[] operations = {
	"Gradient Magnitude",
	"Gradient Direction",
	"Laplacian",
	"Largest Hessian",
	"Smallest Hessian",
	"Hessian Orientation",
	"Gradient X",
	"Gradient Y",
	"Directional derivative",
	"Normalize by local maximum",
	"Local min",
	"Local max",
	"Local diff",
	"Local ismax"
};

/*....................................................................
	PlugIn methods
....................................................................*/
/*------------------------------------------------------------------*/
public void run (
	String arg
) {
	ImagePlus imp = WindowManager.getCurrentImage();
	this.imp = imp;
	if (imp == null) {
		IJ.noImage();
		return;
	}
	if ((1 < imp.getStackSize()) && (imp.getType() == ImagePlus.COLOR_256)) {
		IJ.error("Stack of color images not supported (use grayscale)");
		return;
	}
	if (1 < imp.getStackSize()) {
		if (imp.getStack().isRGB()) {
			IJ.error("RGB color images not supported (use grayscale)");
			return;
		}
	}
	if (1 < imp.getStackSize()) {
		if (imp.getStack().isHSB()) {
			IJ.error("HSB color images not supported (use grayscale)");
			return;
		}
	}
	if (imp.getType() == ImagePlus.COLOR_256) {
		IJ.error("Indexed color images not supported (use grayscale)");
		return;
	}
	if (imp.getType() == ImagePlus.COLOR_RGB) {
		IJ.error("Color images not supported (use grayscale)");
		return;
	}

	GenericDialog dialog = new GenericDialog("Differentials");
	dialog.addChoice("Operation:", operations, operations[operation]);
	dialog.addMessage("--- optional parameters ---");
	dialog.addNumericField("Multiply_d/dx_by",       dir_x, 3,5,"");
	dialog.addNumericField("Multiply_d/dy_by",       dir_y, 3,5,"");
	dialog.addNumericField("Normalization_radius",   max_norm_rad, 3,5,"");

	dialog.showDialog();
	if (dialog.wasCanceled()) {
		return;
	}
	final String operationString = dialog.getNextChoice();
    System.out.println("operationString='"+operationString+"'");
	
	for (int choice = 0; (choice < operations.length); choice++) {
		if (operationString.equals(operations[choice])) {
			operation = choice;
			break;
		}
	}
	dir_x =        (float) dialog.getNextNumber();
	dir_y =        (float) dialog.getNextNumber();
	max_norm_rad = dialog.getNextNumber();
    System.out.println("operation="+operation+", dir_x="+dir_x+", dir_y="+dir_y+", max_norm_rad="+max_norm_rad);
	imp.startTiming();
	if (1 < imp.getStackSize()) {
		if (!(imp.getProcessor().getPixels() instanceof float[])) {
			new StackConverter(imp).convertToGray32();
		}
	}
	else {
		if (!(imp.getProcessor().getPixels() instanceof float[])) {
			new ImageConverter(imp).convertToGray32();
		}
	}
	ImageStack stack = imp.getStack();
	stackSize = stack.getSize();
	Undo.reset();

	setupProgressBar();
	resetProgressBar();

	for (int i = 1; (i <= stackSize); i++) {
		ImageProcessor ip = stack.getProcessor(i);
		doIt(ip);
		imp.getProcessor().resetMinAndMax();
		imp.setSlice(i);
		imp.updateAndRepaintWindow();
	}
	imp.getProcessor().resetMinAndMax();
	imp.setSlice(1);
	imp.updateAndRepaintWindow();
	cleanUpProgressBar();
	IJ.showTime(imp, imp.getStartTime(), "Differentials: ");
	ImageWindow win = imp.getWindow();
	if (win != null) {
		win.running = false;
	}
} /* end run */

/*....................................................................
	public methods
....................................................................*/
/*------------------------------------------------------------------*/
public void getCrossHessian (
	ImageProcessor ip,
	double tolerance
) {
	getHorizontalGradient(ip, tolerance);
	getVerticalGradient(ip, tolerance);
} /* end getCrossHessian */

/*------------------------------------------------------------------*/
public void getHorizontalGradient (
	ImageProcessor ip,
	double tolerance
) {
	if (!(ip.getPixels() instanceof float[])) {
		throw new IllegalArgumentException("Float image required");
	}

	int width = ip.getWidth();
	int height = ip.getHeight();
	double line[] = new double[width];

	for (int y = 0; (y < height); y++) {
		getRow(ip, y, line);
		getSplineInterpolationCoefficients(line, tolerance);
		getGradient(line);
		putRow(ip, y, line);
		stepProgressBar();
	}
} /* end getHorizontalGradient */

/*------------------------------------------------------------------*/
public void getHorizontalHessian (
	ImageProcessor ip,
	double tolerance
) {
	if (!(ip.getPixels() instanceof float[])) {
		throw new IllegalArgumentException("Float image required");
	}

	int width = ip.getWidth();
	int height = ip.getHeight();
	double line[] = new double[width];

	for (int y = 0; (y < height); y++) {
		getRow(ip, y, line);
		getSplineInterpolationCoefficients(line, tolerance);
		getHessian(line);
		putRow(ip, y, line);
		stepProgressBar();
	}
} /* end getHorizontalHessian */

/*------------------------------------------------------------------*/
public void getVerticalGradient (
	ImageProcessor ip,
	double tolerance
) {
	if (!(ip.getPixels() instanceof float[])) {
		throw new IllegalArgumentException("Float image required");
	}

	int width = ip.getWidth();
	int height = ip.getHeight();
	double line[] = new double[height];

	for (int x = 0; (x < width); x++) {
		getColumn(ip, x, line);
		getSplineInterpolationCoefficients(line, tolerance);
		getGradient(line);
		putColumn(ip, x, line);
		stepProgressBar();
	}
} /* end getVerticalGradient */

/*------------------------------------------------------------------*/
public void getVerticalHessian (
	ImageProcessor ip,
	double tolerance
) {
	if (!(ip.getPixels() instanceof float[])) {
		throw new IllegalArgumentException("Float image required");
	}

	int width = ip.getWidth();
	int height = ip.getHeight();
	double line[] = new double[height];

	for (int x = 0; (x < width); x++) {
		getColumn(ip, x, line);
		getSplineInterpolationCoefficients(line, tolerance);
		getHessian(line);
		putColumn(ip, x, line);
		stepProgressBar();
	}
} /* end getVerticalHessian */

/*....................................................................
	private methods
....................................................................*/
/*------------------------------------------------------------------*/
private void antiSymmetricFirMirrorOnBounds (
	double[] h,
	double[] c,
	double[] s
) {
	if (h.length != 2) {
		throw new IndexOutOfBoundsException(
			"The half-length filter size should be 2");
	}
	if (h[0] != 0.0) {
		throw new IllegalArgumentException(
			"Antisymmetry violation (should have h[0]=0.0)");
	}
	if (c.length != s.length) {
		throw new IndexOutOfBoundsException("Incompatible size");
	}
	if (2 <= c.length) {
		s[0] = 0.0;
		for (int i = 1; (i < (s.length - 1)); i++) {
			s[i] = h[1] * (c[i + 1] - c[i - 1]);
		}
		s[s.length - 1] = 0.0;
	}
	else {
		if (c.length == 1) {
			s[0] = 0.0;
		}
		else {
			throw new NegativeArraySizeException("Invalid length of data");
		}
	}
} /* end antiSymmetricFirMirrorOnBounds */

/*------------------------------------------------------------------*/
private void cleanUpProgressBar (
) {
	completed = 0;
	progressBar.show(2.0);
} /* end cleanUpProgressBar */

/*------------------------------------------------------------------*/
private void doIt (
	ImageProcessor ip
) {
	int width = ip.getWidth();
	int height = ip.getHeight();

	if (!(ip.getPixels() instanceof float[])) {
		throw new IllegalArgumentException("Float image required");
	}
	switch (operation) { // for positive values only
	    case LOCAL_ISMAX:
		{
			final float[] floatPixels =  (float[])ip.getPixels();
			final double [] pixels = new double[floatPixels.length];
			for (int i = 0; i < pixels.length; i++) {
				pixels[i] = floatPixels[i];
			}
			Arrays.fill(floatPixels, 0.0f);
			final Thread[] threads = newThreadArray(THREADS_MAX);
			final AtomicInteger ai = new AtomicInteger(0);
			for (int y00 = 0; (y00 < height); y00++) {
				final int y0 = y00;
				int ymin = Math.max(y0-1, 0);
				int ymax = Math.min(y0+1, height-1);
				ai.set(0);
				for (int ithread = 0; ithread < threads.length; ithread++) {
					threads[ithread] = new Thread() {
						public void run() {
							for (int x0 = ai.getAndIncrement(); x0 < width; x0 = ai.getAndIncrement()) {
								int k = y0*width + x0;
								int xmin = Math.max(x0-1, 0);
								int xmax = Math.min(x0+1, width-1);
								double d = pixels[k];
								if (d > 0) {
									maxtest:
									{
										for (int y = ymin; y <= ymax; y++) {
											for (int x = xmin; x <= xmax; x++) {
												int dx = x-x0;
												int dy = y-y0;
												if (pixels[y*width+x] > d) {
													break maxtest;
												}
											}
										}
										floatPixels[k] = 1.0f;
									}
								}
							}
						}
					};
				}		      
				startAndJoin(threads);
				stepProgressBar();
			}
		}
		break;
	
	    case LOCAL_DIFF:
		{
			final int ir = (int) Math.ceil(max_norm_rad);
			final double r2 = max_norm_rad * max_norm_rad;
			final float[] floatPixels =  (float[])ip.getPixels();
			final double [] pixels = new double[floatPixels.length];
			for (int i = 0; i < pixels.length; i++) {
				pixels[i] = floatPixels[i];
			}
			final Thread[] threads = newThreadArray(THREADS_MAX);
			final AtomicInteger ai = new AtomicInteger(0);
			for (int y00 = 0; (y00 < height); y00++) {
				final int y0 = y00;
				int ymin = Math.max(y0-ir, 0);
				int ymax = Math.min(y0+ir, height-1);
				ai.set(0);
				for (int ithread = 0; ithread < threads.length; ithread++) {
					threads[ithread] = new Thread() {
						public void run() {
							for (int x0 = ai.getAndIncrement(); x0 < width; x0 = ai.getAndIncrement()) {
								int k = y0*width + x0;
								int xmin = Math.max(x0-ir, 0);
								int xmax = Math.min(x0+ir, width-1);
								double d = pixels[k];
								double dmax = d;
								double dmin = d;
								for (int y = ymin; y <= ymax; y++) {
									for (int x = xmin; x <= xmax; x++) {
										int dx = x-x0;
										int dy = y-y0;
										if ((dx*dx+dy*dy) > r2) {
											continue;
										}
										if (pixels[y*width+x] > dmax) {
											dmax=pixels[y*width+x];
										} else if (pixels[y*width+x] < dmin) {
											dmin=pixels[y*width+x];
										}

									}
								}
								floatPixels[k] = (float) (dmax - dmin);
							}
						}
					};
				}		      
				startAndJoin(threads);
				stepProgressBar();
			}
		}
		break;
	
		case LOCAL_MIN:
		{
			final int ir = (int) Math.ceil(max_norm_rad);
			final double r2 = max_norm_rad * max_norm_rad;
			final float[] floatPixels =  (float[])ip.getPixels();
			final double [] pixels = new double[floatPixels.length];
			for (int i = 0; i < pixels.length; i++) {
				pixels[i] = floatPixels[i];
			}
			final Thread[] threads = newThreadArray(THREADS_MAX);
			final AtomicInteger ai = new AtomicInteger(0);
			for (int y00 = 0; (y00 < height); y00++) {
				final int y0 = y00;
				int ymin = Math.max(y0-ir, 0);
				int ymax = Math.min(y0+ir, height-1);
				ai.set(0);
				for (int ithread = 0; ithread < threads.length; ithread++) {
					threads[ithread] = new Thread() {
						public void run() {
							for (int x0 = ai.getAndIncrement(); x0 < width; x0 = ai.getAndIncrement()) {
								int k = y0*width + x0;
								int xmin = Math.max(x0-ir, 0);
								int xmax = Math.min(x0+ir, width-1);
								double d = pixels[k];
								double dmin = d;
								for (int y = ymin; y <= ymax; y++) {
									for (int x = xmin; x <= xmax; x++) {
										int dx = x-x0;
										int dy = y-y0;
										if ((dx*dx+dy*dy) > r2) {
											continue;
										}
										if (pixels[y*width+x] < dmin) {
											dmin=pixels[y*width+x];
										}
									}
								}
								floatPixels[k] = (float) dmin;
							}
						}
					};
				}		      
				startAndJoin(threads);
				stepProgressBar();
			}
		}
		break;
		
		case LOCAL_MAX:
		{
			final int ir = (int) Math.ceil(max_norm_rad);
			final double r2 = max_norm_rad * max_norm_rad;
			final float[] floatPixels =  (float[])ip.getPixels();
			final double [] pixels = new double[floatPixels.length];
			for (int i = 0; i < pixels.length; i++) {
				pixels[i] = floatPixels[i];
			}
			final Thread[] threads = newThreadArray(THREADS_MAX);
			final AtomicInteger ai = new AtomicInteger(0);
			for (int y00 = 0; (y00 < height); y00++) {
				final int y0 = y00;
				int ymin = Math.max(y0-ir, 0);
				int ymax = Math.min(y0+ir, height-1);
				ai.set(0);
				for (int ithread = 0; ithread < threads.length; ithread++) {
					threads[ithread] = new Thread() {
						public void run() {
							for (int x0 = ai.getAndIncrement(); x0 < width; x0 = ai.getAndIncrement()) {
								int k = y0*width + x0;
								int xmin = Math.max(x0-ir, 0);
								int xmax = Math.min(x0+ir, width-1);
								double d = pixels[k];
								double dmax = d;
								for (int y = ymin; y <= ymax; y++) {
									for (int x = xmin; x <= xmax; x++) {
										int dx = x-x0;
										int dy = y-y0;
										if ((dx*dx+dy*dy) > r2) {
											continue;
										}
										if (pixels[y*width+x] > dmax) {
											dmax=pixels[y*width+x];
										}
									}
								}
								floatPixels[k] = (float) dmax;
							}
						}
					};
				}		      
				startAndJoin(threads);
				stepProgressBar();
			}
		}
		break;
		
		case NORMALIZE_LMAX:
			{
				int ir = (int) Math.ceil(max_norm_rad);
				double r2 = max_norm_rad * max_norm_rad;
				float[] floatPixels =  (float[])ip.getPixels();
				double [] pixels = new double[floatPixels.length];
				for (int i = 0; i < pixels.length; i++) {
					pixels[i] = floatPixels[i];
				}
				for (int y0 = 0, k = 0; (y0 < height); y0++) {
					int ymin = Math.max(y0-ir, 0);
					int ymax = Math.min(y0+ir, height-1);
					for (int x0 = 0; (x0 < width); x0++, k++) {
						int xmin = Math.max(x0-ir, 0);
						int xmax = Math.min(x0+ir, width-1);
						double d = pixels[k];
						if (d <= 0.0) {
							continue; // only process positive values
 						}
						double dmax = d;
						for (int y = ymin; y <= ymax; y++) {
							for (int x = xmin; x <= xmax; x++) {
								int dx = x-x0;
								int dy = y-y0;
								if ((dx*dx+dy*dy) > r2) {
									continue;
								}
								if (pixels[y*width+x] > dmax) {
									dmax=pixels[y*width+x];
								}
							}
						}
						floatPixels[k] = (float) (d/dmax);
					}
					stepProgressBar();
				}
			}
			break;
		case GRADIENT_XY:
			{
				ImageProcessor h = ip.duplicate();
				ImageProcessor v = ip.duplicate();
				float[] floatPixels =  (float[])ip.getPixels();
				float[] floatPixelsH = (float[])h.getPixels();
				float[] floatPixelsV = (float[])v.getPixels();
		
				getHorizontalGradient(h, FLT_EPSILON);
				getVerticalGradient(v, FLT_EPSILON);
				for (int y = 0, k = 0; (y < height); y++) {
					for (int x = 0; (x < width); x++, k++) {
						floatPixels[k] = floatPixelsH[k] * dir_x + floatPixelsV[k] * dir_y;
					}
					stepProgressBar();
				}
			}
			break;
		case GRADIENT_X:
			{
				getHorizontalGradient(ip, FLT_EPSILON);
			}
			break;
		case GRADIENT_Y:
			{
				getVerticalGradient(ip, FLT_EPSILON);
			}
			break;
		case GRADIENT_MAGNITUDE:
			{
				ImageProcessor h = ip.duplicate();
				ImageProcessor v = ip.duplicate();
				float[] floatPixels = (float[])ip.getPixels();
				float[] floatPixelsH = (float[])h.getPixels();
				float[] floatPixelsV = (float[])v.getPixels();

				getHorizontalGradient(h, FLT_EPSILON);
				getVerticalGradient(v, FLT_EPSILON);
				for (int y = 0, k = 0; (y < height); y++) {
					for (int x = 0; (x < width); x++, k++) {
						floatPixels[k] =
							(float)Math.sqrt(floatPixelsH[k] * floatPixelsH[k]
							+ floatPixelsV[k] * floatPixelsV[k]);
					}
					stepProgressBar();
				}
			}
			break;
		case GRADIENT_DIRECTION:
			{
				ImageProcessor h = ip.duplicate();
				ImageProcessor v = ip.duplicate();
				float[] floatPixels = (float[])ip.getPixels();
				float[] floatPixelsH = (float[])h.getPixels();
				float[] floatPixelsV = (float[])v.getPixels();

				getHorizontalGradient(h, FLT_EPSILON);
				getVerticalGradient(v, FLT_EPSILON);
				for (int y = 0, k = 0; (y < height); y++) {
					for (int x = 0; (x < width); x++, k++) {
						floatPixels[k] =
							(float)Math.atan2(floatPixelsH[k], floatPixelsV[k]);
					}
					stepProgressBar();
				}
			}
			break;
		case LAPLACIAN:
			{
				ImageProcessor hh = ip.duplicate();
				ImageProcessor vv = ip.duplicate();
				float[] floatPixels = (float[])ip.getPixels();
				float[] floatPixelsHH = (float[])hh.getPixels();
				float[] floatPixelsVV = (float[])vv.getPixels();

				getHorizontalHessian(hh, FLT_EPSILON);
				getVerticalHessian(vv, FLT_EPSILON);
				for (int y = 0, k = 0; (y < height); y++) {
					for (int x = 0; (x < width); x++, k++) {
						floatPixels[k] =
							(float)(floatPixelsHH[k] + floatPixelsVV[k]);
					}
					stepProgressBar();
				}
			}
			break;
		case LARGEST_HESSIAN:
			{
				ImageProcessor hh = ip.duplicate();
				ImageProcessor vv = ip.duplicate();
				ImageProcessor hv = ip.duplicate();
				float[] floatPixels = (float[])ip.getPixels();
				float[] floatPixelsHH = (float[])hh.getPixels();
				float[] floatPixelsVV = (float[])vv.getPixels();
				float[] floatPixelsHV = (float[])hv.getPixels();

				getHorizontalHessian(hh, FLT_EPSILON);
				getVerticalHessian(vv, FLT_EPSILON);
				getCrossHessian(hv, FLT_EPSILON);
				for (int y = 0, k = 0; (y < height); y++) {
					for (int x = 0; (x < width); x++, k++) {
						floatPixels[k] =
							(float)(0.5 * (floatPixelsHH[k] + floatPixelsVV[k]
							+ Math.sqrt(4.0 * floatPixelsHV[k]
							* floatPixelsHV[k]
							+ (floatPixelsHH[k] - floatPixelsVV[k])
							* (floatPixelsHH[k] - floatPixelsVV[k]))));
					}
					stepProgressBar();
				}
			}
			break;
		case SMALLEST_HESSIAN:
			{
				ImageProcessor hh = ip.duplicate();
				ImageProcessor vv = ip.duplicate();
				ImageProcessor hv = ip.duplicate();
				float[] floatPixels = (float[])ip.getPixels();
				float[] floatPixelsHH = (float[])hh.getPixels();
				float[] floatPixelsVV = (float[])vv.getPixels();
				float[] floatPixelsHV = (float[])hv.getPixels();

				getHorizontalHessian(hh, FLT_EPSILON);
				getVerticalHessian(vv, FLT_EPSILON);
				getCrossHessian(hv, FLT_EPSILON);
				for (int y = 0, k = 0; (y < height); y++) {
					for (int x = 0; (x < width); x++, k++) {
						floatPixels[k] =
							(float)(0.5 * (floatPixelsHH[k] + floatPixelsVV[k]
							- Math.sqrt(4.0 * floatPixelsHV[k]
							* floatPixelsHV[k]
							+ (floatPixelsHH[k] - floatPixelsVV[k])
							* (floatPixelsHH[k] - floatPixelsVV[k]))));
					}
					stepProgressBar();
				}
			}
			break;
		case HESSIAN_ORIENTATION:
			{
				ImageProcessor hh = ip.duplicate();
				ImageProcessor vv = ip.duplicate();
				ImageProcessor hv = ip.duplicate();
				float[] floatPixels = (float[])ip.getPixels();
				float[] floatPixelsHH = (float[])hh.getPixels();
				float[] floatPixelsVV = (float[])vv.getPixels();
				float[] floatPixelsHV = (float[])hv.getPixels();

				getHorizontalHessian(hh, FLT_EPSILON);
				getVerticalHessian(vv, FLT_EPSILON);
				getCrossHessian(hv, FLT_EPSILON);
				for (int y = 0, k = 0; (y < height); y++) {
					for (int x = 0; (x < width); x++, k++) {
						if (floatPixelsHV[k] < 0.0) {
							floatPixels[k] =
								(float)(-0.5 * Math.acos((floatPixelsHH[k]
								- floatPixelsVV[k])
								/ Math.sqrt(4.0 * floatPixelsHV[k]
								* floatPixelsHV[k]
								+ (floatPixelsHH[k] - floatPixelsVV[k])
								* (floatPixelsHH[k] - floatPixelsVV[k]))));
						}
						else {
							floatPixels[k] =
								(float)(0.5 * Math.acos((floatPixelsHH[k]
								- floatPixelsVV[k])
								/ Math.sqrt(4.0 * floatPixelsHV[k]
								* floatPixelsHV[k]
								+ (floatPixelsHH[k] - floatPixelsVV[k])
								* (floatPixelsHH[k] - floatPixelsVV[k]))));
						}
					}
					stepProgressBar();
				}
			}
			break;
		default:
			throw new IllegalArgumentException("Invalid operation");
	}
	ip.resetMinAndMax();
	imp.updateAndDraw();
} /* end doIt */

/*------------------------------------------------------------------*/
private void getColumn (
	ImageProcessor ip,
	int x,
	double[] column
) {
	int width = ip.getWidth();

	if (ip.getHeight() != column.length) {
		throw new IndexOutOfBoundsException("Incoherent array sizes");
	}
	if (ip.getPixels() instanceof float[]) {
		float[] floatPixels = (float[])ip.getPixels();
		for (int i = 0; (i < column.length); i++) {
			column[i] = (double)floatPixels[x];
			x += width;
		}
	}
	else {
		throw new IllegalArgumentException("Float image required");
	}
} /* end getColumn */

/*------------------------------------------------------------------*/
private void getGradient (
	double[] c
) {
	double h[] = {0.0, -1.0 / 2.0};
	double s[] = new double[c.length];

	antiSymmetricFirMirrorOnBounds(h, c, s);
	System.arraycopy(s, 0, c, 0, s.length);
} /* end getGradient */

/*------------------------------------------------------------------*/
private void getHessian (
	double[] c
) {
	double h[] = {-2.0, 1.0};
	double s[] = new double[c.length];

	symmetricFirMirrorOnBounds(h, c, s);
	System.arraycopy(s, 0, c, 0, s.length);
} /* end getHessian */

/*------------------------------------------------------------------*/
private double getInitialAntiCausalCoefficientMirrorOnBounds (
	double[] c,
	double z,
	double tolerance
) {
	return((z * c[c.length - 2] + c[c.length - 1]) * z / (z * z - 1.0));
} /* end getInitialAntiCausalCoefficientMirrorOnBounds */

/*------------------------------------------------------------------*/
double getInitialCausalCoefficientMirrorOnBounds (
	double[] c,
	double z,
	double tolerance
) {
	double z1 = z, zn = Math.pow(z, c.length - 1);
	double sum = c[0] + zn * c[c.length - 1];
	int horizon = c.length;

	if (0.0 < tolerance) {
		horizon = 2 + (int)(Math.log(tolerance) / Math.log(Math.abs(z)));
		horizon = (horizon < c.length) ? (horizon) : (c.length);
	}
	zn = zn * zn;
	for (int n = 1; (n < (horizon - 1)); n++) {
		zn = zn / z;
		sum = sum + (z1 + zn) * c[n];
		z1 = z1 * z;
	}
	return(sum / (1.0 - Math.pow(z, 2 * c.length - 2)));
} /* end getInitialCausalCoefficientMirrorOnBounds */

/*------------------------------------------------------------------*/
private void getRow (
	ImageProcessor ip,
	int y,
	double[] row
) {
	int rowLength = ip.getWidth();

	if (rowLength != row.length) {
		throw new IndexOutOfBoundsException("Incoherent array sizes");
	}
	y *= rowLength;
	if (ip.getPixels() instanceof float[]) {
		float[] floatPixels = (float[])ip.getPixels();
		for (int i = 0; (i < rowLength); i++) {
			row[i] = (double)floatPixels[y++];
		}
	}
	else {
		throw new IllegalArgumentException("Float image required");
	}
} /* end getRow */

/*------------------------------------------------------------------*/
private void getSplineInterpolationCoefficients (
	double[] c,
	double tolerance
) {
	double z[] = {Math.sqrt(3.0) - 2.0};
	double lambda = 1.0;

	if (c.length == 1) {
		return;
	}
	for (int k = 0; (k < z.length); k++) {
		lambda = lambda * (1.0 - z[k]) * (1.0 - 1.0 / z[k]);
	}
	for (int n = 0; (n < c.length); n++) {
		c[n] = c[n] * lambda;
	}
	for (int k = 0; (k < z.length); k++) {
		c[0] = getInitialCausalCoefficientMirrorOnBounds(c, z[k], tolerance);
		for (int n = 1; (n < c.length); n++) {
			c[n] = c[n] + z[k] * c[n - 1];
		}
		c[c.length - 1] = getInitialAntiCausalCoefficientMirrorOnBounds(c, z[k],
			tolerance);
		for (int n = c.length - 2; (0 <= n); n--) {
			c[n] = z[k] * (c[n+1] - c[n]);
		}
	}
} /* end getSplineInterpolationCoefficients */

/*------------------------------------------------------------------*/
private void putColumn (
	ImageProcessor ip,
	int x,
	double[] column
) {
	int width = ip.getWidth();

	if (ip.getHeight() != column.length) {
		throw new IndexOutOfBoundsException("Incoherent array sizes");
	}
	if (ip.getPixels() instanceof float[]) {
		float[] floatPixels = (float[])ip.getPixels();
		for (int i = 0; (i < column.length); i++) {
			floatPixels[x] = (float)column[i];
			x += width;
		}
	}
	else {
		throw new IllegalArgumentException("Float image required");
	}
} /* end putColumn */

/*------------------------------------------------------------------*/
private void putRow (
	ImageProcessor ip,
	int y,
	double[] row
) {
	int rowLength = ip.getWidth();

	if (rowLength != row.length) {
		throw new IndexOutOfBoundsException("Incoherent array sizes");
	}
	y *= rowLength;
	if (ip.getPixels() instanceof float[]) {
		float[] floatPixels = (float[])ip.getPixels();
		for (int i = 0; (i < rowLength); i++) {
			floatPixels[y++] = (float)row[i];
		}
	}
	else {
		throw new IllegalArgumentException("Float image required");
	}
} /* end putRow */

/*------------------------------------------------------------------*/
private void resetProgressBar (
) {
	completed = 0;
	lastTime = System.currentTimeMillis();
	progressBar.show(2.0);
} /* end resetProgressBar */

/*------------------------------------------------------------------*/
private void setupProgressBar (
) {
	int height = imp.getHeight();
	int width = imp.getWidth();

	completed = 0;
	lastTime = System.currentTimeMillis();
	switch (operation) {
		case LOCAL_ISMAX:
		case LOCAL_MIN:
		case LOCAL_MAX:
		case LOCAL_DIFF:
		case NORMALIZE_LMAX:
			processDuration = stackSize * (width + 1 * height);
			break;
		case GRADIENT_XY:
			processDuration = stackSize * (width + 1 * height);
			break;
		case GRADIENT_X:
			processDuration = stackSize * (width + 1 * height);
			break;
		case GRADIENT_Y:
			processDuration = stackSize * (width + 1 * height);
			break;
		case GRADIENT_MAGNITUDE:
			processDuration = stackSize * (width + 2 * height);
			break;
		case GRADIENT_DIRECTION:
			processDuration = stackSize * (width + 2 * height);
			break;
		case LAPLACIAN:
			processDuration = stackSize * (width + 2 * height);
			break;
		case LARGEST_HESSIAN:
			processDuration = stackSize * (2 * width + 3 * height);
			break;
		case SMALLEST_HESSIAN:
			processDuration = stackSize * (2 * width + 3 * height);
			break;
		case HESSIAN_ORIENTATION:
			processDuration = stackSize * (2 * width + 3 * height);
			break;
		default:
			throw new IllegalArgumentException("Invalid operation");
	}
} /* end setupProgressBar */

/*------------------------------------------------------------------*/
private void stepProgressBar (
) {
	long timeStamp = System.currentTimeMillis();

	completed = completed + 1;
	if (50L < (timeStamp - lastTime)) {
		lastTime = timeStamp;
		progressBar.show((double)completed / (double)processDuration);
	}
} /* end stepProgressBar */

/*------------------------------------------------------------------*/
private void symmetricFirMirrorOnBounds (
	double[] h,
	double[] c,
	double[] s
) {
	if (h.length != 2) {
		throw new IndexOutOfBoundsException(
			"The half-length filter size should be 2");
	}
	if (c.length != s.length) {
		throw new IndexOutOfBoundsException("Incompatible size");
	}
	if (2 <= c.length) {
		s[0] = h[0] * c[0] + 2.0 * h[1] * c[1];
		for (int i = 1; (i < (s.length - 1)); i++) {
			s[i] = h[0] * c[i] + h[1] * (c[i - 1] + c[i + 1]);
		}
		s[s.length - 1] = h[0] * c[c.length - 1] + 2.0 * h[1] * c[c.length - 2];
	}
	else {
		switch (c.length) {
			case 1:
				s[0] = (h[0] + 2.0 * h[1]) * c[0];
				break;
			default:
				throw new NegativeArraySizeException("Invalid length of data");
		}
	}
} /* end symmetricFirMirrorOnBounds */

/* Create a Thread[] array as large as the number of processors available.
 * From Stephan Preibisch's Multithreading.java class. See:
 * http://repo.or.cz/w/trakem2.git?a=blob;f=mpi/fruitfly/general/MultiThreading.java;hb=HEAD
 */
public static Thread[] newThreadArray(int maxCPUs) { // USED in lwir
	int n_cpus = Runtime.getRuntime().availableProcessors();
	if (n_cpus>maxCPUs)n_cpus=maxCPUs;
	return new Thread[n_cpus];
}
/* Start all given threads and wait on each of them until all are done.
 * From Stephan Preibisch's Multithreading.java class. See:
 * http://repo.or.cz/w/trakem2.git?a=blob;f=mpi/fruitfly/general/MultiThreading.java;hb=HEAD
 */
public static void startAndJoin(Thread[] threads) // USED in lwir
{
	for (int ithread = 0; ithread < threads.length; ++ithread)
	{
		threads[ithread].setPriority(Thread.NORM_PRIORITY);
		threads[ithread].start();
	}

	try
	{
		for (int ithread = 0; ithread < threads.length; ++ithread)
			threads[ithread].join();
	} catch (InterruptedException ie)
	{
		throw new RuntimeException(ie);
	}
}


} /* end class Differentials_ */
