|
ThresholdAdjuster |
|
package ij.plugin.frame;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import ij.*;
import ij.plugin.*;
import ij.process.*;
import ij.gui.*;
import ij.measure.*;
import ij.plugin.frame.Recorder;
import ij.plugin.filter.Analyzer;
/** Adjusts the lower and upper threshold levels of the active image. This
class is multi-threaded to provide a more responsive user interface. */
public class ThresholdAdjuster extends PlugInFrame implements PlugIn, Measurements,
Runnable, ActionListener, AdjustmentListener, ItemListener {
public static final String LOC_KEY = "threshold.loc";
static final int RED=0, BLACK_AND_WHITE=1, OVER_UNDER=2;
static final String[] modes = {"Red","Black & White", "Over/Under"};
static final double defaultMinThreshold = 85;
static final double defaultMaxThreshold = 170;
static boolean fill1 = true;
static boolean fill2 = true;
static boolean useBW = true;
static boolean backgroundToNaN = true;
static Frame instance;
static int mode = RED;
ThresholdPlot plot = new ThresholdPlot();
Thread thread;
int minValue = -1;
int maxValue = -1;
int sliderRange = 256;
boolean doAutoAdjust,doReset,doApplyLut,doStateChange,doSet;
Panel panel;
Button autoB, resetB, applyB, setB;
int previousImageID;
int previousImageType;
double previousMin, previousMax;
ImageJ ij;
double minThreshold, maxThreshold; // 0-255
Scrollbar minSlider, maxSlider;
Label label1, label2;
boolean done;
boolean invertedLut;
int lutColor;
static Choice choice;
boolean firstActivation;
public ThresholdAdjuster() {
super("Threshold");
if (instance!=null) {
instance.toFront();
return;
}
WindowManager.addWindow(this);
instance = this;
setLutColor(mode);
IJ.register(PasteController.class);
ij = IJ.getInstance();
Font font = new Font("SansSerif", Font.PLAIN, 10);
GridBagLayout gridbag = new GridBagLayout();
GridBagConstraints c = new GridBagConstraints();
setLayout(gridbag);
// plot
int y = 0;
c.gridx = 0;
c.gridy = y++;
c.gridwidth = 2;
c.fill = GridBagConstraints.BOTH;
c.anchor = GridBagConstraints.CENTER;
c.insets = new Insets(10, 10, 0, 10);
add(plot, c);
plot.addKeyListener(ij);
// minThreshold slider
minSlider = new Scrollbar(Scrollbar.HORIZONTAL, sliderRange/3, 1, 0, sliderRange);
c.gridx = 0;
c.gridy = y++;
c.gridwidth = 1;
c.weightx = IJ.isMacintosh()?90:100;
c.fill = GridBagConstraints.HORIZONTAL;
c.insets = new Insets(5, 10, 0, 0);
add(minSlider, c);
minSlider.addAdjustmentListener(this);
minSlider.addKeyListener(ij);
minSlider.setUnitIncrement(1);
minSlider.setFocusable(false);
// minThreshold slider label
c.gridx = 1;
c.gridwidth = 1;
c.weightx = IJ.isMacintosh()?10:0;
c.insets = new Insets(5, 0, 0, 10);
label1 = new Label(" ", Label.RIGHT);
label1.setFont(font);
add(label1, c);
// maxThreshold slider
maxSlider = new Scrollbar(Scrollbar.HORIZONTAL, sliderRange*2/3, 1, 0, sliderRange);
c.gridx = 0;
c.gridy = y++;
c.gridwidth = 1;
c.weightx = 100;
c.insets = new Insets(0, 10, 0, 0);
add(maxSlider, c);
maxSlider.addAdjustmentListener(this);
maxSlider.addKeyListener(ij);
maxSlider.setUnitIncrement(1);
maxSlider.setFocusable(false);
// maxThreshold slider label
c.gridx = 1;
c.gridwidth = 1;
c.weightx = 0;
c.insets = new Insets(0, 0, 0, 10);
label2 = new Label(" ", Label.RIGHT);
label2.setFont(font);
add(label2, c);
// choice
choice = new Choice();
for (int i=0; i<modes.length; i++)
choice.addItem(modes[i]);
choice.select(mode);
choice.addItemListener(this);
choice.addKeyListener(ij);
c.gridx = 0;
c.gridy = y++;
c.gridwidth = 2;
c.insets = new Insets(5, 5, 0, 5);
c.anchor = GridBagConstraints.CENTER;
c.fill = GridBagConstraints.NONE;
add(choice, c);
// buttons
int trim = IJ.isMacOSX()?11:0;
panel = new Panel();
autoB = new TrimmedButton("Auto",trim);
autoB.addActionListener(this);
autoB.addKeyListener(ij);
panel.add(autoB);
applyB = new TrimmedButton("Apply",trim);
applyB.addActionListener(this);
applyB.addKeyListener(ij);
panel.add(applyB);
resetB = new TrimmedButton("Reset",trim);
resetB.addActionListener(this);
resetB.addKeyListener(ij);
panel.add(resetB);
setB = new TrimmedButton("Set",trim);
setB.addActionListener(this);
setB.addKeyListener(ij);
panel.add(setB);
c.gridx = 0;
c.gridy = y++;
c.gridwidth = 2;
c.insets = new Insets(0, 5, 10, 5);
add(panel, c);
addKeyListener(ij); // ImageJ handles keyboard shortcuts
pack();
Point loc = Prefs.getLocation(LOC_KEY);
if (loc!=null)
setLocation(loc);
else
GUI.center(this);
firstActivation = true;
if (IJ.isMacOSX()) setResizable(false);
show();
thread = new Thread(this, "ThresholdAdjuster");
//thread.setPriority(thread.getPriority()-1);
thread.start();
ImagePlus imp = WindowManager.getCurrentImage();
if (imp!=null)
setup(imp);
}
public synchronized void adjustmentValueChanged(AdjustmentEvent e) {
if (e.getSource()==minSlider)
minValue = minSlider.getValue();
else
maxValue = maxSlider.getValue();
notify();
}
public synchronized void actionPerformed(ActionEvent e) {
Button b = (Button)e.getSource();
if (b==null) return;
if (b==resetB)
doReset = true;
else if (b==autoB)
doAutoAdjust = true;
else if (b==applyB)
doApplyLut = true;
else if (b==setB)
doSet = true;
notify();
}
void setLutColor(int mode) {
switch (mode) {
case RED:
lutColor = ImageProcessor.RED_LUT;
break;
case BLACK_AND_WHITE:
lutColor = ImageProcessor.BLACK_AND_WHITE_LUT;
break;
case OVER_UNDER:
lutColor = ImageProcessor.OVER_UNDER_LUT;
break;
}
}
public synchronized void itemStateChanged(ItemEvent e) {
mode = choice.getSelectedIndex();
setLutColor(mode);
doStateChange = true;
notify();
}
ImageProcessor setup(ImagePlus imp) {
ImageProcessor ip;
int type = imp.getType();
if (type==ImagePlus.COLOR_RGB)
return null;
ip = imp.getProcessor();
boolean minMaxChange = false;
boolean not8Bits = type==ImagePlus.GRAY16 || type==ImagePlus.GRAY32;
if (not8Bits) {
if (ip.getMin()==plot.stackMin && ip.getMax()==plot.stackMax)
minMaxChange = false;
else if (ip.getMin()!=previousMin || ip.getMax()!=previousMax) {
minMaxChange = true;
previousMin = ip.getMin();
previousMax = ip.getMax();
}
}
int id = imp.getID();
if (minMaxChange || id!=previousImageID || type!=previousImageType) {
//IJ.log(minMaxChange +" "+ (id!=previousImageID)+" "+(type!=previousImageType));
if (not8Bits && minMaxChange) {
ip.resetMinAndMax();
imp.updateAndDraw();
}
invertedLut = imp.isInvertedLut();
minThreshold = ip.getMinThreshold();
maxThreshold = ip.getMaxThreshold();
ImageStatistics stats = plot.setHistogram(imp, false);
if (minThreshold==ImageProcessor.NO_THRESHOLD)
autoSetLevels(ip, stats);
else {
minThreshold = scaleDown(ip, minThreshold);
maxThreshold = scaleDown(ip, maxThreshold);
}
scaleUpAndSet(ip, minThreshold, maxThreshold);
updateLabels(imp, ip);
updatePlot();
updateScrollBars();
imp.updateAndDraw();
}
previousImageID = id;
previousImageType = type;
return ip;
}
void autoSetLevels(ImageProcessor ip, ImageStatistics stats) {
if (stats==null || stats.histogram==null) {
minThreshold = defaultMinThreshold;
maxThreshold = defaultMaxThreshold;
return;
}
int threshold = ip.getAutoThreshold(stats.histogram);
//IJ.log(threshold+" "+stats.min+" "+stats.max+" "+stats.dmode);
int count1=0, count2=0;
for (int i=0; i<256; i++) {
if (i<threshold)
count1 += stats.histogram[i];
else
count2 += stats.histogram[i];
}
boolean unbalanced = (double)count1/count2>1.25 || (double)count2/count1>1.25;
//IJ.log(unbalanced+" "+count1+" "+count2);
double lower, upper;
if (unbalanced) {
if ((stats.max-stats.dmode)>(stats.dmode-stats.min))
{minThreshold=threshold; maxThreshold=255.0;}
else
{minThreshold=0.0; maxThreshold=threshold;}
} else {
if (ip.isInvertedLut())
{minThreshold=threshold; maxThreshold=255.0;}
else
{minThreshold=0.0; maxThreshold=threshold;}
}
if (Recorder.record)
Recorder.record("setAutoThreshold");
}
/** Scales threshold levels in the range 0-255 to the actual levels. */
void scaleUpAndSet(ImageProcessor ip, double minThreshold, double maxThreshold) {
if (!(ip instanceof ByteProcessor) && minThreshold!=ImageProcessor.NO_THRESHOLD) {
double min = ip.getMin();
double max = ip.getMax();
if (max>min) {
minThreshold = min + (minThreshold/255.0)*(max-min);
maxThreshold = min + (maxThreshold/255.0)*(max-min);
} else
minThreshold = maxThreshold = min;
}
ip.setThreshold(minThreshold, maxThreshold, lutColor);
}
/** Scales a threshold level to the range 0-255. */
double scaleDown(ImageProcessor ip, double threshold) {
if (ip instanceof ByteProcessor)
return threshold;
double min = ip.getMin();
double max = ip.getMax();
if (max>min)
return ((threshold-min)/(max-min))*255.0;
else
return ImageProcessor.NO_THRESHOLD;
}
/** Scales a threshold level in the range 0-255 to the actual level. */
double scaleUp(ImageProcessor ip, double threshold) {
double min = ip.getMin();
double max = ip.getMax();
if (max>min)
return min + (threshold/255.0)*(max-min);
else
return ImageProcessor.NO_THRESHOLD;
}
void updatePlot() {
plot.minThreshold = minThreshold;
plot.maxThreshold = maxThreshold;
plot.mode = mode;
plot.repaint();
}
void updateLabels(ImagePlus imp, ImageProcessor ip) {
double min = ip.getMinThreshold();
double max = ip.getMaxThreshold();
if (min==ImageProcessor.NO_THRESHOLD) {
label1.setText("");
label2.setText("");
} else {
Calibration cal = imp.getCalibration();
if (cal.calibrated()) {
min = cal.getCValue((int)min);
max = cal.getCValue((int)max);
}
if (((int)min==min && (int)max==max) || (ip instanceof ShortProcessor)) {
label1.setText(""+(int)min);
label2.setText(""+(int)max);
} else {
label1.setText(""+IJ.d2s(min,2));
label2.setText(""+IJ.d2s(max,2));
}
}
}
void updateScrollBars() {
minSlider.setValue((int)minThreshold);
maxSlider.setValue((int)maxThreshold);
}
/** Restore image outside non-rectangular roi. */
void doMasking(ImagePlus imp, ImageProcessor ip) {
ImageProcessor mask = imp.getMask();
if (mask!=null)
ip.reset(mask);
}
void adjustMinThreshold(ImagePlus imp, ImageProcessor ip, double value) {
if (IJ.altKeyDown() || IJ.shiftKeyDown() ) {
double width = maxThreshold-minThreshold;
if (width<1.0) width = 1.0;
minThreshold = value;
maxThreshold = minThreshold+width;
if ((minThreshold+width)>255) {
minThreshold = 255-width;
maxThreshold = minThreshold+width;
minSlider.setValue((int)minThreshold);
}
maxSlider.setValue((int)maxThreshold);
scaleUpAndSet(ip, minThreshold, maxThreshold);
return;
}
minThreshold = value;
if (maxThreshold<minThreshold) {
maxThreshold = minThreshold;
maxSlider.setValue((int)maxThreshold);
}
scaleUpAndSet(ip, minThreshold, maxThreshold);
}
void adjustMaxThreshold(ImagePlus imp, ImageProcessor ip, int cvalue) {
maxThreshold = cvalue;
if (minThreshold>maxThreshold) {
minThreshold = maxThreshold;
minSlider.setValue((int)minThreshold);
}
scaleUpAndSet(ip, minThreshold, maxThreshold);
IJ.setKeyUp(KeyEvent.VK_ALT);
IJ.setKeyUp(KeyEvent.VK_SHIFT);
}
void reset(ImagePlus imp, ImageProcessor ip) {
boolean useStackMinAndMax = false;
if (!(ip instanceof ByteProcessor)) {
ip.resetMinAndMax();
useStackMinAndMax = imp.getStackSize()>1 && IJ.altKeyDown();
}
ip.resetThreshold();
plot.setHistogram(imp, useStackMinAndMax);
updateScrollBars();
if (Recorder.record)
Recorder.record("resetThreshold");
}
void doSet(ImagePlus imp, ImageProcessor ip) {
double level1 = ip.getMinThreshold();
double level2 = ip.getMaxThreshold();
if (level1==ImageProcessor.NO_THRESHOLD) {
level1 = scaleUp(ip, defaultMinThreshold);
level2 = scaleUp(ip, defaultMaxThreshold);
}
Calibration cal = imp.getCalibration();
int digits = (ip instanceof FloatProcessor)||cal.calibrated()?2:0;
level1 = cal.getCValue(level1);
level2 = cal.getCValue(level2);
GenericDialog gd = new GenericDialog("Set Threshold Levels");
gd.addNumericField("Lower Threshold Level: ", level1, digits);
gd.addNumericField("Upper Threshold Level: ", level2, digits);
gd.showDialog();
if (gd.wasCanceled())
return;
level1 = gd.getNextNumber();
level2 = gd.getNextNumber();
level1 = cal.getRawValue(level1);
level2 = cal.getRawValue(level2);
if (level2<level1)
level2 = level1;
double minDisplay = ip.getMin();
double maxDisplay = ip.getMax();
ip.resetMinAndMax();
double minValue = ip.getMin();
double maxValue = ip.getMax();
if (level1<minValue) level1 = minValue;
if (level2>maxValue) level2 = maxValue;
boolean outOfRange = level1<minDisplay || level2>maxDisplay;
if (outOfRange)
plot.setHistogram(imp, false);
else
ip.setMinAndMax(minDisplay, maxDisplay);
minThreshold = scaleDown(ip,level1);
maxThreshold = scaleDown(ip,level2);
scaleUpAndSet(ip, minThreshold, maxThreshold);
updateScrollBars();
if (Recorder.record) {
if (imp.getBitDepth()==32)
Recorder.record("setThreshold", ip.getMinThreshold(), ip.getMaxThreshold());
else {
int min = (int)ip.getMinThreshold();
int max = (int)ip.getMaxThreshold();
if (cal.isSigned16Bit()) {
min = (int)cal.getCValue(level1);
max = (int)cal.getCValue(level2);
}
Recorder.record("setThreshold", min, max);
}
}
}
void changeState(ImagePlus imp, ImageProcessor ip) {
scaleUpAndSet(ip, minThreshold, maxThreshold);
updateScrollBars();
}
void autoThreshold(ImagePlus imp, ImageProcessor ip) {
ip.resetThreshold();
previousImageID = 0;
setup(imp);
}
void apply(ImagePlus imp) {
try {
if (imp.getBitDepth()==32) {
GenericDialog gd = new GenericDialog("NaN Backround");
gd.addCheckbox("Set Background Pixels to NaN", backgroundToNaN);
gd.showDialog();
if (gd.wasCanceled()) {
runThresholdCommand();
return;
}
backgroundToNaN = gd.getNextBoolean();
if (backgroundToNaN)
IJ.run("NaN Background");
else
runThresholdCommand();
} else
runThresholdCommand();
} catch (Exception e)
{/* do nothing */}
//close();
}
void runThresholdCommand() {
Recorder.recordInMacros = true;
IJ.run("Convert to Mask");
Recorder.recordInMacros = false;
}
static final int RESET=0, AUTO=1, HIST=2, APPLY=3, STATE_CHANGE=4, MIN_THRESHOLD=5, MAX_THRESHOLD=6, SET=7;
// Separate thread that does the potentially time-consuming processing
public void run() {
while (!done) {
synchronized(this) {
try {wait();}
catch(InterruptedException e) {}
}
doUpdate();
}
}
void doUpdate() {
ImagePlus imp;
ImageProcessor ip;
int action;
int min = minValue;
int max = maxValue;
if (doReset) action = RESET;
else if (doAutoAdjust) action = AUTO;
else if (doApplyLut) action = APPLY;
else if (doStateChange) action = STATE_CHANGE;
else if (doSet) action = SET;
else if (minValue>=0) action = MIN_THRESHOLD;
else if (maxValue>=0) action = MAX_THRESHOLD;
else return;
minValue = -1;
maxValue = -1;
doReset = false;
doAutoAdjust = false;
doApplyLut = false;
doStateChange = false;
doSet = false;
imp = WindowManager.getCurrentImage();
if (imp==null) {
IJ.beep();
IJ.showStatus("No image");
return;
}
ip = setup(imp);
if (ip==null) {
imp.unlock();
IJ.beep();
IJ.showStatus("RGB images cannot be thresholded");
return;
}
//IJ.write("setup: "+(imp==null?"null":imp.getTitle()));
switch (action) {
case RESET: reset(imp, ip); break;
case AUTO: autoThreshold(imp, ip); break;
case APPLY: apply(imp); break;
case STATE_CHANGE: changeState(imp, ip); break;
case SET: doSet(imp, ip); break;
case MIN_THRESHOLD: adjustMinThreshold(imp, ip, min); break;
case MAX_THRESHOLD: adjustMaxThreshold(imp, ip, max); break;
}
updatePlot();
updateLabels(imp, ip);
ip.setLutAnimation(true);
imp.updateAndDraw();
}
public void windowClosing(WindowEvent e) {
close();
Prefs.saveLocation(LOC_KEY, getLocation());
}
/** Overrides close() in PlugInFrame. */
public void close() {
super.close();
instance = null;
done = true;
synchronized(this) {
notify();
}
}
public void windowActivated(WindowEvent e) {
super.windowActivated(e);
ImagePlus imp = WindowManager.getCurrentImage();
if (imp!=null) {
if (!firstActivation) {
previousImageID = 0;
setup(imp);
}
firstActivation = false;
}
}
} // ThresholdAdjuster class
class ThresholdPlot extends Canvas implements Measurements, MouseListener {
static final int WIDTH = 256, HEIGHT=48;
double minThreshold = 85;
double maxThreshold = 170;
int[] histogram;
Color[] hColors;
int hmax;
Image os;
Graphics osg;
int mode;
double stackMin, stackMax;
public ThresholdPlot() {
addMouseListener(this);
setSize(WIDTH+1, HEIGHT+1);
}
/** Overrides Component getPreferredSize(). Added to work
around a bug in Java 1.4.1 on Mac OS X.*/
public Dimension getPreferredSize() {
return new Dimension(WIDTH+1, HEIGHT+1);
}
ImageStatistics setHistogram(ImagePlus imp, boolean useStackMinAndMax) {
ImageProcessor ip = imp.getProcessor();
ImageStatistics stats = null;
if (!(ip instanceof ByteProcessor)) {
if (useStackMinAndMax) {
stats = new StackStatistics(imp);
if (imp.getLocalCalibration().isSigned16Bit())
{stats.min += 32768; stats.max += 32768;}
stackMin = stats.min;
stackMax = stats.max;
ip.setMinAndMax(stackMin, stackMax);
} else
stackMin = stackMax = 0.0;
Calibration cal = imp.getCalibration();
if (ip instanceof FloatProcessor) {
int digits = Math.max(Analyzer.getPrecision(), 2);
IJ.showStatus("min="+IJ.d2s(ip.getMin(),digits)+", max="+IJ.d2s(ip.getMax(),digits));
} else
IJ.showStatus("min="+(int)cal.getCValue(ip.getMin())+", max="+(int)cal.getCValue(ip.getMax()));
ip = ip.convertToByte(true);
ip.setColorModel(ip.getDefaultColorModel());
}
ip.setRoi(imp.getRoi());
if (stats==null)
stats = ImageStatistics.getStatistics(ip, AREA+MIN_MAX+MODE, null);
int maxCount2 = 0;
histogram = stats.histogram;
for (int i = 0; i < stats.nBins; i++)
if ((histogram[i] > maxCount2) && (i != stats.mode))
maxCount2 = histogram[i];
hmax = stats.maxCount;
if ((hmax>(maxCount2 * 2)) && (maxCount2 != 0)) {
hmax = (int)(maxCount2 * 1.5);
histogram[stats.mode] = hmax;
}
os = null;
ColorModel cm = ip.getColorModel();
if (!(cm instanceof IndexColorModel))
return null;
IndexColorModel icm = (IndexColorModel)cm;
int mapSize = icm.getMapSize();
if (mapSize!=256)
return null;
byte[] r = new byte[256];
byte[] g = new byte[256];
byte[] b = new byte[256];
icm.getReds(r);
icm.getGreens(g);
icm.getBlues(b);
hColors = new Color[256];
for (int i=0; i<256; i++)
hColors[i] = new Color(r[i]&255, g[i]&255, b[i]&255);
return stats;
}
public void update(Graphics g) {
paint(g);
}
public void paint(Graphics g) {
if (g==null) return;
if (histogram!=null) {
if (os==null && hmax>0) {
os = createImage(WIDTH,HEIGHT);
osg = os.getGraphics();
osg.setColor(Color.white);
osg.fillRect(0, 0, WIDTH, HEIGHT);
osg.setColor(Color.gray);
for (int i = 0; i < WIDTH; i++) {
if (hColors!=null) osg.setColor(hColors[i]);
osg.drawLine(i, HEIGHT, i, HEIGHT - ((int)(HEIGHT * histogram[i])/hmax));
}
osg.dispose();
}
if (os==null) return;
g.drawImage(os, 0, 0, this);
} else {
g.setColor(Color.white);
g.fillRect(0, 0, WIDTH, HEIGHT);
}
g.setColor(Color.black);
g.drawRect(0, 0, WIDTH, HEIGHT);
if (mode==ThresholdAdjuster.RED)
g.setColor(Color.red);
else if (mode==ThresholdAdjuster.OVER_UNDER) {
g.setColor(Color.blue);
g.drawRect(1, 1, (int)minThreshold-2, HEIGHT);
g.drawRect(1, 0, (int)minThreshold-2, 0);
g.setColor(Color.green);
g.drawRect((int)maxThreshold+1, 1, WIDTH-(int)maxThreshold, HEIGHT);
g.drawRect((int)maxThreshold+1, 0, WIDTH-(int)maxThreshold, 0);
return;
}
g.drawRect((int)minThreshold, 1, (int)(maxThreshold-minThreshold), HEIGHT);
g.drawLine((int)minThreshold, 0, (int)maxThreshold, 0);
}
public void mousePressed(MouseEvent e) {}
public void mouseReleased(MouseEvent e) {}
public void mouseExited(MouseEvent e) {}
public void mouseClicked(MouseEvent e) {}
public void mouseEntered(MouseEvent e) {}
} // ThresholdPlot class
|
ThresholdAdjuster |
|