@Override
  public void applyInPlace(FastBitmap fastBitmap) {

    if (fastBitmap.isRGB()) {
      fastBitmap.toGrayscale();
    }

    if (invert) new Invert().applyInPlace(fastBitmap);

    int size = fastBitmap.getWidth() * fastBitmap.getHeight();

    int min = ImageStatistics.Minimum(fastBitmap);
    int max = ImageStatistics.Maximum(fastBitmap);

    fastBitmap.toRGB();
    for (int i = 0; i < size; i++) {
      int[] rgb = GrayscaleToHeatMap(fastBitmap.getRed(i), min, max);
      fastBitmap.setRGB(i, rgb);
    }
  }
  /**
   * Calculate binarization threshold for the given image.
   *
   * @param fastBitmap FastBitmap.
   * @return Threshold value.
   */
  public int CalculateThreshold(FastBitmap fastBitmap) {

    ImageStatistics stat = new ImageStatistics(fastBitmap);
    Histogram hist = stat.getHistogramGray();

    int[] histogram = hist.getValues();
    int total = fastBitmap.getWidth() * fastBitmap.getHeight();

    double sum = 0;
    for (int i = 0; i < 256; i++) sum += i * histogram[i];

    double sumB = 0;
    int wB = 0;
    int wF = 0;

    double varMax = 0;
    int threshold = 0;

    for (int i = 0; i < 256; i++) {
      wB += histogram[i];
      if (wB == 0) continue;
      wF = total - wB;

      if (wF == 0) break;

      sumB += (double) (i * histogram[i]);
      double mB = sumB / wB;
      double mF = (sum - sumB) / wF;

      double varBetween = (double) wB * (double) wF * (mB - mF) * (mB - mF);

      if (varBetween > varMax) {
        varMax = varBetween;
        threshold = i;
      }
    }

    return threshold;
  }
  /**
   * Calculate binarization threshold for the given image.
   *
   * @param fastBitmap FastBitmap.
   * @return Threshold value.
   */
  public int CalculateThreshold(FastBitmap fastBitmap) {

    ImageStatistics stat = new ImageStatistics(fastBitmap);
    ImageHistogram hist = stat.getHistogramGray();
    int[] histogram = hist.getValues();

    // Normalize histogram, that is makes the sum of all bins equal to 1.
    double sum = 0;
    for (int i = 0; i < histogram.length; ++i) {
      sum += histogram[i];
    }
    if (sum == 0) {
      // This should not normally happen, but...
      throw new IllegalArgumentException("Empty histogram: sum of all bins is zero.");
    }

    double[] normalizedHist = new double[histogram.length];
    for (int i = 0; i < histogram.length; i++) {
      normalizedHist[i] = histogram[i] / sum;
    }

    //
    double[] pT = new double[histogram.length];
    pT[0] = normalizedHist[0];
    for (int i = 1; i < histogram.length; i++) {
      pT[i] = pT[i - 1] + normalizedHist[i];
    }

    // Entropy for black and white parts of the histogram
    final double epsilon = Double.MIN_VALUE;
    double[] hB = new double[histogram.length];
    double[] hW = new double[histogram.length];
    for (int t = 0; t < histogram.length; t++) {
      // Black entropy
      if (pT[t] > epsilon) {
        double hhB = 0;
        for (int i = 0; i <= t; i++) {
          if (normalizedHist[i] > epsilon) {
            hhB -= normalizedHist[i] / pT[t] * Math.log(normalizedHist[i] / pT[t]);
          }
        }
        hB[t] = hhB;
      } else {
        hB[t] = 0;
      }

      // White  entropy
      double pTW = 1 - pT[t];
      if (pTW > epsilon) {
        double hhW = 0;
        for (int i = t + 1; i < histogram.length; ++i) {
          if (normalizedHist[i] > epsilon) {
            hhW -= normalizedHist[i] / pTW * Math.log(normalizedHist[i] / pTW);
          }
        }
        hW[t] = hhW;
      } else {
        hW[t] = 0;
      }
    }

    // Find histogram index with maximum entropy
    double jMax = hB[0] + hW[0];
    int tMax = 0;
    for (int t = 1; t < histogram.length; ++t) {
      double j = hB[t] + hW[t];
      if (j > jMax) {
        jMax = j;
        tMax = t;
      }
    }

    return tMax;
  }
  private boolean watershedSegment(FastBitmap ip) {
    int width = ip.getWidth();
    int height = ip.getHeight();
    byte[] pixels = ip.getGrayData();
    // Create an array with the coordinates of all points between value 1 and 254
    // This method, suggested by Stein Roervik (stein_at_kjemi-dot-unit-dot-no),
    // greatly speeds up the watershed segmentation routine.

    ImageStatistics is = new ImageStatistics(ip);

    int[] histogram = is.getHistogramGray().getValues();
    int arraySize = width * height - histogram[0] - histogram[255];
    int[] coordinates = new int[arraySize]; // from pixel coordinates, low bits x, high bits y
    int highestValue = 0;
    int maxBinSize = 0;
    int offset = 0;
    int[] levelStart = new int[256];
    for (int v = 1; v < 255; v++) {
      levelStart[v] = offset;
      offset += histogram[v];
      if (histogram[v] > 0) highestValue = v;
      if (histogram[v] > maxBinSize) maxBinSize = histogram[v];
    }
    int[] levelOffset = new int[highestValue + 1];
    for (int y = 0, i = 0; y < height; y++) {
      for (int x = 0; x < width; x++, i++) {
        int v = pixels[i] & 255;
        if (v > 0 && v < 255) {
          offset = levelStart[v] + levelOffset[v];
          coordinates[offset] = x | y << intEncodeShift;
          levelOffset[v]++;
        }
      } // for x
    } // for y
    // Create an array of the points (pixel offsets) that we set to 255 in one pass.
    // If we remember this list we need not create a snapshot of the ImageProcessor.
    int[] setPointList = new int[Math.min(maxBinSize, (width * height + 2) / 3)];
    // now do the segmentation, starting at the highest level and working down.
    // At each level, dilate the particle (set pixels to 255), constrained to pixels
    // whose values are at that level and also constrained (by the fateTable)
    // to prevent features from merging.
    int[] table = makeFateTable();
    final int[] directionSequence = new int[] {7, 3, 1, 5, 0, 4, 2, 6}; // diagonal directions first
    for (int level = highestValue; level >= 1; level--) {
      int remaining =
          histogram[level]; // number of points in the level that have not been processed
      int idle = 0;
      while (remaining > 0 && idle < 8) {
        int dIndex = 0;
        do { // expand each level in 8 directions
          int n =
              processLevel(
                  directionSequence[dIndex % 8],
                  ip,
                  table,
                  levelStart[level],
                  remaining,
                  coordinates,
                  setPointList);
          // IJ.log("level="+level+" direction="+directionSequence[dIndex%8]+"
          // remain="+remaining+"-"+n);
          remaining -= n; // number of points processed
          if (n > 0) idle = 0; // nothing processed in this direction?
          dIndex++;
        } while (remaining > 0 && idle++ < 8);
      }
      if (remaining > 0 && level > 1) { // any pixels that we have not reached?
        int nextLevel = level; // find the next level to process
        do nextLevel--;
        while (nextLevel > 1 && histogram[nextLevel] == 0);
        // in principle we should add all unprocessed pixels of this level to the
        // tasklist of the next level. This would make it very slow for some images,
        // however. Thus we only add the pixels if they are at the border (of the
        // image or a thresholded area) and correct unprocessed pixels at the very
        // end by CleanupExtraLines
        if (nextLevel > 0) {
          int newNextLevelEnd = levelStart[nextLevel] + histogram[nextLevel];
          for (int i = 0, p = levelStart[level]; i < remaining; i++, p++) {
            int xy = coordinates[p];
            int x = xy & intEncodeXMask;
            int y = (xy & intEncodeYMask) >> intEncodeShift;
            int pOffset = x + y * width;
            boolean addToNext = false;
            if (x == 0 || y == 0 || x == width - 1 || y == height - 1)
              addToNext = true; // image border
            else
              for (int d = 0; d < 8; d++)
                if (isWithin(x, y, d, width, height) && pixels[pOffset + dirOffset[d]] == 0) {
                  addToNext = true; // border of area below threshold
                  break;
                }
            if (addToNext) coordinates[newNextLevelEnd++] = xy;
          }
          // tasklist for the next level to process becomes longer by this:
          histogram[nextLevel] = newNextLevelEnd - levelStart[nextLevel];
        }
      }
    }
    return true;
  }