Guest User

ImageJ - MaximumFinder

a guest
Aug 2nd, 2014
306
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Java 68.12 KB | None | 0 0
  1. package ij.plugin.filter;
  2. import ij.plugin.filter.*;
  3. import ij.*;
  4. import ij.gui.*;
  5. import ij.measure.*;
  6. import ij.process.*;
  7. import ij.util.Tools;
  8. import java.awt.*;
  9. import java.util.*;
  10.  
  11. /** This ImageJ plug-in filter finds the maxima (or minima) of an image.
  12.  * It can create a mask where the local maxima of the current image are
  13.  * marked (255; unmarked pixels 0).
  14.  * The plug-in can also create watershed-segmented particles: Assume a
  15.  * landscape of inverted heights, i.e., maxima of the image are now water sinks.
  16.  * For each point in the image, the sink that the water goes to determines which
  17.  * particle it belongs to.
  18.  * When finding maxima (not minima), pixels with a level below the lower threshold
  19.  * can be left unprocessed.
  20.  *
  21.  * Except for segmentation, this plugin works with ROIs, including non-rectangular ROIs.
  22.  * Since this plug-in creates a separate output image it processes
  23.  *    only single images or slices, no stacks.
  24.  *
  25.  * Notes:
  26.  * - When using one instance of MaximumFinder for more than one image in parallel threads,
  27.  *   all must images have the same width and height.
  28.  *
  29.  * version 09-Nov-2006 Michael Schmid
  30.  * version 21-Nov-2006 Wayne Rasband. Adds "Display Point Selection" option and "Count" output type.
  31.  * version 28-May-2007 Michael Schmid. Preview added, bugfix: minima of calibrated images, uses Arrays.sort
  32.  * version 07-Aug-2007 Fixed a bug that could delete particles when doing watershed segmentation of an EDM.
  33.  * version 21-Apr-2007 Adapted for float instead of 16-bit EDM; correct progress bar on multiple calls
  34.  * version 05-May-2009 Works for images>32768 pixels in width or height
  35.  * version 01-Nov-2009 Bugfix: extra lines in segmented output eliminated; watershed is also faster now
  36.  *                     Maximum points encoded in long array for sorting instead of separete objects that need gc
  37.  *                     New output type 'List'
  38.  * version 22-May-2011 Bugfix: Maximum search in EDM and float images with large dynamic range could omit maxima
  39.  * version 13-Sep-2013 added the findMaxima() and findMinima() functions for arrays (Norbert Vischer)
  40.  * version 20-Mar-2014 Watershed segmentation of EDM with tolerance>=1.0 does not kill fine particles
  41.  */
  42.  
  43. public class MaximumFinder implements ExtendedPlugInFilter, DialogListener {
  44.     //filter params
  45.     /** maximum height difference between points that are not counted as separate maxima */
  46.     private static double tolerance = 10;
  47.     /** Output type single points */
  48.     public final static int SINGLE_POINTS=0;
  49.     /** Output type all points around the maximum within the tolerance */
  50.     public final static int IN_TOLERANCE=1;
  51.     /** Output type watershed-segmented image */
  52.     public final static int SEGMENTED=2;
  53.     /** Do not create image, only mark points */
  54.     public final static int POINT_SELECTION=3;
  55.     /** Do not create an image, just list x, y of maxima in the Results table */
  56.     public final static int LIST=4;
  57.     /** Do not create an image, just count maxima and add count to Results table */
  58.     public final static int COUNT=5;
  59.     /** what type of output to create (see constants above)*/
  60.     private static int outputType;
  61.     /** what type of output to create was chosen in the dialog (see constants above)*/
  62.     private static int dialogOutputType = POINT_SELECTION;
  63.     /** output type names */
  64.     final static String[] outputTypeNames = new String[]
  65.         {"Single Points", "Maxima Within Tolerance", "Segmented Particles", "Point Selection", "List", "Count"};
  66.     /** whether to exclude maxima at the edge of the image*/
  67.     private static boolean excludeOnEdges;
  68.     /** whether to accept maxima only in the thresholded height range*/
  69.     private static boolean useMinThreshold;
  70.     /** whether to find darkest points on light background */
  71.     private static boolean lightBackground;
  72.     private ImagePlus imp;                          // the ImagePlus of the setup call
  73.     private int flags = DOES_ALL|NO_CHANGES|NO_UNDO;// the flags (see interfaces PlugInFilter & ExtendedPlugInFilter)
  74.     private boolean   thresholded;                  // whether the input image has a threshold
  75.     private boolean   roiSaved;                     // whether the filter has changed the roi and saved the original roi
  76.     private boolean   previewing;                   // true while dialog is displayed (processing for preview)
  77.     private Vector    checkboxes;                   // a reference to the Checkboxes of the dialog
  78.     private boolean thresholdWarningShown = false;  // whether the warning "can't find minima with thresholding" has been shown
  79.     private Label     messageArea;                  // reference to the textmessage area for displaying the number of maxima
  80.     private double    progressDone;                 // for progress bar, fraction of work done so far
  81.     private int       nPasses = 0;                  // for progress bar, how many images to process (sequentially or parallel threads)
  82.     //the following are class variables for having shorter argument lists
  83.     private int       width, height;                // image dimensions
  84.     private int       intEncodeXMask;               // needed for encoding x & y in a single int (watershed): mask for x
  85.     private int       intEncodeYMask;               // needed for encoding x & y in a single int (watershed): mask for y
  86.     private int       intEncodeShift;               // needed for encoding x & y in a single int (watershed): shift of y
  87.     /** directions to 8 neighboring pixels, clockwise: 0=North (-y), 1=NE, 2=East (+x), ... 7=NW */
  88.     private int[]     dirOffset;                    // pixel offsets of neighbor pixels for direct addressing
  89.     private Polygon points;                    // maxima found by findMaxima() when outputType is POINT_SELECTION
  90.     final static int[] DIR_X_OFFSET = new int[] {  0,  1,  1,  1,  0, -1, -1, -1 };
  91.     final static int[] DIR_Y_OFFSET = new int[] { -1, -1,  0,  1,  1,  1,  0, -1 };
  92.     /** the following constants are used to set bits corresponding to pixel types */
  93.     final static byte MAXIMUM = (byte)1;            // marks local maxima (irrespective of noise tolerance)
  94.     final static byte LISTED = (byte)2;             // marks points currently in the list
  95.     final static byte PROCESSED = (byte)4;          // marks points processed previously
  96.     final static byte MAX_AREA = (byte)8;           // marks areas near a maximum, within the tolerance
  97.     final static byte EQUAL = (byte)16;             // marks contigous maximum points of equal level
  98.     final static byte MAX_POINT = (byte)32;         // marks a single point standing for a maximum
  99.     final static byte ELIMINATED = (byte)64;        // marks maxima that have been eliminated before watershed
  100.     /** type masks corresponding to the output types */
  101.     final static byte[] outputTypeMasks = new byte[] {MAX_POINT, MAX_AREA, MAX_AREA};
  102.     final static float SQRT2 = 1.4142135624f;
  103.  
  104.  
  105.     /** Method to return types supported
  106.      * @param arg   Not used by this plugin-filter
  107.      * @param imp   The image to be filtered
  108.      * @return      Code describing supported formats etc.
  109.      * (see ij.plugin.filter.PlugInFilter & ExtendedPlugInFilter)
  110.      */
  111.     public int setup(String arg, ImagePlus imp) {
  112.         this.imp = imp;
  113.         return flags;
  114.     }
  115.  
  116.     public int showDialog(ImagePlus imp, String command, PlugInFilterRunner pfr) {
  117.         ImageProcessor ip = imp.getProcessor();
  118.         ip.resetBinaryThreshold(); // remove invisible threshold set by MakeBinary and Convert to Mask
  119.         thresholded = ip.getMinThreshold()!=ImageProcessor.NO_THRESHOLD;
  120.         GenericDialog gd = new GenericDialog(command);
  121.         int digits = (ip instanceof FloatProcessor)?2:0;
  122.         String unit = (imp.getCalibration()!=null)?imp.getCalibration().getValueUnit():null;
  123.         unit = (unit==null||unit.equals("Gray Value"))?":":" ("+unit+"):";
  124.         gd.addNumericField("Noise tolerance"+unit,tolerance, digits);
  125.         gd.addChoice("Output type:", outputTypeNames, outputTypeNames[dialogOutputType]);
  126.         gd.addCheckbox("Exclude edge maxima", excludeOnEdges);
  127.         if (thresholded)
  128.             gd.addCheckbox("Above lower threshold", useMinThreshold);
  129.         gd.addCheckbox("Light background", lightBackground);
  130.         gd.addPreviewCheckbox(pfr, "Preview point selection");
  131.         gd.addMessage("    "); //space for number of maxima
  132.         messageArea = (Label)gd.getMessage();
  133.         gd.addDialogListener(this);
  134.         checkboxes = gd.getCheckboxes();
  135.         previewing = true;
  136.         gd.addHelp(IJ.URL+"/docs/menus/process.html#find-maxima");
  137.         gd.showDialog();          //input by the user (or macro) happens here
  138.         if (gd.wasCanceled())
  139.             return DONE;
  140.         previewing = false;
  141.         if (!dialogItemChanged(gd, null))   //read parameters
  142.             return DONE;
  143.         IJ.register(this.getClass());       //protect static class variables (parameters) from garbage collection
  144.         return flags;
  145.     } // boolean showDialog
  146.  
  147.     /** Read the parameters (during preview or after showing the dialog) */
  148.     public boolean dialogItemChanged(GenericDialog gd, AWTEvent e) {
  149.         tolerance = gd.getNextNumber();
  150.         if (tolerance<0) tolerance = 0;
  151.         dialogOutputType = gd.getNextChoiceIndex();
  152.         outputType = previewing ? POINT_SELECTION : dialogOutputType;
  153.         excludeOnEdges = gd.getNextBoolean();
  154.         if (thresholded)
  155.             useMinThreshold = gd.getNextBoolean();
  156.         else
  157.             useMinThreshold = false;
  158.         lightBackground = gd.getNextBoolean();
  159.         boolean invertedLut = imp.isInvertedLut();
  160.         if (useMinThreshold && ((invertedLut&&!lightBackground) || (!invertedLut&&lightBackground))) {
  161.             if (!thresholdWarningShown)
  162.                 if (!IJ.showMessageWithCancel(
  163.                     "Find Maxima",
  164.                     "\"Above Lower Threshold\" option cannot be used\n"+
  165.                     "when finding minima (image with light background\n"+
  166.                     "or image with dark background and inverting LUT).")
  167.                     && !previewing)
  168.                 return false;               // if faulty input is not detected during preview, "cancel" quits
  169.             thresholdWarningShown = true;
  170.             useMinThreshold = false;
  171.             ((Checkbox)(checkboxes.elementAt(1))).setState(false); //reset "Above Lower Threshold" checkbox
  172.         }
  173.         if (!(gd.getPreviewCheckbox()!=null&&gd.getPreviewCheckbox().getState()))
  174.             messageArea.setText("");        // no "nnn Maxima" message when not previewing
  175.         return (!gd.invalidNumber());
  176.     } // public boolean DialogItemChanged
  177.  
  178.     /** Set his to the number of images to process (for the watershed progress bar only).
  179.      *  Don't call or set nPasses to zero if no progress bar is desired. */
  180.     public void setNPasses(int nPasses) {
  181.         this.nPasses = nPasses;
  182.     }
  183.  
  184.     /** The plugin is inferred from ImageJ by this method
  185.      * @param ip The image where maxima (or minima) should be found
  186.      */
  187.     public void run(ImageProcessor ip) {
  188.         Roi roi = imp.getRoi();
  189.         if (outputType == POINT_SELECTION && !roiSaved) {
  190.             imp.saveRoi(); // save previous selection so user can restore it
  191.             roiSaved = true;
  192.         }
  193.         if (roi!=null && (!roi.isArea() || outputType==SEGMENTED)) {
  194.             imp.deleteRoi();
  195.             roi = null;
  196.         }
  197.         boolean invertedLut = imp.isInvertedLut();
  198.         double threshold = useMinThreshold?ip.getMinThreshold():ImageProcessor.NO_THRESHOLD;
  199.         if ((invertedLut&&!lightBackground) || (!invertedLut&&lightBackground)) {
  200.             threshold = ImageProcessor.NO_THRESHOLD;    //don't care about threshold when finding minima
  201.             float[] cTable = ip.getCalibrationTable();
  202.             ip = ip.duplicate();
  203.             if (cTable==null) {                 //invert image for finding minima of uncalibrated images
  204.                 ip.invert();
  205.             } else {                            //we are using getPixelValue, so the CalibrationTable must be inverted
  206.                 float[] invertedCTable = new float[cTable.length];
  207.                 for (int i=cTable.length-1; i>=0; i--)
  208.                     invertedCTable[i] = -cTable[i];
  209.                 ip.setCalibrationTable(invertedCTable);
  210.             }
  211.             ip.setRoi(roi);
  212.         }
  213.         ByteProcessor outIp = null;
  214.         outIp = findMaxima(ip, tolerance, threshold, outputType, excludeOnEdges, false); //process the image
  215.         if (outIp == null) return;              //cancelled by user or previewing or no output image
  216.         if (!Prefs.blackBackground)             //normally, output has an inverted LUT, "active" pixels black (255) - like a mask
  217.             outIp.invertLut();
  218.         String resultName;
  219.         if (outputType == SEGMENTED)            //Segmentation required
  220.             resultName = " Segmented";
  221.         else
  222.             resultName = " Maxima";
  223.         String outname = imp.getTitle();
  224.         if (imp.getNSlices()>1)
  225.             outname += "("+imp.getCurrentSlice()+")";
  226.         outname += resultName;
  227.         if (WindowManager.getImage(outname)!=null)
  228.             outname = WindowManager.getUniqueName(outname);
  229.         ImagePlus maxImp = new ImagePlus(outname, outIp);
  230.         Calibration cal = imp.getCalibration().copy();
  231.         cal.disableDensityCalibration();
  232.         maxImp.setCalibration(cal);             //keep the spatial calibration
  233.         maxImp.show();
  234.      } //public void run
  235.      
  236.  
  237.     /** Finds the image maxima and returns them as a Polygon. There
  238.      * is an example at http://imagej.nih.gov/ij/macros/js/FindMaxima.js.
  239.      * @param ip             The input image
  240.      * @param tolerance      Height tolerance: maxima are accepted only if protruding more than this value
  241.      *                       from the ridge to a higher maximum
  242.      * @param excludeOnEdges Whether to exclude edge maxima
  243.      * @return               A Polygon containing the coordinates of the maxima
  244.      */
  245.     public Polygon getMaxima(ImageProcessor ip, double tolerance, boolean excludeOnEdges) {
  246.         findMaxima(ip, tolerance, ImageProcessor.NO_THRESHOLD,
  247.             MaximumFinder.POINT_SELECTION, excludeOnEdges, false);
  248.         if (points==null)
  249.             return new Polygon();
  250.         else
  251.             return points;
  252.     }
  253.  
  254.     /**
  255.     * Calculates peak positions of 1D array N.Vischer, 13-sep-2013
  256.     *
  257.     * @param xx Array containing peaks.
  258.     * @param tolerance Depth of a qualified valley must exceed tolerance.
  259.     * Tolerance must be >= 0. Flat tops are marked at their centers.
  260.     * @param  excludeOnEdges If 'true', a peak is only
  261.     * accepted if it is separated by two qualified valleys. If 'false', a peak
  262.     * is also accepted if separated by one qualified valley and by a border.
  263.     * @return Positions of peaks, sorted with decreasing amplitude
  264.     */
  265.     public static int[] findMaxima(double[] xx, double tolerance, boolean excludeOnEdges) {
  266.         boolean includeEdge = !excludeOnEdges;
  267.         int len = xx.length;
  268.         if (len == 0)
  269.             return new int[0];
  270.         if (tolerance < 0)
  271.             tolerance = 0;
  272.         int[] maxPositions = new int[len];
  273.         double max = xx[0];
  274.         double min = xx[0];
  275.         int maxPos = 0;
  276.         int lastMaxPos = -1;
  277.         boolean leftValleyFound = includeEdge;
  278.         int maxCount = 0;
  279.         for (int jj = 1; jj < len; jj++) {
  280.             double val = xx[jj];
  281.             if (val > min + tolerance)
  282.                 leftValleyFound = true;
  283.             if (val > max && leftValleyFound) {
  284.                 max = val;
  285.                 maxPos = jj;
  286.             }
  287.             if (leftValleyFound)
  288.                 lastMaxPos = maxPos;
  289.             if (val < max - tolerance && leftValleyFound) {
  290.                 maxPositions[maxCount] = maxPos;
  291.                 maxCount++;
  292.                 leftValleyFound = false;
  293.                 min = val;
  294.                 max = val;
  295.             }
  296.             if (val < min) {
  297.                 min = val;
  298.                 if (!leftValleyFound)
  299.                     max = val;
  300.             }
  301.         }
  302.         if (includeEdge) {
  303.             if (maxCount > 0 && maxPositions[maxCount - 1] != lastMaxPos)
  304.                 maxPositions[maxCount++] = lastMaxPos;
  305.             if (maxCount == 0 && max - min >= tolerance)
  306.                 maxPositions[maxCount++] = lastMaxPos;
  307.         }
  308.         int[] cropped = new int[maxCount];
  309.         System.arraycopy(maxPositions, 0, cropped, 0, maxCount);
  310.         maxPositions = cropped;
  311.         double[] maxValues = new double[maxCount];
  312.         for (int jj = 0; jj < maxCount; jj++) {
  313.             int pos = maxPositions[jj];
  314.             double midPos = pos;
  315.             while (pos < len - 1 && xx[pos] == xx[pos + 1]) {
  316.                 midPos += 0.5;
  317.                 pos++;
  318.             }
  319.             maxPositions[jj] = (int) midPos;
  320.             maxValues[jj] = xx[maxPositions[jj]];
  321.         }
  322.         int[] rankPositions = Tools.rank(maxValues);
  323.         int[] returnArr = new int[maxCount];
  324.         for (int jj = 0; jj < maxCount; jj++) {
  325.             int pos = maxPositions[rankPositions[jj]];
  326.             returnArr[maxCount - jj - 1] = pos;//use descending order
  327.         }
  328.         return returnArr;
  329.     }
  330.    
  331.     /**
  332.     * Returns minimum positions of array xx, sorted with decreasing strength
  333.     */
  334.     public static int[] findMinima(double[] xx, double tolerance, boolean includeEdges) {
  335.         int len = xx.length;
  336.         double[] negArr = new double[len];
  337.         for (int jj = 0; jj < len; jj++)
  338.             negArr[jj] = -xx[jj];
  339.         int[] minPositions = findMaxima(negArr, tolerance, includeEdges);
  340.         return minPositions;
  341.     }
  342.    
  343.     /** Here the processing is done: Find the maxima of an image (does not find minima).
  344.      *
  345.      * LIMITATIONS:          With outputType=SEGMENTED (watershed segmentation), some segmentation lines
  346.      *                       may be improperly placed if local maxima are suppressed by the tolerance.
  347.      *
  348.      * @param ip             The input image
  349.      * @param tolerance      Height tolerance: maxima are accepted only if protruding more than this value
  350.      *                       from the ridge to a higher maximum
  351.      * @param threshold      minimum height of a maximum (uncalibrated); for no minimum height set it to
  352.      *                       ImageProcessor.NO_THRESHOLD
  353.      * @param outputType     What to mark in output image: SINGLE_POINTS, IN_TOLERANCE or SEGMENTED.
  354.      *                       No output image is created for output types POINT_SELECTION, LIST and COUNT.
  355.      * @param excludeOnEdges Whether to exclude edge maxima
  356.      * @param isEDM          Whether the image is a float Euclidian Distance Map.
  357.      * @return               A new byteProcessor with a normal (uninverted) LUT where the marked points
  358.      *                       are set to 255 (Background 0). Pixels outside of the roi of the input ip are not set.
  359.      *                       Returns null if outputType does not require an output or if cancelled by escape
  360.      */
  361.     public ByteProcessor findMaxima(ImageProcessor ip, double tolerance, double threshold,
  362.             int outputType, boolean excludeOnEdges, boolean isEDM) {
  363.         if (dirOffset == null) makeDirectionOffsets(ip);
  364.         Rectangle roi = ip.getRoi();
  365.         byte[] mask = ip.getMaskArray();
  366.         if (threshold!=ImageProcessor.NO_THRESHOLD && ip.getCalibrationTable()!=null &&
  367.                 threshold>0 && threshold<ip.getCalibrationTable().length)
  368.             threshold = ip.getCalibrationTable()[(int)threshold];   //convert threshold to calibrated
  369.         ByteProcessor typeP = new ByteProcessor(width, height);     //will be a notepad for pixel types
  370.         byte[] types = (byte[])typeP.getPixels();
  371.         float globalMin = Float.MAX_VALUE;
  372.         float globalMax = -Float.MAX_VALUE;
  373.         for (int y=roi.y; y<roi.y+roi.height; y++) {         //find local minimum/maximum now
  374.             for (int x=roi.x; x<roi.x+roi.width; x++) {      //ImageStatistics won't work if we have no ImagePlus
  375.                 float v = ip.getPixelValue(x, y);
  376.                 if (globalMin>v) globalMin = v;
  377.                 if (globalMax<v) globalMax = v;
  378.             }
  379.         }
  380.         if (threshold !=ImageProcessor.NO_THRESHOLD)
  381.             threshold -= (globalMax-globalMin)*1e-6;//avoid rounding errors
  382.         //for segmentation, exclusion of edge maxima cannot be done now but has to be done after segmentation:
  383.         boolean excludeEdgesNow = excludeOnEdges && outputType!=SEGMENTED;
  384.  
  385.         if (Thread.currentThread().isInterrupted()) return null;
  386.         IJ.showStatus("Getting sorted maxima...");
  387.         long[] maxPoints = getSortedMaxPoints(ip, typeP, excludeEdgesNow, isEDM, globalMin, globalMax, threshold);
  388.         if (Thread.currentThread().isInterrupted()) return null;
  389.         IJ.showStatus("Analyzing  maxima...");
  390.         float maxSortingError = 0;
  391.         if (ip instanceof FloatProcessor)   //sorted sequence may be inaccurate by this value
  392.             maxSortingError = 1.1f * (isEDM ? SQRT2/2f : (globalMax-globalMin)/2e9f);
  393.         analyzeAndMarkMaxima(ip, typeP, maxPoints, excludeEdgesNow, isEDM, globalMin, tolerance, outputType, maxSortingError);
  394.         //new ImagePlus("Pixel types",typeP.duplicate()).show();
  395.         if (outputType==POINT_SELECTION || outputType==LIST || outputType==COUNT)
  396.             return null;
  397.        
  398.         ByteProcessor outIp;
  399.         byte[] pixels;
  400.         if (outputType == SEGMENTED) {
  401.             // Segmentation required, convert to 8bit (also for 8-bit images, since the calibration
  402.             // may have a negative slope). outIp has background 0, maximum areas 255
  403.             outIp = make8bit(ip, typeP, isEDM, globalMin, globalMax, threshold);
  404.             //if (IJ.debugMode) new ImagePlus("pixel types precleanup", typeP.duplicate()).show();
  405.             cleanupMaxima(outIp, typeP, maxPoints);     //eliminate all the small maxima (i.e. those outside MAX_AREA)
  406.             //if (IJ.debugMode) new ImagePlus("pixel types postcleanup", typeP).show();
  407.             //if (IJ.debugMode) new ImagePlus("pre-watershed", outIp.duplicate()).show();
  408.             if (!watershedSegment(outIp))               //do watershed segmentation
  409.                 return null;                            //if user-cancelled, return
  410.             if (!isEDM) cleanupExtraLines(outIp);       //eliminate lines due to local minima (none in EDM)
  411.             watershedPostProcess(outIp);                //levels to binary image
  412.             if (excludeOnEdges) deleteEdgeParticles(outIp, typeP);
  413.         } else {                                        //outputType other than SEGMENTED
  414.             for (int i=0; i<width*height; i++)
  415.                 types[i] = (byte)(((types[i]&outputTypeMasks[outputType])!=0)?255:0);
  416.             outIp = typeP;
  417.         }
  418.         byte[] outPixels = (byte[])outIp.getPixels();
  419.         //IJ.write("roi: "+roi.toString());
  420.         if (roi!=null) {
  421.             for (int y=0, i=0; y<outIp.getHeight(); y++) { //delete everything outside roi
  422.                 for (int x=0; x<outIp.getWidth(); x++, i++) {
  423.                     if (x<roi.x || x>=roi.x+roi.width || y<roi.y || y>=roi.y+roi.height) outPixels[i] = (byte)0;
  424.                     else if (mask !=null && (mask[x-roi.x + roi.width*(y-roi.y)]==0)) outPixels[i] = (byte)0;
  425.                 }
  426.             }
  427.         }
  428.  
  429.         return outIp;
  430.     } // public ByteProcessor findMaxima
  431.        
  432.     /** Find all local maxima (irrespective whether they finally qualify as maxima or not)
  433.      * @param ip    The image to be analyzed
  434.      * @param typeP A byte image, same size as ip, where the maximum points are marked as MAXIMUM
  435.      *              (do not use it as output: for rois, the points are shifted w.r.t. the input image)
  436.      * @param excludeEdgesNow Whether to exclude edge pixels
  437.      * @param isEDM     Whether ip is a float Euclidian distance map
  438.      * @param globalMin The minimum value of the image or roi
  439.      * @param threshold The threshold (calibrated) below which no pixels are processed. Ignored if ImageProcessor.NO_THRESHOLD
  440.      * @return          Maxima sorted by value. In each array element (long, i.e., 64-bit integer), the value
  441.      *                  is encoded in the upper 32 bits and the pixel offset in the lower 32 bit
  442.      * Note: Do not use the positions of the points marked as MAXIMUM in typeP, they are invalid for images with a roi.
  443.      */    
  444.     long[] getSortedMaxPoints(ImageProcessor ip, ByteProcessor typeP, boolean excludeEdgesNow,
  445.             boolean isEDM, float globalMin, float globalMax, double threshold) {
  446.         Rectangle roi = ip.getRoi();
  447.         byte[] types =  (byte[])typeP.getPixels();
  448.         int nMax = 0;  //counts local maxima
  449.         boolean checkThreshold = threshold!=ImageProcessor.NO_THRESHOLD;
  450.         Thread thread = Thread.currentThread();
  451.         //long t0 = System.currentTimeMillis();
  452.         for (int y=roi.y; y<roi.y+roi.height; y++) {         // find local maxima now
  453.             if (y%50==0 && thread.isInterrupted()) return null;
  454.             for (int x=roi.x, i=x+y*width; x<roi.x+roi.width; x++, i++) {      // for better performance with rois, restrict search to roi
  455.                 float v = ip.getPixelValue(x,y);
  456.                 float vTrue = isEDM ? trueEdmHeight(x,y,ip) : v;  // for EDMs, use interpolated ridge height
  457.                 if (v==globalMin) continue;
  458.                 if (excludeEdgesNow && (x==0 || x==width-1 || y==0 || y==height-1)) continue;
  459.                 if (checkThreshold && v<threshold) continue;
  460.                 boolean isMax = true;
  461.                 /* check wheter we have a local maximum.
  462.                  Note: For an EDM, we need all maxima: those of the EDM-corrected values
  463.                  (needed by findMaxima) and those of the raw values (needed by cleanupMaxima) */
  464.                 boolean isInner = (y!=0 && y!=height-1) && (x!=0 && x!=width-1); //not necessary, but faster than isWithin
  465.                 for (int d=0; d<8; d++) {                         // compare with the 8 neighbor pixels
  466.                     if (isInner || isWithin(x, y, d)) {
  467.                         float vNeighbor = ip.getPixelValue(x+DIR_X_OFFSET[d], y+DIR_Y_OFFSET[d]);
  468.                         float vNeighborTrue = isEDM ? trueEdmHeight(x+DIR_X_OFFSET[d], y+DIR_Y_OFFSET[d], ip) : vNeighbor;
  469.                         if (vNeighbor > v && vNeighborTrue > vTrue) {
  470.                             isMax = false;
  471.                             break;
  472.                         }
  473.                     }
  474.                 }
  475.                 if (isMax) {
  476.                     types[i] = MAXIMUM;
  477.                     nMax++;
  478.                 }
  479.             } // for x
  480.         } // for y
  481.         if (thread.isInterrupted()) return null;
  482.         //long t1 = System.currentTimeMillis();IJ.log("markMax:"+(t1-t0));
  483.        
  484.         float vFactor = (float)(2e9/(globalMax-globalMin)); //for converting float values into a 32-bit int
  485.         long[] maxPoints = new long[nMax];                  //value (int) is in the upper 32 bit, pixel offset in the lower
  486.         int iMax = 0;
  487.         for (int y=roi.y; y<roi.y+roi.height; y++)           //enter all maxima into an array
  488.             for (int x=roi.x, p=x+y*width; x<roi.x+roi.width; x++, p++)
  489.                 if (types[p]==MAXIMUM) {
  490.                     float fValue = isEDM?trueEdmHeight(x,y,ip):ip.getPixelValue(x,y);
  491.                     int iValue = (int)((fValue-globalMin)*vFactor); //32-bit int, linear function of float value
  492.                     maxPoints[iMax++] = (long)iValue<<32|p;
  493.                 }
  494.         //long t2 = System.currentTimeMillis();IJ.log("makeArray:"+(t2-t1));
  495.         if (thread.isInterrupted()) return null;
  496.         Arrays.sort(maxPoints);                                 //sort the maxima by value
  497.         //long t3 = System.currentTimeMillis();IJ.log("sort:"+(t3-t2));
  498.         return maxPoints;
  499.     } //getSortedMaxPoints
  500.  
  501.    /** Check all maxima in list maxPoints, mark type of the points in typeP
  502.     * @param ip             the image to be analyzed
  503.     * @param typeP          8-bit image, here the point types are marked by type: MAX_POINT, etc.
  504.     * @param maxPoints      input: a list of all local maxima, sorted by height. Lower 32 bits are pixel offset
  505.     * @param excludeEdgesNow whether to avoid edge maxima
  506.     * @param isEDM          whether ip is a (float) Euclidian distance map
  507.     * @param globalMin      minimum pixel value in ip
  508.     * @param tolerance      minimum pixel value difference for two separate maxima
  509.     * @param maxSortingError sorting may be inaccurate, sequence may be reversed for maxima having values
  510.     *                       not deviating from each other by more than this (this could be a result of
  511.     *                       precision loss when sorting ints instead of floats, or because sorting does not
  512.     *                       take the height correction in 'trueEdmHeight' into account
  513.     * @param outputType
  514.     */  
  515.    void analyzeAndMarkMaxima(ImageProcessor ip, ByteProcessor typeP, long[] maxPoints, boolean excludeEdgesNow,
  516.         boolean isEDM, float globalMin, double tolerance, int outputType, float maxSortingError) {
  517.         byte[] types =  (byte[])typeP.getPixels();
  518.         float[] edmPixels = isEDM ? (float[])ip.getPixels() : null;
  519.         int nMax = maxPoints.length;
  520.         int [] pList = new int[width*height];       //here we enter points starting from a maximum
  521.         Vector xyVector = null;
  522.         Roi roi = null;
  523.         boolean displayOrCount = outputType==POINT_SELECTION||outputType==LIST||outputType==COUNT;
  524.         if (displayOrCount)
  525.             xyVector=new Vector();
  526.         if (imp!=null)
  527.             roi = imp.getRoi();    
  528.      
  529.         for (int iMax=nMax-1; iMax>=0; iMax--) {    //process all maxima now, starting from the highest
  530.             if (iMax%100 == 0 && Thread.currentThread().isInterrupted()) return;
  531.             int offset0 = (int)maxPoints[iMax];     //type cast gets 32 lower bits, where pixel index is encoded
  532.             //int offset0 = maxPoints[iMax].offset;
  533.             if ((types[offset0]&PROCESSED)!=0)      //this maximum has been reached from another one, skip it
  534.                 continue;
  535.             //we create a list of connected points and start the list at the current maximum
  536.             int x0 = offset0 % width;              
  537.             int y0 = offset0 / width;
  538.             float v0 = isEDM?trueEdmHeight(x0,y0,ip):ip.getPixelValue(x0,y0);
  539.             boolean sortingError;
  540.             do {                                    //repeat if we have encountered a sortingError
  541.                 pList[0] = offset0;
  542.                 types[offset0] |= (EQUAL|LISTED);   //mark first point as equal height (to itself) and listed
  543.                 int listLen = 1;                    //number of elements in the list
  544.                 int listI = 0;                      //index of current element in the list
  545.                 boolean isEdgeMaximum = (x0==0 || x0==width-1 || y0==0 || y0==height-1);
  546.                 sortingError = false;       //if sorting was inaccurate: a higher maximum was not handled so far
  547.                 boolean maxPossible = true;         //it may be a true maximum
  548.                 double xEqual = x0;                 //for creating a single point: determine average over the
  549.                 double yEqual = y0;                 //  coordinates of contiguous equal-height points
  550.                 int nEqual = 1;                     //counts xEqual/yEqual points that we use for averaging
  551.                 do {                                //while neigbor list is not fully processed (to listLen)
  552.                     int offset = pList[listI];
  553.                     int x = offset % width;
  554.                     int y = offset / width;
  555.                     //if(x==18&&y==20)IJ.write("x0,y0="+x0+","+y0+"@18,20;v0="+v0+" sortingError="+sortingError);
  556.                     boolean isInner = (y!=0 && y!=height-1) && (x!=0 && x!=width-1); //not necessary, but faster than isWithin
  557.                     for (int d=0; d<8; d++) {       //analyze all neighbors (in 8 directions) at the same level
  558.                         int offset2 = offset+dirOffset[d];
  559.                         if ((isInner || isWithin(x, y, d)) && (types[offset2]&LISTED)==0) {
  560.                             if (isEDM && edmPixels[offset2]<=0) continue;   //ignore the background (non-particles)
  561.                             if ((types[offset2]&PROCESSED)!=0) {
  562.                                 maxPossible = false; //we have reached a point processed previously, thus it is no maximum now
  563.                                 //if(x0<25&&y0<20)IJ.write("x0,y0="+x0+","+y0+":stop at processed neighbor from x,y="+x+","+y+", dir="+d);
  564.                                 break;
  565.                             }
  566.                             int x2 = x+DIR_X_OFFSET[d];
  567.                             int y2 = y+DIR_Y_OFFSET[d];
  568.                             float v2 = isEDM ? trueEdmHeight(x2, y2, ip) : ip.getPixelValue(x2, y2);
  569.                             if (v2 > v0 + maxSortingError) {
  570.                                 maxPossible = false;    //we have reached a higher point, thus it is no maximum
  571.                                 //if(x0<25&&y0<20)IJ.write("x0,y0="+x0+","+y0+":stop at higher neighbor from x,y="+x+","+y+", dir="+d+",value,value2,v2-v="+v0+","+v2+","+(v2-v0));
  572.                                 break;
  573.                             } else if (v2 >= v0-(float)tolerance) {
  574.                                 if (v2 > v0) {          //maybe this point should have been treated earlier
  575.                                     sortingError = true;
  576.                                     offset0 = offset2;
  577.                                     v0 = v2;
  578.                                     x0 = x2;
  579.                                     y0 = y2;
  580.  
  581.                                 }
  582.                                 pList[listLen] = offset2;
  583.                                 listLen++;              //we have found a new point within the tolerance
  584.                                 types[offset2] |= LISTED;
  585.                                 if (x2==0 || x2==width-1 || y2==0 || y2==height-1) {
  586.                                     isEdgeMaximum = true;
  587.                                     if (excludeEdgesNow) {
  588.                                         maxPossible = false;
  589.                                         break;          //we have an edge maximum;
  590.                                     }
  591.                                 }
  592.                                 if (v2==v0) {           //prepare finding center of equal points (in case single point needed)
  593.                                     types[offset2] |= EQUAL;
  594.                                     xEqual += x2;
  595.                                     yEqual += y2;
  596.                                     nEqual ++;
  597.                                 }
  598.                             }
  599.                         } // if isWithin & not LISTED
  600.                     } // for directions d
  601.                     listI++;
  602.                 } while (listI < listLen);
  603.  
  604.                 if (sortingError)  {                  //if x0,y0 was not the true maximum but we have reached a higher one
  605.                     for (listI=0; listI<listLen; listI++)
  606.                         types[pList[listI]] = 0;    //reset all points encountered, then retry
  607.                 } else {
  608.                     int resetMask = ~(maxPossible?LISTED:(LISTED|EQUAL));
  609.                     xEqual /= nEqual;
  610.                     yEqual /= nEqual;
  611.                     double minDist2 = 1e20;
  612.                     int nearestI = 0;
  613.                     for (listI=0; listI<listLen; listI++) {
  614.                         int offset = pList[listI];
  615.                         int x = offset % width;
  616.                         int y = offset / width;
  617.                         types[offset] &= resetMask;     //reset attributes no longer needed
  618.                         types[offset] |= PROCESSED;     //mark as processed
  619.                         if (maxPossible) {
  620.                             types[offset] |= MAX_AREA;
  621.                             if ((types[offset]&EQUAL)!=0) {
  622.                                 double dist2 = (xEqual-x)*(double)(xEqual-x) + (yEqual-y)*(double)(yEqual-y);
  623.                                 if (dist2 < minDist2) {
  624.                                     minDist2 = dist2;   //this could be the best "single maximum" point
  625.                                     nearestI = listI;
  626.                                 }
  627.                             }
  628.                         }
  629.                     } // for listI
  630.                     if (maxPossible) {
  631.                         int offset = pList[nearestI];
  632.                         types[offset] |= MAX_POINT;
  633.                         if (displayOrCount && !(this.excludeOnEdges && isEdgeMaximum)) {
  634.                             int x = offset % width;
  635.                             int y = offset / width;
  636.                             if (roi==null || roi.contains(x, y))
  637.                                 xyVector.addElement(new int[] {x, y});
  638.                         }
  639.                     }
  640.                 } //if !sortingError
  641.             } while (sortingError);             //redo if we have encountered a higher maximum: handle it now.
  642.         } // for all maxima iMax
  643.  
  644.         if (Thread.currentThread().isInterrupted()) return;
  645.         if (displayOrCount && xyVector!=null) {
  646.             int npoints = xyVector.size();
  647.             if (outputType == POINT_SELECTION && npoints>0) {
  648.                 int[] xpoints = new int[npoints];
  649.                 int[] ypoints = new int[npoints];
  650.                 for (int i=0; i<npoints; i++) {
  651.                     int[] xy = (int[])xyVector.elementAt(i);
  652.                     xpoints[i] = xy[0];
  653.                     ypoints[i] = xy[1];
  654.                 }
  655.                 if (imp!=null) {
  656.                     Roi points = new PointRoi(xpoints, ypoints, npoints);
  657.                     ((PointRoi)points).setHideLabels(true);
  658.                     imp.setRoi(points);
  659.                 }
  660.                 points = new Polygon(xpoints, ypoints, npoints);
  661.             } else if (outputType==LIST) {
  662.                 Analyzer.resetCounter();
  663.                 ResultsTable rt = ResultsTable.getResultsTable();
  664.                 for (int i=0; i<npoints; i++) {
  665.                     int[] xy = (int[])xyVector.elementAt(i);
  666.                     rt.incrementCounter();
  667.                     rt.addValue("X", xy[0]);
  668.                     rt.addValue("Y", xy[1]);
  669.                 }
  670.                 rt.show("Results");
  671.             } else if (outputType==COUNT) {
  672.                 ResultsTable rt = ResultsTable.getResultsTable();
  673.                 rt.incrementCounter();
  674.                 rt.setValue("Count", rt.getCounter()-1, npoints);
  675.                 int measurements = Analyzer.getMeasurements();
  676.                 if ((measurements&Measurements.LABELS)!=0) {
  677.                     String s = imp.getTitle();
  678.                     String roiName = roi!=null?roi.getName():null;
  679.                     if (roiName!=null)
  680.                         s += ":"+roiName;
  681.                     if (imp.getStackSize()>1) {
  682.                         ImageStack stack = imp.getStack();
  683.                         int currentSlice = imp.getCurrentSlice();
  684.                         String label = stack.getShortSliceLabel(currentSlice);
  685.                         String colon = s.equals("")?"":":";
  686.                         if (label!=null && !label.equals(""))
  687.                             s += colon+label;
  688.                         else
  689.                             s += colon+currentSlice;
  690.                     }
  691.                     rt.setLabel(s, rt.getCounter()-1);
  692.                 }
  693.                 rt.show("Results");
  694.             }
  695.         }
  696.         if (previewing)
  697.             messageArea.setText((xyVector==null ? 0 : xyVector.size())+" Maxima");
  698.     } //void analyzeAndMarkMaxima
  699.  
  700.    /** Create an 8-bit image by scaling the pixel values of ip to 1-254 (<lower threshold 0) and mark maximum areas as 255.
  701.     * For use as input for watershed segmentation
  702.     * @param ip         The original image that should be segmented
  703.     * @param typeP      Pixel types in ip
  704.     * @param isEDM      Whether ip is an Euclidian distance map
  705.     * @param globalMin  The minimum pixel value of ip
  706.     * @param globalMax  The maximum pixel value of ip
  707.     * @param threshold  Pixels of ip below this value (calibrated) are considered background. Ignored if ImageProcessor.NO_THRESHOLD
  708.     * @return           The 8-bit output image.
  709.     */
  710.     ByteProcessor make8bit(ImageProcessor ip, ByteProcessor typeP, boolean isEDM, float globalMin, float globalMax, double threshold) {
  711.         byte[] types = (byte[])typeP.getPixels();
  712.         double minValue;
  713.         if (isEDM) {
  714.             threshold = 0.5;
  715.             minValue = 1.;
  716.         } else
  717.             minValue = (threshold == ImageProcessor.NO_THRESHOLD)?globalMin:threshold;
  718.         double offset = minValue - (globalMax-minValue)*(1./253/2-1e-6); //everything above minValue should become >(byte)0
  719.         double factor = 253/(globalMax-minValue);
  720.         //IJ.write("min,max="+minValue+","+globalMax+"; offset, 1/factor="+offset+", "+(1./factor));
  721.         if (isEDM && factor>1)
  722.             factor = 1;   // with EDM, no better resolution
  723.         ByteProcessor outIp = new ByteProcessor(width, height);
  724.         //convert possibly calibrated image to byte without damaging threshold (setMinAndMax would kill threshold)
  725.         byte[] pixels = (byte[])outIp.getPixels();
  726.         long v;
  727.         for (int y=0, i=0; y<height; y++) {
  728.             for (int x=0; x<width; x++, i++) {
  729.                 float rawValue = ip.getPixelValue(x, y);
  730.                 if (threshold!=ImageProcessor.NO_THRESHOLD && rawValue<threshold)
  731.                     pixels[i] = (byte)0;
  732.                 else if ((types[i]&MAX_AREA)!=0)
  733.                     pixels[i] = (byte)255;  //prepare watershed by setting "true" maxima+surroundings to 255
  734.                 else {
  735.                     v = 1 + Math.round((rawValue-offset)*factor);
  736.                     if (v < 1) pixels[i] = (byte)1;
  737.                     else if (v<=254) pixels[i] = (byte)(v&255);
  738.                     else pixels[i] = (byte)254;
  739.                 }
  740.             }
  741.         }
  742.         return outIp;
  743.     } // byteProcessor make8bit
  744.  
  745.    /** Get estimated "true" height of a maximum or saddle point of a Euclidian Distance Map.
  746.      * This is needed since the point sampled is not necessarily at the highest position.
  747.      * For simplicity, we don't care about the Sqrt(5) distance here although this would be more accurate
  748.      * @param x     x-position of the point
  749.      * @param y     y-position of the point
  750.      * @param ip    the EDM (FloatProcessor)
  751.      * @return      estimated height
  752.      */
  753.     float trueEdmHeight(int x, int y, ImageProcessor ip) {
  754.         int xmax = width - 1;
  755.         int ymax = ip.getHeight() - 1;
  756.         float[] pixels = (float[])ip.getPixels();
  757.         int offset = x + y*width;
  758.         float v =  pixels[offset];
  759.         if (x==0 || y==0 || x==xmax || y==ymax || v==0) {
  760.             return v;                               //don't recalculate for edge pixels or background
  761.         } else {
  762.             float trueH = v + 0.5f*SQRT2;           //true height can never by higher than this
  763.             boolean ridgeOrMax = false;
  764.             for (int d=0; d<4; d++) {               //for all directions halfway around:
  765.                 int d2 = (d+4)%8;                   //get the opposite direction and neighbors
  766.                 float v1 = pixels[offset+dirOffset[d]];
  767.                 float v2 = pixels[offset+dirOffset[d2]];
  768.                 float h;
  769.                 if (v>=v1 && v>=v2) {
  770.                     ridgeOrMax = true;
  771.                     h = (v1 + v2)/2;
  772.                 } else {
  773.                     h = Math.min(v1, v2);
  774.                 }
  775.                 h += (d%2==0) ? 1 : SQRT2;          //in diagonal directions, distance is sqrt2
  776.                 if (trueH > h) trueH = h;
  777.             }
  778.             if (!ridgeOrMax) trueH = v;
  779.             return trueH;
  780.         }
  781.     }
  782.    
  783.     /** eliminate unmarked maxima for use by watershed. Starting from each previous maximum,
  784.      * explore the surrounding down to successively lower levels until a marked maximum is
  785.      * touched (or the plateau of a previously eliminated maximum leads to a marked maximum).
  786.      * Then set all the points above this value to this value
  787.      * @param outIp     the image containing the pixel values
  788.      * @param typeP     the types of the pixels are marked here
  789.      * @param maxPoints array containing the coordinates of all maxima that might be relevant
  790.      */    
  791.     void cleanupMaxima(ByteProcessor outIp, ByteProcessor typeP, long[] maxPoints) {
  792.         byte[] pixels = (byte[])outIp.getPixels();
  793.         byte[] types = (byte[])typeP.getPixels();
  794.         int nMax = maxPoints.length;
  795.         int[] pList = new int[width*height];
  796.         for (int iMax = nMax-1; iMax>=0; iMax--) {
  797.             int offset0 = (int)maxPoints[iMax];     //type cast gets lower 32 bits where pixel offset is encoded
  798.             if ((types[offset0]&(MAX_AREA|ELIMINATED))!=0) continue;
  799.             int level = pixels[offset0]&255;
  800.             int loLevel = level+1;
  801.             pList[0] = offset0;                     //we start the list at the current maximum
  802.             //if (xList[0]==122) IJ.write("max#"+iMax+" at x,y="+xList[0]+","+yList[0]+"; level="+level);
  803.             types[offset0] |= LISTED;               //mark first point as listed
  804.             int listLen = 1;                        //number of elements in the list
  805.             int lastLen = 1;
  806.             int listI = 0;                          //index of current element in the list
  807.             boolean saddleFound = false;
  808.             while (!saddleFound && loLevel >0) {
  809.                 loLevel--;
  810.                 lastLen = listLen;                  //remember end of list for previous level
  811.                 listI = 0;                          //in each level, start analyzing the neighbors of all pixels
  812.                 do {                                //for all pixels listed so far
  813.                     int offset = pList[listI];
  814.                     int x = offset % width;
  815.                     int y = offset / width;
  816.                     boolean isInner = (y!=0 && y!=height-1) && (x!=0 && x!=width-1); //not necessary, but faster than isWithin
  817.                     for (int d=0; d<8; d++) {       //analyze all neighbors (in 8 directions) at the same level
  818.                         int offset2 = offset+dirOffset[d];
  819.                         if ((isInner || isWithin(x, y, d)) && (types[offset2]&LISTED)==0) {
  820.                             if ((types[offset2]&MAX_AREA)!=0 || (((types[offset2]&ELIMINATED)!=0) && (pixels[offset2]&255)>=loLevel)) {
  821.                                 saddleFound = true; //we have reached a point touching a "true" maximum...
  822.                                 //if (xList[0]==122) IJ.write("saddle found at level="+loLevel+"; x,y="+xList[listI]+","+yList[listI]+", dir="+d);
  823.                                 break;              //...or a level not lower, but touching a "true" maximum
  824.                             } else if ((pixels[offset2]&255)>=loLevel && (types[offset2]&ELIMINATED)==0) {
  825.                                 pList[listLen] = offset2;
  826.                                 //xList[listLen] = x+DIR_X_OFFSET[d];
  827.                                 //yList[listLen] = x+DIR_Y_OFFSET[d];
  828.                                 listLen++;          //we have found a new point to be processed
  829.                                 types[offset2] |= LISTED;
  830.                             }
  831.                         } // if isWithin & not LISTED
  832.                     } // for directions d
  833.                     if (saddleFound) break;         //no reason to search any further
  834.                     listI++;
  835.                 } while (listI < listLen);
  836.             } // while !levelFound && loLevel>=0
  837.             for (listI=0; listI<listLen; listI++)   //reset attribute since we may come to this place again
  838.                 types[pList[listI]] &= ~LISTED;
  839.             for (listI=0; listI<lastLen; listI++) { //for all points higher than the level of the saddle point
  840.                 int offset = pList[listI];
  841.                 pixels[offset] = (byte)loLevel;     //set pixel value to the level of the saddle point
  842.                 types[offset] |= ELIMINATED;        //mark as processed: there can't be a local maximum in this area
  843.             }
  844.         } // for all maxima iMax
  845.     } // void cleanupMaxima
  846.  
  847.     /** Delete extra structures form watershed of non-EDM images, e.g., foreground patches,
  848.      *  single dots and lines ending somewhere within a segmented particle
  849.      *  Needed for post-processing watershed-segmented images that can have local minima
  850.      *  @param ip 8-bit image with background = 0, lines between 1 and 254 and segmented particles = 255
  851.      */    
  852.     void cleanupExtraLines(ImageProcessor ip) {
  853.         byte[] pixels =  (byte[])ip.getPixels();
  854.         for (int y=0, i=0; y<height; y++) {
  855.             for (int x=0; x<width; x++,i++) {
  856.                 int v = pixels[i];
  857.                 if (v!=(byte)255 && v!=0) {
  858.                     int nRadii = nRadii(pixels, x, y);     //number of lines radiating
  859.                     if (nRadii==0)                              //single point or foreground patch?
  860.                         pixels[i] = (byte)255;
  861.                     else if (nRadii==1)
  862.                         removeLineFrom(pixels, x, y);
  863.                 } // if v<255 && v>0
  864.             } // for x
  865.         } // for y
  866.     } // void cleanupExtraLines
  867.  
  868.     /** delete a line starting at x, y up to the next (4-connected) vertex */
  869.     void removeLineFrom (byte[] pixels, int x, int y) {
  870.         //IJ.log("del line from "+x+","+y);
  871.         //if(x<50&&y<40)IJ.write("x,y start="+x+","+y);
  872.         pixels[x + width*y] = (byte)255;                        //delete the first point
  873.         boolean continues;
  874.         do {
  875.             continues = false;
  876.             boolean isInner = (y!=0 && y!=height-1) && (x!=0 && x!=width-1); //not necessary, but faster than isWithin
  877.             for (int d=0; d<8; d+=2) {                          //analyze 4-connected neighbors
  878.                 if (isInner || isWithin(x, y, d)) {
  879.                     int v = pixels[x + width*y + dirOffset[d]];
  880.                     if (v!=(byte)255 && v!=0) {
  881.                         int nRadii = nRadii(pixels, x+DIR_X_OFFSET[d], y+DIR_Y_OFFSET[d]);
  882.                         if (nRadii<=1) {                        //found a point or line end
  883.                             x += DIR_X_OFFSET[d];
  884.                             y += DIR_Y_OFFSET[d];
  885.                             pixels[x + width*y] = (byte)255;    //delete the point
  886.                             continues = nRadii==1;              //continue along that line
  887.                             break;
  888.                         }
  889.                     }
  890.                 }
  891.             } // for directions d
  892.         //if(!continues && x<50&&y<40)IJ.write("x,y end="+x+","+y);
  893.         } while (continues);
  894.         //IJ.log("deleted to "+x+","+y);
  895.     } // void removeLineFrom
  896.  
  897.     /** Analyze the neighbors of a pixel (x, y) in a byte image; pixels <255 ("non-white") are
  898.      * considered foreground. Edge pixels are considered foreground.
  899.      * @param   ip
  900.      * @param   x coordinate of the point
  901.      * @param   y coordinate of the point
  902.      * @return  Number of 4-connected lines emanating from this point. Zero if the point is
  903.      *          embedded in either foreground or background
  904.      */
  905.     int nRadii (byte[] pixels, int x, int y) {
  906.         int offset = x + y*width;
  907.         int countTransitions = 0;
  908.         boolean prevPixelSet = true;
  909.         boolean firstPixelSet = true;           //initialize to make the compiler happy
  910.         boolean isInner = (y!=0 && y!=height-1) && (x!=0 && x!=width-1); //not necessary, but faster than isWithin
  911.         for (int d=0; d<8; d++) {               //walk around the point and note every no-line->line transition
  912.             boolean pixelSet = prevPixelSet;
  913.             if (isInner || isWithin(x, y, d)) {
  914.                 boolean isSet = (pixels[offset+dirOffset[d]]!=(byte)255);
  915.                 if ((d&1)==0) pixelSet = isSet; //non-diagonal directions: always regarded
  916.                 else if (!isSet)                //diagonal directions may separate two lines,
  917.                     pixelSet = false;           //    but are insufficient for a 4-connected line
  918.             } else {
  919.                 pixelSet = true;
  920.             }
  921.             if (pixelSet && !prevPixelSet)
  922.                 countTransitions ++;
  923.             prevPixelSet = pixelSet;
  924.             if (d==0)
  925.                 firstPixelSet = pixelSet;
  926.         }
  927.         if (firstPixelSet && !prevPixelSet)
  928.             countTransitions ++;
  929.         return countTransitions;
  930.     } // int nRadii
  931.  
  932.     /** after watershed, set all pixels in the background and segmentation lines to 0
  933.      */
  934.     private void watershedPostProcess(ImageProcessor ip) {
  935.         //new ImagePlus("before postprocess",ip.duplicate()).show();
  936.         byte[] pixels = (byte[])ip.getPixels();
  937.         int size = ip.getWidth()*ip.getHeight();
  938.         for (int i=0; i<size; i++) {
  939.            if ((pixels[i]&255)<255)
  940.                 pixels[i] = (byte)0;
  941.         }
  942.         //new ImagePlus("after postprocess",ip.duplicate()).show();
  943.     }
  944.  
  945.     /** delete particles corresponding to edge maxima
  946.      * @param typeP Here the pixel types of the original image are noted,
  947.      * pixels with bit MAX_AREA at the edge are considered indicators of an edge maximum.
  948.      * @param ip the image resulting from watershed segmentaiton
  949.      * (foreground pixels, i.e. particles, are 255, background 0)
  950.      */
  951.     void deleteEdgeParticles(ByteProcessor ip, ByteProcessor typeP) {
  952.         byte[] pixels = (byte[])ip.getPixels();
  953.         byte[] types = (byte[])typeP.getPixels();
  954.         width = ip.getWidth();
  955.         height = ip.getHeight();
  956.         ip.setValue(0);
  957.         Wand wand = new Wand(ip);
  958.         for (int x=0; x<width; x++) {
  959.             int y = 0;
  960.             if ((types[x+y*width]&MAX_AREA) != 0 && pixels[x+y*width] != 0)
  961.                 deleteParticle(x,y,ip,wand);
  962.             y = height - 1;
  963.             if ((types[x+y*width]&MAX_AREA) != 0 && pixels[x+y*width] != 0)
  964.                 deleteParticle(x,y,ip,wand);            
  965.         }
  966.         for (int y=1; y<height-1; y++) {
  967.             int x = 0;
  968.             if ((types[x+y*width]&MAX_AREA) != 0 && pixels[x+y*width] != 0)
  969.                 deleteParticle(x,y,ip,wand);
  970.             x = width - 1;
  971.             if ((types[x+y*width]&MAX_AREA) != 0 && pixels[x+y*width] != 0)
  972.                 deleteParticle(x,y,ip,wand);            
  973.         }
  974.     } //void deleteEdgeParticles
  975.  
  976.     /** delete a particle (set from value 255 to current fill value).
  977.      * Position x,y must be within the particle
  978.      */
  979.     void deleteParticle(int x, int y, ByteProcessor ip, Wand wand) {
  980.         wand.autoOutline(x, y, 255, 255);
  981.         if (wand.npoints==0) {
  982.             IJ.log("wand error selecting edge particle at x, y = "+x+", "+y);
  983.             return;
  984.         }
  985.         Roi roi = new PolygonRoi(wand.xpoints, wand.ypoints, wand.npoints, Roi.TRACED_ROI);
  986.         ip.snapshot();                  //prepare for reset outside of mask
  987.         ip.setRoi(roi);
  988.         ip.fill();
  989.         ip.reset(ip.getMask());
  990.     }
  991.  
  992.     /** Do watershed segmentation on a byte image, with the start points (maxima)
  993.      * set to 255 and the background set to 0. The image should not have any local maxima
  994.      * other than the marked ones. Local minima will lead to artifacts that can be removed
  995.      * later. On output, all particles will be set to 255, segmentation lines remain at their
  996.      * old value.
  997.      * @param ip  The byteProcessor containing the image, with size given by the class variables width and height
  998.      * @return    false if canceled by the user (note: can be cancelled only if called by "run" with a known ImagePlus)
  999.      */    
  1000.     private boolean watershedSegment(ByteProcessor ip) {
  1001.         boolean debug = IJ.debugMode;
  1002.         ImageStack movie=null;
  1003.         if (debug) {
  1004.             movie = new ImageStack(ip.getWidth(), ip.getHeight());
  1005.             movie.addSlice("pre-watershed EDM", ip.duplicate());
  1006.         }
  1007.         byte[] pixels = (byte[])ip.getPixels();
  1008.         // Create an array with the coordinates of all points between value 1 and 254
  1009.         // This method, suggested by Stein Roervik (stein_at_kjemi-dot-unit-dot-no),
  1010.         // greatly speeds up the watershed segmentation routine.
  1011.         int[] histogram = ip.getHistogram();
  1012.         int arraySize = width*height - histogram[0] -histogram[255];
  1013.         int[] coordinates = new int[arraySize];    //from pixel coordinates, low bits x, high bits y
  1014.         int highestValue = 0;
  1015.         int maxBinSize = 0;
  1016.         int offset = 0;
  1017.         int[] levelStart = new int[256];
  1018.         for (int v=1; v<255; v++) {
  1019.             levelStart[v] = offset;
  1020.             offset += histogram[v];
  1021.             if (histogram[v] > 0) highestValue = v;
  1022.             if (histogram[v] > maxBinSize) maxBinSize = histogram[v];
  1023.         }
  1024.         int[] levelOffset = new int[highestValue + 1];
  1025.         for (int y=0, i=0; y<height; y++) {
  1026.             for (int x=0; x<width; x++, i++) {
  1027.                 int v = pixels[i]&255;
  1028.                 if (v>0 && v<255) {
  1029.                     offset = levelStart[v] + levelOffset[v];
  1030.                     coordinates[offset] = x | y<<intEncodeShift;
  1031.                     levelOffset[v] ++;
  1032.                 }
  1033.            } //for x
  1034.         } //for y
  1035.         // Create an array of the points (pixel offsets) that we set to 255 in one pass.
  1036.         // If we remember this list we need not create a snapshot of the ImageProcessor.
  1037.         int[] setPointList = new int[Math.min(maxBinSize, (width*height+2)/3)];
  1038.         // now do the segmentation, starting at the highest level and working down.
  1039.         // At each level, dilate the particle (set pixels to 255), constrained to pixels
  1040.         // whose values are at that level and also constrained (by the fateTable)
  1041.         // to prevent features from merging.
  1042.         int[] table = makeFateTable();
  1043.         IJ.showStatus("Segmenting (Esc to cancel)");
  1044.         final int[] directionSequence = new int[] {7, 3, 1, 5, 0, 4, 2, 6}; // diagonal directions first
  1045.         for (int level=highestValue; level>=1; level--) {
  1046.             int remaining = histogram[level];  //number of points in the level that have not been processed
  1047.             int idle = 0;
  1048.             while (remaining>0 && idle<8) {
  1049.                 int sumN = 0;
  1050.                 int dIndex = 0;
  1051.                 do {                        // expand each level in 8 directions
  1052.                     int n = processLevel(directionSequence[dIndex%8], ip, table,
  1053.                             levelStart[level], remaining, coordinates, setPointList);
  1054.                     //IJ.log("level="+level+" direction="+directionSequence[dIndex%8]+" remain="+remaining+"-"+n);
  1055.                     remaining -= n;         // number of points processed
  1056.                     sumN += n;
  1057.                     if (n > 0) idle = 0;    // nothing processed in this direction?
  1058.                     dIndex++;
  1059.                 } while (remaining>0 && idle++<8);
  1060.                 addProgress(sumN/(double)arraySize);
  1061.                 if (IJ.escapePressed()) {   // cancelled by the user
  1062.                     IJ.beep();
  1063.                     IJ.showProgress(1.0);
  1064.                     return false;
  1065.                 }
  1066.             }
  1067.             if (remaining>0 && level>1) {   // any pixels that we have not reached?
  1068.                 int nextLevel = level;      // find the next level to process
  1069.                 do
  1070.                     nextLevel--;
  1071.                 while (nextLevel>1 && histogram[nextLevel]==0);
  1072.                 // in principle we should add all unprocessed pixels of this level to the
  1073.                 // tasklist of the next level. This would make it very slow for some images,
  1074.                 // however. Thus we only add the pixels if they are at the border (of the
  1075.                 // image or a thresholded area) and correct unprocessed pixels at the very
  1076.                 // end by CleanupExtraLines
  1077.                 if (nextLevel > 0) {
  1078.                     int newNextLevelEnd = levelStart[nextLevel] + histogram[nextLevel];
  1079.                     for (int i=0, p=levelStart[level]; i<remaining; i++, p++) {
  1080.                         int xy = coordinates[p];
  1081.                         int x = xy&intEncodeXMask;
  1082.                         int y = (xy&intEncodeYMask)>>intEncodeShift;
  1083.                         int pOffset = x + y*width;
  1084.                         if ((pixels[pOffset]&255)==255) IJ.log("ERROR");
  1085.                         boolean addToNext = false;
  1086.                         if (x==0 || y==0 || x==width-1 || y==height-1)
  1087.                             addToNext = true;           //image border
  1088.                         else for (int d=0; d<8; d++)
  1089.                             if (isWithin(x, y, d) && pixels[pOffset+dirOffset[d]]==0) {
  1090.                                 addToNext = true;       //border of area below threshold
  1091.                                 break;
  1092.                             }
  1093.                         if (addToNext)
  1094.                             coordinates[newNextLevelEnd++] = xy;
  1095.                     }
  1096.                     //IJ.log("level="+level+": add "+(newNextLevelEnd-levelStart[nextLevel+1])+" points to "+nextLevel);
  1097.                     //tasklist for the next level to process becomes longer by this:
  1098.                     histogram[nextLevel] = newNextLevelEnd - levelStart[nextLevel];
  1099.                 }
  1100.             }
  1101.             if (debug && (level>170 || level>100 && level<110 || level<10))
  1102.                 movie.addSlice("level "+level, ip.duplicate());
  1103.         }
  1104.         if (debug)
  1105.             new ImagePlus("Segmentation Movie", movie).show();
  1106.         return true;
  1107.     } // boolean watershedSegment
  1108.  
  1109.  
  1110.     /** dilate the UEP on one level by one pixel in the direction specified by step, i.e., set pixels to 255
  1111.      * @param pass gives direction of dilation, see makeFateTable
  1112.      * @param ip the EDM with the segmeted blobs successively getting set to 255
  1113.      * @param table             The fateTable
  1114.      * @param levelStart        offsets of the level in pixelPointers[]
  1115.      * @param levelNPoints      number of points in the current level
  1116.      * @param pixelPointers[]   list of pixel coordinates (x+y*width) sorted by level (in sequence of y, x within each level)
  1117.      * @param xCoordinates      list of x Coorinates for the current level only (no offset levelStart)
  1118.      * @return                  number of pixels that have been changed
  1119.      */
  1120.     private int processLevel(int pass, ImageProcessor ip, int[] fateTable,
  1121.             int levelStart, int levelNPoints, int[] coordinates, int[] setPointList) {
  1122.         int xmax = width - 1;
  1123.         int ymax = height - 1;
  1124.         byte[] pixels = (byte[])ip.getPixels();
  1125.         //byte[] pixels2 = (byte[])ip2.getPixels();
  1126.         int nChanged = 0;
  1127.         int nUnchanged = 0;
  1128.         for (int i=0, p=levelStart; i<levelNPoints; i++, p++) {
  1129.             int xy = coordinates[p];
  1130.             int x = xy&intEncodeXMask;
  1131.             int y = (xy&intEncodeYMask)>>intEncodeShift;
  1132.             int offset = x + y*width;
  1133.             int index = 0;      //neighborhood pixel ocupation: index in fateTable
  1134.             if (y>0 && (pixels[offset-width]&255)==255)
  1135.                 index ^= 1;
  1136.             if (x<xmax && y>0 && (pixels[offset-width+1]&255)==255)
  1137.                 index ^= 2;
  1138.             if (x<xmax && (pixels[offset+1]&255)==255)
  1139.                 index ^= 4;
  1140.             if (x<xmax && y<ymax && (pixels[offset+width+1]&255)==255)
  1141.                 index ^= 8;
  1142.             if (y<ymax && (pixels[offset+width]&255)==255)
  1143.                 index ^= 16;
  1144.             if (x>0 && y<ymax && (pixels[offset+width-1]&255)==255)
  1145.                 index ^= 32;
  1146.             if (x>0 && (pixels[offset-1]&255)==255)
  1147.                 index ^= 64;
  1148.             if (x>0 && y>0 && (pixels[offset-width-1]&255)==255)
  1149.                 index ^= 128;
  1150.             int mask = 1<<pass;
  1151.             if ((fateTable[index]&mask)==mask)
  1152.                 setPointList[nChanged++] = offset;  //remember to set pixel to 255
  1153.             else
  1154.                 coordinates[levelStart+(nUnchanged++)] = xy; //keep this pixel for future passes
  1155.  
  1156.         } // for pixel i
  1157.         //IJ.log("pass="+pass+", changed="+nChanged+" unchanged="+nUnchanged);
  1158.         for (int i=0; i<nChanged; i++)
  1159.             pixels[setPointList[i]] = (byte)255;
  1160.         return nChanged;
  1161.     } //processLevel
  1162.  
  1163.     /** Creates the lookup table used by the watershed function for dilating the particles.
  1164.      * The algorithm allows dilation in both straight and diagonal directions.
  1165.      * There is an entry in the table for each possible 3x3 neighborhood:
  1166.      *          x-1          x          x+1
  1167.      *  y-1    128            1          2
  1168.      *  y       64     pxl_unset_yet     4
  1169.      *  y+1     32           16          8
  1170.      * (to find throws entry, sum up the numbers of the neighboring pixels set; e.g.
  1171.      * entry 6=2+4 if only the pixels (x,y-1) and (x+1, y-1) are set.
  1172.      * A pixel is added on the 1st pass if bit 0 (2^0 = 1) is set,
  1173.      * on the 2nd pass if bit 1 (2^1 = 2) is set, etc.
  1174.      * pass gives the direction of rotation, with 0 = to top left (x--,y--), 1 to top,
  1175.      * and clockwise up to 7 = to the left (x--).
  1176.      * E.g. 4 = add on 3rd pass, 3 = add on either 1st or 2nd pass.
  1177.      */
  1178.     private int[] makeFateTable() {
  1179.         int[] table = new int[256];
  1180.         boolean[] isSet = new boolean[8];
  1181.         for (int item=0; item<256; item++) {        //dissect into pixels
  1182.             for (int i=0, mask=1; i<8; i++) {
  1183.                 isSet[i] = (item&mask)==mask;
  1184.                 mask*=2;
  1185.             }
  1186.             for (int i=0, mask=1; i<8; i++) {       //we dilate in the direction opposite to the direction of the existing neighbors
  1187.                 if (isSet[(i+4)%8]) table[item] |= mask;
  1188.                 mask*=2;
  1189.             }
  1190.             for (int i=0; i<8; i+=2)                //if side pixels are set, for counting transitions it is as good as if the adjacent edges were also set
  1191.                 if (isSet[i]) {
  1192.                     isSet[(i+1)%8] = true;
  1193.                     isSet[(i+7)%8] = true;
  1194.                 }
  1195.             int transitions=0;
  1196.             for (int i=0, mask=1; i<8; i++) {
  1197.                 if (isSet[i] != isSet[(i+1)%8])
  1198.                     transitions++;
  1199.             }
  1200.             if (transitions>=4) {                   //if neighbors contain more than one region, dilation ito this pixel is forbidden
  1201.                 table[item] = 0;
  1202.             } else {
  1203.             }
  1204.         }
  1205.         return table;
  1206.     } // int[] makeFateTable
  1207.  
  1208.     /** create an array of offsets within a pixel array for directions in clockwise order:
  1209.      * 0=(x,y-1), 1=(x+1,y-1), ... 7=(x-1,y)
  1210.      * Also creates further class variables:
  1211.      * width, height, and the following three values needed for storing coordinates in single ints for watershed:
  1212.      * intEncodeXMask, intEncodeYMask and intEncodeShift.
  1213.      * E.g., for width between 129 and 256, xMask=0xff and yMask = 0xffffff00 are bitwise masks
  1214.      * for x and y, respectively, and shift=8 is the bit shift to get y from the y-masked value
  1215.      * Returns as class variables: the arrays of the offsets to the 8 neighboring pixels
  1216.      * and the array maskAndShift for watershed
  1217.      */
  1218.     void makeDirectionOffsets(ImageProcessor ip) {
  1219.         width = ip.getWidth();
  1220.         height = ip.getHeight();
  1221.         int shift = 0, mult=1;
  1222.         do {
  1223.             shift++; mult*=2;
  1224.         }
  1225.         while (mult < width);
  1226.         intEncodeXMask = mult-1;
  1227.         intEncodeYMask = ~intEncodeXMask;
  1228.         intEncodeShift = shift;
  1229.         //IJ.log("masks (hex):"+Integer.toHexString(xMask)+","+Integer.toHexString(xMask)+"; shift="+shift);
  1230.         dirOffset  = new int[] {-width, -width+1, +1, +width+1, +width, +width-1,   -1, -width-1 };
  1231.         //dirOffset is created last, so check for it being null before makeDirectionOffsets
  1232.         //(in case we have multiple threads using the same MaximumFinder)
  1233.     }
  1234.  
  1235.     /** returns whether the neighbor in a given direction is within the image
  1236.      * NOTE: it is assumed that the pixel x,y itself is within the image!
  1237.      * Uses class variables width, height: dimensions of the image
  1238.      * @param x         x-coordinate of the pixel that has a neighbor in the given direction
  1239.      * @param y         y-coordinate of the pixel that has a neighbor in the given direction
  1240.      * @param direction the direction from the pixel towards the neighbor (see makeDirectionOffsets)
  1241.      * @return          true if the neighbor is within the image (provided that x, y is within)
  1242.      */
  1243.     boolean isWithin(int x, int y, int direction) {
  1244.         int xmax = width - 1;
  1245.         int ymax = height -1;
  1246.         switch(direction) {
  1247.             case 0:
  1248.                 return (y>0);
  1249.             case 1:
  1250.                 return (x<xmax && y>0);
  1251.             case 2:
  1252.                 return (x<xmax);
  1253.             case 3:
  1254.                 return (x<xmax && y<ymax);
  1255.             case 4:
  1256.                 return (y<ymax);
  1257.             case 5:
  1258.                 return (x>0 && y<ymax);
  1259.             case 6:
  1260.                 return (x>0);
  1261.             case 7:
  1262.                 return (x>0 && y>0);
  1263.         }
  1264.         return false;   //to make the compiler happy :-)
  1265.     } // isWithin
  1266.  
  1267.     /** add work done in the meanwhile and show progress */
  1268.     private void addProgress(double deltaProgress) {
  1269.         if (nPasses==0) return;
  1270.         progressDone += deltaProgress;
  1271.         IJ.showProgress(progressDone/nPasses);
  1272.     }
  1273.  
  1274. }
Advertisement
Add Comment
Please, Sign In to add comment