public class BeadFrame extends Observable {

	/** The bead id. */
	Integer id;

	/** The boundaries. */
	Rectangle2D boundaries;

	/**  The ImagePlus stack containing the bead. */
	ImagePlus ip;

	/**  The BeadImage object from where the BeadFrame has been extracted. */
	BeadImage source;

	/** The stack size. */
	int stackSize;

	/** The calibration object. */
	Calibration calibration;

	/**
	 * Boolean indicating is the BeadFrame is valid. The BeadFrame is usually
	 * marked as invalid when filtered out because of bad fitting
	 */
	boolean isValid = true;

	/** The invalidity reason. */
	String invalidityReason = null;

	/**  The center of the bead in integer. */
	protected int[] center = null;

	/**  The centroid (center of the bead computed by the Counter3D algorithm). */
	protected float[] centroid = null;

	/** Equals sqrt(2*ln(2)), used to calculate FWHM of Gaussians *. */
	public final static double SQRT2LN2 = Math.sqrt(2 * Math.log(2));

	/** Reference to the x dimension *. */
	public static final int X = 0;

	/** Reference to the y dimension *. */
	public static final int Y = 1;

	/** Reference to the z dimension *. */
	public static final int Z = 2;

        public static final int A = 1;
        
        public static final int B = 0;
        
	/**
	 * Stores the x profile, [0=x as a physical distance ,1=raw intensity,
	 * 2=fitted data][pixel nb, from 0 to width-1].
	 */
	double[][] xProfile = null;

	/** Stores the fitting parameters for the x profile *. */
	double[] xParams = null;

	/** Stores the fitting goodness for the x profile *. */
	double xR2 = Double.NaN;

	/** Stores the fitting parameters for the x profile as a string *. */
	String xParamString = "Fitted on y = a + (b-a)*exp(-(x-c)^2/(2*d^2)";

	/**
	 * Stores the y profile, [0=y as a physical distance ,1=raw intensity,
	 * 2=fitted data][pixel nb, from 0 to height-1].
	 */
	double[][] yProfile = null;

	/** Stores the fitting parameters for the y profile *. */
	double[] yParams = null;

	/** Stores the fitting goodness for the y profile *. */
	double yR2 = Double.NaN;

	/** Stores the fitting parameters for the y profile as a string *. */
	String yParamString = "Fitted on y = a + b*exp(-(x-c)^2/(2*d^2)";

	/**
	 * Stores the z profile, [0=z as a physical distance ,1=raw intensity,
	 * 2=fitted data][pixel nb, from 0 to nSlices-1].
	 */
	double[][] zProfile = null;

	/** Stores the fitting parameters for the z profile *. */
	double[] zParams = null;

	/** Stores the fitting goodness for the z profile *. */
	double zR2 = Double.NaN;

	/** The R2. */
	double[] R2 = new double[3];

	/**  Shift in the X axis with the corresponding bead (alter ego). */
	double deltaX = Double.NaN;

	/**  Shift in the Y axis with the corresponding bead (alter ego). */
	double deltaY = Double.NaN;

	/**  Shift in the Z axis with the corresponding bead. */
	double deltaZ = Double.NaN;

	/**  The side view image (computed after PSF Fitting). */
	protected ImagePlus sideViewImage;

	/** The counter. */
	Counter3D counter;

	/** The max intensity. */
	double maxIntensity;

	/** The is ignored. */
	boolean isIgnored = false;

	/** The object3d. */
	protected Object3D object3d;

	/**  The alter ego (corresponding bead in an other channel). */
	BeadFrame alterEgo;

	public static final String MSG_PSF_FOUND = "psf found";
	
	
	/**
	 * Stores the calculated resolutions (FWHM) in all the dimensions of the
	 * image.
	 */
	double[] resol = {0, 0, 0};

	/** The asymetry. */
	double asymetry = Double.NaN;

	/** Stores the fitting parameters for the z profile as a string *. */
	String zParamString = "Fitted on y = a + (b-a)*exp(-(x-c)^2/(2*d^2)";

	/**
	 * The main method.
	 * 
	 * @param args
	 *           the arguments
	 */
	public static void main(String[] args) {

		BeadImageManager manager = new BeadImageManager();

		// manager.add("/home/cyril/test_img/colocalisation/gfp_01_220beads_small.tif");
		// manager.add("/home/cyril/test_img/colocalisation/mc_01_220beads_small.tif");

		manager.add("/home/cyril/test_img/colocalisation/gfp1.tif");
		manager.add("/home/cyril/test_img/colocalisation/mc1.tif");

		manager.setAnalysisType(BeadImageManager.DUAL_CHANNEL);

		manager.autoFrameSize();
		manager.autoFocus(0);
		manager.autoFocus(1);

		manager.processProfiles();

		new ImagePlus("", manager.getBeadImage(0).getMontage().resize(600))
				.show();

	}

	/**
	 * Instantiates a new bead frame.
	 * 
	 * @param id
	 *           the id
	 * @param r
	 *           the r
	 */
	public BeadFrame(int id, Rectangle r) {
		this(r);
		this.id = id;
	}

	/**
	 * Instantiates a new bead frame.
	 * 
	 * @param rectangle
	 *           the rectangle
	 */
	public BeadFrame(Rectangle rectangle) {
		this.boundaries = rectangle;
	}

	/**
	 * Gets the X position of the BeadFrame (not the bead itself).
	 * 
	 * @return the x
	 */
	public int getFrameX() {
		return MathUtils.round(boundaries.getX());
	}

	/**
	 * Gets the Y position of the BeadFrame (not the bead itself) .
	 * 
	 * @return the y
	 */
	public int getFrameY() {
		return MathUtils.round(boundaries.getY());
	}

	/**
	 * Gets the id.
	 * 
	 * @return the id
	 */
	public Integer getId() {
		return id;
	}

	/**
	 * Sets the id.
	 * 
	 * @param id
	 *           the new id
	 */
	public void setId(int id) {
		this.id = id;
	}

	/**
	 * Gets the boundaries of the BeadFrame in a Rectangle2D form.
	 *
	 * @return the boundaries
	 */
	public Rectangle2D getBoundaries() {
		return boundaries;
	}

	/**
	 * Gets the boundaries as Rectangle.
	 * 
	 * @return the boundaries as rectangle
	 */
	public Rectangle getBoundariesAsRectangle() {
		return (Rectangle) boundaries;
	}

	/**
	 * Sets the boundaries.
	 * 
	 * @param boundaries
	 *           the new boundaries
	 */
	public void setBoundaries(Rectangle boundaries) {
		this.boundaries = boundaries;
	}

	/**
	 * Gets the substack.
	 * 
	 * @return the substack
	 */
	public ImagePlus getSubstack() {

		if (ip == null) {

			int width = MathUtils.round(boundaries.getWidth());
			int height = MathUtils.round(boundaries.getHeight());
			int x = getFrameX();
			int y = getFrameY();

			// boundaries.setRect(x, y, width, height);

			ImageStack stack = new ImageStack(width, height);

			for (int i = 0; i != source.getStackSize(); i++) {

				ImageProcessor sourcePlane = source.getPlane(i);
				ImageProcessor targetPlane = ImageProcessorUtils.copyRoi(
						sourcePlane, boundaries.getBounds());

				stack.addSlice(targetPlane);
			}
			setSubstack(new ImagePlus("", stack));
			// if(getId() == 200) ip.show();;
		}

		return ip;
	}

	/**
	 * Sets the substack.
	 * 
	 * @param substack
	 *           the new substack
	 */
	public void setSubstack(ImagePlus substack) {
		this.ip = substack;
		stackSize = ip.getStackSize();
		ip.setCalibration(getCalibration());
	}

	/**
	 * Gets the distance.
	 *
	 * @param p1 the p1
	 * @param p2 the p2
	 * @return the distance
	 */
	private double getDistance(float[] p1, float[] p2) {
		double x1 = p1[0]; // getCentroidXInImage();
		double x2 = p2[0]; // bead.getCentroidXInImage();
		double y1 = p1[1]; // getCentroidYInImage();
		double y2 = p2[1]; // bead.getCentroidYInImage();

		if (p1.length > 2 && p2.length > 2) {
			double z1 = p1[2];
			double z2 = p2[2];
			return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2)
					+ Math.pow(z1 - z2, 2));
		}

		else {

			return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
		}
	}

	/**
	 * Gets the distance with an other bead.
	 *
	 * @param bead           the bead
	 * @return the distance
	 */
	public double getDistance(BeadFrame bead) {

		if (bead == null)
			return Double.NaN;

		double x1 = getFovX(); // getCentroidXInImage();
		double x2 = bead.getFovX();// bead.getCentroidXInImage();
		double y1 = getFovY();// getCentroidYInImage();
		double y2 = bead.getFovY(); // bead.getCentroidYInImage();

		return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));

	}

	/**
	 * Gets the distance with alter ego.
	 *
	 * @return the distance with alter ego
	 */
	public double getDistanceWithAlterEgo() {
		if (alterEgo != null) {
			return getDistance(alterEgo);
		}

		else {
			return Double.POSITIVE_INFINITY;
		}
	}

	/**
	 * Gets the closest bead from a BeadImageList object.
	 * 
	 * @param list
	 *           the list
	 * @return the closest bead
	 */
	public BeadFrame getClosestBead(BeadFrameList list) {
		double minDistance = -1;
		double distance;

		BeadFrame found = null;

		for (BeadFrame bead : list) {
			distance = getDistance(bead);

			if (minDistance == -1 || distance < minDistance) {
				minDistance = distance;
				found = bead;
			}
		}

		if (found == null)
			return null;

		System.out.printf("For bead (%d) : %.3f , %.3f\n", getId(), getFovX(),
				getFovY());
		System.out.printf(
				"The closest bead (%d) has for coordinates : %.3f , %.3f\n",
				found.getId(), found.getFovX(), found.getFovY());

		return found;

	}

	/**
	 * Sets the source.
	 * 
	 * @param beadImage
	 *           the new source
	 */
	public void setSource(BeadImage beadImage) {

		source = beadImage;
		stackSize = source.getStackSize();
		calibration = new Calibration();
		calibration.pixelWidth = source.getCalibration().pixelWidth;
		calibration.pixelHeight = source.getCalibration().pixelHeight;
		calibration.pixelDepth = source.getCalibration().pixelDepth;

	}

	/**
	 * Gets the source.
	 * 
	 * @return the source
	 */
	public BeadImage getSource() {
		return source;
	}

	/**
	 * Gets the calibration.
	 * 
	 * @return the calibration
	 */
	public Calibration getCalibration() {
		// return calibration;
		return getSource().getCalibration();
	}

	/**
	 * Draw.
	 * 
	 * @param ip
	 *           the ip
	 * @param radius
	 *           the radius
	 */
	public void draw(ImageProcessor ip, int radius) {

		Color c = (isValid() ? Color.white : Color.red);
		draw(ip, radius, c);

	}

	/**
	 * Gets the theta.
	 *
	 * @return the theta
	 */
	public double getTheta() {
		return 0.0;
	}

	/**
	 * Gets the theta in degrees.
	 *
	 * @return the theta in degrees
	 */
	public double getThetaInDegrees() {
		return getTheta() * 180 / Math.PI;
	}

	/**
	 * Draw.
	 *
	 * @param ip the ip
	 * @param radius the radius
	 * @param c the c
	 */
	public void draw(ImageProcessor ip, int radius, Color c) {
		int x = getWeightedXInImage(); // (getCenterX());
		int y = getWeightedYInImage(); // (getCenterY());

		ip.setColor(c.darker());

		// ip.drawOval(x-(getWidth()/2), y-(getWidth()/2),
		// getWidth(),getWidth());
		ip.drawRect(getFrameX(), getFrameY(), getWidth(), getHeight());
		ip.drawRect(getFrameX() - 1, getFrameY() - 1, getWidth() + 2,
				getHeight() + 2);
		// ip.setColor(Color.red);
		// ip.drawOval(x-1, y-1, 3, 3);
		ip.setColor(Color.white);
		ip.drawString("" + id, x, y + 16);

	}

	/**
	 * Gets the fov x.
	 * 
	 * @return the fov x
	 */
	public double getFovX() {
		return // (getX()+getWeightedXInImage() -
					// getSource().getImageWidth()/2)*getCalibration().pixelWidth;
		(getCentroidXInImage() * getCalibration().pixelWidth)
				- (getSource().getFieldOfViewWidth() / 2);
	}

	/**
	 * Gets the fov y.
	 * 
	 * @return the fov y
	 */
	public double getFovY() {
		return // (getY()+getWeightedYInImage() -
					// getSource().getImageHeight()/2)*getCalibration().pixelHeight;
		-1.0 * ((getCentroidYInImage() * getCalibration().pixelHeight)
				- (getSource().getFieldOfViewHeight() / 2));// -
	}

	/**
	 * Gets the center x.
	 * 
	 * @return the center x
	 */
	public int getFrameCenterX() {
		return MathUtils.round(boundaries.getCenterX());
	}

	/**
	 * Gets the center y.
	 * 
	 * @return the center y
	 */
	public int getFrameCenterY() {
		return MathUtils.round(boundaries.getCenterY());
	}

	/**
	 * Gets the weighted x in image.
	 * 
	 * @return the weighted x in image
	 */
	public int getWeightedXInImage() {

		if (centroid == null) {
			return getFrameCenterX();
		}

		return getFrameX() + getCentroidXAsInt();
	}

	/**
	 * Gets the weighted y in image.
	 * 
	 * @return the weighted y in image
	 */
	public int getWeightedYInImage() {
		if (centroid == null) {
			return getFrameCenterY();
		}
		return getFrameY() + getCentroidYAsInt();
	}

	/**
	 * Gets the centroid.
	 *
	 * @return the centroid
	 */
	private float[] getCentroid() {
		if (centroid == null) {
			try {
				findCenter();
			} catch (Exception e) {
				e.printStackTrace();
				System.err.println("Couldn't find the center for the bead "
						+ getId());
				centroid = new float[]{-1, -1, -1};
			}

		}
		return centroid;

	}

	/**
	 * Gets the centroid x.
	 * 
	 * @return the centroid x
	 */
	public double getCentroidX() {
		// findCenter();
		return getCentroid()[0];
	}

	/**
	 * Gets the centroid y.
	 * 
	 * @return the centroid y
	 */
	public double getCentroidY() {
		// findCenter();
		return getCentroid()[1];
	}

	/**
	 * Gets the centroid z.
	 * 
	 * @return the centroid z
	 */
	public double getCentroidZ() {
		// findCenter();
		return getCentroid()[2];
	}

	/**
	 * Gets the centroid x in image.
	 * 
	 * @return the centroid x in image
	 */
	public double getCentroidXInImage() {
		return getFrameX() + getCentroidX();
	}

	/**
	 * Gets the centroid y in image.
	 * 
	 * @return the centroid y in image
	 */
	public double getCentroidYInImage() {
		return getFrameY() + getCentroidY();
	}

	/**
	 * Gets the centroid x as int.
	 *
	 * @return the centroid x as int
	 */
	public int getCentroidXAsInt() {
		return center[0];
	}

	/**
	 * Gets the centroid y as int.
	 *
	 * @return the centroid y as int
	 */
	public int getCentroidYAsInt() {
		return center[1];
	}

	/**
	 * Gets the centroid z as int.
	 *
	 * @return the centroid z as int
	 */
	public int getCentroidZAsInt() {
		return center[2];
	}

	/**
	 * ******************************************************************.
	 * 
	 * @param coords
	 *           the coords
	 * @return the intensity
	 */
	/*********************** FITTING AREA ********************************/
	/*********************************************************************/

	public int getIntensity(float[] coords) {
		return getIntensity(MathUtils.round(coords[0]),
				MathUtils.round(coords[1]), MathUtils.round(coords[2]));
	}

	/**
	 * Center boundaries around centroid.
	 */
	public void centerBoundariesAroundCentroid() {

		int oldCenterX = getFrameCenterX();
		int oldCenterY = getFrameCenterY();
		int newCenterX = getWeightedXInImage();
		int newCenterY = getWeightedYInImage();

		if (oldCenterX == newCenterX && oldCenterY == newCenterY)
			return;

		float xShift = newCenterX - oldCenterX;
		float yShift = newCenterY - oldCenterY;

		centroid[0] = centroid[0] + xShift;
		centroid[1] = centroid[1] + yShift;

		center[0] = MathUtils.round(centroid[0]);
		center[1] = MathUtils.round(centroid[1]);

		int width = MathUtils.round(boundaries.getWidth());
		int height = MathUtils.round(boundaries.getHeight());

		boundaries = new Rectangle(newCenterX - (width / 2), newCenterY
				- (height / 2), width, height);
	}

	/**
	 * Gets the intensity.
	 * 
	 * @param x
	 *           the x
	 * @param y
	 *           the y
	 * @param z
	 *           the z
	 * @return the intensity
	 */
	public int getIntensity(int x, int y, int z) {
		getSubstack().setSlice(z);
		return getSubstack().getProcessor().getPixel(x, y);
	}

	/**
	 * Convert int array to float.
	 * 
	 * @param intArray
	 *           the int array
	 * @return the float[]
	 */
	private float[] convertIntArrayToFloat(int[] intArray) {
		float[] floatArray = new float[intArray.length];
		for (int i = 0; i != floatArray.length; i++) {
			floatArray[i] = intArray[i];
		}
		return floatArray;
	}

	/**
	 * Find center.
	 */
	public void findCenter() {

		// if the centroid has already been calculated, the routine is aborted
		if (centroid != null)
			return;

		// a counter 3D object is created.
		counter = new Counter3D(getSubstack(), getSource().getThresholdValue(),
				1, 10000);
		counter.setSizeFiltering(true);

		float[][] centers = new float[0][0];

		try {
			counter.getObjects();
			// the centers returned by the 3D segmentation are harvested
			centers = counter.getCentreOfMassList();
		} catch (ArrayIndexOutOfBoundsException e) {
			
		}

		// If no center is found, then the MetroloJ algorithm is used
		if (centers.length == 0) {
			System.out
					.println("Couldn't find the center using 3D Object Counter for bead "
							+ getId());

			findCenterUsingBrightestPixel();
		}

		// if one or several centers are found
		else {
			// the closest centroid to the center is filtered out
			centroid = filterCentroids(centers);

			// in case the image wouldn't be a stack, the returned centroid would
			// be
			// a 2 value arrays so we transformed it into a 3 values array
			if (centroid.length == 2)
				centroid = new float[]{centroid[0], centroid[1], 0};

			// converting the centroid to int array for (1D fitting)
			center = new int[centroid.length];

			for (int i = 0; i != centroid.length; i++) {
				// System.out.printf("Rounding centroid from %.3f to %d\n",centroid[i],MathUtils.round(centroid[i]));
				center[i] = MathUtils.round(centroid[i]);

			}
		}

		// System.out.println(String.format("Difference from the center : %d x %d pixels",getBoundariesAsRectangle().width/2-center[0],getBoundariesAsRectangle().height/2-center[1]));

		// the boundaries of the substack are recalculated but not updated (the
		// substack will still be the same)
		// centerBoundariesAroundCentroid();

		// getCenterAsIntArray();

		if (ip.getStack().getSize() == 1) {
			center[2] = 0;
			centroid[2] = 0;
		}

		counter = null;

		// centerBoundariesAroundCentroid();

	}

	/**
	 * Find center using brightest pixel.
	 */
	public void findCenterUsingBrightestPixel() {
		// finds the brightest pixels of all stacks
		center = new FindMax().getAllCoordinates(getSubstack());

		// in a case of a flat image, the findMax object returns a 2-values array
		// that must be converted to a 3-values array
		if (center.length == 2)
			center = new int[]{center[0], center[1], 0};

		// since we cannot have a centroid in a subpixel resolution, we convert
		// the int array into a float array
		centroid = convertIntArrayToFloat(center);

		maxIntensity = getSubstack().getStack().getProcessor(center[2] + 1)
				.getPixel(center[0], center[1]);
	}

	/**
	 * Filter centroids.
	 * 
	 * @param centroids
	 *           list of centroids found by the 3D Counter Object
	 * @return the float[] a 3 items array reprensenting x,y, and z coordinates of the center
	 */
	private float[] filterCentroids(float[][] centroids) {

		
		int indexMax = 0;
		int intensityMax = 0;
		double closest = Double.POSITIVE_INFINITY;
		int indexClosest = 0;

		
		float[] center = new float[3];
		
		// getting the boundaries of the center
		if (boundaries == null) {
			boundaries = new Rectangle(0, 0, getSubstack().getWidth(),
					getSubstack().getHeight());
		}
		center[0] = getWidth() / 2;
		center[1] = getHeight() / 2;
		center[2] = source.getFocusPlane()
				* (float) ip.getCalibration().pixelDepth;

		if (centroids.length == 1) {
			indexMax = 0;
		} else {
			float[] point;
			int intensity;
			double distanceToCenter;
			for (int i = 0; i != centroids.length; i++) {
				point = centroids[i];
				intensity = getIntensity(point);
				distanceToCenter = getDistance(center, point);
				if (intensity > intensityMax) {
					indexMax = i;
					intensityMax = intensity;
				}

				if (distanceToCenter < closest) {
					closest = distanceToCenter;
					indexClosest = i;
				}

			}

			object3d = counter.getObject(indexMax);
			maxIntensity = object3d.max;

		}

		return centroids[indexClosest];

	}

	/**
	 * Gets the maximum intensity.
	 *
	 * @return the maximum intensity
	 */
	public double getMaximumIntensity() {
		return maxIntensity;
	}

	/**
	 * Find psf.
	 */
	public void findPSF() {
		if (isValid == false) {
			resol = new double[]{0.0, 0.0, 0.0};
			return;
		}
		try {

			findCenter();
			getSubstack().setSlice(getCentroidZAsInt());

			getXprofileAndFit();
			getYprofileAndFit();
			getZprofileAndFit();

			// System.out.println(getId() + " is finished.");
			getSideViewImage();
			ip = null;

			// System.gc();

			setChanged();
			notifyObservers(new Message(this, MSG_PSF_FOUND));

		} catch (OutOfMemoryError e) {
			System.out.println(new Message(this, "error",
					"There is not enough memory to process the beads !"));
		}

	}

	/**
	 * Gets the minimum bead size.
	 * 
	 * @return the minimum bead size
	 */
	public int getMinimumBeadSize() {

		double volume = getSource().getMicroscope().getXYTheoreticalResolution() / 2;

		volume = 4 / 3 * Math.PI * Math.pow(volume, 3);

		return MathUtils.round(volume
				/ Math.pow(getSource().getCalibration().pixelWidth, 3));
	}

	/**
	 * Gets the find psf job.
	 * 
	 * @return the find psf job
	 */
	public Runnable getFindPSFJob() {
		return new Runnable() {
			@Override
			public void run() {
				try {
					findPSF();
				} catch (Exception e) {
					System.err.println("Error when analysing bead " + getId());
					e.printStackTrace();
				}
			}

		};
	}

	/**
	 * Retrieves data and fills xProfile the x profile through the centre of the
	 * bead retrieved from the current ImageProcessor.
	 * 
	 * @return a double[][] [0=x as a physical distance ,1=raw intensity,
	 *         2=fitted data][pixel nb, from 0 to width-1]
	 */
	protected void getXprofileAndFit() {
		xProfile = new double[3][ip.getWidth()];
		xProfile[1] = ip.getProcessor().getLine(0, center[1], ip.getWidth() - 1,
				center[1]);
		fitProfile(xProfile, xParams, X);
	}

	/**
	 * Retrieves data and fills yProfile the y profile through the centre of the
	 * bead retrieved from the current ImageProcessor.
	 * 
	 * @return a double[][] [0=y as a physical distance ,1=raw intensity,
	 *         2=fitted data][pixel nb, from 0 to width-1]
	 */
	protected void getYprofileAndFit() {
		yProfile = new double[3][ip.getHeight()];
		yProfile[1] = ip.getProcessor().getLine(center[0], 0, center[0],
				ip.getHeight() - 1);
		fitProfile(yProfile, yParams, Y);
	}

	/**
	 * Retrieves data and fills zProfile the z profile through the centre of the
	 * bead retrieved from the current ImageProcessor.
	 * 
	 * @return a double[][] [0=z as a physical distance ,1=raw intensity,
	 *         2=fitted data][pixel nb, from 0 to width-1]
	 */

	protected void getZprofileAndFit() {
            
                if(isFlat()) {
                    zProfile = new double[3][ip.getNSlices()];
                    R2[Z] = 1;
                    return;
                };
            
		ip.setCalibration(new Calibration());
		ip.setRoi(new Line(0, center[1], ip.getWidth() - 1, center[1]));

		ImagePlus crossX = new Slicer().reslice(ip);

		ip.killRoi();
		ip.setCalibration(getCalibration());

		zProfile = new double[3][ip.getNSlices()];
		zProfile[1] = crossX.getProcessor().getLine(center[0], 0, center[0],
				crossX.getHeight() - 1);

		fitProfile(zProfile, zParams, Z);

	}

	/**
	 * Fit profile.
	 * 
	 * @param profile
	 *           the profile
	 * @param params
	 *           the params
	 * @param dimension
	 *           the dimension
	 */
	private void fitProfile(double[][] profile, double[] params, int dimension) {
		double max = profile[1][0];
		double pixelSize = 1;
		int resolIndex = 0;
		Calibration cal = getCalibration();

		switch (dimension) {
			case X :
				pixelSize = cal.pixelWidth;
				break;
			case Y :
				pixelSize = cal.pixelHeight;
				resolIndex = 1;
				break;
			case Z :
				pixelSize = cal.pixelDepth;
				resolIndex = 2;
				break;
		}

		params = new double[4];
		params[0] = max;
		params[1] = max;
		params[2] = 0;
		params[3] = 2 * pixelSize;

		for (int i = 0; i < profile[0].length; i++) {
			profile[0][i] = i * pixelSize;
			double currVal = profile[1][i];
			params[0] = Math.min(params[0], currVal);
			if (currVal > max) {
				params[1] = currVal;
				params[2] = profile[0][i];
				max = currVal;
			}
		}

		zParams = params;

		CurveFitter cv = new CurveFitter(profile[0], profile[1]);
		// cv.setOffsetMultiplySlopeParams(0, 1, 2);
		cv.setInitialParameters(params);
		cv.getMinimizer().setMaximumThreads(1);

		// cv.doCustomFit("y = a + b*exp(-(x-c)*(x-c)/(2*d*d))",params,false);
		cv.doFit(CurveFitter.GAUSSIAN);

		params = cv.getParams();

		for (int i = 0; i < profile[0].length; i++)
			profile[2][i] = CurveFitter.f(CurveFitter.GAUSSIAN, params,
					profile[0][i]);

		String paramString = cv.getResultString();

		params[1] += params[0];

		paramString = paramString.substring(paramString.lastIndexOf("ms") + 2);
		R2[dimension] = cv.getFitGoodness();

		switch (dimension) {
			case X :
				xParamString += paramString;
				xR2 = cv.getFitGoodness();
				xParams = params;
				break;
			case Y :
				yParamString += paramString;
				yR2 = cv.getFitGoodness();
				yParams = params;
				break;
			case Z :
				zParamString += paramString;
				zR2 = cv.getFitGoodness();
				zParams = params;
				break;
		}

		resol[resolIndex] = 2 * SQRT2LN2 * params[3];
	}

	/**
	 * Returns a plot object based on the x profile of the current ImagePlus.
	 * 
	 * @return a plot object
	 */
	public Plot getXplot() {

		Plot plot = new Plot("Profile plot along the x axis", "x ("
				+ getCalibration().getUnit() + ")", "Intensity (AU)", xProfile[0],
				xProfile[2]);
		plot.setSize(300, 200);
		plot.setColor(Color.red);
		plot.addPoints(xProfile[0], xProfile[1], Plot.CIRCLE);
		plot.setColor(Color.black);
		plot.addLabel(0.6, 0.13, "Dots: measured\nLine: fitted");
		return plot;
	}

	/**
	 * Returns a plot object based on the y profile of the current ImagePlus.
	 * 
	 * @return a plot object
	 */
	public Plot getYplot() {
		Plot plot = new Plot("Profile plot along the y axis", "y ("
				+ getCalibration().getUnit() + ")", "Intensity (AU)", yProfile[0],
				yProfile[2]);
		plot.setSize(300, 200);
		plot.setColor(Color.red);
		plot.addPoints(yProfile[0], yProfile[1], Plot.CIRCLE);
		plot.setColor(Color.black);
		plot.addLabel(0.6, 0.13, "Dots: measured\nLine: fitted");
		return plot;
	}

	/**
	 * Returns a plot object based on the z profile of the current ImagePlus.
	 * 
	 * @return a plot object
	 */
	public Plot getZplot() {

		Plot plot = new Plot("Profile plot along the z axis", "z ("
				+ getCalibration().getUnit() + ")", "Intensity (AU)", zProfile[0],
				zProfile[2]);
		plot.setSize(300, 200);
		plot.setColor(Color.red);
		plot.addPoints(zProfile[0], zProfile[1], Plot.CIRCLE);
		plot.setColor(Color.black);
		plot.addLabel(0.6, 0.13, "Dots: measured\nLine: fitted");
		return plot;
	}

	/**
	 * Gets the minimum value among X, Y and Z fitting goodness.
	 * 
	 * @return the fitting goodness
	 */
	public double getMinimumFittingGoodness() {

		DescriptiveStatistics stats = new DescriptiveStatistics(R2);
		return stats.getMin();
	}

	/**
	 * Gets the fitting goodness.
	 * 
	 * @param axe
	 *           the axe
	 * @return the fitting goodness
	 */
	public double getFittingGoodness(int axe) {
		return R2[axe];
	}

	/**
	 * Gets the fitting parameter mean.
	 *
	 * @param parameter the parameter
	 * @return the fitting parameter mean
	 */
	public double getFittingParameterMean(int parameter) {

		DescriptiveStatistics stats = new DescriptiveStatistics();
		for (int a = 0; a != 3; a++) {
			stats.addValue(getFittingParameter(a, parameter));
		}

		return stats.getMean();

	}

	/**
	 * Gets the fitting parameter.
	 *
	 * @param axe the axe
	 * @param parameter the parameter
	 * @return the fitting parameter
	 */
	public double getFittingParameter(int axe, int parameter) {
		try {
			switch (axe) {

				case X :
					return xParams[parameter];
				case Y :
					return yParams[parameter];
				case Z :
					return zParams[parameter];

			}
			return 0.0;
		} catch (NullPointerException e) {
			return 0.0;
		}
	}

	/**
	 * Gets the c parameter.
	 * 
	 * @param axe
	 *           the axe
	 * @return the c parameter
	 */

	public double getCParameter(int axe) {
		try {
			switch (axe) {

				case X :
					return xParams[2];
				case Y :
					return yParams[2];
				case Z :
					return zParams[2];

			}
			return 0.0;
		} catch (NullPointerException e) {
			return 0.0;
		}
	}

	/**
	 * Returns the calculated resolutions in all available dimensions, i.e. FWHM
	 * after fitting the 2 or 3 profiles
	 * 
	 * @return the x, y and z (if applicable) resolutions as a double array of
	 *         size 2 (or 3).
	 */
	public double[] getResolutions() {
		return resol;
	}

	/**
	 * Gets the resolution.
	 * 
	 * @param axe
	 *           the axe
	 * @return the resolution
	 */
	public double getResolution(int axe) {
		return resol[axe];
	}

	/**
	 * Gets the stack size.
	 * 
	 * @return the stack size
	 */
	public double getStackSize() {
		return getCalibration().pixelDepth * stackSize;
	}

	/**
	 * Discard.
	 * 
	 * @param reason
	 *           the reason
	 */
	public void discard(String reason) {
		setValid(false, reason);
		System.out.println("be cause of : " + reason);
	}

	/**
	 * Sets the valid.
	 * 
	 * @param isValid
	 *           the is valid
	 * @param reason
	 *           the reason
	 */
	public void setValid(boolean isValid, String reason) {
		if (isValid == false) {
			System.out.println(getId() + " has been discarded !");

			setInvalidityReason(reason);
		}
		this.isValid = isValid;
	}

	/**
	 * Checks if is valid.
	 * 
	 * @return true, if is valid
	 */
	public boolean isValid() {
		return isValid;
	}

	/**
	 * Gets the correction resolution.
	 * 
	 * @param axe
	 *           the axe
	 * @return the correction resolution
	 */
	public double getCorrectionResolution(int axe) {
		return getSource().getMicroscope().getCorrectedResolution(
				getResolution(axe), axe);
	}

	/**
	 * Gets the distance from center.
	 * 
	 * @return the distance from center
	 */
	public double getDistanceFromCenter() {
		double centerX = getSource().getFieldOfViewWidth() / 2;
		double centerY = getSource().getFieldOfViewHeight() / 2;

		double x = getFovX();
		double y = getFovY();

		double d = Math.pow(x - centerX, 2) + Math.pow(y - centerY, 2);
		return Math.sqrt(d);
	}

	/**
	 * Gets the x params.
	 * 
	 * @return the x params
	 */
	public String getXParams() {
		return xParamString;
	}

	/**
	 * Gets the y params.
	 * 
	 * @return the y params
	 */
	public String getYParams() {
		return yParamString;
	}

	/**
	 * Gets the z params.
	 * 
	 * @return the z params
	 */
	public String getZParams() {
		return zParamString;
	}

	/**
	 * Gets the width.
	 * 
	 * @return the width
	 */
	public int getWidth() {
		// TODO Auto-generated method stub
		return MathUtils.round(boundaries.getWidth());
	}

	/**
	 * Gets the real width.
	 * 
	 * @return the real width
	 */
	public double getRealWidth() {
		return getWidth() * getCalibration().pixelWidth;
	}

	/**
	 * Gets the real height.
	 * 
	 * @return the real height
	 */
	public double getRealHeight() {
		return getHeight() * getCalibration().pixelHeight;
	}

	/**
	 * Gets the height.
	 * 
	 * @return the height
	 */
	public int getHeight() {
		return MathUtils.round(boundaries.getHeight());
	}

	/**
	 * Gets the raw fwhm.
	 * 
	 * @param axe
	 *           the axe
	 * @return the raw fwhm
	 */
	public double getRawFWHM(int axe) {
		return resol[axe];
	}

	/**
	 * Gets the shift.
	 *
	 * @param axe the axe
	 * @return the shift
	 */
	public double getShift(int axe) {
		if (axe == PSFj.X_AXIS)
			return getDeltaX();
		if (axe == PSFj.Y_AXIS)
			return getDeltaY();
		if (axe == PSFj.Z_AXIS)
			return getDeltaZ();
		return Double.NaN;
	}

	/**
	 * Gets the delta x.
	 * 
	 * @return the delta x
	 */
	public double getDeltaX() {
		return deltaX;
	}

	/**
	 * Sets the delta x.
	 * 
	 * @param deltaX
	 *           the new delta x
	 */
	public void setDeltaX(double deltaX) {
		this.deltaX = deltaX;
	}

	/**
	 * Gets the delta y.
	 * 
	 * @return the delta y
	 */
	public double getDeltaY() {
		return deltaY;
	}

	/**
	 * Sets the delta y.
	 * 
	 * @param deltaY
	 *           the new delta y
	 */
	public void setDeltaY(double deltaY) {
		this.deltaY = deltaY;
	}

	/**
	 * Gets the delta z.
	 * 
	 * @return the delta z
	 */
	public double getDeltaZ() {
		return deltaZ;
	}

	/**
	 * Sets the delta z.
	 * 
	 * @param deltaZ
	 *           the new delta z
	 */
	public void setDeltaZ(double deltaZ) {
		this.deltaZ = deltaZ;
	}

	/**
	 * Gets the alter ego.
	 * 
	 * @return the alter ego
	 */
	public BeadFrame getAlterEgo() {
		return alterEgo;
	}

	/**
	 * Gets the z profile.
	 * 
	 * @return the z profile
	 */
	public double getZProfile() {
		return getCParameter(BeadFrame.Z);
	}

	/**
	 * Reset alter ego.
	 */
	public void resetAlterEgo() {
		alterEgo = null;
	}

	/**
	 * Sets the alter ego.
	 * 
	 * @param alterEgo
	 *           the new alter ego
	 */
	public void setAlterEgo(BeadFrame alterEgo) {

		if (alterEgo == null)
			return;

		// calculating parameters
		deltaX = alterEgo.getCentroidXInImage() - getCentroidXInImage();
		deltaY = alterEgo.getCentroidYInImage() - getCentroidYInImage();

		deltaX = deltaX * getCalibration().pixelWidth;
		deltaY = deltaY * getCalibration().pixelHeight;

		deltaZ = alterEgo.getZProfile() - getZProfile();// getZProfile()-alterEgo.getZProfile();

		this.alterEgo = alterEgo;
	}

	/**
	 * Gets the side view image.
	 * 
	 * @return the side view image
	 */
	public ImagePlus getSideViewImage() {
		if (sideViewImage == null) {
			SideViewGenerator svg = new SideViewGenerator();
			sideViewImage = svg.getPanelView(getSubstack(),
					SideViewGenerator.MAX_METHOD, true, true, 5, false, null, 0);

		}
		return sideViewImage;
	}

	/**
	 * Sets the side view image.
	 * 
	 * @param sideViewImage
	 *           the new side view image
	 */
	public void setSideViewImage(ImagePlus sideViewImage) {
		this.sideViewImage = sideViewImage;
	}

	/**
	 * Gets the distance including z.
	 * 
	 * @param bead
	 *           the bead
	 * @return the distance including z
	 */
	public double getDistanceIncludingZ(BeadFrame bead) {
		// TODO Auto-generated method stub
		/*
		 * double x1 = getFovX(); double x2 = bead.getFovX();
		 * 
		 * double y1 = getFovY(); double y2 = bead.getFovY();
		 * 
		 * double z1 = getCentroidZ(); double z2 = bead.getCentroidZ();
		 */

		double distance = Math.pow(deltaX, 2) + Math.pow(deltaY, 2)
				+ Math.pow(deltaZ, 2);

		return Math.sqrt(distance);

	}

	/**
	 * Gets the invalidity reason.
	 * 
	 * @return the invalidity reason
	 */
	public String getInvalidityReason() {
		return invalidityReason;
	}

	/**
	 * Sets the invalidity reason.
	 * 
	 * @param invalidityReason
	 *           the new invalidity reason
	 */
	public void setInvalidityReason(String invalidityReason) {

		if (this.invalidityReason == null)
			this.invalidityReason = invalidityReason;
	}

	/**
	 * Gets the bead color.
	 *
	 * @return the bead color
	 */
	public Color getBeadColor() {
		return getSource().getBeadsColor();
	}

	/**
	 * Gets the overlay with alter ego.
	 *
	 * @return the overlay with alter ego
	 */
	public ImageProcessor getOverlayWithAlterEgo() {
		if (getAlterEgo() != null) {
			ImageProcessor ip1 = getSource().copyRoi(getBoundariesAsRectangle());
			ImageProcessor ip2 = getAlterEgo().getSource().copyRoi(
					getBoundariesAsRectangle());

			ip1.setLut(LUT.createLutFromColor(getBeadColor()));
			ip2.setLut(LUT.createLutFromColor(getAlterEgo().getBeadColor()));

			ip1 = normalizeImage(ip1).convertToRGB();
			ip2 = getAlterEgo().normalizeImage(ip2).convertToRGB();

			ip1.copyBits(ip2, 0, 0, Blitter.ADD);

			return ip1;
		} else
			return new ColorProcessor(getWidth(), getHeight());
	}

	/**
	 * Normalize image.
	 *
	 * @param ip the ip
	 * @return the image processor
	 */
	public ImageProcessor normalizeImage(ImageProcessor ip) {
		ip = ip.convertToFloat();
		System.out.println(ip.getStatistics().mean);
		ip.multiply(1.0 / (ip.getStatistics().mean + ip.getStatistics().stdDev));
		ip.add(-1);
		ip.setMinAndMax(0, ip.getMax());
		return ip.convertToByte(true);
	}

	/**
	 * Gets the asymetry.
	 *
	 * @return the asymetry
	 */
	public double getAsymetry() {
		return getResolution(0) / getResolution(1);
	}

	/**
	 * Gets the slice number.
	 *
	 * @return the slice number
	 */
	public int getSliceNumber() {
		return stackSize;
	}

        
        public boolean isFlat() {
            return getSliceNumber() == 1;
        }
        
	/**
	 * Checks if is ignored.
	 *
	 * @return true, if is ignored
	 */
	public boolean isIgnored() {
		return isIgnored;
	}

	/**
	 * Sets the ignored.
	 *
	 * @param isIgnored the new ignored
	 */
	public void setIgnored(boolean isIgnored) {
		this.isIgnored = isIgnored;
	}
} public class BeadFrame2D extends BeadFrame {

	/** The theta. */
	double theta = 0;

	/** The fitted params. */
	double[] fittedParams;
	
	/** The abc. */
	double[] abc;
	
	/** The X0. */
	public static int X0 = GaussianFit.XC;
	
	/** The Y0. */
	public static int Y0 = GaussianFit.YC;
	
	/** The A0. */
	public static int A0 = GaussianFit.INT;
	
	/** The bgr. */
	public static int BGR = GaussianFit.BGR;
	
	/** The sx. */
	public static int SX = GaussianFit.S1;
	
	/** The sy. */
	public static int SY = GaussianFit.S2;
	
	/** The theta. */
	public static int THETA = GaussianFit.S3;

	/** The theoretical bead. */
	ImageProcessor theoreticalBead;

	/**
	 * The main method.
	 *
	 * @param args the arguments
	 */
	public static void main(String[] args) {

		/*
		 * DataSet dataset = new DataSet();
		 * 
		 * 
		 * String fileName =
		 * "/Users/cyril/Dropbox/PatroloJ Paper (1)/Paper files/Nature Methods Communications/Files for submission/revised version/2dfit_testimages/different_frame_size_"
		 * + new Date().toLocaleString().replace(" ", "_") + ".csv";
		 * 
		 * //String imageName =
		 * "/Users/cyril/Dropbox/PatroloJ Paper (1)/Paper files/Nature Methods Communications/Files for submission/revised version/2dfit_testimages/different sigma_no asymmetry_integer centers_2048x2048/sigma_y=3.0_asymmetry=1.0_theta=0.000.tif"
		 * ; //String imageName =
		 * "/Users/cyril/Dropbox/PatroloJ Paper (1)/Paper files/Nature Methods Communications/Files for submission/revised version/2dfit_testimages/sigma3_asymmetry1-5_different thetas/sigma_y=3.0_asymmetry=1.5_theta=0.800.tif"
		 * ; String imageName =
		 * "/Users/cyril/Dropbox/PatroloJ Paper (1)/Paper files/Nature Methods Communications/Files for submission/revised version/2dfit_testimages/sigma3_theta0-4_different asymmetries/sigma_y=3.0_asymmetry=1.1_theta=0.400.tif"
		 * ;
		 * 
		 * for(int i = 1;i <= Math.pow(2,15);i*=2) { BeadImage image = new
		 * BeadImage(imageName);
		 * 
		 * 
		 * image.workFromMemory(); addNoise(image, i);
		 * dataset.addValue("division", i);
		 * 
		 * analyseBeadImage(image, -1, 20, dataset, ""+i);
		 * 
		 * }
		 * 
		 * TextUtils.writeStringToFile(fileName, dataset.exportToString(), false);
		 * FileUtils.openFolder(fileName);
		 */
		// String folder =
		// "/Users/cyril/Dropbox/PatroloJ Paper (1)/Paper files/Nature Methods Communications/Files for submission/revised version/2dfit_testimages/sigma3_theta_0_different asymmetries/";
		// String folder =
		// "/Users/cyril/Dropbox/PatroloJ Paper (1)/Paper files/Nature Methods Communications/Files for submission/revised version/2dfit_testimages/sigma3_asymmetry1-5_different thetas/";
		// String folder =
		// "/Users/cyril/Dropbox/PatroloJ Paper (1)/Paper files/Nature Methods Communications/Files for submission/revised version/2dfit_testimages/different sigma_no asymmetry_integer centers_2048x2048/";
		String folder = "/Users/cyril/test_img/to analyze";
		//analyseFolder(folder, ".*tif$");
	}

	/**
	 * Instantiates a new bead frame2 d.
	 *
	 * @param id the id
	 * @param r the r
	 */
	public BeadFrame2D(int id, Rectangle r) {
		super(id, r);
		// TODO Auto-generated constructor stub
	}

	/**
	 * Recenter centroid from z fitting results.
	 */
	private void recenterCentroidFromZFitting() {
		
		
		
		// translating the new center from float to double
		float newCenter = new Float(getFittingParameter(Z, 2)
				/ getCalibration().pixelDepth);
		
		// translating to int that will indicate the focus plane
		int newIntCenter = MathUtils.round(newCenter);

		// if the new center is off limit (which can happen) the function is aborted, the old center will be used
		if(newCenter < 0 || newCenter > ip.getStackSize()-1)  { System.out.println("Z fitting off limits");return; }
		
		// updating the z coordinate of the centroid and center
		centroid[2] = newCenter;
		center[2] = newIntCenter;
	}
	
	/**
	 * Recenter centroid from the 2d fitting. (usually done after the filtering)
	 */
	public void recenterCentroidFrom2DFitting() {
		centroid[0] = new Float(fittedParams[X0]);
		centroid[1] = new Float(fittedParams[Y0]);
	}

	/* (non-Javadoc)
	 * @see knop.psfj.BeadFrame#findPSF()
	 */
	public void findPSF() {
		try {
			if (isValid == false) {
				resol = new double[]{0.0, 0.0, 0.0};
				return;
			}
			
			try {
				
			// this method find the intencity weighted center of the bead (or so called centroid)
			findCenter();
			}
			// second try
			catch(ArrayIndexOutOfBoundsException e){
				e.printStackTrace();
				findCenter();
				System.out.println("Second try is a success");
			}
			
			// if no centroid has been found, then it's a dead end.
			if(centroid == null) {
				setValid(false,"Error when calculating center");
			}
			
			// if(boundaries != null) centerBoundariesAroundCentroid();

			// ip = null;

			// the center of the centroid is converted to int and the Z position is used to indicated
			// the focus plane of the bead.
			getSubstack().setSlice(getCentroidZAsInt());

			resol = new double[3];
			R2 = new double[3];

			// making sure no ROI has been set
			ip.restoreRoi();
			
			// fitting in Z
			getZprofileAndFit();
			
			// redefining the focus plane using the results of the Z fiting
			recenterCentroidFromZFitting();
			
			// calculating SideViewImage (for results)
			getSideViewImage();

			// preparing 2D Gaussian fitting
			GaussianFit fit = new GaussianFit(3, 2);
			
			// retrieving the focus plane of the bead based on the previous calculations
			ImageProcessor fittedSlice = ip.getStack().getProcessor(center[2] + 1);

			
			if(fittedSlice.getBitDepth() == 8) fittedSlice = fittedSlice.duplicate().convertToShort(true);
			
			// 2D fitting in action (the second parameter is the max number of iterations
			double[] fitParams = fit.doGaussianFit(fittedSlice, 3500);// fit.params0_;

			
			// if the 2d fitting works
			if (fitParams.length >= 6) {

				
				// the algorithm returns the following parameters
				double sx = fitParams[GaussianFit.S1]; // sigma x
				double sy = fitParams[GaussianFit.S2]; // sigma y
				
				// also : x0,y0 intensity, background, and theta
				
				double st;

				// System.out.printf("sx = %.3f, sy= %.3f, s3 = %.3f, sx/sy = %.3f",sx,sy,s3,sx/sy);
				
				// making sure that sx is the sigma of the lower axis
				if (sx > sy) {
					st = sx;
					sx = sy;
					sy = st;

					
				}

				// retrieving a,b,c parameters of the 2D Gaussian
				abc = fit.getABC();
				
				// updating parameters
				fitParams[SX] = sx;
				fitParams[SY] = sy;

				// calculating the resoluition
				double fwhmX = sx * getCalibration().pixelWidth * 2 * SQRT2LN2;

				double fwhmY = sy * getCalibration().pixelHeight * 2 * SQRT2LN2;

				double intensityMean = ip.getStatistics().mean;
				
				
				// fitting score
				double rms = fit.getRMS();
				
				// updating resolution
				resol[0] = fwhmX;
				resol[1] = fwhmY;

				// making "BeadFrame Compatible" params
				xParams = new double[]{fitParams[GaussianFit.BGR], fitParams[A0],
						resol[0], 0.0};
				yParams = new double[]{fitParams[GaussianFit.BGR], fitParams[A0],
						resol[1], 0.0};

				double sum = 0;
				
				// calculating the fitting score
				for (short p : (short[]) fittedSlice.getPixels()) {
					sum += Math.pow((1.0 * p - intensityMean), 2);
				}
				
				// storing fitting score
				R2[X] = 1.0 - ((fittedSlice.getWidth() * fittedSlice.getHeight() * Math
						.pow(rms, 2)) / sum);

				R2[Y] = R2[X];
				
				// I don't remember why I put this but it must be important
				while (fittedParams == null) {
					fittedParams = fitParams;
				}

				// if the theta has been found (it's always found but we never know...)
				if (fitParams.length >= 7)
					theta += fitParams[GaussianFit.S3];
				
				// updating some strings to be displayed 
				xParamString = String
						.format(
								"    Fitted on i(x,y) = A * exp(-(a*(x-xc)^2 + c*(y-yc)^2 + 2*b*(x-xc)*(y-yc))) + B\n"
										+ " R^2 = %.3f\n"
										+ "\nParameters:\n"
								+ " A = %.3f   (brightness)\n"
								+ " B = %.3f   (background)\n"
								+ " a = %.3f px\n"
								+ " b = %.3f px\n"
								+ " c = %.3f px\n"
								+ " xc = %.3f px\n"
								+ " yc = %.3f px\n"
								/*
								+ " sigma x = %.3f um\n"
								+ " sigma y = %.3f um\n"
								+ " theta = %.3f degrees\n"
								*/
								,
								R2[X],
								fitParams[A0],
								fitParams[BGR],
								abc[0],
								abc[1],
								abc[2],
								fitParams[X0],
								fitParams[Y0]
								
								
								);

				xParamString = xParamString.replace("\n", "\n\t     ");
			}

			// freeing memeory
			ip = null;

			// System.gc();

			setChanged();
			notifyObservers(new Message(this, MSG_PSF_FOUND));

		} catch (Exception e) {

			e.printStackTrace();
		}

		catch (OutOfMemoryError e) {
			e.printStackTrace();
			System.out.println(new Message(this, "error",
					"There is not enough memory to process the beads !"));
		}
	}

	/* (non-Javadoc)
	 * @see knop.psfj.BeadFrame#getTheta()
	 */
	@Override
	public double getTheta() {
		return theta;
	}

	
	/**
	 * Sqr.
	 *
	 * @param d the d
	 * @return the double
	 */
	public double sqr(double d) {
		return Math.pow(d, 2);
	}

	/**
	 * Gets the overlay of the actual data and the theoretical bead.
	 *
	 * @return the overlay with theoretical
	 */
	public ImageProcessor getOverlayWithTheoretical() {
		
		ImageProcessor real = getSubstack().getStack()
				.getProcessor(getCentroidZAsInt() + 1).duplicate();
		ImageProcessor theoretical = generateTheoreticalBead();

		int frameSize = real.getWidth();
		
		int border = 2;
		
		// creating the result image
		ImageProcessor result = new ColorProcessor(frameSize*2+border,frameSize*3+border);
		
		// filling with white
		result.setColor(Color.white);
		result.fill();
		
		real.resetMinAndMax();

		// setting the LUT
		real.setLut(LUT.createLutFromColor(Color.red));
		theoretical.setLut(LUT.createLutFromColor(Color.green));

		// converting into RGB
		real = real.convertToRGB();
		theoretical = theoretical.convertToRGB();

		// copying each image along side
		result.copyBits(real, 0, 0, Blitter.COPY);
		result.copyBits(theoretical, frameSize+border, 0, Blitter.COPY);
		
		// scaling them twice their orignal size
		real = real.resize(frameSize*2,frameSize*2,true);
		theoretical = theoretical.resize(frameSize*2,frameSize*2,true);
	
		// overlaying
		result.copyBits(theoretical, border/2, frameSize+border, Blitter.COPY);
		result.copyBits(real, border/2, frameSize+border, Blitter.ADD);	
		return result;
	}

	/**
	 * Generate a theoretical bead.
	 *
	 * @return the image processor
	 */
	public ImageProcessor generateTheoreticalBead() {

		ImageProcessor ip = new ShortProcessor(boundaries.getBounds().width,
				boundaries.getBounds().height);

		double A, x0, y0, a, b, c, Z, sigma_x, sigma_y, theta, bgr;

		if (fittedParams != null) {

			A = fittedParams[A0];
			x0 = fittedParams[X0];
			y0 = fittedParams[Y0];
			sigma_x = fittedParams[SX];
			sigma_y = fittedParams[SY];
			theta = fittedParams[THETA] + Math.PI / 2;
			bgr = fittedParams[BGR];

			try {
				if (theta < 0) {
					// theta = Math.abs(theta);
					// double t = sigma_x;
					// sigma_x = sigma_y;
					// sigma_y = t;

				}

				a = Math.pow(Math.cos(theta), 2) / 2 / Math.pow(sigma_x, 2)
						+ Math.pow(Math.sin(theta), 2) / 2 / Math.pow(sigma_y, 2);
				b = -Math.sin(2 * theta) / 4 / sqr(sigma_x) + Math.sin(2 * theta)
						/ 4 / sqr(sigma_y);
				c = Math.pow(Math.sin(theta), 2) / 2 / Math.pow(sigma_x, 2)
						+ Math.pow(Math.cos(theta), 2) / 2 / Math.pow(sigma_y, 2);

				// [X, Y] = meshgrid(-5:.1:5, -5:.1:5);

				for (int X = 0; X != ip.getWidth(); X++) {
					for (int Y = 0; Y != ip.getHeight(); Y++) {
						Z = A
								* Math.exp(-(a * sqr(X - x0) + 2 * b * (X - x0)
										* (Y - y0) + c * sqr(Y - y0))) + bgr;
						// System.out.println(Z);
						ip.set(X, Y, MathUtils.round(Z));
					}
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

		return ip;

	}
	
	/**
	 * Gets the fitted parameters.
	 *
	 * @return the fitted parameters
	 */
	public double[] getFittedParameters() {
		return fittedParams;
	}
       
        
	/*
	public static void analyseBeadImage(BeadImage image, int threshold,
			int frameSize, DataSet dataset, String id) {

		try {

			image.setFocusPlane(29);

			image.getMicroscope().setCalibration(new Calibration());

			image.getMicroscope().save();

			if (threshold == -1)
				image.autoThreshold();
			else
				image.setThresholdValue(threshold);
			if (frameSize == -1)
				image.autoBeadEnlargement();
			else
				image.setFrameSize(frameSize);

			System.out.println(image.getFrameSize());

			BeadFrameProcessorAsync processor = new BeadFrameProcessorAsync(
					image.getBeadFrameList());

			long start = System.currentTimeMillis();

			processor.process();
			processor.filter();

		
			 * ImageProcessor verif = image.getPlane(0).convertToRGB();
			 * for(BeadFrame frame : image.getBeadFrameList()) {
			 * verif.setColor(Color.RED);
			 * verif.drawDot(frame.getWeightedXInImage(),
			 * frame.getWeightedYInImage()); verif.setColor(Color.BLUE);
			 * verif.drawDot(frame.getFrameCenterX(),frame.getFrameCenterY()); }
			 

			// ImageProcessorUtils.show(verif);

			long duration = System.currentTimeMillis() - start;
			// duration = duration;
			FovDataSet imageStats = image.getBeadFrameList().getDataSet(true);

			dataset.addValue("frame size", image.getFrameSize());

			// dataset.addValue("", value);

			dataset.addValue("sigma_x", imageStats.getColumnStatistics("sigma_x")
					.getMean());

			dataset
					.addValue("sigma_x_stddev",
							imageStats.getColumnStatistics("sigma_x")
									.getStandardDeviation());

			dataset.addValue("sigma_y", imageStats.getColumnStatistics("sigma_y")
					.getMean());

			dataset
					.addValue("sigma_y_stddev",
							imageStats.getColumnStatistics("sigma_y")
									.getStandardDeviation());

			dataset.addValue("asymmetry",
					imageStats.getColumnStatistics("asymmetry").getMean());

			dataset.addValue("asymmetry_stddev",
					imageStats.getColumnStatistics("asymmetry")
							.getStandardDeviation());

			dataset.addValue("theta", imageStats.getColumnStatistics("theta_rad")
					.getMean());
			dataset.addValue("theta_stddev",
					imageStats.getColumnStatistics("theta_rad")
							.getStandardDeviation());

			dataset.addValue("beads found", image.getTotalBeadCount());
			dataset.addValue("beads deleted", image.getDeletedBeadsCount());

			dataset.addValue("process time", duration);

		} catch (Exception e) {
			e.printStackTrace();
		}
	}*/

} public class BeadFrameList extends ArrayList<BeadFrame> {

	/**
	 * The bead hashmap. This hashmap is used for retrieving beads by there id.
	 * However, it becomes useless when mixing beads from different sources.
	 * */
	HashMap<Integer, BeadFrame> beadHash = new HashMap<Integer, BeadFrame>();

	
	
	String[] FWHM_NORM_NAMES = new String[] {
			"FWHMmin / Theoretical FWHMxy",
			"FWHMmax / Theoretical FWHMxy",
			"FWHMz / Theoretical FWHMz"
};
	
	
	/**
	 * Returns a FovDataSet of the valid beads contained inside the bead frame
	 * list.
	 * 
	 * @return the data set
	 */
	public FovDataSet getDataSet() {
		return getDataSet(true,false);
	}

	/**
	 * Returns a new BeadFrameList object containing only the valid beads.
	 * 
	 * @return the only valid beads
	 */
	public BeadFrameList getOnlyValidBeads() {
		BeadFrameList list = new BeadFrameList();
		for (BeadFrame frame : this) {
			if (frame.isValid())
				list.add(frame);
		}
		return list;
	}

	/**
	 * Returns a new BeadFrameList object containing only the beads paired with
	 * an alter ego.
	 * 
	 * @return the with alter ego
	 */
	public BeadFrameList getWithAlterEgo() {
		BeadFrameList list = new BeadFrameList();
		for (BeadFrame frame : this) {
			if (frame.getAlterEgo() != null)
				list.add(frame);
		}
		return list;
	}

	/**
	 * Return a FovDataSet of the beads containd inside the list. if
	 * excludeNonValid is true, only valid beads will be included in the data
	 * set. Elsewhere, valid and non-valid beads will be included.
	 * 
	 * @param excludeNonValid
	 *            the exclude non valid
	 * @return the data set
	 */
	public FovDataSet getDataSet(boolean excludeNonValid) {
		return getDataSet(excludeNonValid, true);
	}

	

	
	/**
	 * Gets the data set.
	 *
	 * @param excludeNonValid the exclude non valid
	 * @param includeMultichannelData the include multichannel data
	 * @return the data set
	 */
	public FovDataSet getDataSet(boolean excludeNonValid,
			boolean includeMultichannelData) {



		// instanciating the data set
		FovDataSet dataSet = new FovDataSet();

		// if the list is empty, the function is aborted
		if (size() == 0)
			return dataSet;

		// retrieving the microscope settings of the beads contained inside the
		// list
		Microscope m = get(0).getSource().getMicroscope(); // get the microscope
															// setting of the
															// first bead
		String unit = m.getUnit();
		
		
		if(includeMultichannelData) {
			dataSet.addColumn(PSFj.WAVELENGHT_KEY).setName("Wavelength");
		}
		dataSet.addColumn(PSFj.BEAD_ID).setName("Bead Id");;
		
		String[] axeStrings = new String[] {
				"min", "max", "z"
		};
		
		
		
		
		for(int axe : PSFj.AXES) {
			
			
			String columnNameNotNormalized = String.format("FWHM %s",axeStrings[axe]);
			String columnNameNotCorrected = columnNameNotNormalized + " not corrected";
			String columnNameNormalized = FWHM_NORM_NAMES[axe];
			String rCoeffName = String.format("R^2 FWHM%s", axeStrings[axe]);
			if(axe < 2) rCoeffName = "R^2 FWHM min/max";
			dataSet.addColumn(PSFj.getColumnName(PSFj.FWHM_KEY, axe, PSFj.NOT_NORMALIZED)).setUnit(unit).setName(columnNameNotNormalized);;
			dataSet.addColumn(PSFj.getColumnName(PSFj.FWHM_KEY, axe, PSFj.NOT_NORMALIZED)+"_nc").setUnit(unit).setName(columnNameNotCorrected);;;
			dataSet.addColumn(PSFj.getColumnName(PSFj.FWHM_KEY, axe, PSFj.NORMALIZED)).setName(columnNameNormalized);;
			dataSet.addColumn(PSFj.getColumnName(PSFj.R_COEFF_KEY, axe,PSFj.NOT_NORMALIZED)).setName(rCoeffName);
		}
		
		
		dataSet.addColumn(PSFj.ASYMMETRY_KEY).setName("Asymmetry");
		dataSet.addColumn(PSFj.THETA_KEY).setName("Theta").setUnit(MathUtils.DEGREES);
		
		dataSet.addColumn(PSFj.XC).setUnit(unit).setName("Bead x coordinates");
		dataSet.addColumn(PSFj.YC).setUnit(unit).setName("Bead y coordinates");
		dataSet.addColumn(PSFj.RADIUS).setUnit(unit).setName("Distance from the center");
		dataSet.addColumn(PSFj.Z_PROFILE,PSFj.NOT_NORMALIZED).setUnit(unit).setName("z0");;
		dataSet.addColumn(PSFj.Z_PROFILE,PSFj.NORMALIZED).setName("(z0 - mean(z0)) / FWHMz");;
		dataSet.addColumn(PSFj.Z0_ZMEAN).setUnit(unit);
	
		
		dataSet.addColumn(PSFj.THRESHOLD_KEY).setName("Threshold");
		dataSet.addColumn(PSFj.FRAME_SIZE_KEY).setName("Frame size");
		dataSet.addColumn(PSFj.SOURCE_IMAGE).setName("Image name");
		
		if(get(0) instanceof BeadFrame2D) {
			dataSet.addColumn(PSFj.SIGMA_X_KEY).setName("Sigma X").setUnit(unit);
			dataSet.addColumn(PSFj.SIGMA_Y_KEY).setName("Sigma Y").setUnit(unit);
			dataSet.addColumn(PSFj.THETA_IN_RADIAN_KEY).setName("Theta").setUnit(MathUtils.RADIANT);
		}
                
                dataSet
                        .addColumn(PSFj.CENTROID_BRIGHTNESS_KEY)
                        .setName(PSFj.CENTROID_BRIGHTNESS_NAME);
		
                dataSet.addColumn(PSFj.FITTED_BRIGHTNESS).setName(PSFj.FITTED_BRIGHTNESS);
                dataSet.addColumn(PSFj.FITTED_BACKGROUND).setName(PSFj.FITTED_BACKGROUND);
                
		axeStrings = new String[] { "x","y","z" };
		String[] axeNormsValue = new String[] { "XY","XY","Z" };
		
		if(includeMultichannelData) {
			
			for(int axe : PSFj.AXES) {
				String notNormName = String.format("Chromatic shift in %s", axeStrings[axe]);
				String normName = String.format("Chromatic shift in %s / Theo. FWHM %s",axeStrings[axe],axeNormsValue[axe]);
				
				dataSet.addColumn(PSFj.CHR_SHIFT_KEY, axe, PSFj.NOT_NORMALIZED).setUnit(unit).setName(notNormName);;
				dataSet.addColumn(PSFj.CHR_SHIFT_KEY, axe, PSFj.NORMALIZED).setName(normName);
			}
			
			dataSet.addColumn(PSFj.CHR_SHIFT_XY, PSFj.NOT_NORMALIZED).setUnit(unit).setName("Chromatic shift (XY)");
			dataSet.addColumn(PSFj.CHR_SHIFT_XY, PSFj.NORMALIZED).setName("Chromatic shift (XY) / Theo. FWHMxy");
			
			dataSet.addColumn(PSFj.CHR_SHIFT_XYZ,PSFj.NOT_NORMALIZED).setUnit(unit).setName("Chromatic shift (XYZ) ");
			dataSet.addColumn(PSFj.CHR_SHIFT_XYZ,PSFj.NORMALIZED).setName("Chromatic shift (XYZ) / Theo. FWHMz");
			
			
			
		}
		
		dataSet.addColumn(PSFj.IS_FITTING_VALID).setName("Fit valid");
		
		// for each bead of the list
		for (BeadFrame bead : this) {

			// if excludeNonValid is true and the current bead is not valid,
			// it's not included in the data set
			if (excludeNonValid == true && bead.isValid() == false)
				continue;

			
			
			for(int axe :PSFj.AXES) {
				dataSet.addValue(PSFj.getColumnName(PSFj.FWHM_KEY, axe, PSFj.NOT_NORMALIZED),bead.getCorrectionResolution(axe));
				dataSet.addValue(PSFj.getColumnName(PSFj.FWHM_KEY, axe, PSFj.NOT_NORMALIZED)+"_nc",bead.getResolution(axe));
				dataSet.addValue(PSFj.getColumnName(PSFj.FWHM_KEY, axe, PSFj.NORMALIZED),bead.getCorrectionResolution(axe)/bead.getSource().getMicroscope().getTheoreticalResolution(axe));
				
			}
			
			dataSet.addValue(PSFj.SOURCE_IMAGE,bead.getSource().getImageNameWithoutExtension());
			
			// adding bead position to the dataset
			dataSet.addValue(PSFj.BEAD_ID, bead.getId());

			
			if(includeMultichannelData) {
				dataSet.addValue(PSFj.WAVELENGHT_KEY,bead.getSource().getMicroscope().getWaveLength()*1000);
			}
			
			dataSet.addValue(PSFj.XC, bead.getFovX());
			dataSet.addValue(PSFj.YC, bead.getFovY());
			dataSet.addValue(PSFj.RADIUS, bead.getDistanceFromCenter());
                        
			double z_profile = bead.getCParameter(BeadFrame.Z);

			dataSet.addValue(PSFj.Z_PROFILE,PSFj.NOT_NORMALIZED, z_profile);
                        
                       

			/*
			// corrected and uncorrected resolutions
			dataSet.addValue("fwhmX", correctedResolutionX);
			dataSet.addValue("fwhmY", correctedResolutionY);
			dataSet.addValue("fwhmZ", correctedResolutionZ);

			dataSet.addValue("fwhmX_norm",
					correctedResolutionX / m.getXYTheoreticalResolution());
			dataSet.addValue("fwhmY_norm",
					correctedResolutionY / m.getXYTheoreticalResolution());
			dataSet.addValue("fwhmZ_norm",
					correctedResolutionZ / m.getZTheoreticalResolution());

			dataSet.addValue("fwhmX_nc", resolutionX);
			dataSet.addValue("fwhmY_nc", resolutionY);
			dataSet.addValue("fwhmZ_nc", resolutionZ);
			 */
			
			
			// the minimum fitting among X, Y and Z fitting
			//dataSet.addValue("fitting_min", bead.getMinimumFittingGoodness());

			
			
			dataSet.addValue(PSFj.ASYMMETRY_KEY,bead.getAsymetry());
			dataSet.addValue(PSFj.THETA_KEY, 180.0 * bead.getTheta() / Math.PI);
			dataSet.addValue(PSFj.FRAME_SIZE_KEY, bead.getBoundariesAsRectangle().width);
			
			dataSet.addValue(PSFj.THRESHOLD_KEY, bead.getSource().getThresholdValue());
			
                         // adding brightness
                        dataSet.addValue(PSFj.CENTROID_BRIGHTNESS_KEY,bead.getIntensity(bead.centroid));
                        
                        dataSet.addValue(PSFj.FITTED_BRIGHTNESS,bead.getFittingParameter(BeadFrame.X, BeadFrame.A));
                        dataSet.addValue(PSFj.FITTED_BACKGROUND,bead.getFittingParameter(BeadFrame.X, BeadFrame.B));
                        
			if(bead instanceof BeadFrame2D) {
			BeadFrame2D bead2d = (BeadFrame2D) bead;
				if(bead2d.getFittedParameters() == null) {
					//System.out.println("Fitted parameter is null... why ??");
					//System.out.println("isValid : "+bead2d.isValid);
					//System.out.println("fitting goodness : "+bead2d.getFittingGoodness(0));
					dataSet.addValue(PSFj.SIGMA_X_KEY,Double.NaN);
					dataSet.addValue(PSFj.SIGMA_Y_KEY,Double.NaN);
					dataSet.addValue(PSFj.THETA_IN_RADIAN_KEY,Double.NaN);
				}
				else {
					dataSet.addValue(PSFj.SIGMA_X_KEY,bead2d.getFittedParameters()[BeadFrame2D.SX]);
					
					dataSet.addValue(PSFj.SIGMA_Y_KEY,bead2d.getFittedParameters()[BeadFrame2D.SY]);
					dataSet.addValue(PSFj.THETA_IN_RADIAN_KEY,bead2d.getTheta());
					
				}
			}
			
                       
			
			// if the bead has an alter ego
			if (includeMultichannelData) {
				if(bead.getAlterEgo() == null) {
					dataSet.addValue("Corresponding bead id",-1);
				}
				else {
					dataSet.addValue("Corresponding bead id",bead.getAlterEgo().getId());
				}
				
				
				for(int axe : PSFj.AXES) {
					dataSet.addValue(PSFj.CHR_SHIFT_KEY, axe,PSFj.NOT_NORMALIZED, 1.0 * bead.getShift(axe));
					dataSet.addValue(PSFj.CHR_SHIFT_KEY, axe,PSFj.NORMALIZED, 1.0 * bead.getShift(axe)/m.getTheoreticalResolution(axe));
				}
				
				
				dataSet.addValue(PSFj.CHR_SHIFT_XY, PSFj.NOT_NORMALIZED, bead.getDistance(bead.getAlterEgo()));
				
				dataSet.addValue(PSFj.CHR_SHIFT_XY,PSFj.NORMALIZED,
						bead.getDistance(bead.getAlterEgo())
								/ m.getXYTheoreticalResolution());

				dataSet.addValue(PSFj.CHR_SHIFT_XYZ,PSFj.NOT_NORMALIZED,
						bead.getDistanceIncludingZ(bead.getAlterEgo()));
				
				dataSet.addValue(PSFj.CHR_SHIFT_XYZ,PSFj.NORMALIZED,
						bead.getDistanceIncludingZ(bead.getAlterEgo())
								/ m.getZTheoreticalResolution());

			}

			// adding fitting goodness
			for(int axe : PSFj.AXES) {
				dataSet.addValue(PSFj.R_COEFF_KEY,axe,PSFj.NOT_NORMALIZED,bead.getFittingGoodness(axe));
			}

			dataSet.addValue(PSFj.IS_FITTING_VALID, bead.isValid() ? 1 : 0);
		}
		
		for(int axe : PSFj.AXES) {
			dataSet.setTheoriticalValue(PSFj.getColumnName(PSFj.FWHM_KEY, axe, PSFj.NOT_NORMALIZED), get(0).getSource().getMicroscope().getTheoreticalResolution(axe));
		}

		

		dataSet.recalculateZProfileNormalisation();


		
		return dataSet;
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see java.util.ArrayList#add(java.lang.Object)
	 */
	@Override
	public boolean add(BeadFrame bead) {

		beadHash.put(bead.getId(), bead);
		return super.add(bead);

	}
	
	

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.util.ArrayList#clear()
	 */
	@Override
	public void clear() {
		super.clear();
		beadHash.clear();
	}

	/**
	 * Gets a BeadFrame from its integer id.
	 * 
	 * @param id
	 *            the BeadFrame integer id
	 * @return a BeadFrame
	 */
	public BeadFrame getFromId(Integer id) {
		return beadHash.get(id);
	}

	/**
	 * Gets the number of non-valid beads.
	 *
	 * @return the number of non valid beads
	 */
	public int getNonValidBeadFrameCount() {

		return getTotalOfBeads() - getValidBeadFrameCount();
	}

	/**
	 * Gets the valid bead frames.
	 *
	 * @return the valid bead frames
	 */
	public int getValidBeadFrameCount() {
		int count = 0;
		for (BeadFrame frame : this) {
			if (frame.isValid()) {
				count++;
			}
		}
		return count;
	}

	/**
	 * Add the bead frames from an other bead frame list.
	 *
	 * @param beadFrameList            the bead frame list
	 */
	public void mergeList(BeadFrameList beadFrameList) {
		for (BeadFrame frame : beadFrameList) {
			add(frame);
		}
	}

	/**
	 * Gets the valid bead frames.
	 * 
	 * @return a list of the valid bead frames
	 */
	public BeadFrameList getValidBeadFrames() {

		BeadFrameList list = new BeadFrameList();

		for (BeadFrame frame : this) {
			if (frame.isValid()) {
				list.add(frame);
			}
		}

		return list;
	}

	
	
	
	
	/**
	 * Gets the total of beads.
	 * 
	 * @return the total of beads
	 */
	public int getTotalOfBeads() {
		return size();
	}

	/**
	 * Gets the delta x statistics.
	 * 
	 * @return the delta x statistics
	 */
	public DescriptiveStatistics getDeltaXStatistics() {
		DescriptiveStatistics stats = new DescriptiveStatistics();
		for (BeadFrame f : this) {
			if (f.isValid() == false)
				continue;
			stats.addValue(f.getDeltaX());
		}
		return stats;
	}

	/**
	 * Gets the delta y statistics.
	 * 
	 * @return the delta y statistics
	 */
	public DescriptiveStatistics getDeltaYStatistics() {
		DescriptiveStatistics stats = new DescriptiveStatistics();
		for (BeadFrame f : this) {
			if (f.isValid() == false)
				continue;
			stats.addValue(f.getDeltaY());
		}
		return stats;
	}

	/**
	 * Gets the delta z statistics.
	 * 
	 * @return the delta z statistics
	 */
	public DescriptiveStatistics getDeltaZStatistics() {
		DescriptiveStatistics stats = new DescriptiveStatistics();
		for (BeadFrame f : this) {
			if (f.isValid() == false)
				continue;
			stats.addValue(f.getDeltaZ());
		}
		return stats;
	}

	/**
	 * Gets the number of beads paired bead frames (with an alter ego).
	 *
	 * @return the number of beads with an alter ego
	 */
	public int getPairedCount() {
		int count = 0;
		for (BeadFrame f : this) {
			if (f.getAlterEgo() != null) {
				count++;
			}
		}

		return count;
	}

	
	
	/**
	 * Gets the from roi.
	 *
	 * @param roi the roi
	 * @return the from roi
	 */
	public BeadFrameList getFromROI(Rectangle roi) {
		
		if(roi == null) return this;
		BeadFrameList list = new BeadFrameList();
		
		for(BeadFrame frame : this) {
			if(roi.contains(frame.getFrameCenterX(), frame.getFrameCenterY())) list.add(frame);
		}
		
		return list;
		
	}
	
	
	/**
	 * Gets the certain number of beads contained inside a defined zone. The
	 * zone should be in pixel and in the image space.
	 * 
	 * @param zone
	 *            the zone
	 * @param max
	 *            the maximum number of beads to be retrieved
	 * @return the sample from corner
	 */
	public BeadFrameList getSampleFromCorner(Rectangle zone, int max) {

		BeadFrameList list = new BeadFrameList();

		int count = 0;

		for (BeadFrame frame : this) {
			if (zone.contains(new Point(frame.getFrameX(), frame.getFrameY()))
					&& frame.getAlterEgo() != null) {
				list.add(frame);
				count++;

			}
			if (count >= max)
				break;

		}

		return list;

	}

	/**
	 * Gets the bead montage of the bead contained inside the list.
	 *
	 * @return the bead montage
	 */
	public ImageProcessor getBeadMontage() {

		if (size() == 0)
			return new ColorProcessor(10, 10);

		int lines = MathUtils.round(Math.sqrt(size()));
		int columns = lines;

		int fw = get(0).getWidth();
		int fh = get(0).getHeight();

		int w = get(0).getWidth() * lines;
		int h = get(0).getHeight() * lines;

		ColorProcessor montage = new ColorProcessor(w, h);

		int count = 0;

		for (int l = 0; l != lines; l++) {
			for (int c = 0; c != columns; c++) {
				montage.copyBits(get(count).getOverlayWithAlterEgo(), c * fw, l
						* fh, Blitter.COPY);
				montage.setColor(Color.yellow.darker().darker().darker()
						.darker());
				montage.drawRect(c * fw, l * fh, fw, fh);
				count++;
				if (count >= size())
					break;
			}
			if (count >= size())
				break;
		}

		return montage;

	}
	
	/**
	 * Gets the.
	 *
	 * @param start the start
	 * @param count the count
	 * @return the bead frame list
	 */
	public BeadFrameList get(int start, int count) {
		
		BeadFrameList list = new BeadFrameList();
		
		int index = 0;
		for(BeadFrame frame : this) {
			if ((index >= start) && (index - start < count)) {
            list.add(frame);
        }
        index++;
		}
		
		
		return list;
	}
	
	
} public class BeadImage extends Observable {

    /**
     * The file address.
     */
    String fileAddress;

    /**
     * The stack size.
     */
    int stackSize = -1;

    /**
     * The dot location : set of Rectangle of a view pixels wide determining the
     * position of beads.
     */
    ArrayList<Rectangle> beadLocation;

    /**
     * The dot frames : calculated from the the dotLocation, each Rectangle in
     * this list represents the extracted area of a bead.
     */
    BeadFrameList beadFrames = new BeadFrameList();

    /**
     * Image Processor Reader object : used when working from disk.
     */
    private ImageProcessorReader ipr = new ImageProcessorReader();

    /**
     * Rhe original image.
     */
    private ImageStack stack;

    /**
     * Image corresponding to the bead focal plane.
     */
    private ImageProcessor middleImage;

    /**
     * List of stack images : each one is a bead.
     */
    private HashMap<Integer, ImageStack> stackList; // = new HashMap<Integer,
    // ImageStack>();

    /**
     * The not set.
     */
    public static int NOT_SET = -1;

    /**
     * The not found.
     */
    public static int NOT_FOUND = -9;

    /**
     * The threshold level for the background substraction.
     */
    protected int thresholdValue = NOT_SET;

    /**
     * The size of the area extracted for each bead.
     */
    protected int frameSize = NOT_SET;

    /**
     * When auto calculating the automatic frame size will use
     * beadEnlargementFactor * sigma in pixel.
     */
    protected int beadEnlargementFactor = 10;

    /**
     * The lut min.
     */
    protected double minIntentisyOfWholeStack;

    /**
     * The lut max.
     */
    protected double maxIntensityOfWholeStack = NOT_SET;

    /**
     * The segmentation mask.
     */
    protected ImageProcessor segmentationMask;

    /**
     * The preview of the process.
     */
    protected ImageProcessor preview;

    /**
     * The image width.
     */
    protected int imageWidth;

    /**
     * The image height.
     */
    protected int imageHeight;

    /**
     * The preview width.
     */
    protected int previewWidth = 500;

    /**
     * The preview height.
     */
    protected int previewHeight = 500;

    /**
     * The preview x.
     */
    protected int previewX = 0;

    /**
     * The preview y.
     */
    protected int previewY = 0;

    /**
     * The bead focus plane.
     */
    protected int beadFocusPlane = NOT_SET;

    /**
     * The image name.
     */
    protected String imageName;

    /**
     * The image path.
     */
    protected String imageFolder = null;

    /**
     * The min threshold.
     */
    protected int minThreshold = NOT_SET;

    /**
     * The max threshold.
     */
    protected int maxThreshold = NOT_SET;

    /**
     * The file size.
     */
    protected double fileSize;

    /**
     * The progress.
     */
    protected int progress;

    /**
     * The is valid.
     */
    protected boolean isValid = false;

    /**
     * The is opening.
     */
    protected boolean isOpening = false;

    /**
     * The image disk size.
     */
    protected long imageDiskSize = -1;

    /**
     * The raw preview.
     */
    protected ImageProcessor rawPreview = null;

    /**
     * The status.
     */
    protected String status;

    /**
     * The frame number.
     */
    protected int frameNumber;

    protected BeadLocator locator = new BeadLocator3D();

    /**
     * ignored frame number.
     */
    protected int ignoredFrameNumber = 0;

    /**
     * The lut.
     */
    protected EasyLUT lut = new EasyLUT("blue_orange_white");

    /**
     * clean but let stacks in memory.
     */
    public static int CLEAN_BUT_LET_STACKS_IN_MEMORY = 0;

    /**
     * clean all.
     */
    public static int CLEAN_ALL = 1;

    /**
     * clean for deletion.
     */
    public static int CLEAN_FOR_DELETION = 2;

    /**
     * The clean frames.
     */
    public static int CLEAN_FRAMES = 3;

    /**
     * The counter3d.
     */
    Counter3D counter3d;

    /**
     * The microscope.
     */
    protected Microscope microscope;

    public static final String MSG_PARAMETER_CHANGED = "parameter changed";

    public static final String MSG_THRESHOLD_CHANGED = "threshold changed";
    public static final String MSG_FOCUS_CHANGED = "focus changed";
    public static final String MSG_FRAMESIZE_CHANGED = "bead enlargement changed";

    public static final String MSG_PREVIEW_UPDATED = "preview updated";
    public static final String MSG_IMAGE_NOT_OKAY = "image not okay";
    public static final String MSG_IMAGE_OKAY = "image okay";

    public static final String MSG_NEW_CHANNEL_DETECTED = "other channel detected";

    /**
     * Instantiates a new bead image.
     */
    public BeadImage() {

    }

    /**
     * The Constant TOP_LEFT_CORNER.
     */
    public final static int TOP_LEFT_CORNER = 0;

    /**
     * The Constant TOP_RIGHT_CORNER.
     */
    public final static int TOP_RIGHT_CORNER = 1;

    /**
     * The Constant BOTTOM_LEFT_CORNER.
     */
    public final static int BOTTOM_LEFT_CORNER = 2;

    /**
     * The Constant BOTTOM_RIGHT_CORNER.
     */
    public final static int BOTTOM_RIGHT_CORNER = 3;

    /**
     * The Constant CENTER.
     */
    public final static int CENTER = 4;

    /**
     * Instantiates a new bead image from the file with the specified address.
     *
     * @param s the address of the file
     */
    public BeadImage(String s) {
        super();
        setFileAddress(s);

    }

    /**
     * Instantiates a new bead image with an image stack address.
     *
     * @param s the ImageStack
     */
    public BeadImage(ImageStack s) {
        super();
        setStack(s);

    }

    /**
     * Sets the stack image.
     *
     * @param s the new stack
     */
    public void setStack(ImageStack s) {
        stack = s;

        setImageHeight(s.getHeight());
        setImageWidth(s.getWidth());

    }

    /**
     * Checks if the image is in memory or if we work from the disk.
     *
     * @return true, if is in memory
     */
    public boolean isInMemory() {
        return (stack != null);
    }

    /**
     * Checks if is the object is opening the image.
     *
     * @return true, if is opening
     */
    public boolean isOpening() {
        return isOpening;
    }

    /**
     * The main method.
     *
     * @param argv the arguments
     */
    public static void main(String[] argv) {

        BeadImage startImage = new BeadImage(
                "/Users/cyril/Downloads/ApoTIRF_60x_dual_channel/ch2_ApoTIRF_60x.tif");

        startImage.workFromMemory();

     
        startImage.autoFocus();
        startImage.autoThreshold();
        startImage.setFrameSize(20);
        BeadFrameProcessor processor = new BeadFrameProcessorAsync(
                startImage.getBeadFrameList());
        //startImage.buildStackList();
        processor.process();
        processor.filter();
        System.out.println("signal to noise ration : "  + startImage.getSignalToNoiseRatio());

		
    }

    /**
     * Sign of a double.
     *
     * @param d the double
     * @return -1 for negative doubles, 1 for positive ones
     */
    public int signOf(double d) {
        if (d > 0) {
            return 1;
        }
        if (d < 0) {
            return -1;
        }
        return 0;

    }

    /**
     * Wait until the image is opened.
     */
    public void waitForOpening() {
        while (isOpening()) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {

                e.printStackTrace();
            }
        }
    }

    /**
     * Sets the image name.
     *
     * @param name the new image name
     */
    public void setImageName(String name) {
        Pattern p = Pattern.compile("(.*)[.]\\w$");
        Matcher m = p.matcher(name);

        if (!m.matches()) {
            imageName = name;
        } else {
            imageName = m.group(1);
        }

        imageName = name;
    }

    /**
     * Sets the image path.
     *
     * @param path the new image path
     */
    public void setImageFolder(String path) {

        if (path.endsWith("/") == false) {
            path = path + "/";
        }

        imageFolder = path;
    }

    /**
     * Gets the image name.
     *
     * @return the image name
     */
    public String getImageName() {

        return imageName;
    }

    /**
     * Gets the image name without extension.
     *
     * @return the image name without extension
     */
    public String getImageNameWithoutExtension() {

        return getImageName().replace(".tif", "");
    }

    /**
     * Gets the image path.
     *
     * @return the image path
     */
    public String getImageFolder() {
        return imageFolder;
    }

    /**
     * Gets the image intensity sum.
     *
     * @param ip the ip
     * @return the image intensity sum
     */
    public double getImageIntensitySum(ImageProcessor ip) {

        double sum = 0;
        for (int x = 0; x != ip.getWidth(); x++) {
            for (int y = 0; y != ip.getHeight(); y++) {
                sum += (ip.getPixel(x, y));
            }
        }

        return sum;

    }

    /**
     * Gets the file address.
     *
     * @return the file address
     */
    public String getFileAddress() {
        return fileAddress;
    }

    /**
     * Gets the fiel of view area.
     *
     * @return the fiel of view area
     */
    public double getFielOfViewArea() {
        return getCalibration().pixelWidth * getImageWidth() * getImageHeight()
                * getCalibration().pixelHeight;
    }

    /**
     * Gets the field of view width.
     *
     * @return the field of view width
     */
    public double getFieldOfViewWidth() {
        return getCalibration().pixelWidth * getImageWidth();
    }

    /**
     * Gets the field of view height.
     *
     * @return the field of view height
     */
    public double getFieldOfViewHeight() {
        return getCalibration().pixelHeight * getImageHeight();
    }

    /**
     * Gets the bead density.
     *
     * @return the bead density
     */
    public double getBeadDensity() {

        return getBeadLocation().size() / getFielOfViewArea();

    }

    /**
     * Gets the background intensity.
     *
     * @return the background intensity
     */
    public double getBackgroundIntensity() {
        ImageStatistics stats = getPlane(0).getStatistics();
        return stats.mean + stats.stdDev;
    }

    /**
     * Read width and height from disk.
     */
    public synchronized void readWidthAndHeightFromDisk() {
        ImageProcessorReader ipr = new ImageProcessorReader(
                new ChannelSeparator(LociPrefs.makeImageReader()));

        try {
            ipr.setId(fileAddress);

        } catch (FormatException e) {

            e.printStackTrace();
            try {
                ipr.close();
            } catch (IOException e1) {

                e1.printStackTrace();
            }
            isValid = false;
            return;

        } catch (IOException e) {
            e.printStackTrace();
            return;
        }

        int width = ipr.getSizeX();
        int height = ipr.getSizeY();
        int numChannel = ipr.getSizeC();
        int bitsPerPixel = ipr.getBitsPerPixel();

        if (numChannel > 2) {
            notifyObservers("error", "Only monochannel image can be added.", null, null);
            isValid = false;
            return;
        }

        if (bitsPerPixel > 16) {
            isValid = false;
            setInvalidityReason("Only 8-bits and 16-bits tif images are supported.");
            setProgress(0, MSG_IMAGE_OKAY);

            notifyObservers(new Message(this, "error", "Only 8-bits and 16-bits tif images are supported."));
            try {
                ipr.close();
            } catch (IOException e) {

                e.printStackTrace();
            }
            return;
        }

        setImageWidth(width);
        setImageHeight(height);

    }

    /**
     * load the stack into memory.
     */
    public synchronized void workFromMemory() {
		// if the fileAddress is not equal to null, this means that
        // the image has not been previously load into memory
        if (fileAddress != null && stack == null) {
            System.out.println("Loading image in memory...");
            setProgress(0, "Loading image in memory...");

            try {
				// BF.openImagePlus(path)

				// stack = BF.openImagePlus(fileAddress)[0].getImageStack();
				// stack = new
                // Opener().openImage(fileAddress).getImageStack();//
                // IJ.openImage(fileAddress).getStack();
                ImageProcessorReader ipr = new ImageProcessorReader(
                        new ChannelSeparator(LociPrefs.makeImageReader()));
                DescriptiveStatistics standardDeviations = new DescriptiveStatistics();

                ipr.setId(fileAddress);

                int width = ipr.getSizeX();
                int height = ipr.getSizeY();
                int num = ipr.getImageCount();
                int numChannel = ipr.getSizeC();
                int bitsPerPixel = ipr.getBitsPerPixel();

                double stdDev;
                double min;
                double max;

                // if a second channel in the image is detected
                boolean isSecondChannel = false;
                BeadImage secondChannel = null;
                ImageStack secondStack = null;

                if (numChannel == 2) {

                    secondChannel = new BeadImage();
                    secondChannel.setFileAddress(fileAddress);
                    secondChannel.setImageName(secondChannel.getImageName()
                            + "_channel_2");
                    secondStack = new ImageStack(width, height);
                    isSecondChannel = true;
                    secondChannel.setStack(secondStack);

                    notifyObservers(MSG_NEW_CHANNEL_DETECTED,
                            "Two channels detected", null, secondChannel);

                }

                stack = new ImageStack(width, height);

                for (int i = 0; i != num; i++) {
                    setProgress(i, num);
                    setStatus("Loading slice " + (i + 1) + "/" + num + "...");

                    ImageProcessor ip = ipr.openProcessors(i)[0];

                    if (isSecondChannel) {
                        i++;
                        ImageProcessor ip2 = ipr.openProcessors(i)[0];
                        secondStack.addSlice(ip2);
                        secondChannel.setStatus("Loading slice " + (i + 1) + "/"
                                + num + "...");
                        secondChannel.setProgress(i, num);
                    }

                    min = ip.getMin();
                    max = ip.getMax();

                    if (min < minIntentisyOfWholeStack) {
                        minIntentisyOfWholeStack = min;
                    }
                    if (max > maxIntensityOfWholeStack) {
                        maxIntensityOfWholeStack = max;
                    }

                    stdDev = ip.getStatistics().stdDev;

                    if (standardDeviations.getMax() < stdDev) {
                        beadFocusPlane = i;
                    }
                    standardDeviations.addValue(stdDev);
                    stack.addSlice(ip);
                    updateView(ip);
                }
                if (isSecondChannel) {
                    autoFocus();
                    secondChannel.autoFocus();
                    secondChannel.setProgress(100);
                }
                try {
                    ipr.close();
                } catch (Exception e) {
                    System.err.println("Error when closing the image reader.");
                }
                setChanged();
                notifyObservers(new Message(this, MSG_IMAGE_OKAY));
                setStatus("Done.");
                setProgress(100);

                setImageHeight(stack.getHeight());
                setImageWidth(stack.getWidth());

                isValid = true;
                new ImagePlus("", stack).resetDisplayRange();
                // openImage();

            } catch (NullPointerException e) {
                notifyError("Image not valid.");
                e.printStackTrace();
                setProgress(0, "image not okay");
                return;
            } catch (FormatException e) {
                setProgress(0, "This image format is not supported.");
                notifyError("This image format is not supported");
                e.printStackTrace();
            } catch (IOException e) {

                notifyError("File not accessible");
                setProgress(0, "Image not reachable.");
                e.printStackTrace();
            }

        }
    }

    /**
     * Sets the file address and load it if the parameter "load" is true.
     *
     * @param fileAddress the file address
     * @param load load the stack if true
     */
    public void setFileAddress(String fileAddress, boolean load) {

        setFileAddress(fileAddress);
        if (load) {
            workFromMemory();
        }

    }

    /**
     * Sets the file address.
     *
     * @param fileAddress the new file address
     */
    public void setFileAddress(String fileAddress) {

        this.fileAddress = fileAddress;

        File file = new File(fileAddress);

        setImageFolder(file.getParent());
        setImageName(file.getName());

        // make sure to store the image size in memory for further use
        getImageDiskSize();

    }

    /**
     * Notify the observer of an error.
     *
     * @param error the error message
     */
    public void notifyError(String error) {
        setChanged();
        notifyObservers(new Message(this, "error", error));
    }

    /**
     * Gets the raw focus point, without launching the auto calculation
     * algorithm if the focus point is not set.
     *
     * @return the raw focus point
     */
    public int getRawFocusPoint() {
        return beadFocusPlane;
    }

    /**
     * Gets the stack size.
     *
     * @return the stack size
     */
    public int getStackSize() {
        if (stack != null) {
            return stack.getSize();
        } else {
            return 0;
        }
    }

    /**
     * Gets the individual bead stack with the specified id.
     *
     * @param id the bead id
     * @return the image stack of the bead
     */
    public ImageStack getStack(int id) {
        if (stackList == null) {
            buildStackList();
        }

        if (id >= stackList.size()) {
            System.err.println("Stack " + id + " doesn't exist.");
            return null;
        }

        return stackList.get(id);

    }

    /**
     * Builds the stack list : retrieve every bead from the original image stack
     * and store them into a ImageStack array.
     *
     * A list of bead stack is created and each bead stack is filled while
     * loading (and browsing) the stack.
     */
    public void buildStackList() {

        if (stackList != null) {
            return;
        }

        stackList = new HashMap<Integer, ImageStack>();

        setStatus("Retreiving stacks...");

        stackList.clear();

        long start = System.currentTimeMillis();

        // creating an hashmap for storing the different planes
        final HashMap<Integer, ImageProcessor> planes = new HashMap<Integer, ImageProcessor>();

        // an thread that load the images from the disk is created
        new Thread() {
            @Override
            public void run() {
                for (int i = 0; i != getStackSize(); i++) {
                    planes.put(i, getPlane(i));
                }
            }
        }.start();

        // while the loading thread is on...
        BeadFrameList frames = getBeadFrameList();

        System.out.println(frames.size() + " beads to retrieve !");
        // for each plane of the stack
        for (int i = 0; i != getStackSize(); i++) {

            // we wait for each plane to loaded
            while (planes.containsKey(i) == false) {
                try {
                    Thread.currentThread();
                    Thread.sleep(10);
                } catch (InterruptedException e) {

                    e.printStackTrace();
                };
            }

			// the plane is loaded !! Let's start doing some processing
            // (in the mean time, the next thread starts to be loadedà
            // System.out.println("Start processing " + i + " ...");
            // getting the plane
            ImageProcessor sp = planes.get(i);

			// for each bead frame,
            for (BeadFrame frame : frames) {

                final int stackNumber = i;

                // copy the part of the frame from the plane image
                ImageProcessor imagePart = copyRoi(sp,
                        frame.getBoundariesAsRectangle());

				// if the current plane is the first, a new image stack
                // corresponding to the bead is created
                if (stackNumber == 0) {
                    stackList.put(frame.getId(), new ImageStack(
                            imagePart.getWidth(), imagePart.getHeight()));
                }

                // adding the slice to the bead stack
                stackList.get(frame.getId()).addSlice(imagePart);

            }

            // System.out.println("Stop processing " + i + "");
            setProgress(i, getStackSize());

			// freeing some memory
            planes.put(i, null);
            planes.put(i, sp.createProcessor(1, 1));
            sp = null;

        }

        for (Integer id : stackList.keySet()) {
            frames.getFromId(id).setSubstack(new ImagePlus("", stackList.get(id)));
        }

        planes.clear();
        stackList.clear();
        stackList = null;

        System.gc();
        long end = System.currentTimeMillis();

        setStatus("Stacks retreived.");
        setProgress(100);

        System.out.println("Stacks retreived in  " + (end - start) + " ms.");

    }

    /**
     * Gets the plane.
     *
     * @param id the plane id
     * @return the plane image processor
     */
    public ImageProcessor getPlane(int id) {

		// System.out.println("Querying plan " + id + "...");
		/*
         * if (id == -1) { findBeadFocusPlaneWithHeuristic(); }
         */
        try {
            if (stack != null) {
                //System.out.println("Getting plane " + id + "...");
                ImageProcessor plane = stack.getProcessor(id + 1);
				// plane.setColorModel(lut.getColorModel());
                //plane.setLut(lut.getLUT(lutMin, lutMax * 0.8));
                return plane;
            } else {
                try {

                    ImageProcessor plane = ipr.openProcessors(id)[0];

                    plane.setColorModel(lut.getColorModel());

                    return plane;
                } catch (FormatException e) {

                    e.printStackTrace();

                } catch (IOException e) {

                    e.printStackTrace();

                }
            }
        } catch (Exception e) {
			// setProgress(0);
            // setStatus("Can't open image.");

        }

        System.err.println("Couldn't get stack " + id + " !");
        return null;
    }

    /**
     * Gets the stack.
     *
     * @return the stack
     */
    public ImageStack getStack() {
        return stack;
    }

    /**
     * Gets the image corresponding to the focal plane.
     *
     * @return the focal plane image
     */
    public ImageProcessor getMiddleImage() {
        if (middleImage == null) {

            if (getFocusPlane() == -1) {
                return null;
            }

            middleImage = getPlane(getFocusPlane());

            ImageStatistics stats = middleImage.getStatistics();
            minThreshold = DataTricks.round(stats.min) - 1;
            maxThreshold = DataTricks.round(stats.max) + 1;

        }
        return middleImage;
    }

    /**
     * Show.
     *
     * @param ip the ip
     * @return the image plus
     */
    public ImagePlus show(ImageProcessor ip) {
        ImagePlus imp = new ImagePlus("PatroloJ", ip);
        imp.show();
        return imp;
    }

    public void setLocator(BeadLocator locator) {
        this.locator = locator;
        beadLocation = null;
        beadFrames = null;
        preview = null;
    }

    public BeadLocator getLocator() {
        locator.setBeadImage(this);
        return locator;
    }

    /**
     * Gets the bead location. This threshold the focus plane image and detect
     * all objects using a Connected Component algorithm. It retuns a list of
     * Rectangle object representing each detected object (beads usually).
     *
     * @return the bead location
     */
    public synchronized ArrayList<Rectangle> getBeadLocation() {
        if (beadLocation == null) {
            beadLocation = getLocator().getBeadLocation();
            System.out.println("beadLocation : "+beadLocation);
        }
        return beadLocation;
    }

    /**
     * Auto bid enlargement.
     *
     * @return the int
     */
    public int autoFrameSize() {
        return autoBeadEnlargement(getMicroscope());
    }

    /**
     * Auto bead enlargement.
     *
     * @param m the m
     * @return the int
     */
    protected int autoBeadEnlargement(Microscope m) {

        int autoFrameSize;// = MathUtils.round(
        //	1.0 * beadEnlargementFactor * m.getXYTheoreticalResolution() / 2 / Math.sqrt(2 * Math.log(2)) / getMicroscope().getCalibration().pixelWidth
        //);

        autoFrameSize = MathUtils.round(m.getXYTheoreticalResolution() * beadEnlargementFactor / m.getCalibration().pixelWidth);

        setFrameSize(autoFrameSize);

        setChanged();
        notifyObservers(new Message(this, MSG_FRAMESIZE_CHANGED));
        System.out.println("auto calculated frame size : " + frameSize);
        return frameSize;
    }

    /**
     * Gets the enlarged frame.
     *
     * @param r the r
     * @return the enlarged frame
     */
    public Rectangle getEnlargedFrame(Rectangle r) {
        Rectangle rn = new Rectangle();

        int f = frameSize;

        if (frameSize * frameSize >= imageWidth * imageHeight * 0.8) {
            rn.setLocation(0, 0);
            rn.setSize(imageWidth, imageHeight);
            return rn;
        }

        int x = MathUtils.round(r.getX() + r.getWidth() / 2 - f / 2);

        int y = MathUtils.round(r.getY() + r.getHeight() / 2 - f / 2);

        x -= 1;
        y -= 1;

        int w = f;
        int h = f;
        rn.setLocation(x, y);
        rn.setSize(w, h);

        return rn;
    }

    /**
     * Sets the wave length.
     *
     * @param wavelength the new wave length
     */
    public void setWaveLength(double wavelength) {
        getMicroscope().setWaveLength(wavelength);
    }

    /**
     * Sets the wave length in nano meters.
     *
     * @param wavelength the new wave length in nano meters
     */
    public void setWaveLengthInNanoMeters(String wavelength) {
        try {
            getMicroscope().setWaveLength(Double.parseDouble(wavelength) / 1000);
        } catch (Exception e) {
            System.err.println("Couldn't set wavelength properly");
        }

    }

    /**
     * Gets the wave length in nano meters.
     *
     * @return the wave length in nano meters
     */
    public String getWaveLengthInNanoMeters() {
        if (getMicroscope() != null) {
            return "" + MathUtils.round(getMicroscope().getWaveLength() * 1000);
        } else {
            return "";
        }
    }

    /**
     * Gets the enlarged frame.
     *
     * @param r the r
     * @param f the f
     * @return the enlarged frame
     */
    public static Rectangle getEnlargedFrame(Rectangle r, int f) {
        Rectangle rn = new Rectangle();
        rn.setLocation(round(r.getCenterX() - f / 2), round(r.getCenterY() - f
                / 2));
        rn.setSize(f, f);

        return rn;
    }

    /**
     * Gets the BeadFrame list (areas extracted for the PSF analysis). This
     * function uses the result of the getBeadLocation, and create a frame
     * around each object. When two frames are overlapping too much, only the
     * frame containing the brightest bead is kept.
     *
     * @return the BeadFrame list object (areas extracted for the PSF analysis)
     */
    public synchronized BeadFrameList getBeadFrameList() {
        if (beadFrames == null) {
            beadFrames = getLocator().getBeadFrameList();
        }
        return beadFrames;
    }

    /**
     * Gets the frame number.
     *
     * @return the frame number
     */
    public int getFrameNumber() {

        return frameNumber;
    }

    /**
     * Sets the frame number.
     *
     * @param frameNumber the new frame number
     */
    public void setFrameNumber(int frameNumber) {
        this.frameNumber = frameNumber;
    }

    /**
     * Gets the ignored frame number.
     *
     * @return the ignored frame number
     */
    public int getIgnoredFrameNumber() {
        return ignoredFrameNumber;
    }

    /**
     * Gets the bead max intensity.
     *
     * @param r the r
     * @return the bead max intensity
     */
    public double getBeadMaxIntensity(Rectangle r) {

        ImageProcessor middleImage = getMiddleImage();
        middleImage.setRoi((Rectangle) r.clone());
        double max = middleImage.getStatistics().max;
        middleImage.resetRoi();
        return max;
    }

    /**
     * Copy roi.
     *
     * @param r the r
     * @return the image processor
     */
    public ImageProcessor copyRoi(Rectangle r) {
        return copyRoi(getMiddleImage(), r);
    }

    /**
     * Copy roi into a new image.
     *
     * @param ip the source image
     * @param r the rectangle represanting the ROI
     * @return a ShortProcessor containing the ROI.
     */
    public ImageProcessor copyRoi(ImageProcessor ip, Rectangle r) {

		// System.out.println("Copying a roi of "+r.toString());
        ImageProcessor result = ip.createProcessor(r.width, r.height);

        for (int x = r.x; x != r.x + r.width; x++) {
            for (int y = r.y; y != r.y + r.height; y++) {
                result.setColor(ip.getPixel(x, y));
                result.drawPixel(x - r.x, y - r.y);
            }
        }

        return result;
    }

    /**
     * Gets the calibration.
     *
     * @return the calibration
     */
    public Calibration getCalibration() {

        return getMicroscope().getCalibration();
    }

    /**
     * Sets the calibration.
     *
     * @param cal the new calibration
     */
    public void setCalibration(Calibration cal) {
        getMicroscope().setCalibration(cal);
    }

    /**
     * Gets the segmentation mask from a specified image.
     *
     * @param ip the input image
     * @return the segmentation mask
     */
    private ImageProcessor getSegmentationMask(ImageProcessor ip) {
        return getSegmentationMask(ip, getThresholdValue());
    }

    /**
     * Gets the segmentation mask. This mask is used for bead detection.
     *
     * @param ip the ip
     * @param threshold the threshold
     * @return the segmentation mask
     */
    private ImageProcessor getSegmentationMask(ImageProcessor ip, int threshold) {
        System.out.println("Getting segmentation mask with threshold : "+threshold);
        ImageProcessor mask = ip.duplicate();

        mask.threshold(threshold);
        mask = mask.convertToByte(false);

        mask.invert();
        int[] h = mask.getHistogram();
        for (int i = 0; i != h.length; i++) {
            if (h[i] > 0) {
                System.out.println("i : "+i);
            }
        }
        System.out.println("mask : +" + mask);
        return mask;
    }

    /**
     * Gets the segmented image.
     *
     * @return the segmented image
     */
    public ImageProcessor getSegmentedImage() {
        if (segmentationMask == null) {
            System.out.println("Recalculating mask...");
            segmentationMask = getSegmentationMask(getMiddleImage());
        }
        return segmentationMask;
    }

    /**
     * Returns an image showing the focus plane image with rectangles
     * reprensenting the detected bead and their respective area.
     *
     * @return the bead preview
     */
    public synchronized ImageProcessor getBeadPreview() {

        return getBeadPreview(3);

    }

    /**
     * Gets the bead preview.
     *
     * @param trials the trials
     * @return the bead preview
     */
    public ImageProcessor getBeadPreview(int trials) {
        if (trials == 0) {
            return null;
        }
        if (preview == null) {
            try {
                System.out.println("generating new preview");

                preview = getMiddleImage();

                if (preview == null) {
                    return new ShortProcessor(previewWidth, previewHeight);
                }

                preview = preview.convertToRGB();

                for (BeadFrame frame : getBeadFrameList()) {
                    Rectangle2D r = frame.getBoundaries();

                    if (frame.isValid() == true) {
                        preview.setColor(Color.green);
                    } else {
                        preview.setColor(Color.yellow);
                    }

                    if (frame.isValid() == false) {
                        r = getEnlargedFrame((Rectangle) r, MathUtils.round(r.getWidth() / 2));
                    }

                    preview.drawRect(round(r.getX()), round(r.getY()),
                            round(r.getWidth()), round(r.getHeight()));
                    if (frame.getId() == null) {
                        continue;
                    }

                }

                status = "Done.";
                progress = 100;

                notifyObservers(MSG_PREVIEW_UPDATED, "Done.", 100, null);
            } catch (Exception e) {
                trials--;
                getBeadPreview(trials);
            }
        }
        return preview;

    }

    /**
     * Gets the bead preview in a asynchronous way.
     *
     * @return nothing : you should wait for the signal
     */
    public void getBeadPreviewAsync() {
        new Thread() {
            @Override
            public void run() {

                if (thresholdValue == -1) {
                    autoThreshold();
                }
                if (frameSize == -1) {
                    autoBeadEnlargement(new Microscope());
                }

                try {
                    System.out.println("Getting bead previous async");

                    getBeadPreview();
                    System.out.println("Finished...");

                } catch (ConcurrentModificationException e) {
                    System.out.println("oups...");
                }

            }
        }.start();
    }

    /**
     * Gets the auto threshold.
     *
     * @param sigma the sigma
     * @return the auto threshold
     */
    private double getAutoThreshold(double sigma) {
        // retrieve the focal plane
        ImageProcessor backgroundPlane = getPlane(0);

        // if their's a problem with the focal plane, we return -1;
        if (backgroundPlane == null) {
            return -1;
        }

        // gettting focal plane statistics
        setProgress(50, "Calculating threshold...");
        backgroundPlane.resetRoi();
        ImageStatistics stats = backgroundPlane.getStatistics();

        // retrieving the mean of the first image to serve as background mean
        double bgMean = stats.mean;
        double bgStd = stats.stdDev;

        return bgMean + (bgStd * sigma);
    }

    /**
     * Calculate automatically the threshold value for bead detection.
     *
     * @return the calculated threshold
     */
    public int autoThreshold() {

        double threshold = getAutoThreshold(10);

        System.out.println("Auto detected threshold : " + threshold);
        setThresholdValue(MathUtils.round(threshold));

        setProgress(100, "Done.");

        setChanged();
        notifyObservers(new Message(this, MSG_THRESHOLD_CHANGED));

        return getThresholdValue();
    }

    /**
     * Calculate the minimum and maximum values for the threshold setting.
     *
     * @return nothing : they can be accessed via the attributes minThreshold
     * and maxThreshold.
     */
    private void updateMinAndMax() {

        if (getFocusPlane() == -1) {

            minThreshold = 0;
            maxThreshold = 100;
        }

        if (minThreshold == -1 || maxThreshold == -1) {
            ImageStatistics stats = getMiddleImage().getStatistics();
            minThreshold = DataTricks.round(stats.min) - 1;
            maxThreshold = DataTricks.round(stats.max) + 1;
        }
        setChanged();
        notifyObservers(new Message(this, "min and max changed"));
    }

    /**
     * Gets the threshold minimum value (depends on the focal plane).
     *
     * @return the threshold min
     */
    public int getThresholdMin() {

        return minThreshold;
    }

    /**
     * Gets the threshold maximum value of the whole stack
     *
     * @return the threshold max
     */
    public int getThresholdMax() {

        return maxThreshold;
    }

    /**
     * Gets the threshold value used for bead detection.
     *
     * @return the threshold level
     */
    public int getThresholdValue() {

        return thresholdValue;
    }

    /**
     * Sets the threshold value for.
     *
     * @param newThreshold the new threshold value
     */
    public void setThresholdValue(int newThreshold) {

        System.out.println("** setting threshold to " + newThreshold);

        this.thresholdValue = newThreshold;

        // the segmentation mask is reset
        segmentationMask = null;

        // the bead location is reset
        if (beadLocation != null) {
            beadLocation.clear();
        }

        beadLocation = null;

        beadFrames = null;
        preview = null;
        stackList = null;
        System.gc();

    }

    /**
     * Gets the size of the bead area used for analysis.
     *
     * @return the bead area size
     */
    public int getFrameSize() {

        return frameSize;
    }

    /**
     * Gets the bead enlargement factor.
     *
     * @return the bead enlargement factor
     */
    public int getBeadEnlargementFactor() {
        return beadEnlargementFactor;
    }

    /**
     * Sets the bead enlargement factor.
     *
     * @param beadEnlargementFactor the new bead enlargement factor
     */
    protected void setBeadEnlargementFactor(int beadEnlargementFactor) {
        this.beadEnlargementFactor = beadEnlargementFactor;
    }

    /**
     * Sets the bid enlargement.
     *
     * @param beadEnlargement the new bid enlargement
     */
    public void setFrameSize(int beadEnlargement) {

        if (beadEnlargement == 0 || beadEnlargement == this.frameSize) {
            return;
        }

        System.out.println("** setting bead frame to " + beadEnlargement);

        this.frameSize = beadEnlargement;
        if (beadFrames != null) {
            beadFrames.clear();
        }
        beadFrames = null;
        preview = null;
        stackList = null;
        System.gc();

    }

    /**
     * Find exact focus point.
     */
    public void findExactFocusPoint() {

        ImageProcessor ip = getPlane(0);

        int best = 0;
        // a new standard deviation
        double newStdDev = ip.getStatistics().stdDev;

        // the best among the best
        double bestStdDev = newStdDev;

        // for each plane
        for (int i = 1; i != getStackSize(); i++) {
            setProgress(i, getStackSize());

            // get the plane
            ImageProcessor plane = getPlane(i);

			// tell the views that we are looking
            // at this particular plane
            updateView(plane);

            // the standard deviation of this plane is ...
            newStdDev = plane.getStatistics().stdDev;

            // if it's the best, then congratulation
            if (newStdDev > bestStdDev) {
                bestStdDev = newStdDev;

                // id of the best standard deviation
                best = i;
                /*
                 * System.out
                 * .println("A new best standard deviation\nhas been found : " +
                 * newStdDev);
                 */
                // telling the view what we are doing
                setProgress(100 * i / getStackSize(), "Searching focus...");
            }

        }

        // setting the focus plane to the best
        setFocusPlane(best);

        setProgress(0, "Done.");

    }

    /**
     * Sets the focus plane.
     *
     * @param focusPlane the new focus point
     */
    public void setFocusPlane(int focusPlane) {

        if (focusPlane >= 0 && focusPlane < getStackSize()
                && focusPlane != beadFocusPlane) {
            System.out.println("** setting focus to " + focusPlane);
            beadFocusPlane = focusPlane;
            middleImage = null;
            segmentationMask = null;
            // beadLocation = null;
            preview = null;
            if (getLocator().isFocusPlaneDependent()) {
                beadLocation = null;
                beadFrames = null;
            }
            updateMinAndMax();
        }
    }

    /**
     * Auto focus.
     */
    public void autoFocus() {

        if (isProcessing()) {
            return;
        }

        lockForProcessing();

        try {

            // if (isInMemory()) {
            findExactFocusPoint();
			// } else {
            // findBeadFocusPlaneWithHeuristic();
            // }
        } catch (Exception e) {
            e.printStackTrace();
        }
        getThresholdMax();
        setProcessingFinished();
        notifyObservers("focus changed", "Done.", 100, null);
    }

    /**
     * Gets the focus plane.
     *
     * @return the focus plane (from 0 to stackSize -1)
     */
    public int getFocusPlane() {

        return beadFocusPlane;
    }

    /**
     * Find bead focus plane using an heuristic.
     */
    public void findBeadFocusPlaneWithHeuristic() {

        setStatus("Searching for focus point");

        int mean = Math.round(new Float(DataTricks.mean(getPlane(0))));
        mean = mean * 2;
        int plane = getStackSize() / 3;
        double delta_mean = 0;
        int direction = 1;
        int step = 5;
        int initStep = step;

        while (true) {

            ImageProcessor plane1 = getPlane(plane);
            ImageProcessor plane2 = getPlane(plane + step);

            double mean1 = DataTricks.mean(plane1, mean);
            double mean2 = DataTricks.mean(plane2, mean);

			// System.out.println("mean1 : " + mean1);
            // System.out.println("mean2 : " + mean2);
            delta_mean = (mean2 - mean1) / 5;

            if (direction != signOf(delta_mean)) {
                direction = direction * (-1);
                step = step - 1;

                setProgress(initStep - step, initStep);

            }
            updateView(plane1);
            plane = plane + step * direction;
            if (plane < 0) {

            }

            if (step == 0) {
                System.out.println("it breaks at " + plane + " !!!!");

                if (mean1 > mean2) {
                    setFocusPlane(plane);
                } else {
                    setFocusPlane(beadFocusPlane);
                }

                break;
            }
            setProgress(100 * (initStep - step) / initStep,
                    "Searching focus (heuristic) ...");
            plane1 = null;
            plane2 = null;

        }

        setStatus("Done.");
        setProgress(0);

        return;
    }

    /**
     * Gets the bead frame.
     *
     * @param i the i
     * @return the bead frame
     */
    public BeadFrame getBeadFrame(int i) {
        return getBeadFrameList().get(i);
    }

    /**
     * Rounds a double to an int.
     *
     * @param a the a
     * @return the int
     */
    public static int round(double a) {
        return Math.round(new Float(a));
    }

    /**
     * Gets the image height.
     *
     * @return the image height
     */
    public int getImageHeight() {
        return imageHeight;
    }

    /**
     * Sets the image height.
     *
     * @param imageHeight the new image height
     */
    public void setImageHeight(int imageHeight) {
        this.imageHeight = imageHeight;
    }

    /**
     * Gets the image width.
     *
     * @return the image width
     */
    public int getImageWidth() {
        return imageWidth;
    }

    /**
     * Sets the image width.
     *
     * @param imageWidth the new image width
     */
    public void setImageWidth(int imageWidth) {
        this.imageWidth = imageWidth;
    }

    /**
     * Creates the rectangle.
     *
     * @param w the width
     * @param h the height
     * @param x the x coordinate
     * @param y the y coordinate
     * @return the rectangle
     */
    public Rectangle createRectangle(int w, int h, int x, int y) {
        Rectangle r = new Rectangle(new Point(x, y), new Dimension(w, h));
        return r;

    }

    /**
     * Update view (notify the observers.
     *
     * @param ip the image processing that the view is suggested to display
     * @param name the name of the message sent to the observers
     */
    public void updateView(ImageProcessor ip, String name) {
        rawPreview = ip;
        Message message = new Message(this, name, ip);
        setChanged();
        notifyObservers(message);
    }

    /**
     * Update the view (or suggest the observers to display a ImageProcessor
     * object).
     *
     * @param ip the ip
     */
    public void updateView(ImageProcessor ip) {
        updateView(ip.duplicate(), "beadimage changing raw preview");
    }

	// **********************************
    // Pattern Observer related functions
    // **********************************
    /**
     * Notify model change.
     */
    public void notifyModelChange() {
        setChanged();
        notifyObservers(this);
    }

    /**
     * Notify observers.
     *
     * @param name the name of the message
     * @param strData the string data inside the message (e.g : "Doing
     * something...")
     * @param intData the int data inside the message (e.g : 10 %)
     * @param data the data
     */
    public void notifyObservers(String name, String strData, Integer intData,
            Object data) {

        setChanged();
        notifyObservers(new Message(this, name, strData, intData, data));
    }

    /**
     * Gets the raw preview (doesn't calculate the preview like it usually do).
     *
     * @return the raw preview
     */
    public ImageProcessor getRawPreview() {
        return preview;
    }

    /**
     * Sets the progress of an ongoing process.
     *
     * @param progress the progress
     * @param status the status
     */
    public void setProgress(final int progress, final String status) {
        this.progress = progress;
        this.status = status;

        sendMessage();

    }

    /**
     * Sets the progress.
     *
     * @param progress the new progress
     */
    public void setProgress(int progress) {
        if (progress == this.progress) {
            return;
        }
        this.progress = progress;
        sendMessage();
    }

    /**
     * Sets the progress.
     *
     * @param progress the progress
     * @param total the total
     */
    public void setProgress(int progress, int total) {
        setProgress(100 * progress / total);
    }

    /**
     * Sets the status.
     *
     * @param status the new status
     */
    public void setStatus(String status) {

        this.status = status;
        if (this.status.equals(status)) {
            return;
        }
        sendMessage();

    }

    /**
     * Send message saying that the processing status changed.
     */
    public void sendMessage() {
        try {
            notifyObservers("processing status changed", status, progress, null);
        } catch (Exception e) {
            System.err.println("Error while notifying observers.");
            e.printStackTrace();
        }
    }

    /**
     * Gets the progress.
     *
     * @return the progress
     */
    public int getProgress() {
        return progress;
    }

    /**
     * Gets the status.
     *
     * @return the status
     */
    public String getStatus() {
        return status;
    }

    /**
     * Checks if the image is valid.
     *
     * @return true, if is valid
     */
    public boolean isValid() {
        return isValid;
    }

    /**
     * Gets the size of the image in the disk.
     *
     * @return the image disk size
     */
    public long getImageDiskSize() {

        if (imageDiskSize == -1) {
            if (getFileAddress() != null) {
                File file = new File(getFileAddress());
                imageDiskSize = file.length() / 1000 / 1000;
            }
        }
        return imageDiskSize;

    }

    /**
     * Sets the image disk size.
     *
     * @param imageDiskSize the new image disk size
     */
    public void setImageDiskSize(long imageDiskSize) {
        this.imageDiskSize = imageDiskSize;
    }

    /**
     * Ask for deletion.
     */
    public void askForDeletion() {
        isValid = false;
        setChanged();
        notifyObservers(new Message(this, MSG_IMAGE_NOT_OKAY));
    }

    /**
     * Auto focus async.
     */
    public void autoFocusAsync() {
        new Thread() {
            @Override
            public void run() {
                autoFocus();
            }
        }.start();

    }

    /**
     * Clean memory.
     */
    public void cleanMemory() {
        cleanMemory(CLEAN_ALL);
    }

    /**
     * Clean memory.
     *
     * @param level the level
     */
    public void cleanMemory(int level) {
        preview = null;
        rawPreview = null;
        middleImage = null;

        if (level == CLEAN_FRAMES) {
            if (beadFrames != null) {
                beadFrames = null;
            }
        }

        if (level == CLEAN_ALL || level == CLEAN_FOR_DELETION) {

            beadFrames = null;
            beadLocation = null;
            stackList = null;

            preview = null;
            segmentationMask = null;

        }

        if (level == CLEAN_FOR_DELETION) {
            stack = null;
            try {
                if (ipr != null) {
                    ipr.close();
                }
            } catch (Exception e) {

                e.printStackTrace();
            }
            ipr = null;

            stackList = null;
            beadLocation = null;
            beadFrames = null;

        }

        System.gc();

    }

    /**
     * Update bead counts.
     */
    public void updateBeadCounts() {

        if (beadFrames != null) {
            frameNumber = beadFrames.size();
            for (BeadFrame frame : beadFrames) {
                if (frame.isIgnored()) {
                    //ignoredFrameCount++;
                } else if (frame.isValid()) {

                }
            }
        }

    }

    /**
     * Gets the kepts beads count.
     *
     * @return the kepts beads count
     */
    public int getValidBeadCount() {
        return getBeadFrameList().getValidBeadFrameCount();
    }

    /**
     * Gets the paired bead count.
     *
     * @return the paired bead count
     */
    public int getPairedBeadCount() {
        return getBeadFrameList().getValidBeadFrames().getWithAlterEgo().size();
    }

    /**
     * Gets the total bead count.
     *
     * @return the total bead count
     */
    public int getTotalBeadCount() {
        return getBeadFrameList().size();
    }

    /**
     * The is processing.
     */
    boolean isProcessing;

    /**
     * Checks if is processing.
     *
     * @return true, if is processing
     */
    public boolean isProcessing() {
        // System.out.println("isProcessing : " + isProcessing);
        return isProcessing;
    }

    /**
     * Lock for processing.
     */
    public synchronized void lockForProcessing() {
        // System.out.println("Locking for processing");
        isProcessing = true;
    }

    /**
     * Sets the processing finished.
     */
    public synchronized void setProcessingFinished() {
        // System.out.println("Process finished");
        isProcessing = false;
    }

    /**
     * Gets the microscope.
     *
     * @return the microscope
     */
    public Microscope getMicroscope() {

        if (microscope == null) {
            microscope = Microscope.loadMicroscopeFromImage(fileAddress);
        }

        return microscope;
    }

    /**
     * Sets the microscope.
     *
     * @param microscope the new microscope
     */
    public void setMicroscope(Microscope microscope) {
        this.microscope = microscope;
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {

        String result = "******\n[Bead Image] " + stack;
        result += "\nfile : " + getImageName();
        result += "\nBead found : " + getBeadLocation().size();
        result += "\nFrame number : " + getBeadFrameList().size();
        result += "\nThreshold : " + getThresholdValue();
        result += "\nFrame size : " + getFrameSize();
        result += "\nFocus : " + getFocusPlane();
        result += "\n************";
        return result;
    }

    /**
     * Gets the counter.
     *
     * @return the counter
     */
    public Counter3D getCounter() {
        return counter3d;
    }

    /**
     * Reset preview.
     */
    public void resetPreview() {
        middleImage = null;
        preview = null;
        System.out.println("reseting bead preview");
        System.gc();
    }

    /**
     * Gets the map.
     *
     * @return the map
     */
    public ImageProcessor getMap() {

        ImageProcessor ip = getMiddleImage().convertToRGB();

        for (BeadFrame frame : getBeadFrameList()) {

            frame.draw(ip, 10);

        }

        return ip;

    }

    /**
     * Gets the beads color.
     *
     * @return the beads color
     */
    public Color getBeadsColor() {
        if (getMicroscope() == null) {
            return Color.black;
        }
        return VisibleColor
                .wavelengthToColor(getMicroscope().getWaveLength() * 1000);
    }

    /**
     * Gets the icon.
     *
     * @param size the size
     * @return the icon
     */
    public ImageIcon getIcon(int size) {
        ImageProcessor i = new ColorProcessor(size, size);
        i.setColor(getBeadsColor());
        i.fill();
        return new ImageIcon(i.getBufferedImage());

    }

    /**
     * Gets the zone.
     *
     * @param x the x
     * @param y the y
     * @param factor the factor
     * @return the zone
     */
    public Rectangle getZone(int x, int y, int factor) {

        int beadNumber = getBeadFrameList().getWithAlterEgo().size();
        int w = MathUtils.round(Math.sqrt(1.0 * getImageWidth()
                * getImageHeight() / beadNumber * 8));

        // int w = getImageWidth() / factor;
        int h = w; // getImageHeight() / factor;

        int x0 = x;
        int y0 = y;

        x = x0 * getImageWidth() / 2;
        y = y0 * getImageHeight() / 2;

        x = x - w / 2 * (x0 + 1);
        y = y - h / 2 * (y0 + 1);

        x = x + getImageWidth() / 2;
        y = y + getImageHeight() / 2;

        System.out.println(new Rectangle(x, y, w, h).toString()
                + String.format(" for %d,%d", x0, y0));

        return new Rectangle(x, y, w, h);

    }

    /**
     * Gets the corner name.
     *
     * @param x the x
     * @param y the y
     * @return the corner name
     */
    private String getCornerName(int x, int y) {

        String[] xString = {"LEFT", "CENTER", "RIGHT"};
        String[] yString = {"TOP", "CENTER", "BOTTOM"};

        return yString[y + 1] + " - " + xString[x + 1];

    }

    /**
     * Gets the montage.
     *
     * @return the montage
     */
    public ImageProcessor getMontage() {

        int beadPerCorner = 4;

        int beadNumber = getBeadFrameList().getWithAlterEgo().size();

        if (beadNumber < 4 * 9 * 2) {
            beadPerCorner = 1;
        }

        if (beadNumber < 18) {
            return getBeadFrameList().getValidBeadFrames().getBeadMontage();
        }

        int enlargementFactor = 3;

        int cornerWidth = MathUtils.round(Math.sqrt(beadPerCorner))
                * getBeadFrame(0).getWidth() * enlargementFactor;
        int cornerHeight = MathUtils.round(Math.sqrt(beadPerCorner))
                * getBeadFrame(0).getHeight() * enlargementFactor;
        int mw = cornerWidth * 3;
        int mh = cornerHeight * 3;

        ColorProcessor finalMontage = new ColorProcessor(mw, mh);

        for (int x = -1; x != 2; x++) {
            for (int y = -1; y != 2; y++) {

                int xf = (x + 1) * cornerWidth;
                int yf = (y + 1) * cornerHeight;
                ImageProcessor beadMontage = getBeadFrameList().getWithAlterEgo()
                        .getSampleFromCorner(getZone(x, y, 4), beadPerCorner)
                        .getBeadMontage();
                beadMontage = beadMontage.resize(beadMontage.getWidth() * enlargementFactor, beadMontage.getHeight() * enlargementFactor, true);
                finalMontage.copyBits(beadMontage, xf, yf, Blitter.COPY);

                finalMontage.setColor(Color.white.darker().darker());
                finalMontage.setFont(new Font(java.awt.Font.SANS_SERIF, java.awt.Font.CENTER_BASELINE, 10));
                finalMontage.drawString(getCornerName(x, y), xf + 5, yf + 16);

                finalMontage.setColor(Color.yellow.darker());
                finalMontage.drawRect((x + 1) * cornerWidth, (y + 1) * cornerHeight,
                        cornerWidth, cornerHeight);

            }
        }

        return finalMontage;

    }

    /**
     * Gets the distance threshold.
     *
     * @return the distance threshold
     */
    public double getDistanceThreshold() {
        return getMicroscope().getXYTheoreticalResolution() * 2;
    }

    /**
     * The bg mean.
     */
    double bgMean = -1;

    /**
     * The bg std dev.
     */
    double bgStdDev = -1;

    /**
     * The bead max intensity.
     */
    double beadMaxIntensity = -1;

    /**
     * The bead max standard deviation.
     */
    double beadMaxStandardDeviation = -1;

    /**
     * The bead mean b paramter.
     */
    double beadMeanBParamter = -1;

    /**
     * The bead mean b parameter std dev.
     */
    double beadMeanBParameterStdDev = -1;

    /**
     * The offset.
     */
    double offset = 0;

    /**
     * The invalidity reason.
     */
    private String invalidityReason;

    /**
     * Gets the signal mean.
     *
     * @return the signal mean
     */
    public double getSignalMean() {
        return beadMeanBParamter;
    }

    /**
     * Gets the signal standard deviation.
     *
     * @return the signal standard deviation
     */
    public double getSignalStandardDeviation() {
        calculateSignalAndNoise();
        return beadMeanBParameterStdDev;
    }

    /**
     * Gets the signal to noise ratio.
     *
     * @return the signal to noise ratio
     */
    public double getSignalToNoiseRatio() {
        calculateSignalAndNoise();

        return (getSignalMean() - getBackgroundMean()) / getBackgroundStandardDeviation();

    }

    /**
     * Gets the background mean.
     *
     * @return the background mean
     */
    public double getBackgroundMean() {
        calculateSignalAndNoise();
        return bgMean;
    }

    /**
     * Gets the background standard deviation.
     *
     * @return the background standard deviation
     */
    public double getBackgroundStandardDeviation() {
        calculateSignalAndNoise();
        return bgStdDev;
    }

    /**
     * Calculate signal and noise.
     */
    public void calculateSignalAndNoise() {
        if (bgMean == -1) {

			// ImageProcessor beadMask = getSegmentedImage().duplicate();
			// bgMask.invert();
            // middleImage.setMask(beadMask);
            DescriptiveStatistics stats = new DescriptiveStatistics();
            DescriptiveStatistics aStats = new DescriptiveStatistics();
            DescriptiveStatistics bStats = new DescriptiveStatistics();
            for (BeadFrame bead : getBeadFrameList().getOnlyValidBeads()) {
                stats.addValue(bead.getMaximumIntensity());
                aStats.addValue(bead.getFittingParameter(0, 0) - offset);
                bStats.addValue(bead.getFittingParameter(0, 1) - offset);

            }
            beadMaxIntensity = stats.getMean();
            beadMaxStandardDeviation = stats.getStandardDeviation();
            bgMean = aStats.getMean();
            bgStdDev = aStats.getStandardDeviation();
            beadMeanBParamter = bStats.getMean();
            beadMeanBParameterStdDev = bStats.getStandardDeviation();

            /*
             * beadMask.invert(); beadMask.dilate(); beadMask.dilate();
             * beadMask.dilate(); beadMask.dilate();
			 
             for (double i = 0; i != 5; i += 0.5) {
             ImageProcessor middleImage = getMiddleImage().duplicate();
             double threshold = getAutoThreshold(i);
             double realMean = middleImage.getStatistics().mean;
             double realStdDev = middleImage.getStatistics().stdDev;
				
             ImageProcessor bgMask = getSegmentationMask(middleImage,
             MathUtils.round(threshold));
             middleImage.setMask(bgMask);
				
				
             dataset.addValue("i", i);
             dataset.addValue("threshold",threshold);
             dataset.addValue("bgMean", value);
             dataset.addValue("bgStandard",gbS)
				
             bgMean = middleImage.getStatistics().mean;
             bgStdDev = middleImage.getStatistics().stdDev;
             System.out
             .println(String
             .format(
             "Signal to noise : \nMean : %.0f,\nBg. Mean : %.0f,\nDev : %.0f",
             beadMeanIntensity, bgMean, bgStdDev));
             new ImagePlus("bgMask", bgMask).show();
             }
             /*
             * middleImage.setMask(beadMask); bgMean =
             * middleImage.getStatistics().mean; bgStdDev =
             * middleImage.getStatistics().stdDev;
			 
             */
            System.out.println(String.format(
                    "** Old Method **\nMean : %.0f,\nBg. Mean : %.0f,\nDev : %.0f\nBMean : %.0f\n", beadMaxIntensity, bgMean, bgStdDev, beadMeanBParamter));
        }
    }

    /**
     * Gets the deleted beads count.
     *
     * @return the deleted beads count
     */
    public int getDeletedBeadsCount() {
        return getTotalBeadCount() - getValidBeadCount();
    }

    /**
     * Gets the invalidy reason.
     *
     * @return the invalidy reason
     */
    public String getInvalidyReason() {
        if (this.invalidityReason != null) {
            return this.invalidityReason;
        }
        return null;
    }

    /**
     * Sets the invalidity reason.
     *
     * @param reason the new invalidity reason
     */
    public void setInvalidityReason(String reason) {
        if (invalidityReason == null) {
            invalidityReason = reason;
        }
    }

    public int getMaxaximumIntensityOfTheWholeStack() {
		// TODO Auto-generated method stub

        if (maxIntensityOfWholeStack == NOT_SET) {

            DescriptiveStatistics stats = new DescriptiveStatistics();
            for (int i = 0; i != stack.getSize(); i++) {

                stats.addValue(stack.getProcessor(i + 1).getStatistics().max);

            }
            maxIntensityOfWholeStack = stats.getMax();

        }

        return MathUtils.round(maxIntensityOfWholeStack);
    }

    public void setIgnoredFrameNumber(int size) {
        ignoredFrameNumber = size;
    }

} public class BeadImageManager extends Observable implements Observer {

	/** The bead image list. */
	ArrayList<BeadImage> beadImageList = new ArrayList<BeadImage>();

	/** The wizard window2. */
	WizardWindow wizardWindow2;

	/** The calibration. */
	Calibration calibration;

	/** The status. */
	private String status;

	/** The progress. */
	private int progress;

	/** The runtime. */
	Runtime runtime = Runtime.getRuntime();

	/** The heat map step. */
	int heatMapStep = 20;

	/** The do export. */
	boolean doExport;

	/** The current column. */
	String currentColumn = "fwhmZ";

	/** The data set. */
	FovDataSet dataSet = new FovDataSet();

	/** The export directory. */
	private String exportDirectory;

	/** The automatic. */
	public static int AUTOMATIC = 0;

	/** The load on memory. */
	public static int LOAD_ON_MEMORY = 1;

	/** The load from disk. */
	public static int LOAD_FROM_DISK = 2;

	/** The reading lock. Used to read the imformations of only one image a time */
	private static Boolean readingLock = new Boolean(true);

	/** The loading lock. Used to load only one entire image at a time */
	private static Boolean loadingLock = new Boolean(true);

	// is ready
	boolean isReady = true;

	/** The graph hash. */
	public HashMap<String, PsfJGraph> graphHash;

	/**
	 * Fitting Threshold. All fitting presenting a R² inferior to this value
	 * will be ignored
	 **/
	double fittingThreshold = 0.80;

	/** hash map containing different channels */
	HashMap<Integer, Microscope> channels = new HashMap<Integer, Microscope>();

	/** The single channel analysis type. */
	public static int SINGLE_CHANNEL = 1;

	/** The dual channel analysis type. */
	public static int DUAL_CHANNEL = 2;

	/** The current type of the analysis (single or dual-channel) */
	protected int analysisType = DUAL_CHANNEL;

        
        public static int LOCATOR_2D = 0;
        public static int LOCATOR_3D = 1;
        
        
        protected BeadLocator locator = new BeadLocator2D();
        
        
	/**
	 * The main method.
	 * 
	 * @param args
	 *            the arguments
	 */
	public static void main(String[] args) {
		
		final BeadImageManager manager = new BeadImageManager();

		manager.add("/home/cyril/test_img/6/6_gfp.tif");
		manager.add("/home/cyril/test_img/6/6_mcherry.tif");
		
		// waiting for the images to load
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		
		System.out.println("let's go !");

		// autocalculating parameters
		for (BeadImage image : manager.getBeadImageList()) {
			image.autoThreshold();
			image.autoFrameSize();
		}
		
		// setting the export directory
		manager.setExportDirectory("/home/cyril/test_img/6/results/");
		
		// processing the psf
		manager.processProfiles();
		
		// exporting (the boolean parameter will prevent the document to open once finished)
		manager.exportPDFSumUp(false);
		manager.exportHeatMaps(false);
		manager.exportCSVFile(false);
		manager.exportInPDF(false);
		
		// printing the dataset
		System.out.println(manager.getDataSet().exportToString());
		
		
		// System.out.println("too soon ?");
	}

	/**
	 * Instantiates a new bead image manager.
	 */
	public BeadImageManager() {
		super();
	}

	/**
	 * Adds a beadImage object.
	 * 
	 * @param beadImage
	 *            the bead image
	 */
	public synchronized void add(BeadImage beadImage) {

		// add an bead image to the list
		beadImageList.add(beadImage);
                beadImage.setLocator(getLocator());
		if (exportDirectory == null) {
			exportDirectory = beadImage.getImageFolder();
		}
		setChanged();
		notifyObservers(new Message(this, "beadimage list modified"));

	}

	/**
	 * Adds a ImagePlus object as a bead image.
	 * 
	 * @param ipl
	 *            the ImagePlus to add
	 */
	public void add(ImagePlus ipl) {

		// creating a bead image object
		BeadImage beadImage = new BeadImage();

		// setting the image witdh and height of the bead image from the
		// ImagePlus object
		beadImage.imageWidth = ipl.getWidth();
		beadImage.imageHeight = ipl.getHeight();

		// adding the manager as observer of the bead image
		beadImage.addObserver(this);

		// setting the stack
		beadImage.setStack(ipl.getImageStack());

		// calculating the focus
		beadImage.autoFocus();
                
                beadImage.autoThreshold();
                
		// setting the name from the bead iamge
		beadImage.setImageName(ipl.getShortTitle());

		// trying to determine the bead image
		beadImage.setFileAddress(PSFj.getDefaultHomeDirectory()
				+ File.separatorChar + "PSFj" + File.separatorChar
				+ ipl.getTitle());

		// adding the bead image to the list
		add(beadImage);

	}

	/** int used to determine when comes the next warning check. */
	int imagesYetToLoad = 0;

	public void addAsync(File[] files) {
		for (File f : files) {
			addAsync(f.getAbsolutePath());
		}
	}

	/**
	 * Adds the async.
	 * 
	 * @param fileAddress
	 *            the file address
	 */
	public void addAsync(final String fileAddress) {
		new Thread() {
			public void run() {
				add(fileAddress);
			}
		}.start();
	}

	/**
	 * Adds the image stack from a complete address.
	 * 
	 * @param fileAddress
	 *            the image stack file address
	 */
	private String checkImage(String fileAddress) {

		// flag determining if the image is valid or not
		boolean isValid = true;

		// no reason for now
		String reason = null;

		// trying to open the image
		ImageProcessorReader ipr = new ImageProcessorReader(
				new ChannelSeparator(LociPrefs.makeImageReader()));

		try {
			ipr.setId(fileAddress);

		}
		// if the format cannot be read
		catch (FormatException e) {

			e.printStackTrace();
			try {

				//
				ipr.close();
				reason = "Error when adding image.";
			}
			// if the file cannot be accessed
			catch (IOException e1) {

				e1.printStackTrace();
			}
			isValid = false;
			
			// if no reason has been set
			if (reason == null)
				reason = "PSFj doesn't support this type of image.";

		}
		// don't know why there are two catching of IO exception but let's let
		// it like this
		catch (IOException e) {
			e.printStackTrace();
			
			// if no reason has been set
			if (reason == null)
				reason = "Error when reading the image.";
			isValid = false;
		}

		System.out.println(isValid);
		// the image has been opened, getting informations about the image;
		if (isValid == true) {

			int width = ipr.getSizeX();
			int height = ipr.getSizeY();
			int numChannel = ipr.getSizeC();
			int bitsPerPixel = ipr.getBitsPerPixel();

			// checking file size
			long fileSize = new File(fileAddress).length() / 1000 / 1000;

			long imageAmount = 0;

			for (BeadImage image : getBeadImageList()) {
				imageAmount += image.getImageDiskSize();
			}

			if (imageAmount + fileSize > MemoryUtils.getTotalMemory() * 0.55) {
				isValid = false;

				return String.format("You can only load %dMB of stacks.",
						MemoryUtils.getTotalMemory() * 60 / 100);
			}

			// checking the channel number
			if (numChannel > 2) {
				if (reason == null)
					reason = "Only monochannel image can be added.";
				isValid = false;
			}

			// checking the bit size
			if (bitsPerPixel > 16) {
				isValid = false;
				if (reason == null)
					reason = "Only 8-bits and 16-bits tif images are supported.";
			}

			// checking if the image has not been added already
			for (BeadImage image : getBeadImageList()) {
				if (image.getFileAddress().equals(fileAddress)) {
					isValid = false;
					if (reason == null)
						reason = "You cannot add the same image twice.";
				}
			}

			// checking if the size is not wrong
			if (width <= 0) {
				isValid = false;
				if (reason == null)
					reason = "Error when reading the image. The stack may be corrupted";
			}
			
			// getting the first image ever added
			if (getBeadImageCount() > 0)
				System.out.println("first one = "
						+ getBeadImage(0).getImageWidth());

			// checking if the image is the same size as the other
			if (getBeadImageCount() > 0
					&& (getBeadImage(0).getImageWidth() != width || getBeadImage(
							0).getImageHeight() != height)) {

				if (reason == null)
					reason = "Please add an image stacks of the same size.";
				isValid = false;
			}

			try {
				ipr.close();
			} catch (IOException e) {

				e.printStackTrace();
			}

		}

		return reason;

	}

	// checking for possible warnings (not errors)
	public void checkForWarnings() {
		
		// checking if bead images has different wavelengths;
		
		System.out.println("Checking for warnings : " + imagesYetToLoad);
		
		// if no image in the list, no need to continue
		if (countBeadImage() == 0)
			return;
		
		// if no image are yet to load, no need to continue
		if (imagesYetToLoad > 0)
			return;

		// we assume all the images have different wavelength
		boolean hasDifferentWavelengths = false;
		
		// getting the wavelength of the first image
		double firstWaveLength = getBeadImage(0).getMicroscope()
				.getWaveLength();

		// if the number of image stacks is greated than 2
		if (countBeadImage() >= 2) {
			// for each image of the list
			for (BeadImage image : getBeadImageList()) {
				
				// getting the wavelength
				double wavelength = image.getMicroscope().getWaveLength();
				
				// if this wavelength is different from the first wavelength
				if (wavelength > 0 && wavelength != firstWaveLength) {
					
					// then this image is said to have a different wavelength from the first
					image.setStatus("Wavelength different from the first.");
					
					// the different wavelength flag is set
					hasDifferentWavelengths = true;
					
					// the loop is broken
					break;
				}
			}
			
			// if there is different wavelengths among the bead images and if it's a single color analysis
			// then the user should be warned that one image may override the other image parameters.
			if (hasDifferentWavelengths && !isDualColorAnalysis())
				notifyWarning("You added images from different wavelengths.\nContinuing a Single-Color analysis will override\nthe image parameters.");

		}

	}

	// notify a warning to the view
	private void notifyWarning(String message) {

		setChanged();
		notifyObservers(new Message(this, "warning", message));
	}

	// add an image from the file address
	public void add(String fileAddress) {

		// the bead image
		BeadImage beadImage = null;
		
		// assuming the bead image is valid
		boolean isValid = true;
		
		// possible problem encountered during loading
		String problem;

		// image yet to load is incremented (this method can be called simoustaneouly so precaution must be taken);
		imagesYetToLoad++;
                
		// making sure that only one image is analyzed at once
		synchronized (readingLock) {
			
			// checking the possible probrems (it returns null if no problem)
			problem = checkImage(fileAddress);
			
			// if no problem has been detected
			if (problem == null) {
				//creating a new bead image
				beadImage = new BeadImage();
				
                                
                                
				// setting the image as valid
				beadImage.isOpening = true;
				
				// setting the file address in the bead image
				beadImage.setFileAddress(fileAddress);
				
				// setting the manager as observer
				beadImage.addObserver(this);
				add(beadImage);
				
				// read the bead image properties from the disk (width, height etc.)
				beadImage.readWidthAndHeightFromDisk();
                                
                                beadImage.isOpening = false;

			} else {
				// display the problem
				System.err.println(problem);
				setChanged();
				notifyObservers(new Message(this, "error", problem));
				
				// setting as invalid
				isValid = false;

			}
			imagesYetToLoad--;
			if (!isValid)
				return;

			// if the wavelength of the new image is different, the mode is
			// switchback
			// to multicolor
			if (isValid
					&& getBeadImageCount() == 2
					&& beadImage.getMicroscope().getWaveLength() != getBeadImage(
							0).getMicroscope().getWaveLength()
					&& beadImage.getMicroscope().getWaveLength() > 0) {
				setAnalysisType(DUAL_CHANNEL);
			}

			else {
				setAnalysisType(SINGLE_CHANNEL);
			}

		}
		
		// checking for warnings like different wavelengths or formats
		checkForWarnings();

		if (!isValid)
			return;

		// if the export directory has not been set, the folder that containts the image is set as default
		if (exportDirectory == null)
			exportDirectory = beadImage.getImageFolder();
		
		// loading the image in memory (using a lock to avoid loading multiple images at the same time
		synchronized (loadingLock) {
			
			isReady = false;
			try {
				// loading the image from memory
				beadImage.workFromMemory();
			}
			// shouldn't happen but we never know
			catch (OutOfMemoryError e) {
				e.printStackTrace();
				notifyObservers(new Message(this, "error",
						"Memory overloaded !"));
			}
			
			// any other error
			catch (Exception e) {
				e.printStackTrace();
			}
			isReady = true;
		}
		
		// the image has been loaded, let's notify the observers
		setChanged();
		notifyObservers(new Message(this, "image loaded"));

	}

	/**
	 * Gets the bead image object from it's position in the bead image list
	 * 
	 * @param i
	 *            the id of the image in the list
	 * @return the bead image
	 */
	public BeadImage getBeadImage(int i) {

		if (i == -1)
			i++;

		if (i < beadImageList.size() && i >= 0) {
			return beadImageList.get(i);
		} else {
			System.out.println("Couln't find bead image number " + i + ".");
			return null;
		}
	}

	/**
	 * Returns the number of bead images that the manager contains
	 * 
	 * @return the image stack count
	 */
	public int countBeadImage() {
		return beadImageList.size();
	}

	/**
	 * Removes the bead image which has the id "i"
	 * 
	 * @param i
	 *            the id of the image that must be removed
	 */
	public void remove(int i) {
		if (i < beadImageList.size()) {
			System.out.println("Removing number " + i);
			remove(getBeadImage(i));

		} else {
			System.out.println("Couln't delete this item. It doesn't exist : "
					+ i);
		}
	}

	/**
	 * Removes the specified bead image object.
	 * 
	 * @param beadImage
	 *            the bead image
	 */
	public void remove(BeadImage beadImage) {
		if (beadImageList != null) {
			beadImageList.remove(beadImage);
		}
		
		// remove all observers of this image (for garbage collection)
		beadImage.deleteObservers();

		//System.out.println(beadImageList.size());
		// System.out.println(beadImageList.get(0));

		// reseting the dataset
		dataSet = new FovDataSet();
		
		// reseting the export directory
		exportDirectory = null;

		// cleaning the bead image from the memory
		beadImage.cleanMemory(BeadImage.CLEAN_FOR_DELETION);
		
		// reseting the microscope settings
		if (countBeadImage() == 2
				&& getBeadImage(0).getMicroscope().getWaveLength() != getBeadImage(
						1).getMicroscope().getWaveLength()) {
			setAnalysisType(DUAL_CHANNEL);
		}
		
		// invoking garbage collection
		System.gc();
		
		setChanged();
		notifyObservers(new Message(this, "beadimage list modified"));
	}

	/**
	 * Gets the bead image object list.
	 * 
	 * @return the bead image list
	 */
	public ArrayList<BeadImage> getBeadImageList() {
		return beadImageList;
	}

	/**
	 * Gets the image list in a string form.
	 * 
	 * @return the image string list
	 */
	public ArrayList<String> getImageList() {

		ArrayList<String> imageList = new ArrayList<String>();
		for (BeadImage b : getBeadImageList()) {
			imageList.add(b.getImageName());
			System.out.println(b.getImageName());
		}
		return imageList;
	}

	/**
	 * Gets the progress of the ongoing process.
	 * 
	 * @return the progress
	 */
	public int getProgress() {
		return progress;
	}

	/**
	 * Gets the status of the ongoing process.
	 * 
	 * @return the status
	 */
	public String getStatus() {
		return status;
	}

	/**
	 * Gets the microscope of the image with the specified id
	 * 
	 * @param id
	 *            the id of the image
	 * @return the microscope configuration
	 */
	public Microscope getMicroscope(int id) {
		if (getBeadImage(id) != null)
			return getBeadImage(id).getMicroscope();
		else
			return null;
	}

	
	/**
	 * Sets the microscope configuration for the specified image
	 * 
	 * @param beadImageId
	 *            the bead image id
	 * @param microscope
	 *            the new microscope
	 */
	public void setMicroscope(int beadImageId, Microscope microscope) {
		getBeadImage(beadImageId).setMicroscope(microscope);
	}

	/*
	 * Processes the message coming from different sources
	 */
	@Override
	public void update(Observable o, Object data) {
		
		
		// if the data sent are simple objects
		if (data instanceof Integer) {
			progress = (Integer) data;
		}
		if (data instanceof String) {
			status = (String) data;
		}

		//if the sent data is a Message object
		if (data instanceof Message) {
			Message message = (Message) data;
			
			// other channel detected (when adding an image stack that contains both channels)
			if (message.getName().contains("other channel detected")) {
				
				BeadImage image = (BeadImage) message.getData();
				add(image);
				return;

			}
			
			// Bead Image processor ? progress information
			if (message.getOrigin() instanceof BeadFrameProcessor
					&& message.getName() == "progress") {
				BeadImage image = (BeadImage) message.getData();
				if (image != null)
					image.setProgress(message.getIntData(),
							message.getStringData());
			}
			
			// image is not okay, let's remove it :-)
			if (message.getName().contains("image not okay")) {
				remove((BeadImage) message.getOrigin());
				setChanged();
				notifyObservers(new Message(this, "image not okay"));
				return;
			}

		}
		
		setChanged();
		notifyObservers(data);
	}

	/**
	 * Calculate the focus plane for a given bead image.
	 * 
	 * @param beadImageId
	 *            the bead image id
	 */
	public void autoFocus(int beadImageId) {
		if (beadImageId >= 0 && beadImageId < countBeadImage()) {
			getBeadImage(beadImageId).autoFocus();
		}
	}

	/**
	 * Sets the focus plane for the specified bead image image.
	 * 
	 * @param beadImageId
	 *            the bead image id
	 * @param focus
	 *            the focus plane
	 */
	public void setFocusPlane(int beadImageId, int focus) {
		if (beadImageId >= 0 && beadImageId < countBeadImage()) {
			getBeadImage(beadImageId).setFocusPlane(focus);
		}
	}

	/**
	 * Gets the focus point.
	 * 
	 * @param beadImageId
	 *            the bead image id
	 * @return the focus point
	 */
	public int getFocusPlane(int beadImageId) {
		if (beadImageId >= 0 && beadImageId < countBeadImage()) {
			return getBeadImage(beadImageId).getFocusPlane();
		}
		return -2;
	}

	/**
	 * Gets the threshold value set for the bead image
	 * 
	 * @return the threshold value
	 */
	public int getThresholdValue() {
		if (countBeadImage() > 0)
			return getBeadImage(0).getThresholdValue();
		else
			return 0;
	}

	/**
	 * Gets the frame size
	 * 
	 * @return the frame size
	 */
	public int getFrameSize() {
		if (countBeadImage() > 0)
			return getBeadImage(0).getFrameSize();
		else
			return 0;
	}

	
	/**
	 * Sets the threshold value.
	 * 
	 * @param threshold
	 *            the new threshold value
	 */
	public void setThresholdValue(int threshold) {
		for (BeadImage beadImage : beadImageList) {

			System.out.println("** setting threshold to " + threshold);
			beadImage.setThresholdValue(threshold);
		}
	}

	/**
	 * Sets the threshold value.
	 * 
	 * @param beadImageId
	 *            the bead image id
	 * @param thresholdValue
	 *            the threshold value
	 */
	public void setThresholdValue(int beadImageId, int thresholdValue) {
		if (getAnalysisType() == SINGLE_CHANNEL) {
			setThresholdValue(thresholdValue);
		} else {
			getBeadImage(beadImageId).setThresholdValue(thresholdValue);
		}
	}

	/**
	 * Sets the bead enlargement.
	 * 
	 * @param enlargement
	 *            the new bead enlargement
	 */

	public void setFrameSize(int enlargement) {
		for (BeadImage beadImage : beadImageList) {
			beadImage.setFrameSize(enlargement);
		}
	}

	/**
	 * Sets the frame size.
	 * 
	 * @param beadImageId
	 *            the bead image id
	 * @param frameSize
	 *            the frame size in pixels
	 */
	public void setFrameSize(int beadImageId, int frameSize) {
		if (getAnalysisType() == SINGLE_CHANNEL) {
			setFrameSize(frameSize);
		} else {
			getBeadImage(beadImageId).setFrameSize(frameSize);
		}
	}

	/**
	 * Gets the bead frame size factor.
	 * 
	 * @return the bead frame size factor
	 */
	public int getBeadEnlargementFactor() {
		if (countBeadImage() > 0)
			return getBeadImage(0).getBeadEnlargementFactor();
		else
			return 0;
	}

	
	
	/**
	 * Sets the bead enlargement factor.
	 * 
	 * @param enlargement
	 *            the new bead enlargement factor
	 */
	public void setBeadEnlargementFactor(int enlargement) {
		for (BeadImage beadImage : beadImageList) {
			beadImage.setBeadEnlargementFactor(enlargement);
		}
	}

	/**
	 * Update the preview image asynchronously.
	 */
	public void updatePreviewAsync() {
		for (BeadImage beadImage : beadImageList) {
			beadImage.getBeadPreviewAsync();
		}
	}

	/**
	 * If the bead image focus plane and bead enlargement are not set, it launch
	 * the automated calculations.
	 */
	public synchronized void verifyBeadImageParameters() {

		// If one of them is not set
		for (BeadImage image : getBeadImageList()) {

			// verifying focus
			if (image.getFocusPlane() == BeadImage.NOT_SET) {
				// autofocus async is launched
				if (image.isProcessing() == false)
					;
				image.autoFocusAsync();
			}

			// values used on monocolor analysis
			int firstThreshold = getBeadImage(0).getThresholdValue();
			int firstFrameSize = getBeadImage(0).getFrameSize();

			// verifying threshold value
			if (image.getThresholdValue() == BeadImage.NOT_SET) {
				if (isDualColorAnalysis()) {
					image.autoThreshold();
				} else {
					if (firstThreshold == BeadImage.NOT_SET) {
						firstThreshold = getBeadImage(0).autoThreshold();
					}

					image.setThresholdValue(firstThreshold);

				}
			}

			// verifying frame size

			if (image.getFrameSize() == BeadImage.NOT_SET) {

				if (firstFrameSize == BeadImage.NOT_SET) {
					firstFrameSize = getBeadImage(0).autoFrameSize();
				}
				if (isDualColorAnalysis()) {
					image.autoFrameSize();
				} else
					image.setFrameSize(firstFrameSize);

			}

		}

		setChanged();
		notifyObservers(new Message(this, "verification over"));

	}

	/**
	 * Auto threshold.
	 * 
	 * @param i
	 *            the i
	 * @return the int
	 */
	public synchronized int autoThreshold(int i) {

		// does automatic threshold for all images

		if (getBeadImageCount() == 0)
			return 0;
		
		// if single color analysis
		if (getAnalysisType() == SINGLE_CHANNEL) {
			
			// auto threshold for each one
			for (BeadImage image : getBeadImageList()) {
				image.autoThreshold();
			}
			
			//but returns the first image threshold value
			return getBeadImage(0).getThresholdValue();

		} else {
			// or return the threshold of the specified image
			return getBeadImage(i).autoThreshold();
		}
	}

	/**
	 * Gets the bead image count. Obviously a duplicated method... but let it here for now
	 * 
	 * @return the bead image count
	 */
	private int getBeadImageCount() {
		return getBeadImageList().size();
	}

	/**
	 * Calculates and returns the automatic threshold for all the images
	 * 
	 * @return the int
	 */
	public synchronized int autoThreshold() {

		return autoThreshold(0);

	}

	/**
	 * Gets the data set of the field of view
	 * 
	 * @return the data set
	 */
	public FovDataSet getDataSet() {
		return dataSet;
	}

	/**
	 * Gets the z profile mean.
	 * 
	 * @param channel
	 *            the channel
	 * @return the z profile mean
	 */
	public double getZProfileMean(int channel) {

		double mean;
		
		
		if (isDualColorAnalysis()) {
			
			// the mean comes is taken from from the specified image
			mean = getBeadImage(channel)
					.getBeadFrameList()
					.getDataSet(true)
					.getColumnStatistics(
							PSFj.getColumnID(PSFj.Z_PROFILE,
									PSFj.NOT_NORMALIZED)).getMean();
		}
		
		
		// if is single color analysis, the mean comes from all images mixed together.
		else {

			mean = getMergedDataSet(true).getColumnStatistics(
					PSFj.getColumnID(PSFj.Z_PROFILE, PSFj.NOT_NORMALIZED))
					.getMean();
		}
		
		// displaying the z profile mean
		System.out.println(String.format(
				"z profile mean for channel %d = %.3f", channel, mean));
		return mean;
	}

	/**
	 * Gets the total amount of beads kept after filtering
	 * 
	 * @return the total amount of beads kept after filtering
	 */
	public int getKeptBeadsCount() {
		int keptBeadsCount = 0;
		// summing up all images kept bead count
		for (BeadImage image : getBeadImageList()) {
			keptBeadsCount += image.getValidBeadCount();
		}
		return keptBeadsCount;
	}

	/**
	 * Gets the analyzed bead count.
	 * 
	 * @return the analyzed bead count
	 */
	public int getAnalyzedBeadCount() {
		return getDeletedBeads() + getKeptBeadsCount();
	}

	/**
	 * Process profiles for each bead of each image stack.
	 */
	public void processProfiles() {
		
		// process the bead images
		processBeadImages();
		
		// if single analysis the dataset are merged to one to get more information about the field of view
		if (getAnalysisType() == SINGLE_CHANNEL) {
			mergeDataSets();
		}
		
		// in case of dual color analysis the dataset are compared
		else if (getAnalysisType() == DUAL_CHANNEL) {
			compareDataSets();
		}
		
		// the graph list are built
		buildGraphList();

		// if exporting has been set
		if (doExport)
			export();
		try {
			setChanged();
			notifyObservers(new Message(this, "process finished",
					"process finished"));
		} catch (NullPointerException e) {
			e.printStackTrace();
		}
	}

	/** The graph list. */
	ArrayList<PsfJGraph> graphList;

	/**
	 * Builds the graph list.
	 */
	protected void buildGraphList() {
		
		// resetting the graph list and array
		graphList = new ArrayList<PsfJGraph>();
		graphHash = new HashMap<String, PsfJGraph>();
		
		
		String progressText = "Building heatmaps...";
		int progress = 10;
		System.gc();
		setProgress(progressText, progress += 10);
		
		// the graphs are different depending on the analysis type
		if (getAnalysisType() == DUAL_CHANNEL) {

			int channelId = 0;

			for (BeadImage image : getBeadImageList()) {

				// Getting the dataset for only the valid beads
				FovDataSet dataSet = image.getBeadFrameList()
						.getOnlyValidBeads().getDataSet(true, false);

				System.out.println(dataSet);
				setProgress(progressText, 30 + channelId * 20);
				// for each axe x,y,z
				for (int axe : PSFj.AXES) {

					// id used to retrieve the informations
					String columnId = PSFj.getColumnName(PSFj.FWHM_KEY, axe,
							PSFj.NOT_NORMALIZED);

					System.out.println(columnId);

					// id used to name the heatmap
					String heatmapId = PSFj.getHeatmapName(PSFj.FWHM_KEY, axe,
							channelId);

					System.out.println(columnId);
					// We create a FWMH Heatmap
					FWHMHeatMap heatmap = new FWHMHeatMap(dataSet, image,
							columnId);

					// Setting the title
					heatmap.setTitle(dataSet.getColumnName(columnId) + " of "
							+ image.getImageName());

					// Adding to the graph list and map
					graphList.add(heatmap);
					heatmap.getGenerator(PSFj.NOT_NORMALIZED)
							.getAnnotatedHeatMap();
					graphHash.put(heatmapId, heatmap);

				}

				// Generating ZProfile heatmap
				ZProfileHeatMap heatmap = new ZProfileHeatMap(dataSet, image);

				heatmap.setTitle("Planarity of " + image.getImageName());

				graphList.add(heatmap);
				
				graphHash.put(PSFj.getHeatmapName(PSFj.Z_PROFILE, channelId),
						heatmap);
				
				
				setProgress(progressText, 40 + channelId * 20);
				
				// Generating Asymetry heatmap
				FullHeatMap asymetryMap = new AsymmetryHeatMap(dataSet,
						getBeadImage(channelId), PSFj.getColumnID(
								PSFj.ASYMMETRY_KEY, PSFj.NOT_NORMALIZED, 0));
				// Theta map
				FullHeatMap thetaMap = new ThetaHeatMap(dataSet,
						getBeadImage(channelId), PSFj.getColumnID(
								PSFj.THETA_KEY, PSFj.NOT_NORMALIZED, 0));

				graphHash.put(
						PSFj.getHeatmapName(PSFj.ASYMMETRY_KEY, channelId),
						asymetryMap);
				graphHash.put(PSFj.getHeatmapName(PSFj.THETA_KEY, channelId),
						thetaMap);

				graphList.add(asymetryMap);
				graphList.add(thetaMap);

				channelId++;
			}

			
			FovDataSet dataSet = getBeadImage(0).getBeadFrameList()
					.getWithAlterEgo().getDataSet(true, true);
			BeadImage image = getBeadImage(0);
			Microscope m = image.getMicroscope();

			// Creating the chromatic shift heatmap ( use only x,y shift for
			// distance calculation )
			DistanceHeatMap distanceHeatMap = new DistanceHeatMap(dataSet,
					getBeadImage(0), PSFj.getColumnID(PSFj.CHR_SHIFT_XY,
							PSFj.NOT_NORMALIZED),
					m.getXYTheoreticalResolution());
			distanceHeatMap.setManager(this);
			// Creating the chromatic shift heatmap that use x,y,z shifts
			DistanceHeatMap distanceHeatMap3d = new DistanceHeatMap(dataSet,
					image, PSFj.getColumnID(PSFj.CHR_SHIFT_XYZ,
							PSFj.NOT_NORMALIZED), m.getZTheoreticalResolution());
			distanceHeatMap3d.setManager(this);
			// adding them to list and hash
			graphList.add(distanceHeatMap);
			graphHash.put(PSFj.getHeatmapName(PSFj.CHR_SHIFT_XY, -1),
					distanceHeatMap);

			graphList.add(distanceHeatMap3d);
			graphHash.put(PSFj.getHeatmapName(PSFj.CHR_SHIFT_XYZ, -1),
					distanceHeatMap3d);
			setProgress(progressText, 80);
			// Taking of shift heatmaps (x, y, z)
			for (int axe : PSFj.AXES) {

				String columnName = PSFj.getColumnID(PSFj.CHR_SHIFT_KEY, axe,
						PSFj.NOT_NORMALIZED, 0);
				String heatmapName = PSFj.getHeatmapName(PSFj.CHR_SHIFT_KEY,
						axe, channelId);

				ShiftHeatMap shiftHeatmap = new ShiftHeatMap(dataSet, image,
						columnName, m.getTheoreticalResolution(axe));

				shiftHeatmap.setManager(this);

				graphList.add(shiftHeatmap);
				graphHash.put(heatmapName, shiftHeatmap);
			}
			BeadMontage montage = new BeadMontage(this);
			graphList.add(montage);
			graphHash.put(PSFj.BEAD_MONTAGE_KEY, montage);

		}

		
		else {

			progress = 20;

			setProgress(progressText, progress += 15);
			for (int axe : PSFj.AXES) {
				mergeDataSets();

				String columnName = PSFj.getColumnID(PSFj.FWHM_KEY, axe,
						PSFj.NOT_NORMALIZED, 0);
				String heatmapName = PSFj
						.getHeatmapName(PSFj.FWHM_KEY, axe, -1);

				FWHMHeatMap heatmap = new FWHMHeatMap(dataSet, getBeadImage(0),
						columnName);
				graphList.add(heatmap);
				graphHash.put(heatmapName, heatmap);
				setProgress(progressText, progress += 15);
			}

			// z profile
			ZProfileHeatMap heatmap = new ZProfileHeatMap(dataSet,
					getBeadImage(0));
			heatmap.setTitle("Planarity");
			graphList.add(heatmap);
			graphHash.put(PSFj.getHeatmapName(PSFj.Z_PROFILE, -1), heatmap);
			setProgress(progressText, progress += 15);

			// Asymmetry
			FullHeatMap asymetryMap = new AsymmetryHeatMap(dataSet,
					getBeadImage(0), PSFj.getColumnID(PSFj.ASYMMETRY_KEY,
							PSFj.NOT_NORMALIZED, 0));
			setProgress(progressText, progress += 15);

			// theta
			FullHeatMap thetaMap = new ThetaHeatMap(dataSet, getBeadImage(0),
					PSFj.getColumnID(PSFj.THETA_KEY, PSFj.NOT_NORMALIZED, 0));

			graphHash.put(PSFj.getHeatmapName(PSFj.ASYMMETRY_KEY, -1),
					asymetryMap);
			graphHash.put(PSFj.getHeatmapName(PSFj.THETA_KEY, -1), thetaMap);

			setProgress(progressText, progress += 15);

			graphList.add(asymetryMap);
			graphList.add(thetaMap);
			setProgress(progressText, progress += 15);
		}

		for (BeadImage image : getBeadImageList()) {
			graphList.add(new BeadMap(image));
		}

		System.gc();

		setProgress("", 0);
	}

	/**
	 * Gets the graph.
	 * 
	 * @param id
	 *            the id
	 * @return the graph
	 */
	public PsfJGraph getGraph(String id) {
		return getGraphHash().get(id);
	}

	/**
	 * Gets the graph hash.
	 * 
	 * @return the graph hash
	 */
	public HashMap<String, PsfJGraph> getGraphHash() {
		if (graphHash == null) {
			buildGraphList();
		}

		return graphHash;

	}

	/**
	 * Gets the graph list.
	 * 
	 * @return the graph list
	 */
	public ArrayList<PsfJGraph> getGraphList() {
		if (graphList == null) {
			buildGraphList();
		}

		return graphList;

	}

	/**
	 * Process bead images one by one
	 */
	protected void processBeadImages() {
		
		// reseting the common dataset
		dataSet = new FovDataSet();
		
		graphList = null;
		System.gc();
		
		// setting all the bead image status to pending
		for (BeadImage beadImage : getBeadImageList()) {
			beadImage.setProgress(0, "Pending...");
		}
		
		for (BeadImage beadImage : getBeadImageList()) {
			
			//creating a async processing for better performances
			BeadFrameProcessor processor = new BeadFrameProcessorAsync(
					beadImage.getBeadFrameList());

			System.out.println(beadImage.getBeadFrameList().size());
			// adding the manager as observer
			processor.addObserver(this);
			
			//setting the fitting threshold (even through it's not really used...)
			processor.setFittingThreshold(fittingThreshold);
			
			// processing the beads
			processor.process();
			
			// filtering the beads
			processor.filter();
			
			processor.deleteObserver(this);
			
			//done
			beadImage.setProgress(100, "Done.");

		}
	}

	/**
	 * Compares the data sets in case of dual color analysis
	 */
	public void compareDataSets() {
		
		// merge first data set if really it's not a dual color analysis but this case shouldn't happen
		System.out.println("Data Set List : " + getBeadImageCount());
		if (getBeadImageCount() != 2) {
			mergeDataSets();
			return;
		}
		
		
		//getting two bead images
		BeadImage image1 = getBeadImage(0);
		BeadImage image2 = getBeadImage(1);
		
		// stats
		DescriptiveStatistics deltaDstats = new DescriptiveStatistics();

		BeadFrame closestBead;
		BeadFrame competitor;
		
		
		double distance;
		double competitorDistance;

		dataSet = new FovDataSet();

		int max = image1.getBeadFrameList().getValidBeadFrameCount();
		int count = 0;

		// Pairing beads with their nearest neighbour in the other channel
		for (BeadFrame bead : image1.getBeadFrameList().getOnlyValidBeads()) {

			if (count % 10 == 0) {
				setProgress("Comparing beads...", count * 100 / max);
			}
			count++;
			
			// getting the closest bead in the other channel
			closestBead = bead.getClosestBead(image2.getBeadFrameList()
					.getOnlyValidBeads());
			
			// if the closest doesn't exists (thresholdwise)
			if (closestBead == null) {
				continue;
			}
		
			else {
				// getting the distance
				distance = bead.getDistance(closestBead);
				
				if (closestBead.getAlterEgo() != null) {
					// if a bead has already been assigned the closest bead, it's a competitor.
					// Let's see which one is the closest.
					competitor = closestBead.getAlterEgo();
					
					// distances
					distance = bead.getDistance(closestBead);
					competitorDistance = closestBead.getDistance(competitor);
					
					// if the distance is less than the competitor, then this bead is the best match for the corresponding one
					if (distance < competitorDistance) {
						// System.out.println("* Competitor : OUT !!");
						
						// they are associated
						bead.setAlterEgo(closestBead);
						closestBead.setAlterEgo(bead);
						
						// the competitor is out
						competitor.resetAlterEgo();
						
						// and we explain to him why :-)
						competitor
								.setInvalidityReason("A closer corresponding bead exists : n°"
										+ bead.getId());
						deltaDstats.addValue(distance);
					}
					// if not, the competor wins and the current beads is left out.
					else {
						bead.resetAlterEgo();
						bead.setInvalidityReason("A closer corresponding bead exists : n°"
								+ competitor.getId());
					}

				}
				// if no competitor, then it's perfect
				else {
					bead.setAlterEgo(closestBead);
					closestBead.setAlterEgo(bead);
					deltaDstats.addValue(bead.getDistance(closestBead));
				}
			}
		}
		
		
		// forgot what it is
		double deltaD;

		// deleting beads too far away from their corresponding beads
		double threshold = getDistanceThreshold();// deltaDmedian
													// +
													// deltaDstdDev;
		count = 0;
		
		max = image1.getValidBeadCount();
		
		
		// for each bead that has a alter ego (corresponding bead in the other channel)
		for (BeadFrame bead : image1.getBeadFrameList().getOnlyValidBeads()
				.getWithAlterEgo()) {

			String reason = "";
			// the distance between those beads
			deltaD = bead.getDistance(bead.getAlterEgo());
			
			//if this distance is greater than the threshold, the bead is burned out
			if (deltaD > threshold) {
				reason = String
						.format("Corresponding bead too far away : %.3f µm (limit : %.3f µm)",
								deltaD, threshold);

				bead.getAlterEgo().setInvalidityReason(reason);
				bead.getAlterEgo().resetAlterEgo();
				bead.setInvalidityReason(reason);
				// bead.setValid(false,reason);
				bead.resetAlterEgo();
			}
			
			// refreshing some progress bar
			count++;
			if (count % 10 == 0) {
				setProgress("Comparing beads...", count * 100 / max);
			}

		}
		
		// just adding those information to the main data set (but is not really used)
		dataSet.mergeDataSet(image1.getBeadFrameList().getWithAlterEgo()
				.getDataSet());

	}

	/**
	 * Gets the merged data set of all beads
	 * 
	 * @param excludeInvalidBeads
	 *            the exclude invalid beads if true
	 * @return the merged dataset
	 */
	public FovDataSet getMergedDataSet(boolean excludeInvalidBeads) {
		// other metadata are saved
		FovDataSet dataSet = new FovDataSet();

		FovDataSet validBeadsDataSet = new FovDataSet();

		for (BeadImage image : getBeadImageList()) {
			dataSet.mergeDataSet(
					image.getBeadFrameList().getDataSet(excludeInvalidBeads,
							false), true);
			validBeadsDataSet.mergeDataSet(
					image.getBeadFrameList().getDataSet(true, false), true);
			System.out.printf("Merging Data sets <- %d\n",
					dataSet.getColumnSize());
		}

		return dataSet;
	}

	/**
	 * Merge data sets.
	 */
	public void mergeDataSets() {
		
		setDataSet(getMergedDataSet(true));

	}

	/**
	 * Compile the final data set.
	 */
	public void compileFinalDataSet() {
		for (BeadImage image : getBeadImageList()) {

			BeadFrameList list = image.getBeadFrameList();

			dataSet.setMetaDataValue(image.getImageName() + " analyzed beads",
					list.getTotalOfBeads());
			dataSet.setMetaDataValue(image.getImageName()
					+ " filtered out beads", list.getNonValidBeadFrameCount());
			dataSet.setMetaDataValue(image.getImageName() + " beads left",
					list.getValidBeadFrameCount());

			double stackSize = image.getCalibration().pixelDepth
					* image.getStackSize();
			dataSet.setMetaDataValue("z_profile_interval_min", 0);
			dataSet.setMetaDataValue("z_profile_interval_max", stackSize);
		}

		setProgress("Generating Heatmaps", 0);
		int i = 0;
		int numGraph = getGraphList().size();

		for (PsfJGraph graph : getGraphList()) {
			graph.getGraph();
			i++;
			setProgress("Generating heatmaps...", i * 100 / numGraph);
		}
		setProgress("", 0);
	}

	/**
	 * Gets the corresponding bead.
	 * 
	 * @param dataSet1
	 *            the data set1
	 * @param dataSet2
	 *            the data set2
	 * @param id
	 *            the id
	 * @return the corresponding bead
	 */
	public int getCorrespondingBead(FovDataSet dataSet1, FovDataSet dataSet2,
			int id) {

		double x;
		double y;
		double z;

		x = dataSet1.getDoubleValue("x", id);
		y = dataSet1.getDoubleValue("y", id);
		z = dataSet1.getDoubleValue("z_profile", id);

		Vector3D v = new Vector3D(x, y, z);
		DescriptiveStatistics distances = new DescriptiveStatistics();
		HashMap<Double, Integer> distancesToId = new HashMap<Double, Integer>();

		for (int i = 0; i != dataSet2.getColumnSize(); i++) {
			Vector3D v2 = new Vector3D(dataSet2.getDoubleValue("x", i),
					dataSet2.getDoubleValue("y", i), dataSet2.getDoubleValue(
							"z_profile", i));
			Vector3D d = v.subtract(v2);
			distances.addValue(d.getNorm());
			distancesToId.put(new Double(d.getNorm()), new Integer(i));
		}

		System.out.println("Corresponding bead to " + id + " (dist : "
				+ distances.getMin() + ")");
		return distancesToId.get(distances.getMin());

	}

	/**
	 * Export all generated documents
	 */
	public void export() {
		exportCSVFile();
		exportPDFSumUp();
		
		exportHeatMaps();
		exportInPDF();

	}

	/**
	 * Export xls file.
	 */
	@Deprecated
	public void exportXLSFile() {
		exportXLSFile(getExportDirectory() + getAnalysisName() + "_data.xls",
				false);
	}

	/**
	 * Export xls file.
	 * 
	 * @param path
	 *            the path
	 * @param openAfter
	 *            the open after
	 */
	public void exportXLSFile(String path, boolean openAfter) {
		exportCSVFile(path, "\t", openAfter);
	}

	/**
	 * Export csv files.
	 * 
	 * @param path
	 *            the folder to export
	 * @param openAfter
	 *            the open after
	 */
	public void exportCSVFile(String path, boolean openAfter) {
		exportCSVFile(path, ",", openAfter);
	}

	/**
	 * Export csv file(s) in the specified folder. Opens the folder if openAfter is true.
	 * 
	 * @param path
	 *            the path
	 * @param separator
	 *            the separator
	 * @param openAfter
	 *            the open after
	 */
	public void exportCSVFile(String path, String separator, boolean openAfter) {
		CsvExporter exporter = new CsvExporter(this);
		exporter.exportCsv(path, openAfter);
	}
        
        public void exportCSVFileV2(String path, String separator, boolean openAfter) {
            CsvExporter exporter = new CsvExporerParsable(this);
            exporter.exportCsv(path, openAfter);
        }

	/**
	 * Export csv file(s) in the export folder. Opens the folder if openAfter is true.
	 * 
	 * @param openAfter
	 *            the open after
	 */
	public void exportCSVFile(boolean openAfter) {
		String path;

		if (!isDualColorAnalysis()) {
			path = getExportDirectory() + getAnalysisName() + "_data.csv";
		} else {
			path = getExportDirectory();
		}
		exportCSVFile(path, openAfter);
	}

	/**
	 * Export csv file to the export folder but doesn't open it.
	 */
	public void exportCSVFile() {
		exportCSVFile(false);
	}

	/**
	 * Export in individual bead pdf reports and open them once finished.
	 */
	public void exportInPDF() {
		exportInPDF(true);
	}

	/**
	 * Checks if is dual color analysis.
	 * 
	 * @return true, if is dual color analysis
	 */
	public boolean isDualColorAnalysis() {
		return getAnalysisType() == DUAL_CHANNEL;
	}

	/**
	 * Export the individual bead pdf files and open them if openAfter is true
	 * 
	 * @param openAfter
	 *            the open after
	 */
	public void exportInPDF(boolean openAfter) {
		
		// create export
		PDFExporter pdfExporter = new PDFExporter(this);
		
		// the operation can take time
		pdfExporter.addObserver(this);
		
		// process the writting
		pdfExporter.writeSingleFileReport();

		// pdfExporter.loadImage("knoplablogo.png");
		pdfExporter.deleteObserver(this);
		
		// if it said to open after
		if (openAfter) {

			FileUtils.openFolder(getExportDirectory());

			FileUtils.openFolder(getExportDirectory()
					+ getBeadImage(0).getImageNameWithoutExtension()
					+ "_beads_page_1.pdf");
			if (isDualColorAnalysis())
				FileUtils.openFolder(getExportDirectory()
						+ getBeadImage(1).getImageNameWithoutExtension()
						+ "_beads_page_1.pdf");
		}

	}


	/**
	 * Export PDF Sum up.
	 */

	public void exportPDFSumUp() {
		exportPDFSumUp(true);
	}

	/**
	 * Export pdf sum up.
	 * 
	 * @param openAfter
	 *            the open after
	 */
	public void exportPDFSumUp(boolean openAfter) {
		PDFSumUpExporter exporter = new PDFSumUpExporter(this);
		exporter.export();
		exporter.closeDocument();
		exporter.closeDocument();
		setProgress("Sum-up exported", 0);
		if (openAfter)
			FileUtils.openFolder(exporter.getDefaultDocumentName());
	}

	public void exportHeatMaps() {
		exportHeatMaps(false);
	}

	/**
	 * Export heat maps.
	 */
	public void exportHeatMaps(boolean openAfter) {

		int size = getGraphList().size();
		int i = 1;
		setProgress("Generating heatmaps...", i * 100 / size);
		if (!FileUtils.folderExists(getExportDirectory())) {
			FileUtils.createFolder(getExportDirectory());
		}
		
		for (PsfJGraph graph : getGraphHash().values()) {
			try {
				FullHeatMap heatmap = (FullHeatMap) graph;
				saveHeatMap(heatmap);
			} catch (Exception e) {
				saveHeatMap(graph.getGraph(), graph.getSaveId(), "png");
			}
			i++;
			setProgress("Generating heatmaps...", i * 100 / size);
		}
		if (openAfter)
			FileUtils.openFolder(getExportDirectory());
		setProgress("", 0);
	}

	/**
	 * Save heat map.
	 * 
	 * @param heatmap
	 *            the heatmap
	 */
	public void saveHeatMap(FullHeatMap heatmap) {

		String channel = "";

		if (getAnalysisType() == DUAL_CHANNEL
				&& (heatmap instanceof FWHMHeatMap || heatmap instanceof ZProfileHeatMap)) {
			channel = "_"
					+ heatmap.getBeadImage().getMicroscope()
							.getWaveLengthAsString().replace(" ", "");
		}

		HeatMapGenerator generator = heatmap
				.getGenerator(PsfJGraph.NOT_NORMALIZED);
		saveHeatMap(generator.getColoredHeatMap(), heatmap.getSaveId()
				+ channel + "_colored", "png");
		saveHeatMap(generator.getRawHeatMap(), heatmap.getSaveId() + channel
				+ "_raw", "tif");
		saveHeatMap(generator.getAnnotatedHeatMap(), heatmap.getSaveId()
				+ channel + "_annotated", "png");
	}

	/**
	 * Save heat map.
	 * 
	 * @param heatmap
	 *            the heatmap
	 * @param suffix
	 *            the suffix
	 */
	public void saveHeatMap(ImageProcessor heatmap, String suffix) {
		saveHeatMap(heatmap, suffix, "png");
	}

	/**
	 * Save heat map.
	 * 
	 * @param heatmap
	 *            the heatmap
	 * @param suffix
	 *            the suffix
	 * @param format
	 *            the format
	 */
	public void saveHeatMap(ImageProcessor heatmap, String suffix, String format) {
		String path = getExportDirectory();
		String completePath = path + suffix + "." + format;
		if (format.contains("tif"))
			IJ.saveAsTiff(new ImagePlus("", heatmap), completePath);

		else if (format.contains("png")) {
			FileSaver fileSaver = new FileSaver(new ImagePlus("", heatmap));
			fileSaver.saveAsPng(completePath);
		}

	}

	/**
	 * Checks if is do export.
	 * 
	 * @return true, if is do export
	 */
	public boolean isDoExport() {
		return doExport;
	}

	/**
	 * Sets the do export.
	 * 
	 * @param doExport
	 *            the new do export
	 */
	public void setDoExport(boolean doExport) {
		this.doExport = doExport;
	}

	/**
	 * Reset data set.
	 */
	public void resetDataSet() {
		System.out.println("reseting dataset");
		dataSet = null;

		graphList = null;
		graphHash = null;

		for (BeadImage image : getBeadImageList()) {
			image.cleanMemory(image.CLEAN_ALL);
			//image.getBeadFrameList();
		}
	}

	/**
	 * Sets the indivicual bead data set.
	 * 
	 * @param d
	 *            the new data set
	 */
	public void setDataSet(FovDataSet d) {
		dataSet = d;
	}

	/**
	 * Sets the current column.
	 * 
	 * @param column
	 *            the new current column
	 */
	public void setCurrentColumn(String column) {
		currentColumn = column;
	}

	/**
	 * Gets the heat map step.
	 * 
	 * @return the heat map step
	 */
	public int getHeatMapStep() {
		return heatMapStep;
	}

	/**
	 * Sets the heat map step.
	 * 
	 * @param heatMapStep
	 *            the new heat map step
	 */
	public void setHeatMapStep(int heatMapStep) {
		this.heatMapStep = heatMapStep;
	}

	/**
	 * Calculates automatically the frame size for each bead image
	 */
	public void autoFrameSize() {
		for (BeadImage b : getBeadImageList()) {
			b.autoFrameSize();
		}
	}

	/**
	 * Sets the export directory.
	 * 
	 * @param text
	 *            the new export directory
	 */
	public void setExportDirectory(String text) {
		if (text.endsWith("/") == false) {
			text += "/";
		}
		if (!new File(text).exists())
			FileUtils.createFolder(text);
		exportDirectory = text;
	}

	/**
	 * Gets the export directory.
	 * 
	 * @return the export directory
	 */
	public String getExportDirectory() {
		if (exportDirectory == null) {
			exportDirectory = new File(getBeadImage(0).getFileAddress())
					.getParentFile().getAbsolutePath();
		}
		return exportDirectory;
	}

	/**
	 * Merge data set.
	 * 
	 * @param selectedFile
	 *            the selected file
	 */
	public void mergeDataSet(File selectedFile) {
		getDataSet().mergeDataSet(
				new FovDataSet(selectedFile.getAbsolutePath()));
	}

	/**
	 * Sets the progress for observers
	 * 
	 * @param message
	 *            the message
	 * @param progress
	 *            the progress
	 */
	public void setProgress(String message, Integer progress) {
		setChanged();
		notifyObservers(new Message(this, "progress changed", message, progress));
	}

	/**
	 * Gets the file names of each bead image (useful for exporting)
	 * 
	 * @return the file names
	 */
	public ArrayList<String> getFileNames() {
		ArrayList<String> fileNames = new ArrayList<String>();

		for (BeadImage beadImage : getBeadImageList()) {
			fileNames.add(beadImage.getImageName());
		}

		return fileNames;

	}

	/**
	 * Gets the bead frame list of all bead images.
	 * 
	 * @return the bead frame list
	 */
	public BeadFrameList getBeadFrameList() {
		BeadFrameList list = new BeadFrameList();
		for (BeadImage image : getBeadImageList()) {
			list.mergeList(image.getBeadFrameList());
		}

		return list;
	}

	/**
	 * Gets the fitting threshold.
	 * 
	 * @return the fitting threshold
	 */
	public double getFittingThreshold() {
		return fittingThreshold;
	}

	/**
	 * Sets the fitting threshold.
	 * 
	 * @param fittingThreshold
	 *            the new fitting threshold
	 */
	public void setFittingThreshold(double fittingThreshold) {
		this.fittingThreshold = fittingThreshold;
	}

	/**
	 * Gets the deleted beads.
	 * 
	 * @param beadImageId
	 *            the bead image id
	 * @return the deleted beads
	 */
	public int getDeletedBeads(int beadImageId) {
		return getBeadImage(beadImageId).getDeletedBeadsCount();
	}

	/**
	 * Gets the paired beads.
	 * 
	 * @return the paired beads
	 */
	public int getPairedBeads() {
		return getBeadImage(0).getBeadFrameList().getPairedCount();
	}

	/**
	 * Gets the deleted beads.
	 * 
	 * @return the deleted beads
	 */
	public int getDeletedBeads() {

		int deletedBeads = 0;
		for (BeadImage image : getBeadImageList()) {
			deletedBeads += image.getDeletedBeadsCount();
		}
		return deletedBeads;
	}

	/**
	 * Gets the analysis name.
	 * 
	 * @return the analysis name
	 */
	public String getAnalysisName() {
		return FileUtils.getFileNameWithoutExtension(getBeadImage(0)
				.getImageName());
	}

	/**
	 * Gets the analysis type.
	 * 
	 * @return the analysis type
	 */
	public int getAnalysisType() {

		if (getBeadImageCount() <= 1)
			return SINGLE_CHANNEL;

		return analysisType;
	}

	/**
	 * Sets the analysis type.
	 * 
	 * @param analysisType
	 *            the new analysis type
	 */
	public void setAnalysisType(int analysisType) {

		if (analysisType != this.analysisType) {

			this.analysisType = analysisType;

			setChanged();
			notifyObservers(new Message(this, "analysis type changed"));
		}
	}
        
       
        
        public BeadLocator getLocator() {
            return locator;
        }
        
        
        public void setLocator(BeadLocator locator) {
            
            System.out.println("Updating model to "+locator.getClass().getSimpleName());
            this.locator = locator;
            for(BeadImage image : getBeadImageList()) {
                image.setLocator(locator);
            }
            
            
            
        }

	/**
	 * Gets the calibration of a defined bead image from its id
	 * 
	 * @param beadImageId
	 *            the bead image id
	 * @return the calibration
	 */
	public Calibration getCalibration(int beadImageId) {
		return getMicroscope(beadImageId).getCalibration();
	}

	
	/**
	 * Detects the informations in the microscope sources are the same as the microscope informations contained in the added
	 * stacks
	 * @param source
	 * @return true is the configuration will override the configurations of the bead images.
	 */
	public boolean isThereMicroscopeDataOverriding(Microscope source) {

		// list of booleans
		ArrayList<Boolean> allValueAreEqual = new ArrayList<Boolean>();

		// There is overriding when at least one value is different and the
		// target not equal to 0
		for (BeadImage image : getBeadImageList()) {

			Microscope target = image.getMicroscope();

			// there is NO overriding if the values are the same or if the
			// target microscope equals to 0
			if (!isDualColorAnalysis()) {
				allValueAreEqual.add(source.getWaveLength() == target
						.getWaveLength() || target.getWaveLength() <= 0);
			}
			allValueAreEqual.add(source.getBeadDiameter() == target
					.getBeadDiameter() || target.getBeadDiameter() <= 0);
			allValueAreEqual.add(source.getNA() == target.getNA()
					|| target.getNA() <= 0);
			allValueAreEqual.add(source.getRefractionIndex() == target
					.getRefractionIndex() || target.getNA() <= 0);
			allValueAreEqual.add(source.getPixelWidth() == target
					.getPixelWidth() || target.getPixelWidth() <= 0);
			allValueAreEqual.add(source.getPixelDepth() == target
					.getPixelDepth() || target.getPixelDepth() <= 0);
			allValueAreEqual.add(source.getPixelHeight() == target
					.getPixelHeight() || target.getPixelHeight() <= 0);
		}
		int i = 0;
		for (Boolean b : allValueAreEqual) {
			System.out.println(String.format("question %d = ", i++) + b);
		}
		
		return !PSFj.testList(allValueAreEqual);
	}

	/**
	 * Update the microscope informations of the bead images with the given configuration
	 * 
	 * @param microscope
	 *            the microscope configuration that should override the rest
	 */
	public void updateMicroscope(Microscope microscope) {
		boolean includeWaveLength = (getAnalysisType() == SINGLE_CHANNEL);
		for (BeadImage b : getBeadImageList()) {

			b.getMicroscope().mergeFromOtherMicroscope(microscope,
					includeWaveLength);
		}
	}

	/**
	 * Reset the preview.
	 */
	public void resetPreview() {
		getBeadImage(0).resetPreview();

	}

	/**
	 * Gets the heatmap statistics as string.
	 * 
	 * @param heatmapId
	 *            the heatmap id
	 * @param normalized
	 *            the normalized or not
	 * @param unit
	 *            the unit
	 * @return the heatmap statistics as string
	 */
	public String getHeatmapStatisticsAsString(String heatmapId,
			int normalized, String unit) {
		DescriptiveStatistics stats = getHeatmapStatistics(heatmapId,
				normalized);

		if (heatmapId.contains(PSFj.Z_PROFILE)) {
			return MathUtils.PLUS_MINUS
					+ " "
					+ MathUtils
							.formatDouble(stats.getStandardDeviation(), unit);
		}

		return MathUtils.formatStatistics(stats, unit);

	}

	/**
	 * Gets the heatmap statistics as string.
	 * 
	 * @param heatmap
	 *            the heatmap
	 * @param normalized
	 *            the normalized
	 * @return the heatmap statistics as string
	 */
	public String getHeatmapStatisticsAsString(FullHeatMap heatmap,
			int normalized) {

		if (heatmap.getField().contains(PSFj.Z_PROFILE)) {
			return MathUtils.PLUS_MINUS
					+ " "
					+ MathUtils.formatDouble(
							getHeatmapStatistics(heatmap, normalized)
									.getStandardDeviation(), heatmap
									.getGenerator(normalized).getUnit());
		}

		return MathUtils.formatStatistics(
				getHeatmapStatistics(heatmap, normalized), heatmap
						.getGenerator(normalized).getUnit());
	}

	/**
	 * Gets the heatmap statistics.
	 * 
	 * @param graph
	 *            the graph
	 * @param normalized
	 *            the normalized
	 * @return the heatmap statistics
	 */
	public DescriptiveStatistics getHeatmapStatistics(PsfJGraph graph,
			int normalized) {
		DescriptiveStatistics stats = new DescriptiveStatistics();

		try {
			FullHeatMap heatmap = (FullHeatMap) graph;

			HeatMapGenerator generator = heatmap.getGenerator(normalized);

			if (generator.getSpace().size() == 0) {
				System.out
						.println("Not enough beads, getting column statistics");
				stats = generator.getCurrnetColumnStatistics();
			} else {
				ImageProcessor ip = heatmap.getGenerator(normalized)
						.getSpaceImage();

				for (float f : (float[]) ip.getPixels()) {
					if (!Double.isNaN(f))
						stats.addValue(f);
				}
			}

		} catch (Exception e) {

			e.printStackTrace();

		}
		return stats;
	}

	/**
	 * Gets the heatmap statistics.
	 * 
	 * @param heatmapId
	 *            the heatmap id
	 * @param normalized
	 *            the normalized
	 * @return the heatmap statistics
	 */
	public DescriptiveStatistics getHeatmapStatistics(String heatmapId,
			int normalized) {
		if (getGraph(heatmapId) == null) {
			System.err.println("Error when getting heatmap " + heatmapId);
			return new DescriptiveStatistics();
		}

		return getHeatmapStatistics(getGraph(heatmapId), normalized);

	}

	/**
	 * Gets the merged preview in case of dual color analysis (not sure if it's used...)
	 * 
	 * @param displayedImage
	 *            the displayed image
	 * @return the merged preview
	 */
	public ImageProcessor getMergedPreview(int displayedImage) {
		ImageProcessor ip = getBeadImage(0).getMiddleImage()
				.convertToByte(true);
		ImageProcessor ip2 = getBeadImage(1).getMiddleImage().convertToByte(
				true);

		ip.setLut(LUT.createLutFromColor(getBeadImage(0).getBeadsColor()));
		ip2.setLut(LUT.createLutFromColor(getBeadImage(1).getBeadsColor()));

		ip = ip.convertToRGB();
		ip2 = ip2.convertToRGB();

		ip.copyBits(ip2, 0, 0, Blitter.ADD);

		for (BeadFrame frame : getBeadImage(displayedImage).getBeadFrameList()) {
			frame.draw(ip, getBeadImage(displayedImage).getFrameSize(),
					Color.white);
		}

		return ip;
	}

	/**
	 * Gets the merged map.
	 * 
	 * @return the merged map
	 */
	public ImageProcessor getMergedMap() {

		return null;
	}

	/**
	 * Gets the ignored beads count.
	 * 
	 * @return the ignored beads count
	 */
	public int getIgnoredBeadsCount() {
		int count = 0;
		for (BeadImage image : getBeadImageList()) {
			count += image.getIgnoredFrameNumber();
		}
		return count;
	}

	/**
	 * Gets the distance threshold.
	 * 
	 * @return the distance threshold
	 */
	public double getDistanceThreshold() {
		// TODO Auto-generated method stub
		return getMicroscope(0).getXYTheoreticalResolution() * 2;
	}

	public boolean isReady() {
		return isReady;
	}

	public void setReady(boolean isReady) {
		this.isReady = isReady;
	}
        
        public boolean isImageLoading() {
            for(BeadImage image : getBeadImageList()) {
                if(image.isOpening == true) {
                    return true;
                }
            }
            return false;
        }

} public class Microscope {

	public static String MICROSCOPE_SECTION = "microscope";
	public static String CALIBRATION_SECTION = "pixel";

	public static String PIXEL_WIDTH_KEY = "width";
	public static String PIXEL_HEIGHT_KEY = "height";
	public static String PIXEL_DEPTH_KEY = "depth";

	public static String PIXEL_SIZE_KEY = "cal_pixel_size";
	public static String MAGNIFICATION_KEY = "magnification";
	public static String STEP_SIZE_KEY = "step_size";

	public static String BEAD_DIAMETER_KEY = "beadsize";
	public static String RI_KEY = "ri";
	public static String NA_KEY = "na";

	public static String WAVELENGTH_KEY = "wavelength";

	public static String UNIT_KEY = "unit";

	public static String IDENTIFIER_KEY = "id";



	public static final String DEFAULT_UNIT = new String(MathUtils.MICROMETERS);
	
	
	/** Stores the type of microscope **/
	public int microscope = 0;

	/** Stores the emission wavelength (nm) **/
	protected double wavelength = 0;

	/** Stores the numerical aperture **/
	protected double NA = 0;


	protected double refraction = 1.0;
	/** Stores the calibration **/
	protected Calibration cal = null;

	/** Stores the calculated resolutions **/
	public double[] resolution = new double[3];

	/**
	 * Stores the header of the report, containing the informations about the
	 * microscope setup
	 **/
	public String reportHeader = "";

	/** Stores the date of creation of the report **/
	public String date = "";

	/** Stores the informations about the sample **/
	public String sampleInfos = "";

	/** Stores the comment field **/
	public String comments = "";

	protected double beadDiameter;

	protected double magnification;
	protected double additionalMagnification;

	protected double spaceBetweenStacks;
	protected double pixelSize;

	private IniFile config;

	public static int X = 0;
	public static int Y = 1;
	public static int Z = 2;

	String identifier = "";

	String unit = DEFAULT_UNIT;
	
	/**
	 * Creates a new microscope object, used to store informations about the
	 * microscope and the sample.
	 *
	 * @param cal the cal
	 * @param microscope            microscope type (0: wide-field, 1: confocal)
	 * @param wavelength            emission wavelength (nm)
	 * @param NA            numerical aperture
	 * @param refraction the refraction
	 * @param sampleInfos            sample informations to be displayed on the report
	 * @param comments            comments to be displayed on the report
	 */
	public Microscope(Calibration cal, int microscope, double wavelength,
			double NA, double refraction,  String sampleInfos,
			String comments) {
		this.cal = cal;
		this.microscope = microscope;
		this.wavelength = wavelength;
		this.NA = NA;
		this.refraction = refraction;
	
		this.sampleInfos = sampleInfos;
		this.comments = comments;

		calculateTheoreticalResolutions();

		

		DateFormat df = DateFormat.getDateTimeInstance(DateFormat.LONG,
				DateFormat.SHORT);
		date = df.format(Calendar.getInstance().getTime()).toString();
	}

	
	
	
	
	public Microscope() {

		Preferences prefs = Preferences.userRoot().node("/PSFj25");
		cal = new Calibration();
		cal.pixelWidth = prefs.getDouble(PIXEL_WIDTH_KEY, 0.0);
		cal.pixelHeight = prefs.getDouble(PIXEL_HEIGHT_KEY, 0.0);
		cal.pixelDepth = prefs.getDouble(PIXEL_DEPTH_KEY, 0.0);

		cal.setUnit(prefs.get(UNIT_KEY, DEFAULT_UNIT));
		
	
		
		wavelength = prefs.getDouble(WAVELENGTH_KEY, 0.0);

		NA = prefs.getDouble(NA_KEY, 0);
		refraction = prefs.getDouble(RI_KEY, 0.0);
		;
		beadDiameter = prefs.getDouble(BEAD_DIAMETER_KEY, 0.0);

		magnification = prefs.getDouble(MAGNIFICATION_KEY, 0);

		identifier = "";
		unit = DEFAULT_UNIT;
		calculateTheoreticalResolutions();

	}

	
	public Microscope(Microscope m) {
		this();
		setWaveLength(m.getWaveLength());
		setBeadDiameter(m.getBeadDiameter());
		setNA(m.getNA());
		setRefraction(m.getRefractionIndex());
		setCalibration(m.getCalibration());
		
	}
	
	
	public Microscope(String filename) {
		this(new IniFile(filename.replaceAll("\\.\\w+$", "") + ".ini"));
	}

	public static void main(String[] args) {
		Microscope m = new Microscope("/Users/cyril/test_img/6_gfp.ini");
		m.save();
		System.out.println(m.getUnit());
		System.out.println(m.getMicroscopeHeader());
	}
	
	public Microscope(IniFile ini) {
		super();
		config = ini;
		cal = new Calibration();

		Preferences prefs =PSFj.getPreferences();

		wavelength = config.getDoubleValue(MICROSCOPE_SECTION, WAVELENGTH_KEY,
				prefs.getDouble(WAVELENGTH_KEY, 0.0));
		NA = config.getDoubleValue(MICROSCOPE_SECTION, NA_KEY,
				prefs.getDouble(NA_KEY, 0.0));
		refraction = config.getDoubleValue(MICROSCOPE_SECTION, RI_KEY,
				prefs.getDouble(RI_KEY, 0.0));
		beadDiameter = config.getDoubleValue(MICROSCOPE_SECTION, BEAD_DIAMETER_KEY,
				prefs.getDouble(BEAD_DIAMETER_KEY, 0.0));

		if (config.getDoubleValue(MICROSCOPE_SECTION, PIXEL_DEPTH_KEY, -1) > 0.0) {

			spaceBetweenStacks = config.getDoubleValue(MICROSCOPE_SECTION,
					STEP_SIZE_KEY, prefs.getDouble(STEP_SIZE_KEY, 0.0));
			pixelSize = config.getDoubleValue(MICROSCOPE_SECTION,
					PIXEL_SIZE_KEY, prefs.getDouble(PIXEL_SIZE_KEY, 0.0));
			magnification = config.getDoubleValue(MICROSCOPE_SECTION,
					MAGNIFICATION_KEY, prefs.getDouble(MAGNIFICATION_KEY, 0.0));

			calculateCalibrationFromPixelSize();
		} else {
			cal.pixelWidth = config.getDoubleValue(CALIBRATION_SECTION,
					PIXEL_WIDTH_KEY, prefs.getDouble(PIXEL_WIDTH_KEY, 0.0));
			cal.pixelHeight = config.getDoubleValue(CALIBRATION_SECTION,
					PIXEL_HEIGHT_KEY, prefs.getDouble(PIXEL_HEIGHT_KEY, 0.0));
			cal.pixelDepth = config.getDoubleValue(CALIBRATION_SECTION,
					PIXEL_DEPTH_KEY, prefs.getDouble(PIXEL_DEPTH_KEY, 0.0));

		}

		setIdentifier(config.getStringValue(MICROSCOPE_SECTION, IDENTIFIER_KEY));

		setUnit(config.getStringValue(CALIBRATION_SECTION, UNIT_KEY, DEFAULT_UNIT));
		
		if(!MathUtils.isMetricUnit(cal.getUnit()) && !cal.getUnit().equals(MathUtils.PIXEL) && !cal.getUnit().equals(MathUtils.PIXELS)) {
			setUnit(DEFAULT_UNIT);
		}
		
		
		save();

	}
	
	
	public void setPixelWidth(double width) {
		getCalibration().pixelWidth = width;
	}
	
	public void setPixelHeight(double height) {
		getCalibration().pixelHeight = height;
	}
	
	public void setPixelDepth(double depth) {
		getCalibration().pixelDepth = depth;
	}
	
	
	public boolean isAdvancedMode() {
		return !(pixelSize > 0 && magnification > 0 && spaceBetweenStacks > 0);
	}

	
	public void setUnit(String unit) {
		this.unit = unit;
		getCalibration().setUnit(unit);
	}
	
	public static String getBaseName(String filename) {
		return filename.replaceAll("\\.\\w+$", "");
	}
	
	public static Microscope loadMicroscopeFromImage(String filename) {
		File image = new File(filename);
		String basename = image.getAbsolutePath().replaceAll("\\.\\w+$", "");

		System.out.println(basename);

		Microscope m = new Microscope(basename + ".ini");

		return m;

	}

	
	public void calculateCalibrationFromPixelSize() {
		
		if(pixelSize == 0 || magnification == 0 || spaceBetweenStacks == 0) return;
		
		double pixelWidth = pixelSize
				/ getMagnification();
		
		double pixelDepth = spaceBetweenStacks;
		
				
		getCalibration().pixelWidth = pixelWidth;
		getCalibration().pixelHeight = pixelWidth;
		getCalibration().pixelDepth = pixelDepth;
	}
	
	
	public void save() {

		/*
		Preferences prefs = PSFj.getPreferences();
		prefs.putDouble(PIXEL_WIDTH_KEY, cal.pixelWidth);
		prefs.putDouble(PIXEL_HEIGHT_KEY, cal.pixelHeight);
		prefs.putDouble(PIXEL_DEPTH_KEY, cal.pixelDepth);

		prefs.putDouble(WAVELENGTH_KEY, wavelength);
		prefs.putDouble(NA_KEY, NA);
		prefs.putDouble(RI_KEY, refraction);
		prefs.putDouble(BEAD_SIZE_KEY, beadSize);

		prefs.putDouble(MAGNIFICATION_KEY, magnification);
		prefs.putDouble(STEP_SIZE_KEY, cal.pixelDepth);
		prefs.putDouble(PIXEL_SIZE_KEY, pixelSize);
		 
		
		
		try {
			prefs.flush();
		} catch (BackingStoreException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	*/
		if (config != null) {
			config.addStringValue(MICROSCOPE_SECTION, WAVELENGTH_KEY,
					MathUtils.roundToString(wavelength, 4));
			config.addStringValue(MICROSCOPE_SECTION, NA_KEY,
					MathUtils.roundToString(NA, 4));
			config.addStringValue(MICROSCOPE_SECTION, RI_KEY,
					MathUtils.roundToString(refraction, 4));
			config.addStringValue(MICROSCOPE_SECTION, BEAD_DIAMETER_KEY,
					MathUtils.roundToString(beadDiameter, 4));

			if (pixelSize > 0.0) {
				config.addStringValue(MICROSCOPE_SECTION, PIXEL_SIZE_KEY,
						MathUtils.roundToString(pixelSize, 4));
				config.addStringValue(MICROSCOPE_SECTION, MAGNIFICATION_KEY,
						MathUtils.roundToString(getMagnification(), 3));
				config.addStringValue(MICROSCOPE_SECTION, STEP_SIZE_KEY,
						MathUtils.roundToString(spaceBetweenStacks, 4));
			}
			config.addStringValue(CALIBRATION_SECTION, PIXEL_WIDTH_KEY,
					MathUtils.roundToString(cal.pixelWidth, 4));
			config.addStringValue(CALIBRATION_SECTION, PIXEL_HEIGHT_KEY,
					MathUtils.roundToString(cal.pixelHeight, 4));
			config.addStringValue(CALIBRATION_SECTION, PIXEL_DEPTH_KEY,
					MathUtils.roundToString(cal.pixelDepth, 4));
			config.addStringValue(CALIBRATION_SECTION, UNIT_KEY, getUnit());
			config.addStringValue(MICROSCOPE_SECTION, IDENTIFIER_KEY,
					getIdentifier());
			config.save();
		}

	}

	/**
	 * Rounds any double to the user provided number of digits
	 * 
	 * @param nb2round
	 *            number to round
	 * @param nbOfDigits
	 *            number of digits
	 * @return a rounded double
	 */
	private double round(double nb2round, int nbOfDigits) {
		return Math.round(nb2round * Math.pow(10, nbOfDigits))
				/ Math.pow(10, nbOfDigits);
	}

	public Calibration getCalibration() {
		return cal;
	}

	public String getMicroscopeHeader() {

		reportHeader = "";
		if (getIdentifier().equals("") == false) {
		//	reportHeader += getIdentifier() + "\n";
		}
		reportHeader += "Emission wavelength: " + MathUtils.formatDouble(wavelength,getUnit()) + " "
				+ "\nNA: " + NA + "\nn: "
				+ round(refraction, 3) + "\nVoxel size: "
				+ formatDouble(cal.pixelWidth) + " (x) x"
				+ formatDouble(cal.pixelHeight) + " (y) x"
				+ formatDouble(cal.pixelDepth) + " (z) "
				+ "\nBead diameter : " + formatDouble(beadDiameter) + " "
				+ "\n\n";
		return reportHeader;
	}

	public void calculateTheoreticalResolutions() {
		resolution[0] = 0.84 * 0.61 * wavelength / NA;
		resolution[1] = resolution[0];
		resolution[2] = 1.76 * wavelength * refraction / Math.pow(NA, 2);
	}

	public double getXYTheoreticalResolutionWithBeadSize() {
		calculateTheoreticalResolutions();
		return Math.sqrt(Math.pow(beadDiameter, 2.5) + Math.pow(resolution[0], 2));
	}

	public double getZTheoreticalResolutionWithBeadSize() {
		calculateTheoreticalResolutions();
		return Math.sqrt(Math.pow(beadDiameter, 2.5) + Math.pow(resolution[2], 2));
	}

	public double getZTheoreticalResolution() {
		calculateTheoreticalResolutions();
		return resolution[2];
	}

	public double getXYTheoreticalResolution() {
		calculateTheoreticalResolutions();
		return resolution[0];
	}

	
	

	public double getCorrectedResolution(double fwhm, int axe) {

		double expectedFWHM;

		if (axe == X || axe == Y) {
			expectedFWHM = getXYTheoreticalResolution();
		} else {
			expectedFWHM = getZTheoreticalResolution();
		}

		return fwhm * getAlpha(fwhm, expectedFWHM, beadDiameter, axe);

	}

	/**
	 * Gets the p. The formula is given in the technical support of the
	 * associated publication.
	 * 
	 * @param mu
	 *            the mu
	 * @param sigma
	 *            the sigma
	 * @param beadDiameter
	 *            the bead diamter
	 * @return the p
	 */
	private double getP(double mu, double sigma, double beadDiameter, double P0) {

		// left part of the formula
		double left = P0 / (sigma * beadDiameter * Math.sqrt(2.0 * Math.PI));

		// top part of what's inside the exponencial
		double right_up = Math.pow((Math.log(beadDiameter) - mu), 2);

		// bottom part of what's inside the exponenciel
		double right_down = 2.0 * Math.pow(sigma, 2);

		// let's assemble everything according to the formula
		double result = left * Math.exp(-1.0 * right_up / right_down);

		return result;
	}

	private double getS(double beadDiameter, double c, double s0) {

		return s0 * (beadDiameter / (1.0 + beadDiameter + c));
	}

	/**
	 * Calculate the alpha correction factor for the FWHM according to the ratio
	 * between the expected FWHM and the bead diameter. For more informations,
	 * please check the technical support in the publication.
	 * 
	 * @param fwhm
	 *            the FWHM (or calculated resolution
	 * @param expectedFWHM
	 *            the expected (or theoretical) resolution
	 * @param beadDiameter
	 *            the bead diameter
	 * @param axe
	 *            the axe of the calculate (X, Y or Z)
	 * @return the alpha correction factor
	 */
	private double getAlpha(double fwhm, double expectedFWHM,
			double beadDiameter, int axe) {

		double alpha;

		double mu;
		double sigma;
		double s0;
		double p0;
		double c;
		if (axe == X || axe == Y) {

			s0 = 0.012;
			c = 6.07;
			p0 = -12.28;

			mu = 4.05;
			sigma = 1.71;

		} else {

			s0 = -0.10;
			c = -0.0062;
			p0 = 0.90;

			mu = 0.36;
			sigma = 0.46;
		}

		double p = getP(mu, sigma, beadDiameter, p0);
		double s = getS(beadDiameter, c, s0);

		double top = expectedFWHM;
		double bottom = Math.sqrt(Math.pow(expectedFWHM, 2)
				+ Math.pow(beadDiameter, 2))
				+ s + p;

		// System.out.println(String.format("[Axe %d]\nFWHM : %.3f\nExpected : %.3f\np: %.3f\ns : %.3f\nalpha : %.3f",axe,fwhm,expectedFWHM,p,s,top/bottom));

		return top / bottom;
	}

	public double getBeadDiameter() {
		return beadDiameter;
	}

	public String getUnit() {
		return unit;//getCalibration().getUnit();
	}

	public String toString() {
		String result = String
				.format("[Microscope : CPS : %s, Step : %s, M : %s]\n[Calibration : %sx %s x %s]",
						MathUtils.roundToString(pixelSize, 4),
						MathUtils.roundToString(this.spaceBetweenStacks, 4),
						MathUtils.roundToString(getMagnification(), 2),
						MathUtils.roundToString(cal.pixelWidth, 4),
						MathUtils.roundToString(cal.pixelWidth, 4),
						MathUtils.roundToString(cal.pixelDepth, 4));
		return result;

	}

	public Objective getObjective(String name, double magnification) {
		return new Objective(name, magnification);
	}

	public class Objective implements Comparable<Objective> {
		String name;
		double magnification;

		public Objective(String name, double magnification) {
			this.name = name;
			this.magnification = magnification;
		}

		@Override
		public int compareTo(Objective o) {
			if (getMagnification() == o.getMagnification()) {
				return 0;
			}
			return -1;
		}

		public boolean equals(Object o) {
			if (o instanceof Objective) {
				if (((Objective) o).getMagnification() == getMagnification()) {

					return true;
				} else
					return false;
			} else {
				return false;
			}
		}

		public double getMagnification() {
			return magnification;
		}

		public String toString() {
			return name;
		}

	}

	public String getWaveLengthAsString() {
		return formatDouble(wavelength);
	}

	public String getNAAsString() {
		return "" + MathUtils.round(NA, 5);
	}

	public String getRefractionIndexAsString() {
		return "" + MathUtils.round(refraction, 5);
	}

	public String getBeadSizeAsString() {
		return formatDouble(beadDiameter);
	}

	public String getVoxelSizeAsString() {
		String result = "";
		result += formatDouble(cal.pixelWidth);
		result += " x " + formatDouble(cal.pixelHeight);
		result += " x " + formatDouble(cal.pixelDepth);

		return result;
	}

	public String getConfigurationFileFromImage(String imageAddress) {
		String basename = imageAddress.replaceAll("\\.\\w+$", "");
		String iniFile = basename + ".ini";
		return iniFile;
	}

	public double getWaveLength() {
		return wavelength;
	}

	public void setWaveLength(double w) {
		wavelength = w;
	}

	public void setIdentifier(String text) {

		identifier = text;

		if (config != null) {
			// System.out.println("Setting idenfier to "+text);
			if (text.equals(""))
				return;
			config.addStringValue(MICROSCOPE_SECTION, IDENTIFIER_KEY, text);
		}
	}

	public String getIdentifier() {
		if (config != null)
			return config
					.getStringValue(MICROSCOPE_SECTION, IDENTIFIER_KEY, "");
		return identifier;

	}

	public void setNA(double nA) {
		NA = nA;
	}

	public double getNA() {
		return NA;
	}

	public void setRefraction(double refraction) {
		this.refraction = refraction;
	}

	public void setCalibration(Calibration cal) {
		this.cal = cal;
	}

	public void setSpaceBetweenStacks(double spaceBetweenStacks) {
		this.spaceBetweenStacks = spaceBetweenStacks;
	}

	public double getMagnification() {
		return magnification;
	}

	public void setMagnification(double magnification) {
		this.magnification = magnification;
	}

	public double getAdditionalMagnification() {
		return additionalMagnification;
	}

	public void setAdditionalMagnification(double additionalMagnification) {
		this.additionalMagnification = additionalMagnification;
	}

	public double getRefractionIndex() {
		return refraction;
	}

	public double getCameraPixelSize() {
		return pixelSize;
	}

	public void setCameraPixelSize(double p) {
		pixelSize = p;
	}

	public void setBeadDiameter(double d) {
		beadDiameter = d;
	}

	public double getSpaceBetweenStacks() {
		return spaceBetweenStacks;
	}

	public double getPixelWidth() {
		return getCalibration().pixelWidth;
	}
	
	public double getPixelHeight() {
		return getCalibration().pixelHeight;
	}
	
	public double getPixelDepth() {
		return getCalibration().pixelDepth;
	}
	
	
	public boolean isConfigurationValid() {
		
		
		ArrayList<Boolean> checkPoints = new ArrayList<Boolean>();
		
		checkPoints.add(beadDiameter > 0);
		checkPoints.add(wavelength > 0);
		checkPoints.add(refraction > 0);
		checkPoints.add(NA > 0);
		
		checkPoints.add(getCalibration().pixelWidth > 0);
		checkPoints.add(getCalibration().pixelHeight > 0);
		checkPoints.add(getCalibration().pixelDepth > 0);
		
		for(boolean checkPoint : checkPoints) {
			if(checkPoint == false) return false;
		}
		return true;
	}
	
	public void mergeFromOtherMicroscope(Microscope m, boolean includeWavelength) {

		if (includeWavelength) {
			setWaveLength(m.getWaveLength());
		}

		
		
		setIdentifier(m.getIdentifier());

		setNA(m.getNA());
		setRefraction(m.getRefractionIndex());
		System.out.println("The entered bead diameter is :"+m.getBeadDiameter());
		setBeadDiameter(m.getBeadDiameter());

		setSpaceBetweenStacks(m.getSpaceBetweenStacks());
		setMagnification(m.getMagnification());
		setCameraPixelSize(m.getCameraPixelSize());
		
		Calibration nc = new Calibration();
		Calibration oc = m.getCalibration();
		
		nc.setUnit(oc.getUnit());
		nc.pixelWidth = oc.pixelWidth;
		nc.pixelHeight = oc.pixelHeight;
		nc.pixelDepth = oc.pixelDepth;
		
		setCalibration(nc);
		setUnit(m.getUnit());
		save();
	}

	public String formatDouble(double number) {
		// TODO Auto-generated method stub
		return MathUtils.formatDouble(number, getUnit());
	}

	public double getTheoreticalResolution(int axe) {
		if(axe == PSFj.Z_AXIS) return getZTheoreticalResolution();
		else return getXYTheoreticalResolution();
	}
	
	public String getTheoreticalResolutionAsString(int axe) {
		return formatDouble(getTheoreticalResolution(axe));
	}

} public class ReportSections {
    /**
     * Creates a new instance of ReportSection, an easy way to generate a pdf report
     */
    public ReportSections(){

    }

    /**
     * Generates an image of the RT-MFM's logo to be added to the report
     * @return and Image containing the RT-MFM's logo
     */
    public Image logoRTMFM(){
        Image logo = null;
        try {
            logo = Image.getInstance(getClass().getResource("/home/cyril/bioinformatics.gif"));
            logo.setAlignment(Image.ALIGN_CENTER);
            logo.scalePercent(50);

        } catch (BadElementException ex) {
            Logger.getLogger(ReportSections.class.getName()).log(Level.SEVERE, null, ex);
        } catch (MalformedURLException ex) {
            Logger.getLogger(ReportSections.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(ReportSections.class.getName()).log(Level.SEVERE, null, ex);
        }
        catch(NullPointerException ex) {
        	 Logger.getLogger(ReportSections.class.getName()).log(Level.SEVERE, null, ex);
        }
        return logo;
    }

    /**
     * Generates the main title to be added to the pdf document
     * @param title a string to be used as title
     * @return the title ready to be added to the pdf document
     */
    public Paragraph bigTitle(String title){
        Paragraph reportTitle=new Paragraph();
        reportTitle.add(new Chunk(title, new Font(Font.HELVETICA, 18, Font.BOLD)));
        reportTitle.setAlignment(Paragraph.ALIGN_CENTER);
        reportTitle.setLeading(20);
        reportTitle.setSpacingBefore(15);
        reportTitle.setSpacingAfter(15);
        return reportTitle;
    }

    /**
     * Generates the paragraph title to be added to the pdf document
     * @param title a string to be used as title
     * @return the paragraph title ready to be added to the pdf document
     */
    public Paragraph title(String title){
        Paragraph reportTitle=new Paragraph();
        Font font=new Font(Font.HELVETICA, 14, Font.BOLD);
        //font.setStyle(Font.UNDERLINE);
        reportTitle.add(new Chunk(title, font));
        reportTitle.setAlignment(Paragraph.ALIGN_LEFT);
        reportTitle.setSpacingBefore(15);
        return reportTitle;
    }

    public Paragraph subtitle(String subtitle) {
    	Paragraph reportTitle=new Paragraph();
        Font font=new Font(Font.HELVETICA, 12, Font.BOLD);
        //font.setStyle(Font.UNDERLINE);
        reportTitle.add(new Chunk(subtitle, font));
        reportTitle.setAlignment(Paragraph.ALIGN_LEFT);
        reportTitle.setSpacingBefore(5);
        return reportTitle;
    }
    
    /**
     * Generates the paragraph title to be added to the pdf document
     * @param title a string to be used as title
     * @return the paragraph title ready to be added to the pdf document
     */
    public Paragraph paragraph(String title){
        Paragraph paragraph=new Paragraph();
        Font font=new Font(Font.HELVETICA, 12, Font.NORMAL);
        paragraph.add(new Chunk(title, font));
        paragraph.setAlignment(Paragraph.ALIGN_LEFT);
        paragraph.setSpacingBefore(15);
        return paragraph;
    }
    
    
    public Paragraph littleNote(String note) {
   	 return littleNote(note,Paragraph.ALIGN_CENTER);
    }
    
    public Paragraph littleNote(String note,int alignement) {
    	Paragraph paragraph = new Paragraph();
    	Font font = new Font(Font.HELVETICA, 10, Font.NORMAL);
    	paragraph.add(new Chunk(note,font));
    	paragraph.setAlignment(alignement);
    	paragraph.setSpacingAfter(0);
    	return paragraph;
    }
    
    /**
     * Generates an Image ready to be added to the pdf document, based on an ImagePlus
     * @param image the ImagePlus from which the Image object will be generated
     * @param zoom the zoom to be applied to the image (0.0-100.0) as a float
     * @return an Image ready to be added to the pdf document
     */
    
    public Image imagePlus(PsfJGraph graph, float zoom) {
    	return imagePlus(new ImagePlus("",graph.getGraph().resize(1700)),zoom);
    }
    
    
    public Image imagePlus(ImagePlus image, float zoom){
        Image img = null;
        
        try {
            img = Image.getInstance(image.getImage(), null);
            img.setAlignment(Image.ALIGN_CENTER);
            //img.scalePercent(zoom);
            float height = zoom;
            img.scaleAbsolute(height*image.getWidth()/image.getHeight(),height);
           
            //img.scalePercent(zoom);
            
            img.setSpacingBefore(0);
            img.setSpacingAfter(0);
        } catch (BadElementException ex) {
            Logger.getLogger(ReportSections.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(ReportSections.class.getName()).log(Level.SEVERE, null, ex);
        }
       // img.setSpacingBefore(15);
        return img;
    }

    /**
     * Generated a PdfPTable ready to be added to the pdf document
     * @param content a String[][] array, first index being the rows, second the columns.
     * @param widthPercentage size of the table, as a percentage of the page's width
     * @return an PdfPTable ready to be added to the pdf document
     */
    public PdfPTable table(String[][] content, float widthPercentage){
        PdfPTable table = new PdfPTable(content.length);
        table.getDefaultCell().setHorizontalAlignment(Element.ALIGN_CENTER);
        table.getDefaultCell().setVerticalAlignment(Element.ALIGN_MIDDLE);
        table.setWidthPercentage(widthPercentage);
        for (int j=0; j<content[0].length; j++){
            for (int i=0; i<content.length; i++){
                Font font=new Font(Font.HELVETICA, 12, Font.NORMAL);
                if (i==0 || j==0) font=new Font(Font.HELVETICA, 12, Font.BOLD);
                table.addCell(new Paragraph(new Chunk(content[i][j], font)));
            }
        }
        table.setHorizontalAlignment(PdfTable.ALIGN_LEFT);
        table.setSpacingBefore(15);
        return table;
    }


} public class SideViewGenerator {
    public static final int XZ_VIEW=0;
    public static final int YZ_VIEW=1;

    public static final int AVG_METHOD = 0;
    public static final int MAX_METHOD = 1;
    public static final int MIN_METHOD = 2;
    public static final int SUM_METHOD = 3;
    public static final int SD_METHOD = 4;
    public static final int MEDIAN_METHOD = 5;


    /**
     *
     */
    public SideViewGenerator(){
    }

    /**
     * getXYview returns the "top-view" of a stack
     * @param ip the ImagePlus of the stack to be represented
     * @param projType projection type as an integer
     * @return an ImagePlus containing the xy view
    */
     public ImagePlus getXYview(ImagePlus ip, int projType){
        Calibration cal=ip.getCalibration();
        ip.setCalibration(new Calibration());
        
        ZProjector zp=new ZProjector(ip);
        zp.setMethod(projType);
        zp.doProjection();
        ImagePlus output=zp.getProjection();

        ip.setCalibration(cal);
        output.setCalibration(cal);

        return output;
    }

    /**
     * getXZview returns a "side-view" of a stack
     * @param ip the ImagePlus of the stack to be represented
     * @param projType projection type as an integer
     * @param keepCalibration true to take into account the disparity of calibration in XY/Z, false otherwise
     * @return an ImagePlus containing the xz view
    */
     public ImagePlus getXZview(ImagePlus ip, int projType, boolean keepCalibration){
        return sideView(ip, projType, keepCalibration, XZ_VIEW);
    }

    /**
     * getYZview returns a "side-view" of a stack
     * @param ip the ImagePlus of the stack to be represented
     * @param projType projection type as an integer
     * @param keepCalibration true to take into account the disparity of calibration in XY/Z, false otherwise
     * @return an ImagePlus containing the yz view
    */
     public ImagePlus getYZview(ImagePlus ip, int projType, boolean keepCalibration){
        return sideView(ip, projType, keepCalibration, YZ_VIEW);
    }

     /**
      * getPanelView generates and returns a panel containing the XY, YZ and XZ view (MIP)
      * @param ip the ImagePlus of the stack from hich to extract the panel
      * @param projType projection type as an integer
      * @param keepCalibration true to take into account the disparity of calibration in XY/Z, false otherwise
      * @param addScaleBar true to draw a scale bar on the XY view
      * @param size size of the scale bar in the Calibration's unit
      * @param addCross true to draw a cross on all views
      * @param coordCross array of integers containing the 3 coordinates to be used for drawing the cross
      * @param crossRadius width and height of the cross to be drawn
      * @return an ImagePlus containing the panel view
      */
     public ImagePlus getPanelView(ImagePlus ip, int projType, boolean keepCalibration, boolean addScaleBar, int size, boolean addCross, double[] coordCross, int crossRadius){
         Font font=new Font("Times New Roman", Font.BOLD, 12);
         Calibration cal=ip.getCalibration();
         double xzRatio=cal.pixelDepth/cal.pixelWidth;
         double yzRatio=cal.pixelDepth/cal.pixelHeight;

         ImageProcessor xy=getXYview(ip, projType).getProcessor();
         if (addCross){
             int[] coord=new int[2];
             coord[0]=(int) (coordCross[0]+.5);
             coord[1]=(int) (coordCross[1]+.5);
             addCross(xy, coord, crossRadius);
         }
         xy.setColor(Color.white);
         xy.setFont(font);
        // xy.drawString("XY", 3, 15);
        // if (addScaleBar) addScaleBar(xy, cal, size);
         
         if(ip.getStack().getSize() == 1) {
         	return new ImagePlus("",new ByteProcessor(20,20));
         }
         ImageProcessor xz=getXZview(ip, projType, keepCalibration).getProcessor();
         if (addCross){
             int[] coord=new int[2];
             coord[0]=(int) (coordCross[0]+.5);
             coord[1]=keepCalibration? (int) (xzRatio*(coordCross[2]+.5)):(int) (coordCross[2]+.5);
             addCross(xz, coord, crossRadius);
         }
         xz.setColor(Color.white);
         xz.setFont(font);
        // xz.drawString("XZ", 3, 15);
         
         ImageProcessor yz=getYZview(ip, projType, keepCalibration).getProcessor().rotateRight();
         yz.flipHorizontal();
         if (addCross){
             int[] coord=new int[2];
             coord[0]=keepCalibration? (int) (yzRatio*(coordCross[2]+.5)):(int) (coordCross[2]+.5);
             coord[1]=(int) (coordCross[1]+.5);
             addCross(yz, coord, crossRadius);
         }
         yz.setColor(Color.white);
         yz.setFont(font);
        // yz.drawString("YZ", 3, 15);
         

         ImageProcessor iproc=xy.createProcessor(xy.getWidth()+10+yz.getWidth(), xy.getHeight()+10+xz.getHeight());
         iproc.setColorModel(iproc.getDefaultColorModel());
         iproc.setColor(Color.white);
         iproc.fill();
         iproc.insert(xy, 0, 0);
         iproc.insert(yz, xy.getWidth()+10, 0);
         iproc.insert(xz, 0, xy.getHeight()+10);
         
         return new ImagePlus ("Panel view", iproc);
     }

     /**
      * Generates either the XZ or YZ side-view over the ImagePlus, taking into account the calibration or not
      * @param ip the ImagePlus of the stack to be represented
      * @param projection type as an integer
      * @param keepCalibration true to take into account the disparity of calibration in XY/Z, false otherwise
      * @param view should be 0 for XZ view and 1 for YZ view
      * @return an ImagePlus containing the view
      */
     private ImagePlus sideView(ImagePlus ip, int projType, boolean keepCalibration, int view){
        Calibration cal=ip.getCalibration();

        ip.setCalibration(new Calibration());
        
        ImagePlus reslicedStack=null;
        
        if (view==XZ_VIEW){
            reslicedStack=new Slicer().reslice(ip);
        }else{
            for (int i=0; i<ip.getWidth(); i++){
                Line line=new Line(i, 0, i, ip.getHeight()-1);
                ip.setRoi(line);
                ImagePlus slice=new Slicer().reslice(ip);
                if (i==0) reslicedStack=NewImage.createImage("YZ view", slice.getWidth(), slice.getHeight(), ip.getWidth(), slice.getBitDepth(), NewImage.FILL_BLACK);
                reslicedStack.setSlice(i+1);
                reslicedStack.setProcessor("YZ view", slice.getProcessor());
            }
            ip.killRoi();
        }
        ip.setCalibration(cal);
        
        ZProjector zp=new ZProjector(reslicedStack);
        zp.setMethod(projType);
        zp.doProjection();

        ImagePlus output=zp.getProjection();
        
        if (keepCalibration){
            ImageProcessor iproc=output.getProcessor();
            iproc.setInterpolate(true);

            if (view==XZ_VIEW){
                iproc=iproc.resize(output.getWidth(), (int) (output.getHeight()*cal.pixelDepth/cal.pixelWidth));
            }else{
                iproc=iproc.resize(output.getWidth(), (int) (output.getHeight()*cal.pixelDepth/cal.pixelHeight));
            }
            output=new ImagePlus("sideView", iproc);
        }else{
            if (view==XZ_VIEW){
                cal.pixelHeight=cal.pixelDepth;
                cal.pixelDepth=1;
            }else{
                cal.pixelWidth=cal.pixelHeight;
                cal.pixelHeight=cal.pixelDepth;
                cal.pixelDepth=1;
            }
            output.setCalibration(cal);
        }

        return output;
     }

     /**
      * Draws a scale bar on the ImageProcessor used as argument (adapted from the original ImageJ ScaleBar class)
      * @param ip ImageProcessor on which to draw the scale bar
      * @param cal Calibration of the current ImageProcessor
      * @param barWidth width of the scale bar expressed in the current Calibration's units
      */
     public void addScaleBar(ImageProcessor ip, Calibration cal, int barWidth){
         int fraction =20;

         int barWidthInPixels=(int) (barWidth/cal.pixelWidth);
         int barHeightInPixels=4;

         String barString=barWidth+" "+cal.getUnits();

         int stringWidth=ip.getStringWidth(barString);
         int fontSize=12;

         int width=ip.getWidth();
         int height=ip.getHeight();
         int x=width-width/fraction-barWidthInPixels;
         int y=height-height/fraction-barHeightInPixels-fontSize;
         int xOffset=(int) (barWidthInPixels-stringWidth)/2;
         int yOffset=(int) (barHeightInPixels+fontSize+fontSize/4);

         ip.setColor(Color.white);
         ip.setRoi(x, y, barWidthInPixels, barHeightInPixels);
         ip.fill();
         ip.drawString(barString, x+xOffset, y+yOffset);
     }

     /**
      * Draws a cross on the ImageProcessor used as argument
      * @param ip ImageProcessor on which to draw the cross
      * @param coord a 2D array containing the coordinated of the centre of the cross
      * @param radius width/height of the cross
      */
     public void addCross(ImageProcessor ip, int[] coord, int radius){
         ip.setColor(Color.white);
         ip.setLineWidth((int) Math.max(2, Math.max(ip.getWidth(), ip.getHeight())/500));
         ip.multiply(0.5);
         ip.drawLine(coord[0], (int) (coord[1]-radius), coord[0], (int) (coord[1]+radius));
         ip.drawLine((int) (coord[0]-radius), coord[1], (int) (coord[0]+radius), coord[1]);
     }


} public class FileUtils {

	/**
	 * The main method.
	 * 
	 * @param args
	 *           the arguments
	 */
	public static void main(String[] args) {

		JFrame frame = new JFrame();
		//System.setProperty("apple.awt.fileDialogForDirectories", "true");

		System.out.println(getDirectory("/home/cyril/test_img/",
				"/home/cyril/test_img/"));
		System.out.println(FileUtils
				.getFileNameWithoutExtension("tattayoyo.13fdsfs.fsd.tif"));
	}

	/**
	 * Folder exists.
	 * 
	 * @param folder
	 *           the folder
	 * @return true, if successful
	 */
	public static boolean folderExists(String folder) {
		File file = new File(folder);
		return file.exists();

	}

	/**
	 * Delete file.
	 * 
	 * @param path
	 *           the path
	 * @return true, if successful
	 */
	public static boolean deleteFile(String path) {
		File file = new File(path);

		if (file.isDirectory()) {
			for (File subfile : file.listFiles()) {
				deleteFile(subfile.getAbsolutePath());
			}
		}

		file.delete();

		return true;
	}

	/**
	 * Creates the folder.
	 * 
	 * @param folder
	 *           the folder
	 * @return true, if successful
	 */
	public static boolean createFolder(String folder) {
		File file = new File(folder);
		if (!file.exists()) {
			try {
				file.mkdirs();
			} catch (Exception e) {
				e.printStackTrace();
				IJ.error("Error when creating folder : " + folder);
				return false;
			}
		}
		return true;
	}

	/**
	 * Open folder.
	 * 
	 * @param folder
	 *           the folder
	 */
	public static void openFolder(String folder) {
		Desktop desktop = Desktop.getDesktop();

		try {
			desktop.open(new File(folder));
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * Gets the file name without extension.
	 * 
	 * @param filename
	 *           the filename
	 * @return the file name without extension
	 */
	public static String getFileNameWithoutExtension(String filename) {
		Pattern pattern = Pattern.compile("(.*)[.]\\w*$");

		Matcher m = pattern.matcher(filename);

		if (m.matches()) {
			return m.group(1);
		} else
			return filename;

	}

	/**
	 * Gets the today date.
	 * 
	 * @return the today date
	 */
	public static String getTodayDate() {
		Date d = new Date();
		return d.toString();
	}

	/**
	 * Gets the directory.
	 * 
	 * @param title
	 *           the title
	 * @param def
	 *           the def
	 * @return the directory
	 */
	public static String getDirectory(String title, String def) {
		// Java2.setSystemLookAndFeel();
		
		if (!IJ.isWindows()) {

		//	System.setProperty("apple.awt.fileDialogForDirectories", "true");

			JFrame frame = new JFrame();
			System.setProperty("apple.awt.fileDialogForDirectories", "true");

			if (def == null || new File(def).exists() == false) {
				def = System.getProperty("user.home");
			}

			FileDialog d = new FileDialog(frame);

			d.setTitle(title);
			d.setDirectory(new File(def).getAbsolutePath());
			d.setFile(new File(def).getName());

			d.setFilenameFilter(new FilenameFilter() {

				@Override
				public boolean accept(File dir, String name) {
					// TODO Auto-generated method stub
					return new File(dir.getAbsolutePath() + name).isDirectory();
				}

			});

			//d.setMultipleMode(false);

			d.setMode(FileDialog.LOAD);

			d.setVisible(true);

			frame.dispose();
			if(d.getFile() == null) return null;
			
			return new File(d.getDirectory() +  d.getFile()).getAbsolutePath();
			
		} else {
			JFileChooser fc = new JFileChooser();
			fc.setDialogTitle(title);
                        
			fc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
			File fdir = null;
			if (def != null)
				fdir = new File(def);
			if (fdir != null)
				fc.setCurrentDirectory(fdir);

			int returnVal = fc.showSaveDialog(null);
			if (returnVal != JFileChooser.APPROVE_OPTION)
				return null;

			return fc.getSelectedFile().getAbsolutePath();
		}
		
	}

	/**
	 * Load image ressource.
	 * 
	 * @param source
	 *           the source
	 * @param file
	 *           the file
	 * @return the buffered image
	 */
	public static BufferedImage loadImageRessource(Object source, String file) {
		BufferedImage image = null;
		try {

			image = ImageIO.read(source.getClass().getResource(file));

		} catch (IOException ioe) {
			System.out.println(ioe.getMessage());
			try {
				image = ImageIO.read(source.getClass().getResource("src" + file));

			} catch (IOException e) {
				System.out.println(e.getMessage());
			}
		}

		return image;

	}

} public class MathUtils {

	
	
	/** The micrometers. */
	public static String MICROMETERS = "um";
	
	/** The degrees. */
	public static String DEGREES = "degrees";
	
	/** The degree symbol. */
	public static String DEGREE_SYMBOL = "°";
	
	/** The radiant. */
	public static String RADIANT = "rad";
	
	/** The pixels. */
	public static String PIXELS = "pixels";
	
	/** The pixel. */
	public static String PIXEL = "pixel";
	
	/** The plus minus. */
	public static String PLUS_MINUS = "+/-";
	//public static String PLUS_MINUS = "±";
	/** The units. */
	public static String[] units = new String[] { "m", "mm", MICROMETERS, "nm", "pm" };

	/**
	 * Round.
	 *
	 * @param d the d
	 * @return the int
	 */
	public static int round(double d) {
		return (int) Math.round(d);
			
		
		
		//return Math.round(new Float(d));
	}

	
	
	/**
	 * Round.
	 *
	 * @param nb2round the nb2round
	 * @param nbOfDigits the nb of digits
	 * @return the double
	 */
	public static double round(double nb2round, int nbOfDigits) {
		return Math.round(nb2round * Math.pow(10, nbOfDigits))
				/ Math.pow(10, nbOfDigits);
	}

	/**
	 * Round to string.
	 *
	 * @param nb2round the nb2round
	 * @param nbOfDigits the nb of digits
	 * @return the string
	 */
	public static String roundToString(double nb2round, int nbOfDigits) {
		if(nbOfDigits == 0) return ""+Math.round(nb2round);
		return new Double(round(nb2round, nbOfDigits)).toString();
	}

	/**
	 * Gets the lower unit.
	 *
	 * @param unit the unit
	 * @return the lower unit
	 */
	public static String getLowerUnit(String unit) {
		for (int i = 0; i != units.length; i++) {
			if (unit.equals(units[i]) && i != units.length - 1) {
				return units[i + 1];
			}
		}
		//System.err.println("Error when getting lower unit. Returning");
		return unit;
	}
	
	
	/**
	 * Format double.
	 *
	 * @param number the number
	 * @param unit the unit
	 * @return the string
	 */
	public static String formatDouble(double number, String unit) {
		return formatDouble(number, unit,-1);
	}
	
	/**
	 * Format double.
	 *
	 * @param number the number
	 * @param unit the unit
	 * @param numbersAfterDot the numbers after dot
	 * @return the string
	 */
	public static String formatDouble(double number, String unit,int numbersAfterDot) {
		if(unit.equals("")) {
			if(numbersAfterDot < 0) numbersAfterDot = 3;
			return MathUtils.roundToString(number, numbersAfterDot);
		}
		
		
		//
		else if (Math.abs(number) >= 100) {
			if(numbersAfterDot < 0 ) numbersAfterDot = 0;
			return MathUtils.roundToString(number, numbersAfterDot) + " " + unit;
		} else if (Math.abs(number)  < 100 && Math.abs(number)  > 10) {
			
			if(numbersAfterDot < 0 ) numbersAfterDot = 1;
			String result = MathUtils.roundToString(number, numbersAfterDot);
			
			/*
			if((1.0*MathUtils.round(number)) - number == 0.0) {
				result = ""+round(number);
			}*/
			return result + " " + unit;

		}

		else if (Math.abs(number)  < 10 && Math.abs(number)  > 1) {
			if(numbersAfterDot < 0) numbersAfterDot = 2;
			return MathUtils.roundToString(number, numbersAfterDot) + " " + unit;
		}
		else {
			
			if(getLowerUnit(unit).equals(unit)) {
				return MathUtils.roundToString(number, 0);
			}
			return formatDouble(number * 1000, getLowerUnit(unit),numbersAfterDot);
		}
	}

	/**
	 * Format statistics.
	 *
	 * @param stats the stats
	 * @param unit the unit
	 * @return the string
	 */
	public static String formatStatistics(DescriptiveStatistics stats, String unit) {
      String value = MathUtils.formatDouble(stats.getMean(), unit);
      System.out.println("number after dot : "+getNumberAfterDot(value));
      String variation = MathUtils.formatDouble(stats.getStandardDeviation(), unit,getNumberAfterDot(value));
      
      
      

      return String.format("%s %s %s",value,PLUS_MINUS,variation) ;
  }
	
	
	/**
	 * The main method.
	 *
	 * @param args the arguments
	 */
	public static void main(String[] args) {
		System.out.println(MathUtils.round(1.44));
		System.out.println(MathUtils.round(1.49999));
		System.out.println(MathUtils.formatDouble(0.1, "um"));
		System.out.println(MathUtils.formatDouble(0.0034553,MICROMETERS));
		System.out.println(MathUtils.formatDouble(0.033454,MICROMETERS));
		System.out.println(MathUtils.formatDouble(0.31123,MICROMETERS));
		System.out.println(MathUtils.formatDouble(3.1123,MICROMETERS));
		System.out.println(MathUtils.formatDouble(30.05,MICROMETERS));
		System.out.println(MathUtils.formatDouble(300.404,MICROMETERS));
		System.out.println(MathUtils.formatDouble(17.0,MICROMETERS,1));
		
		DescriptiveStatistics stats = new DescriptiveStatistics();
		
		for(int i = 0; i!= 100;i++) {
			stats.addValue(100 + 1.0*i/12);
		}
		System.out.println(stats);
		System.out.println(MathUtils.formatStatistics(stats, DEGREES));
		System.out.println(MathUtils.getNumberAfterDot("12.534 nm"));
		
		System.out.println(MathUtils.getNumberAfterDot("12.53"));
		System.out.println(MathUtils.getNumberAfterDot("12 nm"));
		System.out.println(MathUtils.getNumberAfterDot("12.34 nm"));
	}
	
	/**
	 * Checks if is metric unit.
	 *
	 * @param unit the unit
	 * @return true, if is metric unit
	 */
	public static boolean isMetricUnit(String unit) {
		for (String u : units) {
			
			if(u.equals(unit)) return true;
		}
		return false;
	}
	
	
	/**
	 * Gets the number after dot.
	 *
	 * @param number the number
	 * @return the number after dot
	 */
	public static int getNumberAfterDot(String number) {
		
		System.out.println(number + " -> ");
		
		int dotPosition = number.indexOf(".");
		int spacePosition = number.indexOf(" ") - 1;
		
		if(dotPosition < 0) return 0;
		if(spacePosition < 0) spacePosition = number.length()-1;
		return spacePosition - dotPosition;
		
		
		
		
	}
	
	
} public class Message {
	
	/** The data. */
	Object data;
	
	/** The origin. */
	Object origin;
	
	/** The name. */
	String name;
	
	/** The int data. */
	Integer intData;
	
	/** The string data. */
	String stringData;
	
	
	
	
	/**
	 * Instantiates a new message.
	 *
	 * @param origin the origin
	 * @param name the name
	 */
	public Message(Object origin, String name) {
		setName(name);
		setOrigin(origin);
	}
	
	/**
	 * Instantiates a new message.
	 *
	 * @param origin the origin
	 * @param name the name
	 * @param intData the int data
	 */
	public Message(Object origin, String name, Integer intData) {
		this(origin,name);
		setIntData(intData);
	}
	
	
	
	/**
	 * Instantiates a new message.
	 *
	 * @param origin the origin
	 * @param name the name
	 * @param stringData the string data
	 * @param intData the int data
	 * @param obj the obj
	 */
	public Message(Object origin, String name, String stringData, Integer intData, Object obj) {
		this(origin,name);
		setStringData(stringData);
		setIntData(intData);
		setData(obj);
	}
	
	/**
	 * Instantiates a new message.
	 *
	 * @param origin the origin
	 * @param name the name
	 * @param stringData the string data
	 * @param intData the int data
	 */
	public Message(Object origin, String name, String stringData, Integer intData) {
		this(origin,name,stringData,intData,null);
	}
	
	
	/**
	 * Instantiates a new message.
	 *
	 * @param origin the origin
	 * @param name the name
	 * @param stringData the string data
	 */
	public Message(Object origin, String name, String stringData) {
		this(origin,name);
		setStringData(stringData);
	}
	
	/**
	 * Instantiates a new message.
	 *
	 * @param origin the origin
	 * @param name the name
	 * @param obj the obj
	 */
	public Message(Object origin, String name, Object obj) {
		this(origin,name);
		setData(obj);
	}
	
	
	/**
	 * Gets the data.
	 *
	 * @return the data
	 */
	public Object getData() {
		return data;
	}
	
	/**
	 * Sets the data.
	 *
	 * @param data the new data
	 */
	public void setData(Object data) {
		this.data = data;
	}
	
	/**
	 * Gets the origin.
	 *
	 * @return the origin
	 */
	public Object getOrigin() {
		return origin;
	}
	
	/**
	 * Sets the origin.
	 *
	 * @param origin the new origin
	 */
	public void setOrigin(Object origin) {
		this.origin = origin;
	}
	
	/**
	 * Gets the name.
	 *
	 * @return the name
	 */
	public String getName() {
		return name;
	}
	
	/**
	 * Sets the name.
	 *
	 * @param name the new name
	 */
	public void setName(String name) {
		this.name = name;
	}
	
	/**
	 * Gets the int data.
	 *
	 * @return the int data
	 */
	public Integer getIntData() {
		return intData;
	}
	
	/**
	 * Sets the int data.
	 *
	 * @param intData the new int data
	 */
	public void setIntData(Integer intData) {
		this.intData = intData;
	}
	
	/**
	 * Gets the string data.
	 *
	 * @return the string data
	 */
	public String getStringData() {
		return stringData;
	}
	
	/**
	 * Sets the string data.
	 *
	 * @param stringData the new string data
	 */
	public void setStringData(String stringData) {
		this.stringData = stringData;
	}

	
	/* (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	public String toString() {
		
		String result = "";
		result += "[Message]";
		result += "\nOrigin : "+getOrigin().getClass();
		result += "\nName : " +getName();
		result += "\nInt : " + getIntData();
		result += "\nString : " + getStringData();
		if(getData() != null) {
			result += "\nData : " + getData().getClass();
		}
		result += "\n";
		
		return result;
		
	}
	
	
	
	
}
import ij.ImagePlus;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.util.Observable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import knop.psfj.BeadFrame;
import knop.psfj.BeadFrame2D;
import knop.psfj.BeadFrameList;
import knop.psfj.BeadImage;
import knop.psfj.BeadImageManager;
import knop.psfj.resolution.Microscope;
import knop.psfj.resolution.ReportSections;
import knop.psfj.resolution.SideViewGenerator;
import knop.psfj.utils.FileUtils;
import knop.psfj.utils.MathUtils;
import knop.psfj.view.Message;
import com.lowagie.text.Document;
import com.lowagie.text.DocumentException;
import com.lowagie.text.Image;
import com.lowagie.text.Paragraph;
import com.lowagie.text.pdf.PdfWriter;
/*
    This file is part of PSFj.

    PSFj is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    PSFj 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 PSFj.  If not, see <http://www.gnu.org/licenses/>. 
    
	Copyright 2013,2014 Cyril MONGIS, Patrick Theer, Michael Knop
	
 */
package knop.psfj.exporter;





// TODO: Auto-generated Javadoc
/**
 * The Class PDFExporter.
 */
public class PDFExporter extends Observable {

	/** The manager. */
	BeadImageManager manager;

	/** The svg. */
	SideViewGenerator svg = new SideViewGenerator();

	/** The current document. */
	Document currentDocument;
	
	/** The current writer. */
	PdfWriter currentWriter;

	/** The total count. */
	int totalCount;
	
	/** The actual count. */
	int actualCount = 0;
	
	
	/**
	 * The main method.
	 *
	 * @param arg the arguments
	 */
	public static void main(String[] arg) {

		BeadImageManager manager = new BeadImageManager();
		manager.add("/home/cyril/test_img/6/6_gfp.tif");
		//manager.add("/home/cyril/test_img/6/6_mcherry.tif");

		manager.processProfiles();

		manager.exportInPDF(true);

	}

	/**
	 * Gets the manager.
	 *
	 * @return the manager
	 */
	public BeadImageManager getManager() {
		return manager;
	}

	/**
	 * Sets the manager.
	 *
	 * @param manager the new manager
	 */
	public void setManager(BeadImageManager manager) {
		this.manager = manager;
		totalCount = 0;
		for(BeadImage image : manager.getBeadImageList()) totalCount+=image.getTotalBeadCount();
	}

	/**
	 * Instantiates a new PDF exporter.
	 *
	 * @param manager the manager
	 */
	public PDFExporter(BeadImageManager manager) {
		setManager(manager);
	}

	
	/**
	 * Increment count.
	 */
	public void incrementCount() {
		actualCount ++;
		if(actualCount % 10 == 0 || actualCount == 0) {
			setChanged();
			Integer progress = new Integer(100 * actualCount
					/ totalCount);
			if(progress == 0) progress = 1;
			notifyObservers(new Message(manager, "progress changed",
					String.format("Generating PDF report : %d/%d",actualCount,totalCount), progress));
			
		}
		
		if(actualCount == totalCount) {
			setChanged();
			notifyObservers(new Message(manager, "progress changed",
					"Done.", new Integer(0)));
		}
		
	}
	
	
	
	
	
	/**
	 * Write single file report.
	 */
	public void writeSingleFileReport() {
		
		
		ExecutorService executor = Executors.newFixedThreadPool(2);
		
		
		for(BeadImage image : manager.getBeadImageList()) {
			int beadPerPage = 100;
			int start = 0;
			int pageCount = 1;
			BeadFrameList frameList = image.getBeadFrameList();
			BeadFrameList toDo = frameList.get(start, beadPerPage);
			
			while(toDo.size() > 0) {
				String path = manager.getExportDirectory()+image.getImageNameWithoutExtension()+"_beads_page_"+pageCount+".pdf";
				executor.execute(writeSingleFileReport(toDo, path, pageCount));
				
				pageCount++;
				start+=beadPerPage;
				toDo = frameList.get(start, beadPerPage);
			}
			
		
			
		
			
		}
		executor.shutdown();
		try {
			executor.awaitTermination(1, TimeUnit.DAYS);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		sendFinishMessage("Done.");
		
	}
	
	
	/**
	 * Send finish message.
	 *
	 * @param message the message
	 */
	public void sendFinishMessage(String message) {
		manager.setProgress("Done", 0);
	}
	
	
	/**
	 * Write single file report.
	 *
	 * @param frameList the frame list
	 * @param path the path
	 * @param page the page
	 * @return the runnable
	 */
	public Runnable writeSingleFileReport(final BeadFrameList frameList, final String path, int page) {
		return new Runnable() {
			public void run() {
				
				Document document = null;
				PdfWriter writer = null;
				
				
				
				if (FileUtils.folderExists(path))