float CalculateError(const Mat_<float>& ground_truth_shape, const Mat_<float>& predicted_shape) {
	Mat_<float> temp;
	//temp = ground_truth_shape.rowRange(36, 37)-ground_truth_shape.rowRange(45, 46);
	temp = ground_truth_shape.rowRange(1, 2) - ground_truth_shape.rowRange(6, 7);
	float x = mean(temp.col(0))[0];
	float y = mean(temp.col(1))[1];
	float interocular_distance = sqrt(x*x + y*y);
	float sum = 0;
	for (int i = 0; i < ground_truth_shape.rows; i++) {
		sum += norm(ground_truth_shape.row(i) - predicted_shape.row(i));
	}
	return sum / (ground_truth_shape.rows*interocular_distance);
}
Point3f GetPupilPosition(Mat_<double> eyeLdmks3d){
	
	eyeLdmks3d = eyeLdmks3d.t();

	Mat_<double> irisLdmks3d = eyeLdmks3d.rowRange(0,8);

	Point3f p (mean(irisLdmks3d.col(0))[0], mean(irisLdmks3d.col(1))[0], mean(irisLdmks3d.col(2))[0]);
	return p;
}
void adjustImage(Mat_<uchar>& img,
                 Mat_<float>& ground_truth_shape,
                 BoundingBox& bounding_box){
	/*imshow("test",img);
	waitKey(0);*/
    float left_x  = max(1.0, (double)bounding_box.centroid_x - bounding_box.width*0.8);
    float top_y   = max(1.0, (double)bounding_box.centroid_y - bounding_box.height*0.8);
    float right_x = min(img.cols-1.0,(double)bounding_box.centroid_x+bounding_box.width*0.8);
    float bottom_y= min(img.rows-1.0,(double)bounding_box.centroid_y+bounding_box.height*0.8);
    
	img = img.rowRange((int)top_y,(int)bottom_y).colRange((int)left_x,(int)right_x).clone();
	

	

	bounding_box.start_x = ((int)right_x - (int)left_x)*CROP;
	bounding_box.start_y = ((int)bottom_y - (int)top_y)*CROP;
	bounding_box.width = ((int)right_x - (int)left_x)*(1-2*CROP)-1;
	bounding_box.height = ((int)bottom_y - (int)top_y)*(1-2*CROP)-1;
	bounding_box.centroid_x = bounding_box.start_x + bounding_box.width / 2.0;
	bounding_box.centroid_y = bounding_box.start_y + bounding_box.height / 2.0;
    
    for(int i=0;i<ground_truth_shape.rows;i++){
        ground_truth_shape(i,0) = ground_truth_shape(i,0)-left_x;
        ground_truth_shape(i,1) = ground_truth_shape(i,1)-top_y;
    }
	//imshow("test1",img);
	//waitKey(0);
	float ori_height=img.rows;
	float ori_weight=img.cols;

	if (ori_height>MAXHEIGHT_POS)
	{
		float scale=MAXHEIGHT_POS/ori_height;
		resize(img,img,Size(ori_weight*scale,ori_height*scale));
		bounding_box.start_x*=scale;
		bounding_box.start_y*=scale;
		bounding_box.centroid_x*=scale;
		bounding_box.centroid_y*=scale;
		bounding_box.width*=scale;
		bounding_box.height*=scale;
		for(int i=0;i<ground_truth_shape.rows;i++){
			ground_truth_shape(i,0) *= scale;
			ground_truth_shape(i,1) *= scale;
		}
	} 
	/*rectangle(img,Point(bounding_box.start_x,bounding_box.start_y),Point(bounding_box.start_x+bounding_box.width,bounding_box.start_y+bounding_box.height),Scalar(255));
	for(int i=0;i<ground_truth_shape.rows;i++){
		circle(img,Point(ground_truth_shape(i,0),ground_truth_shape(i,1)),3,Scalar(255));
	}
	imshow("test2",img);
	waitKey(0);*/
}
int MultipleGraphsClassifier::predict(DisjointSetForest &segmentation, const Mat_<Vec3b> &image, const Mat_<float> &mask) {
	int maxGraphSize = max(segmentation.getNumberOfComponents(), this->maxTrainingGraphSize);
	int minGraphSize = min(segmentation.getNumberOfComponents(), this->minTrainingGraphSize);

	if (minGraphSize <= this->k) {
		cout<<"the smallest graph has size "<<minGraphSize<<", cannot compute "<<this->k<<" eigenvectors"<<endl;
		exit(EXIT_FAILURE);
	}

	// compute each feature graph for the test sample
	vector<WeightedGraph> featureGraphs;
	featureGraphs.reserve(this->features.size());

	for (int i = 0; i < (int)this->features.size(); i++) {
		featureGraphs.push_back(this->computeFeatureGraph(i, segmentation, image, mask));
	}

	// order graphs by feature, to compute pattern vectors feature by
	// feature.
	vector<vector<WeightedGraph> > graphsByFeatures(this->features.size());

	// add feature graphs for the test sample at index 0
	for (int i = 0; i < (int)this->features.size(); i++) {
		graphsByFeatures[i].reserve(this->trainingFeatureGraphs.size() + 1);
		graphsByFeatures[i].push_back(featureGraphs[i]);
	}

	// add feature graphs for each training sample
	for (int i = 0; i < (int)this->trainingFeatureGraphs.size(); i++) {
		for (int j = 0; j < (int)this->features.size(); j++) {
			graphsByFeatures[j].push_back(get<0>(this->trainingFeatureGraphs[i])[j]);
		}
	}

	// compute the corresponding pattern vectors
	vector<vector<VectorXd> > patternsByFeatures(this->features.size());
	
	for (int i = 0; i < (int)this->features.size(); i++) {
		patternsByFeatures[i] = patternVectors(graphsByFeatures[i], this->k, maxGraphSize);
	}

	// concatenate pattern vectors by image, converting to opencv format
	// to work with cv's ML module.
	Mat_<float> longPatterns = Mat_<float>::zeros(this->trainingFeatureGraphs.size() + 1, maxGraphSize * k * this->features.size());

	for (int i = 0; i < (int)this->trainingFeatureGraphs.size() + 1; i++) {
		VectorXd longPattern(maxGraphSize * k * this->features.size());
		for (int j = 0; j < this->features.size(); j++) {
			longPattern.block(j * maxGraphSize * k, 0, maxGraphSize * k, 1) = patternsByFeatures[j][i];
		}

		Mat_<double> cvLongPattern;

		eigenToCv(longPattern, cvLongPattern);

		Mat_<float> castLongPattern = Mat_<float>(cvLongPattern);

		castLongPattern.copyTo(longPatterns.row(i));
	}

	// classification of long patterns using SVM
	CvKNearest svmClassifier;

	Mat_<int> classes(this->trainingFeatureGraphs.size(), 1);
	
	for (int i = 0; i < (int)this->trainingFeatureGraphs.size(); i++) {
		classes(i,0) = get<1>(this->trainingFeatureGraphs[i]);
	}

	svmClassifier.train(longPatterns.rowRange(1, longPatterns.rows), classes);

	return (int)svmClassifier.find_nearest(longPatterns.row(0), 1);
}