/** See how well it processes an image which is not an GrayS32 */
  @Test
  public void checkOtherType() {
    GrayS32 orig = new GrayS32(width, height);
    GImageMiscOps.fillUniform(orig, rand, 0, 20);
    GrayU8 orig8 = ConvertImage.convert(orig, (GrayU8) null);

    int N = 3;
    ImageDimension dimen = UtilWavelet.transformDimension(orig, N);

    GrayS32 found = new GrayS32(dimen.width, dimen.height);
    GrayS32 expected = new GrayS32(dimen.width, dimen.height);

    WaveletDescription<WlCoef_I32> desc =
        FactoryWaveletDaub.biorthogonal_I32(5, BorderType.REFLECT);

    GrayS32 storage = new GrayS32(dimen.width, dimen.height);
    WaveletTransformOps.transformN(desc, orig.clone(), expected, storage, N);

    WaveletTransformInt<GrayU8> alg = new WaveletTransformInt<>(desc, N, 0, 255, GrayU8.class);
    alg.transform(orig8, found);

    // see if the two techniques produced the same results
    BoofTesting.assertEquals(expected, found, 0);

    // see if it can convert it back
    GrayU8 reconstructed = new GrayU8(width, height);
    alg.invert(found, reconstructed);
    BoofTesting.assertEquals(orig8, reconstructed, 0);
    // make sure the input has not been modified
    BoofTesting.assertEquals(expected, found, 0);
  }
  @Test
  public void compareToWaveletTransformOps() {
    GrayS32 orig = new GrayS32(width, height);
    GImageMiscOps.fillUniform(orig, rand, 0, 20);
    GrayS32 origCopy = orig.clone();

    int N = 3;
    ImageDimension dimen = UtilWavelet.transformDimension(orig, N);

    GrayS32 found = new GrayS32(dimen.width, dimen.height);
    GrayS32 expected = new GrayS32(dimen.width, dimen.height);

    WaveletDescription<WlCoef_I32> desc =
        FactoryWaveletDaub.biorthogonal_I32(5, BorderType.REFLECT);

    GrayS32 storage = new GrayS32(dimen.width, dimen.height);
    WaveletTransformOps.transformN(desc, orig.clone(), expected, storage, N);

    WaveletTransformInt<GrayS32> alg = new WaveletTransformInt<>(desc, N, 0, 255, GrayS32.class);
    alg.transform(orig, found);

    // make sure the original input was not modified like it is in WaveletTransformOps
    BoofTesting.assertEquals(origCopy, orig, 0);
    // see if the two techniques produced the same results
    BoofTesting.assertEquals(expected, found, 0);

    // test inverse transform
    GrayS32 reconstructed = new GrayS32(width, height);
    alg.invert(found, reconstructed);
    BoofTesting.assertEquals(orig, reconstructed, 0);
    // make sure the input has not been modified
    BoofTesting.assertEquals(expected, found, 0);
  }
  public static void vertical(Kernel1D_I32 kernel, GrayS32 input, GrayS32 output) {
    final int[] dataSrc = input.data;
    final int[] dataDst = output.data;
    final int[] dataKer = kernel.data;

    final int kernelWidth = kernel.getWidth();
    final int offsetL = kernel.getOffset();
    final int offsetR = kernelWidth - offsetL - 1;

    final int imgWidth = output.getWidth();
    final int imgHeight = output.getHeight();

    final int yEnd = imgHeight - offsetR;

    for (int y = 0; y < offsetL; y++) {
      int indexDst = output.startIndex + y * output.stride;
      int i = input.startIndex + y * input.stride;
      final int iEnd = i + imgWidth;

      int kStart = offsetL - y;

      int weight = 0;
      for (int k = kStart; k < kernelWidth; k++) {
        weight += dataKer[k];
      }

      for (; i < iEnd; i++) {
        int total = 0;
        int indexSrc = i - y * input.stride;
        for (int k = kStart; k < kernelWidth; k++, indexSrc += input.stride) {
          total += (dataSrc[indexSrc]) * dataKer[k];
        }
        dataDst[indexDst++] = ((total + weight / 2) / weight);
      }
    }

    for (int y = yEnd; y < imgHeight; y++) {
      int indexDst = output.startIndex + y * output.stride;
      int i = input.startIndex + y * input.stride;
      final int iEnd = i + imgWidth;

      int kEnd = imgHeight - (y - offsetL);

      int weight = 0;
      for (int k = 0; k < kEnd; k++) {
        weight += dataKer[k];
      }

      for (; i < iEnd; i++) {
        int total = 0;
        int indexSrc = i - offsetL * input.stride;
        for (int k = 0; k < kEnd; k++, indexSrc += input.stride) {
          total += (dataSrc[indexSrc]) * dataKer[k];
        }
        dataDst[indexDst++] = ((total + weight / 2) / weight);
      }
    }
  }
  private static void _renderLabeled(GrayS32 labelImage, BufferedImage out, int[] colors) {
    int w = labelImage.getWidth();
    int h = labelImage.getHeight();

    for (int y = 0; y < h; y++) {
      int indexSrc = labelImage.startIndex + y * labelImage.stride;
      for (int x = 0; x < w; x++) {
        int rgb = colors[labelImage.data[indexSrc++]];
        out.setRGB(x, y, rgb);
      }
    }
  }
  public static void horizontal(Kernel1D_I32 kernel, GrayS32 input, GrayS32 output) {
    final int[] dataSrc = input.data;
    final int[] dataDst = output.data;
    final int[] dataKer = kernel.data;

    final int kernelWidth = kernel.getWidth();
    final int offsetL = kernel.getOffset();
    final int offsetR = kernelWidth - offsetL - 1;

    final int width = input.getWidth();
    final int height = input.getHeight();

    for (int i = 0; i < height; i++) {
      int indexDest = output.startIndex + i * output.stride;
      int j = input.startIndex + i * input.stride;
      final int jStart = j;
      int jEnd = j + offsetL;

      for (; j < jEnd; j++) {
        int total = 0;
        int weight = 0;
        int indexSrc = jStart;
        for (int k = kernelWidth - (offsetR + 1 + j - jStart); k < kernelWidth; k++) {
          int w = dataKer[k];
          weight += w;
          total += (dataSrc[indexSrc++]) * w;
        }
        dataDst[indexDest++] = ((total + weight / 2) / weight);
      }

      j += width - (offsetL + offsetR);
      indexDest += width - (offsetL + offsetR);

      jEnd = jStart + width;
      for (; j < jEnd; j++) {
        int total = 0;
        int weight = 0;
        int indexSrc = j - offsetL;
        final int kEnd = jEnd - indexSrc;

        for (int k = 0; k < kEnd; k++) {
          int w = dataKer[k];
          weight += w;
          total += (dataSrc[indexSrc++]) * w;
        }
        dataDst[indexDest++] = ((total + weight / 2) / weight);
      }
    }
  }
  private static void renderLabeled(
      GrayS32 labelImage, int[] colors, IntegerInterleavedRaster raster) {
    int rasterIndex = 0;
    int data[] = raster.getDataStorage();

    int w = labelImage.getWidth();
    int h = labelImage.getHeight();

    for (int y = 0; y < h; y++) {
      int indexSrc = labelImage.startIndex + y * labelImage.stride;
      for (int x = 0; x < w; x++) {
        data[rasterIndex++] = colors[labelImage.data[indexSrc++]];
      }
    }
  }
  public static BufferedImage renderLabeled(GrayS32 labelImage, int colors[], BufferedImage out) {

    if (out == null) {
      out =
          new BufferedImage(
              labelImage.getWidth(), labelImage.getHeight(), BufferedImage.TYPE_INT_RGB);
    }

    try {
      if (out.getRaster() instanceof IntegerInterleavedRaster) {
        renderLabeled(labelImage, colors, (IntegerInterleavedRaster) out.getRaster());
      } else {
        _renderLabeled(labelImage, out, colors);
      }
      // hack so that it knows the image has been modified
      out.setRGB(0, 0, out.getRGB(0, 0));
    } catch (SecurityException e) {
      _renderLabeled(labelImage, out, colors);
    }
    return out;
  }
  public static void convolve(Kernel2D_I32 kernel, GrayS32 input, GrayS32 output) {
    final int[] dataSrc = input.data;
    final int[] dataDst = output.data;
    final int[] dataKer = kernel.data;

    final int kernelWidth = kernel.getWidth();
    final int offsetL = kernel.getOffset();
    final int offsetR = kernelWidth - offsetL - 1;

    final int width = input.getWidth();
    final int height = input.getHeight();

    // convolve across the left and right borders
    for (int y = 0; y < height; y++) {

      int minI = y >= offsetL ? -offsetL : -y;
      int maxI = y < height - offsetR ? offsetR : height - y - 1;

      int indexDst = output.startIndex + y * output.stride;

      for (int x = 0; x < offsetL; x++) {

        int total = 0;
        int weight = 0;

        for (int i = minI; i <= maxI; i++) {
          int indexSrc = input.startIndex + (y + i) * input.stride + x;
          int indexKer = (i + offsetL) * kernelWidth;

          for (int j = -x; j <= offsetR; j++) {
            int w = dataKer[indexKer + j + offsetL];
            weight += w;
            total += (dataSrc[indexSrc + j]) * w;
          }
        }

        dataDst[indexDst++] = ((total + weight / 2) / weight);
      }

      indexDst = output.startIndex + y * output.stride + width - offsetR;
      for (int x = width - offsetR; x < width; x++) {

        int maxJ = width - x - 1;

        int total = 0;
        int weight = 0;

        for (int i = minI; i <= maxI; i++) {
          int indexSrc = input.startIndex + (y + i) * input.stride + x;
          int indexKer = (i + offsetL) * kernelWidth;

          for (int j = -offsetL; j <= maxJ; j++) {
            int w = dataKer[indexKer + j + offsetL];
            weight += w;
            total += (dataSrc[indexSrc + j]) * w;
          }
        }

        dataDst[indexDst++] = ((total + weight / 2) / weight);
      }
    }

    // convolve across the top border while avoiding convolving the corners again
    for (int y = 0; y < offsetL; y++) {

      int indexDst = output.startIndex + y * output.stride + offsetL;

      for (int x = offsetL; x < width - offsetR; x++) {

        int total = 0;
        int weight = 0;

        for (int i = -y; i <= offsetR; i++) {
          int indexSrc = input.startIndex + (y + i) * input.stride + x;
          int indexKer = (i + offsetL) * kernelWidth;

          for (int j = -offsetL; j <= offsetR; j++) {
            int w = dataKer[indexKer + j + offsetL];
            weight += w;
            total += (dataSrc[indexSrc + j]) * w;
          }
        }
        dataDst[indexDst++] = ((total + weight / 2) / weight);
      }
    }

    // convolve across the bottom border
    for (int y = height - offsetR; y < height; y++) {

      int maxI = height - y - 1;
      int indexDst = output.startIndex + y * output.stride + offsetL;

      for (int x = offsetL; x < width - offsetR; x++) {

        int total = 0;
        int weight = 0;

        for (int i = -offsetL; i <= maxI; i++) {
          int indexSrc = input.startIndex + (y + i) * input.stride + x;
          int indexKer = (i + offsetL) * kernelWidth;

          for (int j = -offsetL; j <= offsetR; j++) {
            int w = dataKer[indexKer + j + offsetL];
            weight += w;
            total += (dataSrc[indexSrc + j]) * w;
          }
        }
        dataDst[indexDst++] = ((total + weight / 2) / weight);
      }
    }
  }