@Test
  public void incorrectInput() {
    init(30, false);

    // compute true essential matrix
    DenseMatrix64F E = MultiViewOps.createEssential(worldToCamera.getR(), worldToCamera.getT());

    // create an alternative incorrect matrix
    Vector3D_F64 T = worldToCamera.getT().copy();
    T.x += 0.1;
    DenseMatrix64F Emod = MultiViewOps.createEssential(worldToCamera.getR(), T);

    ModelFitter<DenseMatrix64F, AssociatedPair> alg = createAlgorithm();

    // compute and compare results
    assertTrue(alg.fitModel(pairs, Emod, found));

    // normalize to allow comparison
    CommonOps.divide(E.get(2, 2), E);
    CommonOps.divide(Emod.get(2, 2), Emod);
    CommonOps.divide(found.get(2, 2), found);

    double error0 = 0;
    double error1 = 0;

    // very crude error metric
    for (int i = 0; i < 9; i++) {
      error0 += Math.abs(Emod.data[i] - E.data[i]);
      error1 += Math.abs(found.data[i] - E.data[i]);
    }

    //		System.out.println("error "+error1+"   other "+error0);
    assertTrue(error1 < error0);
  }
  @Override
  public void decode(double[] param, CalibratedPoseAndPoint outputModel) {

    int paramIndex = 0;

    // first decode the transformation
    for (int i = 0; i < numViews; i++) {
      // don't decode if it is already known
      if (knownView[i]) continue;

      Se3_F64 se = outputModel.getWorldToCamera(i);

      rotation.setParamVector(param[paramIndex++], param[paramIndex++], param[paramIndex++]);

      RotationMatrixGenerator.rodriguesToMatrix(rotation, se.getR());

      Vector3D_F64 T = se.getT();
      T.x = param[paramIndex++];
      T.y = param[paramIndex++];
      T.z = param[paramIndex++];
    }

    // now decode the points
    for (int i = 0; i < numPoints; i++) {
      Point3D_F64 p = outputModel.getPoint(i);
      p.x = param[paramIndex++];
      p.y = param[paramIndex++];
      p.z = param[paramIndex++];
    }
  }
  public void evaluateObservationNoise(
      double minPixelError, double maxPixelError, int N, boolean isPlanar) {
    System.out.println("------------------------");

    rand = new Random(234);

    for (int i = 0; i <= N; i++) {
      double mag = (maxPixelError - minPixelError) * i / N + minPixelError;

      init(NUM_POINTS, false, isPlanar);
      addPixelNoise(mag);

      if (!target.process(observationPose, found))
        throw new RuntimeException("Not expected to fail");

      double expectedEuler[] = RotationMatrixGenerator.matrixToEulerXYZ(motion.getR());
      double foundEuler[] = RotationMatrixGenerator.matrixToEulerXYZ(found.getR());

      Vector3D_F64 expectedTran = motion.getT();
      Vector3D_F64 foundTran = found.getT();

      double errorTran = expectedTran.distance(foundTran);
      double errorEuler = 0;
      double sum = 0;
      for (int j = 0; j < 3; j++) {
        double e = expectedEuler[j] - foundEuler[j];
        errorEuler += e * e;
        sum += expectedEuler[j];
      }
      errorEuler = 100 * Math.sqrt(errorEuler) / Math.sqrt(sum);

      System.out.printf("%3d angle %6.2f%% translation %6.2e\n", i, errorEuler, errorTran);
    }
  }
  @Override
  public double computeDistance(PointVectorNN pv) {
    n.set(pv.p.x - model.center.x, pv.p.y - model.center.y, pv.p.z - model.center.z);
    n.normalize();

    if (Math.abs(n.dot(pv.normal)) < tolAngleCosine) return Double.MAX_VALUE;

    return Math.abs(Distance3D_F64.distance(model, pv.p));
  }
  @Test
  public void multTran_2d_3d() {
    Vector2D_F64 a = new Vector2D_F64(-1, 2);
    DenseMatrix64F M = new DenseMatrix64F(3, 3, true, 1, 2, 3, 4, 5, 6, 7, 8, 9);
    Vector3D_F64 c = new Vector3D_F64();

    GeometryMath_F64.multTran(M, a, c);

    assertEquals(14, c.getX(), GrlConstants.DOUBLE_TEST_TOL);
    assertEquals(16, c.getY(), GrlConstants.DOUBLE_TEST_TOL);
    assertEquals(18, c.getZ(), GrlConstants.DOUBLE_TEST_TOL);
  }
  @Test
  public void sub() {
    Vector3D_F64 a = new Vector3D_F64(1, 2, 3);
    Vector3D_F64 b = new Vector3D_F64(3, 1, 4);
    Vector3D_F64 c = new Vector3D_F64();

    GeometryMath_F64.sub(a, b, c);

    assertEquals(-2, c.getX(), GrlConstants.DOUBLE_TEST_TOL);
    assertEquals(1, c.getY(), GrlConstants.DOUBLE_TEST_TOL);
    assertEquals(-1, c.getZ(), GrlConstants.DOUBLE_TEST_TOL);
  }
  @Test
  public void add_scale() {
    Vector3D_F64 a = new Vector3D_F64(1, 2, 3);
    Vector3D_F64 b = new Vector3D_F64(3, 1, 4);
    Vector3D_F64 c = new Vector3D_F64();

    GeometryMath_F64.add(2, a, -1, b, c);

    assertEquals(-1, c.getX(), GrlConstants.DOUBLE_TEST_TOL);
    assertEquals(3, c.getY(), GrlConstants.DOUBLE_TEST_TOL);
    assertEquals(2, c.getZ(), GrlConstants.DOUBLE_TEST_TOL);
  }
  @Test
  public void addMult() {
    Vector3D_F64 a = new Vector3D_F64(1, 2, 3);
    Vector3D_F64 b = new Vector3D_F64(2, 3, 4);
    Vector3D_F64 c = new Vector3D_F64();
    DenseMatrix64F M = new DenseMatrix64F(3, 3, true, 1, 1, 1, 1, 1, 1, 1, 1, 1);

    GeometryMath_F64.addMult(a, M, b, c);

    assertEquals(10, c.getX(), GrlConstants.DOUBLE_TEST_TOL);
    assertEquals(11, c.getY(), GrlConstants.DOUBLE_TEST_TOL);
    assertEquals(12, c.getZ(), GrlConstants.DOUBLE_TEST_TOL);
  }
  public void evaluateMinimal(double pixelSigma, boolean isPlanar, int numTrials) {

    // make sure each test case has the same random seed
    rand = new Random(234);

    double totalEuler = 0;
    double totalTran = 0;
    int totalFail = 0;

    for (int i = 0; i < numTrials; i++) {
      init(target.getMinimumPoints(), false, isPlanar);
      addPixelNoise(pixelSigma);

      if (!target.process(observationPose, found)) {
        totalFail++;
        continue;
      }

      double expectedEuler[] = RotationMatrixGenerator.matrixToEulerXYZ(motion.getR());
      double foundEuler[] = RotationMatrixGenerator.matrixToEulerXYZ(found.getR());

      Vector3D_F64 expectedTran = motion.getT();
      Vector3D_F64 foundTran = found.getT();

      double errorTran = expectedTran.distance(foundTran);
      double errorEuler = 0;
      double sum = 0;
      for (int j = 0; j < 3; j++) {
        double e = expectedEuler[j] - foundEuler[j];
        errorEuler += e * e;
        sum += expectedEuler[j];
      }
      errorEuler = 100 * Math.sqrt(errorEuler) / Math.sqrt(sum);

      //			System.out.println(errorEuler);

      totalEuler += errorEuler;
      totalTran += errorTran;
    }

    int N = numTrials - totalFail;

    System.out.printf(
        "%20s N = %d failed %.3f%% euler = %3f%% tran = %5f\n",
        name,
        target.getMinimumPoints(),
        (totalFail / (double) numTrials),
        (totalEuler / N),
        (totalTran / N));
  }
  /**
   * Randomly generate points on a plane by randomly selecting two vectors on the plane using cross
   * products
   */
  private List<Point3D_F64> randPointOnPlane(PlaneNormal3D_F64 plane, int N) {
    Vector3D_F64 v = new Vector3D_F64(-2, 0, 1);
    Vector3D_F64 a = UtilTrig_F64.cross(plane.n, v);
    a.normalize();
    Vector3D_F64 b = UtilTrig_F64.cross(plane.n, a);
    b.normalize();

    List<Point3D_F64> ret = new ArrayList<Point3D_F64>();

    for (int i = 0; i < N; i++) {
      double v0 = rand.nextGaussian();
      double v1 = rand.nextGaussian();

      Point3D_F64 p = new Point3D_F64();
      p.x = plane.p.x + v0 * a.x + v1 * b.x;
      p.y = plane.p.y + v0 * a.y + v1 * b.y;
      p.z = plane.p.z + v0 * a.z + v1 * b.z;

      ret.add(p);
    }

    return ret;
  }