@Test
  public void meanShift() {
    int w = 32;

    CirculantTracker<ImageFloat32> alg =
        new CirculantTracker<ImageFloat32>(1f / 16, 0.2, 1e-2, 0.075, 1.0, w, 255, interp);

    int peakX = 13;
    int peakY = 17;

    alg.getResponse().reshape(w, w);
    for (int i = 0; i < w; i++) {
      double b = Math.exp(-(i - peakY) * (i - peakY) / 3.0);
      for (int j = 0; j < w; j++) {
        double a = Math.exp(-(j - peakX) * (j - peakX) / 3.0);

        alg.getResponse().set(j, i, a * b);
      }
    }

    alg.subpixelPeak(peakX - 2, peakY + 1);

    assertEquals(2, alg.offX, 0.3);
    assertEquals(-1, alg.offY, 0.3);
  }
  @Test
  public void computeGaussianWeights() {
    int w = 16;
    CirculantTracker<ImageFloat32> alg =
        new CirculantTracker<ImageFloat32>(1f / 16, 0.2, 1e-2, 0.075, 1.0, w, 255, interp);

    alg.gaussianWeight.reshape(w, w);
    alg.gaussianWeightDFT.reshape(w, w);

    alg.computeGaussianWeights(w);

    centeredSymmetricChecks(alg.gaussianWeight, true);
  }
  @Test
  public void computeAlphas() {
    InterleavedF64 yf = new InterleavedF64(width, height, 2);
    InterleavedF64 kf = new InterleavedF64(width, height, 2);
    InterleavedF64 alphaf = new InterleavedF64(width, height, 2);

    ImageMiscOps.fillUniform(yf, rand, -10, 10);
    ImageMiscOps.fillUniform(kf, rand, -10, 10);
    ImageMiscOps.fillUniform(alphaf, rand, -10, 10);

    float lambda = 0.01f;
    CirculantTracker.computeAlphas(yf, kf, lambda, alphaf);

    for (int y = 0; y < height; y++) {
      for (int x = 0; x < width; x++) {
        Complex64F a = new Complex64F(yf.getBand(x, y, 0), yf.getBand(x, y, 1));
        Complex64F b = new Complex64F(kf.getBand(x, y, 0) + lambda, kf.getBand(x, y, 1));

        Complex64F c = new Complex64F();
        ComplexMath64F.div(a, b, c);

        double foundReal = alphaf.getBand(x, y, 0);
        double foundImg = alphaf.getBand(x, y, 1);

        assertEquals(c.real, foundReal, 1e-4);
        assertEquals(c.imaginary, foundImg, 1e-4);
      }
    }
  }
  @Test
  public void elementMultConjB() {
    InterleavedF64 a = new InterleavedF64(width, height, 2);
    InterleavedF64 b = new InterleavedF64(width, height, 2);
    InterleavedF64 c = new InterleavedF64(width, height, 2);

    ImageMiscOps.fillUniform(a, rand, -10, 10);
    ImageMiscOps.fillUniform(b, rand, -10, 10);
    ImageMiscOps.fillUniform(c, rand, -10, 10);

    CirculantTracker.elementMultConjB(a, b, c);

    for (int y = 0; y < height; y++) {
      for (int x = 0; x < width; x++) {
        Complex64F aa = new Complex64F(a.getBand(x, y, 0), a.getBand(x, y, 1));
        Complex64F bb = new Complex64F(b.getBand(x, y, 0), b.getBand(x, y, 1));

        Complex64F cc = new Complex64F();
        ComplexMath64F.conj(bb, bb);
        ComplexMath64F.mult(aa, bb, cc);

        double foundReal = c.getBand(x, y, 0);
        double foundImg = c.getBand(x, y, 1);

        assertEquals(cc.real, foundReal, 1e-4);
        assertEquals(cc.imaginary, foundImg, 1e-4);
      }
    }
  }
  @Test
  public void performLearning() {
    float interp_factor = 0.075f;

    ImageFloat32 a = new ImageFloat32(20, 25);
    ImageFloat32 b = new ImageFloat32(20, 25);

    ImageMiscOps.fill(a, 100);
    ImageMiscOps.fill(b, 200);

    CirculantTracker<ImageFloat32> alg =
        new CirculantTracker<ImageFloat32>(1f / 16, 0.2, 1e-2, 0.075, 1.0, 64, 255, interp);
    alg.initialize(a, 0, 0, 20, 25);

    // copy its internal value
    ImageFloat64 templateC = new ImageFloat64(alg.template.width, alg.template.height);
    templateC.setTo(alg.template);

    // give it two images
    alg.performLearning(b);

    // make sure the images aren't full of zero
    assertTrue(Math.abs(ImageStatistics.sum(templateC)) > 0.1);
    assertTrue(Math.abs(ImageStatistics.sum(alg.template)) > 0.1);

    int numNotSame = 0;
    // the result should be an average of the two
    for (int i = 0; i < a.data.length; i++) {
      if (Math.abs(a.data[i] - alg.templateNew.data[i]) > 1e-4) numNotSame++;

      // should be more like the original one than the new one
      double expected =
          templateC.data[i] * (1 - interp_factor) + interp_factor * alg.templateNew.data[i];
      double found = alg.template.data[i];

      assertEquals(expected, found, 1e-4);
    }

    // make sure it is actually different
    assertTrue(numNotSame > 100);
  }
  @Test
  public void computeCosineWindow() {
    ImageFloat64 found = new ImageFloat64(20, 25);

    CirculantTracker.computeCosineWindow(found);

    // should be between 0 and 1
    for (int i = 0; i < found.data.length; i++) {
      assertTrue(found.data[i] >= 0 && found.data[i] <= 1);
    }

    centeredSymmetricChecks(found, false);
  }
  @Test
  public void basicTrackingCheck() {
    ImageFloat32 a = new ImageFloat32(30, 35);
    ImageFloat32 b = new ImageFloat32(30, 35);

    // randomize input image and move it
    GImageMiscOps.fillUniform(a, rand, 0, 200);
    GImageMiscOps.fillUniform(b, rand, 0, 200);

    CirculantTracker<ImageFloat32> alg =
        new CirculantTracker<ImageFloat32>(1f / 16, 0.2, 1e-2, 0.075, 1.0, 64, 255, interp);
    alg.initialize(a, 5, 6, 20, 25);

    shiftCopy(2, 4, a, b);
    alg.performTracking(b);

    double tolerance = 1;

    Rectangle2D_F32 r = alg.getTargetLocation();
    assertEquals(5 + 2, r.x0, tolerance);
    assertEquals(6 + 4, r.y0, tolerance);
  }
  @Test
  public void imageDotProduct() {
    ImageFloat64 a = new ImageFloat64(width, height);
    ImageMiscOps.fillUniform(a, rand, 0, 10);

    double total = 0;
    for (int y = 0; y < height; y++) {
      for (int x = 0; x < width; x++) {
        total += a.get(x, y) * a.get(x, y);
      }
    }
    double found = CirculantTracker.imageDotProduct(a);
    assertEquals(total, found, 1e-8);
  }
  public void dense_gauss_kernel(int offX, int offY) {
    ImageFloat64 region = new ImageFloat64(32, 32);
    ImageFloat64 target = new ImageFloat64(32, 32);
    ImageFloat64 k = new ImageFloat64(32, 32);

    CirculantTracker<ImageFloat32> alg =
        new CirculantTracker<ImageFloat32>(1f / 16, 0.2, 1e-2, 0.075, 1.0, 32, 255, interp);
    alg.initialize(new ImageFloat32(32, 32), 0, 0, 32, 32);

    // create a shape inside the image
    GImageMiscOps.fillRectangle(region, 200, 10, 15, 5, 7);

    // copy a shifted portion of the region
    shiftCopy(offX, offY, region, target);

    // process and see if the peak is where it should be
    alg.dense_gauss_kernel(0.2f, region, target, k);

    int maxX = -1, maxY = -1;
    double maxValue = -1;
    for (int y = 0; y < k.height; y++) {
      for (int x = 0; x < k.width; x++) {
        if (k.get(x, y) > maxValue) {
          maxValue = k.get(x, y);
          maxX = x;
          maxY = y;
        }
      }
    }

    int expectedX = k.width / 2 - offX;
    int expectedY = k.height / 2 - offY;

    assertEquals(expectedX, maxX);
    assertEquals(expectedY, maxY);
  }
  /**
   * Check a few simple motions. It seems to be accurate to within 1 pixel. Considering alphas seems
   * to be the issue
   */
  @Test
  public void updateTrackLocation() {
    ImageFloat32 a = new ImageFloat32(100, 100);
    ImageFloat32 b = new ImageFloat32(100, 100);

    // randomize input image and move it
    GImageMiscOps.fillUniform(a, rand, 0, 200);
    GImageMiscOps.fillUniform(b, rand, 0, 200);
    shiftCopy(0, 0, a, b);

    CirculantTracker<ImageFloat32> alg =
        new CirculantTracker<ImageFloat32>(1f / 16, 0.2, 1e-2, 0.075, 1.0, 64, 255, interp);
    alg.initialize(a, 5, 6, 20, 25);

    alg.updateTrackLocation(b);

    // only pixel level precision.
    float tolerance = 1f;

    // No motion motion
    Rectangle2D_F32 r = alg.getTargetLocation();
    assertEquals(5, r.x0, tolerance);
    assertEquals(6, r.y0, tolerance);

    // check estimated motion
    GImageMiscOps.fillUniform(b, rand, 0, 200);
    shiftCopy(-3, 2, a, b);
    alg.updateTrackLocation(b);
    r = alg.getTargetLocation();
    assertEquals(5 - 3, r.x0, tolerance);
    assertEquals(6 + 2, r.y0, tolerance);

    // try out of bounds case
    GImageMiscOps.fillUniform(b, rand, 0, 200);
    shiftCopy(-6, 0, a, b);
    alg.updateTrackLocation(b);
    assertEquals(5 - 6, r.x0, tolerance);
    assertEquals(6, r.y0, tolerance);
  }