Package ij.plugin.filter

Source Code of ij.plugin.filter.RankFilters

package ij.plugin.filter;
import ij.*;
import ij.gui.GenericDialog;
import ij.gui.DialogListener;
import ij.gui.Roi;
import ij.process.*;
import ij.plugin.ContrastEnhancer;
import java.awt.*;
import java.awt.event.*;
import java.util.Arrays;

/** This plugin implements the Mean, Minimum, Maximum, Variance, Median, Open Maxima, Close Maxima,
*  Remove Outliers, Remove NaNs and Despeckle commands.
*/

public class RankFilters implements ExtendedPlugInFilter, DialogListener {
  public static final int   MEAN=0, MIN=1, MAX=2, VARIANCE=3, MEDIAN=4, OUTLIERS=5, DESPECKLE=6, REMOVE_NAN=7,
            OPEN=8, CLOSE=9;
    private static int HIGHEST_FILTER = CLOSE;
  private static final int BRIGHT_OUTLIERS = 0, DARK_OUTLIERS = 1;
  private static final String[] outlierStrings = {"Bright","Dark"};
  // Filter parameters
  private double radius;
  private double threshold;
  private int whichOutliers;
  private int filterType;
  // Remember filter parameters for the next time
  private static double[] lastRadius = new double[HIGHEST_FILTER+1]; //separate for each filter type
  private static double lastThreshold = 50.;
  private static int lastWhichOutliers = BRIGHT_OUTLIERS;
  //
  // F u r t h e r   c l a s s   v a r i a b l e s
  int flags = DOES_ALL|SUPPORTS_MASKING|KEEP_PREVIEW;
  private ImagePlus imp;
  private int nPasses = 1;      // The number of passes (color channels * stack slices)
  private PlugInFilterRunner pfr;
  private int pass;
  // M u l t i t h r e a d i n g - r e l a t e d
    private int numThreads = Prefs.getThreads();
  // Current state of processing is in class variables. Thus, stack parallelization must be done
  // ONLY with one thread for the image (not using these class variables):
  private int highestYinCache;    // the highest line read into the cache so far
  private boolean threadWaiting;    // a thread waits until it may read data
  private boolean copyingToCache;    // whether a thread is currently copying data to the cache

  private boolean isMultiStepFilter(int filterType) {
    return filterType>=OPEN;
  }

  /** Setup of the PlugInFilter. Returns the flags specifying the capabilities and needs
   * of the filter.
   *
   * @param arg  Defines type of filter operation
   * @param imp  The ImagePlus to be processed
   * @return    Flags specifying further action of the PlugInFilterRunner
   */    
  public int setup(String arg, ImagePlus imp) {
    this.imp = imp;
    if (arg.equals("mean"))
      filterType = MEAN;
    else if (arg.equals("min"))
      filterType = MIN;
    else if (arg.equals("max"))
      filterType = MAX;
    else if (arg.equals("variance")) {
      filterType = VARIANCE;
      flags |= FINAL_PROCESSING;
    } else if (arg.equals("median"))
      filterType = MEDIAN;
    else if (arg.equals("outliers"))
      filterType = OUTLIERS;
    else if (arg.equals("despeckle"))
      filterType = DESPECKLE;
    else if (arg.equals("close"))
      filterType = CLOSE;
    else if (arg.equals("open"))
      filterType = OPEN;
    else if (arg.equals("nan")) {
      filterType = REMOVE_NAN;
      if (imp!=null && imp.getBitDepth()!=32) {
        IJ.error("RankFilters","\"Remove NaNs\" requires a 32-bit image");
        return DONE;
      }
    } else if (arg.equals("final")) {   //after variance filter, adjust brightness&contrast
        if (imp!=null  && imp.getBitDepth()!=8 && imp.getBitDepth()!=24 && imp.getRoi()==null)
            new ContrastEnhancer().stretchHistogram(imp.getProcessor(), 0.5);
    } else if (arg.equals("masks")) {
      showMasks();
      return DONE;
    } else {
      IJ.error("RankFilters","Argument missing or undefined: "+arg);
      return DONE;
    }
        if (isMultiStepFilter(filterType) && imp!=null) {  //composite filter: 'open maxima' etc:
            Roi roi = imp.getRoi();
            if (roi!=null && !roi.getBounds().contains(new Rectangle(imp.getWidth(), imp.getHeight())))
                //Roi < image? (actually tested: NOT (Roi>=image))
                flags |= SNAPSHOT;          //snapshot for resetRoiBoundary
        }
    return flags;
  }

  public int showDialog(ImagePlus imp, String command, PlugInFilterRunner pfr) {
    if (filterType == DESPECKLE) {
      filterType = MEDIAN;
      radius = 1.0;
    } else {
      GenericDialog gd = new GenericDialog(command+"...");
      radius = lastRadius[filterType]<=0 ? 2 :  lastRadius[filterType];
      gd.addNumericField("Radius", radius, 1, 6, "pixels");
      int digits = imp.getType() == ImagePlus.GRAY32 ? 2 : 0;
      if (filterType==OUTLIERS) {
        gd.addNumericField("Threshold", lastThreshold, digits);
        gd.addChoice("Which outliers", outlierStrings, outlierStrings[lastWhichOutliers]);
        gd.addHelp(IJ.URL+"/docs/menus/process.html#outliers");
      } else if (filterType==REMOVE_NAN)
        gd.addHelp(IJ.URL+"/docs/menus/process.html#nans");
      gd.addPreviewCheckbox(pfr);    //passing pfr makes the filter ready for preview
      gd.addDialogListener(this);    //the DialogItemChanged method will be called on user input
      gd.showDialog();        //display the dialog; preview runs in the  now
      if (gd.wasCanceled()) return DONE;
      IJ.register(this.getClass())//protect static class variables (filter parameters) from garbage collection
      if (Macro.getOptions() == null) { //interactive only: remember parameters entered
          lastRadius[filterType] = radius;
          if (filterType == OUTLIERS) {
              lastThreshold = threshold;
              lastWhichOutliers = whichOutliers;
          }
      }
    }
    this.pfr = pfr;
    flags = IJ.setupDialog(imp, flags)//ask whether to process all slices of stack (if a stack)
    if ((flags&DOES_STACKS)!=0) {
        int size = imp.getWidth() * imp.getHeight();
        Roi roi = imp.getRoi();
        if (roi != null) {
            Rectangle roiRect = roi.getBounds();
            size = roiRect.width * roiRect.height;
            }
            double workToDo = size*(double)radius;  //estimate computing time (arb. units)
            if (filterType==MEAN || filterType==VARIANCE) workToDo *= 0.5;
            else if (filterType==MEDIAN) workToDo *= radius*0.5;
            if (workToDo < 1e6 && imp.getImageStackSize()>=numThreads) {
                numThreads = 1;             //for fast operations, avoid overhead of multi-threading in each image
                flags |= PARALLELIZE_STACKS;
            }
        }
    return flags;
  }

  public boolean dialogItemChanged(GenericDialog gd, AWTEvent e) {
    radius = gd.getNextNumber();
    if (filterType == OUTLIERS) {
      threshold = gd.getNextNumber();
      whichOutliers = gd.getNextChoiceIndex();
    }
    int maxRadius = (filterType==MEDIAN || filterType==OUTLIERS || filterType==REMOVE_NAN) ? 100 : 1000;
    if (gd.invalidNumber() || radius<0 || radius>maxRadius || (filterType==OUTLIERS && threshold <0))
      return false;
    return true;
  }

  public void run(ImageProcessor ip) {
    rank(ip, radius, filterType, whichOutliers, (float)threshold);
    if (IJ.escapePressed())                                 // interrupted by user?
        ip.reset();
  }

    /** Filters an image by any method except 'despecle' or 'remove outliers'.
   *  @param ip     The ImageProcessor that should be filtered (all 4 types supported)
   *  @param radius  Determines the kernel size, see Process>Filters>Show Circular Masks.
   *           Must not be negative. No checking is done for large values that would
   *           lead to excessive computing times.
   *  @param filterType May be MEAN, MIN, MAX, VARIANCE, or MEDIAN.
   */
  public void rank(ImageProcessor ip, double radius, int filterType) {
        rank(ip, radius, filterType, 0, 50f);
  }

  /** Filters an image by any method except 'despecle' (for 'despeckle', use 'median' and radius=1)
   * @param ip The image subject to filtering
   * @param radius The kernel radius
   * @param filterType as defined above; DESPECKLE is not a valid type here; use median and
     *        a radius of 1.0 instead
     * @param whichOutliers BRIGHT_OUTLIERS or DARK_OUTLIERS for 'outliers' filter
   * @param threshold Threshold for 'outliers' filter
   */
    public void rank(ImageProcessor ip, double radius, int filterType, int whichOutliers, float threshold) {
        Rectangle roi = ip.getRoi();
        ImageProcessor mask = ip.getMask();
        Rectangle roi1 = null;
        int[] lineRadii = makeLineRadii(radius);
        boolean isImagePart = (roi.width<ip.getWidth()) || (roi.height<ip.getHeight());
        boolean[] aborted = new boolean[1];                     // returns whether interrupted during preview or ESC pressed
        for (int ch=0; ch<ip.getNChannels(); ch++) {
            int filterType1 = filterType;
            if (isMultiStepFilter(filterType)) {
                filterType1 = (filterType==OPEN) ? MIN : MAX;
                    if (isImagePart) { //composite filters ('open maxima' etc.) need larger area in first step
                    int kRadius = kRadius(lineRadii);
                    int kHeight = kHeight(lineRadii);
                    Rectangle roiClone = (Rectangle)roi.clone();
                    roiClone.grow(kRadius, kHeight/2);
                    roi1 = roiClone.intersection(new Rectangle(ip.getWidth(), ip.getHeight()));
                    ip.setRoi(roi1);
                }
            }
            doFiltering(ip, lineRadii, filterType1, whichOutliers, threshold, ch, aborted);
            if (aborted[0]) break;
            if (isMultiStepFilter(filterType)) {
                ip.setRoi(roi);
                ip.setMask(mask);
                int filterType2 = (filterType==OPEN) ? MAX : MIN;
                doFiltering(ip, lineRadii, filterType2, whichOutliers, threshold, ch, aborted);
                if (aborted[0]) break;
                if (isImagePart)
                    resetRoiBoundary(ip, roi, roi1);
      }
        }
    }

  // Filter a grayscale image or one channel of an RGB image with several threads
  // Implementation: each thread uses the same input buffer (cache), always works on the next unfiltered line
  // Usually, one thread reads reads several lines into the cache, while the others are processing the data.
  // 'aborted[0]' is set if the main thread has been interrupted (during preview) or ESC pressed.
  // 'aborted' must not be a class variable because it signals the other threads to stop; and this may be caused
  // by an interrupted preview thread after the main calculation has been started.
  private void doFiltering(final ImageProcessor ip, final int[] lineRadii, final int filterType,
      final int whichOutliers, final float threshold, final int colorChannel, final boolean[] aborted) {
    Rectangle roi = ip.getRoi();
    int width = ip.getWidth();
        Object pixels = ip.getPixels();

    int numThreads = Math.min(roi.height, this.numThreads);

        int kHeight = kHeight(lineRadii);
        int kRadius  = kRadius(lineRadii);
        final int cacheWidth = roi.width+2*kRadius;
        final int cacheHeight = kHeight + (numThreads>1 ? 2*numThreads : 0);
        // 'cache' is the input buffer. Each line y in the image is mapped onto cache line y%cacheHeight
        final float[] cache = new float[cacheWidth*cacheHeight];
    highestYinCache = Math.max(roi.y-kHeight/2, 0) - 1//this line+1 will be read into the cache first 

        final int[] yForThread = new int[numThreads];    //threads announce here which line they currently process
        Arrays.fill(yForThread, -1);
        yForThread[numThreads-1] = roi.y-1;          //first thread started should begin at roi.y

    final Thread[] threads = new Thread[numThreads];
    for (int t=numThreads-1; t>0; t--) {
      final int ti=t;
      final Thread thread = new Thread(
                    new Runnable() {
                        final public void run() {
                          doFiltering(ip, lineRadii, cache, cacheWidth, cacheHeight,
                              filterType, whichOutliers, threshold, colorChannel,
                              yForThread, ti, aborted);
                        }
                    },
            "RankFilters-"+t);
            thread.setPriority(Thread.currentThread().getPriority());
            thread.start();
            threads[ti] = thread;
    }

    doFiltering(ip, lineRadii, cache, cacheWidth, cacheHeight,
                filterType, whichOutliers, threshold, colorChannel,
                yForThread, 0, aborted);
    try {
            for (final Thread thread : threads)
                if (thread != null) thread.join();
        }
        catch (InterruptedException e) {
          aborted[0] = true;
        }
        showProgress(1.0, ip instanceof ColorProcessor);
        pass++;
  }

  // Filter a grayscale image or one channel of an RGB image using one thread
  //
  // Synchronization: unless a thread is waiting, we avoid the overhead of 'synchronized'
  // statements. That's because a thread waiting for another one should be rare.
    //
  // Data handling: The area needed for processing a line  is written into the array 'cache'.
  // This is a stripe of sufficient width for all threads to have each thread processing one
  // line, and some extra space if one thread is finished to start the next line.
  // This array is padded at the edges of the image so that a surrounding with radius kRadius
  // for each pixel processed is within 'cache'. Out-of-image
  // pixels are set to the value of the nearest edge pixel. When adding a new line, the lines in
  // 'cache' are not shifted but rather the smaller array with the start and end pointers of the
    // kernel area is modified to point at the addresses for the next line.
  //
  // Algorithm: For mean and variance, except for very small radius, usually do not calculate the
  // sum over all pixels. This sum is calculated for the first pixel of every line only. For the
  // following pixels, add the new values and subtract those that are not in the sum any more.
  // For min/max, also first look at the new values, use their maximum if larger than the old
  // one. The look at the values not in the area any more; if it does not contain the old
  // maximum, leave the maximum unchanged. Otherwise, determine the maximum inside the area.
  // For outliers, calculate the median only if the pixel deviates by more than the threshold
  // from any pixel in the area. Therfore min or max is calculated; this is a much faster
  // operation than the median.
  private void doFiltering(ImageProcessor ip, int[] lineRadii, float[] cache, int cacheWidth, int cacheHeight,
      int filterType, int whichOutliers, float threshold, int colorChannel,
      int [] yForThread, int threadNumber, boolean[] aborted) {
    if (aborted[0] || Thread.currentThread().isInterrupted()) return;
    int width = ip.getWidth();
    int height = ip.getHeight();
    Rectangle roi = ip.getRoi();
       
        int kHeight = kHeight(lineRadii);
        int kRadius  = kRadius(lineRadii);
        int kNPoints = kNPoints(lineRadii);

    int xmin = roi.x - kRadius;
    int xmax = roi.x + roi.width + kRadius;
        int[]cachePointers = makeCachePointers(lineRadii, cacheWidth);

        int padLeft = xmin<0 ? -xmin : 0;
        int padRight = xmax>width? xmax-width : 0;
    int xminInside = xmin>0 ? xmin : 0;
    int xmaxInside = xmax<width ? xmax : width;
    int widthInside = xmaxInside - xminInside;
       
    boolean minOrMax = filterType == MIN || filterType == MAX;
    boolean minOrMaxOrOutliers = minOrMax || filterType == OUTLIERS;
    boolean sumFilter = filterType == MEAN || filterType == VARIANCE;
    boolean medianFilter = filterType == MEDIAN || filterType == OUTLIERS;
    double[] sums = sumFilter ? new double[2] : null;
    float[] medianBuf1 = (medianFilter||filterType==REMOVE_NAN) ? new float[kNPoints] : null;
    float[] medianBuf2 = (medianFilter||filterType==REMOVE_NAN) ? new float[kNPoints] : null;
    float sign = filterType==MIN ? -1f : 1f;
    if (filterType == OUTLIERS)    //sign is -1 for high outliers: compare number with minimum
      sign = (ip.isInvertedLut()==(whichOutliers==DARK_OUTLIERS)) ? -1f : 1f;
    boolean smallKernel = kRadius < 2;

        Object pixels = ip.getPixels();
        boolean isFloat = pixels instanceof float[];
        float maxValue = isFloat ? Float.NaN : (float)ip.maxValue();
        float[] values = isFloat ? (float[])pixels : new float[roi.width];

    int numThreads = yForThread.length;
    long lastTime = System.currentTimeMillis();
    int previousY = kHeight/2-cacheHeight;
    boolean rgb = ip instanceof ColorProcessor;
       
        while (!aborted[0]) {
          int y = arrayMax(yForThread) + 1;    // y of the next line that needs processing
          yForThread[threadNumber] = y;
      //IJ.log("thread "+threadNumber+" @y="+y+" needs"+(y-kHeight/2)+"-"+(y+kHeight/2)+" highestYinC="+highestYinCache);
      boolean threadFinished = y >= roi.y+roi.height;
          if (numThreads>1 && (threadWaiting || threadFinished))    // 'if' is not synchronized to avoid overhead
            synchronized(this) {
              notifyAll();          // we may have blocked another thread
          //IJ.log("thread "+threadNumber+" @y="+y+" notifying");
            }
          if (threadFinished) return;        // all done, break the loop

      if (threadNumber==0) {          // main thread checks for abort and ProgressBar
        long time = System.currentTimeMillis();
        if (time-lastTime>100) {
          lastTime = time;
          showProgress((y-roi.y)/(double)(roi.height), rgb);
          if (Thread.currentThread().isInterrupted() || (imp!= null && IJ.escapePressed())) {
            aborted[0] = true;
            synchronized(this) {notifyAll();}
            return;
          }
        }
      }
     
      for (int i=0; i<cachePointers.length; i++)   //shift kernel pointers to new line
                cachePointers[i] = (cachePointers[i] + cacheWidth*(y-previousY))%cache.length;
            previousY = y;

      if (numThreads>1) {            // thread synchronization
        int slowestThreadY = arrayMinNonNegative(yForThread);
        if (y - slowestThreadY + kHeight > cacheHeight) {  // we would overwrite data needed by another thread
          synchronized(this) {
            do {
              notifyAll();      // avoid deadlock: wake up others waiting
              threadWaiting = true;
              //IJ.log("Thread "+threadNumber+" waiting @y="+y+" slowest@y="+slowestThreadY);
              try {
                wait();
                if (aborted[0]) return;
              } catch (InterruptedException e) {
                aborted[0] = true;
                notifyAll();
                return;
              }
              slowestThreadY = arrayMinNonNegative(yForThread);
            } while (y - slowestThreadY + kHeight > cacheHeight);
            threadWaiting = false;
          }
        }
      }

      if (numThreads==1) {                              // R E A D
          int yStartReading = y==roi.y ? Math.max(roi.y-kHeight/2, 0) : y+kHeight/2;
        for (int yNew = yStartReading; yNew<=y+kHeight/2; yNew++) {  //only 1 line except at start
          readLineToCacheOrPad(pixels, width, height, roi.y, xminInside, widthInside,
              cache, cacheWidth, cacheHeight, padLeft, padRight, colorChannel, kHeight, yNew);
        }
      } else {
        if (!copyingToCache || highestYinCache < y+kHeight/2) synchronized(cache) {
          copyingToCache = true;        // copy new line(s) into cache
          while (highestYinCache < arrayMinNonNegative(yForThread) - kHeight/2 + cacheHeight - 1) {
            int yNew = highestYinCache + 1;
            readLineToCacheOrPad(pixels, width, height, roi.y, xminInside, widthInside,
              cache, cacheWidth, cacheHeight, padLeft, padRight, colorChannel, kHeight, yNew);
            highestYinCache = yNew;
          }
          copyingToCache = false;
        }
            }

      int cacheLineP = cacheWidth * (y % cacheHeight) + kRadius;  //points to pixel (roi.x, y)
      filterLine(values, width, cache, cachePointers, kNPoints, cacheLineP, roi, y,  // F I L T E R
          sums, medianBuf1, medianBuf2, sign, maxValue, isFloat, filterType,
          smallKernel, sumFilter, minOrMax, minOrMaxOrOutliers);
        if (!isFloat)    //Float images: data are written already during 'filterLine'
                writeLineToPixels(values, pixels, roi.x+y*width, roi.width, colorChannel)// W R I T E
      //IJ.log("thread "+threadNumber+" @y="+y+" line done");
        } // while (!aborted[0]); loop over y (lines)
  }

  private int arrayMax(int[] array) {
    int max = Integer.MIN_VALUE;
    for (int i=0; i<array.length; i++)
      if (array[i] > max) max = array[i];
    return max;
  }

  //only checks non-negative numbers in the array. Returns Integer.MAX_VALUE if no such values.
  private int arrayMinNonNegative(int[] array) {
    int min = Integer.MAX_VALUE;
    for (int i=0; i<array.length; i++)
      if (array[i]>=0 && array[i]<min) min = array[i];
    return min;
  }

  private void filterLine(float[] values, int width, float[] cache, int[] cachePointers, int kNPoints, int cacheLineP, Rectangle roi, int y,
      double[] sums, float[] medianBuf1, float[] medianBuf2, float sign, float maxValue, boolean isFloat, int filterType,
      boolean smallKernel, boolean sumFilter, boolean minOrMax, boolean minOrMaxOrOutliers) {
            int valuesP = isFloat ? roi.x+y*width : 0;
      float max = 0f;
      float median = Float.isNaN(cache[cacheLineP]) ? 0 : cache[cacheLineP]// a first guess
      boolean fullCalculation = true;
      for (int x=0; x<roi.width; x++, valuesP++) {              // x is with respect to roi.x
        if (fullCalculation) {
          fullCalculation = smallKernel;  //for small kernel, always use the full area, not incremental algorithm
          if (minOrMaxOrOutliers)
            max = getAreaMax(cache, x, cachePointers, 0, -Float.MAX_VALUE, sign);
          if (minOrMax) {
                        values[valuesP] = max*sign;
                        continue;
                    }
          else if (sumFilter)
            getAreaSums(cache, x, cachePointers, sums);
        } else {
          if (minOrMaxOrOutliers) {
            float newPointsMax = getSideMax(cache, x, cachePointers, true, sign);
            if (newPointsMax >= max) { //compare with previous maximum 'max'
              max = newPointsMax;
            } else {
              float removedPointsMax = getSideMax(cache, x, cachePointers, false, sign);
              if (removedPointsMax >= max)
                max = getAreaMax(cache, x, cachePointers, 1, newPointsMax, sign);
            }
            if (minOrMax) {
              values[valuesP] = max*sign;
                            continue;
                }
          } else if (sumFilter) {
            addSideSums(cache, x, cachePointers, sums);
            if (Double.isNaN(sums[0])) //avoid perpetuating NaNs into remaining line
              fullCalculation = true;
          }
        }
        if (sumFilter) {
          if (filterType == MEAN)
            values[valuesP] = (float)(sums[0]/kNPoints);
          else  {// Variance: sum of squares - square of sums
            float value = (float)((sums[1] - sums[0]*sums[0]/kNPoints)/kNPoints);
                        if (value>maxValue) value = maxValue;
                        values[valuesP] = value;
                    }
        } else if (filterType == MEDIAN) {
          median = getMedian(cache, x, cachePointers, medianBuf1, medianBuf2, kNPoints, median);
          values[valuesP] = median;
        } else if (filterType == OUTLIERS) {
            float v = cache[cacheLineP+x];
            if (v*sign+threshold < max) {       //for low outliers: median can't be higher than max (sign is +1)
                median = getMedian(cache, x, cachePointers, medianBuf1, medianBuf2, kNPoints, median);
                        if (v*sign+threshold < median*sign)
                v = median;                 //beyond threshold (below if sign=+1), replace outlier by median
                    }
          values[valuesP] = v;
        } else if (filterType == REMOVE_NAN) {   //float only; then 'values' is pixels array
          if (Float.isNaN(values[valuesP]))
            values[valuesP] = getNaNAwareMedian(cache, x, cachePointers, medianBuf1, medianBuf2, kNPoints, median);
          else
            median = values[valuesP]//initial guess for the next point
        }
      } // for x
        }

  /** Read a line into the cache (including padding in x).
   *  If y>=height, instead of reading new data, it duplicates the line y=height-1.
   *  If y==0, it also creates the data for y<0, as far as necessary, thus filling the cache with
   *  more than one line (padding by duplicating the y=0 row).
   */
  private static void readLineToCacheOrPad(Object pixels, int width, int height, int roiY, int xminInside, int widthInside,
      float[]cache, int cacheWidth, int cacheHeight, int padLeft, int padRight, int colorChannel,
      int kHeight, int y) {
    int lineInCache = y%cacheHeight;
    if (y < height) {
      readLineToCache(pixels, y*width, xminInside, widthInside,
          cache, lineInCache*cacheWidth, padLeft, padRight, colorChannel);
      if (y==0) for (int prevY = roiY-kHeight/2; prevY<0; prevY++) {  //for y<0, pad with y=0 border pixels
        int prevLineInCache = cacheHeight+prevY;
        System.arraycopy(cache, 0, cache, prevLineInCache*cacheWidth, cacheWidth);
      }
    } else
      System.arraycopy(cache, cacheWidth*((height-1)%cacheHeight), cache, lineInCache*cacheWidth, cacheWidth);
  }

    /** Read a line into the cache (includes conversion to flaot). Pad with edge pixels in x if necessary */
    private static void readLineToCache(Object pixels, int pixelLineP, int xminInside, int widthInside,
                                float[] cache, int cacheLineP, int padLeft, int padRight, int colorChannel) {
        if (pixels instanceof byte[]) {
            byte[] bPixels = (byte[])pixels;
            for (int pp=pixelLineP+xminInside, cp=cacheLineP+padLeft; pp<pixelLineP+xminInside+widthInside; pp++,cp++)
                cache[cp] = bPixels[pp]&0xff;
        } else if (pixels instanceof short[]){
            short[] sPixels = (short[])pixels;
            for (int pp=pixelLineP+xminInside, cp=cacheLineP+padLeft; pp<pixelLineP+xminInside+widthInside; pp++,cp++)
                cache[cp] = sPixels[pp]&0xffff;
        } else if (pixels instanceof float[]) {
            System.arraycopy(pixels, pixelLineP+xminInside, cache, cacheLineP+padLeft, widthInside);
        } else {    //RGB
            int[] cPixels = (int[])pixels;
            int shift = 16 - 8*colorChannel;
            int byteMask = 255<<shift;
            for (int pp=pixelLineP+xminInside, cp=cacheLineP+padLeft; pp<pixelLineP+xminInside+widthInside; pp++,cp++)
                cache[cp] = (cPixels[pp]&byteMask)>>shift;
        }
        for (int cp=cacheLineP; cp<cacheLineP+padLeft; cp++)
            cache[cp] = cache[cacheLineP+padLeft];
        for (int cp=cacheLineP+padLeft+widthInside; cp<cacheLineP+padLeft+widthInside+padRight; cp++)
            cache[cp] = cache[cacheLineP+padLeft+widthInside-1];
    }

    /** Write a line to pixels arrax, converting from float (not for float data!)
     *  No checking for overflow/underflow
     */
    private static void writeLineToPixels(float[] values, Object pixels, int pixelP, int length, int colorChannel) {
        if (pixels instanceof byte[]) {
            byte[] bPixels = (byte[])pixels;
            for (int i=0, p=pixelP; i<length; i++,p++)
                bPixels[p] = (byte)(((int)(values[i] + 0.5f))&0xff);
        } else if (pixels instanceof short[]) {
            short[] sPixels = (short[])pixels;
            for (int i=0, p=pixelP; i<length; i++,p++)
                sPixels[p] = (short)(((int)(values[i] + 0.5f))&0xffff);
        } else {    //RGB
            int[] cPixels = (int[])pixels;
            int shift = 16 - 8*colorChannel;
            int resetMask = 0xffffffff^(0xff<<shift);
            for (int i=0, p=pixelP; i<length; i++,p++)
                cPixels[p] = (cPixels[p]&resetMask) | (((int)(values[i] + 0.5f))<<shift);
        }
    }

    /** Get max (or -min if sign=-1) within the kernel area.
   *  @param x between 0 and cacheWidth-1
   *  @param ignoreRight should be 0 for analyzing all data or 1 for leaving out the row at the right
   *  @param max should be -Float.MAX_VALUE or the smallest value the maximum can be */
  private static float getAreaMax(float[] cache, int xCache0, int[] kernel, int ignoreRight, float max, float sign) {
    for (int kk=0; kk<kernel.length; kk++) {  // y within the cache stripe (we have 2 kernel pointers per cache line)
      for (int p=kernel[kk++]+xCache0; p<=kernel[kk]+xCache0-ignoreRight; p++) {
        float v = cache[p]*sign;
        if (max < v) max = v;
      }
    }
    return max;
  }

  /** Get max (or -min if sign=-1) at the right border inside or left border outside the kernel area.
   *  x between 0 and cacheWidth-1 */
  private static float getSideMax(float[] cache, int xCache0, int[] kernel, boolean isRight, float sign) {
    float max = -Float.MAX_VALUE;
        if (!isRight) xCache0--;
    for (int kk= isRight ? 1 : 0; kk<kernel.length; kk+=2) {  // y within the cache stripe (we have 2 kernel pointers per cache line)
      float v = cache[xCache0 + kernel[kk]]*sign;
      if (max < v) max = v;
    }
    return max;
  }

  /** Get sum of values and values squared within the kernel area.
   *  x between 0 and cacheWidth-1
   *  Output is written to array sums[0] = sum; sums[1] = sum of squares */
  private static void getAreaSums(float[] cache, int xCache0, int[] kernel, double[] sums) {
    double sum=0, sum2=0;
    for (int kk=0; kk<kernel.length; kk++) {  // y within the cache stripe (we have 2 kernel pointers per cache line)
      for (int p=kernel[kk++]+xCache0; p<=kernel[kk]+xCache0; p++) {
        float v = cache[p];
        sum += v;
        sum2 += v*v;
      }
    }
    sums[0] = sum;
    sums[1] = sum2;
    return;
  }

  /** Add all values and values squared at the right border inside minus at the left border outside the kernal area.
   *  Output is added or subtracted to/from array sums[0] += sum; sums[1] += sum of squares  when at
   *  the right border, minus when at the left border */
  private static void addSideSums(float[] cache, int xCache0, int[] kernel, double[] sums) {
    double sum=0, sum2=0;
    for (int kk=0; kk<kernel.length; /*k++;k++ below*/) {
      float v = cache[kernel[kk++]+(xCache0-1)];
      sum -= v;
      sum2 -= v*v;
      v = cache[kernel[kk++]+xCache0];
      sum += v;
      sum2 += v*v;
    }
    sums[0] += sum;
    sums[1] += sum2;
    return;
  }

  /** Get median of values within kernel-sized neighborhood. Kernel size kNPoints should be odd.
   */
  private static float getMedian(float[] cache, int xCache0, int[] kernel,
      float[] aboveBuf, float[]belowBuf, int kNPoints, float guess) {
    int nAbove = 0, nBelow = 0;
    for (int kk=0; kk<kernel.length; kk++) {
      for (int p=kernel[kk++]+xCache0; p<=kernel[kk]+xCache0; p++) {
        float v = cache[p];
        if (v > guess) {
          aboveBuf[nAbove] = v;
          nAbove++;
        }
        else if (v < guess) {
          belowBuf[nBelow] = v;
          nBelow++;
        }
      }
    }
    int half = kNPoints/2;
    if (nAbove>half)
      return findNthLowestNumber(aboveBuf, nAbove, nAbove-half-1);
    else if (nBelow>half)
      return findNthLowestNumber(belowBuf, nBelow, half);
    else
      return guess;
  }

  /** Get median of values within kernel-sized neighborhood.
   *  NaN data values are ignored; the output is NaN only if there are only NaN values in the
   *  kernel-sized neighborhood */
  private static float getNaNAwareMedian(float[] cache, int xCache0, int[] kernel,
      float[] aboveBuf, float[]belowBuf, int kNPoints, float guess) {
    int nAbove = 0, nBelow = 0;
    for (int kk=0; kk<kernel.length; kk++) {
      for (int p=kernel[kk++]+xCache0; p<=kernel[kk]+xCache0; p++) {
        float v = cache[p];
        if (Float.isNaN(v)) {
          kNPoints--;
        } else if (v > guess) {
          aboveBuf[nAbove] = v;
          nAbove++;
        }
        else if (v < guess) {
          belowBuf[nBelow] = v;
          nBelow++;
        }
      }
    }
    if (kNPoints == 0) return Float.NaN;  //only NaN data in the neighborhood?
    int half = kNPoints/2;
    if (nAbove>half)
      return findNthLowestNumber(aboveBuf, nAbove, nAbove-half-1);
    else if (nBelow>half)
      return findNthLowestNumber(belowBuf, nBelow, half);
    else
      return guess;
  }

  /** Find the n-th lowest number in part of an array
   *  @param buf The input array. Only values 0 ... bufLength are read. <code>buf</code> will be modified.
   *  @param bufLength Number of values in <code>buf</code> that should be read
   *  @param n which value should be found; n=0 for the lowest, n=bufLength-1 for the highest
   *  @return the value */
  public final static float findNthLowestNumber(float[] buf, int bufLength, int n) {
    // Hoare's find, algorithm, based on http://www.geocities.com/zabrodskyvlada/3alg.html
    // Contributed by Heinz Klar
    int i,j;
    int l=0;
    int m=bufLength-1;
    float med=buf[n];
    float dum ;

    while (l<m) {
      i=l ;
      j=m ;
      do {
        while (buf[i]<med) i++ ;
        while (med<buf[j]) j-- ;
        dum=buf[j];
        buf[j]=buf[i];
        buf[i]=dum;
        i++ ; j-- ;
      } while ((j>=n) && (i<=n)) ;
      if (j<n) l=i ;
      if (n<i) m=j ;
      med=buf[n] ;
    }
  return med ;
  }

    /** Reset region between inner rectangle 'roi' and outer rectangle 'roi1' to the snapshot */
    private void resetRoiBoundary(ImageProcessor ip, Rectangle roi, Rectangle roi1) {
        int width = ip.getWidth();
        Object pixels = ip.getPixels();
        Object snapshot = ip.getSnapshotPixels();
        for (int y=roi1.y, p = roi1.x+y*width; y<roi.y; y++,p+=width)
            System.arraycopy(snapshot, p, pixels, p, roi1.width);
        int leftWidth = roi.x - roi1.x;
        int rightWidth = roi1.x+roi1.width - (roi.x+roi.width);
        for (int y=roi.y, pL=roi1.x+y*width, pR=roi.x+roi.width+y*width; y<roi.y+roi.height; y++,pL+=width,pR+=width) {
            if (leftWidth > 0)
                System.arraycopy(snapshot, pL, pixels, pL, leftWidth);
            if (rightWidth > 0)
                System.arraycopy(snapshot, pR, pixels, pR, rightWidth);
        }
        for (int y=roi.y+roi.height, p = roi1.x+y*width; y<roi1.y+roi1.height; y++,p+=width)
            System.arraycopy(snapshot, p, pixels, p, roi1.width);
    }

    /** @deprecated
     * Not needed any more, use the rank(ip, ...) method, which creates the kernel */
    public void makeKernel(double radius) {
        this.radius = radius;
    }

  /** Create a circular kernel (structuring element) of a given radius.
     *  @param radius:
     *  Radius = 0.5 includes the 4 neighbors of the pixel in the center,
   *  radius = 1 corresponds to a 3x3 kernel size.
     *  @param width: width of the roi (or image if roi width=image width) for filtering.
     *  @return:
   *  The output is an array that gives the length of each line of the structuring element
   *  (kernel) to the left (negative) and to the right (positive):
   *  [0] left in line 0, [1] right in line 0,
     *  [2] left in line 2, ...
     *  The maximum (absolute) value should be kernelRadius.
     *  Array elements at the end:
     *  length-2: nPoints, number of pixels in the kernel area
     *  length-1: kernelRadius in x direction (kernel width is 2*kernelRadius+1)
     *  Kernel height can be calculated as (array length - 1)/2 (odd number);
     *  Kernel radius in y direction is kernel height/2 (truncating integer division).
     *  Note that kernel width and height are the same for the circular kernels used here,
     *  but treated separately for the case of future extensions with non-circular kernels.
   */
  protected int[] makeLineRadii(double radius) {
        if (radius>=1.5 && radius<1.75) //this code creates the same sizes as the previous RankFilters
      radius = 1.75;
    else if (radius>=2.5 && radius<2.85)
      radius = 2.85;
    int r2 = (int) (radius*radius) + 1;
    int kRadius = (int)(Math.sqrt(r2+1e-10));
        int kHeight = 2*kRadius + 1;
        int[] kernel = new int[2*kHeight + 2];
    kernel[2*kRadius]   = -kRadius;
    kernel[2*kRadius+1] =  kRadius;
        int nPoints = 2*kRadius+1;
        for (int y=1; y<=kRadius; y++) {        //lines above and below center together
      int dx = (int)(Math.sqrt(r2-y*y+1e-10));
      kernel[2*(kRadius-y)]   = -dx;
      kernel[2*(kRadius-y)+1] =  dx;
      kernel[2*(kRadius+y)]   = -dx;
      kernel[2*(kRadius+y)+1] =  dx;
            nPoints += 4*dx+2//2*dx+1 for each line, above&below
    }
        kernel[kernel.length-2] = nPoints;
        kernel[kernel.length-1] = kRadius;
        //for (int i=0; i<kHeight;i++)IJ.log(i+": "+kernel[2*i]+"-"+kernel[2*i+1]);
        return kernel;
  }

    //kernel height
    private int kHeight(int[] lineRadii) {
        return (lineRadii.length-2)/2;
    }

    //kernel radius in x direction. width is 2+kRadius+1
    private int kRadius(int[] lineRadii) {
        return lineRadii[lineRadii.length-1];
    }
   
    //number of points in kernal area
    private int kNPoints(int[] lineRadii) {
        return lineRadii[lineRadii.length-2];
    }

   //cache pointers for a given kernel
    private int[] makeCachePointers(int[] lineRadii, int cacheWidth) {
    int kRadius = kRadius(lineRadii);
    int kHeight = kHeight(lineRadii);
    int[] cachePointers = new int[2*kHeight];
    for (int i=0; i<kHeight; i++) {
      cachePointers[2*i]   = i*cacheWidth+kRadius + lineRadii[2*i];
      cachePointers[2*i+1] = i*cacheWidth+kRadius + lineRadii[2*i+1];
    }
    return cachePointers;
  }

  void showMasks() {
    int w=150, h=150;
    ImageStack stack = new ImageStack(w, h);
    //for (double r=0.1; r<3; r+=0.01) {
    for (double r=0.5; r<50; r+=0.5) {
      ImageProcessor ip = new FloatProcessor(w,h,new int[w*h]);
      float[] pixels = (float[])ip.getPixels();
      int[] lineRadii = makeLineRadii(r);
      int kHeight = kHeight(lineRadii);
      int kRadius = kRadius(lineRadii);
      int y0 = h/2-kHeight/2;
      for (int i = 0, y = y0; i<kHeight; i++, y++)
        for (int x = w/2+lineRadii[2*i], p = x+y*w; x <= w/2+lineRadii[2*i+1]; x++, p++)
          pixels[p] = 1f;
      stack.addSlice("radius="+r+", size="+(2*kRadius+1), ip);
    }
    new ImagePlus("Masks", stack).show();
  }

  /** This method is called by ImageJ to set the number of calls to run(ip)
   *  corresponding to 100% of the progress bar */
  public void setNPasses (int nPasses) {
    this.nPasses = nPasses;
    pass = 0;
  }

  private void showProgress(double percent, boolean rgb) {
    int nPasses2 = rgb?nPasses*3:nPasses;
    percent = (double)pass/nPasses2 + percent/nPasses2;
    IJ.showProgress(percent);
  }

}
TOP

Related Classes of ij.plugin.filter.RankFilters

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.