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.*; /** 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; int previousSlice; 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; imax) { max = count; mode = i; } } double avg = sum/256.0; if (IJ.debugMode) IJ.log("ratio="+IJ.d2s(max/avg,2)+", max= "+max+" , avg="+IJ.d2s(avg,2)+", mode="+mode); if (max/avg>1.5) { if ((stats.max-mode)>(mode-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 (maxThresholdmaxThreshold) { 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 (level2maxValue) level2 = maxValue; IJ.wait(500); ip.setThreshold(level1, level2, lutColor); setup(imp); //boolean outOfRange = level1maxDisplay; //if (outOfRange) // plot.setHistogram(imp, false); //else // ip.setMinAndMax(minDisplay, maxDisplay); 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(); if (imp.isComposite()) IJ.showStatus("\"Composite\" mode images cannot be thresholded"); else 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; int originalModeCount; 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()); } Roi roi = imp.getRoi(); if (roi!=null && !roi.isArea()) roi = null; ip.setRoi(roi); if (stats==null) stats = ImageStatistics.getStatistics(ip, AREA+MIN_MAX+MODE, null); int maxCount2 = 0; histogram = stats.histogram; originalModeCount = histogram[stats.mode]; 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