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);
    }
  }
  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));
  }
  /** Give it a grid and see if it computed a legitimate homography */
  @Test
  public void basicTest() {
    // create a grid an apply an arbitrary transform to it
    PlanarCalibrationTarget config = GenericCalibrationGrid.createStandardConfig();

    DenseMatrix64F R = RotationMatrixGenerator.eulerXYZ(0.02, -0.05, 0.01, null);
    Vector3D_F64 T = new Vector3D_F64(0, 0, -1000);
    Se3_F64 motion = new Se3_F64(R, T);

    List<Point2D_F64> observations = GenericCalibrationGrid.observations(motion, config);

    // compute the homography
    Zhang99ComputeTargetHomography alg = new Zhang99ComputeTargetHomography(config);

    assertTrue(alg.computeHomography(observations));

    DenseMatrix64F H = alg.getHomography();

    // test this homography property: x2 = H*x1
    List<Point2D_F64> gridPoints = config.points;
    for (int i = 0; i < observations.size(); i++) {
      Point2D_F64 a = GeometryMath_F64.mult(H, gridPoints.get(i), new Point2D_F64());

      double diff = a.distance(observations.get(i));
      assertEquals(0, diff, 1e-8);
    }
  }
  /** The two sets of points match perfectly and with a little bit of noise */
  @Test
  public void test3D() {
    for (double noise = 0; noise <= 0.01; noise += 0.01) {
      // can only correct small changes
      DenseMatrix64F R = RotationMatrixGenerator.eulerXYZ(0.01, -0.002, 0.03, null);
      Vector3D_F64 T = new Vector3D_F64(0.02, 0.03, 0.01);

      Se3_F64 tran = new Se3_F64(R, T);

      List<Point3D_F64> srcPts = UtilPoint3D_F64.random(-10, 10, 30, rand);
      List<Point3D_F64> srcOrig = UtilPoint3D_F64.copy(srcPts);
      List<Point3D_F64> modelPts = new ArrayList<Point3D_F64>();
      for (Point3D_F64 p : srcPts) {
        modelPts.add(SePointOps_F64.transform(tran, p, null));
      }

      // add noise
      UtilPoint3D_F64.noiseNormal(modelPts, noise, rand);

      PointModel<Point3D_F64> model = new PointModel<Point3D_F64>(modelPts);
      StoppingCondition stop = new StoppingCondition(10, 0.1 * noise / srcPts.size() + 1e-8);
      IterativeClosestPoint<Se3_F64, Point3D_F64> alg =
          new IterativeClosestPoint<Se3_F64, Point3D_F64>(stop, new MotionSe3PointSVD_F64());
      alg.setModel(model);

      alg.process(srcPts);

      Se3_F64 foundTran = alg.getPointsToModel();

      checkTransform(srcOrig, modelPts, foundTran, noise * 10 + 1e-8);
    }
  }
  @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++];
    }
  }
  @Override
  public void encode(CalibratedPoseAndPoint model, double[] param) {
    int paramIndex = 0;

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

      Se3_F64 se = model.getWorldToCamera(i);

      // force the "rotation matrix" to be an exact rotation matrix
      // otherwise Rodrigues will have issues with the noise
      if (!svd.decompose(se.getR())) throw new RuntimeException("SVD failed");

      DenseMatrix64F U = svd.getU(null, false);
      DenseMatrix64F V = svd.getV(null, false);

      CommonOps.multTransB(U, V, R);

      // extract Rodrigues coordinates
      RotationMatrixGenerator.matrixToRodrigues(R, rotation);

      param[paramIndex++] = rotation.unitAxisRotation.x * rotation.theta;
      param[paramIndex++] = rotation.unitAxisRotation.y * rotation.theta;
      param[paramIndex++] = rotation.unitAxisRotation.z * rotation.theta;

      Vector3D_F64 T = se.getT();

      param[paramIndex++] = T.x;
      param[paramIndex++] = T.y;
      param[paramIndex++] = T.z;
    }

    for (int i = 0; i < numPoints; i++) {
      Point3D_F64 p = model.getPoint(i);

      param[paramIndex++] = p.x;
      param[paramIndex++] = p.y;
      param[paramIndex++] = p.z;
    }
  }