void drawOutline(ImageProcessor ip, Roi roi, int count) {
   if (showChoice == OVERLAY_OUTLINES || showChoice == OVERLAY_MASKS) {
     if (overlay == null) {
       overlay = new Overlay();
       overlay.drawLabels(true);
       overlay.setLabelFont(new Font("SansSerif", Font.PLAIN, fontSize));
     }
     Roi roi2 = (Roi) roi.clone();
     roi2.setStrokeColor(Color.cyan);
     if (lineWidth != 1) roi2.setStrokeWidth(lineWidth);
     if (showChoice == OVERLAY_MASKS) roi2.setFillColor(Color.cyan);
     overlay.add(roi2);
   } else {
     Rectangle r = roi.getBounds();
     int nPoints = ((PolygonRoi) roi).getNCoordinates();
     int[] xp = ((PolygonRoi) roi).getXCoordinates();
     int[] yp = ((PolygonRoi) roi).getYCoordinates();
     int x = r.x, y = r.y;
     if (!inSituShow) ip.setValue(0.0);
     ip.moveTo(x + xp[0], y + yp[0]);
     for (int i = 1; i < nPoints; i++) ip.lineTo(x + xp[i], y + yp[i]);
     ip.lineTo(x + xp[0], y + yp[0]);
     if (showChoice != BARE_OUTLINES) {
       String s = ResultsTable.d2s(count, 0);
       ip.moveTo(r.x + r.width / 2 - ip.getStringWidth(s) / 2, r.y + r.height / 2 + fontSize / 2);
       if (!inSituShow) ip.setValue(1.0);
       ip.drawString(s);
     }
   }
 }
Example #2
0
 void createMask(ImagePlus imp) {
   Roi roi = imp.getRoi();
   boolean useInvertingLut = Prefs.useInvertingLut;
   Prefs.useInvertingLut = false;
   if (roi == null || !(roi.isArea() || roi.getType() == Roi.POINT)) {
     createMaskFromThreshold(imp);
     Prefs.useInvertingLut = useInvertingLut;
     return;
   }
   ImagePlus maskImp = null;
   Frame frame = WindowManager.getFrame("Mask");
   if (frame != null && (frame instanceof ImageWindow))
     maskImp = ((ImageWindow) frame).getImagePlus();
   if (maskImp == null) {
     ImageProcessor ip = new ByteProcessor(imp.getWidth(), imp.getHeight());
     if (!Prefs.blackBackground) ip.invertLut();
     maskImp = new ImagePlus("Mask", ip);
     maskImp.show();
   }
   ImageProcessor ip = maskImp.getProcessor();
   ip.setRoi(roi);
   ip.setValue(255);
   ip.fill(ip.getMask());
   maskImp.updateAndDraw();
   Prefs.useInvertingLut = useInvertingLut;
 }
 public ImageProcessor expandImage(ImageProcessor ipOld, int wNew, int hNew, int xOff, int yOff) {
   ImageProcessor ipNew = ipOld.createProcessor(wNew, hNew);
   if (zeroFill) ipNew.setValue(0.0);
   else ipNew.setColor(Toolbar.getBackgroundColor());
   ipNew.fill();
   ipNew.insert(ipOld, xOff, yOff);
   return ipNew;
 }
 boolean eraseOutsideRoi(ImageProcessor ip, Rectangle r, ImageProcessor mask) {
   int width = ip.getWidth();
   int height = ip.getHeight();
   ip.setRoi(r);
   if (excludeEdgeParticles && polygon != null) {
     ImageStatistics stats = ImageStatistics.getStatistics(ip, MIN_MAX, null);
     if (fillColor >= stats.min && fillColor <= stats.max) {
       double replaceColor = level1 - 1.0;
       if (replaceColor < 0.0 || replaceColor == fillColor) {
         replaceColor = level2 + 1.0;
         int maxColor = imageType == BYTE ? 255 : 65535;
         if (replaceColor > maxColor || replaceColor == fillColor) {
           IJ.error("Particle Analyzer", "Unable to remove edge particles");
           return false;
         }
       }
       for (int y = minY; y < maxY; y++) {
         for (int x = minX; x < maxX; x++) {
           int v = ip.getPixel(x, y);
           if (v == fillColor) ip.putPixel(x, y, (int) replaceColor);
         }
       }
     }
   }
   ip.setValue(fillColor);
   if (mask != null) {
     mask = mask.duplicate();
     mask.invert();
     ip.fill(mask);
   }
   ip.setRoi(0, 0, r.x, height);
   ip.fill();
   ip.setRoi(r.x, 0, r.width, r.y);
   ip.fill();
   ip.setRoi(r.x, r.y + r.height, r.width, height - (r.y + r.height));
   ip.fill();
   ip.setRoi(r.x + r.width, 0, width - (r.x + r.width), height);
   ip.fill();
   ip.resetRoi();
   // IJ.log("erase: "+fillColor+"	"+level1+"	"+level2+"	"+excludeEdgeParticles);
   // (new ImagePlus("ip2", ip.duplicate())).show();
   return true;
 }
  public void drawInto(ImageProcessor ip, int value, int linewidth, GeneralPath path) {
    ip.setValue(value);
    ip.setLineWidth(linewidth);
    PathIterator it = path.getPathIterator(null);
    float[] seg = new float[6];
    int px = 0, py = 0;
    while (!it.isDone()) {
      int l = it.currentSegment(seg);
      int x = Math.round(seg[0]);
      int y = Math.round(seg[1]);
      double d = Math.sqrt((px - x) * (px - x) + (py - y) * (py - y));

      if (l == PathIterator.SEG_MOVETO || d > 15) ip.moveTo(x, y);
      else ip.lineTo(x, y);
      px = x;
      py = y;
      it.next();
    }
  }
  public ImageStack expandStack(ImageStack stackOld, int wNew, int hNew, int xOff, int yOff) {
    int nFrames = stackOld.getSize();
    ImageProcessor ipOld = stackOld.getProcessor(1);
    java.awt.Color colorBack = Toolbar.getBackgroundColor();

    ImageStack stackNew = new ImageStack(wNew, hNew, stackOld.getColorModel());
    ImageProcessor ipNew;

    for (int i = 1; i <= nFrames; i++) {
      IJ.showProgress((double) i / nFrames);
      ipNew = ipOld.createProcessor(wNew, hNew);
      if (zeroFill) ipNew.setValue(0.0);
      else ipNew.setColor(colorBack);
      ipNew.fill();
      ipNew.insert(stackOld.getProcessor(i), xOff, yOff);
      stackNew.addSlice(stackOld.getSliceLabel(i), ipNew);
    }
    return stackNew;
  }
Example #7
0
 void createMask(ImagePlus imp) {
   Roi roi = imp.getRoi();
   boolean useInvertingLut = Prefs.useInvertingLut;
   Prefs.useInvertingLut = false;
   boolean selectAll =
       roi != null
           && roi.getType() == Roi.RECTANGLE
           && roi.getBounds().width == imp.getWidth()
           && roi.getBounds().height == imp.getHeight()
           && imp.isThreshold();
   if (roi == null || !(roi.isArea() || roi.getType() == Roi.POINT) || selectAll) {
     createMaskFromThreshold(imp);
     Prefs.useInvertingLut = useInvertingLut;
     return;
   }
   ImagePlus maskImp = null;
   Frame frame = WindowManager.getFrame("Mask");
   if (frame != null && (frame instanceof ImageWindow))
     maskImp = ((ImageWindow) frame).getImagePlus();
   if (maskImp == null) {
     ImageProcessor ip = new ByteProcessor(imp.getWidth(), imp.getHeight());
     if (!Prefs.blackBackground) ip.invertLut();
     maskImp = new ImagePlus("Mask", ip);
     maskImp.show();
   }
   ImageProcessor ip = maskImp.getProcessor();
   ip.setRoi(roi);
   ip.setValue(255);
   ip.fill(ip.getMask());
   Calibration cal = imp.getCalibration();
   if (cal.scaled()) {
     Calibration cal2 = maskImp.getCalibration();
     cal2.pixelWidth = cal.pixelWidth;
     cal2.pixelHeight = cal.pixelHeight;
     cal2.setUnit(cal.getUnit());
   }
   maskImp.updateAndRepaintWindow();
   Prefs.useInvertingLut = useInvertingLut;
 }
  public void run(ImageProcessor ip) {
    int w = ip.getWidth(); // Get width of image
    int h = ip.getHeight(); // Get height of image

    /**
     * ------------------------------------------------------------------ BEGIN PRELIMINARY STEPS
     * ------------------------------------------------------------------*
     */

    /**
     * ----------------------------------- BEGIN: CREATE IMAGES THAT WILL BE USED IN COMPUTATIONS
     * -----------------------------------*
     */
    // Create the smoothed image to be used for the different edge images
    ImageProcessor Smoothed_Ip_xf = new FloatProcessor(w, h);
    ImageProcessor Smoothed_Ip_yf = new FloatProcessor(w, h);
    ImageProcessor Smoothed_Ip_45f = new FloatProcessor(w, h);
    ImageProcessor Smoothed_Ip_135f = new FloatProcessor(w, h);

    // Create the edge image detecting vertical edges
    ImageProcessor G_xf_Ip = new FloatProcessor(w, h);

    // Create the edge image detecting horizontal edges
    ImageProcessor G_yf_Ip = new FloatProcessor(w, h);

    // Create the edge image detecting 135 degree edges
    ImageProcessor G_45f_Ip = new FloatProcessor(w, h);

    // Create the edge image detecting 45 degree edges
    ImageProcessor G_135f_Ip = new FloatProcessor(w, h);

    // Create the gradient magnitude image
    ImageProcessor GMag_Ip = new ByteProcessor(w, h); // Byte version
    ImageProcessor GMagf_Ip = new FloatProcessor(w, h); // Floating point version

    // Create the gradient direction image
    ImageProcessor GDir_Ip = new ByteProcessor(w, h); // Byte version
    ImageProcessor GDirf_Ip = new FloatProcessor(w, h); // Floating point version

    // Create the edge image (output from non-maximal suppression)
    ImageProcessor Edge_Ip = new ByteProcessor(w, h); // Byte Version
    Edge_Ip.setValue(0); // 0 = Black
    Edge_Ip.fill(); // Fill Edge_Ip all black

    ImageProcessor Copy_Edge_Ip = new ByteProcessor(w, h); // Byte Version
    Copy_Edge_Ip.setValue(0); // 0 = Black
    Copy_Edge_Ip.fill(); // Fill Edge_Ip all black

    ImageProcessor Edgef_Ip = new FloatProcessor(w, h); // Floating Point version
    Edgef_Ip.setValue(0); // 0 = Black
    Edgef_Ip.fill(); // Fill Edge_Ip all black

    ImageProcessor Copy_Edgef_Ip = new FloatProcessor(w, h); // Floating Point version
    Copy_Edgef_Ip.setValue(0); // 0 = Black
    Copy_Edgef_Ip.fill(); // Fill Edge_Ip all black

    // Create the Threshold with Hysteresis image
    ImageProcessor Threshold_Ip = new ByteProcessor(w, h);
    Threshold_Ip.setValue(0); // 0 = Black
    Threshold_Ip.fill(); // Fill Edge_Ip all black
    /**
     * ----------------------------------- END: CREATE IMAGES THAT WILL BE USED IN COMPUTATIONS
     * -----------------------------------*
     */

    /**
     * ----------------------------------------------------- BEGIN: USER INPUT
     * -----------------------------------------------------*
     */
    double STDDev = 0,
        TLow = 0,
        THigh =
            0; // Declare and initialize variables for the standard deviation, low threshold, and
               // high threshold
    int Size = 0; // Initialize size of Gaussian Filter
    boolean EdgeStrengthImage; // specifies whether edge strength image should be shown
    GenericDialog gd = new GenericDialog("User Inputs");
    gd.addNumericField(
        "Size of Gaussian Filter (Odd Integer)", Size, 0); // Field for Size of Gaussian Filter
    gd.addNumericField("Standard Deviation", STDDev, 0); // Field for Standard Deviation
    gd.addNumericField("Low Threshold (1 - 255)", TLow, 0); // Field for Low Threshold
    gd.addNumericField("High Threshold (1 - 255)", THigh, 0); // Field for High Threshold
    gd.showDialog();
    if (gd.wasCanceled()) {
      return;
    } else {
      Size =
          (int)
              gd
                  .getNextNumber(); // Set Size variable from user input. This allows the user to
                                    // set the size of the Gaussian Filter
      STDDev = gd.getNextNumber(); // Set STDDev variable from user input
      TLow = gd.getNextNumber(); // Set TLow variable from user input
      THigh = gd.getNextNumber(); // Set THigh variable from user input
    }

    /**
     * ----------------------------------------------------- END: USER INPUT
     * -----------------------------------------------------*
     */

    /**
     * ------------------------------------------------------------------ END PRELIMINARY STEPS
     * ------------------------------------------------------------------*
     */

    /**
     * ------------------------------------------------------------------ BEGIN CANNY EDGE DETECTION
     * ------------------------------------------------------------------*
     */

    /**
     * ----------------------------------------------------- BEGIN STEP 1: NOISE REDUCTION VIA
     * GAUSSIAN ----------------------------------------------------- *
     */
    ImageProcessor ipf = ip.convertToFloat(); // Convert original image to floating point
    int SizeSquared = (int) Math.pow(Size, 2); // Square the size of Gaussian Kernel
    int HalfSize = (Size - 1) / 2; // Cut the size of the Gaussian Kernel almost in half
    float pix; // Temporary storage to be used throughout code to store pixel values
    float[] GaussianFilter =
        new float[SizeSquared]; // Initialize Gaussian Filter to be used in the convolution
    double[] GaussianFilterD =
        new double
            [SizeSquared]; // Initialize Gaussian Filter to be used...this filter will be composed
                           // of numbers of type double
    double Constant = 1 / (2 * Math.PI * Math.pow(STDDev, 2)); // Compute 1/(2*pi*sigma^2)
    double ExponentDenom = 2 * Math.pow(STDDev, 2); // Compute 2*sigma^2
    double Value; // Temporary storage to store the computations that will form the Gaussian Filter

    // FOR LOOP TO FORM GaussianFilterD
    for (int i = 0; i < Size; i++) {
      for (int j = 0; j < Size; j++) {
        Value =
            Math.exp(
                -1
                    * (Math.pow(j - HalfSize, 2) + Math.pow(i - HalfSize, 2))
                    / (ExponentDenom)); // Set Value = e^(-(i^2 + j^2)/(2*sigma^2))
        GaussianFilterD[Size * i + j] = Constant * Value; // Place Value in GaussianFilterD
      }
    }

    // FOR LOOP TO FORM GaussianFilter using GaussianFilterD
    for (int i = 0; i < Math.pow(Size, 2); i++) {
      GaussianFilter[i] = (float) GaussianFilterD[i]; // Convert double values to float one by one
    }

    // CONVOLVE IMAGE WITH GAUSSIAN
    Convolver cv = new Convolver(); // Create the convolver
    cv.setNormalize(true); // Normalize the filter
    cv.convolve(
        ipf,
        GaussianFilter,
        Size,
        Size); // Apply the GaussianFilter using convolution on the image ipf

    // For loop to create 4 smoothed images to be used in detecting the 4 edges: 0 deg, 45 deg, 90
    // deg, 135 deg
    for (int i = 0; i < w; i++) {
      for (int j = 0; j < h; j++) {
        pix = ipf.getf(i, j);
        Smoothed_Ip_xf.setf(
            i, j,
            pix); // Smoothed image to convolve with Sobel filter in x-direction (detects vertical
                  // edges)
        Smoothed_Ip_yf.setf(
            i, j,
            pix); // Smoothed image to convolve with Sobel filter in y-direction (detects horizontal
                  // edges)
        Smoothed_Ip_45f.setf(
            i, j,
            pix); // Smoothed image to convolve with Sobel filter in 45 degree direction (detects
                  // 135 degree edges)
        Smoothed_Ip_135f.setf(
            i, j,
            pix); // Smoothed image to convolve with Sobel filter in 135 degree direction (detects
                  // 45 degree edges)
      }
    }

    /**
     * ----------------------------------------------------- END STEP 1: NOISE REDUCTION VIA
     * GAUSSIAN ----------------------------------------------------- *
     */

    /**
     * ----------------------------------------------------- BEGIN STEP 2: COMPUTE GRADIENT
     * MAGNITUDE AND DIRECTION IMAGES ----------------------------------------------------- *
     */

    // Sobel Filters to detect edges
    float[] G_x = {-1, 0, 1, -2, 0, 2, -1, 0, 1}; // Sobel operator in x direction
    float[] G_y = {-1, -2, -1, 0, 0, 0, 1, 2, 1}; // Sobel operator in y direction
    float[] G_135 = {2, 1, 0, 1, 0, -1, 0, -1, -2}; // Sobel operator in 135 degree direction
    float[] G_45 = {0, 1, 2, -1, 0, 1, -2, -1, 0}; // Sobel operator in 45 degree direction

    // CONVOLVE IMAGE USING SOBEL FILTERS
    cv.convolve(
        Smoothed_Ip_xf,
        G_x,
        3,
        3); // Apply the Sobel filter in x-direction using convolution on the smoothed image
    cv.convolve(
        Smoothed_Ip_yf,
        G_y,
        3,
        3); // Apply the Sobel filter in y-direction using convolution on the smoothed image
    cv.convolve(
        Smoothed_Ip_45f,
        G_45,
        3,
        3); // Apply the Sobel filter in 45 degree direction using convolution on the smoothed image
    cv.convolve(
        Smoothed_Ip_135f,
        G_135,
        3,
        3); // Apply the Sobel filter in 135 degree direction using convolution on the smoothed
            // image

    // For loop to define the 4 floating point images G_xf_Ip, G_yf_Ip, G_45f_Ip, G_135f_Ip
    for (int i = 0; i < w; i++) {
      for (int j = 0; j < h; j++) {
        pix = Smoothed_Ip_xf.getf(i, j);
        G_xf_Ip.setf(i, j, pix);
        pix = Smoothed_Ip_yf.getf(i, j);
        G_yf_Ip.setf(i, j, pix);
        pix = Smoothed_Ip_45f.getf(i, j);
        G_45f_Ip.setf(i, j, pix);
        pix = Smoothed_Ip_135f.getf(i, j);
        G_135f_Ip.setf(i, j, pix);
      }
    }

    // COMPUTE THE GRADIENT MAGNITUDE IMAGE GMagf_Ip
    float pix1, pix2; // Variables to store pixel values of G_xf_Ip and G_yf_Ip
    for (int i = 0; i < w; i++) {
      for (int j = 0; j < h; j++) {
        pix1 = G_xf_Ip.getf(i, j); // Get pixel value
        pix2 = G_yf_Ip.getf(i, j); // Get Pixel Value
        pix =
            (float)
                Math.sqrt(
                    pix1 * pix1
                        + pix2
                            * pix2); // Take the square root of the sum of the squares of both pixel
                                     // values
        GMagf_Ip.setf(
            i, j, 255 * pix); // Place in (i,j) position of GMagf_Ip and scale by 255 to view
      }
    }

    // COMPUTE THE GRADIENT DIRECTION IMAGE
    double v1, v2, v3, v4; // Temporary storage for the abs. value of pixel vlaues
    int pixi; // Temporary storage for pixel values
    double[][] GDir =
        new double[h]
            [w]; // Define 2D Array to store gradient direction values. Will use this during Step 3
                 // Non-Maximal Suppression instead of the gradient direction image

    // For loop to compute gradient direction image
    for (int i = 0; i < w; i++) {
      for (int j = 0; j < h; j++) {
        // Obtain the absolute value of each pixel in the following four floating point images
        v1 = Math.abs(G_xf_Ip.getf(i, j));
        v2 = Math.abs(G_yf_Ip.getf(i, j));
        v3 = Math.abs(G_45f_Ip.getf(i, j));
        v4 = Math.abs(G_135f_Ip.getf(i, j));

        // If statements to find the maximal response (strongest edge direction)

        if (v1 > v2 && v1 > v3 && v1 > v4) // Vertical edge orientation
        {
          GDir_Ip.putPixel(i, j, 0);
          GDir[j][i] = 0; // 0 stands for vertical edge orientation
        } else if (v3 > v1 && v3 > v2 && v3 > v4) // 45 degree edge orientation
        {
          GDir_Ip.putPixel(i, j, 1);
          GDir[j][i] = 1; // 1 stands for 45 degree edge orientation
        } else if (v2 > v1 && v2 > v3 && v2 > v4) // Horizontal edge orientation
        {
          GDir_Ip.putPixel(i, j, 2);
          GDir[j][i] = 2; // 2 stands for horizontal edge orientation
        } else if (v4 > v1 && v4 > v2 && v4 > v3) // 135 degree edge orientation
        {
          GDir_Ip.putPixel(i, j, 3);
          GDir[j][i] = 3; // 3 stands for 135 degree edge orientation
        }

        pixi =
            (int) (255.0 / 3.0)
                * GDir_Ip.getPixel(
                    i, j); // Scale the gradient direction image so we can actually see it
        GDir_Ip.putPixel(
            i, j,
            pixi); // Place pixi in the (i,j) location of GDir_Ip...we can now view the gradient
                   // direction image
      }
    }

    // OUTPUT GRADIENT DIRECTION IMAGE
    String DirTitle = "Gradient Direction";
    ImagePlus GDir_Im = new ImagePlus(DirTitle, GDir_Ip);
    GDir_Im.show();

    // OUTPUT GRADIENT MAGNITUDE IMAGE (need to convert to ByteProcessor to view first)
    GMagf_Ip.resetMinAndMax();
    GMag_Ip.insert(GMagf_Ip.convertToByte(true), 0, 0);

    String MagTitle = "Gradient Magnitude";
    ImagePlus GMag_Im = new ImagePlus(MagTitle, GMag_Ip);
    GMag_Im.show();

    /**
     * ----------------------------------------------------- BEGIN STEP 2: COMPUTE GRADIENT
     * MAGNITUDE AND DIRECTION IMAGES ----------------------------------------------------- *
     */

    /**
     * ----------------------------------------------------- BEGIN STEP 3: NON-MAXIMAL SUPPRESSION
     * -----------------------------------------------------*
     */
    // Ignoring boundaries for convenience
    float Magnitude; // Storage for magnitude
    double Direction; // Storage for direction

    // FOR LOOP TO COMPUTE NON-MAXIMAL SUPPRESSION IMAGE
    for (int i = 1; i < w - 1; i++) {
      for (int j = 1; j < h - 1; j++) {
        Magnitude =
            GMagf_Ip.getf(
                i, j); // Get the magnitude in the (i,j) position of the gradient magnitude image

        if (Magnitude != 0) // If the magnitude is non-zero then we find the direction of the edge
        {
          Direction = GDir[j][i]; // Obtain the direction
          float n1GradMag = 0,
              n2GradMag = 0; // Initialize storage for the magnitude of the 2 neighboring pixels

          if (Direction
              == 0) // If Direction is 0 get the magnitude in the columns to the left and right
          {
            n1GradMag = GMagf_Ip.getf(i - 1, j);
            n2GradMag = GMagf_Ip.getf(i + 1, j);
          } else if (Direction
              == 1) // If Direction is 45 degree get the magnitude in adjacent pixels
          {
            n1GradMag = GMagf_Ip.getf(i + 1, j - 1);
            n2GradMag = GMagf_Ip.getf(i - 1, j + 1);
          } else if (Direction
              == 2) // If Direction is 2 get the magnitude in the rows above and below
          {
            n1GradMag = GMagf_Ip.getf(i, j - 1);
            n2GradMag = GMagf_Ip.getf(i, j + 1);
          } else if (Direction
              == 3) // If Direction is 135 degrees get the magnitude in adjacent pixels
          {
            n1GradMag = GMagf_Ip.getf(i - 1, j - 1);
            n2GradMag = GMagf_Ip.getf(i + 1, j + 1);
          }

          if (Magnitude > n1GradMag
              && Magnitude
                  > n2GradMag) // Check to see if the magnitude of pixel under inspection is the
                               // largest relative to its adjacent pixels
          {
            pix = GMagf_Ip.getf(i, j); // Store magnitude in (i,j) position in pix variable
            Edgef_Ip.setf(
                i, j,
                pix); // Place this pixel value in the (i,j) position of Edge Image (Edge Image is
                      // output from non-maximal suppression)
            Copy_Edgef_Ip.setf(
                i, j,
                pix); // Place this pixel value in the (i,j) position of Copy Edge Image (this will
                      // be used for thresholding with hysteresis)

          } else {
          } // Else do nothing since edge image was already filled with 0's at beginning of code
        } else {
        } // Do nothing if magnitude is 0
      }
    }

    Copy_Edgef_Ip.resetMinAndMax();
    Copy_Edge_Ip.insert(Copy_Edgef_Ip.convertToByte(true), 0, 0);

    // Display edge magnitude image but we need to convert to ByteProcessor first
    Edgef_Ip.resetMinAndMax();
    Edge_Ip.insert(Edgef_Ip.convertToByte(true), 0, 0);

    String cTitle = "Non-Maximal Suppression";
    ImagePlus Edge_Im = new ImagePlus(cTitle, Edge_Ip);
    Edge_Im.show();
    /**
     * ----------------------------------------------------- END STEP 3: NON-MAXIMAL SUPPRESSION
     * -----------------------------------------------------*
     */

    /**
     * ----------------------------------------------------- BEGIN STEP 4: THRESHOLDING WITH
     * HYSTERESIS -----------------------------------------------------*
     */
    int count = 0; // Initialize counter to be used in while loop
    int IterationCount = 500; // Number of iterations to perform

    // Scan through all pixels in the edge image and mark all edges with magnitude above the high
    // threshold as a true edge otherwise if the magnitude is below the low threshold
    // then we delete that pixel (set it to zero)
    for (int i = 0; i < w; i++) {
      for (int j = 0; j < h; j++) {
        Magnitude = Copy_Edge_Ip.getPixel(i, j); // Obtain magnitude in edge image

        if (Magnitude
            > THigh) // If Magnitude is larger than the high threshold then make pixel white
        {
          Threshold_Ip.putPixel(i, j, 255); // True edge (Updates Threshold image to be output)
          Copy_Edge_Ip.putPixel(
              i, j,
              255); // True edge (Updates edge image which will be used in the iterative
                    // thresholding)
        } else if (Magnitude
            < TLow) // Else if magnitude is below the low threshold then make pixel black
        {
          Threshold_Ip.putPixel(i, j, 0); // Not an edge (Updates Threshold image to be output)
          Copy_Edge_Ip.putPixel(
              i, j,
              0); // Not an edge (Updates edge image which will be used in the iterative
                  // thresholding)
        }
      }
    }

    while (count < IterationCount) // Iterate again and again
    {
      // Ignore boundary pixels for convenience
      for (int i = 1; i < w - 1; i++) {
        for (int j = 1; j < h - 1; j++) {
          Magnitude = Copy_Edge_Ip.getPixel(i, j); // Obtain magnitude in edge image
          if (Magnitude == 255) // If we reach a true edge then we look at its 8 neighbors
          {
            // Obtain the magnitude in the 8 neighbors
            int n1, n2, n3, n4, n5, n6, n7, n8;
            n1 = Copy_Edge_Ip.getPixel(i - 1, j);
            n2 = Copy_Edge_Ip.getPixel(i - 1, j - 1);
            n3 = Copy_Edge_Ip.getPixel(i, j - 1);
            n4 = Copy_Edge_Ip.getPixel(i + 1, j - 1);
            n5 = Copy_Edge_Ip.getPixel(i + 1, j);
            n6 = Copy_Edge_Ip.getPixel(i + 1, j + 1);
            n7 = Copy_Edge_Ip.getPixel(i, j + 1);
            n8 = Copy_Edge_Ip.getPixel(i - 1, j + 1);
            if (n1
                >= TLow) // If n1 is greater than or equal to the low threshold then we mark it as
                         // an edge
            {
              Copy_Edge_Ip.putPixel(i - 1, j, 255); // Update edge image
              Threshold_Ip.putPixel(
                  i - 1, j,
                  255); // Update threshold image (This is the output image from thresholding with
                        // hysteresis)
            }
            if (n2
                >= TLow) // If n2 is greater than or equal to the low threshold then we mark it as
                         // an edge
            {
              Copy_Edge_Ip.putPixel(i - 1, j - 1, 255); // Update edge image
              Threshold_Ip.putPixel(
                  i - 1, j - 1,
                  255); // Update threshold image (This is the output image from thresholding with
                        // hysteresis)
            }
            if (n3
                >= TLow) // If n3 is greater than or equal to the low threshold then we mark it as
                         // an edge
            {
              Copy_Edge_Ip.putPixel(i, j - 1, 255); // Update edge image
              Threshold_Ip.putPixel(
                  i, j - 1,
                  255); // Update threshold image (This is the output image from thresholding with
                        // hysteresis)
            }
            if (n4
                >= TLow) // If n4 is greater than or equal to the low threshold then we mark it as
                         // an edge
            {
              Copy_Edge_Ip.putPixel(i + 1, j - 1, 255); // Update edge image
              Threshold_Ip.putPixel(
                  i + 1, j - 1,
                  255); // Update threshold image (This is the output image from thresholding with
                        // hysteresis)
            }
            if (n5
                >= TLow) // If n5 is greater than or equal to the low threshold then we mark it as
                         // an edge
            {
              Copy_Edge_Ip.putPixel(i + 1, j, 255); // Update edge image
              Threshold_Ip.putPixel(
                  i + 1, j,
                  255); // Update threshold image (This is the output image from thresholding with
                        // hysteresis)
            }
            if (n6
                >= TLow) // If n6 is greater than or equal to the low threshold then we mark it as
                         // an edge
            {
              Copy_Edge_Ip.putPixel(i + 1, j + 1, 255); // Update edge image
              Threshold_Ip.putPixel(
                  i + 1, j + 1,
                  255); // Update threshold image (This is the output image from thresholding with
                        // hysteresis)
            }
            if (n7
                >= TLow) // If n7 is greater than or equal to the low threshold then we mark it as
                         // an edge
            {
              Copy_Edge_Ip.putPixel(i, j + 1, 255); // Update edge image
              Threshold_Ip.putPixel(
                  i, j + 1,
                  255); // Update threshold image (This is the output image from thresholding with
                        // hysteresis)
            }
            if (n8
                >= TLow) // If n8 is greater than or equal to the low threshold then we mark it as
                         // an edge
            {
              Copy_Edge_Ip.putPixel(i - 1, j + 1, 255); // Update edge image
              Threshold_Ip.putPixel(
                  i - 1, j + 1,
                  255); // Update threshold image (This is the output image from thresholding with
                        // hysteresis)
            }
          }
        }
      }
      count++; // Update counter and continue iterations
    }

    // Display the threshold with hysteresis image.
    String ThreshTitle = "Threshold with Hysteresis";
    ImagePlus Threshold_Im = new ImagePlus(ThreshTitle, Threshold_Ip);
    Threshold_Im.show();
    /**
     * ----------------------------------------------------- END STEP 4: THRESHOLDING WITH
     * HYSTERESIS -----------------------------------------------------*
     */

    /**
     * ------------------------------------------------------------------ END CANNY EDGE DETECTION
     * ------------------------------------------------------------------*
     */
  }
  @Override
  public Grid2D applyToolToImage(Grid2D imageProcessor) {
    FloatProcessor imp = new FloatProcessor(imageProcessor.getWidth(), imageProcessor.getHeight());
    imp.setPixels(imageProcessor.getBuffer());

    if (!initBead) initializeBead();
    ImageProcessor imp1 = imp.duplicate(); // original

    double[][] beadMean3D = config.getBeadMeanPosition3D(); // [beadNo][x,y,z]
    double[] uv = new double[1];

    SimpleMatrix pMatrix = config.getGeometry().getProjectionMatrix(imageIndex).computeP();
    // [projection #][bead #][u, v, state[0: initial, 1: registered, 2: updated by hough searching]]
    double[][][] beadPosition2D = config.getBeadPosition2D();

    int noBeadRegistered = 0;

    double[][] xy1 = new double[WeightBearingBeadPositionBuilder.beadNo][2]; // original
    double[][] xy2 =
        new double[WeightBearingBeadPositionBuilder.beadNo]
            [2]; // warped	(mapped to the mean), control points, reference

    double[][] xy1_hat = new double[WeightBearingBeadPositionBuilder.beadNo][2]; // original
    double[][] xy2_hat = new double[WeightBearingBeadPositionBuilder.beadNo][2]; // original

    // double distanceReferenceToCurrentBead = 0;

    for (int i = WeightBearingBeadPositionBuilder.currentBeadNo; i >= 0; i--) {

      if (beadMean3D[i][0] != 0
          || beadMean3D[i][1] != 0
          || beadMean3D[i][2] != 0) { // assume bead 3d is registered.

        uv = compute2DCoordinates(beadMean3D[i], pMatrix);

        // find bead location if registered by txt: state 1
        if (beadPosition2D[imageIndex][i][2] == 1) {

          noBeadRegistered++;

          if (isDisplay) {
            imp1.setValue(2);
            imp1.drawLine(
                (int) Math.round(beadPosition2D[imageIndex][i][0] - 10),
                (int) Math.round(beadPosition2D[imageIndex][i][1] - 10),
                (int) Math.round(beadPosition2D[imageIndex][i][0] + 10),
                (int) Math.round(beadPosition2D[imageIndex][i][1] + 10));
            imp1.drawLine(
                (int) Math.round(beadPosition2D[imageIndex][i][0] - 10),
                (int) Math.round(beadPosition2D[imageIndex][i][1] + 10),
                (int) Math.round(beadPosition2D[imageIndex][i][0] + 10),
                (int) Math.round(beadPosition2D[imageIndex][i][1] - 10));
            imp1.drawString(
                "Bead " + i + " (state:" + (int) beadPosition2D[imageIndex][i][2] + ")",
                (int) beadPosition2D[imageIndex][i][0],
                (int) beadPosition2D[imageIndex][i][1] - 10);
          }

          xy1[noBeadRegistered - 1][0] = beadPosition2D[imageIndex][i][0];
          xy1[noBeadRegistered - 1][1] = beadPosition2D[imageIndex][i][1];

          xy2[noBeadRegistered - 1][0] = uv[0];
          xy2[noBeadRegistered - 1][1] = uv[1];

        } else if (imageIndex != 0
            && imageIndex != config.getGeometry().getNumProjectionMatrices() - 1) {

          if (beadPosition2D[imageIndex - 1][i][2] == 1
              && beadPosition2D[imageIndex + 1][i][2] == 1) {

            noBeadRegistered++;

            double xMean =
                (beadPosition2D[imageIndex - 1][i][0] + beadPosition2D[imageIndex - 1][i][0]) / 2;
            double yMean =
                (beadPosition2D[imageIndex + 1][i][1] + beadPosition2D[imageIndex + 1][i][1]) / 2;

            if (isDisplay) {
              imp1.setValue(2);
              imp1.drawLine(
                  (int) Math.round(xMean - 10),
                  (int) Math.round(yMean - 10),
                  (int) Math.round(xMean + 10),
                  (int) Math.round(yMean + 10));
              imp1.drawLine(
                  (int) Math.round(xMean - 10),
                  (int) Math.round(yMean + 10),
                  (int) Math.round(xMean + 10),
                  (int) Math.round(yMean - 10));
              imp1.drawString("Bead " + i + " (state:" + "M)", (int) xMean, (int) yMean - 10);
            }

            xy1[noBeadRegistered - 1][0] = xMean;
            xy1[noBeadRegistered - 1][1] = yMean;

            xy2[noBeadRegistered - 1][0] = uv[0];
            xy2[noBeadRegistered - 1][1] = uv[1];
          }
        }

        // mean projected bead
        //				imp1.drawLine((int) Math.round(uv[0]-10), (int) Math.round(uv[1]), (int)
        // Math.round(uv[0]+10), (int) Math.round(uv[1]));
        //				imp1.drawLine((int) Math.round(uv[0]), (int) Math.round(uv[1]-10), (int)
        // Math.round(uv[0]), (int) Math.round(uv[1]+10));
      }
    }

    if (isDisplay) {
      for (int x = 0; x < config.getGeometry().getDetectorWidth(); x += 50)
        imp1.drawLine(x, 0, x, config.getGeometry().getDetectorHeight());
      for (int y = 0; y < config.getGeometry().getDetectorHeight(); y += 50)
        imp1.drawLine(0, y, config.getGeometry().getDetectorWidth(), y);
    }

    if (isCornerIncluded) {
      xy1[noBeadRegistered + 0][0] = 0;
      xy1[noBeadRegistered + 0][1] = 0;
      xy2[noBeadRegistered + 0][0] = 0;
      xy2[noBeadRegistered + 0][1] = 0;

      xy1[noBeadRegistered + 1][0] = 0;
      xy1[noBeadRegistered + 1][1] = config.getGeometry().getDetectorHeight();
      xy2[noBeadRegistered + 1][0] = 0;
      xy2[noBeadRegistered + 1][1] = config.getGeometry().getDetectorHeight();

      xy1[noBeadRegistered + 2][0] = config.getGeometry().getDetectorWidth();
      xy1[noBeadRegistered + 2][1] = 0;
      xy2[noBeadRegistered + 2][0] = config.getGeometry().getDetectorWidth();
      xy2[noBeadRegistered + 2][1] = 0;

      xy1[noBeadRegistered + 3][0] = config.getGeometry().getDetectorWidth();
      xy1[noBeadRegistered + 3][1] = config.getGeometry().getDetectorHeight();
      xy2[noBeadRegistered + 3][0] = config.getGeometry().getDetectorWidth();
      xy2[noBeadRegistered + 3][1] = config.getGeometry().getDetectorHeight();

      noBeadRegistered = noBeadRegistered + 4;
    }

    boolean fScaling = true;

    double minX = Double.MAX_VALUE;
    double maxX = 0;
    double minY = Double.MAX_VALUE;
    double maxY = 0;
    double c = 0;
    if (fScaling) { // ----- scaling to reduce condition # of A matrix
      for (int i = 0; i < noBeadRegistered; i++) {
        minX = Math.min(minX, xy1[i][0]);
        maxX = Math.max(maxX, xy1[i][0]);
        minY = Math.min(minY, xy1[i][1]);
        maxY = Math.max(maxY, xy1[i][1]);
      }
      c = Math.max(maxX - minX, maxY - minY);

      for (int i = 0; i < noBeadRegistered; i++) {
        xy1_hat[i][0] = (xy1[i][0] - minX) / c;
        xy1_hat[i][1] = (xy1[i][1] - minY) / c;

        xy2_hat[i][0] = (xy2[i][0] - minX) / c;
        xy2_hat[i][1] = (xy2[i][1] - minY) / c;
      }
    } else {
      xy1_hat = xy1;
      xy2_hat = xy2;
    }

    ImageProcessor imp2 = imp1.duplicate(); // warped

    /*
     * A*x = b
     * Matrix A = (n + 3) * (n + 3);
     * n (noBeadRegistered + 4): # of control points + 4 corner points (assume corner points are static)
     */

    int n = noBeadRegistered + 3;

    SimpleMatrix A = new SimpleMatrix(n, n);
    SimpleVector x_x = new SimpleVector(n);
    SimpleVector x_y = new SimpleVector(n);
    SimpleVector b_x = new SimpleVector(n);
    SimpleVector b_y = new SimpleVector(n);

    double rij = 0;
    double valA = 0;
    double valb_x = 0;
    double valb_y = 0;

    // Matrix L formation
    // alpha: mean of distances between control points' xy-projections) is a constant only present
    // on the diagonal of K
    // lambda: TPS smoothing regularization coefficient

    double alpha = 0.0;
    double lambda = 1.6; // 1.6
    for (int i = 0; i < noBeadRegistered; i++) { // i= # of row
      for (int j = i; j < noBeadRegistered; j++) { // j= # of column
        alpha +=
            Math.sqrt(
                Math.pow(xy2_hat[i][0] - xy2_hat[j][0], 2)
                    + Math.pow(xy2_hat[i][1] - xy2_hat[j][1], 2));
      }
    }
    alpha = alpha / Math.pow(noBeadRegistered, 2);

    for (int i = 0; i < n; i++) { // i= # of row
      for (int j = i; j < n; j++) { // j= # of column
        if (i < 3 && j < 3) valA = 0;
        else if (i >= 3 && j >= 3 && i == j) {
          valA = Math.pow(alpha, 2) * lambda;
          // valA = lambda;
          if (imageIndex < 10)
            System.out.println("Regularization = " + valA + ", lambda= " + lambda);
        } else if (i == 0 && j >= 0) valA = 1;
        else if (i == 1 && j >= 3) valA = xy1_hat[j - 3][0];
        else if (i == 2 && j >= 3) valA = xy1_hat[j - 3][1];
        else {
          rij =
              Math.pow(xy1_hat[j - 3][0] - xy1_hat[i - 3][0], 2)
                  + Math.pow(xy1_hat[j - 3][1] - xy1_hat[i - 3][1], 2);
          if (rij == 0) valA = 0;
          else valA = rij * Math.log(rij);
        }

        A.setElementValue(i, j, valA);
        A.setElementValue(j, i, valA);
      }

      if (i < 3) {
        valb_x = 0;
        valb_y = 0;
      } else {
        //				valb_x = xy2_hat[i-3][0]-xy1_hat[i-3][0];
        //				valb_y = xy2_hat[i-3][1]-xy1_hat[i-3][1];
        valb_x = xy2[i - 3][0] - xy1[i - 3][0];
        valb_y = xy2[i - 3][1] - xy1[i - 3][1];
        //				if (imageIndex > 150 && imageIndex < 170)
        //					System.out.println("Idx" + imageIndex + ",Elevation" + (i-3) + ": " + valb_x + "---"
        // + valb_y);
      }

      b_x.setElementValue(i, valb_x);
      b_y.setElementValue(i, valb_y);
    }

    // System.out.println("A condition number=" + A.conditionNumber(MatrixNormType.MAT_NORM_L1));
    // System.out.println("A condition number=" + A.conditionNumber(MatrixNormType.MAT_NORM_LINF));

    x_x = Solvers.solveLinearSysytemOfEquations(A, b_x);
    x_y = Solvers.solveLinearSysytemOfEquations(A, b_y);

    if (fScaling) {
      // ----- pixel space coefficients a, b scaling back
      double tmpA0 =
          x_x.getElement(0) - x_x.getElement(1) * (minX / c) - x_x.getElement(2) * (minY / c);
      for (int j = 0; j < noBeadRegistered; j++) {
        tmpA0 -=
            Math.log(c)
                * 2
                * x_x.getElement(j + 3)
                * (Math.pow(xy1_hat[j][0], 2) + Math.pow(xy1_hat[j][1], 2));
      }
      x_x.setElementValue(0, tmpA0);
      tmpA0 = x_y.getElement(0) - x_y.getElement(1) * (minX / c) - x_y.getElement(2) * (minY / c);
      for (int j = 0; j < noBeadRegistered; j++) {
        tmpA0 -=
            Math.log(c)
                * 2
                * x_y.getElement(j + 3)
                * (Math.pow(xy1_hat[j][0], 2) + Math.pow(xy1_hat[j][1], 2));
      }
      x_y.setElementValue(0, tmpA0);

      x_x.setElementValue(1, x_x.getElement(1) / c);
      x_y.setElementValue(1, x_y.getElement(1) / c);
      x_x.setElementValue(2, x_x.getElement(2) / c);
      x_y.setElementValue(2, x_y.getElement(2) / c);

      for (int i = 3; i < n; i++) {
        x_x.setElementValue(i, x_x.getElement(i) / Math.pow(c, 2));
        x_y.setElementValue(i, x_y.getElement(i) / Math.pow(c, 2));
      }
      // ----- pixel space coefficients a, b scaling back end
    }

    double devU = 0;
    double devV = 0;
    // Do warping
    // if (imageIndex == 0) {
    for (int y = 0; y < config.getGeometry().getDetectorHeight(); y++) {
      // for (int y=252; y<253; y++) {
      for (int x = 0; x < config.getGeometry().getDetectorWidth(); x++) {
        // for (int x=606; x<607; x++) {
        devU = x_x.getElement(0) + x_x.getElement(1) * x + x_x.getElement(2) * y;
        devV = x_y.getElement(0) + x_y.getElement(1) * x + x_y.getElement(2) * y;
        for (int i = 0; i < noBeadRegistered; i++) {
          rij = Math.pow(xy1[i][0] - x, 2) + Math.pow(xy1[i][1] - y, 2);
          if (rij > 0) {
            devU += x_x.getElement(i + 3) * rij * Math.log(rij);
            devV += x_y.getElement(i + 3) * rij * Math.log(rij);
          }
        }

        //					devU = 0;
        //					devV = 0;

        imp2.setf(x, y, (float) imp1.getInterpolatedValue(x - devU, y - devV));

        // System.out.println("x, y=" + x + ", " + y + "\t" + devU + ", " + devV);
        // maxDevU = Math.max(maxDevU, devU);
        // maxDevV = Math.max(maxDevV, devV);
      }
    }

    // Error estimate after transformation
    //			for (int i=0; i<= WeightBearingBeadPositionBuilder.currentBeadNo; i++){
    //
    //				if (beadMean3D[i][0] != 0 || beadMean3D[i][1] != 0 || beadMean3D[i][2] != 0){ // assume
    // bead 3d is registered.
    //
    //					// find bead location if registered by txt: state 1
    //					if (beadPosition2D[imageIndex][i][2] == 1){
    //
    //						// Projected Reference
    //						uv = compute2DCoordinates(beadMean3D[i], pMatrix);
    //						double x = uv[0];
    //						double y = uv[1];
    //						// bead detected position in 2d
    //						// Transform to 2D coordinates, time variant position
    //						//beadPosition2D[imageIndex][i][0];
    //						//beadPosition2D[imageIndex][i][1];
    //
    //						devU = x_x.getElement(0) + x_x.getElement(1)*x + x_x.getElement(2)*y;
    //						devV = x_y.getElement(0) + x_y.getElement(1)*x + x_y.getElement(2)*y;
    //						for (int j=0; j<noBeadRegistered; j++){
    //							rij = Math.pow(xy1[j][0]-x, 2) + Math.pow(xy1[j][1]-y, 2);
    //							if (rij > 0) {
    //								devU += x_x.getElement(j+3)*rij*Math.log(rij);
    //								devV += x_y.getElement(j+3)*rij*Math.log(rij);
    //							}
    //						}
    //
    //						distanceReferenceToCurrentBead +=
    // Math.sqrt(Math.pow(uv[0]-(beadPosition2D[imageIndex][i][0]+devU),
    // 2)+Math.pow(uv[1]-(beadPosition2D[imageIndex][i][1]+devV), 2));
    //
    //					}
    //				}
    //			}
    //			System.out.println("Euclidean distance\t" + imageIndex + "\t" +
    // distanceReferenceToCurrentBead/noBeadRegistered);

    // }

    if (isDisplay) {
      for (int i = WeightBearingBeadPositionBuilder.currentBeadNo; i >= 0; i--) {

        if (beadMean3D[i][0] != 0
            || beadMean3D[i][1] != 0
            || beadMean3D[i][2] != 0) { // assume bead 3d is registered.

          uv = compute2DCoordinates(beadMean3D[i], pMatrix);

          imp2.setValue(2);
          // mean projected bead
          imp2.drawLine(
              (int) Math.round(uv[0] - 10),
              (int) Math.round(uv[1]),
              (int) Math.round(uv[0] + 10),
              (int) Math.round(uv[1]));
          imp2.drawLine(
              (int) Math.round(uv[0]),
              (int) Math.round(uv[1] - 10),
              (int) Math.round(uv[0]),
              (int) Math.round(uv[1] + 10));
        }
      }
    }
    Grid2D result = new Grid2D((float[]) imp2.getPixels(), imp2.getWidth(), imp2.getHeight());
    return result;
  }
  public void run(ImageProcessor ip) {
    String[] imageNames = getOpenImageNames();
    if (imageNames[0] == "None") {
      IJ.error("need at least 2 binary open images");
      return;
    }
    double previousMinOverlap = Prefs.get("BVTB.BinaryFeatureExtractor.minOverlap", 0);
    boolean previousCombine = Prefs.get("BVTB.BinaryFeatureExtractor.combine", false);

    GenericDialog gd = new GenericDialog("Binary Feature Extractor");
    gd.addChoice("Objects image", imageNames, imageNames[0]);
    gd.addChoice("Selector image", imageNames, imageNames[1]);
    gd.addNumericField("Object_overlap in % (0=off)", previousMinOverlap, 0, 9, "");
    gd.addCheckbox("Combine objects and selectors", previousCombine);
    gd.addCheckbox("Count output", true);
    gd.addCheckbox("Analysis tables", false);
    gd.showDialog();
    if (gd.wasCanceled()) {
      return;
    }
    String objectsImgTitle = gd.getNextChoice();
    String selectorsImgTitle = gd.getNextChoice();
    double minOverlap = gd.getNextNumber();
    boolean combineImages = gd.getNextBoolean();
    boolean showCountOutput = gd.getNextBoolean();
    boolean showAnalysis = gd.getNextBoolean();
    if (gd.invalidNumber() || minOverlap < 0 || minOverlap > 100) {
      IJ.error("invalid number");
      return;
    }
    Prefs.set("BVTB.BinaryFeatureExtractor.minOverlap", minOverlap);
    Prefs.set("BVTB.BinaryFeatureExtractor.combine", combineImages);

    if (objectsImgTitle.equals(selectorsImgTitle)) {
      IJ.error("images need to be different");
      return;
    }

    ImagePlus objectsImp = WindowManager.getImage(objectsImgTitle);
    ImageProcessor objectsIP = objectsImp.getProcessor();
    ImagePlus selectorsImp = WindowManager.getImage(selectorsImgTitle);
    ImageProcessor selectorsIP = selectorsImp.getProcessor();

    if (!objectsIP.isBinary() || !selectorsIP.isBinary()) {
      IJ.error("works with 8-bit binary images only");
      return;
    }

    if ((objectsImp.getWidth() != selectorsImp.getWidth())
        || objectsImp.getHeight() != selectorsImp.getHeight()) {
      IJ.error("images need to be of the same size");
      return;
    }

    // close any existing RoiManager before instantiating a new one for this analysis
    RoiManager oldRM = RoiManager.getInstance2();
    if (oldRM != null) {
      oldRM.close();
    }

    RoiManager objectsRM = new RoiManager(true);
    ResultsTable objectsRT = new ResultsTable();
    ParticleAnalyzer analyzeObjects =
        new ParticleAnalyzer(analyzerOptions, measurementFlags, objectsRT, 0.0, 999999999.9);
    analyzeObjects.setRoiManager(objectsRM);

    analyzeObjects.analyze(objectsImp);
    objectsRM.runCommand("Show None");
    int objectNumber = objectsRT.getCounter();

    Roi[] objectRoi = objectsRM.getRoisAsArray();

    ResultsTable measureSelectorsRT = new ResultsTable();
    Analyzer overlapAnalyzer = new Analyzer(selectorsImp, measurementFlags, measureSelectorsRT);

    ImagePlus outputImp =
        IJ.createImage("output", "8-bit black", objectsImp.getWidth(), objectsImp.getHeight(), 1);
    ImageProcessor outputIP = outputImp.getProcessor();

    double[] measuredOverlap = new double[objectNumber];

    outputIP.setValue(255.0);
    for (int o = 0; o < objectNumber; o++) {
      selectorsImp.killRoi();
      selectorsImp.setRoi(objectRoi[o]);
      overlapAnalyzer.measure();
      measuredOverlap[o] = measureSelectorsRT.getValue("%Area", o);
      if (minOverlap != 0.0 && measuredOverlap[o] >= minOverlap) {
        outputIP.fill(objectRoi[o]);
        finalCount++;
      } else if (minOverlap == 0.0 && measuredOverlap[o] > 0.0) {
        outputIP.fill(objectRoi[o]);
        finalCount++;
      }
    }
    // measureSelectorsRT.show("Objects");

    selectorsImp.killRoi();
    RoiManager selectorRM = new RoiManager(true);
    ResultsTable selectorRT = new ResultsTable();
    ParticleAnalyzer.setRoiManager(selectorRM);
    ParticleAnalyzer analyzeSelectors =
        new ParticleAnalyzer(analyzerOptions, measurementFlags, selectorRT, 0.0, 999999999.9);
    analyzeSelectors.analyze(selectorsImp);
    selectorRM.runCommand("Show None");
    int selectorNumber = selectorRT.getCounter();

    if (combineImages) {
      outputImp.updateAndDraw();
      Roi[] selectorRoi = selectorRM.getRoisAsArray();

      ResultsTable measureObjectsRT = new ResultsTable();
      Analyzer selectorAnalyzer = new Analyzer(outputImp, measurementFlags, measureObjectsRT);

      double[] selectorOverlap = new double[selectorNumber];
      outputIP.setValue(255.0);
      for (int s = 0; s < selectorNumber; s++) {
        outputImp.killRoi();
        outputImp.setRoi(selectorRoi[s]);
        selectorAnalyzer.measure();
        selectorOverlap[s] = measureObjectsRT.getValue("%Area", s);
        if (selectorOverlap[s] > 0.0d) {
          outputIP.fill(selectorRoi[s]);
        }
      }
      selectorRoi = null;
      selectorAnalyzer = null;
      measureObjectsRT = null;
    }
    // selectorRT.show("Selectors");
    outputImp.killRoi();
    String outputImageTitle = WindowManager.getUniqueName("Extracted_" + objectsImgTitle);
    outputImp.setTitle(outputImageTitle);
    outputImp.show();
    outputImp.changes = true;

    if (showCountOutput) {
      String[] openTextWindows = WindowManager.getNonImageTitles();
      boolean makeNewTable = true;
      for (int w = 0; w < openTextWindows.length; w++) {
        if (openTextWindows[w].equals("BFE_Results")) {
          makeNewTable = false;
        }
      }

      TextWindow existingCountTable = ResultsTable.getResultsWindow();
      if (makeNewTable) {
        countTable = new ResultsTable();
        countTable.setPrecision(0);
        countTable.setValue("Image", 0, outputImageTitle);
        countTable.setValue("Objects", 0, objectNumber);
        countTable.setValue("Selectors", 0, selectorNumber);
        countTable.setValue("Extracted", 0, finalCount);
        countTable.show("BFE_Results");
      } else {
        IJ.renameResults("BFE_Results", "Results");
        countTable = ResultsTable.getResultsTable();
        countTable.setPrecision(0);
        countTable.incrementCounter();
        countTable.addValue("Image", outputImageTitle);
        countTable.addValue("Objects", objectNumber);
        countTable.addValue("Selectors", selectorNumber);
        countTable.addValue("Extracted", finalCount);
        IJ.renameResults("Results", "BFE_Results");
        countTable.show("BFE_Results");
      }
    }

    if (showAnalysis) {
      ResultsTable extractedRT = new ResultsTable();
      ParticleAnalyzer analyzeExtracted =
          new ParticleAnalyzer(
              ParticleAnalyzer.CLEAR_WORKSHEET | ParticleAnalyzer.RECORD_STARTS,
              measurementFlags,
              extractedRT,
              0.0,
              999999999.9);
      analyzeExtracted.analyze(outputImp);
      objectsRT.show("Objects");
      selectorRT.show("Selectors");
      extractedRT.show("Extracted");
    } else {
      objectsRT = null;
      selectorRT = null;
    }

    objectsRM = null;
    measureSelectorsRT = null;
    analyzeObjects = null;
    overlapAnalyzer = null;
    objectRoi = null;
    selectorRM = null;

    objectsImp.killRoi();
    objectsImp.changes = false;
    selectorsImp.changes = false;
  }
 void drawRoiFilledParticle(ImageProcessor ip, Roi roi, ImageProcessor mask, int count) {
   int grayLevel = (count < 65535) ? count : 65535;
   ip.setValue((double) grayLevel);
   ip.setRoi(roi.getBounds());
   ip.fill(mask);
 }
 void analyzeParticle(int x, int y, ImagePlus imp, ImageProcessor ip) {
   // Wand wand = new Wand(ip);
   ImageProcessor ip2 = redirectIP != null ? redirectIP : ip;
   wand.autoOutline(x, y, level1, level2, wandMode);
   if (wand.npoints == 0) {
     IJ.log("wand error: " + x + " " + y);
     return;
   }
   Roi roi = new PolygonRoi(wand.xpoints, wand.ypoints, wand.npoints, roiType);
   Rectangle r = roi.getBounds();
   if (r.width > 1 && r.height > 1) {
     PolygonRoi proi = (PolygonRoi) roi;
     pf.setPolygon(proi.getXCoordinates(), proi.getYCoordinates(), proi.getNCoordinates());
     ip2.setMask(pf.getMask(r.width, r.height));
     if (floodFill) ff.particleAnalyzerFill(x, y, level1, level2, ip2.getMask(), r);
   }
   ip2.setRoi(r);
   ip.setValue(fillColor);
   ImageStatistics stats = getStatistics(ip2, measurements, calibration);
   boolean include = true;
   if (excludeEdgeParticles) {
     if (r.x == minX || r.y == minY || r.x + r.width == maxX || r.y + r.height == maxY)
       include = false;
     if (polygon != null) {
       Rectangle bounds = roi.getBounds();
       int x1 = bounds.x + wand.xpoints[wand.npoints - 1];
       int y1 = bounds.y + wand.ypoints[wand.npoints - 1];
       int x2, y2;
       for (int i = 0; i < wand.npoints; i++) {
         x2 = bounds.x + wand.xpoints[i];
         y2 = bounds.y + wand.ypoints[i];
         if (!polygon.contains(x2, y2)) {
           include = false;
           break;
         }
         if ((x1 == x2 && ip.getPixel(x1, y1 - 1) == fillColor)
             || (y1 == y2 && ip.getPixel(x1 - 1, y1) == fillColor)) {
           include = false;
           break;
         }
         x1 = x2;
         y1 = y2;
       }
     }
   }
   ImageProcessor mask = ip2.getMask();
   if (minCircularity > 0.0 || maxCircularity < 1.0) {
     double perimeter = roi.getLength();
     double circularity =
         perimeter == 0.0 ? 0.0 : 4.0 * Math.PI * (stats.pixelCount / (perimeter * perimeter));
     if (circularity > 1.0) circularity = 1.0;
     // IJ.log(circularity+"	"+perimeter+"  "+stats.area);
     if (circularity < minCircularity || circularity > maxCircularity) include = false;
   }
   if (stats.pixelCount >= minSize && stats.pixelCount <= maxSize && include) {
     particleCount++;
     if (roiNeedsImage) roi.setImage(imp);
     stats.xstart = x;
     stats.ystart = y;
     saveResults(stats, roi);
     if (showChoice != NOTHING) drawParticle(drawIP, roi, stats, mask);
   }
   if (redirectIP != null) ip.setRoi(r);
   ip.fill(mask);
 }
  /**
   * Performs particle analysis on the specified ImagePlus and ImageProcessor. Returns false if
   * there is an error.
   */
  public boolean analyze(ImagePlus imp, ImageProcessor ip) {
    if (this.imp == null) this.imp = imp;
    showResults = (options & SHOW_RESULTS) != 0;
    excludeEdgeParticles = (options & EXCLUDE_EDGE_PARTICLES) != 0;
    resetCounter = (options & CLEAR_WORKSHEET) != 0;
    showProgress = (options & SHOW_PROGRESS) != 0;
    floodFill = (options & INCLUDE_HOLES) == 0;
    recordStarts = (options & RECORD_STARTS) != 0;
    addToManager = (options & ADD_TO_MANAGER) != 0;
    displaySummary = (options & DISPLAY_SUMMARY) != 0;
    inSituShow = (options & IN_SITU_SHOW) != 0;
    outputImage = null;
    ip.snapshot();
    ip.setProgressBar(null);
    if (Analyzer.isRedirectImage()) {
      redirectImp = Analyzer.getRedirectImage(imp);
      if (redirectImp == null) return false;
      int depth = redirectImp.getStackSize();
      if (depth > 1 && depth == imp.getStackSize()) {
        ImageStack redirectStack = redirectImp.getStack();
        redirectIP = redirectStack.getProcessor(imp.getCurrentSlice());
      } else redirectIP = redirectImp.getProcessor();
    } else if (imp.getType() == ImagePlus.COLOR_RGB) {
      ImagePlus original = (ImagePlus) imp.getProperty("OriginalImage");
      if (original != null
          && original.getWidth() == imp.getWidth()
          && original.getHeight() == imp.getHeight()) {
        redirectImp = original;
        redirectIP = original.getProcessor();
      }
    }
    if (!setThresholdLevels(imp, ip)) return false;
    width = ip.getWidth();
    height = ip.getHeight();
    if (!(showChoice == NOTHING || showChoice == OVERLAY_OUTLINES || showChoice == OVERLAY_MASKS)) {
      blackBackground = Prefs.blackBackground && inSituShow;
      if (slice == 1) outlines = new ImageStack(width, height);
      if (showChoice == ROI_MASKS) drawIP = new ShortProcessor(width, height);
      else drawIP = new ByteProcessor(width, height);
      drawIP.setLineWidth(lineWidth);
      if (showChoice == ROI_MASKS) {
      } // Place holder for now...
      else if (showChoice == MASKS && !blackBackground) drawIP.invertLut();
      else if (showChoice == OUTLINES) {
        if (!inSituShow) {
          if (customLut == null) makeCustomLut();
          drawIP.setColorModel(customLut);
        }
        drawIP.setFont(new Font("SansSerif", Font.PLAIN, fontSize));
        if (fontSize > 12 && inSituShow) drawIP.setAntialiasedText(true);
      }
      outlines.addSlice(null, drawIP);

      if (showChoice == ROI_MASKS || blackBackground) {
        drawIP.setColor(Color.black);
        drawIP.fill();
        drawIP.setColor(Color.white);
      } else {
        drawIP.setColor(Color.white);
        drawIP.fill();
        drawIP.setColor(Color.black);
      }
    }
    calibration = redirectImp != null ? redirectImp.getCalibration() : imp.getCalibration();

    if (rt == null) {
      rt = Analyzer.getResultsTable();
      analyzer = new Analyzer(imp);
    } else analyzer = new Analyzer(imp, measurements, rt);
    if (resetCounter && slice == 1) {
      if (!Analyzer.resetCounter()) return false;
    }
    beginningCount = Analyzer.getCounter();

    byte[] pixels = null;
    if (ip instanceof ByteProcessor) pixels = (byte[]) ip.getPixels();
    if (r == null) {
      r = ip.getRoi();
      mask = ip.getMask();
      if (displaySummary) {
        if (mask != null) totalArea = ImageStatistics.getStatistics(ip, AREA, calibration).area;
        else totalArea = r.width * calibration.pixelWidth * r.height * calibration.pixelHeight;
      }
    }
    minX = r.x;
    maxX = r.x + r.width;
    minY = r.y;
    maxY = r.y + r.height;
    if (r.width < width || r.height < height || mask != null) {
      if (!eraseOutsideRoi(ip, r, mask)) return false;
    }
    int offset;
    double value;
    int inc = Math.max(r.height / 25, 1);
    int mi = 0;
    ImageWindow win = imp.getWindow();
    if (win != null) win.running = true;
    if (measurements == 0) measurements = Analyzer.getMeasurements();
    if (showChoice == ELLIPSES) measurements |= ELLIPSE;
    measurements &= ~LIMIT; // ignore "Limit to Threshold"
    roiNeedsImage =
        (measurements & PERIMETER) != 0
            || (measurements & SHAPE_DESCRIPTORS) != 0
            || (measurements & FERET) != 0;
    particleCount = 0;
    wand = new Wand(ip);
    pf = new PolygonFiller();
    if (floodFill) {
      ImageProcessor ipf = ip.duplicate();
      ipf.setValue(fillColor);
      ff = new FloodFiller(ipf);
    }
    roiType = Wand.allPoints() ? Roi.FREEROI : Roi.TRACED_ROI;

    for (int y = r.y; y < (r.y + r.height); y++) {
      offset = y * width;
      for (int x = r.x; x < (r.x + r.width); x++) {
        if (pixels != null) value = pixels[offset + x] & 255;
        else if (imageType == SHORT) value = ip.getPixel(x, y);
        else value = ip.getPixelValue(x, y);
        if (value >= level1 && value <= level2) analyzeParticle(x, y, imp, ip);
      }
      if (showProgress && ((y % inc) == 0)) IJ.showProgress((double) (y - r.y) / r.height);
      if (win != null) canceled = !win.running;
      if (canceled) {
        Macro.abort();
        break;
      }
    }
    if (showProgress) IJ.showProgress(1.0);
    if (showResults) rt.updateResults();
    imp.killRoi();
    ip.resetRoi();
    ip.reset();
    if (displaySummary && IJ.getInstance() != null) updateSliceSummary();
    if (addToManager && roiManager != null) roiManager.setEditMode(imp, true);
    maxParticleCount = (particleCount > maxParticleCount) ? particleCount : maxParticleCount;
    totalCount += particleCount;
    if (!canceled) showResults();
    return true;
  }