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; private Object theFilter; private ImagePlus imp;
private int flags; private Overlay originalOverlay; private boolean previewCheckboxOn; private boolean bgPreviewOn; private boolean bgKeepPreview; private Thread previewThread; private GenericDialog gd; private Checkbox previewCheckbox; private long previewTime; private boolean ipChanged; private int processedAsPreview; private Object snapshotPixels; private Hashtable<Thread, int[]> slicesForThread; private Hashtable<Thread, ImageProcessor> roisForThread; Hashtable sliceForThread = new Hashtable(); private int nPasses; private int pass; private boolean doStack;
public PlugInFilterRunner(Object theFilter, String command, String arg) {
this.theFilter = theFilter;
this.command = command;
imp = WindowManager.getCurrentImage();
flags = ((PlugInFilter)theFilter).setup(arg, imp); if ((flags&PlugInFilter.DONE)!=0) return;
if (!checkImagePlus(imp, flags, command)) return; if ((flags&PlugInFilter.NO_IMAGE_REQUIRED)!=0)
imp = null; Roi roi = null;
if (imp != null) {
roi = imp.getRoi();
if (roi!=null) roi.endPaste(); if (!imp.lock())
return; nPasses = ((flags&PlugInFilter.CONVERT_TO_FLOAT)!=0) ? imp.getProcessor().getNChannels():1;
}
if (theFilter instanceof ExtendedPlugInFilter) { try {
flags = ((ExtendedPlugInFilter)theFilter).showDialog(imp, command, this); } catch(Exception e) {
killPreview();
if (Macro.MACRO_CANCELED.equals(e.getMessage()))
throw new RuntimeException(Macro.MACRO_CANCELED);
}
if (snapshotPixels != null)
Undo.setup(Undo.FILTER, imp); 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 ((flags&PlugInFilter.DONE)!=0) {
if (imp != null)
imp.unlock();
return;
} else if (imp==null) {
((PlugInFilter)theFilter).run(null); return;
}
int slices = imp.getStackSize();
if ((flags&PlugInFilter.PARALLELIZE_IMAGES)!=0)
flags &= ~PlugInFilter.PARALLELIZE_STACKS;
doStack = slices>1 && (flags&PlugInFilter.DOES_STACKS)!=0;
imp.startTiming();
if (doStack || processedAsPreview==0) { ImageProcessor ip = imp.getProcessor();
pass = 0;
if (!doStack) { FloatProcessor fp = null;
prepareProcessor(ip, imp);
announceSliceNumber(imp.getCurrentSlice());
if (theFilter instanceof ExtendedPlugInFilter)
((ExtendedPlugInFilter)theFilter).setNPasses(nPasses);
if ((flags&PlugInFilter.NO_CHANGES)==0) { boolean disableUndo = Prefs.disableUndo || (flags&PlugInFilter.NO_UNDO)!=0;
if (!disableUndo || ((ip instanceof ColorProcessor)&&WindowManager.getWindow("B&C")!=null)) {
ip.snapshot();
snapshotPixels = ip.getSnapshotPixels();
}
}
processOneImage(ip, fp, snapshotPixels); if ((flags&PlugInFilter.NO_CHANGES)==0) { if (snapshotPixels != null) {
ip.setSnapshotPixels(snapshotPixels);
Undo.setup(Undo.FILTER, imp);
} else
Undo.reset();
}
if ((flags&PlugInFilter.NO_CHANGES)==0&&(flags&PlugInFilter.KEEP_THRESHOLD)==0)
ip.resetBinaryThreshold();
} else { if ((flags&PlugInFilter.NO_UNDO_RESET)==0)
Undo.reset(); 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(); 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++) { 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();
startSlice = endSlice+1;
}
processStack(startSlice, slices); if (slicesForThread != null) {
while (slicesForThread.size()>0) { Thread theThread = (Thread)slicesForThread.keys().nextElement();
try {
theThread.join(); } catch (InterruptedException e) {}
slicesForThread.remove(theThread); }
}
}
} 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();
}
private void processStack(int firstSlice, int endSlice) {
ImageStack stack = imp.getStack();
ImageProcessor ip = stack.getProcessor(firstSlice);
prepareProcessor(ip, imp);
ip.setLineWidth(Line.getWidth()); 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);
ip.setSnapshotPixels(null);
processOneImage(ip, fp, null);
if (IJ.escapePressed()) {IJ.beep(); break;}
}
}
}
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);
}
}
private void processOneImage(ImageProcessor ip, FloatProcessor fp, Object snapshotPixels) {
if ((flags&PlugInFilter.PARALLELIZE_IMAGES)!=0) {
processImageUsingThreads(ip, fp, snapshotPixels);
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 (snapshotPixels==null && (doMasking || ((flags&PlugInFilter.SNAPSHOT)!=0) && !convertToFloat)) {
ip.snapshot();
this.snapshotPixels = ip.getSnapshotPixels();
}
if (convertToFloat) {
for (int i=0; i<ip.getNChannels(); i++) {
fp = ip.toFloat(i, fp);
fp.setSliceNumber(ip.getSliceNumber());
if (thread.isInterrupted()) return; if ((flags&PlugInFilter.SNAPSHOT)!=0) fp.snapshot();
if (doStack) IJ.showProgress(pass/(double)nPasses);
((PlugInFilter)theFilter).run(fp);
if (thread.isInterrupted()) return;
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) {
if (snapshotPixels != null)
ip.setSnapshotPixels(snapshotPixels); ip.reset(ip.getMask()); }
}
private void processImageUsingThreads(ImageProcessor ip, FloatProcessor fp, Object snapshotPixels) {
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 (snapshotPixels==null && (doMasking || ((flags&PlugInFilter.SNAPSHOT)!=0) && !convertToFloat)) {
ip.snapshot();
this.snapshotPixels = ip.getSnapshotPixels();
}
if (convertToFloat) {
for (int i=0; i<ip.getNChannels(); i++) {
fp = ip.toFloat(i, fp);
fp.setSliceNumber(ip.getSliceNumber());
if (thread.isInterrupted()) return; if ((flags&PlugInFilter.SNAPSHOT)!=0) fp.snapshot();
if (doStack) IJ.showProgress(pass/(double)nPasses);
processChannelUsingThreads(fp);
if (thread.isInterrupted()) return;
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) {
if (snapshotPixels != null)
ip.setSnapshotPixels(snapshotPixels);
ip.reset(ip.getMask()); }
}
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)); pass++;
if (roisForThread != null) {
for (Enumeration<Thread> en = roisForThread.keys(); en.hasMoreElements();) {
Thread theThread = en.nextElement();
try {
theThread.join(); } catch (InterruptedException e) { interruptRoiThreads(roisForThread); Thread.currentThread().interrupt(); break;
}
}
}
roisForThread = null;
ip.setMask(mask); ip.setRoi(roi);
}
ImageProcessor duplicateProcessor(ImageProcessor ip, Rectangle roi) {
ImageProcessor ip2 = (ImageProcessor)ip.clone();
ip2.setRoi(roi);
return ip2;
}
void interruptRoiThreads(Hashtable<Thread, ImageProcessor> roisForThread) {
if (roisForThread==null) return; for (Enumeration<Thread> en = roisForThread.keys(); en.hasMoreElements();)
((Thread)en.nextElement()).interrupt(); for (Enumeration<Thread> en = roisForThread.keys(); en.hasMoreElements();)
try {
((Thread)en.nextElement()).join();
} catch (Exception e){}
}
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;}
} return true;
}
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);
}
private void announceSliceNumber(int slice) {
synchronized(sliceForThread){
Integer number = Integer.valueOf(slice);
sliceForThread.put(Thread.currentThread(), number);
}
}
public int getSliceNumber() {
synchronized(sliceForThread){
Integer number = (Integer)sliceForThread.get(Thread.currentThread());
return (number == null) ? -1 : number.intValue();
}
}
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); 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]));
}
}
}
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 (snapshotPixels==null && (flags&PlugInFilter.NO_CHANGES)==0) {
ip.snapshot();
snapshotPixels = ip.getSnapshotPixels();
}
boolean previewDataOk = false;
while(bgPreviewOn) {
if (previewCheckboxOn) gd.previewRunning(true); interruptable: {
if (imp.getRoi() != originalRoi) {
imp.setRoi(originalRoi); if (originalRoi!=null && originalRoi.isArea())
ip.setRoi(originalRoi);
else
ip.setRoi((Roi)null);
}
if (ipChanged) { ip.setSnapshotPixels(snapshotPixels);
ip.reset();
}
ipChanged = false;
previewDataOk = false;
long startTime = System.currentTimeMillis();
pass = 0;
if (theFilter instanceof ExtendedPlugInFilter)
((ExtendedPlugInFilter)theFilter).setNPasses(nPasses); if (thread.isInterrupted())
break interruptable;
processOneImage(ip, fp, snapshotPixels); 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); IJ.showStatus(""); synchronized(this) {
if (!bgPreviewOn)
break; try {
wait(); } catch (InterruptedException e) {
previewDataOk = false;
}
} } if (thread.isInterrupted())
previewDataOk = false; if (!previewDataOk || !bgKeepPreview) { imp.setRoi(originalRoi); if (ipChanged) { ip.setSnapshotPixels(snapshotPixels);
ip.reset();
ipChanged = false;
}
}
imp.updateAndDraw(); sliceForThread.remove(thread); }
private void killPreview() {
if (previewThread == null) return;
synchronized (this) {
previewThread.interrupt(); bgPreviewOn = false; if (roisForThread!=null)
interruptRoiThreads(roisForThread);
}
waitForPreviewDone();
imp.setOverlay(originalOverlay);
}
private void waitForPreviewDone() {
if (previewThread.isAlive()) try { previewThread.setPriority(Thread.currentThread().getPriority());
} catch (Exception e) {}
synchronized (this) {
bgPreviewOn = false; notify(); }
try {previewThread.join();} catch (InterruptedException e){}
previewThread = null;
}
public void setDialog(GenericDialog gd) {
if (gd != null && imp != null) {
previewCheckbox = gd.getPreviewCheckbox();
if (previewCheckbox != null) {
gd.addDialogListener(this);
this.gd = gd;
}
} }
public boolean dialogItemChanged(GenericDialog gd, AWTEvent e) {
if (previewCheckbox == null || imp == null) return true;
previewCheckboxOn = previewCheckbox.getState();
if (previewCheckboxOn && previewThread == null) {
bgPreviewOn = true; previewThread = new Thread(this, command+" Preview");
int priority = Thread.currentThread().getPriority() - 2;
if (priority < Thread.MIN_PRIORITY) priority = Thread.MIN_PRIORITY;
previewThread.setPriority(priority); previewThread.start();
if (IJ.debugMode)
IJ.log(command+" Preview thread was started");
return true;
}
if (previewThread != null) { if (!previewCheckboxOn) { killPreview();
return true;
} else
previewThread.interrupt(); }
return true;
}
}