/**
   * Calculates the zero crossing mask of a 2D image and returns it as a BitSet buffer.
   *
   * @param buffer the slice to work with
   * @param extents the extents of the image
   * @return BitSet buffer with mask set when a zero crossing is detected
   */
  public BitSet calcZeroXMaskBitset(float[] buffer, int[] extents) {
    setStartTime();
    makeKernels2D();

    float[] xResultBuffer;
    float[] yResultBuffer;
    AlgorithmSeparableConvolver xConvolver =
        new AlgorithmSeparableConvolver(
            buffer, extents, GxxData, kExtents, false); // assume not color

    xConvolver.run();
    xResultBuffer = xConvolver.getOutputBuffer();
    xConvolver.finalize();
    xConvolver = null;

    AlgorithmSeparableConvolver yConvolver =
        new AlgorithmSeparableConvolver(
            buffer, extents, GyyData, kExtents, false); // assume not color

    yConvolver.run();
    yResultBuffer = yConvolver.getOutputBuffer();
    yConvolver.finalize();
    yConvolver = null;

    for (int i = 0; i < buffer.length; i++) {
      xResultBuffer[i] = -(xResultBuffer[i] + yResultBuffer[i]);
    }

    computeElapsedTime();

    return genLevelMask(extents[0], extents[1], xResultBuffer, 0, zeroDetectionType);
  }
  /**
   * This function produces the EdgeLap of input image.
   *
   * @param detectionType the type of zero crossing detection to perform
   */
  private void calcStoreInDest3D(int detectionType) {
    int nImages;
    int length, totalLength;
    float[] buffer, xResultBuffer, yResultBuffer, zResultBuffer;
    int start;
    float[] sliceBuffer;

    try {
      destImage.setLock();
    } catch (IOException error) {
      errorCleanUp("Algorithm EdgeLapSep: Image(s) locked", false);

      return;
    }

    try {
      length = srcImage.getSliceSize();
      totalLength = srcImage.getSliceSize() * srcImage.getExtents()[2];
      nImages = srcImage.getExtents()[2];
      buffer = new float[totalLength];
      sliceBuffer = new float[length];
      srcImage.exportData(0, totalLength, buffer); // locks and releases lock

      // fireProgressStateChanged(srcImage.getImageName(), "Calculating Zero X-ings ...");
    } catch (IOException error) {
      buffer = null;
      sliceBuffer = null;
      xResultBuffer = null;
      yResultBuffer = null;
      zResultBuffer = null;
      errorCleanUp("Algorithm EdgeLapSep exportData: Image(s) locked", true);

      return;
    } catch (OutOfMemoryError e) {
      buffer = null;
      sliceBuffer = null;
      xResultBuffer = null;
      yResultBuffer = null;
      zResultBuffer = null;
      errorCleanUp("Algorithm EdgeLapSep: Out of memory", true);

      return;
    }

    // initProgressBar();
    fireProgressStateChanged(0, srcImage.getImageName(), "Convolving X dimension ...");

    /** Minimum and maximum progress value for the convolving part */
    int min = 0;
    int max = min + Math.round(100 / 2.0f);
    float stepPerDimension = ((float) (max - min)) / 3.0f;
    AlgorithmSeparableConvolver xConvolver = null;

    if (Math.round(stepPerDimension) > 1) {
      xConvolver =
          new AlgorithmSeparableConvolver(
              buffer, srcImage.getExtents(), GxxData, kExtents, false); // assume not color

      xConvolver.setProgressValues(generateProgressValues(min, min + Math.round(stepPerDimension)));
      linkProgressToAlgorithm(xConvolver);
    } else {
      xConvolver =
          new AlgorithmSeparableConvolver(
              buffer, srcImage.getExtents(), GxxData, kExtents, false); // assume not color
    }

    if (!entireImage) {
      xConvolver.setMask(mask);
    }

    xConvolver.run();
    xResultBuffer = xConvolver.getOutputBuffer();
    xConvolver.finalize();
    xConvolver = null;

    fireProgressStateChanged(
        min + Math.round(stepPerDimension), srcImage.getImageName(), "Convolving Y dimension...");

    AlgorithmSeparableConvolver yConvolver = null;

    if ((Math.round(stepPerDimension * 2) - Math.round(stepPerDimension)) > 1) {
      yConvolver =
          new AlgorithmSeparableConvolver(
              buffer, srcImage.getExtents(), GyyData, kExtents, false); // assume not color

      yConvolver.setProgressValues(
          generateProgressValues(
              min + Math.round(stepPerDimension), min + Math.round(stepPerDimension * 2)));

      linkProgressToAlgorithm(yConvolver);
    } else {
      yConvolver =
          new AlgorithmSeparableConvolver(
              buffer, srcImage.getExtents(), GyyData, kExtents, false); // assume not color
    }

    if (!entireImage) {
      yConvolver.setMask(mask);
    }

    yConvolver.run();
    yResultBuffer = yConvolver.getOutputBuffer();
    yConvolver.finalize();
    yConvolver = null;

    fireProgressStateChanged(
        min + Math.round(stepPerDimension * 2),
        srcImage.getImageName(),
        "Convolving Z dimension...");

    AlgorithmSeparableConvolver zConvolver = null;

    if ((Math.round(stepPerDimension * 3) - Math.round(stepPerDimension * 2)) > 1) {
      zConvolver =
          new AlgorithmSeparableConvolver(
              buffer, srcImage.getExtents(), GzzData, kExtents, false); // assume not color

      zConvolver.setProgressValues(
          generateProgressValues(min + Math.round(stepPerDimension * 2), max));

      linkProgressToAlgorithm(zConvolver);
    } else {
      zConvolver =
          new AlgorithmSeparableConvolver(
              buffer, srcImage.getExtents(), GzzData, kExtents, false); // assume not color
    }

    if (!entireImage) {
      zConvolver.setMask(mask);
    }

    zConvolver.run();
    zResultBuffer = zConvolver.getOutputBuffer();
    zConvolver.finalize();
    zConvolver = null;

    min = max;
    max = 100;

    float stepPerImage = ((float) (max - min)) / nImages;

    for (int s = 0; (s < nImages) && !threadStopped; s++) {
      fireProgressStateChanged(
          min + Math.round(stepPerImage * s),
          srcImage.getImageName(),
          "Calculating the edges of slice " + (s + 1) + "...");

      start = s * length;

      for (int i = start; (i < (start + length)) && !threadStopped; i++) {

        if (entireImage || mask.get(i)) {
          destImage.set(i, -(xResultBuffer[i] + yResultBuffer[i] + zResultBuffer[i]));
        } else {
          destImage.set(i, buffer[i]);
        }
      }

      try {
        destImage.exportDataNoLock(start, length, sliceBuffer);
      } catch (IOException error) {
        buffer = null;
        sliceBuffer = null;
        errorCleanUp("Algorithm EdgeLapSep exportData: " + error, true);

        return;
      }

      genZeroXMask(s, sliceBuffer, detectionType);
    }

    if (threadStopped) {
      finalize();

      return;
    }

    zXMask.calcMinMax();
    destImage.calcMinMax();
    destImage.releaseLock();

    setCompleted(true);
  }
  /**
   * This function produces the EdgeLap of input image.
   *
   * @param nImages number of images on which to find zero crossings. If 2D image then nImage = 1.
   *     If 3D image where each image is to processed independently then nImages equals the number
   *     of images in the volume.
   * @param detectionType the type of zero crossing detection to perform
   */
  private void calcStoreInDest2D(int nImages, int detectionType) {

    // int i, s, idx;
    int length;
    int start;
    float[] buffer, xResultBuffer, yResultBuffer;

    try {
      destImage.setLock();
    } catch (IOException error) {
      errorCleanUp("Algorithm EdgeLapSep: Image(s) locked", false);

      return;
    }

    try {
      length = srcImage.getSliceSize();
      buffer = new float[length];
      // fireProgressStateChanged(srcImage.getImageName(), "Calculating the Edge ...");
    } catch (OutOfMemoryError e) {
      buffer = null;
      errorCleanUp("Algorithm Edge Lap Sep: Out of memory", true);

      return;
    }

    fireProgressStateChanged(0, srcImage.getImageName(), "Calculating the Edge ...");

    float stepPerImage = 100f / nImages;

    // initProgressBar();

    for (int s = 0; (s < nImages) && !threadStopped; s++) {
      fireProgressStateChanged(
          Math.round(stepPerImage * s),
          srcImage.getImageName(),
          "Calculating the edges of slice " + (s + 1) + "...");
      start = s * length;

      try {
        srcImage.exportData(start, length, buffer); // locks and releases lock
      } catch (IOException error) {
        errorCleanUp("Algorithm EdgeLapSep: Image(s) locked", false);

        return;
      }

      int min = Math.round(stepPerImage * s);
      int max = min + Math.round(((float) (Math.round(stepPerImage * (s + 1)) - min)) / 2.0f);
      AlgorithmSeparableConvolver xConvolver = null;

      if ((max - min) > 1) {
        xConvolver =
            new AlgorithmSeparableConvolver(
                buffer,
                new int[] {srcImage.getExtents()[0], srcImage.getExtents()[1]},
                GxxData,
                kExtents,
                false);

        xConvolver.setProgressValues(generateProgressValues(min, max));
        linkProgressToAlgorithm(xConvolver);

      } else {
        xConvolver =
            new AlgorithmSeparableConvolver(
                buffer,
                new int[] {srcImage.getExtents()[0], srcImage.getExtents()[1]},
                GxxData,
                kExtents,
                false); // assume not color
      }

      if (!entireImage) {
        xConvolver.setMask(mask);
      }

      xConvolver.run();
      xResultBuffer = xConvolver.getOutputBuffer();
      xConvolver.finalize();
      xConvolver = null;

      min = max;
      max = Math.round(stepPerImage * (s + 1));

      AlgorithmSeparableConvolver yConvolver = null;

      if ((max - min) > 1) {
        yConvolver =
            new AlgorithmSeparableConvolver(
                buffer,
                new int[] {srcImage.getExtents()[0], srcImage.getExtents()[1]},
                GyyData,
                kExtents,
                false);

        yConvolver.setProgressValues(generateProgressValues(min, max));
        linkProgressToAlgorithm(yConvolver);

      } else {
        yConvolver =
            new AlgorithmSeparableConvolver(
                buffer,
                new int[] {srcImage.getExtents()[0], srcImage.getExtents()[1]},
                GyyData,
                kExtents,
                false); // assume not color
      }

      if (!entireImage) {
        yConvolver.setMask(mask);
      }

      yConvolver.run();
      yResultBuffer = yConvolver.getOutputBuffer();
      yConvolver.finalize();
      yConvolver = null;

      for (int i = 0, idx = start; (i < buffer.length) && !threadStopped; i++, idx++) {

        if (entireImage || mask.get(i)) {
          destImage.set(idx, -(xResultBuffer[i] + yResultBuffer[i]));
        } else {
          destImage.set(idx, buffer[i]);
        }
      }

      try {
        destImage.exportDataNoLock(start, length, buffer);
      } catch (IOException error) {
        errorCleanUp("Algorithm EdgeLapSep exportData: " + error, false);

        return;
      }

      genZeroXMask(s, buffer, detectionType);
    }

    if (threadStopped) {
      finalize();

      return;
    }

    zXMask.calcMinMax();
    destImage.calcMinMax();
    destImage.releaseLock();

    setCompleted(true);
  }