Squiz Matrix  4.12.2
 All Data Structures Namespaces Functions Variables Pages
ThresholdAdjuster.java
1 package ij.plugin.frame;
2 import java.awt.*;
3 import java.awt.event.*;
4 import java.awt.image.*;
5 import ij.*;
6 import ij.plugin.*;
7 import ij.process.*;
8 import ij.gui.*;
9 import ij.measure.*;
10 import ij.plugin.frame.Recorder;
11 
14 public class ThresholdAdjuster extends PlugInFrame implements PlugIn, Measurements,
15  Runnable, ActionListener, AdjustmentListener, ItemListener {
16 
17  static final int RED=0, BLACK_AND_WHITE=1, OVER_UNDER=2;
18  static final String[] modes = {"Red","Black & White", "Over/Under"};
19  static final double defaultMinThreshold = 85;
20  static final double defaultMaxThreshold = 170;
21  static boolean fill1 = true;
22  static boolean fill2 = true;
23  static boolean useBW = true;
24  static boolean backgroundToNaN = true;
25  static Frame instance;
26  static int mode = RED;
27  ThresholdPlot plot = new ThresholdPlot();
28  Thread thread;
29 
30  int minValue = -1;
31  int maxValue = -1;
32  int sliderRange = 256;
33  boolean doAutoAdjust,doReset,doApplyLut,doStateChange,doSet;
34 
35  Panel panel;
36  Button autoB, resetB, applyB, setB;
37  int previousImageID;
38  int previousImageType;
39  double previousMin, previousMax;
40  ImageJ ij;
41  double minThreshold, maxThreshold; // 0-255
42  Scrollbar minSlider, maxSlider;
43  Label label1, label2;
44  boolean done;
45  boolean invertedLut;
46  int lutColor;
47  static Choice choice;
48  boolean firstActivation;
49 
50  public ThresholdAdjuster() {
51  super("Threshold");
52  if (instance!=null) {
53  instance.toFront();
54  return;
55  }
56 
57  instance = this;
58  setLutColor(mode);
59 
60  ij = IJ.getInstance();
61  Font font = new Font("SansSerif", Font.PLAIN, 10);
62  GridBagLayout gridbag = new GridBagLayout();
63  GridBagConstraints c = new GridBagConstraints();
64  setLayout(gridbag);
65 
66  // plot
67  int y = 0;
68  c.gridx = 0;
69  c.gridy = y++;
70  c.gridwidth = 2;
71  c.fill = GridBagConstraints.BOTH;
72  c.anchor = GridBagConstraints.CENTER;
73  c.insets = new Insets(10, 10, 0, 10);
74  add(plot, c);
75 
76  // minThreshold slider
77  minSlider = new Scrollbar(Scrollbar.HORIZONTAL, sliderRange/3, 1, 0, sliderRange);
78  c.gridx = 0;
79  c.gridy = y++;
80  c.gridwidth = 1;
81  c.weightx = IJ.isMacintosh()?90:100;
82  c.fill = GridBagConstraints.HORIZONTAL;
83  c.insets = new Insets(5, 10, 0, 0);
84  add(minSlider, c);
85  minSlider.addAdjustmentListener(this);
86  minSlider.setUnitIncrement(1);
87 
88  // minThreshold slider label
89  c.gridx = 1;
90  c.gridwidth = 1;
91  c.weightx = IJ.isMacintosh()?10:0;
92  c.insets = new Insets(5, 0, 0, 10);
93  label1 = new Label(" ", Label.RIGHT);
94  label1.setFont(font);
95  add(label1, c);
96 
97  // maxThreshold slider
98  maxSlider = new Scrollbar(Scrollbar.HORIZONTAL, sliderRange*2/3, 1, 0, sliderRange);
99  c.gridx = 0;
100  c.gridy = y++;
101  c.gridwidth = 1;
102  c.weightx = 100;
103  c.insets = new Insets(0, 10, 0, 0);
104  add(maxSlider, c);
105  maxSlider.addAdjustmentListener(this);
106  maxSlider.setUnitIncrement(1);
107 
108  // maxThreshold slider label
109  c.gridx = 1;
110  c.gridwidth = 1;
111  c.weightx = 0;
112  c.insets = new Insets(0, 0, 0, 10);
113  label2 = new Label(" ", Label.RIGHT);
114  label2.setFont(font);
115  add(label2, c);
116 
117  // choice
118  choice = new Choice();
119  for (int i=0; i<modes.length; i++)
120  choice.addItem(modes[i]);
121  choice.select(mode);
122  choice.addItemListener(this);
123  c.gridx = 0;
124  c.gridy = y++;
125  c.gridwidth = 2;
126  c.insets = new Insets(5, 5, 0, 5);
127  c.anchor = GridBagConstraints.CENTER;
128  c.fill = GridBagConstraints.NONE;
129  add(choice, c);
130 
131  // buttons
132  int trim = IJ.isMacOSX()?11:0;
133  panel = new Panel();
134  autoB = new TrimmedButton("Auto",trim);
135  autoB.addActionListener(this);
136  autoB.addKeyListener(ij);
137  panel.add(autoB);
138  applyB = new TrimmedButton("Apply",trim);
139  applyB.addActionListener(this);
140  applyB.addKeyListener(ij);
141  panel.add(applyB);
142  resetB = new TrimmedButton("Reset",trim);
143  resetB.addActionListener(this);
144  resetB.addKeyListener(ij);
145  panel.add(resetB);
146  setB = new TrimmedButton("Set",trim);
147  setB.addActionListener(this);
148  setB.addKeyListener(ij);
149  panel.add(setB);
150  c.gridx = 0;
151  c.gridy = y++;
152  c.gridwidth = 2;
153  c.insets = new Insets(0, 5, 10, 5);
154  add(panel, c);
155 
156  addKeyListener(ij); // ImageJ handles keyboard shortcuts
157  pack();
158  GUI.center(this);
159  firstActivation = true;
160  show();
161 
162  thread = new Thread(this, "ThresholdAdjuster");
163  //thread.setPriority(thread.getPriority()-1);
164  thread.start();
165  ImagePlus imp = IJ.getInstance().getImagePlus();
166  if (imp!=null)
167  setup(imp);
168  }
169 
170  public synchronized void adjustmentValueChanged(AdjustmentEvent e) {
171  if (e.getSource()==minSlider)
172  minValue = minSlider.getValue();
173  else
174  maxValue = maxSlider.getValue();
175  notify();
176  }
177 
178  public synchronized void actionPerformed(ActionEvent e) {
179  Button b = (Button)e.getSource();
180  if (b==null) return;
181  if (b==resetB)
182  doReset = true;
183  else if (b==autoB)
184  doAutoAdjust = true;
185  else if (b==applyB)
186  doApplyLut = true;
187  else if (b==setB)
188  doSet = true;
189  notify();
190  }
191 
192  void setLutColor(int mode) {
193  switch (mode) {
194  case RED:
195  lutColor = ImageProcessor.RED_LUT;
196  break;
197  case BLACK_AND_WHITE:
198  lutColor = ImageProcessor.BLACK_AND_WHITE_LUT;
199  break;
200  case OVER_UNDER:
201  lutColor = ImageProcessor.OVER_UNDER_LUT;
202  break;
203  }
204  }
205 
206  public synchronized void itemStateChanged(ItemEvent e) {
207  mode = choice.getSelectedIndex();
208  setLutColor(mode);
209  doStateChange = true;
210  notify();
211  }
212 
213  ImageProcessor setup(ImagePlus imp) {
214  ImageProcessor ip;
215  int type = imp.getType();
216  if (type==ImagePlus.COLOR_RGB)
217  return null;
218  ip = imp.getProcessor();
219  boolean minMaxChange = false;
220  if (type==ImagePlus.GRAY16 || type==ImagePlus.GRAY32) {
221  if (ip.getMin()!=previousMin || ip.getMax()!=previousMax)
222  minMaxChange = true;
223  previousMin = ip.getMin();
224  previousMax = ip.getMax();
225  }
226  int id = imp.getID();
227  if (minMaxChange || id!=previousImageID || type!=previousImageType) {
228  invertedLut = imp.isInvertedLut();
229  minThreshold = ip.getMinThreshold();
230  maxThreshold = ip.getMaxThreshold();
231  ImageStatistics stats = plot.setHistogram(imp);
232  if (minThreshold==ip.NO_THRESHOLD)
233  autoSetLevels(ip, stats);
234  else {
235  minThreshold = scaleDown(ip, minThreshold);
236  maxThreshold = scaleDown(ip, maxThreshold);
237  }
238  scaleUpAndSet(ip, minThreshold, maxThreshold);
239  updateLabels(imp, ip);
240  updatePlot();
241  updateScrollBars();
242  imp.updateAndDraw();
243  }
244  previousImageID = id;
245  previousImageType = type;
246  return ip;
247  }
248 
249  void autoSetLevels(ImageProcessor ip, ImageStatistics stats) {
250  if (stats==null || stats.histogram==null) {
251  minThreshold = defaultMinThreshold;
252  maxThreshold = defaultMaxThreshold;
253  return;
254  }
255  int threshold = ip.getAutoThreshold(stats.histogram);
256  //IJ.log(threshold+" "+stats.min+" "+stats.max+" "+stats.dmode);
257  if ((stats.max-stats.dmode)>(stats.dmode-stats.min)) {
258  minThreshold = threshold;
259  maxThreshold = stats.max;
260  } else {
261  minThreshold = stats.min;
262  maxThreshold = threshold;
263  }
264  }
265 
267  void scaleUpAndSet(ImageProcessor ip, double minThreshold, double maxThreshold) {
268  if (!(ip instanceof ByteProcessor) && minThreshold!=ImageProcessor.NO_THRESHOLD) {
269  double min = ip.getMin();
270  double max = ip.getMax();
271  if (max>min) {
272  minThreshold = min + (minThreshold/255.0)*(max-min);
273  maxThreshold = min + (maxThreshold/255.0)*(max-min);
274  } else
275  minThreshold = ImageProcessor.NO_THRESHOLD;
276  }
277  ip.setThreshold(minThreshold, maxThreshold, lutColor);
278  }
279 
281  double scaleDown(ImageProcessor ip, double threshold) {
282  if (ip instanceof ByteProcessor)
283  return threshold;
284  double min = ip.getMin();
285  double max = ip.getMax();
286  if (max>min)
287  return ((threshold-min)/(max-min))*255.0;
288  else
289  return ImageProcessor.NO_THRESHOLD;
290  }
291 
293  double scaleUp(ImageProcessor ip, double threshold) {
294  double min = ip.getMin();
295  double max = ip.getMax();
296  if (max>min)
297  return min + (threshold/255.0)*(max-min);
298  else
299  return ImageProcessor.NO_THRESHOLD;
300  }
301 
302  void updatePlot() {
303  plot.minThreshold = minThreshold;
304  plot.maxThreshold = maxThreshold;
305  plot.mode = mode;
306  plot.repaint();
307  }
308 
309  void updateLabels(ImagePlus imp, ImageProcessor ip) {
310  double min = ip.getMinThreshold();
311  double max = ip.getMaxThreshold();
312  if (min==ImageProcessor.NO_THRESHOLD) {
313  label1.setText("");
314  label2.setText("");
315  } else {
316  Calibration cal = imp.getCalibration();
317  if (cal.calibrated()) {
318  min = cal.getCValue((int)min);
319  max = cal.getCValue((int)max);
320  }
321  if (((int)min==min && (int)max==max) || (ip instanceof ShortProcessor)) {
322  label1.setText(""+(int)min);
323  label2.setText(""+(int)max);
324  } else {
325  label1.setText(""+IJ.d2s(min,2));
326  label2.setText(""+IJ.d2s(max,2));
327  }
328  }
329  }
330 
331  void updateScrollBars() {
332  minSlider.setValue((int)minThreshold);
333  maxSlider.setValue((int)maxThreshold);
334  }
335 
337  void doMasking(ImagePlus imp, ImageProcessor ip) {
338  ImageProcessor mask = imp.getMask();
339  if (mask!=null)
340  ip.reset(mask);
341  }
342 
343  void adjustMinThreshold(ImagePlus imp, ImageProcessor ip, double value) {
344  if (IJ.altKeyDown()) {
345  double width = maxThreshold-minThreshold;
346  if (width<1.0) width = 1.0;
347  minThreshold = value;
348  maxThreshold = minThreshold+width;
349  if ((minThreshold+width)>255) {
350  minThreshold = 255-width;
351  maxThreshold = minThreshold+width;
352  minSlider.setValue((int)minThreshold);
353  }
354  maxSlider.setValue((int)maxThreshold);
355  scaleUpAndSet(ip, minThreshold, maxThreshold);
356  return;
357  }
358  minThreshold = value;
359  if (maxThreshold<minThreshold) {
360  maxThreshold = minThreshold;
361  maxSlider.setValue((int)maxThreshold);
362  }
363  scaleUpAndSet(ip, minThreshold, maxThreshold);
364  }
365 
366  void adjustMaxThreshold(ImagePlus imp, ImageProcessor ip, int cvalue) {
367  maxThreshold = cvalue;
368  if (minThreshold>maxThreshold) {
369  minThreshold = maxThreshold;
370  minSlider.setValue((int)minThreshold);
371  }
372  scaleUpAndSet(ip, minThreshold, maxThreshold);
373  }
374 
375  void reset(ImagePlus imp, ImageProcessor ip) {
376  plot.setHistogram(imp);
377  ip.resetThreshold();
378  updateScrollBars();
379  if (Recorder.record)
380  Recorder.record("resetThreshold");
381  }
382 
383  void doSet(ImagePlus imp, ImageProcessor ip) {
384  double level1 = ip.getMinThreshold();
385  double level2 = ip.getMaxThreshold();
386  if (level1==ImageProcessor.NO_THRESHOLD) {
387  level1 = scaleUp(ip, defaultMinThreshold);
388  level2 = scaleUp(ip, defaultMaxThreshold);
389  }
390  Calibration cal = imp.getCalibration();
391  int digits = (ip instanceof FloatProcessor)||cal.calibrated()?2:0;
392  level1 = cal.getCValue(level1);
393  level2 = cal.getCValue(level2);
394  GenericDialog gd = new GenericDialog("Set Threshold Levels");
395  gd.addNumericField("Lower Threshold Level: ", level1, digits);
396  gd.addNumericField("Upper Threshold Level: ", level2, digits);
397  gd.showDialog();
398  if (gd.wasCanceled())
399  return;
400  level1 = gd.getNextNumber();
401  level2 = gd.getNextNumber();
402  level1 = cal.getRawValue(level1);
403  level2 = cal.getRawValue(level2);
404  if (level2<level1)
405  level2 = level1;
406  double minDisplay = ip.getMin();
407  double maxDisplay = ip.getMax();
408  ip.resetMinAndMax();
409  double minValue = ip.getMin();
410  double maxValue = ip.getMax();
411  if (level1<minValue) level1 = minValue;
412  if (level2>maxValue) level2 = maxValue;
413  boolean outOfRange = level1<minDisplay || level2>maxDisplay;
414  if (outOfRange)
415  plot.setHistogram(imp);
416  else
417  ip.setMinAndMax(minDisplay, maxDisplay);
418 
419  minThreshold = scaleDown(ip,level1);
420  maxThreshold = scaleDown(ip,level2);
421  scaleUpAndSet(ip, minThreshold, maxThreshold);
422  updateScrollBars();
423  if (Recorder.record) {
424  if (imp.getBitDepth()==32)
425  Recorder.record("setThreshold", ip.getMinThreshold(), ip.getMaxThreshold());
426  else
427  Recorder.record("setThreshold", (int)ip.getMinThreshold(), (int)ip.getMaxThreshold());
428  }
429  }
430 
431  void changeState(ImagePlus imp, ImageProcessor ip) {
432  scaleUpAndSet(ip, minThreshold, maxThreshold);
433  updateScrollBars();
434  }
435 
436  void autoThreshold(ImagePlus imp, ImageProcessor ip) {
437  ip.resetThreshold();
438  previousImageID = 0;
439  setup(imp);
440  }
441 
442  void apply(ImagePlus imp) {
443  try {
444  if (imp.getBitDepth()==32) {
445  GenericDialog gd = new GenericDialog("NaN Backround");
446  gd.addCheckbox("Set Background Pixels to NaN", backgroundToNaN);
447  gd.showDialog();
448  if (gd.wasCanceled()) {
449  IJ.run("Threshold");
450  return;
451  }
452  backgroundToNaN = gd.getNextBoolean();
453  if (backgroundToNaN)
454  IJ.run("NaN Background");
455  else
456  IJ.run("Threshold");
457  } else
458  IJ.run("Threshold");
459  } catch (Exception e)
460  {/* do nothing */}
461  //close();
462  }
463 
464  static final int RESET=0, AUTO=1, HIST=2, APPLY=3, STATE_CHANGE=4, MIN_THRESHOLD=5, MAX_THRESHOLD=6, SET=7;
465 
466  // Separate thread that does the potentially time-consuming processing
467  public void run() {
468  while (!done) {
469  synchronized(this) {
470  try {wait();}
471  catch(InterruptedException e) {}
472  }
473  doUpdate();
474  }
475  }
476 
477  void doUpdate() {
478  ImagePlus imp;
479  ImageProcessor ip;
480  int action;
481  int min = minValue;
482  int max = maxValue;
483  if (doReset) action = RESET;
484  else if (doAutoAdjust) action = AUTO;
485  else if (doApplyLut) action = APPLY;
486  else if (doStateChange) action = STATE_CHANGE;
487  else if (doSet) action = SET;
488  else if (minValue>=0) action = MIN_THRESHOLD;
489  else if (maxValue>=0) action = MAX_THRESHOLD;
490  else return;
491  minValue = -1;
492  maxValue = -1;
493  doReset = false;
494  doAutoAdjust = false;
495  doApplyLut = false;
496  doStateChange = false;
497  doSet = false;
498  imp = IJ.getInstance().getImagePlus();
499  if (imp==null) {
500  IJ.beep();
501  IJ.showStatus("No image");
502  return;
503  }
504  ip = setup(imp);
505  if (ip==null) {
506  imp.unlock();
507  IJ.beep();
508  IJ.showStatus("RGB images cannot be thresolded");
509  return;
510  }
511  //IJ.write("setup: "+(imp==null?"null":imp.getTitle()));
512  switch (action) {
513  case RESET: reset(imp, ip); break;
514  case AUTO: autoThreshold(imp, ip); break;
515  case APPLY: apply(imp); break;
516  case STATE_CHANGE: changeState(imp, ip); break;
517  case SET: doSet(imp, ip); break;
518  case MIN_THRESHOLD: adjustMinThreshold(imp, ip, min); break;
519  case MAX_THRESHOLD: adjustMaxThreshold(imp, ip, max); break;
520  }
521  updatePlot();
522  updateLabels(imp, ip);
523  ip.setLutAnimation(true);
524  imp.updateAndDraw();
525  }
526 
527  public void windowClosing(WindowEvent e) {
528  close();
529  }
530 
532  public void close() {
533  super.close();
534  instance = null;
535  done = true;
536  synchronized(this) {
537  notify();
538  }
539  }
540 
541  public void windowActivated(WindowEvent e) {
542  super.windowActivated(e);
543  ImagePlus imp = IJ.getInstance().getImagePlus();
544  if (imp!=null) {
545  if (!firstActivation) {
546  previousImageID = 0;
547  setup(imp);
548  }
549  firstActivation = false;
550  }
551  }
552 
553 } // ThresholdAdjuster class
554 
555 
556 class ThresholdPlot extends Canvas implements Measurements, MouseListener {
557 
558  static final int WIDTH = 256, HEIGHT=48;
559  double minThreshold = 85;
560  double maxThreshold = 170;
561  int[] histogram;
562  Color[] hColors;
563  int hmax;
564  Image os;
565  Graphics osg;
566  int mode;
567 
568  public ThresholdPlot() {
569  addMouseListener(this);
570  setSize(WIDTH+1, HEIGHT+1);
571  }
572 
575  public Dimension getPreferredSize() {
576  return new Dimension(WIDTH+1, HEIGHT+1);
577  }
578 
579  ImageStatistics setHistogram(ImagePlus imp) {
580  ImageProcessor ip = imp.getProcessor();
581  if (!(ip instanceof ByteProcessor)) {
582  double min = ip.getMin();
583  double max = ip.getMax();
584  ip.setMinAndMax(min, max);
585  Rectangle r = ip.getRoi();
586  ip = new ByteProcessor(ip.createImage());
587  ip.setRoi(r);
588  }
589  ip.setMask(imp.getMask());
590  ImageStatistics stats = ImageStatistics.getStatistics(ip, AREA+MIN_MAX+MODE, null);
591  int maxCount2 = 0;
592  histogram = stats.histogram;
593  for (int i = 0; i < stats.nBins; i++)
594  if ((histogram[i] > maxCount2) && (i != stats.mode))
595  maxCount2 = histogram[i];
596  hmax = stats.maxCount;
597  if ((hmax>(maxCount2 * 2)) && (maxCount2 != 0)) {
598  hmax = (int)(maxCount2 * 1.5);
599  histogram[stats.mode] = hmax;
600  }
601  os = null;
602 
603  ColorModel cm = ip.getColorModel();
604  if (!(cm instanceof IndexColorModel))
605  return null;
606  IndexColorModel icm = (IndexColorModel)cm;
607  int mapSize = icm.getMapSize();
608  if (mapSize!=256)
609  return null;
610  byte[] r = new byte[256];
611  byte[] g = new byte[256];
612  byte[] b = new byte[256];
613  icm.getReds(r);
614  icm.getGreens(g);
615  icm.getBlues(b);
616  hColors = new Color[256];
617  for (int i=0; i<256; i++)
618  hColors[i] = new Color(r[i]&255, g[i]&255, b[i]&255);
619  return stats;
620  }
621 
622  public void update(Graphics g) {
623  paint(g);
624  }
625 
626  public void paint(Graphics g) {
627  if (histogram!=null) {
628  if (os==null && hmax>0) {
629  os = createImage(WIDTH,HEIGHT);
630  osg = os.getGraphics();
631  osg.setColor(Color.white);
632  osg.fillRect(0, 0, WIDTH, HEIGHT);
633  osg.setColor(Color.gray);
634  for (int i = 0; i < WIDTH; i++) {
635  if (hColors!=null) osg.setColor(hColors[i]);
636  osg.drawLine(i, HEIGHT, i, HEIGHT - ((int)(HEIGHT * histogram[i])/hmax));
637  }
638  osg.dispose();
639  }
640  g.drawImage(os, 0, 0, this);
641  } else {
642  g.setColor(Color.white);
643  g.fillRect(0, 0, WIDTH, HEIGHT);
644  }
645  g.setColor(Color.black);
646  g.drawRect(0, 0, WIDTH, HEIGHT);
647  if (mode==ThresholdAdjuster.RED)
648  g.setColor(Color.red);
649  else if (mode==ThresholdAdjuster.OVER_UNDER) {
650  g.setColor(Color.blue);
651  g.drawRect(1, 1, (int)minThreshold-2, HEIGHT);
652  g.drawRect(1, 0, (int)minThreshold-2, 0);
653  g.setColor(Color.green);
654  g.drawRect((int)maxThreshold+1, 1, WIDTH-(int)maxThreshold, HEIGHT);
655  g.drawRect((int)maxThreshold+1, 0, WIDTH-(int)maxThreshold, 0);
656  return;
657  }
658  g.drawRect((int)minThreshold, 1, (int)(maxThreshold-minThreshold), HEIGHT);
659  g.drawLine((int)minThreshold, 0, (int)maxThreshold, 0);
660  }
661 
662  public void mousePressed(MouseEvent e) {}
663  public void mouseReleased(MouseEvent e) {}
664  public void mouseExited(MouseEvent e) {}
665  public void mouseClicked(MouseEvent e) {}
666  public void mouseEntered(MouseEvent e) {}
667 
668 } // ThresholdPlot class