package cz.cuni.lf1.lge.ThunderSTORM.results; import java.awt.*; import java.awt.image.*; import java.awt.event.*; import java.io.*; import java.awt.datatransfer.*; import ij.*; import ij.gui.ImageCanvas; import ij.gui.ImageWindow; import ij.gui.NewImage; import ij.gui.TrimmedButton; import ij.process.*; import ij.measure.*; import ij.plugin.filter.Analyzer; import ij.text.TextWindow; import static cz.cuni.lf1.lge.ThunderSTORM.util.MathProxy.min; import static cz.cuni.lf1.lge.ThunderSTORM.util.MathProxy.max; /** * This class is an extended ImageWindow that displays histograms. */ public final class IJHistogramWindow extends ImageWindow implements Measurements, ActionListener, ClipboardOwner, MouseListener, MouseMotionListener, ImageListener, KeyListener, Runnable { static final int WIN_WIDTH = 300; static final int WIN_HEIGHT = 240; static final int HIST_WIDTH = 256; static final int HIST_HEIGHT = 128; static final int BAR_HEIGHT = 12; static final int XMARGIN = 20; static final int YMARGIN = 10; static final int INTENSITY = 0, RED = 1, GREEN = 2, BLUE = 3; protected ImageStatistics stats; protected int[] histogram; protected LookUpTable lut; protected Rectangle frame = null; protected Button list, save, copy, log, live, rgb, roiFilter; protected Label value, count; protected static String defaultDirectory = null; protected int decimalPlaces; protected int digits; protected long newMaxCount; protected int plotScale = 1; protected boolean logScale; protected Calibration cal; protected int yMax; public static int nBins = 256; private int srcImageID; // ID of source image private ImagePlus srcImp; // source image for live histograms private Thread bgThread; // thread background drawing private boolean doUpdate; // tells background thread to update private int channel; // RGB channel private String blankLabel; private String paramName; /** * Displays a histogram using the title "Histogram of ImageName". */ public IJHistogramWindow(ImagePlus imp) { super(NewImage.createRGBImage("Histogram of " + imp.getShortTitle(), WIN_WIDTH, WIN_HEIGHT, 1, NewImage.FILL_WHITE)); showHistogram(imp, 256, 0.0, 0.0); } /** * Displays a histogram using the specified title and number of bins. * Currently, the number of bins must be 256 expect for 32 bit images. */ public IJHistogramWindow(String title, ImagePlus imp, int bins) { super(NewImage.createRGBImage(title, WIN_WIDTH, WIN_HEIGHT, 1, NewImage.FILL_WHITE)); showHistogram(imp, bins, 0.0, 0.0); } /** * Displays a histogram using the specified title, number of bins and * histogram range. Currently, the number of bins must be 256 and the * histogram range range must be the same as the image range expect for 32 * bit images. */ public IJHistogramWindow(String title, ImagePlus imp, int bins, double histMin, double histMax) { super(NewImage.createRGBImage(title, WIN_WIDTH, WIN_HEIGHT, 1, NewImage.FILL_WHITE)); showHistogram(imp, bins, histMin, histMax); } /** * Displays a histogram using the specified title, number of bins, histogram * range and yMax. */ public IJHistogramWindow(String title, ImagePlus imp, int bins, double histMin, double histMax, int yMax) { super(NewImage.createRGBImage(title, WIN_WIDTH, WIN_HEIGHT, 1, NewImage.FILL_WHITE)); this.yMax = yMax; showHistogram(imp, bins, histMin, histMax); } /** * Displays a histogram using the specified title and ImageStatistics. */ public IJHistogramWindow(String title, ImagePlus imp, ImageStatistics stats) { super(NewImage.createRGBImage(title, WIN_WIDTH, WIN_HEIGHT, 1, NewImage.FILL_WHITE)); //IJ.log("HistogramWindow: "+stats.histMin+" "+stats.histMax+" "+stats.nBins); this.yMax = stats.histYMax; showHistogram(imp, stats); } /** * Displays a histogram using the specified parameter name, title and ImageStatistics. */ public IJHistogramWindow(String paramName, String title, ImagePlus imp, ImageStatistics stats) { super(NewImage.createRGBImage(title, WIN_WIDTH, WIN_HEIGHT, 1, NewImage.FILL_WHITE)); //IJ.log("HistogramWindow: "+stats.histMin+" "+stats.histMax+" "+stats.nBins); this.paramName = paramName; this.yMax = stats.histYMax; showHistogram(imp, stats); } /** * Draws the histogram using the specified title and number of bins. * Currently, the number of bins must be 256 expect for 32 bit images. */ public void showHistogram(ImagePlus imp, int bins) { showHistogram(imp, bins, 0.0, 0.0); } /** * Draws the histogram using the specified title, number of bins and * histogram range. Currently, the number of bins must be 256 and the * histogram range range must be the same as the image range expect for 32 * bit images. */ public void showHistogram(ImagePlus imp, int bins, double histMin, double histMax) { boolean limitToThreshold = (Analyzer.getMeasurements() & LIMIT) != 0; if(channel != INTENSITY && imp.getType() == ImagePlus.COLOR_RGB) { ColorProcessor cp = (ColorProcessor) imp.getProcessor(); ImageProcessor ip = cp.toFloat(channel, null); ImagePlus imp2 = new ImagePlus("", ip); imp2.setRoi(imp.getRoi()); stats = imp2.getStatistics(AREA + MEAN + MODE + MIN_MAX, bins, histMin, histMax); } else { stats = imp.getStatistics(AREA + MEAN + MODE + MIN_MAX + (limitToThreshold ? LIMIT : 0), bins, histMin, histMax); } showHistogram(imp, stats); } /** * Draws the histogram using the specified title and ImageStatistics. */ public void showHistogram(ImagePlus imp, ImageStatistics stats) { if(list == null) { setup(imp); } this.stats = stats; cal = imp.getCalibration(); boolean limitToThreshold = (Analyzer.getMeasurements() & LIMIT) != 0; imp.getMask(); histogram = stats.histogram; if(limitToThreshold && histogram.length == 256) { ImageProcessor ip = imp.getProcessor(); if(ip.getMinThreshold() != ImageProcessor.NO_THRESHOLD) { int lower = scaleDown(ip, ip.getMinThreshold()); int upper = scaleDown(ip, ip.getMaxThreshold()); for(int i = 0; i < lower; i++) { histogram[i] = 0; } for(int i = upper + 1; i < 256; i++) { histogram[i] = 0; } } } lut = imp.createLut(); int type = imp.getType(); boolean fixedRange = type == ImagePlus.GRAY8 || type == ImagePlus.COLOR_256 || type == ImagePlus.COLOR_RGB; ImageProcessor ip = this.imp.getProcessor(); ip.setColor(Color.white); ip.resetRoi(); ip.fill(); ImageProcessor srcIP = imp.getProcessor(); drawHistogram(imp, ip, fixedRange, stats.histMin, stats.histMax); this.imp.updateAndDraw(); } private void setup(ImagePlus imp) { boolean isRGB = imp.getType() == ImagePlus.COLOR_RGB; Panel buttons = new Panel(); int hgap = IJ.isMacOSX() || isRGB ? 1 : 5; buttons.setLayout(new FlowLayout(FlowLayout.RIGHT, hgap, 0)); int trim = IJ.isMacOSX() ? 6 : 0; list = new TrimmedButton("List", trim); list.addActionListener(this); buttons.add(list); //copy = new TrimmedButton("Copy", trim); //copy.addActionListener(this); //buttons.add(copy); log = new TrimmedButton("Log", trim); log.addActionListener(this); buttons.add(log); //live = new TrimmedButton("Live", trim); //live.addActionListener(this); //buttons.add(live); /*if(imp != null && isRGB) { rgb = new TrimmedButton("RGB", trim); rgb.addActionListener(this); buttons.add(rgb); }*/ roiFilter = new TrimmedButton("Apply ROI to filter", trim); roiFilter.addActionListener(this); buttons.add(roiFilter); add(buttons); if(!(IJ.isMacOSX() && isRGB)) { Panel valueAndCount = new Panel(); valueAndCount.setLayout(new GridLayout(2, 1, 0, 0)); blankLabel = IJ.isMacOSX() ? " " : " "; value = new Label(blankLabel); Font font = new Font("Monospaced", Font.PLAIN, 12); value.setFont(font); valueAndCount.add(value); count = new Label(blankLabel); count.setFont(font); valueAndCount.add(count); buttons.add(valueAndCount); } pack(); } public void setup() { setup(null); } @Override public void mouseMoved(int x, int y) { if(value == null || count == null) { return; } if((frame != null) && x >= frame.x && x <= (frame.x + frame.width)) { x = x - frame.x; if(x > 255) { x = 255; } int index = (int) (x * ((double) histogram.length) / HIST_WIDTH); String vlabel = null, clabel = null; if(blankLabel.length() == 11) // OS X { vlabel = " "; clabel = " "; } else { vlabel = " value="; clabel = " count="; } String v = vlabel + d2s(cal.getCValue(stats.histMin + index * stats.binSize)) + blankLabel; String c = clabel + histogram[index] + blankLabel; int len = vlabel.length() + blankLabel.length(); value.setText(v.substring(0, len)); count.setText(c.substring(0, len)); } else { value.setText(blankLabel); count.setText(blankLabel); } } protected void drawHistogram(ImageProcessor ip, boolean fixedRange) { drawHistogram(null, ip, fixedRange, 0.0, 0.0); } void drawHistogram(ImagePlus imp, ImageProcessor ip, boolean fixedRange, double xMin, double xMax) { int x, y; long maxCount2 = 0; int mode2 = 0; int saveModalCount; ip.setColor(Color.black); ip.setLineWidth(1); decimalPlaces = Analyzer.getPrecision(); digits = cal.calibrated() || stats.binSize != 1.0 ? decimalPlaces : 0; saveModalCount = histogram[stats.mode]; for(int i = 0; i < histogram.length; i++) { if((histogram[i] > maxCount2) && (i != stats.mode)) { maxCount2 = histogram[i]; mode2 = i; } } newMaxCount = histogram[stats.mode]; if((newMaxCount > (maxCount2 * 2)) && (maxCount2 != 0)) { newMaxCount = (int) (maxCount2 * 1.5); //histogram[stats.mode] = newMaxCount; } if(logScale || IJ.shiftKeyDown() && !liveMode()) { drawLogPlot(yMax > 0 ? yMax : newMaxCount, ip); } drawPlot(yMax > 0 ? yMax : newMaxCount, ip); histogram[stats.mode] = saveModalCount; x = XMARGIN + 1; y = YMARGIN + HIST_HEIGHT + 2; if(imp == null) { lut.drawUnscaledColorBar(ip, x - 1, y, 256, BAR_HEIGHT); } else { drawAlignedColorBar(imp, xMin, xMax, ip, x - 1, y, 256, BAR_HEIGHT); } y += BAR_HEIGHT + 15; drawText(ip, x, y, fixedRange); srcImageID = imp.getID(); } void drawAlignedColorBar(ImagePlus imp, double xMin, double xMax, ImageProcessor ip, int x, int y, int width, int height) { ImageProcessor ipSource = imp.getProcessor(); float[] pixels = null; ImageProcessor ipRamp = null; if(ipSource instanceof ColorProcessor) { ipRamp = new FloatProcessor(width, height); if(channel == RED) { ipRamp.setColorModel(LUT.createLutFromColor(Color.red)); } else if(channel == GREEN) { ipRamp.setColorModel(LUT.createLutFromColor(Color.green)); } else if(channel == BLUE) { ipRamp.setColorModel(LUT.createLutFromColor(Color.blue)); } pixels = (float[]) ipRamp.getPixels(); } else { pixels = new float[width * height]; } for(int j = 0; j < height; j++) { for(int i = 0; i < width; i++) { pixels[i + width * j] = (float) (xMin + i * (xMax - xMin) / (width - 1)); } } if(!(ipSource instanceof ColorProcessor)) { ColorModel cm = null; if(imp.isComposite()) { cm = ((CompositeImage) imp).getChannelLut(); } else if(ipSource.getMinThreshold() == ImageProcessor.NO_THRESHOLD) { cm = ipSource.getColorModel(); } else { cm = ipSource.getCurrentColorModel(); } ipRamp = new FloatProcessor(width, height, pixels, cm); } double min = ipSource.getMin(); double max = ipSource.getMax(); ipRamp.setMinAndMax(min, max); ImageProcessor bar = null; if(ip instanceof ColorProcessor) { bar = ipRamp.convertToRGB(); } else { bar = ipRamp.convertToByte(true); } ip.insert(bar, x, y); ip.setColor(Color.black); ip.drawRect(x - 1, y, width + 2, height); } /** * Scales a threshold level to the range 0-255. */ int scaleDown(ImageProcessor ip, double threshold) { double min = ip.getMin(); double max = ip.getMax(); if(max > min) { return (int) (((threshold - min) / (max - min)) * 255.0); } else { return 0; } } void drawPlot(long maxCount, ImageProcessor ip) { if(maxCount == 0) { maxCount = 1; } frame = new Rectangle(XMARGIN, YMARGIN, HIST_WIDTH, HIST_HEIGHT); ip.drawRect(frame.x - 1, frame.y, frame.width + 2, frame.height + 1); int index, y; for(int i = 0; i < HIST_WIDTH; i++) { index = (int) (i * (double) histogram.length / HIST_WIDTH); y = (int) (((double) HIST_HEIGHT * (double) histogram[index]) / maxCount); if(y > HIST_HEIGHT) { y = HIST_HEIGHT; } ip.drawLine(i + XMARGIN, YMARGIN + HIST_HEIGHT, i + XMARGIN, YMARGIN + HIST_HEIGHT - y); } } void drawLogPlot(long maxCount, ImageProcessor ip) { frame = new Rectangle(XMARGIN, YMARGIN, HIST_WIDTH, HIST_HEIGHT); ip.drawRect(frame.x - 1, frame.y, frame.width + 2, frame.height + 1); double max = Math.log(maxCount); ip.setColor(Color.gray); int index, y; for(int i = 0; i < HIST_WIDTH; i++) { index = (int) (i * (double) histogram.length / HIST_WIDTH); y = histogram[index] == 0 ? 0 : (int) (HIST_HEIGHT * Math.log(histogram[index]) / max); if(y > HIST_HEIGHT) { y = HIST_HEIGHT; } ip.drawLine(i + XMARGIN, YMARGIN + HIST_HEIGHT, i + XMARGIN, YMARGIN + HIST_HEIGHT - y); } ip.setColor(Color.black); } void drawText(ImageProcessor ip, int x, int y, boolean fixedRange) { ip.setFont(new Font("SansSerif", Font.PLAIN, 12)); ip.setAntialiasedText(true); double hmin = cal.getCValue(stats.histMin); double hmax = cal.getCValue(stats.histMax); double range = hmax - hmin; if(fixedRange && !cal.calibrated() && hmin == 0 && hmax == 255) { range = 256; } ip.drawString(d2s(hmin), x - 4, y); ip.drawString(d2s(hmax), x + HIST_WIDTH - getWidth(hmax, ip) + 10, y); double binWidth = range / stats.nBins; binWidth = Math.abs(binWidth); boolean showBins = binWidth != 1.0 || !fixedRange; int col1 = XMARGIN + 5; int col2 = XMARGIN + HIST_WIDTH / 2; int row1 = y + 25; if(showBins) { row1 -= 8; } int row2 = row1 + 15; int row3 = row2 + 15; int row4 = row3 + 15; long count = stats.longPixelCount > 0 ? stats.longPixelCount : stats.pixelCount; String modeCount = " (" + stats.maxCount + ")"; if(modeCount.length() > 12) { modeCount = ""; } ip.drawString("Count: " + count, col1, row1); ip.drawString("Mean: " + d2s(stats.mean), col1, row2); ip.drawString("StdDev: " + d2s(stats.stdDev), col1, row3); ip.drawString("Mode: " + d2s(stats.dmode) + modeCount, col2, row3); ip.drawString("Min: " + d2s(stats.min), col2, row1); ip.drawString("Max: " + d2s(stats.max), col2, row2); if(showBins) { ip.drawString("Bins: " + d2s(stats.nBins), col1, row4); ip.drawString("Bin Width: " + d2s(binWidth), col2, row4); } } /* String d2s(double d) { if (d==Double.MAX_VALUE||d==-Double.MAX_VALUE) return "0"; else if (Double.isNaN(d)) return("NaN"); else if (Double.isInfinite(d)) return("Infinity"); else if ((int)d==d) return ResultsTable.d2s(d,0); else return ResultsTable.d2s(d,decimalPlaces); } */ private String d2s(double d) { if((int) d == d) { return IJ.d2s(d, 0); } else { return IJ.d2s(d, 8); } } int getWidth(double d, ImageProcessor ip) { return ip.getStringWidth(d2s(d)); } protected void showList() { StringBuilder sb = new StringBuilder(); String vheading = stats.binSize == 1.0 ? "value" : "bin start"; if(cal.calibrated() && !cal.isSigned16Bit()) { for(int i = 0; i < stats.nBins; i++) { sb.append(i).append("\t").append(ResultsTable.d2s(cal.getCValue(stats.histMin + i * stats.binSize), digits)).append("\t").append(histogram[i]).append("\n"); } TextWindow tw = new TextWindow(getTitle(), "level\t" + vheading + "\tcount", sb.toString(), 200, 400); } else { for(int i = 0; i < stats.nBins; i++) { sb.append(ResultsTable.d2s(cal.getCValue(stats.histMin + i * stats.binSize), digits)).append("\t").append(histogram[i]).append("\n"); } TextWindow tw = new TextWindow(getTitle(), vheading + "\tcount", sb.toString(), 200, 400); } } protected void copyToClipboard() { Clipboard systemClipboard = null; try { systemClipboard = getToolkit().getSystemClipboard(); } catch(Exception e) { systemClipboard = null; } if(systemClipboard == null) { IJ.error("Unable to copy to Clipboard."); return; } IJ.showStatus("Copying histogram values..."); CharArrayWriter aw = new CharArrayWriter(stats.nBins * 4); PrintWriter pw = new PrintWriter(aw); for(int i = 0; i < stats.nBins; i++) { pw.print(ResultsTable.d2s(cal.getCValue(stats.histMin + i * stats.binSize), digits) + "\t" + histogram[i] + "\n"); } String text = aw.toString(); pw.close(); StringSelection contents = new StringSelection(text); systemClipboard.setContents(contents, this); IJ.showStatus(text.length() + " characters copied to Clipboard"); } void replot() { ImageProcessor ip = this.imp.getProcessor(); frame = new Rectangle(XMARGIN, YMARGIN, HIST_WIDTH, HIST_HEIGHT); ip.setColor(Color.white); ip.setRoi(frame.x - 1, frame.y, frame.width + 2, frame.height); ip.fill(); ip.resetRoi(); ip.setColor(Color.black); if(logScale) { drawLogPlot(yMax > 0 ? yMax : newMaxCount, ip); drawPlot(yMax > 0 ? yMax : newMaxCount, ip); } else { drawPlot(yMax > 0 ? yMax : newMaxCount, ip); } this.imp.updateAndDraw(); } /* void rescale() { Graphics g = img.getGraphics(); plotScale *= 2; if ((newMaxCount/plotScale)<50) { plotScale = 1; frame = new Rectangle(XMARGIN, YMARGIN, HIST_WIDTH, HIST_HEIGHT); g.setColor(Color.white); g.fillRect(frame.x, frame.y, frame.width, frame.height); g.setColor(Color.black); } drawPlot(newMaxCount/plotScale, g); //ImageProcessor ip = new ColorProcessor(img); //this.imp.setProcessor(null, ip); this.imp.setImage(img); } */ @Override public void actionPerformed(ActionEvent e) { Object b = e.getSource(); if(b == live) { toggleLiveMode(); } else if(b == rgb) { changeChannel(); } else if(b == list) { showList(); } else if(b == copy) { copyToClipboard(); } else if(b == log) { logScale = !logScale; replot(); } else if(b == roiFilter) { applyRoiAsFilter(); } } @Override public void lostOwnership(Clipboard clipboard, Transferable contents) { // } public int[] getHistogram() { int[] hist = new int[histogram.length]; for(int i = 0; i < histogram.length; i++) { hist[i] = (int) histogram[i]; } return hist; } public double[] getXValues() { double[] values = new double[stats.nBins]; for(int i = 0; i < stats.nBins; i++) { values[i] = cal.getCValue(stats.histMin + i * stats.binSize); } return values; } private void toggleLiveMode() { if(liveMode()) { removeListeners(); } else { enableLiveMode(); } } private void changeChannel() { ImagePlus improc = WindowManager.getImage(srcImageID); if(improc == null || improc.getType() != ImagePlus.COLOR_RGB) { channel = INTENSITY; } else { channel++; if(channel > BLUE) { channel = INTENSITY; } showHistogram(improc, 256); String name = this.imp.getTitle(); if(name.startsWith("Red ")) { name = name.substring(4); } else if(name.startsWith("Green ")) { name = name.substring(6); } else if(name.startsWith("Blue ")) { name = name.substring(5); } switch(channel) { case INTENSITY: this.imp.setTitle(name); break; case RED: this.imp.setTitle("Red " + name); break; case GREEN: this.imp.setTitle("Green " + name); break; case BLUE: this.imp.setTitle("Blue " + name); break; } } } private boolean liveMode() { return live != null && live.getForeground() == Color.red; } private void enableLiveMode() { if(bgThread == null) { srcImp = WindowManager.getImage(srcImageID); if(srcImp == null) { return; } bgThread = new Thread(this, "Live Histogram"); bgThread.setPriority(Math.max(bgThread.getPriority() - 3, Thread.MIN_PRIORITY)); bgThread.start(); imageUpdated(srcImp); } createListeners(); if(srcImp != null) { imageUpdated(srcImp); } } // these listeners are activated if there are in the source ImagePlus @Override public synchronized void mousePressed(MouseEvent e) { doUpdate = true; notify(); } @Override public synchronized void mouseDragged(MouseEvent e) { doUpdate = true; notify(); } @Override public synchronized void mouseClicked(MouseEvent e) { doUpdate = true; notify(); } @Override public synchronized void keyPressed(KeyEvent e) { ImagePlus improc = WindowManager.getImage(srcImageID); if(improc == null || improc.getRoi() != null) { doUpdate = true; notify(); } } // unused listeners @Override public void mouseReleased(MouseEvent e) { // } @Override public void mouseExited(MouseEvent e) { // } @Override public void mouseEntered(MouseEvent e) { // } @Override public void mouseMoved(MouseEvent e) { // } @Override public void imageOpened(ImagePlus imp) { // } @Override public void keyTyped(KeyEvent e) { // } @Override public void keyReleased(KeyEvent e) { // } // This listener is called if the source image content is changed @Override public synchronized void imageUpdated(ImagePlus imp) { if(imp == srcImp) { doUpdate = true; notify(); } } // If either the source image or this image are closed, exit @Override public void imageClosed(ImagePlus imp) { if(imp == srcImp || imp == this.imp) { if(bgThread != null) { bgThread.interrupt(); } bgThread = null; removeListeners(); srcImp = null; } } // the background thread for live plotting. @Override public void run() { while(true) { if(doUpdate && srcImp != null) { if(srcImp.getRoi() != null) { IJ.wait(50); //delay to make sure the roi has been updated } if(srcImp != null) { if(srcImp.getBitDepth() == 16 && ImagePlus.getDefault16bitRange() != 0) { showHistogram(srcImp, 256, 0, Math.pow(2, ImagePlus.getDefault16bitRange()) - 1); } else { showHistogram(srcImp, 256); } } } synchronized(this) { if(doUpdate) { doUpdate = false; //and loop again } else { try { wait(); } //notify wakes up the thread catch(InterruptedException e) { //interrupted tells the thread to exit return; } } } } } private void createListeners() { //IJ.log("createListeners"); if(srcImp == null) { return; } ImageCanvas canvas = srcImp.getCanvas(); if(canvas == null) { return; } canvas.addMouseListener(this); canvas.addMouseMotionListener(this); canvas.addKeyListener(this); ImagePlus.addImageListener(this); Font font = live.getFont(); live.setFont(new Font(font.getName(), Font.BOLD, font.getSize())); live.setForeground(Color.red); } private void removeListeners() { //IJ.log("removeListeners"); if(srcImp == null) { return; } ImageCanvas canvas = srcImp.getCanvas(); canvas.removeMouseListener(this); canvas.removeMouseMotionListener(this); canvas.removeKeyListener(this); ImagePlus.removeImageListener(this); Font font = live.getFont(); live.setFont(new Font(font.getName(), Font.PLAIN, font.getSize())); live.setForeground(Color.black); } private void applyRoiAsFilter() { Rectangle roi = this.imp.getProcessor().getRoi(); int left = max(frame.x, roi.x) - frame.x; int right = min((frame.x + frame.width - 1), (roi.x + roi.width - 1)) - frame.x; int indexLeft = (int) (left * ((double) histogram.length) / HIST_WIDTH); int indexRight = (int) (right * ((double) histogram.length) / HIST_WIDTH); double leftVal = cal.getCValue(stats.histMin + indexLeft * stats.binSize); // param > val double rightVal = cal.getCValue(stats.histMin + indexRight * stats.binSize); // param < val IJResultsTable.getResultsTable().tableWindow.getFilter().addNewFilter(paramName, leftVal, rightVal); } }