https://github.com/ialhashim/topo-blend
Revision 39b13612ebd645a65eda854771b517371f2f858a authored by ennetws on 13 March 2015, 18:17:18 UTC, committed by ennetws on 13 March 2015, 18:17:18 UTC
1 parent c702819
Raw File
Tip revision: 39b13612ebd645a65eda854771b517371f2f858a authored by ennetws on 13 March 2015, 18:17:18 UTC
Create README.md
Tip revision: 39b1361
GraphCorresponder.cpp
#include "GraphCorresponder.h"

#include <QFile>
#include <algorithm>
#include <fstream>

#include "GraphDistance.h"

#define INVALID_VALUE -1

GraphCorresponder::GraphCorresponder( Structure::Graph *source, Structure::Graph *target )
{
	this->sg = source;
	this->tg = target;

	spactialW = 0.5;
	structuralW = 1.0;
	sizeW = 0.0;
	orientationW = 0.0;

	scoreThreshold = 0.4f;

	// Useful for testing
	if( qMin(source->nodes.size(), target->nodes.size()) < 3 ) scoreThreshold = 1.0f;

	sIsLandmark.resize(sg->nodes.size(), false);
	tIsLandmark.resize(tg->nodes.size(), false);

	isReady = false;
}

// Helper functions
QString baseName(QString s){
	QString result;
	QFileInfo fname(s);
	result = fname.baseName();
	return result;
}

QString GraphCorresponder::sgName()
{
	return baseName(sg->property["name"].toString());
}

QString GraphCorresponder::tgName()
{
	return baseName(tg->property["name"].toString());
}

// Matrix operations
template <class Type>
void GraphCorresponder::initializeMatrix(std::vector< std::vector<Type> > & M, Type value)
{
	int sN = sg->nodes.size();
	int tN = tg->nodes.size();
	std::vector<Type> tmp(tN, value);
	M.clear();
	M.resize(sN, tmp);
}

void GraphCorresponder::normalizeMatrix(MATRIX & M)
{
	float maxDis = -1;

	// Find the maximum value
	int sN = sg->nodes.size();
	int tN = tg->nodes.size();
	for(int i = 0; i < sN; i++){
		for (int j = 0; j < tN; j++)
		{
			if (M[i][j] != INVALID_VALUE && M[i][j] > maxDis)
				maxDis = M[i][j];
		}
	}

	// Normalize
	for(int i = 0; i < sN; i++){
		for (int j = 0; j < tN; j++)
		{
			if (M[i][j] != INVALID_VALUE)
			{
				M[i][j] /= maxDis;
			}
		}
	}
}

bool GraphCorresponder::minElementInMatrix( MATRIX &M, int &row, int &column, float &minValue )
{
	if (M.size() == 0 || M[0].size() == 0)
	{
		qDebug()  << "Warning: minElementInMatrix: the input matrix cannot be empty.";
		return false;
	}

	minValue = FLT_MAX;
	for (int i = 0; i < (int)M.size(); i++){
		for (int j = 0; j < (int)M[0].size(); j++)
		{
			if (M[i][j] != INVALID_VALUE && M[i][j] < minValue)
			{
				minValue = M[i][j];
				row = i;
				column = j;
			}
		}
	}

	return minValue != FLT_MAX;
}

// Point Landmarks
void GraphCorresponder::addPointLandmark()
{
	QVector<POINT_ID> sSelections = sg->selectedControlPointsByColor(Qt::green);
	QVector<POINT_ID> tSelections = tg->selectedControlPointsByColor(Qt::green);

	if (sSelections.empty() || tSelections.empty()) 
	{
		qDebug() << "You need select landmarks on both the source and target graphs.";
		return;
	}

	pointLandmarks.push_back(std::make_pair(sSelections, tSelections));

	// Clear the selections
	sg->clearSelections();
	tg->clearSelections();
}

void GraphCorresponder::prepareOneToOnePointLandmarks()
{
	// Clear
	sPointLandmarks.clear();
	tPointLandmarks.clear();

	// Prepare
	foreach (POINT_LANDMARK pLandmark, pointLandmarks)
	{
		QVector<POINT_ID> sIDs = pLandmark.first;
		QVector<POINT_ID> tIDs = pLandmark.second;

		// One to many
		if (sIDs.size() == 1)
		{
			POINT_ID sID = sIDs.front();

			foreach( POINT_ID tID, tIDs)
			{
				sPointLandmarks.push_back(sID);
				tPointLandmarks.push_back(tID);
			}
		}
		// Many to one
		else if (tIDs.size() == 1)
		{
			POINT_ID tID = tIDs.front();

			foreach( POINT_ID sID, sIDs)
			{
				sPointLandmarks.push_back(sID);
				tPointLandmarks.push_back(tID);
			}
		}
	}
}

QVector< QVector<double> > GraphCorresponder::computeLandmarkFeatures( Structure::Graph *g, QVector<POINT_ID> &pointLandmarks )
{
	QVector< QVector<double> > result_features(g->nodes.size(), QVector<double>());

	foreach (POINT_ID landmark, pointLandmarks)
	{
		Vector3 startpoint = g->nodes[landmark.first]->controlPoint(landmark.second);

		GraphDistance gd(g);
		gd.computeDistances(startpoint, DIST_RESOLUTION);

		for (int nID = 0; nID < (int)g->nodes.size(); nID++)
		{
			double dis = gd.distance(g->nodes[nID]->center());

			result_features[nID].push_back(dis);
		}
	}

	return result_features;
}

double GraphCorresponder::distanceBetweenLandmarkFeatures( QVector<double> sFeature, QVector<double> tFeature )
{
	if (sFeature.size() != tFeature.size())
	{
		qDebug() << "Landmark features have different dimensions.";
		return -1;
	}

	// Euclidean distance
	double dis = 0;
	for (int i = 0; i < (int) sFeature.size(); i++)
	{
		double dif = sFeature[i] - tFeature[i];
		dis += dif * dif;
	}

	return sqrt(dis);
}

void GraphCorresponder::computeLandmarkFeatureMatrix( MATRIX & M )
{
	// One to one point landmarks
	prepareOneToOnePointLandmarks();

	// In case there are no point landmarks
	if(sPointLandmarks.empty())
	{
		initializeMatrix<float>(M, 0.0);
		return;
	}

	// Landmark features
	sLandmarkFeatures = computeLandmarkFeatures(sg, sPointLandmarks);
	tLandmarkFeatures = computeLandmarkFeatures(tg, tPointLandmarks);

	// Compute differences between features
	initializeMatrix<float>(M, INVALID_VALUE);

	int sN = sg->nodes.size();
	int tN = tg->nodes.size();
	for(int i = 0; i < sN; i++){
		for (int j = 0; j < tN; j++)
		{
			if (validM[i][j])
			{
				M[i][j] = distanceBetweenLandmarkFeatures(sLandmarkFeatures[i], tLandmarkFeatures[j]);
			}
		}
	}

	normalizeMatrix(M);
}

// Part landmarks
void GraphCorresponder::addLandmarks( QVector<QString> sParts, QVector<QString> tParts )
{
	// Mark those parts
	foreach(QString strID, sParts)
	{
		Structure::Node * snode = sg->getNode(strID);
		if(!snode) continue;

		int idx = snode->property["index"].toInt();
		sIsLandmark[idx] = true;

		// Make sure its not in list of non-corresponding
		nonCorresS.remove(idx);
		clearFor(strID, true);
	}

	foreach(QString strID, tParts)
	{
		Structure::Node * tnode = tg->getNode(strID);
		if(!tnode) continue;

		int idx = tnode->property["index"].toInt();
		tIsLandmark[idx] = true;

		nonCorresT.remove(idx);
		clearFor(strID, false);
	}

	// Store correspondence
	landmarks.push_back(std::make_pair(sParts, tParts));
}

void GraphCorresponder::addCorrespondences( QVector<QString> sParts, QVector<QString> tParts, float presetScore )
{
	if(!sParts.size() && !tParts.size()) return;

	// Check if those parts are available
	foreach(QString strID, sParts)
	{
		Structure::Node * node = sg->getNode(strID);
		if (!node)
		{
			qDebug() << "Add landmarks: " << strID << " doesn't exist in the source graph.";
			return;
		}
		int idx = node->property["index"].toInt();
		if (sIsCorresponded[idx])
		{
			qDebug() << "Add landmarks: " << strID << " in the source graph has already been corresponded.";
			return;
		}
	}

	foreach(QString strID, tParts)
	{
		Structure::Node * node = tg->getNode(strID);
		if (!node)
		{
			qDebug() << "Add landmarks: " << strID << " doesn't exist in the target graph.";
			return;
		}
		int idx = node->property["index"].toInt();
		if (tIsCorresponded[idx])
		{
			qDebug() << "Add landmarks: " << strID << " in the target graph has already been corresponded.";
			return;
		}
	}

	// Mark those parts
	foreach(QString strID, sParts)
	{
		int idx = sg->getNode(strID)->property["index"].toInt();
		sIsCorresponded[idx] = true;
	}

	foreach(QString strID, tParts)
	{
		int idx = tg->getNode(strID)->property["index"].toInt();
		tIsCorresponded[idx] = true;
	}

	// Store correspondence
	PART_LANDMARK vector2vector = std::make_pair(sParts, tParts);
	insertCorrespondence( vector2vector );

	// Set to specific score
	int N = qMax(sParts.size(), tParts.size());
	corrScores[vector2vector] = std::vector<float>(N, presetScore);
}

void GraphCorresponder::removeLandmark( int id )
{
	if (id > (int)landmarks.size()) return;


	PART_LANDMARK vector2vector = landmarks[id];

	// Update isLandmark
	foreach(QString strID, vector2vector.first)
	{
		int idx = sg->getNode(strID)->property["index"].toInt();
		sIsLandmark[idx] = false;
	}

	foreach(QString strID, vector2vector.second)
	{
		int idx = tg->getNode(strID)->property["index"].toInt();
		tIsLandmark[idx] = false;
	}


	// Erase
	landmarks.erase(landmarks.begin() + id);
}

void GraphCorresponder::computeValidationMatrix()
{
	initializeMatrix<bool>(validM, true);

	int sN = sg->nodes.size();
	int tN = tg->nodes.size();

	// Types
	for(int i = 0; i < sN; i++){
		for (int j = 0; j < tN; j++)
		{
			if (sg->nodes[i]->type() != tg->nodes[j]->type())
				validM[i][j] = false;
		}
	}

	// Landmarks
	foreach (PART_LANDMARK vector2vector, landmarks)
	{
		// Convert strID to int id
		std::set<int> sIDs, tIDs;
		foreach (QString strID, vector2vector.first)
			sIDs.insert(sg->getNode(strID)->property["index"].toInt());
		foreach (QString strID, vector2vector.second)
			tIDs.insert(tg->getNode(strID)->property["index"].toInt());

		// Set distances to invalid value
		foreach (int r, sIDs)
			for (int c = 0; c < (int)tg->nodes.size(); c++)
				validM[r][c] = false;

		foreach (int c, tIDs)
			for (int r = 0; r < (int)sg->nodes.size(); r++)
				validM[r][c] = false;
	}
}

void GraphCorresponder::saveLandmarks(QString filename)
{
	QFile file(filename);
	if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) return;
	QTextStream outF(&file);

	outF << landmarks.size() << "\n\n";

	foreach (PART_LANDMARK vector2vector, landmarks)
	{
		outF << vector2vector.first.size() << '\t';;
		foreach (QString strID, vector2vector.first)
			outF << strID << '\t';
		outF << '\n';

		outF << vector2vector.second.size() << '\t';
		foreach (QString strID, vector2vector.second)
			outF << strID << '\t';
		outF << "\n\n";
	}

	file.close();
}

void GraphCorresponder::clear()
{
	sIsLandmark.clear();
	tIsLandmark.clear();

	disM.clear();
	validM.clear();
	spatialM.clear();

	landmarks.clear();
	correspondences.clear();
	corrScores.clear();

	sIsCorresponded.clear();
	tIsCorresponded.clear();

	nonCorresS.clear();
	nonCorresT.clear();
	
	sIsCorresponded.clear();
	tIsCorresponded.clear();

	sIsCorresponded.resize(sg->nodes.size(), false);
	tIsCorresponded.resize(tg->nodes.size(), false);
	sIsLandmark.resize(sg->nodes.size(), false);
	tIsLandmark.resize(tg->nodes.size(), false);

	foreach(Structure::Node * n, sg->nodes) n->property.remove("correspond");
	foreach(Structure::Node * n, tg->nodes) n->property.remove("correspond");

	this->isReady = false;

	emit( cleared() );
}

void GraphCorresponder::loadLandmarks(QString filename)
{
	QFile file(filename);
	if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) return;
	QTextStream inF(&file);

	clear();

	int nbCorr;
	inF >> nbCorr;

	for (int i = 0; i < nbCorr; i++)
	{
		int n;
		QString strID;
		QVector<QString> sParts, tParts;

		inF >> n;
		for (int j = 0; j < n; j++)
		{
			inF >> strID;
			sParts.push_back(strID);
		}

		inF >> n;
		for (int j = 0; j < n; j++)
		{
			inF >> strID;
			tParts.push_back(strID);
		}

		this->addLandmarks(sParts, tParts);
	}

	file.close();
}

void GraphCorresponder::computeHausdorffDistanceMatrix( MATRIX & M )
{
	initializeMatrix<float>(M, INVALID_VALUE);

	// The control points of each node in the target graph
	Array2D_Vector3 targetControlPointsArray;
	foreach (Structure::Node *tNode, this->tg->nodes)
		targetControlPointsArray.push_back(tNode->controlPoints());

	// The Hausdorff distance
	int sN = sg->nodes.size();
	int tN = tg->nodes.size();
	for(int i = 0; i < sN; i++)
	{
		std::vector<Vector3> sPoints = sg->nodes[i]->controlPoints();
		for (int j = 0; j < tN; j++)
		{
			if (validM[i][j])
			{
				std::vector<Vector3> &tPoints = targetControlPointsArray[j];
				M[i][j] = HausdorffDistance(sPoints, tPoints);
			}
		}
	}

	normalizeMatrix(M);
}


// Size and orientation matrices
void GraphCorresponder::computeSizeDiffMatrix( MATRIX & M )
{
	initializeMatrix<float>(M, INVALID_VALUE);

	int sN = sg->nodes.size();
	int tN = tg->nodes.size();
	for(int i = 0; i < sN; i++){
		for (int j = 0; j < tN; j++)
		{
			if (validM[i][j])
			{
				float diff;
				if (sg->nodes[i]->type() == Structure::CURVE)
				{
					Structure::Curve *curve1 = (Structure::Curve *) sg->nodes[i];
					Structure::Curve *curve2 = (Structure::Curve *) tg->nodes[j];
					diff = curve1->curve.GetLength(0, 1) - curve2->curve.GetLength(0, 1);
				}
				else
				{
					Structure::Sheet *sheet1 = (Structure::Sheet *) sg->nodes[i];
					Structure::Sheet *sheet2 = (Structure::Sheet *) tg->nodes[j];
					diff = sheet1->area() - sheet2->area();
				}

				M[i][j] = fabsf(diff);
			}
		}
	}

	normalizeMatrix(M);
}

void GraphCorresponder::computeOrientationDiffMatrix( MATRIX & M )
{
	initializeMatrix<float>(M, INVALID_VALUE);

	int sN = sg->nodes.size();
	int tN = tg->nodes.size();
	for(int i = 0; i < sN; i++){
		for (int j = 0; j < tN; j++)
		{
			if (validM[i][j])
			{
				Vector3 vec1, vec2;
				if (sg->nodes[i]->type() == Structure::CURVE)
				{
					Structure::Curve *curve1 = (Structure::Curve *) sg->nodes[i];
					Structure::Curve *curve2 = (Structure::Curve *) tg->nodes[j];
					std::vector<Vector3> ctrlPnts1 = curve1->controlPoints();
					std::vector<Vector3> ctrlPnts2 = curve2->controlPoints();

					vec1 = ctrlPnts1.front() - ctrlPnts1.back();
					vec2 = ctrlPnts2.front() - ctrlPnts2.back();
				}
				else
				{
					Structure::Sheet *sSheet = (Structure::Sheet *) sg->nodes[i];
					Structure::Sheet *tSheet = (Structure::Sheet *) tg->nodes[j];
					Array2D_Vector3 sCtrlPoint = sSheet->surface.mCtrlPoint;
					Array2D_Vector3 tCtrlPoint = tSheet->surface.mCtrlPoint;
					Vector3 scenter = sSheet->center();
					Vector3 tcenter = tSheet->center();

					// Get the extreme points.
					Vector3 s00 = sCtrlPoint.front().front();
					Vector3 s01 = sCtrlPoint.front().back();
					Vector3 s10 = sCtrlPoint.back().front();
					Vector3 sU = s10 - s00;
					Vector3 sV = s01 - s00;

					Vector3 t00 = tCtrlPoint.front().front();
					Vector3 t01 = tCtrlPoint.front().back();
					Vector3 t10 = tCtrlPoint.back().front();
					Vector3 tU = t10 - t00;
					Vector3 tV = t01 - t00;

					// Normals
					vec1 = cross(sU, sV);
					vec2 = cross(tU, tV);
				}

				vec1.normalize();
				vec2.normalize();
				M[i][j] = 1.0 - abs(dot(vec1, vec2));
			}
		}
	}

	normalizeMatrix(M);
}

void GraphCorresponder::insertCorrespondence( PART_LANDMARK vector2vector )
{
	// Check for existing ones
	for(int i = 0; i < (int)correspondences.size(); i++)
	{
		if(correspondences[i] == vector2vector)
		{
			qDebug() << "Problem!";
		}
	}

	correspondences.push_back(vector2vector);
}

// Final disM
void GraphCorresponder::prepareAllMatrices()
{
	// Validation matrix
	computeValidationMatrix();

	// Distance matrices
	computeHausdorffDistanceMatrix(spatialM);
	//computeSizeDiffMatrix(sizeM);
	//computeOrientationDiffMatrix(orientationM);
	computeLandmarkFeatureMatrix(structuralM);
}

void GraphCorresponder::computeFinalDistanceMatrix()
{
	initializeMatrix<float>(disM, INVALID_VALUE);

	int sN = sg->nodes.size();
	int tN = tg->nodes.size();
	for(int i = 0; i < sN; i++) {
		for (int j = 0; j < tN; j++)
		{
			if (validM[i][j])
			{
				disM[i][j] = spactialW * spatialM[i][j] + structuralW * structuralM[i][j];
			}
		}
	}

	normalizeMatrix(disM);
}


// Part to Part
void GraphCorresponder::computePartToPartCorrespondences()
{
	// Clear
	correspondences.clear();
	corrScores.clear();

	// The final disM
	std::vector< std::vector<float> > disMatrix = disM;

	// Parameters
	int sN = sg->nodes.size();
	int tN = tg->nodes.size();
	float tolerance = 0.04f;

	// Force un-corresponded
	foreach(int ri, nonCorresS)
		for (int ci = 0; ci < tN; ci++)
			disMatrix[ri][ci] = INVALID_VALUE;
	
	for (int ri = 0; ri < sN; ri++)
		foreach(int ci, nonCorresT)
			disMatrix[ri][ci] = INVALID_VALUE;

	int r, c;
	float minValue;
	while (minElementInMatrix(disMatrix, r, c, minValue))
	{
		if (minValue > scoreThreshold) break;

		// source:r <-> target:c
		float upperBound = disMatrix[r][c] + tolerance;

		// Search for "many" in source
		std::vector<int> r_many;
		std::vector<float> r_scores;
		for (int ri = 0; ri < sN; ri++)
		{
			if (ri == r || disMatrix[ri][c] == INVALID_VALUE)
				continue;

			// ri is close to c
			if (disMatrix[ri][c] <= upperBound) 
			{
				r_scores.push_back(disMatrix[ri][c]);
				r_many.push_back(ri);
			}
		}

		// Search for "many" in target
		std::vector<int> c_many;
		std::vector<float> c_scores;
		for (int ci = 0; ci < tN; ci++)
		{
			if (ci == c || disMatrix[r][ci] == INVALID_VALUE)	
				continue;

			// ci is close to r
			if (disMatrix[r][ci] < upperBound) 
			{
				c_scores.push_back(disMatrix[r][ci]);
				c_many.push_back(ci);
			}
		}

		// Results
		QVector<QString> sVector, tVector;
		sVector.push_back(sg->nodes[r]->id);
		tVector.push_back(tg->nodes[c]->id);
		std::vector<float> scores;
		scores.push_back(minValue);

		if (c_many.size() > r_many.size()) // r <-> {c_i}
		{
			foreach(int ci, c_many)
			{
				tVector.push_back(tg->nodes[ci]->id);

				for (int i = 0; i < sN; i++) // Column c
					disMatrix[i][ci] = INVALID_VALUE;
			}

			scores.insert(scores.end(), c_scores.begin(), c_scores.end());
		}
		else // {r_i} <-> c
		{
			foreach(int ri, r_many)
			{
				sVector.push_back(sg->nodes[ri]->id);

				for (int j = 0; j < tN; j++) // Row r
					disMatrix[ri][j] = INVALID_VALUE;
			}

			scores.insert(scores.end(), r_scores.begin(), r_scores.end());
		}

		// Save results
		PART_LANDMARK vector2vector = std::make_pair(sVector, tVector);
		this->insertCorrespondence( vector2vector );
		this->corrScores[vector2vector] = scores;
		
		// Remove r and c in the disMatrix
		for (int i = 0; i < sN; i++) // Column c
			disMatrix[i][c] = INVALID_VALUE;
		for (int j = 0; j < tN; j++) // Row r
			disMatrix[r][j] = INVALID_VALUE;
	}

	// Add the part landmarks as correspondences too
	foreach(PART_LANDMARK landmark, landmarks)
	{
		insertCorrespondence( landmark );
		int n = qMax(landmark.first.size(),landmark.second.size());
		std::vector<float> fake_score(n, -1);
		corrScores[landmark] = fake_score;
	}
}


// Point to Point
void GraphCorresponder::correspondAllNodes()
{
	// Adjust the frames for corresponded parts
	// Then Point-to-Point correspondences can be easily retrieved via parameterized NURBS. 
	for (int i = 0; i < (int)correspondences.size(); i++)
	{
		QVector<QString> &sVector = correspondences[i].first;
		QVector<QString> &tVector = correspondences[i].second;

		// One to many
		if (sVector.size() == 1)
		{
			Structure::Node *sNode = sg->getNode(sVector.front());
			foreach(QString tID, tVector)
			{
				Structure::Node *tNode = tg->getNode(tID);
				correspondTwoNodes(sNode, tNode);
			}
		}
		// Many to one
		else if (tVector.size() == 1)
		{
			Structure::Node *tNode = tg->getNode(tVector.front());
			foreach(QString sID, sVector)
			{
				Structure::Node *sNode = sg->getNode(sID);
				correspondTwoNodes(sNode, tNode);
			}
		}
		// Many to many
		else
		{
			qDebug() << "Many to Many correspondence happened. ";
		}
	}
}

void GraphCorresponder::correspondTwoNodes( Structure::Node *sNode, Structure::Node *tNode )
{
	if (sNode->type() != tNode->type())
	{
		qDebug() << "Two nodes with different types cannot be corresponded. ";
		return;
	}

	if (sNode->type() == Structure::SHEET)
		correspondTwoSheets((Structure::Sheet*) sNode, (Structure::Sheet*) tNode, tg);
	else
		correspondTwoCurves((Structure::Curve*) sNode, (Structure::Curve*) tNode, tg);
}

void GraphCorresponder::correspondTwoCurves( Structure::Curve *sCurve, Structure::Curve *tCurve, Structure::Graph * tgt )
{
	std::vector<Vector3> sCtrlPoint = sCurve->controlPoints();
	std::vector<Vector3> tCtrlPoint = tCurve->controlPoints();

	// Euclidean for now, can use Geodesic distance instead if need
	Vector3 scenter = sCurve->center();
	Vector3 sfront = sCtrlPoint.front() - scenter;
	Vector3 tcenter = tCurve->center();
	Vector3 tfront = tCtrlPoint.front() - tcenter;
	Vector3 tback = tCtrlPoint.back() - tcenter;

	float f2f = (sfront - tfront).norm();
	float f2b = (sfront - tback).norm();

	float diff = std::abs(f2f-f2b);
    float threshold = 0.1f;

	if (f2f > f2b && diff > threshold)
	{
		// Flip the target
		std::vector<Scalar> tCtrlWeight = tCurve->controlWeights();
		std::reverse(tCtrlPoint.begin(), tCtrlPoint.end());
		std::reverse(tCtrlWeight.begin(), tCtrlWeight.end());

        NURBS::NURBSCurved newCurve(tCtrlPoint, tCtrlWeight);
		tCurve->curve = newCurve;

		// Update the coordinates of links
		foreach( Structure::Link * l, tgt->getEdges(tCurve->id) )
		{
			l->setCoord(tCurve->id, inverseCoords( l->getCoord(tCurve->id) ));
		}
	}
}

void GraphCorresponder::correspondTwoSheets( Structure::Sheet *sSheet, Structure::Sheet *tSheet, Structure::Graph * tgt)
{
	// Old properties
    NURBS::NURBSRectangled &oldRect = tSheet->surface;
	int uDegree = oldRect.GetDegree(0);
	int vDegree = oldRect.GetDegree(1);
	bool uLoop = oldRect.IsLoop(0);
	bool vLoop = oldRect.IsLoop(1);
	bool uOpen = oldRect.IsOpen(0);
	bool vOpen = oldRect.IsOpen(1);
	bool isModified = false;
	bool isUVFlipped = false;

	// Control points and weights
	Array2D_Vector3 sCtrlPoint = sSheet->surface.mCtrlPoint;
	Array2D_Real sCtrlWeight = sSheet->surface.mCtrlWeight;

	Array2D_Vector3 tCtrlPoint = tSheet->surface.mCtrlPoint;
	Array2D_Real tCtrlWeight = tSheet->surface.mCtrlWeight;

	Array2D_Vector3 tCtrlPointNew;
	Array2D_Real tCtrlWeightNew;

	Vector3 scenter = sSheet->center();
	Vector3 tcenter = tSheet->center();

	// Get the extreme points.
	Vector3 s00 = sCtrlPoint.front().front();
	Vector3 s01 = sCtrlPoint.front().back();
	Vector3 s10 = sCtrlPoint.back().front();
	Vector3 sU = s10 - s00;
	Vector3 sV = s01 - s00;

	Vector3 t00 = tCtrlPoint.front().front();
	Vector3 t01 = tCtrlPoint.front().back();
	Vector3 t10 = tCtrlPoint.back().front();
	Vector3 tU = t10 - t00;
	Vector3 tV = t01 - t00;

	// Flip if need
	Vector3 sUV = cross(sU, sV);
	Vector3 tUV = cross(tU, tV);
	if (dot(sUV, tUV) < 0)
	{
		// Reverse the target along u direction
		std::reverse(tCtrlPoint.begin(), tCtrlPoint.end());
		std::reverse(tCtrlWeight.begin(), tCtrlWeight.end());

		// Update tU
		tU = -tU;
		tUV = -tUV;
		isModified = true;

		// Update the coordinates of links
		foreach( Structure::Link * l, tgt->getEdges(tSheet->id) ){
			Array1D_Vector4d oldCoord = l->getCoord(tSheet->id), newCoord;
			foreach(Vector4d c, oldCoord) newCoord.push_back(Vector4d(1-c[0], c[1], c[2], c[3]));
			l->setCoord(tSheet->id, newCoord);
		}
	}

	// Rotate if need
	Scalar cosAngle = dot(sU.normalized(), tU.normalized());
	Scalar cos45 = sqrtf(2.0) / 2;

	// Do Nothing
	if (cosAngle > cos45)
	{
		tCtrlPointNew = tCtrlPoint;
		tCtrlWeightNew = tCtrlWeight;
	}
	// Rotate 180 degrees
	else if (cosAngle < - cos45)
	{
		//  --> sV				tU
		// |					|
		// sU             tV <--
		// By flipping along both directions
		std::reverse(tCtrlPoint.begin(), tCtrlPoint.end());
		std::reverse(tCtrlWeight.begin(), tCtrlWeight.end());

		for (int i = 0; i < (int)tCtrlPoint.size(); i++)
		{
			std::reverse(tCtrlPoint[i].begin(), tCtrlPoint[i].end());
			std::reverse(tCtrlWeight[i].begin(), tCtrlWeight[i].end());
		}

		// The new control points and weights
		tCtrlPointNew = tCtrlPoint;
		tCtrlWeightNew = tCtrlWeight;
		isModified = true;

		// Update the coordinates of links
		foreach( Structure::Link * l, tgt->getEdges(tSheet->id) ){
			Array1D_Vector4d oldCoord = l->getCoord(tSheet->id), newCoord;
			foreach(Vector4d c, oldCoord) newCoord.push_back(Vector4d(1-c[0], 1-c[1], c[2], c[3]));
			l->setCoord(tSheet->id, newCoord);
		}
	}
	// Rotate 90 degrees 
	else
	{
		Vector3 stU = cross(sU, tU);
		if (dot(stU, sUV) >= 0)
		{
			//  --> sV		tV
			// |			|
			// sU           --> tU
			// Transpose and reverse along U
			tCtrlPointNew = transpose<Vector3>(tCtrlPoint);
			tCtrlWeightNew = transpose<Scalar>(tCtrlWeight);

			std::reverse(tCtrlPointNew.begin(), tCtrlPointNew.end());
			std::reverse(tCtrlWeightNew.begin(), tCtrlWeightNew.end());

			// Update the coordinates of links
			foreach( Structure::Link * l, tgt->getEdges(tSheet->id) ){
				Array1D_Vector4d oldCoord = l->getCoord(tSheet->id), newCoord;
				foreach(Vector4d c, oldCoord) newCoord.push_back(Vector4d(1- c[1], c[0], c[2], c[3]));
				l->setCoord(tSheet->id, newCoord);
			}
		}
		else
		{
			//  --> sV	tU<--
			// |			 |
			// sU			tV
			// Reverse along U and Transpose
			std::reverse(tCtrlPoint.begin(), tCtrlPoint.end());
			std::reverse(tCtrlWeight.begin(), tCtrlWeight.end());

			tCtrlPointNew = transpose<Vector3>(tCtrlPoint);
			tCtrlWeightNew = transpose<Scalar>(tCtrlWeight);

			// Update the coordinates of links
			foreach( Structure::Link * l, tgt->getEdges(tSheet->id) ){
				Array1D_Vector4d oldCoord = l->getCoord(tSheet->id), newCoord;
				foreach(Vector4d c, oldCoord) newCoord.push_back(Vector4d(c[1], 1- c[0], c[2], c[3]));
				l->setCoord(tSheet->id, newCoord);
			}
		}

		isModified = true;
		isUVFlipped = true;
	}

	// Create a new sheet if need
	if (isModified)
	{
        NURBS::NURBSRectangled newRect;
		if (isUVFlipped)
            newRect = NURBS::NURBSRectangled(tCtrlPointNew, tCtrlWeightNew, vDegree, uDegree, vLoop, uLoop, vOpen, uOpen);
		else
            newRect = NURBS::NURBSRectangled(tCtrlPointNew, tCtrlWeightNew, uDegree, vDegree, uLoop, vLoop, uOpen, vOpen);

		tSheet->surface = newRect;
	}
}


// Main access
void GraphCorresponder::computeCorrespondences()
{
	if (isReady) return;

	corrScores.clear();

	// Prepare
	prepareAllMatrices();

	// Distance matrix
	computeFinalDistanceMatrix();

	// Part to Part correspondence
	computePartToPartCorrespondences();

	// Point to Point correspondence
	correspondAllNodes();

	// Reset correspondence states
	sIsCorresponded.clear();
	tIsCorresponded.clear();
	sIsCorresponded.resize(sg->nodes.size(), false);
	tIsCorresponded.resize(tg->nodes.size(), false);

	if(correspondences.empty()) doHopelessCorrespondence();

	// Mark the corresponded nodes
	foreach (PART_LANDMARK vector2vector, correspondences)
	{
		foreach (QString sID, vector2vector.first)
		{
			int sid = sg->getNode(sID)->property["index"].toInt();
			sIsCorresponded[sid] = true;
		}

		foreach (QString tID, vector2vector.second)
		{
			int tid = tg->getNode(tID)->property["index"].toInt();
			tIsCorresponded[tid] = true;
		}
	}
}

void GraphCorresponder::doHopelessCorrespondence()
{
	double maxVolS = -1, maxVolT = -1;
	QString maxS, maxT;

	// Get parts with maximum volume on both graphs
	foreach(Structure::Node* n, sg->nodes){
		double volume = n->bbox().volume();
		if(volume > maxVolS){
			maxVolS = volume;
			maxS = n->id;
		}
	}
	foreach(Structure::Node* n, tg->nodes){
		double volume = n->bbox().volume();
		if(volume > maxVolT){
			maxVolT = volume;
			maxT = n->id;
		}
	}

	// Force a correspondence
	this->addCorrespondences(QVector<QString>() << maxS, QVector<QString>() << maxT, -1);

	qDebug() << "WARNING: forced correspondence between [" << maxS << "] and [" << maxT << "].";
}

// Others
std::vector<QString> GraphCorresponder::nonCorresSource()
{
	std::vector<QString> nodes;
	
	for (int i = 0; i < (int)sIsCorresponded.size(); i++)
	{
		if (!sIsCorresponded[i])
			nodes.push_back(sg->nodes[i]->id);
	}

	return nodes;
}

std::vector<QString> GraphCorresponder::nonCorresTarget()
{
	std::vector<QString> nodes;

	for (int i = 0; i < (int) tIsCorresponded.size(); i++)
	{
		if (!tIsCorresponded[i])
			nodes.push_back(tg->nodes[i]->id);
	}

	return nodes;
}

void GraphCorresponder::clearFor( QString sID, bool isSource )
{
	// Only keep landmarks that don't have sID
	QVector<PART_LANDMARK> lkeep, lremove;
	for(int i = 0; i < landmarks.size(); i++)
	{
		bool isKeep = (isSource && !landmarks[i].first.contains(sID)) || (!isSource && !landmarks[i].second.contains(sID));
		if( isKeep ) 
			lkeep.push_back(landmarks[i]);
		else
			lremove.push_back(landmarks[i]);
	}

	landmarks = lkeep;

	foreach(PART_LANDMARK landmark, lremove)
	{
		foreach(QString sid, landmark.first){
			sIsLandmark[sg->getNode(sid)->property["index"].toInt()] = false;
		}

		foreach(QString tid, landmark.second){
			tIsLandmark[tg->getNode(tid)->property["index"].toInt()] = false;
		}
	}
}

void GraphCorresponder::setNonCorresSource(QString sID)
{
	clearFor(sID, true);

	nonCorresS.insert( sg->getNode(sID)->property["index"].toInt() );
}

void GraphCorresponder::setNonCorresTarget(QString tID)
{
	clearFor(tID, false);

	nonCorresT.insert( tg->getNode(tID)->property["index"].toInt() );
}

void GraphCorresponder::saveCorrespondences( QString filename, bool isWithScores )
{
	QFile file(filename);
	if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) return;
	QTextStream outF(&file);

	// Header
	outF << (isWithScores ? "hasScore" : "" ) << " " << correspondences.size() << " " << nonCorresS.size() << " " << nonCorresT.size() << "\n\n";

	// Add items
	int nCoor = correspondences.size();
	for (int i = 0; i < nCoor; i++)
	{
		// Correspondences and scores
		PART_LANDMARK vec2vec = correspondences[i];
		std::vector<float> scores = corrScores[vec2vec];

		outF << vec2vec.first.size() << '\t';;
		foreach (QString strID, vec2vec.first)
			outF << strID << '\t';
		outF << '\n';

		outF << vec2vec.second.size() << '\t';
		foreach (QString strID, vec2vec.second)
			outF << strID << '\t';

		if(isWithScores) outF << "\n" << scores.front() << "\n";

		outF << "\n\n";
	}

	// Non-corresponded
	{
		foreach(int i, nonCorresS) outF << i << '\t';
		outF << "\n";

		foreach(int i, nonCorresT) outF << i << '\t';
		outF << "\n";
	}

	file.close();
}

void GraphCorresponder::loadCorrespondences( QString filename, bool isReversed )
{
	QFile file(filename);
	if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) return;
	QTextStream inF(&file);

	clear();

	int nbCorr;
	//inF >> nbCorr; // old way
	QString header = inF.readLine();
	bool isWithScores = header.contains("Score", Qt::CaseInsensitive);
	
	QVector<int> counts;
	QRegExp re("\\d*");
	foreach(QString token, header.split(" ", QString::SkipEmptyParts)){
		if(re.exactMatch(token))
			counts.push_back(token.toInt());
	}

	nbCorr = counts.front();

	for (int i = 0; i < nbCorr; i++)
	{
		int n;
		QString strID;
		QVector<QString> sParts, tParts;

		inF >> n;
		for (int j = 0; j < n; j++)
		{
			inF >> strID;
			sParts.push_back(strID);
		}

		inF >> n;
		for (int j = 0; j < n; j++)
		{
			inF >> strID;
			tParts.push_back(strID);
		}

		float score = -1;
		if(isWithScores) inF >> score;

		// This is useful when file is saved as g1_g2 is loaded for g2_g1
		if( isReversed ) qSwap(sParts, tParts);

		// Store correspondence
		addCorrespondences(sParts, tParts, score);
	}

	if(counts.size() == 3)
	{
		for(int i = 0; i < counts[1]; i++){
			int nid = -1;
			inF >> nid;
			nonCorresS.insert(nid);
		}

		for(int i = 0; i < counts[2]; i++){
			int nid = -1;
			inF >> nid;
			nonCorresT.insert(nid);
		}

		if( isReversed ) qSwap(nonCorresS, nonCorresT);
	}

	isReady = true;  // block automatic computing correspondences

	file.close();
}

void GraphCorresponder::visualizePart2PartDistance( int sourceID )
{
	if (disM.empty()) 
		computeFinalDistanceMatrix();

	// The source
	sourceID %= sg->nodes.size();
	for (int i = 0; i < sg->nodes.size(); i++)
	{
		Structure::Node *sNode = sg->nodes[i];
		float color = (i == sourceID)? 1.0 : 0.0;
		sNode->vis_property["color"] = qtJetColorMap(color);
		sNode->vis_property["showControl"] = false;
	}

	// Visualize by jet color
	for (int j = 0; j < tg->nodes.size(); j++)
	{
		Structure::Node *tNode = tg->nodes[j];

		float value;
		if (disM[sourceID][j] == INVALID_VALUE)
			value = 0;
		else
			value = 1 - disM[sourceID][j];
		tNode->vis_property["color"] = qtJetColorMap(value);
		tNode->vis_property["showControl"] = false;
	}
}
back to top