View Javadoc

1   /**
2    * 
3    */
4   package de.desy.acop.video.analysis;
5   
6   
7   import java.awt.Rectangle;
8   import java.awt.image.BufferedImage;
9   import java.beans.PropertyChangeEvent;
10  import java.beans.PropertyChangeListener;
11  import java.beans.PropertyChangeSupport;
12  import java.util.concurrent.Executor;
13  import java.util.concurrent.Executors;
14  import java.util.concurrent.RejectedExecutionHandler;
15  import java.util.concurrent.ThreadFactory;
16  import java.util.concurrent.ThreadPoolExecutor;
17  import java.util.concurrent.TimeUnit;
18  import java.util.logging.Level;
19  
20  import de.desy.acop.displayers.tools.DismissableBlockingQueue;
21  import de.desy.acop.displayers.tools.TINEThreadFactory;
22  import de.desy.acop.transport.ConnectionFailed;
23  import de.desy.acop.transport.ConnectionParameters;
24  import de.desy.acop.video.VideoServerConnection;
25  import de.desy.acop.video.displayer.ImageFormat;
26  import de.desy.acop.video.displayer.TineHandler;
27  import de.desy.acop.video.displayer.TineImageReceiver;
28  import de.desy.acop.video.displayer.timage.TImageConverter;
29  import de.desy.acop.video.timageio.TBufferedImage;
30  import de.desy.tine.types.IMAGE;
31  import de.desy.tine.types.IMAGE.FrameHeader;
32  
33  /**
34   * </code>ImageAnalysisEngine</code> provides </code>AImage</code> image analysis 
35   * data. It has two modes of operation: local and remote analysis. In local analysis
36   * mode, the </code>IMAGE</code> arguments of the </code>updateValue</code> method
37   * are processed locally. In remote mode this engine reads </code>AImage</code> data
38   * from a video analysis server. 
39   * 
40   * @author Tilen Kusterle, Cosylab
41   *
42   */
43  @SuppressWarnings("deprecation")
44  public class ImageAnalysisEngine implements TineImageReceiver {
45  
46  	private class AnalysisExecutor implements Runnable {
47  		final int iW;
48  		final int iH;
49  		final int rX;
50  		final int rY;
51  		final int rW;
52  		final int rH;
53  		final int r2X;
54  		final int r2Y;
55  		final int r2W;
56  		final int r2H;
57  		final double th;
58  		final IMAGE im;
59  		final double[] bg;
60  		final boolean calculateThreshold;
61  		final boolean performFit;
62  		final boolean smoothing;
63  		public AnalysisExecutor(IMAGE im, double[] bg, int iW, int iH, int rX, int rY, int rW, int rH, 
64  				int r2X, int r2Y, int r2W, int r2H, double threshold, boolean calculateThreshold, boolean performFit, boolean smoothing) {
65  			this.iW = iW;
66  			this.iH = iH;
67  			this.rX = rX;
68  			this.rY = rY;
69  			this.rW = rW;
70  			this.rH = rH;
71  			this.th = threshold;
72  			this.im = im;
73  			this.bg = bg;
74  			this.r2X = r2X;
75  			this.r2Y = r2Y;
76  			this.r2W = r2W;
77  			this.r2H = r2H;
78  			this.calculateThreshold = calculateThreshold;
79  			this.performFit = performFit;
80  			this.smoothing = smoothing;
81  		}
82  		public void run() {
83  			analyzeImage(im, bg, iW, iH, rX, rY, rW, rH, r2X, r2Y, r2W, r2H, th, calculateThreshold, performFit,smoothing);
84  		}
85  	}
86  	
87  	public static final String PROPERTY_A_IMAGE = "aImage";
88  	public static final String PROPERTY_BACKGROUND = "background";
89  	public static final String PROPERTY_PRECISE_BACKGROUND = "preciseBackground";
90  	public static final String PROPERTY_RESET = "reset";
91  		
92  	private PropertyChangeSupport pcSupport;
93  	private Executor executor;
94  	private ThreadPoolExecutor analysisExecutor;
95  	private boolean localAnalysis = true;
96  	
97  	private VideoServerConnection videoServerConnection;
98  	
99  //	private TImageConverter imageConverter = new TImageConverter(); // mdavid: added
100 	
101 	private ColorDecoder decoder;
102 	
103 	private AImage aImage;
104 	private IMAGE image;
105 	private IMAGE background;
106 	private double[] preciseBackground;
107 	private int imageWidth = -1;
108 	private int imageHeight = -1;
109 	private int backgroundWidth = -1;
110 	private int backgroundHeight = -1;
111 	private int roiX = -1;
112 	private int roiY = -1;
113 	private int roiW = -1;
114 	private int roiH = -1;
115 	private int roi2X = -1;
116 	private int roi2Y = -1;
117 	private int roi2W = -1;
118 	private int roi2H = -1;
119 	private double threshold = Double.NaN;
120 	private boolean calculateThreshold = true;
121 	private boolean performFit = false;
122 	private boolean performSmoothing = false;
123 	
124 //	private double[] previousStartValuesX = new double[5];
125 //	private double[] previousStartValuesY = new double[5];
126 	private double[] previousStartValuesX = null;
127 	private double[] previousStartValuesY = null;
128 	/**
129 	 * Constructs this </code>ImageAnalysisEngine</code>. 
130 	 * @param decoder the </code>ColorDecoder</code> to be used with this 
131 	 * </code>ImageAnalysisEngine</code>
132 	 */
133 	public ImageAnalysisEngine(ColorDecoder decoder) {
134 		this.decoder = decoder;
135 	}
136 		
137 	@Override
138 	public void resetForReceiving() {
139 		getPcSupport().firePropertyChange(PROPERTY_RESET, false, true);
140 	}
141 
142 	@Override
143 	public synchronized void updateValue(IMAGE newImage) {
144 		if (!localAnalysis) return;
145 //		logger.log(Level.INFO,"New update received.");
146 //		image = TImageUtils.clone(newImage); // mdavid: commented
147 		image = newImage.clone();
148 
149 		FrameHeader hdr = image.getFrameHeader();
150 		int w = hdr.aoiWidth == -1 ? hdr.sourceWidth : hdr.aoiWidth;
151 		int h = hdr.aoiHeight == -1 ? hdr.sourceHeight : hdr.aoiHeight;
152 		
153 		if (w != imageWidth || h != imageHeight) {
154 			roiX = 0;
155 			roiY = 0;
156 			roiW = w;
157 			roiH = h;
158 			roi2X = 0;
159 			roi2Y = 0;
160 			roi2W = w;
161 			roi2H = h;
162 		}
163 		
164 		imageWidth = w;
165 		imageHeight = h;
166 
167 		if ((imageWidth != backgroundWidth || imageHeight != backgroundHeight) && background != null) {
168 			setBackground(null);
169 		}
170 		else performAnalysis();
171 	}
172 	
173 	/**
174 	 * Sets the precise background array. This is the array that holds values per pixels
175 	 * for whole background image, but instead of int values, pixel values are given as
176 	 * luminosity doubles. Negative values are permitted and will be taken into account
177 	 * during analysis, but forced to 0, when real image is constructed.
178 	 * 
179 	 * @param background the pixel value array
180 	 * @param width the width of the image
181 	 */
182 	public synchronized void setPreciseBackground(double[] background, int width) {
183 		TineHandler.logger.log(Level.INFO, "Background image set.");
184 		setPreciseBackground(background,width, true);
185 	}
186 	
187 	/**
188 	 * Returns the double array that is used as background image.
189 	 * 
190 	 * @see #setPreciseBackground(double[], boolean)
191 	 * @return the double array image
192 	 */
193 	public synchronized double[] getPreciseBackground() {
194 		return preciseBackground;
195 	}
196 	
197 	/**
198 	 * Returns the width of the background image.
199 	 * 
200 	 * @return the width
201 	 */
202 	public synchronized int getBackgroundWidth() {
203 		return backgroundWidth;
204 	}
205 	
206 	/**
207 	 * Sets the background image and initiates required actions.
208 	 * 
209 	 * @param background the background image array
210 	 * @param width the width of the image
211 	 * @param reanalyze true if the image should be reanalyzed
212 	 */
213 	private synchronized void setPreciseBackground(final double[] preciseBackground, final int width, boolean reanalyze) {
214 		this.background = null;
215 		this.preciseBackground = null;
216 		backgroundWidth = -1;
217 		backgroundHeight = -1;
218 		if (preciseBackground != null) {
219 			int w = width;
220 			int h = preciseBackground.length/width;
221 			
222 			if (w == imageWidth && h == imageHeight) {
223 				backgroundWidth = w;
224 				backgroundHeight = h;
225 				this.preciseBackground = preciseBackground;
226 				this.background = toImage(preciseBackground,width);
227 			}
228 		}
229 		
230 		if (reanalyze) {
231 			if (localAnalysis) performAnalysis();
232 			else {
233 				executeTask(new Runnable() {
234 					@Override
235 					public void run() {
236 						try {
237 							getVideoServerConnection().setBackground(background);
238 						} catch (ConnectionFailed e) {
239 							e.printStackTrace();
240 						}
241 					}
242 				});
243 			}
244 			getPcSupport().firePropertyChange(PROPERTY_PRECISE_BACKGROUND, null, this.preciseBackground);
245 			getPcSupport().firePropertyChange(PROPERTY_BACKGROUND, null, this.background);
246 		}
247 	}
248 	
249 	/**
250 	 * Transforms the double array to a Gray image.
251 	 * 
252 	 * @param image the image to transform 
253 	 * @param width the width of the image
254 	 * @return generated IMAGE
255 	 */
256 	public static IMAGE toImage(double[] image,int width) {
257 		if (image == null || image.length == 0) return null;
258 		if (width < 1 || image.length/width < 1) return null;
259 		BufferedImage bi = toBufferedImage(image,width);
260 //		return TBufferedImage.toIMAGE(bi);
261 		return TImageConverter.parseBufferedImage(bi);
262 	}	
263 	
264 	/**
265 	 * Transforms teh double array to a gray buffered image.
266 	 * 
267 	 * @param image the double image array 
268 	 * @param width the width of the image
269 	 * @return buffered image
270 	 */
271 	public static BufferedImage toBufferedImage(double[] image,int width) {
272 		if (image == null || image.length == 0) return null;
273 		if (width < 1 || image.length/width < 1) return null;
274 		int i = 0;
275 		int length = image.length;
276 		int[] rgb = new int[length];
277 		try {
278 			int alpha = (255 << 24);
279 			int val;
280     		for (;;) {
281     			val = (int)(image[i]+0.5);
282     			if (val < 0) val = 0;
283     			rgb[i] =  alpha + (val << 16) + (val << 8) + (val);
284     			i++;
285     		}
286 		} catch (ArrayIndexOutOfBoundsException e) {
287 			//ignore
288 		}
289 		BufferedImage bi = new BufferedImage(width,length/width,BufferedImage.TYPE_BYTE_GRAY);
290 		
291 		bi.setRGB(0,0,width,length/width,rgb,0,width);
292 		return bi;
293 	}	
294 	
295 	/**
296 	 * Gets the background image used by this </code>ImageAnalysisEngine</code>.
297 	 * @return the background image
298 	 */
299 	public synchronized IMAGE getBackground() {
300 		return background;
301 	}
302 		
303 	/**
304 	 * Sets the background image to be used by this </code>ImageAnalysisEngine</code>.
305 	 * @param background the image to set
306 	 */
307 	public synchronized void setBackground(IMAGE background) {
308 		TineHandler.logger.log(Level.INFO, "Background image set.");
309 		setBackground(background, true);
310 		this.background = background;
311 	}
312 	
313 	private synchronized void setBackground(final IMAGE background, boolean reanalyze) {
314 		if (background == null) {
315 			setPreciseBackground(null,-1);
316 		} else {
317 			int[] rgb = toBufferedImage(background).getRGB(0, 0, background.getFrameHeader().sourceWidth, 
318 					background.getFrameHeader().sourceHeight, null, 0, background.getFrameHeader().sourceWidth);
319 			double[] data = getDecoder().transform(rgb);
320 			setPreciseBackground(data,background.getFrameHeader().sourceWidth,reanalyze);
321 			this.background = background;
322 		}
323 	}
324 	
325 	/**
326 	 * Sets the region of interest and reanalyzes pixels.
327 	 * @param roiX the starting x coordinate
328 	 * @param roiY the starting y coordinate
329 	 * @param roiW the width of the region
330 	 * @param roiH the height of the region
331 	 */
332 	public synchronized void setRoi(final int roiX, final int roiY, final int roiW, final int roiH) {
333 		if (this.roiX == roiX && this.roiY == roiY && this.roiW == roiW && this.roiH == roiH) return;
334 		TineHandler.logger.log(Level.INFO, "ROI: x="+roiX+", y="+roiY +", w="+roiW + ", h="+roiH);
335 		setAnalysisParameters(threshold,roiX,roiY,roiW,roiH,roi2X,roi2Y,roi2W,roi2H,calculateThreshold,performFit,performSmoothing);
336 	}
337 	
338 	/**
339 	 * Sets the region of interest and reanalyzes pixels.
340 	 * @param roiX the starting x coordinate
341 	 * @param roiY the starting y coordinate
342 	 * @param roiW the width of the region
343 	 * @param roiH the height of the region
344 	 */
345 	public synchronized void setRoi2(final int roi2X, final int roi2Y, final int roi2W, final int roi2H) {
346 		if (this.roi2X == roi2X && this.roi2Y == roi2Y && this.roi2W == roi2W && this.roi2H == roi2H) return;
347 		TineHandler.logger.log(Level.INFO, "Threshold ROI: x="+roi2X+", y="+roi2Y +", w="+roi2W + ", h="+roi2H);
348 		setAnalysisParameters(threshold,roiX,roiY,roiW,roiH,roi2X,roi2Y,roi2W,roi2H,calculateThreshold,performFit,performSmoothing);
349 	}
350 	
351 	/**
352 	 * Gets the </code>IMAGE</code>.
353 	 * @return the </code>IMAGE</code>
354 	 */
355 	public synchronized IMAGE getImage() {
356 		return image;
357 	}
358 	
359 	/**
360 	 * Gets the </code>AImage</code>.
361 	 * @return the </code>AImage</code>
362 	 */
363 	public synchronized AImage getAImage() {
364 		return aImage;
365 	}
366 	
367 	/**
368 	 * Returns the rectangle describing the region of interest.
369 	 * 
370 	 * @return the region of intereset
371 	 */
372 	public synchronized Rectangle getROI() {
373 		return new Rectangle(roiX,roiY,roiW,roiH);
374 	}
375 	
376 	/**
377 	 * Returns the rectangle describing the region of interest used for
378 	 * threshold calculation.
379 	 * 
380 	 * @return the threshold region of interest 
381 	 */
382 	public synchronized Rectangle getThresholdROI() {
383 		return new Rectangle(roi2X,roi2Y,roi2W,roi2H);
384 	}
385 	
386 	/**
387 	 * Gets the threshold value.
388 	 * 
389 	 * @see #setThreshold(double)
390 	 * 
391 	 * @return the threshold value
392 	 */
393 	public synchronized double getThreshold() {
394 		return threshold;
395 	}
396 	
397 	/**
398 	 * Sets the threshold value, which discards all pixels which have
399 	 * returned by <code>ColorDecoder</code> lover than threshold value. 
400 	 * Threshold value has effect only between values Const2D and Amplitude2D 
401 	 * as described by <code>AImage</code>.
402 	 * 
403 	 * @see AImage
404 	 * @see AImage#getAmplitude2D()
405 	 * @see AImage#getConst2D()
406 	 * 
407 	 * @param threshold the threshold value to set
408 	 */
409 	public synchronized void setThreshold(final double threshold) {
410 		if (this.threshold == threshold) return;
411 //		this.threshold = threshold;
412 		TineHandler.logger.log(Level.INFO, "Threshold: " + threshold);
413 		setAnalysisParameters(threshold,roiX,roiY,roiW,roiH,roi2X,roi2Y,roi2W,roi2H,calculateThreshold,performFit,performSmoothing);
414 	}
415 	
416 	/**
417 	 * Sets the calculate thershold parameter. When true the threshold values
418 	 * is calculated at each image analysis performed and the original user
419 	 * defined threshold is overridden with the calculated one.
420 	 * 
421 	 * @param calculateThreshold true if the threshold should be calculated or false
422 	 * 			otherwise
423 	 */
424 	public synchronized void setCalculateThreshold(final boolean calculateThreshold) {
425 		if (this.calculateThreshold == calculateThreshold) return;
426 		TineHandler.logger.log(Level.INFO, "Calculate threshold: " + calculateThreshold);
427 		setAnalysisParameters(roiX,roiY,roiW,roiH,roi2X,roi2Y,roi2W,roi2H,calculateThreshold,performFit,performSmoothing);
428 	}
429 	
430 	/**
431 	 * Returns true if the threshold is calculated by this engine, or false if
432 	 * the user defined the threshold to be used.
433 	 * 
434 	 * @return true if threshold calculation is automatic
435 	 */
436 	public boolean isCalculateThreshold() {
437 		return calculateThreshold;
438 	}
439 	
440 	/**
441 	 * Sets the perform fit parameter. When true fitting is performed on the data.
442 	 * When true, the time required to perform the analysis might be significantly
443 	 * longer
444 	 * 
445 	 * @param performFit true if fitting should be performed or false otherwise
446 	 */
447 	public synchronized void setPerformFit(final boolean performFit) {
448 		if (this.performFit == performFit) return;
449 		TineHandler.logger.log(Level.INFO, "Perform fit: " + performFit);
450 		setAnalysisParameters(roiX,roiY,roiW,roiH,roi2X,roi2Y,roi2W,roi2H,calculateThreshold,performFit,performSmoothing);
451 	}
452 	
453 	/**
454 	 * Sets the perform smoothing parameter. When true smoothing is performed on the side
455 	 * view data. 
456 	 * 
457 	 * @param performSmoothing true if smoothing should be performed or false otherwise
458 	 */
459 	public synchronized void setPerformSmoothing(final boolean performSmoothing) {
460 		if (this.performSmoothing == performSmoothing) return;
461 		TineHandler.logger.log(Level.INFO, "Perform smoothing: " + performSmoothing);
462 		setAnalysisParameters(roiX,roiY,roiW,roiH,roi2X,roi2Y,roi2W,roi2H,calculateThreshold,performFit,performSmoothing);
463 	}
464 	
465 	/**
466 	 * Returns true if fitting is performed by this engine, or false if
467 	 * otherwise
468 	 * 
469 	 * @return true if fitting is performed
470 	 */
471 	public boolean isPerformFit() {
472 		return performFit;
473 	}
474 	
475 	/**
476 	 * Gets the decoder used by this </code>ImageAnalysisEngine</code>.
477 	 * @return the </code>ColorDecoder</code> used
478 	 */
479 	public synchronized ColorDecoder getDecoder() {
480 		return decoder;
481 	}
482 	
483 	/**
484 	 * Sets a </code>ColorDecoder</code> to this </code>ImageAnalysisEngine</code>
485 	 * @param decoder the </code>ColorDecoder</code> to set
486 	 */
487 	public synchronized void setDecoder(ColorDecoder decoder) {
488 		if (this.decoder == decoder) return;
489 		this.decoder = decoder;
490 		if (localAnalysis) performAnalysis();
491 		else {
492 			// set to server?
493 		}
494 	}
495 	
496 	/**
497 	 * Sets the analysis parameters (only region of interest, threshold remains the 
498 	 * same) and reanalyzes pixels.
499 	 * @param roiX the starting x coordinate
500 	 * @param roiY the starting y coordinate
501 	 * @param roiW the width of the region
502 	 * @param roiH the height of the region
503 	 * @param calculateThreshold true if the threshold is calculated by the server or false otherwise
504 	 * @param performFit true if the fitting should be performed or false otherwise
505 	 */
506 	public void setAnalysisParameters(final int roiX, final int roiY, final int roiW, final int roiH, 
507 			final int roi2X, final int roi2Y, final int roi2W, final int roi2H, 
508 			final boolean calculateThreshold, boolean performFit, boolean performSmoothing) {
509 		setAnalysisParameters(threshold, roiX, roiY, roiW, roiH, roi2X, 
510 				roi2Y, roi2W, roi2H, calculateThreshold,performFit, performSmoothing);
511 	}
512 	
513 	/**
514 	 * Sets the analysis parameters (threshold and region of interest) and reanalyzes pixels.
515 	 * @param threshold the threshold
516 	 * @param roiX the starting x coordinate
517 	 * @param roiY the starting y coordinate
518 	 * @param roiW the width of the region
519 	 * @param roiH the height of the region
520 	 */
521 	public synchronized void setAnalysisParameters(final double threshold, 
522 			final int roiX,	final int roiY, final int roiW, final int roiH, 
523 			final int roi2X, final int roi2Y, final int roi2W, final int roi2H,
524 			final boolean calculateThreshold, final boolean performFit,final boolean performSmoothing) {
525 		
526 		if (!localAnalysis) {
527 			executeTask(new Runnable() {
528 				@Override
529 				public void run() {
530 					try {
531 						getVideoServerConnection().setAnalysisParameters(threshold, roiX, roiY, roiW, roiH, roi2X, roi2Y, roi2W, roi2H,calculateThreshold,performFit);
532 					} catch (ConnectionFailed e) {
533 						e.printStackTrace();
534 					}
535 				}
536 			});
537 		} else {
538 			this.calculateThreshold = calculateThreshold;
539 			this.performFit = performFit;
540 			this.performSmoothing = performSmoothing;
541 			if (this.threshold == threshold && this.roiX == roiX && this.roiY == roiY && this.roiW == roiW && this.roiH == roiH
542 					&& this.roi2X == roi2X && this.roi2Y == roi2Y && this.roi2H == roi2H && this.roi2W == roi2W) return;
543 			this.threshold = threshold;
544 			if (roiX >= imageWidth) this.roiX = imageWidth-1;
545 			else if (roiX < 0) this.roiX = 0;
546 			else this.roiX = roiX;
547 			if (roiY >= imageHeight) this.roiY = imageHeight-1;
548 			else if (roiY < 0) this.roiY = 0;
549 			else this.roiY = roiY;
550 			if (this.roiX+roiW > imageWidth) this.roiW = imageWidth-this.roiX;
551 			else if (roiW <= 0) this.roiW = 1;
552 			else this.roiW = roiW;
553 			if (this.roiY+roiH > imageHeight) this.roiH = imageHeight-this.roiY;
554 			else if (roiH <= 0) this.roiH = 1;
555 			else this.roiH = roiH;
556 
557 			if (roi2X >= imageWidth) this.roi2X = imageWidth-1;
558 			else if (roi2X < 0) this.roi2X = 0;
559 			else this.roi2X = roi2X;
560 			if (roi2Y >= imageHeight) this.roi2Y = imageHeight-1;
561 			else if (roi2Y < 0) this.roi2Y = 0;
562 			else this.roi2Y = roi2Y;
563 			if (this.roi2X+roi2W > imageWidth) this.roi2W = imageWidth-this.roi2X;
564 			else if (roi2W <= 0) this.roi2W = 1;
565 			else this.roi2W = roi2W;
566 			if (this.roi2Y+roi2H > imageHeight) this.roi2H = imageHeight-this.roi2Y;
567 			else if (roi2H <= 0) this.roi2H = 1;
568 			else this.roi2H = roi2H;
569 			performAnalysis();
570 		}
571 		
572 	}
573 	
574 	/**
575 	 * Sets the analysis mode for this </code>ImageAnalysisEngine</code>.
576 	 * @param local </code>true</code> for local analysis and
577 	 * </code>false</code> for remote analysis
578 	 */
579 	public void setLocalAnalysisMode(boolean local) {
580 		if (localAnalysis == local) return;
581 		TineHandler.logger.log(Level.INFO, "Analysis mode: " + local);
582 		localAnalysis = local;
583 		if (localAnalysis) {
584 			executeTask(new Runnable() {
585 				@Override
586 				public void run() {
587 					getVideoServerConnection().disconnect();
588 				}
589 			});
590 			setRoi(0, 0, imageWidth, imageHeight);
591 		}
592 	}
593 	
594 	/**
595 	 * Gets the analysis mode for this </code>ImageAnalysisEngine</code>.
596 	 * @return the </code>true</code> if local analysis is selected and
597 	 * </code>false</code> if remote analysis is selected
598 	 */
599 	public boolean isLocalAnalysisMode() {
600 		return localAnalysis;
601 	}
602 	
603 	/**
604 	 * Starts connection to remote video analysis server.
605 	 */
606 	public void startRemoteAnalysis() {
607 		TineHandler.logger.log(Level.INFO, "Start remote analysis.");
608 		try {
609 			getVideoServerConnection().connect();
610 		} catch (ConnectionFailed e) {
611 			e.printStackTrace();
612 		}
613 	}
614 	
615 	/**
616 	 * Stops connection to remote video analysis server.
617 	 */
618 	public void stopRemoteAnalysis() {
619 		TineHandler.logger.log(Level.INFO, "Stop remote analysis.");
620 		getVideoServerConnection().disconnect();
621 	}
622 	
623 	/**
624 	 * Sets </code>ConnectionParameters</code> for remote video analysis server.
625 	 * @param cp the </code>ConnectionParameters</code> to set
626 	 */
627 	public void setAnalysisServerConnectionParameters(ConnectionParameters cp) {
628 		getVideoServerConnection().setConnectionParameters(cp);
629 	}
630 	
631 	private VideoServerConnection getVideoServerConnection() {
632 		if (videoServerConnection == null) {
633 			videoServerConnection = new VideoServerConnection();
634 			videoServerConnection.addPropertyChangeListener(new PropertyChangeListener() {
635 				@Override
636 				public void propertyChange(PropertyChangeEvent evt) {
637 					if (localAnalysis) return;
638 					if (evt.getPropertyName() == VideoServerConnection.PROPERTY_A_IMAGE) {
639 						setAImage((AImage) evt.getNewValue());
640 					}
641 					else if (evt.getPropertyName() == VideoServerConnection.PROPERTY_BACKGROUND) {
642 						setBackground((IMAGE) evt.getNewValue(), false);
643 					}
644 					else if (evt.getPropertyName() == VideoServerConnection.PROPERTY_PRECISE_BACKGROUND) {
645 						//TODO handle this
646 //						setPreciseBackground((double[]) evt.getNewValue(), videoServerConnection.getPreciseBackgroundWidth(),false);
647 					}
648 				}
649 			});
650 		}
651 		return videoServerConnection;
652 	}
653 	
654 	private synchronized void setAImage(AImage aImage) {
655 		this.aImage = aImage;
656 		getPcSupport().firePropertyChange(PROPERTY_A_IMAGE, null, aImage);
657 	}
658 	
659 	private synchronized void performAnalysis() {
660 		if (image == null) return;
661 		executeAnalysis(new AnalysisExecutor(image,preciseBackground,imageWidth,imageHeight,roiX,roiY,roiW,roiH,
662 				roi2X, roi2Y, roi2W, roi2H, threshold,calculateThreshold,performFit,performSmoothing));
663 	}
664 		
665 	private void executeAnalysis(Runnable r) {
666 		if (analysisExecutor == null) {
667 			ThreadFactory factory = new TINEThreadFactory("TINE-Dismissable");
668 			analysisExecutor = new  ThreadPoolExecutor(1, 1, 0, TimeUnit.NANOSECONDS,
669 		              new DismissableBlockingQueue<Runnable>(2, true), factory){
670 				@Override
671 				protected void afterExecute(Runnable r, Throwable t) {
672 					super.afterExecute(r, t);
673 					if (t != null) {
674 						t.printStackTrace();
675 					}
676 				}
677 			};
678 			analysisExecutor.setRejectedExecutionHandler(new RejectedExecutionHandler(){
679 				public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
680 					//ignore
681 				}
682 			});
683 		}
684 
685 		analysisExecutor.execute(r);
686 	}
687 	
688 	@Deprecated
689 	@SuppressWarnings("unused")
690 	private static int tineFormatToBufferedType(IMAGE im) {
691 		int format = im.getFrameHeader().imageFormat;
692 		if (format == ImageFormat.IMAGE_FORMAT_GRAY.getId()) {
693 			return BufferedImage.TYPE_BYTE_GRAY;
694 		} else if (format == ImageFormat.IMAGE_FORMAT_RGB.getId()) {
695 			return BufferedImage.TYPE_INT_RGB;
696 		}
697 		return BufferedImage.TYPE_INT_RGB;
698 	}
699 	
700 	private void analyzeImage(final IMAGE im, double[] bg, int iW, int iH, int rX, int rY, int rW, int rH,
701 			int r2X, int r2Y, int r2W, int r2H, double threshold, boolean calculateThreshold,
702 			boolean performFit, boolean smoothing) {
703 //		logger.log(Level.FINE, "Analysis: Starting analysis.");
704 		IMAGE newImage = im;
705 		BufferedImage bufferedImage = toBufferedImage(im);
706 		int aH = im.getFrameHeader().aoiHeight;
707 		int aW = im.getFrameHeader().aoiWidth;
708 
709 		if (aH != -1) {
710 			iW = aW;
711 			iH = aH;
712 		}
713 		
714 		if (bufferedImage == null) throw new IllegalStateException("No BufferedImage!");
715 		
716 		int[] data = bufferedImage.getRGB(0, 0, iW, iH, null, 0, iW);
717 		double[] values = decoder.transform(data);
718 		
719 		int i, j;
720 		
721 		if (bg != null) {		
722 			int[] newImageVals = new int[values.length];
723 			try { 
724 				i = 0;
725 				//this is actually faster
726     			for (;;) {
727     				values[i] -= bg[i];
728     				newImageVals[i] = (int)(values[i]+0.5);
729     				if (newImageVals[i] < 0) {
730     					newImageVals[i] = 0;
731     				}
732     				i++;
733     			}
734 			} catch (ArrayIndexOutOfBoundsException e) {
735 				//ignore
736 			}
737 			bufferedImage = new BufferedImage(bufferedImage.getWidth(),bufferedImage.getHeight(),BufferedImage.TYPE_BYTE_GRAY);
738 			bufferedImage.setRGB(0,0,iW,iH,newImageVals,0,iW);
739 			newImage = TImageConverter.parseBufferedImage(bufferedImage,im.getFrameHeader().clone(),im.getSourceHeader().clone());
740 			newImage.getFrameHeader().aoiWidth = im.getFrameHeader().aoiWidth;
741 			newImage.getFrameHeader().aoiHeight = im.getFrameHeader().aoiHeight;
742 			newImage.getFrameHeader().xStart = im.getFrameHeader().xStart;
743 			newImage.getFrameHeader().yStart = im.getFrameHeader().yStart;
744 			newImage.getFrameHeader().sourceHeight = im.getFrameHeader().sourceHeight;
745 			newImage.getFrameHeader().sourceWidth = im.getFrameHeader().sourceWidth;
746 		}
747 		
748 	
749 		
750 		//calculate the threshold value
751 		//we want to "fit" A e^(x-B)^2/2C^2 + D + F x, where D is the threshold
752 		//to find D, sum up the values in the threshold ROI and outside ROI and calculate the
753 		//average value. That is the new threshold, if there wasn't any specified by the user
754 //		logger.log(Level.FINE, "Analysis: Calculating threshold.");
755 		int thresholdPointsCount = 0;
756 		if (calculateThreshold) {
757 			double sum = 0;
758 			int r = rX + rW, b = rY+rH;
759 			boolean inside = false;
760 			for(j = 0; j < r2H; j++)	{
761 				inside = rY <= r2Y + j && r2Y + j < b;
762 				for(i = 0; i < r2W; i++) {
763 					//do not include indexes, which are in normal ROI
764 					if (rX <= r2X + i && r2X + i < r && inside) continue;
765 					sum += values[i+r2X+iW*(j+r2Y)];
766 					thresholdPointsCount++;
767 				}
768 			}
769 			if (thresholdPointsCount != 0) {
770 				threshold = sum/thresholdPointsCount;
771 			} else {
772 				threshold = Double.NaN;
773 			}
774 		}
775 //		logger.log(Level.FINE, "Analysis: Performing statistical analysis.");
776 		double[] sideViewX = new double [iW];
777 		double[] sideViewY = new double [iH];
778 		
779 		double maxValue = -Double.MAX_VALUE;
780 		double minValue = Double.MAX_VALUE;
781 		double value;
782 		//if threshold is less than 0 (that can happen if such background
783 		//is substracted, that the values of background are larger than the
784 		//image values), do not subtract the threshold.
785 		if (Double.isNaN(threshold)) {
786 			for (i = 0; i < iW; ++i) {
787     			for (j = 0; j < iH; ++j) {
788     				value = values[i+j*iW];
789     //		for (i = 0; i < rW; ++i) {
790     //			for (j = 0; j < rH; ++j) {
791     //				value = values[i+rX+iW*(j+rY)];
792     				if (minValue > value) minValue = value;
793     				if (maxValue < value) maxValue = value;
794     				sideViewX[i] += (value);
795     				sideViewY[j] += (value);
796     			}
797     		}
798 		} else {
799     		for (i = 0; i < iW; ++i) {
800     			for (j = 0; j < iH; ++j) {
801     				value = values[i+j*iW];
802     //		for (i = 0; i < rW; ++i) {
803     //			for (j = 0; j < rH; ++j) {
804     //				value = values[i+rX+iW*(j+rY)];
805     				if (minValue > value) minValue = value;
806     				if (value < threshold) continue;
807     				if (maxValue < value) maxValue = value;
808     				sideViewX[i] += (value-threshold);
809     				sideViewY[j] += (value-threshold);
810     			}
811     		}
812 		}
813 			
814 		//Do not do these: request from Gero
815 //		for (i = 0; i < iW; i++) {
816 //			if (sideViewX[i] < 0) {
817 //				sideViewX[i] = 0;
818 //			}
819 //		}
820 //		for (j = 0; j < iH; j++) {
821 //			if (sideViewY[j] < 0) {
822 //				sideViewY[j] = 0;
823 //			}
824 //		}
825 		
826 		//smoothing 	
827 		if (smoothing) {
828 			sideViewX = FittingUtilities.smooth(sideViewX);
829 			sideViewY = FittingUtilities.smooth(sideViewY);
830 		}
831 		//end of smoothing
832 		double[] roiSideViewX = new double[rW];
833 		double[] roiSideViewY = new double[rH];
834 		
835 		for (i = 0; i < rW; ++i){
836 			roiSideViewX[i]= sideViewX[rX+i];
837 		}
838 		
839 		for (j = 0; j < rH; ++j){
840 			roiSideViewY[j]= sideViewY[rY+j];
841 		}
842 		
843 		try {
844 			i = 0;
845 			for (;;) {
846 				if (values[i] < 0) values[i] = 0;
847 				i++;
848 			}
849 		} catch (ArrayIndexOutOfBoundsException e) {
850 			//ignore
851 		}
852 		
853 		double sum = 0;
854 		double sumX = 0;
855 		double sumY = 0;
856 		double sumXX = 0;
857 		double sumYY = 0;
858 		double sumXY = 0;
859 		
860 		if (Double.isNaN(threshold)) {
861     		for(j = 0; j < rH; j++)
862     		{
863     			for(i = 0; i < rW; i++)
864     			{
865     				value = values[i+rX+iW*(j+rY)];
866 					sum = sum+value;
867 					sumX = sumX+value*(i+rX);
868 					sumY = sumY+value*(j+rY);
869     			}
870     		}
871 		} else {
872 			for(j = 0; j < rH; j++)
873 			{
874 				for(i = 0; i < rW; i++)
875 				{
876 					value = values[i+rX+iW*(j+rY)];
877 					if(threshold < value)
878 					{
879 						value -=threshold;
880 						sum = sum+value;
881 						sumX = sumX+value*(i+rX);
882 						sumY = sumY+value*(j+rY);
883 					}
884 				}
885 			}
886 		}
887 		
888 		double Y = sumY/sum;
889 		double X = sumX/sum;
890 		
891 		double argx, argy;
892 		if (Double.isNaN(threshold)) {
893 			for(j = 0; j < rH; j++)
894     		{
895     			for(i = 0; i < rW; i++)
896     			{
897     				value = values[i+rX+iW*(j+rY)];
898 					argx = i+rX-X;
899 					argy = j+rY-Y;
900 					sumXX = sumXX+value*argx*argx;
901 					sumXY = sumXY+value*argx*argy;
902 					sumYY = sumYY+value*argy*argy;
903     			}
904     		}
905 		} else {
906     		for(j = 0; j < rH; j++)
907     		{
908     			for(i = 0; i < rW; i++)
909     			{
910     				value = values[i+rX+iW*(j+rY)];
911     				if(threshold < value)
912     				{
913     					value -=threshold;
914     					argx = i+rX-X;
915     					argy = j+rY-Y;
916     					sumXX = sumXX+value*argx*argx;
917     					sumXY = sumXY+value*argx*argy;
918     					sumYY = sumYY+value*argy*argy;
919     				}
920     			}
921     		}
922 		}
923 		//TODO
924 //		if (calculateThreshold && threshold < 0) {
925 //			threshold = Double.NaN;
926 //		}
927 		double XX = sumXX/sum;
928 		double XY = sumXY/sum;
929 		double YY = sumYY/sum;
930 
931 		double b = 0;
932 		double c = 0;
933 		double D = 0;
934 		double A = 0;
935 		double B = 0;
936 		b = -2*(XX+YY);
937 		c = (XX*YY)-(XY*XY);
938 		D = Math.sqrt((b*b)-(4*c));
939 		A = (-b-D)/2;
940 		B = (-b+D)/2;
941 		double phase = Math.atan2(2*XX, YY-XX)*(90/Math.PI);
942 		
943 		double[] xMinMax = findMinMax(roiSideViewX,0,rW);
944 		double[] yMinMax = findMinMax(roiSideViewY,0,rH);
945 
946 		//perform the statistics calculation on the sideview data
947 		double xM = 0;
948 		double yM = 0;
949 		double suma = 0;
950 		double xSTD = 0;
951 		double ySTD = 0;
952 		double constX = xMinMax[0];
953 		double constY = yMinMax[0];
954 		for (i = 0; i < rW; i++) {
955 			value = roiSideViewX[i]-constX;
956 			if (value < 0) value = 0;
957 			xM += i*value;
958 			xSTD += i*i*value;
959 			suma += value;
960 		}
961 		xM = xM/suma;
962 		xSTD = Math.sqrt(xSTD/suma-xM*xM);
963 		suma = 0;
964 		for (i = 0; i < rH; i++) {
965 			value = roiSideViewY[i]-constY;
966 			if (value < 0) value = 0;
967 			yM += i*value;
968 			ySTD += i*i*value;
969 			suma += value;
970 		}
971 		yM = yM/suma;
972 		ySTD = Math.sqrt(ySTD/suma-yM*yM);
973 		
974 		double[] startValuesX = new double[]{xMinMax[1]-constX,xSTD,xM,constX,0};
975 		double[] startValuesY = new double[]{yMinMax[1]-constY,ySTD,yM,constY,0};
976 		
977 		if (performFit) {
978 //				logger.log(Level.FINE, "Analysis: Fitting gauss function to side views.");
979 						
980     		int l = roiSideViewX.length;
981     		double[] steps = new double[l];
982     		double[] weights = new double[l];
983     		for (i = 0; i < l; i++) {
984     			steps[i] = i;
985     			weights[i] = 1;
986     		}
987     		
988     		try {
989         		startValuesX = FittingUtilities.lmLinear(steps,roiSideViewX,weights,startValuesX);
990         		//fallback: try again with different starting vector
991         		if ((startValuesX[0] > 7000000 || startValuesX[0] <= 1|| startValuesX[1] <= 1)) {
992         			if (previousStartValuesX != null) {
993         				startValuesX = previousStartValuesX;
994         				i = 0;
995                 		do {
996                 			startValuesX = FittingUtilities.lmLinear(steps,roiSideViewX,weights,startValuesX);
997                 			i++;
998                 		} while ((startValuesX[0] > 7000000 || startValuesX[0] <= 1 || startValuesX[1] <= 1) && i < 3);
999                 		if ((startValuesX[0] > 7000000 || startValuesX[0] <= 1 || startValuesX[1] <= 1) ) {
1000                 			startValuesX = new double[]{Double.NaN,Double.NaN,Double.NaN,Double.NaN,Double.NaN};
1001                 			previousStartValuesX = null;
1002                 		} else {
1003                 			previousStartValuesX = startValuesX;
1004                 		}
1005         			} else {
1006             			startValuesX = new double[]{Double.NaN,Double.NaN,Double.NaN,Double.NaN,Double.NaN};
1007             			previousStartValuesX = null;
1008         			}
1009         		} else {
1010     				//in case there is not enough tail, the fit will over/undershot in the constant
1011     				//we need to add some tail	
1012             		if (rW < iW) {
1013             			double slope = startValuesX[4];
1014             			startValuesX = new double[]{xMinMax[1]-constX,xSTD,rX+xM,constX,0};
1015     					double[] roiSX = new double[iW];
1016     					for (i = 0; i < iW; i++) {
1017     						roiSX[i] = sideViewX[i] - slope*i;
1018     					}
1019     					
1020     					steps = new double[iW];
1021                 		weights = new double[iW];
1022                 		for (i = 0; i < iW; i++) {
1023                 			steps[i] = i;
1024                 			weights[i] = 1;
1025                 		}
1026                 		startValuesX = FittingUtilities.lmLinear(steps,roiSX,weights,startValuesX);
1027                 		if ((startValuesX[0] > 7000000 || startValuesX[0] <= 1|| startValuesX[1] <= 1) && previousStartValuesX != null) {
1028                 			startValuesX = previousStartValuesX;
1029                      		do {
1030                          		startValuesX = FittingUtilities.lmLinear(steps,roiSX,weights,startValuesX);
1031                     			i++;
1032                     		} while ((startValuesX[0] > 7000000 || startValuesX[0] <= 1|| startValuesX[1] <= 1) && i < 3);
1033                 		}
1034                   		startValuesX[4] += slope;
1035                   		if ((startValuesX[0] > 7000000 || startValuesX[0] <= 1|| startValuesX[1] <= 1)) {
1036                 			startValuesX = new double[]{Double.NaN,Double.NaN,Double.NaN,Double.NaN,Double.NaN};
1037                 			previousStartValuesX = null;
1038                   		} else {
1039                   			previousStartValuesX = startValuesX;
1040                   		}
1041     				} else {
1042     					previousStartValuesX = startValuesX;
1043     				}
1044         		}
1045     		} catch (Exception e) {
1046     			startValuesX = new double[]{Double.NaN,Double.NaN,Double.NaN,Double.NaN,Double.NaN};
1047     			previousStartValuesX = null;
1048     		}
1049 
1050     		try {
1051         		l = roiSideViewY.length;
1052         		steps = new double[l];
1053         		weights = new double[l];
1054         		for (i = 0; i < l; i++) {
1055         			steps[i] = i;
1056         			weights[i] = 1;
1057         		}
1058         		
1059         		startValuesY = FittingUtilities.lmLinear(steps,roiSideViewY,weights,startValuesY);
1060         		//fallback: try again with different starting vector
1061         		if ((startValuesY[0] > 7000000 || startValuesY[0] <= 1 || startValuesY[1] <= 1) ) {
1062         			if (previousStartValuesY != null) {
1063         				startValuesY = previousStartValuesY;
1064         				i = 0;
1065                 		do {
1066                 			startValuesY = FittingUtilities.lmLinear(steps,roiSideViewY,weights,startValuesY);
1067                 			i++;
1068                 		} while ((startValuesY[0] > 7000000 || startValuesY[0] <= 1 || startValuesY[1] <= 1) && i < 3);
1069                 		if ((startValuesY[0] > 7000000 || startValuesY[0] <= 1 || startValuesY[1] <= 1) ) {
1070                 			startValuesY = new double[]{Double.NaN,Double.NaN,Double.NaN,Double.NaN,Double.NaN};
1071                 			previousStartValuesY = null;
1072                 		} else {
1073                 			previousStartValuesY = startValuesY;
1074                 		}
1075         			} else {
1076         				startValuesY = new double[]{Double.NaN,Double.NaN,Double.NaN,Double.NaN,Double.NaN};
1077         				previousStartValuesY = null;
1078         			}
1079         		}  else {
1080     				//in case there is not enough tail, the fit will over/undershot in the constant
1081     				//we need to add some tailvalues	
1082             		if (rH < iH) {
1083             			double slope = startValuesY[4];
1084             			startValuesY = new double[]{yMinMax[1]-constY,ySTD,rY+yM,constY,0};
1085     					double[] roiSY = new double[iH];
1086     					for (i = 0; i < iH; i++) {
1087     						roiSY[i] = sideViewY[i] - slope*i;
1088     					}
1089     					steps = new double[iH];
1090                 		weights = new double[iH];
1091                 		for (i = 0; i < iH; i++) {
1092                 			steps[i] = i;
1093                 			weights[i] = 1;
1094                 		}
1095                 		i = 0;
1096                 		startValuesY = FittingUtilities.lmLinear(steps,roiSY,weights,startValuesY);
1097                 		if ((startValuesY[0] > 7000000 || startValuesY[0] <= 1 || startValuesY[1] <= 1) && previousStartValuesY != null) {
1098                 			startValuesY = previousStartValuesY;
1099                     		do {
1100                     			startValuesY = FittingUtilities.lmLinear(steps,roiSY,weights,startValuesY);
1101                     			i++;
1102                     		} while ((startValuesY[0] > 7000000 || startValuesY[0] <= 1 || startValuesY[1] <= 1) && i < 3);
1103                 		}
1104     
1105                 		startValuesY[4] += slope;
1106                 		if ((startValuesY[0] > 7000000 || startValuesY[0] <= 1 || startValuesY[1] <= 1) ) {
1107                 			startValuesY = new double[]{Double.NaN,Double.NaN,Double.NaN,Double.NaN,Double.NaN};
1108                 			previousStartValuesY = null;
1109                 		} else {
1110                 			previousStartValuesY = startValuesY;
1111                 		}
1112     				} else {
1113     					previousStartValuesY = startValuesY;
1114     				}
1115         		}
1116     		} catch (Exception e) {
1117     			startValuesY = new double[]{Double.NaN,Double.NaN,Double.NaN,Double.NaN,Double.NaN};
1118     			previousStartValuesY = null;
1119     		}
1120     		
1121     		if (FittingUtilities.isNaN(startValuesY)) {
1122     			startValuesY = new double[]{yMinMax[1]-constY,ySTD,rY+yM,constY,0};
1123     		}
1124     		if (FittingUtilities.isNaN(startValuesX)) {
1125     			startValuesX = new double[]{xMinMax[1]-constX,ySTD,rX+xM,constX,0};
1126     		}
1127 		} else {
1128 			startValuesX = new double[]{Double.NaN,Double.NaN,Double.NaN,Double.NaN,Double.NaN};
1129 			startValuesY = new double[]{Double.NaN,Double.NaN,Double.NaN,Double.NaN,Double.NaN};
1130 			previousStartValuesY = null;
1131 			previousStartValuesX = null;
1132 		}
1133 		
1134 
1135 		try {
1136 			if (!Double.isNaN(threshold)){
1137 				maxValue = maxValue - threshold; 
1138 			}
1139 			AImage newAImage = new AImage(
1140 					newImage, rX, rY, rW, rH, r2X, r2Y, r2W, r2H, threshold,
1141 					sideViewX,rX+xM,xSTD,xMinMax[1]-constX,constX,
1142 					startValuesX[2], startValuesX[1], startValuesX[0], startValuesX[3], startValuesX[4],
1143 					sideViewY,rY+yM,ySTD,yMinMax[1]-constY,constY,
1144 					startValuesY[2], startValuesY[1], startValuesY[0], startValuesY[3], startValuesY[4],
1145 //					Math.sqrt(A), Math.sqrt(B), phase, maxValue-threshold, minValue, 
1146 					Math.sqrt(A), Math.sqrt(B), phase, maxValue, minValue,
1147 					thresholdPointsCount,calculateThreshold,performFit,smoothing
1148 			);
1149 			setAImage(newAImage);
1150 			
1151 		} catch (Exception e) {
1152 			e.printStackTrace();
1153 		}
1154 		
1155 	}
1156 	
1157 //	private void analyzeImage(final IMAGE im, final IMAGE bg, int iW, int iH, int rX, int rY, int rW, int rH,
1158 //			int r2X, int r2Y, int r2W, int r2H, double threshold, boolean calculateThreshold,
1159 //			boolean performFit) {
1160 ////		logger.log(Level.FINE, "Analysis: Starting analysis.");
1161 //		IMAGE newImage = im;
1162 //		BufferedImage bufferedImage = toBufferedImage(im);
1163 //		//imageConverter.parseTineImageNoClone(im, false); // mdavid: changed
1164 //		if (bufferedImage == null) throw new IllegalStateException("No BufferedImage!");
1165 //		
1166 //		int[] data = bufferedImage.getRGB(0, 0, iW, iH, null, 0, iW);
1167 //		double[] values = decoder.transform(data);
1168 //		
1169 //		int i, j;
1170 //		
1171 //		if (bg != null) {
1172 ////			logger.log(Level.FINE, "Analysis: Subtracting background.");
1173 //			bufferedImage = toBufferedImage(bg);
1174 ////			bufferedImage = imageConverter.parseTineImage(bg, false); // mdavid: changed
1175 //			if (bufferedImage == null) throw new IllegalStateException("No BufferedImage!");
1176 //
1177 //			
1178 //			int[] backData = bufferedImage.getRGB(0, 0, iW, iH, null, 0, iW);
1179 //			double[] backVals = decoder.transform(backData);
1180 //			try { 
1181 //				i = 0;
1182 //				//this is actually faster
1183 //    			for (;;) {
1184 //    				values[i] -= backVals[i];
1185 //    				data[i] -= backData[i];
1186 //    				if (values[i] < 0) {
1187 //    					values[i] = 0;
1188 //    					data[i] = 0;
1189 //    				}
1190 //    				i++;
1191 //    			}
1192 //			} catch (ArrayIndexOutOfBoundsException e) {
1193 //				//ignore
1194 //			}
1195 //			
1196 ////			BufferedImage bf = new BufferedImage(bufferedImage.getWidth(),bufferedImage.getHeight(),bufferedImage.getType() != BufferedImage.TYPE_CUSTOM ? bufferedImage.getType() : BufferedImage.TYPE_INT_RGB);
1197 //			
1198 //			bufferedImage = new BufferedImage(bufferedImage.getWidth(),bufferedImage.getHeight(),tineFormatToBufferedType(im));
1199 //			bufferedImage.setRGB(0,0,iW,iH,data,0,iW);
1200 //			newImage = TImageConverter.parseBufferedImage(bufferedImage,im.getFrameHeader().clone(),im.getSourceHeader().clone());
1201 //		}
1202 //		
1203 //	
1204 //		
1205 //		//calculate the threshold value
1206 //		//we want to "fit" A e^(x-B)^2/2C^2 + D, where D is the threshold
1207 //		//to find D, sum up the boundary values (10 pixels) around the ROI and calculate
1208 //		//the average value. that is the new threshold, if there wasn't any specified by the
1209 //		//user
1210 ////		logger.log(Level.FINE, "Analysis: Calculating threshold.");
1211 //		int thresholdPointsCount = 0;
1212 //		if (calculateThreshold) {
1213 //			double sum = 0;
1214 //			int r = rX + rW, b = rY+rH;
1215 //			boolean inside = false;
1216 //			for(j = 0; j < r2H; j++)	{
1217 //				inside = rY <= r2Y + j && r2Y + j < b;
1218 //				for(i = 0; i < r2W; i++) {
1219 //					//do not include indexes, which are in normal ROI
1220 //					if (rX <= r2X + i && r2X + i < r && inside) continue;
1221 //					sum += values[i+r2X+iW*(j+r2Y)];
1222 //					thresholdPointsCount++;
1223 //				}
1224 //			}
1225 //			if (thresholdPointsCount != 0) {
1226 //				threshold = sum/thresholdPointsCount;
1227 //			} else {
1228 //				threshold = 0;
1229 //			}
1230 //
1231 //		}
1232 //		
1233 ////		logger.log(Level.FINE, "Analysis: Performing statistical analysis.");
1234 //		double[] sideViewX = new double [iW];
1235 //		double[] sideViewY = new double [iH];
1236 //		
1237 //		double maxValue = -Double.MAX_VALUE;
1238 //		double minValue = Double.MAX_VALUE;
1239 //		double value;
1240 //		for (i = 0; i < iW; ++i) {
1241 //			for (j = 0; j < iH; ++j) {
1242 //				value = values[i+j*iW];
1243 //				if (minValue > value) minValue = value;
1244 //				if (value < threshold) continue;
1245 //				if (maxValue < value) maxValue = value;
1246 //				sideViewX[i] += (value-threshold);
1247 //				sideViewY[j] += (value-threshold);
1248 //			}
1249 //		}
1250 //		
1251 //		double[] roiSideViewX = new double[iW];
1252 //		double[] roiSideViewY = new double[iH];
1253 //		
1254 //		value = sideViewX[rX];
1255 //		for (i = 0; i < rX; i++) {
1256 //			roiSideViewX[i] = value;
1257 //		}
1258 //		for (i = rX; i < rX+rW; ++i){
1259 //			roiSideViewX[i]= sideViewX[i];
1260 //		}
1261 //		value = sideViewX[rX+rW-1];
1262 //		for (i = rX+rW; i < iW; i++) {
1263 //			roiSideViewX[i] = value;
1264 //		}
1265 //		
1266 //		value = sideViewY[rY];
1267 //		for (i = 0; i < rY; i++) {
1268 //			roiSideViewY[i] = value;
1269 //		}
1270 //		for (j = rY; j < rY+rH; ++j){
1271 //			roiSideViewY[j]= sideViewY[j];
1272 //		}
1273 //		value = sideViewY[rY+rH-1];
1274 //		for (i = rY+rH; i < iH; i++) {
1275 //			roiSideViewY[i] = value;
1276 //		}
1277 //		
1278 //		double sum = 0;
1279 //		double sumX = 0;
1280 //		double sumY = 0;
1281 //		double sumXX = 0;
1282 //		double sumYY = 0;
1283 //		double sumXY = 0;
1284 //		
1285 //		for(j = 0; j < rH; j++)
1286 //		{
1287 //			for(i = 0; i < rW; i++)
1288 //			{
1289 //				value = values[i+rX+iW*(j+rY)];
1290 //				if(threshold < value)
1291 //				{
1292 //					value -=threshold;
1293 //					sum = sum+value;
1294 //					sumX = sumX+value*(i+rX);
1295 //					sumY = sumY+value*(j+rY);
1296 //				}
1297 //			}
1298 //		}
1299 //		
1300 //		double Y = sumY/sum;
1301 //		double X = sumX/sum;
1302 //		
1303 //		double argx, argy;
1304 //		for(j = 0; j < rH; j++)
1305 //		{
1306 //			for(i = 0; i < rW; i++)
1307 //			{
1308 //				value = values[i+rX+iW*(j+rY)];
1309 //				if(threshold < value)
1310 //				{
1311 //					value -=threshold;
1312 //					argx = i+rX-X;
1313 //					argy = j+rY-Y;
1314 //					sumXX = sumXX+value*argx*argx;
1315 //					sumXY = sumXY+value*argx*argy;
1316 //					sumYY = sumYY+value*argy*argy;
1317 //				}
1318 //			}
1319 //		}
1320 //
1321 //		double XX = sumXX/sum;
1322 //		double XY = sumXY/sum;
1323 //		double YY = sumYY/sum;
1324 //
1325 //		double b = 0;
1326 //		double c = 0;
1327 //		double D = 0;
1328 //		double A = 0;
1329 //		double B = 0;
1330 //		b = -2*(XX+YY);
1331 //		c = (XX*YY)-(XY*XY);
1332 //		D = Math.sqrt((b*b)-(4*c));
1333 //		A = (-b-D)/2;
1334 //		B = (-b+D)/2;
1335 //		double phase = Math.atan2(2*XX, YY-XX)*(90/Math.PI);
1336 //		
1337 //		double[] xMinMax = findMinMax(roiSideViewX,rX,rW);
1338 //		double[] yMinMax = findMinMax(roiSideViewY,rY,rH);
1339 //
1340 //		//perform the statistics calculation on the sideview data
1341 //		int end = rX+rW;
1342 //		double xM = 0;
1343 //		double yM = 0;
1344 //		double suma = 0;
1345 //		double xSTD = 0;
1346 //		double ySTD = 0;
1347 //		double constX = rX != 0 ? roiSideViewX[0] : xMinMax[0];
1348 //		double constY = rY != 0 ? roiSideViewY[0] : yMinMax[0];
1349 //		for (i = rX; i < end; i++) {
1350 //			value = roiSideViewX[i]-constX;
1351 //			if (value < 0) value = 0;
1352 //			xM += i*value;
1353 //			xSTD += i*i*value;
1354 //			suma += value;
1355 //		}
1356 //		xM = xM/suma;
1357 //		xSTD = Math.sqrt(xSTD/suma-xM*xM);
1358 //		end = rY + rH;
1359 //		suma = 0;
1360 //		for (i = rY; i < end; i++) {
1361 //			value = roiSideViewY[i]-constY;
1362 //			if (value < 0) value = 0;
1363 //			yM += i*value;
1364 //			ySTD += i*i*value;
1365 //			suma += value;
1366 //		}
1367 //		yM = yM/suma;
1368 //		ySTD = Math.sqrt(ySTD/suma-yM*yM);
1369 //				
1370 //		
1371 //		double[] startValuesX = new double[]{xMinMax[1]-constX,xSTD,xM,constX,0};
1372 //		double[] startValuesY = new double[]{yMinMax[1]-constY,ySTD,yM,constY,0};
1373 //		try {
1374 //			if (performFit) {
1375 ////				logger.log(Level.FINE, "Analysis: Fitting gauss function to side views.");
1376 //        		int l = roiSideViewX.length;
1377 //        		double[] steps = new double[l];
1378 //        		double[] weights = new double[l];
1379 //        		for (i = 0; i < l; i++) {
1380 //        			steps[i] = i;
1381 //        			weights[i] = 1;
1382 //        		}
1383 //        		startValuesX = FittingUtilities.lm2(steps,roiSideViewX,weights,startValuesX);
1384 //        		//fallback: try again with different starting vector
1385 //        		if ((startValuesX[0] > 7000000 || startValuesX[0] <= 1|| startValuesX[1] <= 1)) {
1386 //        			i = 0;
1387 ////        			startValuesX = previousStartValuesX;
1388 //            		do {
1389 //            			startValuesX = FittingUtilities.lm2(steps,roiSideViewX,weights,startValuesX);
1390 //            			i++;
1391 //            		} while ((startValuesX[0] > 700000 || startValuesX[0] <= 1|| startValuesX[1] <= 1) && i < 5);
1392 //        		}
1393 //        		if ((startValuesX[0] > 7000000 || startValuesX[0] <= 1|| startValuesX[1] <= 1)) {
1394 //        			startValuesX = new double[]{Double.NaN,Double.NaN,Double.NaN,Double.NaN,Double.NaN};
1395 //        		}
1396 //
1397 //        		l = roiSideViewY.length;
1398 //        		steps = new double[l];
1399 //        		weights = new double[l];
1400 //        		for (i = 0; i < l; i++) {
1401 //        			steps[i] = i;
1402 //        			weights[i] = 1;
1403 //        		}
1404 //        		
1405 //        		startValuesY = FittingUtilities.lm2(steps,roiSideViewY,weights,startValuesY);
1406 //        		//fallback: try again with different starting vector
1407 //        		if ((startValuesY[0] > 7000000 || startValuesY[0] <= 1 || startValuesY[1] <= 1) ) {
1408 //            		i = 0;
1409 ////            		startValuesY = previousStartValuesY;
1410 //            		do {
1411 //            			startValuesY = FittingUtilities.lm2(steps,roiSideViewY,weights,startValuesY);
1412 //            			i++;
1413 //            		} while ((startValuesY[0] > 7000000 || startValuesY[0] <= 1 || startValuesY[1] <= 1) && i < 5);
1414 //        		}
1415 //        		if ((startValuesY[0] > 7000000 || startValuesY[0] <= 1 || startValuesY[1] <= 1) ) {
1416 //        			startValuesY = new double[]{Double.NaN,Double.NaN,Double.NaN,Double.NaN,Double.NaN};
1417 //        		}
1418 //        		
1419 ////        		previousStartValuesX = startValuesX;
1420 ////        		previousStartValuesY = startValuesY;
1421 //			} else {
1422 //				startValuesX = new double[]{Double.NaN,Double.NaN,Double.NaN,Double.NaN,Double.NaN};
1423 //				startValuesY = new double[]{Double.NaN,Double.NaN,Double.NaN,Double.NaN,Double.NaN};
1424 //			}
1425 //		} catch (Exception e) {
1426 //			startValuesX = new double[]{Double.NaN,Double.NaN,Double.NaN,Double.NaN,Double.NaN};
1427 //			startValuesY = new double[]{Double.NaN,Double.NaN,Double.NaN,Double.NaN,Double.NaN};
1428 //		}
1429 //
1430 //		try {
1431 //			AImage newAImage = new AImage(
1432 //					newImage, rX, rY, rW, rH, r2X, r2Y, r2W, r2H, threshold,
1433 ////					sideViewX, X, Math.sqrt(XX), xMinMax[1]-xMinMax[0], xMinMax[0],
1434 //					sideViewX,xM,xSTD,xMinMax[1]-constX,constX,
1435 //					startValuesX[2], startValuesX[1], startValuesX[0], startValuesX[3], startValuesX[4],
1436 ////					sideViewY, Y, Math.sqrt(YY), yMinMax[1]-yMinMax[0], yMinMax[0],
1437 //					sideViewY,yM,ySTD,yMinMax[1]-constY,constY,
1438 //					startValuesY[2], startValuesY[1], startValuesY[0], startValuesY[3], startValuesY[4],
1439 //					Math.sqrt(A), Math.sqrt(B), phase, maxValue-threshold, minValue, 
1440 //					thresholdPointsCount,calculateThreshold,performFit
1441 //			);
1442 ////			logger.log(Level.FINE, "Analysis: Analysis completed.");
1443 //			setAImage(newAImage);
1444 //			
1445 //		} catch (Exception e) {
1446 //			e.printStackTrace();
1447 //		}
1448 //		
1449 //	}
1450 	
1451 	private double[] findMinMax(double[] values,int start, int length) {
1452 		double min = Double.MAX_VALUE;
1453 		double max = -Double.MAX_VALUE;
1454 		for (int i = start; i < start+length; i++) {
1455 			if (values[i] > max) max = values[i];
1456 			if (values[i] < min) min = values[i];
1457 		}
1458 		return new double[] {min, max};
1459 	}
1460 	
1461 	private void executeTask(Runnable r) {
1462 		if (executor == null) {
1463 			executor = Executors.newSingleThreadExecutor();
1464 		}
1465 		executor.execute(r);
1466 	}
1467 	
1468 	private PropertyChangeSupport getPcSupport() {
1469 		if (pcSupport == null) {
1470 			pcSupport = new PropertyChangeSupport(this);
1471 		}
1472 		return pcSupport;
1473 	}
1474 	
1475 	/**
1476 	 * Adds a </code>PropertyChangeListener</code> for the specified propertyName.
1477 	 * @param propertyName the name of the property
1478 	 * @param listener the </code>PropertyChangeListener</code> to add
1479 	 */
1480 	public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
1481 		getPcSupport().addPropertyChangeListener(propertyName, listener);
1482 	}
1483 	
1484 	/**
1485 	 * Removes a </code>PropertyChangeListener</code> for the specified propertyName.
1486 	 * @param propertyName the name of the property
1487 	 * @param listener the </code>PropertyChangeListener</code> to remove
1488 	 */
1489 	public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
1490 		getPcSupport().removePropertyChangeListener(propertyName, listener);
1491 	}
1492 	
1493 	/**
1494 	 * Adds a </code>PropertyChangeListener</code>.
1495 	 * @param listener the </code>PropertyChangeListener</code> to add
1496 	 */
1497 	public void addPropertyChangeListener(PropertyChangeListener listener) {
1498 		getPcSupport().addPropertyChangeListener(listener);
1499 	}
1500 	
1501 	/**
1502 	 * Removes a </code>PropertyChangeListener</code>.
1503 	 * @param listener the </code>PropertyChangeListener</code> to remove
1504 	 */
1505 	public void removePropertyChangeListener(PropertyChangeListener listener) {
1506 		getPcSupport().removePropertyChangeListener(listener);
1507 	}
1508 	
1509 	/**
1510 	 * Transforms the IMAGE to BufferedImage using the appropriate algorithm
1511 	 * for the given image.
1512 	 * 
1513 	 * @param image the image to transform
1514 	 * @return transformed image
1515 	 */
1516 	public static BufferedImage toBufferedImage(IMAGE image) {
1517 		if (TBufferedImage.isSupported(image.getFrameHeader().imageFormat)) {
1518 			return TBufferedImage.toBufferedImage(image,null,false,false);
1519 		} else {
1520 			return new TImageConverter().parseTineImage(image,false);
1521 		}
1522 	}
1523 }
1524