All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ij.plugin.frame.ThresholdAdjuster Maven / Gradle / Ivy

Go to download

ImageJ is an open source Java image processing program inspired by NIH Image for the Macintosh.

There is a newer version: 1.54p
Show newest version
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.util.Tools;
import ij.plugin.frame.Recorder;
import ij.plugin.filter.*;
import ij.plugin.ChannelSplitter;
import ij.plugin.Thresholder;

/** 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 PlugInDialog implements PlugIn, Measurements, Runnable,
	ActionListener, AdjustmentListener, ItemListener, FocusListener, KeyListener, MouseWheelListener, ImageListener {

	public static final String LOC_KEY = "threshold.loc";
	public static final String MODE_KEY = "threshold.mode";
	public static final String DARK_BACKGROUND = "threshold.dark";
	public static final String NO_RESET = "threshold.no-reset";
	public static final String RAW_VALUES = "threshold.raw";
	public static final String SIXTEEN_BIT = "threshold.16-bit";
	static final int RED=0, BLACK_AND_WHITE=1, OVER_UNDER=2;
	static final String[] modes = {"Red","B&W", "Over/Under"};
	static final double defaultMinThreshold = 0;//85;
	static final double defaultMaxThreshold = 255;//170;
	static final int DEFAULT = 0;
	static boolean fill1 = true;
	static boolean fill2 = true;
	static boolean useBW = true;
	static boolean backgroundToNaN = true;
	static ThresholdAdjuster instance;
	static int mode = RED;
	static String[] methodNames = AutoThresholder.getMethods();
	static String method = methodNames[DEFAULT];
	static AutoThresholder thresholder = new AutoThresholder();
	ThresholdPlot plot = new ThresholdPlot();
	Thread thread;  //background thread calculating and applying the threshold

	int minValue = -1;  // min slider, 0-255
	int maxValue = -1;
	int sliderRange = 256;
	boolean doAutoAdjust,doReset,doApplyLut,doStateChange,doSet; //actions required from user interface

	Panel panel;
	Button autoB, resetB, applyB, setB;
	int previousImageID;
	int previousImageType;
	int previousRoiHashCode;
	double previousMin, previousMax;
	int previousSlice;
	boolean imageWasUpdated;
	ImageJ ij;
	double minThreshold, maxThreshold;  // 0-255
	Scrollbar minSlider, maxSlider;
	TextField minLabel, maxLabel;           // for current threshold
	Label percentiles;
	boolean done;
	int lutColor;
	Choice methodChoice, modeChoice;
	Checkbox darkBackgroundCheckbox, stackCheckbox, noResetCheckbox, rawValues, sixteenBitCheckbox;
	boolean firstActivation = true;
	boolean setButtonPressed;
	boolean noReset = true;
	boolean sixteenBit = false;
	boolean enterPressed;
	boolean windowActivated;

	public ThresholdAdjuster() {
		super("Threshold");
		ImagePlus cimp = WindowManager.getCurrentImage();
		if (cimp!=null && cimp.getBitDepth()==24) {
			IJ.error("Threshold Adjuster",
				"Image>Adjust>Threshold only works with grayscale images.\n \n"
				+"You can:\n"
				+"   Convert to grayscale: Image>Type>8-bit\n"
				+"   Convert to RGB stack: Image>Type>RGB Stack\n"
				+"   Convert to HSB stack: Image>Type>HSB Stack\n"
				+"   Convert to 3 grayscale images: Image>Color>Split Channels\n"
				+"   Do color thresholding: Image>Adjust>Color Threshold\n");
			return;
		}
		if (instance!=null) {
			instance.firstActivation = true;
			instance.toFront();
			instance.setup(cimp, true);
			instance.updateScrollBars();
			return;
		}

		WindowManager.addWindow(this);
		instance = this;
		mode = (int)Prefs.get(MODE_KEY, RED);
		if (modeOVER_UNDER) mode = RED;
		setLutColor(mode);
		IJ.register(PasteController.class);

		ij = IJ.getInstance();
		Font font = IJ.font10;
		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); //top left bottom right
		add(plot, c);
		plot.addKeyListener(ij);

		// percentiles
		c.gridx = 0;
		c.gridy = y++;
		c.insets = new Insets(1, 10, 0, 10);
		percentiles = new Label("");
		percentiles.setFont(font);
		add(percentiles, c);

		// minThreshold slider
		minSlider = new Scrollbar(Scrollbar.HORIZONTAL, sliderRange/3, 1, 0, sliderRange);
		GUI.fixScrollbar(minSlider);
		c.gridx = 0;
		c.gridy = y++;
		c.gridwidth = 1;
		c.weightx = IJ.isMacintosh()?90:100;
		c.fill = GridBagConstraints.HORIZONTAL;
		c.insets = new Insets(1, 10, 0, 0);
		add(minSlider, c);
		minSlider.addAdjustmentListener(this);
		minSlider.addMouseWheelListener(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);
		String text = "000000";
		int columns = 4;
		minLabel = new TextField(text,columns);
		minLabel.setFont(font);
		add(minLabel, c);
		minLabel.addFocusListener(this);
		minLabel.addMouseWheelListener(this);
		minLabel.addKeyListener(this);

		// maxThreshold slider
		maxSlider = new Scrollbar(Scrollbar.HORIZONTAL, sliderRange*2/3, 1, 0, sliderRange);
		GUI.fixScrollbar(maxSlider);
		c.gridx = 0;
		c.gridy = y++;
		c.gridwidth = 1;
		c.weightx = 100;
		c.insets = new Insets(2, 10, 0, 0);
		add(maxSlider, c);
		maxSlider.addAdjustmentListener(this);
		maxSlider.addMouseWheelListener(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(2, 0, 0, 10);
		maxLabel = new TextField(text,columns);
		maxLabel.setFont(font);
		add(maxLabel, c);
		maxLabel.addFocusListener(this);
		maxLabel.addMouseWheelListener(this);
		maxLabel.addKeyListener(this);

		// choices
		panel = new Panel();
		methodChoice = new Choice();
		for (int i=0; istats2.min || ip.getMax()255)
			minThreshold = 255;
		*/
	}

	/** Whether the (auto)-thresholded pixels should be those 
	 * with high values, i.e., the background should be at low values.
	 * (E.g. dark background and non-inverting LUT)
	*/
	boolean thresholdHigh(ImageProcessor ip) {
		boolean darkb = darkBackgroundCheckbox!=null && darkBackgroundCheckbox.getState();
		boolean invertedLut = ip.isInvertedLut();
		return invertedLut ? !darkb : darkb;
	}

	/** Scales threshold levels in the range 0-255 to the actual levels. */
	void scaleUpAndSet(ImageProcessor ip, double lower, double upper) {
		ip.scaleAndSetThreshold(lower, upper, 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) {
			double scaledThr = ((threshold-min)/(max-min))*255.0;
			if (scaledThr < 0.0) scaledThr = 0.0;
			if (scaledThr > 255.0) scaledThr = 255.0;
			return scaledThr;
		} 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(ImageProcessor ip) {
		int min = (int)Math.round(minThreshold);
		if (min<0) min=0;
 		if (min>255) min=255;
 		if (ip.getMinThreshold()==ImageProcessor.NO_THRESHOLD)
			min = -1;
 		int max = (int)Math.round(maxThreshold);
 		if (max<0) max=0;
 		if (max>255) max=255;
 		plot.setThreshold(min,max);
		plot.mode = mode;
		plot.repaint();
	}

	void updatePercentiles(ImagePlus imp, ImageProcessor ip) {
		if (percentiles==null)
			return;
		ImageStatistics stats = plot.stats;
		int minThresholdInt = (int)Math.round(minThreshold);
		if (minThresholdInt<0) minThresholdInt=0;
		if (minThresholdInt>255) minThresholdInt=255;
		int maxThresholdInt = (int)Math.round(maxThreshold);
		if (maxThresholdInt<0) maxThresholdInt=0;
		if (maxThresholdInt>255) maxThresholdInt=255;
		if (stats!=null && stats.histogram!=null && stats.histogram.length==256
		&& ip.getMinThreshold()!=ImageProcessor.NO_THRESHOLD) {
			int[] histogram = stats.histogram;
			int below = 0, inside = 0, above = 0;
			int minValue=0, maxValue=255;
			if (imp.getBitDepth()==16 && !entireStack(imp)) {   //16-bit histogram for better accuracy
				ip.setRoi(imp.getRoi());
				histogram = ip.getHistogram();
				minThresholdInt = (int)Math.round(ip.getMinThreshold());
				if (minThresholdInt<0) minThresholdInt=0;
				maxThresholdInt = (int)Math.round(ip.getMaxThreshold());
				if (maxThresholdInt>65535) maxThresholdInt=65535;
				minValue=0; maxValue=histogram.length-1;
			}
			for (int i=minValue; i"+maxThresholdInt+":"+above+" sum="+total);
			if (mode==OVER_UNDER)
				percentiles.setText("below: "+IJ.d2s(100.*below/total)+" %,  above: "+IJ.d2s(100.*above/total)+" %");
			else
				percentiles.setText(IJ.d2s(100.*inside/total)+" %");
		} else
			percentiles.setText("");
	}

	void updateLabels(ImagePlus imp, ImageProcessor ip) {
		if (minLabel==null || maxLabel==null || enterPressed)
			return;
		double min = ip.getMinThreshold();
		double max = ip.getMaxThreshold();
		if (min==ImageProcessor.NO_THRESHOLD) {
			minLabel.setText("");
			maxLabel.setText("");
		} else {
			Calibration cal = imp.getCalibration();
			boolean calibrated = cal.calibrated() && !rawValues.getState();
			if (calibrated) {
				min = cal.getCValue((int)min);
				max = cal.getCValue((int)max);
			}
			if ((((int)min==min && (int)max==max && Math.abs(min)<1e6 && Math.abs(max)<1e6)) ||
					(ip instanceof ShortProcessor && (cal.isSigned16Bit() || !calibrated))) {
				minLabel.setText(ResultsTable.d2s(min,0));
				maxLabel.setText(ResultsTable.d2s(max,0));
			} else {
				minLabel.setText(min==-1e30 ? "-1e30" : d2s(min));
				maxLabel.setText(max== 1e30 ? "1e30" : d2s(max));
			}
		}
	}

	/** Converts a number to a String, such that it should not take much space (for the minLabel, maxLabel TextFields) */
	String d2s(double x) {
		int digits = 2;
		if (Math.abs(x)<100) digits=3;
		if (Math.abs(x)<10) digits=4;
		if (x<0 && digits==4) digits=3;
		return Math.abs(x)>=1e6 ? IJ.d2s(x,-2) : ResultsTable.d2s(x,digits);  //the latter uses exp notation also for small x
	}

	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);
		}
		if (minThreshold < 0) {     //remove NO_THRESHOLD
			minThreshold = 0;
			minSlider.setValue((int)minThreshold);
		}
		scaleUpAndSet(ip, minThreshold, maxThreshold);
		IJ.setKeyUp(KeyEvent.VK_ALT);
		IJ.setKeyUp(KeyEvent.VK_SHIFT);
	}

	void reset(ImagePlus imp, ImageProcessor ip) {
		//IJ.log("reset1: "+noReset+" "+sixteenBitChanged+" "+mode);
		ip.resetThreshold();
		if (!noReset)
			resetMinAndMax(ip);
		ImageStatistics stats = plot.setHistogram(imp, entireStack(imp),rawValues.getState());
		if (ip.getBitDepth()!=8 && entireStack(imp))
			ip.setMinAndMax(stats.min, stats.max);
		updateScrollBars();
		if (IJ.recording()) {
			if (Recorder.scriptMode())
				Recorder.recordCall("IJ.resetThreshold(imp);");
			else
				Recorder.record("resetThreshold");
		}
	}

	/** Numeric input via 'Set' dialog or minLabel, maxLabel TextFields */
	void doSet(ImagePlus imp, ImageProcessor ip) {
		double level1 = ip.getMinThreshold();
		double level2 = ip.getMaxThreshold();
		Calibration cal = imp.getCalibration();
		if (level1==ImageProcessor.NO_THRESHOLD) {
			level1 = scaleUp(ip, defaultMinThreshold);
			level2 = scaleUp(ip, defaultMaxThreshold);
		}
		boolean calibrated = cal.calibrated() && !rawValues.getState();
		if (calibrated) {
			level1 = cal.getCValue(level1);
			level2 = cal.getCValue(level2);
		}
		if (setButtonPressed) {
				int digits = (ip instanceof FloatProcessor)||(calibrated && !cal.isSigned16Bit()) ? Math.max(Analyzer.getPrecision(), 4) : 0;
			GenericDialog gd = new GenericDialog("Set Threshold Levels");
			gd.addNumericField("Lower threshold level: ", level1, Math.abs(level1)<1e7 ? digits : -4, 10, null);
			gd.addNumericField("Upper threshold level: ", level2, Math.abs(level2)<1e7 ? digits : -4, 10, null);
			gd.showDialog();
			if (gd.wasCanceled()) {
				setButtonPressed = false;
				return;
			}
			level1 = gd.getNextNumber();
			level2 = gd.getNextNumber();
			setButtonPressed = false;
		} else {
			level1 = Tools.parseDouble(minLabel.getText(), level1);
			level2 = Tools.parseDouble(maxLabel.getText(), level2);
		}
		enterPressed = false;
		if (calibrated) {
			level1 = cal.getRawValue(level1);
			level2 = cal.getRawValue(level2);
		}
		if (level2maxValue) level2 = maxValue;
		}
		IJ.wait(500);
		ip.setThreshold(level1, level2, lutColor);
		ip.setSnapshotPixels(null); // disable undo
		previousImageID = 0;
		setup(imp, false);
		updateScrollBars();
		if (IJ.recording()) {
			if (imp.getBitDepth()==32) {
				if (Recorder.scriptMode())
					Recorder.recordCall("IJ.setThreshold(imp, "+IJ.d2s(ip.getMinThreshold(),4)+", "+IJ.d2s(ip.getMaxThreshold(),4)+");");
				else
					Recorder.record("setThreshold", ip.getMinThreshold(), ip.getMaxThreshold());
			} else {
				int min = (int)ip.getMinThreshold();
				int max = (int)ip.getMaxThreshold();
				if (cal.isSigned16Bit() && calibrated) {
					min = (int)cal.getCValue(level1);
					max = (int)cal.getCValue(level2);
					if (Recorder.scriptMode())
						Recorder.recordCall("IJ.setThreshold(imp, "+min+", "+max+");");
					else
						Recorder.record("setThreshold", min, max);
				} else {
					if (Recorder.scriptMode())
						Recorder.recordCall("IJ.setRawThreshold(imp, "+min+", "+max+");");
					else
						Recorder.record("setThreshold", min, max, "raw");
				}
			}
		}
	}

	void changeState(ImagePlus imp, ImageProcessor ip) {
		scaleUpAndSet(ip, minThreshold, maxThreshold);
		updateScrollBars();
	}

	void autoThreshold(ImagePlus imp, ImageProcessor ip) {
		ip.resetThreshold();
		previousImageID = 0;
		setup(imp, true);
		updateScrollBars();
 	}

 	void apply(ImagePlus imp) {
 		if (imp.getProcessor().getMinThreshold()==ImageProcessor.NO_THRESHOLD) {
 			IJ.error("Thresholder", "Threshold is not set");
 			return;
 		}
 		try {
 			if (imp.getBitDepth()==32) {
				YesNoCancelDialog d = new YesNoCancelDialog(null, "Thresholder",
					"Convert to 8-bit mask or set background pixels to NaN?",  "Convert to Mask", "Set to NaN");
				if (d.cancelPressed())
					return;
				else if (!d.yesPressed()) {
					Recorder.recordInMacros = true;
					IJ.run("NaN Background");
					Recorder.recordInMacros = false;
					return;
				}
			}
			runThresholdCommand();
 		} catch (Exception e) {}
 	}

 	void runThresholdCommand() {
		Thresholder.setMethod(method);
		Thresholder.setBackground(darkBackgroundCheckbox.getState()?"Dark":"Light");
		if (IJ.recording()) {
			Recorder.setCommand("Convert to Mask");
			(new Thresholder()).run("mask");
			Recorder.saveCommand();
		} else
			(new Thresholder()).run("mask");
	}

	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) {
				if (!doAutoAdjust && !doReset && !doApplyLut && !doStateChange && !doSet && minValue<0 &&  maxValue<0) {
					try {wait();}
					catch(InterruptedException e) {}
				}
			}
			doUpdate();
		}
	}

	/** Triggered by the user interface, with the corresponding boolean, e.g., 'doAutoAdjust' */
	void doUpdate() {
		ImagePlus imp;
		ImageProcessor ip;
		int action;
		int min = minValue;
		int max = maxValue;
		if (doReset)            { action = RESET;        doReset = false; }
		else if (doAutoAdjust)  { action = AUTO;         doAutoAdjust = false; }
		else if (doApplyLut)    { action = APPLY;        doApplyLut = false; }
		else if (doStateChange) { action = STATE_CHANGE; doStateChange = false; }
		else if (doSet)         { action = SET;          doSet = false; }
		else if (minValue>=0)   { action = MIN_THRESHOLD; minValue = -1; }
		else if (maxValue>=0)   { action = MAX_THRESHOLD; maxValue = -1; }
		else return;
		imp = WindowManager.getCurrentImage();
		if (imp==null) {
			IJ.beep();
			IJ.showStatus("No image");
			return;
		}
		ip = setup(imp, false);
		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;
		}
		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(ip);
		updateLabels(imp, ip);
		updatePercentiles(imp, ip);
		ip.setLutAnimation(true);
		imp.updateAndDraw();
	}

	/** Overrides close() in PlugInFrame. */
    public void close() {
    	super.close();
		instance = null;
		done = true;
		Prefs.saveLocation(LOC_KEY, getLocation());
		Prefs.set(MODE_KEY, mode);
		Prefs.set(DARK_BACKGROUND, darkBackgroundCheckbox.getState());
		Prefs.set(NO_RESET, noResetCheckbox.getState());
		Prefs.set(SIXTEEN_BIT, sixteenBitCheckbox.getState());
		Prefs.set(RAW_VALUES, rawValues.getState());
		synchronized(this) {
			notify();
		}
	}

    public void windowActivated(WindowEvent e) {
    	super.windowActivated(e);
    	plot.requestFocus();
		ImagePlus imp = WindowManager.getCurrentImage();
		if (!firstActivation && imp!=null) {
			windowActivated = true;
			setup(imp, false);
			updateScrollBars();
		}
	}

	// Returns a hashcode for the specified ROI that typically changes
	// if it is moved,  even though is still the same object.
	int roiHashCode(Roi roi) {
		return roi!=null?roi.getHashCode():0;
	}

	/** Notifies the ThresholdAdjuster that the image has changed.
	 *  If the image has no threshold, it does not autothreshold the image.
	*/
	public static void update() {
		if (instance!=null) {
			ThresholdAdjuster ta = ((ThresholdAdjuster)instance);
			ImagePlus imp = WindowManager.getCurrentImage();
			if (imp!=null && ta.previousImageID==imp.getID()) {
				if ((imp.getCurrentSlice()!=ta.previousSlice) && ta.entireStack(imp))
					return;
				ta.previousImageID = 0;
				ta.setup(imp, false);
				ta.updateScrollBars();
			}
		}
	}
	
	public static boolean isDarkBackground() {
		return instance!=null?instance.darkBackgroundCheckbox.getState():false;
	}


	/** Returns the current thresholding method ("Default", "Huang", etc). */
	public static String getMethod() {
		return method;
	}

	/** Sets the thresholding method ("Default", "Huang", etc). */
	public static void setMethod(String thresholdingMethod) {
		if (thresholdingMethod==null)
			return;
		boolean valid = false;
		for (int i=0; i0)
				method = method.substring(0,index);
			if (instance!=null)
				instance.methodChoice.select(method);
		}
	}

	/** Returns the current mode ("Red","B&W" or"Over/Under"). */
	public static String getMode() {
		return modes[mode];
	}

	/** Sets the current mode ("Red","B&W" or"Over/Under"). */
	public static void setMode(String tmode) {
		if (instance!=null) synchronized (instance) {
			ThresholdAdjuster ta = ((ThresholdAdjuster)instance);
			if (modes[0].equals(tmode))
				mode = 0;
			else if (modes[1].equals(tmode))
				mode = 1;
			else if (modes[2].equals(tmode))
				mode = 2;
			else
				return;
			ta.setLutColor(mode);
			ta.doStateChange = true;
			ta.modeChoice.select(mode);
			ta.notify();
		}
	}

} // ThresholdAdjuster class


class ThresholdPlot extends Canvas implements Measurements, MouseListener {
	double scale = Prefs.getGuiScale();
	int width = (int)Math.round(256*scale);
	int height= (int)Math.round(48*scale);
	int lowerThreshold = -1;
 	int upperThreshold = (int)Math.round(170*scale);

	ImageStatistics stats;
	int[] histogram;
	Color[] hColors;
	int hmax;               // maximum of histogram to display
	Image os;
	Graphics osg;
	int mode;
	int originalModeCount;
	double stackMin, stackMax;
	int imageID2;           // ImageID of previous call
	boolean entireStack2;   // 'entireStack' of previous call
	double mean2;

	public ThresholdPlot() {
		addMouseListener(this);
		setSize(width+2, height+2);
	}

    /** 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+2, height+2);
    }

	ImageStatistics setHistogram(ImagePlus imp, boolean entireStack, boolean rawValues) {
		if (IJ.debugMode) IJ.log("ThresholdAdjuster:setHistogram: "+entireStack+" "+entireStack2);
		double mean = entireStack?imp.getProcessor().getStats().mean:0.0;
		if (entireStack && stats!=null && imp.getID()==imageID2
		&& entireStack==entireStack2 && mean==mean2)
			return stats;
		mean2 = mean;
		ImageProcessor ip = imp.getProcessor();
		ColorModel cm = ip.getColorModel();
		stats = null;
		if (entireStack) {
			if (imp.isHyperStack()) {
				ImageStack stack = ChannelSplitter.getChannel(imp, imp.getChannel());
				stats = new StackStatistics(new ImagePlus("", stack));
			} else
				stats = new StackStatistics(imp);
		}
		if (!(ip instanceof ByteProcessor)) {
			if (entireStack) {
				if (imp.getLocalCalibration().isSigned16Bit())
					{stats.min += 32768; stats.max += 32768;}
				stackMin = stats.min;
				stackMax = stats.max;
				ip.setMinAndMax(stackMin, stackMax);
				imp.updateAndDraw();
			} else {
				stackMin = stackMax = 0.0;
				if (entireStack2) {
					ip.resetMinAndMax();
					imp.updateAndDraw();
				}
			}
			Calibration cal = imp.getCalibration();
			boolean calibrated = cal.calibrated() && !rawValues;
			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 {
				int digits = calibrated && !cal.isSigned16Bit() ? 2 : 0;
				double cmin = calibrated?cal.getCValue(ip.getMin()):ip.getMin();
				double cmax = calibrated?cal.getCValue(ip.getMax()):ip.getMax();
				IJ.showStatus("min="+IJ.d2s(cal.getCValue(cmin), digits)+", max="+IJ.d2s(cal.getCValue(cmax), digits));
			}
			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 = ip.getStats();
		if (IJ.debugMode) IJ.log("  stats: "+stats);
		int maxCount2 = 0;  // number of pixels in 2nd-highest bin, used for y scale if mode is too high
		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 * 1.5)) && (maxCount2 != 0))
			hmax = (int)(maxCount2 * 1.2);
		os = null;

		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];
		final int brightnessLimit = 1800; // 0 ... 2550 scale; brightness is reduced above
		for (int i=0; i<256; i++) {     //avoid colors that are too bright (invisible)
			int sum = 4*(r[i]&255) + 5*(g[i]&255) + (b[i]&255);
			if (sum > brightnessLimit) {
				r[i] = (byte)(((r[i]&255)*brightnessLimit*2)/(sum+brightnessLimit));
				g[i] = (byte)(((g[i]&255)*brightnessLimit*2)/(sum+brightnessLimit));
				b[i] = (byte)(((b[i]&255)*brightnessLimit*2)/(sum+brightnessLimit));
			}
			hColors[i] = new Color(r[i]&255, g[i]&255, b[i]&255);
		}
		imageID2 = imp.getID();
		entireStack2 = entireStack;
		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();
				if (scale>1)
					((Graphics2D)osg).setStroke(new BasicStroke((float)scale));
				osg.setColor(Color.white);
				osg.fillRect(0, 0, width, height);
				osg.setColor(Color.gray);
				double scale2 = width/256.0;
				int barWidth = 1;
				if (scale>1) barWidth=2;
				if (scale>2) barWidth=3;
				for (int i = 0; i < 256; i++) {
					if (hColors!=null) osg.setColor(hColors[i]);
					int x =(int)(i*scale2);
					for (int j = 0; j




© 2015 - 2025 Weber Informatics LLC | Privacy Policy