@Test
  public final void testErosionCubicMeshC6() {
    ImageStack mask = createCubicMeshImage();
    invertGray8Stack(mask);

    ImageStack marker = ImageStack.create(20, 20, 20, 8);
    marker.setVoxel(5, 5, 5, 255);
    invertGray8Stack(marker);

    GeodesicReconstruction3DHybrid0Gray8 algo = new GeodesicReconstruction3DHybrid0Gray8();
    algo.setReconstructionType(GeodesicReconstructionType.BY_EROSION);
    algo.setConnectivity(6);

    ImageStack result = algo.applyTo(marker, mask);

    assertEquals(0, result.getVoxel(5, 15, 5), .01);
    assertEquals(255, result.getVoxel(0, 0, 0), .01);

    int sizeX = mask.getWidth();
    int sizeY = mask.getHeight();
    int sizeZ = mask.getSize();

    for (int z = 0; z < sizeZ; z++) {
      for (int y = 0; y < sizeY; y++) {
        for (int x = 0; x < sizeX; x++) {
          assertEquals(result.getVoxel(x, y, z), mask.getVoxel(x, y, z), .01);
        }
      }
    }
  }
  /**
   * Creates an image of cube edges, similar to this:
   *
   * <p>*---------* ** ** * * * * *********** * | * | * | *------|--* | / | * |/ |* ***********
   *
   * <p>Typical planes are as follow: z = 0 z=1,2,3 z = 4 X * * * * . . . . . * * * * * . . . . * .
   * . . . . * . . . * . . . . * . . . . . * . . . * . . . . * . . . . . * . . . * Z . . . * * . . .
   * * * . . . *
   *
   * <p>(reconstruction starts from the X, and terminates at the Z)
   */
  private ImageStack createThinCubicMeshImage() {
    int sizeX = 5;
    int sizeY = 5;
    int sizeZ = 5;
    int bitDepth = 8;

    // create empty stack
    ImageStack stack = ImageStack.create(sizeX, sizeY, sizeZ, bitDepth);

    // First, the edges in the x direction
    for (int x = 0; x < 5; x++) {
      stack.setVoxel(x, 0, 0, 255);
      stack.setVoxel(x, 0, 4, 255);
    }

    // then, the edges in the y direction
    for (int y = 0; y < 5; y++) {
      stack.setVoxel(4, y, 0, 255);
      stack.setVoxel(0, y, 4, 255);
      stack.setVoxel(4, y, 4, 255);
    }

    // Finally, the edges in the z direction
    for (int z = 0; z < 5; z++) {
      stack.setVoxel(0, 4, z, 255);
      stack.setVoxel(4, 4, z, 255);
    }

    return stack;
  }
  @Test
  public final void testDilationCochleaVolumeC6() {
    String fileName = getClass().getResource("/files/bat-cochlea-volume.tif").getFile();
    ImagePlus imagePlus = IJ.openImage(fileName);

    assertNotNull(imagePlus);

    assertTrue(imagePlus.getStackSize() > 0);

    ImageStack mask = imagePlus.getStack();
    // Ensure regularity of the mask
    mask = Morphology.opening(mask, CubeStrel.fromRadius(1));

    int width = mask.getWidth();
    int height = mask.getHeight();
    int depth = mask.getSize();
    int bitDepth = mask.getBitDepth();
    ImageStack marker = ImageStack.create(width, height, depth, bitDepth);

    marker.setVoxel(20, 80, 50, 255);

    GeodesicReconstruction3DHybrid0Gray8 algo = new GeodesicReconstruction3DHybrid0Gray8();
    algo.setConnectivity(6);
    algo.verbose = false;

    ImageStack result = algo.applyTo(marker, mask);

    for (int z = 0; z < depth; z++) {
      for (int y = 0; y < height; y++) {
        for (int x = 0; x < width; x++) {
          assertEquals(result.getVoxel(x, y, z), mask.getVoxel(x, y, z), .01);
        }
      }
    }
  }
  @Test
  public final void testDilationHilbertCurveC26() {
    String fileName = getClass().getResource("/files/hilbert3d.tif").getFile();
    ImagePlus imagePlus = IJ.openImage(fileName);

    assertNotNull(imagePlus);

    assertTrue(imagePlus.getStackSize() > 0);

    ImageStack mask = imagePlus.getStack();

    int width = mask.getWidth();
    int height = mask.getHeight();
    int depth = mask.getSize();
    int bitDepth = mask.getBitDepth();
    ImageStack marker = ImageStack.create(width, height, depth, bitDepth);

    marker.setVoxel(3, 0, 0, 255);

    GeodesicReconstruction3DHybrid0Gray8 algo = new GeodesicReconstruction3DHybrid0Gray8();
    algo.setConnectivity(26);
    algo.verbose = false;

    ImageStack result = algo.applyTo(marker, mask);

    for (int z = 0; z < depth; z++) {
      for (int y = 0; y < height; y++) {
        for (int x = 0; x < width; x++) {
          assertEquals(result.getVoxel(x, y, z), mask.getVoxel(x, y, z), .01);
        }
      }
    }
  }
  @Test
  public final void testApplyTo() {
    GeodesicReconstructionByDilation3DGray8 algo = new GeodesicReconstructionByDilation3DGray8();

    String fileName = getClass().getResource("/files/bat-cochlea-volume.tif").getFile();
    ImagePlus imagePlus = IJ.openImage(fileName);
    assertNotNull(imagePlus);

    assertTrue(imagePlus.getStackSize() > 0);

    ImageStack mask = imagePlus.getStack();
    int width = mask.getWidth();
    int height = mask.getHeight();
    int depth = mask.getSize();
    int bitDepth = mask.getBitDepth();
    ImageStack marker = ImageStack.create(width, height, depth, bitDepth);

    marker.setVoxel(20, 80, 50, 255);

    ImageStack result = algo.applyTo(marker, mask);

    for (int z = 0; z < depth; z++) {
      for (int y = 0; y < height; y++) {
        for (int x = 0; x < width; x++) {
          assertEquals(result.getVoxel(x, y, z), mask.getVoxel(x, y, z), .01);
        }
      }
    }
  }
  /**
   * Resizes the current array to the given dimensions.
   *
   * @param input the array to resize
   * @param lenX the desired length in x-direction (with, horizontal length)
   * @param lenY the desired length in y-direction (height, vertical length)
   * @return a new 2d float array with the specified dimensions, and values interpolated from the
   *     input array.
   */
  public static float[][] resize(float[][] input, int lenX, int lenY) {
    float[] linearized = linearize2DArray(input);

    ImageStack is = ImageStack.create(input.length, input[0].length, 1, 32);
    is.setPixels(linearized, 1);

    ImageProcessor ip = new ImagePlus("", is).getProcessor();
    ip.setInterpolationMethod(ImageProcessor.BICUBIC);
    float[] resized = (float[]) ip.resize(lenX, lenY).getPixels();

    return linearizedArrayTo2D(resized, lenX, lenY);
  }
  @Test
  public final void testDilationThinCubicMeshC26() {
    ImageStack mask = createThinCubicMeshImage();

    ImageStack marker = ImageStack.create(5, 5, 5, 8);
    marker.setVoxel(0, 0, 0, 255);

    GeodesicReconstruction3DHybrid0Gray8 algo = new GeodesicReconstruction3DHybrid0Gray8();
    algo.setConnectivity(26);

    ImageStack result = algo.applyTo(marker, mask);

    assertEquals(255, result.getVoxel(0, 4, 0), .01);
  }
  private ImageStack loadImages() {

    String fileNameNumberRef = new String();
    String fileNameNumberCorr = new String();

    if (numOfIteration < 9) {
      fileNameNumberRef = "000" + numOfIteration;
      fileNameNumberCorr = "000" + (numOfIteration + 1);
    }

    if (numOfIteration == 9) {
      fileNameNumberRef = "0009";
      fileNameNumberCorr = "0010";
    }

    if (numOfIteration >= 10 && numOfIteration < 99) {
      fileNameNumberRef = "00" + numOfIteration;
      fileNameNumberCorr = "00" + (numOfIteration + 1);
    }

    if (numOfIteration == 99) {
      fileNameNumberRef = "0099";
      fileNameNumberCorr = "0100";
    }

    if (numOfIteration >= 100 && numOfIteration < 999) {
      fileNameNumberRef = "0" + numOfIteration;
      fileNameNumberCorr = "0" + (numOfIteration + 1);
    }

    if (numOfIteration == 999) {
      fileNameNumberRef = "0999";
      fileNameNumberCorr = "1000";
    }

    if (numOfIteration >= 1000) {
      fileNameNumberRef = String.valueOf(numOfIteration);
      fileNameNumberCorr = String.valueOf(numOfIteration + 1);
    }

    ImagePlus ref = new ImagePlus(dir + name + fileNameNumberRef + format);
    ImagePlus corr = new ImagePlus(dir + name + (fileNameNumberCorr) + format);
    ImageStack tmp = ImageStack.create(ref.getWidth(), ref.getHeight(), 2, ref.getBitDepth());

    tmp.setPixels(ref.getProcessor().getPixels(), 1);
    tmp.setPixels(corr.getProcessor().getPixels(), 2);

    return tmp;
  }
  /**
   * Combination of {@link ImageJInterpolation#crop(float[][], float, float, float, float)} and
   * {@link ImageJInterpolation#resize(float[][], int, int)}. Crops and resizes the input array into
   * a new output array.
   *
   * @param input the array to work on
   * @param lenX the desired length in x-direction (with, horizontal length)
   * @param lenY the desired length in y-direction (height, vertical length)
   * @param minX the lower index in x-direction
   * @param minY the lower index in y-direction
   * @param maxX the higher index in x-direction
   * @param maxY the higher index in y-direction
   * @return a new cropped and resized version of the input array
   */
  public static float[][] cropAndResize(
      float[][] input, int lenX, int lenY, float minX, float minY, float maxX, float maxY) {

    float[] linearized = linearize2DArray(input);

    ImageStack is = ImageStack.create(input[0].length, input.length, 1, 32);
    is.setPixels(linearized, 1);

    ImageProcessor ip = new ImagePlus("", is).getProcessor();
    ip.setInterpolationMethod(ImageProcessor.BICUBIC);
    ip.setRoi((int) minX, (int) minY, (int) Math.ceil(maxX - minX), (int) Math.ceil(maxY - minY));

    float[] resized = (float[]) ip.crop().resize(lenX, lenY).getPixels();

    return linearizedArrayTo2D(resized, lenX, lenY);
  }
  private ImageStack createCubicMeshImage() {
    int sizeX = 20;
    int sizeY = 20;
    int sizeZ = 20;
    int bitDepth = 8;

    // create empty stack
    ImageStack stack = ImageStack.create(sizeX, sizeY, sizeZ, bitDepth);

    // number of voxels between edges and 'tube' borders
    int gap = 2;

    // First, the edges in the x direction
    for (int z = 5 - gap - 1; z <= 5 + gap + 1; z++) {
      for (int y = 5 - gap - 1; y <= 5 + gap + 1; y++) {
        for (int x = 5 - gap - 1; x <= 15 + gap + 1; x++) {
          stack.setVoxel(x, y, z, 255);
          stack.setVoxel(x, y, z + 10, 255);
        }
      }
    }

    // then, the edges in the y direction
    for (int z = 5 - gap - 1; z <= 5 + gap + 1; z++) {
      for (int x = 5 - gap - 1; x <= 5 + gap + 1; x++) {
        for (int y = 5 - gap - 1; y <= 15 + gap + 1; y++) {
          stack.setVoxel(x + 10, y, z, 255);
          stack.setVoxel(x, y, z + 10, 255);
          stack.setVoxel(x + 10, y, z + 10, 255);
        }
      }
    }

    // Finally, the edges in the z direction
    for (int y = 5 - gap - 1; y <= 5 + gap + 1; y++) {
      for (int x = 5 - gap - 1; x <= 5 + gap + 1; x++) {
        for (int z = 5 - gap - 1; z <= 15 + gap + 1; z++) {
          stack.setVoxel(x, y + 10, z, 255);
          stack.setVoxel(x + 10, y + 10, z, 255);
        }
      }
    }

    return stack;
  }
  @Test
  public final void testDilationCubicHollowMesh() {
    ImageStack mask = createCubicHollowMeshImage();

    ImageStack marker = ImageStack.create(20, 20, 20, 8);
    for (int z = 0; z < 20; z++) {
      for (int y = 0; y < 20; y++) {
        for (int x = 0; x < 20; x++) {
          marker.setVoxel(x, y, z, 255);
        }
      }
    }
    marker.setVoxel(5, 5, 5, 0);

    GeodesicReconstruction3DHybrid0Gray8 algo = new GeodesicReconstruction3DHybrid0Gray8();

    ImageStack result = algo.applyTo(marker, mask);

    assertEquals(0, result.getVoxel(5, 15, 5), .01);
    assertStackEquals(mask, result);
  }
  @Test
  public final void testErosionCochleaVolumeC26() {
    String fileName = getClass().getResource("/files/bat-cochlea-volume.tif").getFile();
    ImagePlus imagePlus = IJ.openImage(fileName);

    assertNotNull(imagePlus);
    assertTrue(imagePlus.getStackSize() > 0);

    ImageStack mask = imagePlus.getStack();
    // Ensure regularity of the mask
    mask = Morphology.opening(mask, CubeStrel.fromRadius(1));
    invertGray8Stack(mask);

    int width = mask.getWidth();
    int height = mask.getHeight();
    int depth = mask.getSize();
    int bitDepth = mask.getBitDepth();
    ImageStack marker = ImageStack.create(width, height, depth, bitDepth);
    marker.setVoxel(20, 80, 50, 255);
    invertGray8Stack(marker);

    GeodesicReconstruction3DHybrid0Gray8 algo =
        new GeodesicReconstruction3DHybrid0Gray8(GeodesicReconstructionType.BY_EROSION);
    algo.setConnectivity(26);

    ImageStack result = algo.applyTo(marker, mask);

    for (int z = 0; z < depth; z++) {
      for (int y = 0; y < height; y++) {
        for (int x = 0; x < width; x++) {
          if (Math.abs(result.getVoxel(x, y, z) - mask.getVoxel(x, y, z)) > .1) {
            System.out.println("x=" + x + " y=" + y + " z=" + z);
            System.out.println("  mask = " + (int) mask.getVoxel(x, y, z));
            System.out.println("  res  = " + (int) result.getVoxel(x, y, z));
            assertTrue(false);
          }
        }
      }
    }
  }
  /** Creates a 3D image containing thin cube mesh. */
  private ImageStack createCubeGraphImage() {
    int sizeX = 11;
    int sizeY = 11;
    int sizeZ = 11;
    int bitDepth = 8;

    // create empty stack
    ImageStack stack = ImageStack.create(sizeX, sizeY, sizeZ, bitDepth);

    // coordinates of the cube edges
    int x1 = 1;
    int x2 = 9;
    int y1 = 1;
    int y2 = 9;
    int z1 = 1;
    int z2 = 9;

    // First, the edges in the x direction
    for (int x = x1; x <= x2; x++) {
      stack.setVoxel(x, y1, z1, 255);
      stack.setVoxel(x, y1, z2, 255);
    }

    // then, the edges in the y direction
    for (int y = y1; y <= y2; y++) {
      stack.setVoxel(x2, y, z1, 255);
      stack.setVoxel(x1, y, z2, 255);
      stack.setVoxel(x2, y, z2, 255);
    }

    // Finally, the edges in the z direction
    for (int z = z1; z <= z2; z++) {
      stack.setVoxel(x1, y2, z, 255);
      stack.setVoxel(x2, y2, z, 255);
    }

    return stack;
  }