File: PlugInFilterRunner.java

package info (click to toggle)
imagej 1.51i%2Bdfsg-2
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 5,244 kB
  • ctags: 13,220
  • sloc: java: 113,144; sh: 285; xml: 50; makefile: 8
file content (623 lines) | stat: -rw-r--r-- 25,073 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
package ij.plugin.filter;
import ij.*;
import ij.process.*;
import ij.gui.*;
import ij.plugin.filter.PlugInFilter.*;
import ij.plugin.filter.*;
import ij.measure.Calibration;
import ij.macro.Interpreter;
import java.awt.*;
import java.util.*;

public class PlugInFilterRunner implements Runnable, DialogListener {
	private String command;					// the command, can be but need not be the name of the PlugInFilter
	private Object theFilter;					// the instance of the PlugInFilter
	private ImagePlus imp;
	private int flags;							// the flags returned by the PlugInFilter
	private boolean snapshotDone;		// whether the ImageProcessor has a snapshot already
	private Overlay originalOverlay;		// overlay before pressing 'preview', to revert
	private boolean previewCheckboxOn;			// the state of the preview checkbox (true = on)
	private boolean bgPreviewOn;		// tells the background thread that preview is allowed
	private boolean bgKeepPreview;		// tells the background thread to keep the result of preview
	private Thread previewThread;		 // the thread of the preview
	private GenericDialog gd;			 // the dialog (if it registers here by setDialog)
	private Checkbox previewCheckbox;	 // reference to the preview Checkbox of the dialog
	private long previewTime;				// time (ms) needed for preview processing
	private boolean ipChanged;			// whether the image data have been changed
	private int processedAsPreview;			// the slice processed during preview (if non-zero)
	private Hashtable<Thread, int[]> slicesForThread;		// gives first&last slice that a given thread should process
	private Hashtable<Thread, ImageProcessor> roisForThread;// gives ROI that a given thread should process
	Hashtable sliceForThread = new Hashtable(); // here the stack slice currently processed is stored.
	private int nPasses;						// the number of calls to the run(ip) method of the filter
	private int pass;						// passes done so far
	private boolean doStack;
	
	/** The constructor runs a PlugInFilter or ExtendedPlugInFilter by calling its
	 * setup, run, etc. methods. For details, see the documentation of interfaces
	 * PlugInFilter and ExtendedPlugInFilter.
	 * @param theFilter The PlugInFilter to be run
	 * @param command	The command that has caused running the PlugInFilter
	 * @param arg		The argument specified for this PlugInFilter in IJ_Props.txt or in the
	 * plugins.config file of a .jar archive conatining a collection of plugins. <code>arg</code>
	 * may be a string of length zero.
	 */
	public PlugInFilterRunner(Object theFilter, String command, String arg) {
		this.theFilter = theFilter;
		this.command = command;
		imp = WindowManager.getCurrentImage();
		flags = ((PlugInFilter)theFilter).setup(arg, imp);	// S E T U P
		if ((flags&PlugInFilter.DONE)!=0) return;
		if (!checkImagePlus(imp, flags, command)) return;	// check whether the PlugInFilter can handle this image type
		if ((flags&PlugInFilter.NO_IMAGE_REQUIRED)!=0)
			imp = null;									// if the plugin does not want an image, it should not get one
		Roi roi = null;
		if (imp != null) {
			roi = imp.getRoi();
			if (roi!=null) roi.endPaste();				// prepare the image: finish previous paste operation (if any)
			if (!imp.lock())
				return;					// exit if image is in use
			nPasses = ((flags&PlugInFilter.CONVERT_TO_FLOAT)!=0) ? imp.getProcessor().getNChannels():1;
		}
		if (theFilter instanceof ExtendedPlugInFilter) { // calling showDialog required?
			try {
				flags = ((ExtendedPlugInFilter)theFilter).showDialog(imp, command, this);  // D I A L O G (may include preview)
			} catch(Exception e) {
				killPreview();
				if (Macro.MACRO_CANCELED.equals(e.getMessage()))
					throw new RuntimeException(Macro.MACRO_CANCELED);
			}
			if (snapshotDone)
				Undo.setup(Undo.FILTER, imp);			// ip has a snapshot that may be used for Undo
			boolean keepPreviewFlag = (flags&ExtendedPlugInFilter.KEEP_PREVIEW)!=0;
			if (keepPreviewFlag && imp!=null && previewThread!=null && ipChanged &&
					previewCheckbox!=null && previewCheckboxOn) {
				bgKeepPreview = true;
				waitForPreviewDone();
				processedAsPreview = imp.getCurrentSlice();
			} else {
				killPreview();
				previewTime = 0;
			}
		} // if ExtendedPlugInFilter
		if ((flags&PlugInFilter.DONE)!=0) {
			if (imp != null)
				imp.unlock();
			return;
		} else if (imp==null) {
			((PlugInFilter)theFilter).run(null);		// not DONE, but NO_IMAGE_REQUIRED
			return;
		}
		/* preparing for the run(ip) method of the PlugInFilter... */
		int slices = imp.getStackSize();
		//IJ.log("processedAsPreview="+processedAsPreview+"; slices="+slices+"; doesStacks="+((flags&PlugInFilter.DOES_STACKS)!=0));
		if ((flags&PlugInFilter.PARALLELIZE_IMAGES)!=0)
			flags &= ~PlugInFilter.PARALLELIZE_STACKS;
		doStack = slices>1 && (flags&PlugInFilter.DOES_STACKS)!=0;
		imp.startTiming();
		if (doStack || processedAsPreview==0) {				// if processing during preview was not enough
			IJ.showStatus(command + (doStack ? " (Stack)..." : "..."));
			ImageProcessor ip = imp.getProcessor();
			pass = 0;
			if (!doStack) {	   // single image
				FloatProcessor fp = null;
				prepareProcessor(ip, imp);
				announceSliceNumber(imp.getCurrentSlice());
				if (theFilter instanceof ExtendedPlugInFilter)
					((ExtendedPlugInFilter)theFilter).setNPasses(nPasses);
				if ((flags&PlugInFilter.NO_CHANGES)==0) {	// for filters modifying the image
					boolean disableUndo = Prefs.disableUndo || (flags&PlugInFilter.NO_UNDO)!=0;
					if (!disableUndo) {
						ip.snapshot();
						snapshotDone = true;
					}
				}
				processOneImage(ip, fp, snapshotDone);		// may also set snapShotDone
				if ((flags&PlugInFilter.NO_CHANGES)==0) {	// (filters doing no modifications don't change undo status)
					if (snapshotDone)
						Undo.setup(Undo.FILTER, imp);
					else
						Undo.reset();
				}
				if ((flags&PlugInFilter.NO_CHANGES)==0&&(flags&PlugInFilter.KEEP_THRESHOLD)==0)
					ip.resetBinaryThreshold();
			} else {  //  stack
				Undo.reset();	 // no undo for processing a complete stack
				IJ.resetEscape();
				int slicesToDo = processedAsPreview!=0 ? slices-1 : slices;
				nPasses *= slicesToDo;
				if (theFilter instanceof ExtendedPlugInFilter)
					((ExtendedPlugInFilter)theFilter).setNPasses(nPasses);
				int threads = 1;
				if ((flags&PlugInFilter.PARALLELIZE_STACKS)!=0) {
					threads = Prefs.getThreads(); // multithread support for multiprocessor machines
					if (threads>slicesToDo) threads = slicesToDo;
					if (threads>1) slicesForThread = new Hashtable<Thread, int[]>(threads-1);
				}
				int startSlice = 1;
				for (int i=1; i<threads; i++) {				// setup the background threads
					int endSlice = (slicesToDo*i)/threads;
					if (processedAsPreview!=0 && processedAsPreview<=endSlice) endSlice++;
					Thread bgThread = new Thread(this, command+" "+startSlice+"-"+endSlice);
					slicesForThread.put(bgThread, new int[] {startSlice, endSlice});
					bgThread.start();
					//IJ.log("Stack: Thread for slices "+startSlice+"-"+endSlice+" started");
					startSlice = endSlice+1;
				}
				//IJ.log("Stack: Slices "+startSlice+"-"+slices+" by main thread");
				processStack(startSlice, slices);			// the current thread does the rest
				if (slicesForThread != null) {
					while (slicesForThread.size()>0) {	  // for all other threads:
						Thread theThread = (Thread)slicesForThread.keys().nextElement();
						try {
							theThread.join();				// wait until thread has finished
						} catch (InterruptedException e) {}
						slicesForThread.remove(theThread);	// and remove it from the list.
					}
				}
			}
		} // end processing:
		if ((flags&PlugInFilter.FINAL_PROCESSING)!=0 && !IJ.escapePressed())
			((PlugInFilter)theFilter).setup("final", imp);
		if (IJ.escapePressed()) {
			IJ.showStatus(command + " INTERRUPTED");
			IJ.showProgress(1,1);
		} else
			IJ.showTime(imp, imp.getStartTime()-previewTime, command + ": ", doStack?slices:1);
		IJ.showProgress(1.0);
		if (ipChanged) {
			imp.changes = true;
			imp.updateAndDraw();
		}
		ImageWindow win = imp.getWindow();
		if (win!=null) {
			win.running = false;
			win.running2 = false;
		}
		imp.unlock();
	}

	/** Process a stack or part of it. The slice given by class variable
	 *	processedAsPreview remains unchanged.
	 * @param firstSlice Slice number of the first slice to be processed
	 * @param endSlice	 Slice number of the last slice to be processed
	 */
	private void processStack(int firstSlice, int endSlice) {
		ImageStack stack = imp.getStack();
		ImageProcessor ip = stack.getProcessor(firstSlice);
		prepareProcessor(ip, imp);
		ip.setLineWidth(Line.getWidth());		//in contrast to imp.getProcessor, stack.getProcessor does not do this
		FloatProcessor fp = null;
		int slices = imp.getNSlices();
		for (int i=firstSlice; i<=endSlice; i++) {
			if (i != processedAsPreview) {
				announceSliceNumber(i);
				ip.setPixels(stack.getPixels(i));
				ip.setSliceNumber(i);
				processOneImage(ip, fp, false);
				if (IJ.escapePressed()) {IJ.beep(); break;}
			}
		}
	}

	/** prepare an ImageProcessor by setting roi and CalibrationTable.
	 */
	private void prepareProcessor(ImageProcessor ip, ImagePlus imp) {
		ImageProcessor mask = imp.getMask();
		Roi roi = imp.getRoi();
		if (roi!=null && roi.isArea())
			ip.setRoi(roi);
		else
			ip.setRoi((Roi)null);
		if (imp.getStackSize()>1) {
			ImageProcessor ip2 = imp.getProcessor();
			double min1 = ip2.getMinThreshold();
			double max1 = ip2.getMaxThreshold();
			double min2 = ip.getMinThreshold();
			double max2 = ip.getMaxThreshold();
			if (min1!=ImageProcessor.NO_THRESHOLD && (min1!=min2||max1!=max2))
				ip.setThreshold(min1, max1, ImageProcessor.NO_LUT_UPDATE);
		}
		//float[] cTable = imp.getCalibration().getCTable();
		//ip.setCalibrationTable(cTable);
	}

	/**
	 * Process a single image with the PlugInFilter.
	 * @param ip		The image data that should be processed
	 * @param fp		A Floatprocessor as a target for conversion to Float. May be null.
	 * @param snapshotDone Whether a snapshot of ip has been be taken previously. if
	 * snapshotDone is true, the snapshot needed for the SUPPORTS_MASKING or SNAPSHOT
	 * flags of the filter is not created any more.
	 * Class variables used: flags (input), snapshotDone (set if a snapshot of ip
	 * is taken), ipChanged (set if ip was probably changed).
	 */
	private void processOneImage(ImageProcessor ip, FloatProcessor fp, boolean snapshotDone) {
		if ((flags&PlugInFilter.PARALLELIZE_IMAGES)!=0) {
			processImageUsingThreads(ip, fp, snapshotDone);
			return;
		}
		Thread thread = Thread.currentThread();
		boolean convertToFloat = (flags&PlugInFilter.CONVERT_TO_FLOAT)!=0 && !(ip instanceof FloatProcessor);
		boolean doMasking = (flags&PlugInFilter.SUPPORTS_MASKING)!=0 && ip.getMask() != null;
		if (!snapshotDone && (doMasking || ((flags&PlugInFilter.SNAPSHOT)!=0) && !convertToFloat)) {
			ip.snapshot();
			this.snapshotDone = true;
		}
		if (convertToFloat) {
			for (int i=0; i<ip.getNChannels(); i++) {
				fp = ip.toFloat(i, fp);
				fp.setSliceNumber(ip.getSliceNumber());
				if (thread.isInterrupted()) return;			// interrupt processing for preview?
				if ((flags&PlugInFilter.SNAPSHOT)!=0) fp.snapshot();
				if (doStack) IJ.showProgress(pass/(double)nPasses);
				((PlugInFilter)theFilter).run(fp);
				if (thread.isInterrupted()) return;
				//IJ.log("slice="+getSliceNumber()+" pass="+pass+"/"+nPasses);
				pass++;
				if ((flags&PlugInFilter.NO_CHANGES)==0) {
					ipChanged = true;
					ip.setPixels(i, fp);
				}
			}
		} else {
			if ((flags&PlugInFilter.NO_CHANGES)==0) ipChanged = true;
				if (doStack) IJ.showProgress(pass/(double)nPasses); 
			((PlugInFilter)theFilter).run(ip);
			pass++;
		}
		if (thread.isInterrupted()) return;
		if (doMasking)
			ip.reset(ip.getMask());	 //restore image outside irregular roi
   }

	private void processImageUsingThreads(ImageProcessor ip, FloatProcessor fp, boolean snapshotDone) {
		if (IJ.debugMode)
			IJ.log("using threads: "+ip.getNChannels());
		Thread thread = Thread.currentThread();
		boolean convertToFloat = (flags&PlugInFilter.CONVERT_TO_FLOAT)!=0 && !(ip instanceof FloatProcessor);
		boolean doMasking = (flags&PlugInFilter.SUPPORTS_MASKING)!=0 && ip.getMask() != null;
		if (!snapshotDone && (doMasking || ((flags&PlugInFilter.SNAPSHOT)!=0) && !convertToFloat)) {
			ip.snapshot();
			this.snapshotDone = true;
		}
		if (convertToFloat) {
			for (int i=0; i<ip.getNChannels(); i++) {
				fp = ip.toFloat(i, fp);
				fp.setSliceNumber(ip.getSliceNumber());
				if (thread.isInterrupted()) return;			// interrupt processing for preview?
				if ((flags&PlugInFilter.SNAPSHOT)!=0) fp.snapshot();
				if (doStack) IJ.showProgress(pass/(double)nPasses);
				processChannelUsingThreads(fp);
				if (thread.isInterrupted()) return;
				//IJ.log("slice="+getSliceNumber()+" pass="+pass+"/"+nPasses);
				if ((flags&PlugInFilter.NO_CHANGES)==0) {
					ipChanged = true;
					ip.setPixels(i, fp);
				}
			}
		} else {
			if ((flags&PlugInFilter.NO_CHANGES)==0) ipChanged = true;
				if (doStack) IJ.showProgress(pass/(double)nPasses); 
			processChannelUsingThreads(ip);
		}
		if (thread.isInterrupted()) return;
		if (doMasking)
			ip.reset(ip.getMask());	 //restore image outside irregular roi
	}

	/** process an image or a single color channel of an RGB image by splitting
	 *	into ROIs and using a separate thread for each roi */
	private void processChannelUsingThreads(ImageProcessor ip) {
		ImageProcessor mask = ip.getMask();
		Rectangle roi = ip.getRoi();
		int threads = Prefs.getThreads();
		if (IJ.debugMode)
			IJ.log("processing channel: "+threads);
		if (threads>roi.height) threads = roi.height;
		if (threads>1) roisForThread = new Hashtable<Thread, ImageProcessor>(threads-1);
		int y1 = roi.y;
		for (int i=1; i<threads; i++) {
			int y2 = roi.y+(roi.height*i)/threads-1;
			Thread bgThread = new Thread(this, command+" "+y1+"-"+y2);
			Rectangle roi2 = new Rectangle(roi.x, y1, roi.width, y2-y1+1);
			roisForThread.put(bgThread, duplicateProcessor(ip, roi2));
			bgThread.start();
			if (IJ.debugMode)
				IJ.log("  starting thread: "+y1+"-"+y2);
			y1 = y2+1;
		}
		if (IJ.debugMode)
			IJ.log("  main thread "+y1+"-"+(roi.y+roi.height));
		Rectangle roi2 = new Rectangle(roi.x, y1, roi.width, roi.y+roi.height-y1);
		((PlugInFilter)theFilter).run(duplicateProcessor(ip, roi2)); 	// current thread does the rest
		pass++;
		if (roisForThread != null) {
			for (Enumeration<Thread> en = roisForThread.keys(); en.hasMoreElements();) {
				Thread theThread = en.nextElement();
				try {
					theThread.join();		// wait until thread has finished
				} catch (InterruptedException e) { // if preview cancelled:
					interruptRoiThreads(roisForThread); //interrupt all threads and join
					Thread.currentThread().interrupt(); //restore 'interrupted' state
					break;
				}
			}
		}
		roisForThread = null;
		ip.setMask(mask);  // restore ROI
		ip.setRoi(roi);
	}

	ImageProcessor duplicateProcessor(ImageProcessor ip, Rectangle roi) {
		ImageProcessor ip2 = (ImageProcessor)ip.clone();
		ip2.setRoi(roi);
		return ip2;
	}

	/** interrupt threads processing the rois of an image and wait till they have finished */
	void interruptRoiThreads(Hashtable<Thread, ImageProcessor> roisForThread) {
		if (roisForThread==null) return;	//class variable may become null in other thread
		for (Enumeration<Thread> en = roisForThread.keys(); en.hasMoreElements();)
			((Thread)en.nextElement()).interrupt(); //interrupt all threads
		for (Enumeration<Thread> en = roisForThread.keys(); en.hasMoreElements();)
			try {
				((Thread)en.nextElement()).join();
			} catch (Exception e){}
	}
 
	/** test whether an ImagePlus can be processed based on the flags specified
	 *	and display an error message if not.
	 */
	private boolean checkImagePlus(ImagePlus imp, int flags, String cmd) {
		boolean imageRequired = (flags&PlugInFilter.NO_IMAGE_REQUIRED)==0;
		if (imageRequired && imp==null)
			{IJ.noImage(); return false;}
		if (imageRequired) {
			if (imp.getProcessor()==null)
				{wrongType(flags, cmd); return false;}
			int type = imp.getType();
			switch (type) {
				case ImagePlus.GRAY8:
					if ((flags&PlugInFilter.DOES_8G)==0)
					{wrongType(flags, cmd); return false;}
					break;
				case ImagePlus.COLOR_256:
					if ((flags&PlugInFilter.DOES_8C)==0)
					{wrongType(flags, cmd); return false;}
					break;
				case ImagePlus.GRAY16:
					if ((flags&PlugInFilter.DOES_16)==0)
					{wrongType(flags, cmd); return false;}
					break;
				case ImagePlus.GRAY32:
					if ((flags&PlugInFilter.DOES_32)==0)
					{wrongType(flags, cmd); return false;}
					break;
				case ImagePlus.COLOR_RGB:
					if ((flags&PlugInFilter.DOES_RGB)==0)
					{wrongType(flags, cmd); return false;}
					break;
			}
			if ((flags&PlugInFilter.ROI_REQUIRED)!=0 && imp.getRoi()==null)
			{IJ.error(cmd, "This command requires a selection"); return false;}
			if ((flags&PlugInFilter.STACK_REQUIRED)!=0 && imp.getStackSize()==1)
			{IJ.error(cmd, "This command requires a stack"); return false;}
		} // if imageRequired
		return true;
	}

	/** Display an error message, telling the allowed image types
	 */
	static void wrongType(int flags, String cmd) {
		String s = "\""+cmd+"\" requires an image of type:\n \n";
		if ((flags&PlugInFilter.DOES_8G)!=0) s +=  "	8-bit grayscale\n";
		if ((flags&PlugInFilter.DOES_8C)!=0) s +=  "	8-bit color\n";
		if ((flags&PlugInFilter.DOES_16)!=0) s +=  "	16-bit grayscale\n";
		if ((flags&PlugInFilter.DOES_32)!=0) s +=  "	32-bit (float) grayscale\n";
		if ((flags&PlugInFilter.DOES_RGB)!=0) s += "	RGB color\n";
		IJ.error(s);
	}

	/** Make the slice number accessible to the PlugInFilter by putting it
	 * into the appropriate hashtable.
	 */
	private void announceSliceNumber(int slice) {
		synchronized(sliceForThread){
			Integer number = new Integer(slice);
			sliceForThread.put(Thread.currentThread(), number);
		}
	}

	/** Return the slice number currently processed by the calling thread.
	 * @return The slice number. Returns -1 on error (when not processing).
	 */
	public int getSliceNumber() {
		synchronized(sliceForThread){
			Integer number = (Integer)sliceForThread.get(Thread.currentThread());
			return (number == null) ? -1 : number.intValue();
		}
	}

	/** The dispatcher for the background threads
	 */
	public void run() {
		Thread thread = Thread.currentThread();
		try {
			if (thread==previewThread)
				runPreview();
			else if (roisForThread!=null && roisForThread.containsKey(thread)) {
				ImageProcessor ip = (ImageProcessor)roisForThread.get(thread);
				((PlugInFilter)theFilter).run(ip);
				ip.setPixels(null);
				ip.setSnapshotPixels(null);
			} else if (slicesForThread!=null && slicesForThread.containsKey(thread)) {
				int[] range = (int[])slicesForThread.get(thread);
				processStack(range[0], range[1]);
			} else
				IJ.error("PlugInFilterRunner internal error:\nunsolicited background thread");
		} catch (Exception err) {
			if (thread==previewThread) {
				gd.previewRunning(false);
				IJ.wait(100); // needed on Macs
				previewCheckbox.setState(false);
				bgPreviewOn = false;
				previewThread = null;
			}
			String msg = ""+err;
			if (msg.indexOf(Macro.MACRO_CANCELED)==-1) {
				IJ.beep();
				IJ.log("ERROR: "+msg+"\nin "+thread.getName()+
					"\nat "+(err.getStackTrace()[0])+"\nfrom "+(err.getStackTrace()[1]));
			}
		}
	}
			
	/** The background thread for preview */
	private void runPreview() {
		if (IJ.debugMode)
			IJ.log("preview thread started; imp="+imp.getTitle());
		Thread thread = Thread.currentThread();
		ImageProcessor ip = imp.getProcessor();
		Roi originalRoi = imp.getRoi();
		originalOverlay = imp.getOverlay();
		FloatProcessor fp = null;
		prepareProcessor(ip, imp);
		announceSliceNumber(imp.getCurrentSlice());
		if (!snapshotDone && (flags&PlugInFilter.NO_CHANGES)==0) {
			ip.snapshot();
			snapshotDone = true;
		}
		boolean previewDataOk = false;
		while(bgPreviewOn) {
			if (previewCheckboxOn) gd.previewRunning(true); // visual feedback
			interruptable: {
				if (imp.getRoi() != originalRoi) {
					imp.setRoi(originalRoi);		// restore roi; the PlugInFilter may have affected it
					if (originalRoi!=null && originalRoi.isArea())
						ip.setRoi(originalRoi);
					else
						ip.setRoi((Roi)null);
				}
				if (ipChanged)						// restore image data if necessary
					ip.reset();
				ipChanged = false;
				previewDataOk = false;
				long startTime = System.currentTimeMillis();
				pass = 0;
				if (theFilter instanceof ExtendedPlugInFilter)
					((ExtendedPlugInFilter)theFilter).setNPasses(nPasses); //this should reset pass in the filter
				if (thread.isInterrupted())
					break interruptable;
				//IJ.log("process preview start now");
				processOneImage(ip, fp, true);		// P R O C E S S   (sets ipChanged)
				IJ.showProgress(1.0);
				if (thread.isInterrupted())
					break interruptable;
				previewDataOk = true;
				previewTime = System.currentTimeMillis() - startTime;
				imp.updateAndDraw();
				if (IJ.debugMode)
					IJ.log("preview processing done");
			}
			gd.previewRunning(false);				// optical feedback
			IJ.showStatus("");						//delete last status messages from processing
			synchronized(this) {
				if (!bgPreviewOn)
					break;							//thread should stop and possibly keep the data
				try {
					wait();							//wait for interrupted (don't keep preview) or notify (keep preview)
				} catch (InterruptedException e) {
					previewDataOk = false;
				}
			} // synchronized
		} // while bgPreviewOn
		if (thread.isInterrupted())
			previewDataOk = false;					//interrupted always means "don't keep preview"
		if (!previewDataOk || !bgKeepPreview) {		//no need to keep the result
			imp.setRoi(originalRoi);				//restore roi
			if (ipChanged) {						//revert the image data
				ip.reset();
				ipChanged = false;
			}
		}
		imp.updateAndDraw();						//display current state of image (reset or result of preview)
		sliceForThread.remove(thread);				//no need to announce the slice number any more
	 }

	/** stop the background process responsible for preview as fast as possible
	 and wait until the preview thread has finished */
	private void killPreview() {
		if (previewThread == null) return;
		synchronized (this) {
			previewThread.interrupt();		//ask for premature finishing (interrupt first -> no keepPreview)
			bgPreviewOn = false;				//tell a possible background thread to terminate when it has finished
			if (roisForThread!=null)
				interruptRoiThreads(roisForThread);
		}
		waitForPreviewDone();
		imp.setOverlay(originalOverlay);
	}

	/** stop the background process responsible for preview and wait until the preview thread has finished */
	private void waitForPreviewDone() {
		if (previewThread.isAlive()) try {		//a NullPointerException is possible if the thread finishes in the meanwhile
			previewThread.setPriority(Thread.currentThread().getPriority());
		} catch (Exception e) {}
		synchronized (this) {
			bgPreviewOn = false;	 //tell a possible background thread to terminate
			notify();							//(but finish processing unless interrupted)
		}
		try {previewThread.join();}				//wait until the background thread is done
		catch (InterruptedException e){}
		previewThread = null;
	}

	/* set the GenericDialog gd where the preview comes from (if gd is
	 * suitable for listening, i.e., if it has a preview checkbox).
	 */
	public void setDialog(GenericDialog gd) {
		if (gd != null && imp != null) {
			previewCheckbox = gd.getPreviewCheckbox();
			if (previewCheckbox != null) {
				gd.addDialogListener(this);
				this.gd = gd;
			}
		} //IJ.log("setDialog done");
	}

	/** The listener to any change in the dialog. It is used for preview.
	 * It is invoked every time the user changes something in the dialog
	 * (except OK and cancel buttons), provided that all previous
	 * listeners (parameter checking) have returned true.
	 *
	 * @param e The event that has happened in the dialog. This method may
	 *			be also called with e=null, e.g. to start preview already
	 *			when the dialog appears.
	 * @return	Always true. (The return value determines whether the
	 *			dialog will enable the OK button)
	 */
	public boolean dialogItemChanged(GenericDialog gd, AWTEvent e) {
		if (previewCheckbox == null || imp == null) return true;
		previewCheckboxOn = previewCheckbox.getState();
		if (previewCheckboxOn  && previewThread == null) {
			bgPreviewOn = true;						//need to start a background thread for preview
			previewThread = new Thread(this, command+" Preview");
			int priority = Thread.currentThread().getPriority() - 2;
			if (priority < Thread.MIN_PRIORITY) priority = Thread.MIN_PRIORITY;
			previewThread.setPriority(priority);	//preview on lower priority than dialog
			previewThread.start();
			if (IJ.debugMode)
				IJ.log(command+" Preview thread was started");
			return true;
		}
		if (previewThread != null) {				//thread runs already
			if (!previewCheckboxOn) {				//preview toggled off
				killPreview();
				return true;
			} else
				previewThread.interrupt();	//all other changes: restart calculating preview (with new parameters)
		}
		return true;
	}
	
}