void CTomGineThread::AddSifts(const std::vector<blortRecognizer::Siftex>& sl)
{
	m_mutex.Lock();
		m_lastsiftexlist = sl;
		for(unsigned i=0; i<sl.size(); i++)
		  m_siftexlist.push_back(sl[i]);
	m_mutex.Unlock();
	
	double pos[3] = {0,0,0};
	double normal[3] = {0,0,0};
	double view[3] = {0,0,0};
	unsigned sc = sl.size();
	
	for(unsigned i=0; i<sc; i++){
	  pos[0] += sl[i].pos.x;
	  pos[1] += sl[i].pos.y;
	  pos[2] += sl[i].pos.z;
	  normal[0] += sl[i].normal.x;
	  normal[1] += sl[i].normal.y;
	  normal[2] += sl[i].normal.z;
	  view[0] += sl[i].viewray.x;
	  view[1] += sl[i].viewray.y;
	  view[2] += sl[i].viewray.z;
	}
	
	vec3 vPos = vec3(float(pos[0]/sc), float(pos[1]/sc), float(pos[2]/sc)); 
	//vec3 vNormal = vec3(float(normal[0]/sc), float(normal[1]/sc), float(normal[2]/sc)); 
	vec3 vView = vec3(float(view[0]/sc), float(view[1]/sc), float(view[2]/sc));
	
	vec3 p = vPos - vView;
    //vec3 p = sl[0].pos - sl[0].viewray;
	
	vec3 f = vView;	f.normalize();
	vec3 u = vec3(0,0,1);
	vec3 s;
	s.cross(u,f); s.normalize();
	u.cross(f,s); u.normalize();

	float rad = 0.0f;
	for(unsigned i=0; i<sc; i++){
		vec3 d = vPos - sl[i].pos;
		float l = d.length();
		if(rad < l)
		  rad=l;
	}
	m_viewscale.push_back(vec3(rad, rad, vView.length()));

	float fR[9] = { s.x, s.y, s.z,
					u.x, u.y, u.z,
					f.x, f.y, f.z};
	mat3 R = mat3(fR);
	TomGine::tgPose pose;
	pose.SetPose(R, p);
	m_viewlist.push_back(pose);
}
vec3 tgErrorMetric::GetMean(const TomGine::tgPose &p1, const TomGine::tgPose &p2)
{
	double err_x = 0.0;
	double err_y = 0.0;
	double err_z = 0.0;
	double dN = 1.0 / pointlist.size();
	for(unsigned i=0; i<pointlist.size(); i++){
		mat3 R1, R2;
		vec3 t1, t2;
		
		p1.GetPose(R1,t1);
		p2.GetPose(R2,t2);
		
		vec3 verr = (R1 * pointlist[i] + t1) - (R2 * pointlist[i] + t2);
		
		err_x += double(fabs(verr.x)) * dN;
		err_y += double(fabs(verr.y)) * dN;
		err_z += double(fabs(verr.z)) * dN;
	}
	vec3 err = vec3(err_x, err_y, err_z);
	return err;
}
void Recognizer3D::Convert(P::PoseCv& p1, TomGine::tgPose& p2){

    mat3 cR;
    vec3 ct;

    cR[0] = cvmGet(p1.R,0,0); cR[3] = cvmGet(p1.R,0,1); cR[6] = cvmGet(p1.R,0,2);
    cR[1] = cvmGet(p1.R,1,0); cR[4] = cvmGet(p1.R,1,1); cR[7] = cvmGet(p1.R,1,2);
    cR[2] = cvmGet(p1.R,2,0); cR[5] = cvmGet(p1.R,2,1); cR[8] = cvmGet(p1.R,2,2);

    ct.x = cvmGet(p1.t,0,0);
    ct.y = cvmGet(p1.t,1,0);
    ct.z = cvmGet(p1.t,2,0);

    p2.SetPose(cR,ct);
}
void getCamPose(const char* pose_cal_file, TomGine::tgPose& camPose){
    CDataFile cdfParams;
    if(!cdfParams.Load(pose_cal_file)){
        char errmsg[128];
        sprintf(errmsg, "[utilities::getCamPose] Can not open pose_cal file '%s'", pose_cal_file);
        throw std::runtime_error(errmsg);
    }

    vec3 t, r;
    mat3 R;

    std::string pose = cdfParams.GetString("pose");
    sscanf( pose.c_str(), "[%f %f %f] [%f %f %f]",
            &(t.x), &(t.y), &(t.z), &(r.x), &(r.y), &(r.z) );

    R.fromRotVector(r);

    camPose.SetPose(R,t);
}
void ConvertCv2tgPoseWorld(	const cv::Mat_<double> &R, const cv::Mat_<double> &T, 
                                const TomGine::tgPose &camPose, TomGine::tgPose &pose)
{
    mat3 r;
    vec3 t, rvec;
    TomGine::tgPose pose_cam_coord, camPoseT;
    camPoseT = camPose.Transpose();

    t.x = T(0,0);
    t.y = T(1,0);
    t.z = T(2,0);
    rvec.x = R(0,0);
    rvec.y = R(1,0);
    rvec.z = R(2,0);
    r.fromRotVector(rvec);
    r = r.transpose();

    pose_cam_coord.SetPose(r,t);

    pose = camPoseT * pose_cam_coord;
}
void getCamPoseCvCheckerboard(const cv::Mat_<double> &R, const cv::Mat_<double> &T, TomGine::tgPose &camPose)
{
    vec3 r, t;
    mat3 Rm;
    cv::Mat_<double> cv_Rm = cv::Mat_<double> ( 3,3 );
    cv::Mat_<double> cv_rvec = cv::Mat_<double> ( 3,1 );
    cv::Mat_<double> cv_tvec = cv::Mat_<double> ( 3,1 );
    cv::Rodrigues(R, cv_Rm);
    cv::transpose(cv_Rm, cv_Rm);
    cv_tvec = -cv_Rm * T;				// t = -R^T * t + t
    cv::Rodrigues(cv_Rm, cv_rvec);

    r.x = cv_rvec(0,0);
    r.y = cv_rvec(1,0);
    r.z = cv_rvec(2,0);
    t.x = cv_tvec(0,0);
    t.y = cv_tvec(1,0);
    t.z = cv_tvec(2,0);
    Rm.fromRotVector(r);

    camPose.SetPose(Rm,t);
}
bool Recognizer3D::learnSifts(IplImage* tFrame, const TomGine::tgModel& m1, const TomGine::tgPose& pose)
{	
    P::Array<P::KeypointDescriptor*> m_tmp_keys;

    mat3 R, Rt;
    vec3 t;
    TomGine::tgModel m2 = m1;
    m_lastsiftexlist.clear();

    // Transform model vertices to camera coordinats
    pose.GetPose(R,t);
    Rt = R.transpose();
    for(unsigned int i=0; i<m2.m_vertices.size(); i++)
    {
        m2.m_vertices[i].pos = (R * m2.m_vertices[i].pos) + t;
        m2.m_vertices[i].normal = R * m2.m_vertices[i].normal;
    }

    IplImage* tImg = 0;
    IplImage* grey = 0;
    float dcpfx = 1/m_cp.fx;
    float dcpfy = 1/m_cp.fy;

    tImg = cvCreateImage ( cvGetSize ( tFrame ), 8, 3 );
    grey = cvCreateImage ( cvGetSize ( tFrame ), 8, 1 );

    // undistort
    cvRemap(tFrame, tImg, pMapX, pMapY );

    // convert to gray scale image
    cvConvertImage(tImg, grey);

    //EXPERIMENTAL:
    //    cv::Mat descriptors;
    //    std::vector<cv::KeyPoint> keypoints;
    //    cv_detect->extract(cv::Mat(grey, true), descriptors, keypoints);
    //
    //    pal_blort::CvDetect3D::cvKeypoints2BlortKeyPoints(
    //            descriptors, keypoints, m_image_keys);
    // END OF EXPERIMENTAL

    sift.Operate(grey, m_image_keys);

    for(unsigned int i=0; i<m_image_keys.Size(); i++)
    {
        vec3 point, normal;
        std::vector<vec3> pl;		// point list
        std::vector<vec3> nl;		// normal list
        std::vector<double> zl; // z-value list (depth)
        float u,v,x,y;

        u = (float)m_image_keys[i]->p.x;
        v = (float)m_image_keys[i]->p.y;
        x = (u-m_cp.cx) * dcpfx;
        y = (v-m_cp.cy) * dcpfy;

        // Create ray from pixel
        TomGine::tgRay ray;
        ray.start = vec3(0.0f,0.0f,0.0f);
        ray.dir =  vec3(x,y,1.0f);
        ray.dir.normalize();

        if(	TomGine::tgCollission::IntersectRayModel(pl, nl, zl, ray, m2)
            && !pl.empty()
            && !zl.empty())
            {
            // determine nearest intersection point
            unsigned int idx_min = 0;
            float z_min = zl[0];
            for(unsigned int idx=0; idx<zl.size(); idx++){
                if(zl[idx] < z_min){
                    idx_min = idx;
                    z_min = zl[idx];
                }
            }

            if(z_min > 0.0f){
                // Transform to object space
                point = (Rt * (pl[idx_min] - t));
                normal = (Rt * nl[idx_min]);
                vec3 r = (Rt * ray.dir) * zl[idx_min];

                Siftex s;
                s.pos = point;
                s.normal = normal;
                s.viewray = r;

                normal.normalize();
                r.normalize();

                if( (r * normal) < -0.1f)
                {
                    // Save sift
                    m_siftexlist.push_back(s);
                    m_lastsiftexlist.push_back(s);
                    m_image_keys[i]->SetPos(point.x, point.y, point.z);
                    m_tmp_keys.PushBack(m_image_keys[i]);
                }
            }
        }

    }

    if(m_tmp_keys.Size() > 0)
    {
        m_sift_model_learner.AddToModel(m_tmp_keys, *m_sift_models[0]);
        ROS_INFO("[Recognizer3D::learnSifts] added %d sifts to model\n", m_tmp_keys.Size());
        m_model_loaded = true;
    }

    if(m_display){
        display_image = cv::Mat(tImg, true);
    }

    cvReleaseImage(&tImg);
    cvReleaseImage(&grey);

    return true;
}
// Converts pose from Camera coordinates to world space
void ConvertCam2World(const TomGine::tgPose& pC, const TomGine::tgPose& camPose, TomGine::tgPose& pW){

    pW = camPose.Transpose() * pC;
}
int main(int argc, char *argv[] )
{
    std::string config_root = argv[1];

    //this line should force opengl to run software rendering == no GPU
    //putenv("LIBGL_ALWAYS_INDIRECT=1");

    ros::init(argc, argv, "blort_learnsifts");
    ros::NodeHandle nh("blort_learnsifts");
    image_transport::ImageTransport it(nh);
    image_transport::Subscriber image_sub = it.subscribe("/blort_image", 1, &imageCb);
    cam_info_sub = nh.subscribe("/blort_camera", 1, &cam_info_callback);
    ros::Publisher confidences_pub = nh.advertise<blort_ros::TrackerConfidences>("confidences", 100);

    printf("\n Learnsifts \n\n");

    printf(" Tracker control\n");
    printf(" -------------------------------------------\n");
    printf(" [q,Esc] Quit program\n");
    printf(" [Return] Save SIFT and ply models.\n");
    printf(" [Space] Save texture face and extract SIFTS.\n");
    printf(" -------------------------------------------\n");
    printf(" [4] Texture edge tracking\n");
    printf(" [5] Texture color tracking\n");
    printf(" [a] High accuracy tracking (less robust)\n");
    printf(" [e] Show edges of image\n");
    printf(" [i] Print tracking information to console\n");
    printf(" [l] Lock/Unlock tracking\n");
    printf(" [m] Switch display mode of model\n");
    printf(" [p] Show particles\n");
    printf(" [r] Reset tracker to initial pose\n");
    printf(" [s] Save model to file (including textures)\n");
    printf(" [t] Capture model texture from image\n");
    printf(" [u] Untexture/remove texture from model\n");
    printf(" \n\n ");

    blortGLWindow::Event event;
    TomGine::tgTimer timer;
    std::vector<vec3> view_list;

    // File names
    //FIXME: make these ROS parameters or eliminate them and use the content as parameters
    std::string pose_cal = pal_blort::addRoot("bin/pose.cal", config_root);
    std::string tracking_ini(pal_blort::addRoot("bin/tracking.ini", config_root));
    std::vector<std::string> ply_models, sift_files, model_names;
    std::string ply_model, sift_file, model_name;

    GetPlySiftFilenames(tracking_ini.c_str(), ply_models, sift_files, model_names);
    ply_model = ply_models[0]; sift_file = sift_files[0]; model_name = model_names[0];
    printf("Object name: %s\n", model_name.c_str());
    sift_file = pal_blort::addRoot(sift_file, config_root);

    // Get Parameter from file
    TomGine::tgPose camPose, camPoseT;
    Tracking::Tracker::Parameter trackParams;
    GetTrackingParameter(trackParams, tracking_ini.c_str(), config_root);
    getCamPose(pose_cal.c_str(), camPose);
    camPoseT = camPose.Transpose();

    printf("=> Getting camera intrinsics ... ");
    // wait for the first camera_info message
    need_cam_init = true;
    while(need_cam_init)
    {
        ros::spinOnce();
    }
    setCameraPose(tgCamParams, pose_cal.c_str());
    trackParams.camPar = tgCamParams;

    printf("OK\n");

    // Initialise image
    IplImage *image = cvCreateImage( cvSize(camera_info.width, camera_info.height), 8, 3 ); //FIXME dirty

    printf("=> Initialising tracker ... ");

    // Create OpenGL Window and Tracker
    CRecognizerThread* pThreadRec =
            new CRecognizerThread(blortRecognizer::CameraParameter(camera_info));
    blortGLWindow::GLWindow window(trackParams.camPar.width, trackParams.camPar.height, "Training");
    Tracking::TextureTracker tracker;
    tracker.init(trackParams);
    float rec_time = 0.0f;
    printf("OK\n");

    // Model for Tracker
    TomGine::tgPose trPose;
    trPose.t = vec3(0.0, 0.1, 0.0);
    trPose.Rotate(0.0f, 0.0f, 0.5f);    
    std::string modelFullPath = pal_blort::addRoot(ply_model, config_root);
    printf("=> Trying to get the object model from file: %s\n", modelFullPath.c_str());
    int modelID = tracker.addModelFromFile(modelFullPath.c_str(), trPose, model_name.c_str(), true);
    printf(" OK\n");
    tracker.setLockFlag(true);



    // Model for Recognizer / TomGine (ray-model intersection)
    TomGine::tgModel model;
    TomGine::tgPose tg_pose;
    TomGine::tgModelLoader modelloader;
    modelloader.LoadPly(model, pal_blort::addRoot(ply_model, config_root).c_str());

    Tracking::movement_state movement = Tracking::ST_FAST;
    Tracking::quality_state quality = Tracking::ST_OK;
    Tracking::confidence_state confidence = Tracking::ST_BAD;

    need_new_image = true;
    while(need_new_image)
    {
        ros::spinOnce();
    }
    *image = lastImage;

    pThreadRec->Event();

    bool quit = false;
    while(!quit)
    {
        tracker.getModelMovementState(modelID, movement);
        tracker.getModelQualityState(modelID, quality);
        tracker.getModelConfidenceState(modelID, confidence);

        if(confidence == Tracking::ST_GOOD && movement == Tracking::ST_STILL && quality != Tracking::ST_LOCKED)
        {
            ROS_WARN("Sorry, no learning with this node.");
            ROS_INFO("New initial pose fixed: \nt: [%f, %f, %f] \nq:[%f, %f, %f, %f]",
                     trPose.t.x,
                     trPose.t.y,
                     trPose.t.z,
                     trPose.q.x,
                     trPose.q.y,
                     trPose.q.z,
                     trPose.q.w);
        }

        //recovery, if object lost
        if(quality == Tracking::ST_LOST && rec_time > 0.2f)
        {
//            TomGine::tgPose recPose;
//            float conf;
//
//            pThreadRec->Recognize(image, recPose, conf);
//            if(conf > recovery_conf_threshold)
//            {
//                ConvertCam2World(recPose, camPose, trPose);
//                tracker.setModelInitialPose(modelID, trPose);
//                tracker.resetUnlockLock();
//            }
//            //tracker.reset();
//
//            ROS_INFO("orig conf: %f", conf);
//
//            // if the recovery's confidence is high enough then
//            // kick the tracker out of this state and let it try
//
//
//            rec_time = 0.0f;
//            ROS_WARN("Tried to recover for the %d. time.", ++rec3dcounter);
        }else{
            rec_time += timer.Update();
        }

        if(!need_new_image)
        {
            // Get image
            timer.Update();
            *image = lastImage;

            // Track object
            tracker.image_processing((unsigned char*)image->imageData);
            tracker.track();

            tracker.drawImage(0);
            tracker.drawCoordinates();
            tracker.getModelPose(modelID, trPose);
            tracker.drawResult(2.0f);
            tg_pose = trPose;
            need_new_image = true;
        }
        // Keyboard inputs:
        while(window.GetEvent(event)){
            quit = !InputControl(&tracker, event, config_root);
            if(event.type == blortGLWindow::TMGL_Press)
            {
                if(event.input == blortGLWindow::TMGL_Space)
                {
                    // When hitting space bar -> Learn new sift features
                    TomGine::tgPose pT1;
                    TomGine::tgPose pT2, pT3, pT4;
                    tracker.getModelPoseCamCoords(modelID, pT1);

                    mat3 R; vec3 a;
                    pT1.GetPose(R, a);
                    a = R.transpose() * pT1.t;
                    a.normalize();
                    view_list.push_back(a);

                    pT2 = pT1;
                    pThreadRec->LearnSifts(image, model, pT2);

                    std::vector<blortRecognizer::Siftex> sl;
                    pThreadRec->GetLastSifts(sl);
                    tracker.textureFromImage(true);
                }
                else if(event.input == blortGLWindow::TMGL_Return)
                {
                    // When hitting Return -> Save sift model and recognize
                    //TomGine::tgPose recPose;
                    //float conf;
                    pThreadRec->SaveSiftModel(sift_file.c_str());
                    //pThreadRec->Recognize(image, recPose, conf);
                    //ConvertCam2World(recPose, camPose, trPose);
                    //tracker.setModelInitialPose(modelID, trPose);
                    //tracker.reset(modelID);
                    tracker.saveModels(pal_blort::addRoot("Resources/ply/", config_root).c_str());
                }
            }
            event.type = blortGLWindow::TMGL_None;
        }

        window.Update();

        //publish tracker inner confidences just for fun
        Tracking::ModelEntry* myModelEntry = tracker.getModelEntry(modelID);
        blort_ros::TrackerConfidences tr_conf;
        tr_conf.edgeConf = myModelEntry->c_edge;
        tr_conf.confThreshold = myModelEntry->c_th;
        tr_conf.lostConf = myModelEntry->c_lost;
        tr_conf.distance = myModelEntry->t;
	confidences_pub.publish(tr_conf);

        window.Activate();
        ros::spinOnce();
    }
    delete(pThreadRec);
    cvReleaseImage(&image);
}