@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;
    }
  }
  public void process(CalibratedPoseAndPoint model, double[] output) {
    int outputIndex = 0;

    for (int view = 0; view < model.getNumViews(); view++) {
      Se3_F64 worldToCamera = model.getWorldToCamera(view);

      FastQueue<PointIndexObservation> observedPts = observations.get(view).getPoints();

      for (int i = 0; i < observedPts.size; i++) {
        PointIndexObservation o = observedPts.data[i];

        Point3D_F64 worldPt = model.getPoint(o.pointIndex);

        SePointOps_F64.transform(worldToCamera, worldPt, cameraPt);

        output[outputIndex++] = cameraPt.x / cameraPt.z - o.obs.x;
        output[outputIndex++] = cameraPt.y / cameraPt.z - o.obs.y;
      }
    }
  }
  /**
   * Configures the residual function.
   *
   * @param model Model being processed
   * @param codec Decodes parameterized version of parameters
   * @param obs Contains camera observations of each point.
   */
  public void configure(
      ModelCodec<CalibratedPoseAndPoint> codec,
      CalibratedPoseAndPoint model,
      List<ViewPointObservations> obs) {
    this.model = model;
    this.codec = codec;
    this.observations = obs;

    numObservations = 0;
    for (int view = 0; view < model.getNumViews(); view++) {
      numObservations += obs.get(view).getPoints().size() * 2;
    }
  }