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
segment.cpp
#include "segment.h"
#include "StarlabDrawArea.h"
#include "../CustomDrawObjects.h"
#include <QStack>

#include "Graph.h"

using namespace std;
using namespace SurfaceMesh;

#define qRanged(min, v, max) ( qMax(min, qMin(v, max)) )
#define RADIANS(deg)    ((deg)/180.0 * M_PI)
#define DEGREES(rad)    ((rad)/M_PI * 180.0)

typedef SurfaceMesh::Model::Halfedge_around_face_circulator HalfedgeFaceIter;
typedef SurfaceMesh::Model::Vertex_around_face_circulator VertFaceIter;
typedef SurfaceMesh::Model::Face_around_vertex_circulator FaceVertIter;
typedef SurfaceMesh::Model::Vertex_around_vertex_circulator VertIter;

uint qHash( const SurfaceMesh::Model::Face &key ){return qHash(key.idx()); }
uint qHash( const SurfaceMesh::Model::Vertex &key ){return qHash(key.idx()); }

void segment::initParameters(RichParameterSet* pars){
	pars->addParam( new RichFloat("angle_threshold", 10.0f, "Angle threshold"));
	pars->addParam( new RichFloat("min_radius_scale", 0.1f, "Minimum component scale"));
	pars->addParam( new RichInt("k_nighbours", 3, "K-levels"));
	pars->addParam( new RichBool("visualize", true, "Visualize regions"));
	pars->addParam( new RichBool("extract", false, "Extract regions"));

	// Create helper object to access points and edge lengths
	SurfaceMeshHelper h(mesh());
	points = h.getVector3VertexProperty(SurfaceMesh::VPOINT);
	farea = h.computeFaceAreas();
	elen = h.computeEdgeLengths();
}

void segment::initMesh()
{
	resetClassfication();
	resetVisitFlag();
	resetVertexClass();

	faceTarget = mesh()->face_property<QString>("f:target", "");
}

void segment::resetClassfication()
{
	mesh()->remove_face_property<int>(fclass);
	fclass = mesh()->add_face_property<int>("f:class", SHEET);
}

void segment::resetVisitFlag()
{
	mesh()->remove_face_property<bool>(fvisited);
	fvisited = mesh()->add_face_property("f:visited", false);
}

void segment::resetVertexClass()
{
	mesh()->remove_vertex_property<Scalar>(vclass);
	vclass = mesh()->add_vertex_property<Scalar>("v:class", 0);
}

void segment::applyFilter(RichParameterSet* pars)
{
	drawArea()->deleteAllRenderObjects();

	// Get user specified parameters
	double theta = pars->getFloat("angle_threshold");
	double minRadiusScale = pars->getFloat("min_radius_scale");
	int k = pars->getInt("k_nighbours");
	bool visualize = pars->getBool("visualize");
	bool extract = pars->getBool("extract");

	double minRadius = minRadiusScale * mesh()->bbox().diagonal().norm();

	// Perform segmentation
	performCurveSheetSegmentation(theta, minRadius, k, extract, visualize);
}

void segment::performCurveSheetSegmentation(double theta, double minRadius, int k, bool isExtract, bool isVisualize)
{
	// Clear debug artifacts
	drawArea()->deleteAllRenderObjects();
	PolygonSoup * poly_soup = new PolygonSoup;
	LineSegments * lines = new LineSegments;
	PointSoup * point_soup = new PointSoup;

	initMesh();

	// 1) Check valid faces by minimum angle
	foreach(Face f, mesh()->faces())
		if( minAngle(f) < RADIANS(theta) )
			fclass[f] = CURVE;

	// 2) Assign vertex classification from faces
	classifyVertsFromFaces();

	// 3) Blur classification
	foreach(Vertex v, mesh()->vertices()){
		QSet<Vertex> k_rings;
		collectRing(v, k_rings, k);

		// Get median
		QVector<double> nighbours;
		foreach(Vertex v, k_rings) nighbours.push_back(vclass[v]);
		vclass[v] = median(nighbours);

		if(isVisualize) point_soup->addPoint(points[v], qtJetColorMap(vclass[v]));
	}

	// 4) Cutoff 
	foreach(Vertex v, mesh()->vertices()){
		if(vclass[v] > 0.5){
			Surface_mesh::Face_around_vertex_circulator adjF(mesh(), v), fend = adjF;
			do { fclass[adjF] = SHEET; } while(++adjF != fend);
		}
	}

	// 5) Filter very small components
	if(minRadius > 0)
	{
		GrowRegions();
		foreach(Region r, sheet_regions)
		{
			Eigen::AlignedBox3d region_bbox;

			foreach(Face f, r)
			{
				Eigen::Vector3d center_f = center(f);
				region_bbox = region_bbox.merged( Eigen::AlignedBox3d(center_f,center_f) );
			}

			if(region_bbox.diagonal().norm() < minRadius)
				invertRegion(r);
		}
	}

	// 6) Find set of faces in all regions
	GrowRegions();

	// 7) Split curve regions into parts
	int curve_count = (int)curve_regions.size();
	for(int i = 0; i < curve_count; i++)
		splitCurveRegion( curve_regions[i] );

	// 8) Save as separate meshes
	if(isExtract){
		int cid = 0, sid = 0;

		document()->pushBusy();
		// Curves
		for(int i = 0; i < (int) curve_regions.size(); i++)
		{
			if(!curve_regions[i].size()) continue;

			SurfaceMesh::Model * m = new SurfaceMesh::Model("", QString("%1_curve%2").arg(mesh()->name).arg(cid++));
			setMeshFromRegion(curve_regions[i], m);
			m->updateBoundingBox();
			document()->addModel(m);
		}

		// Sheets
		for(int i = 0; i < (int) sheet_regions.size(); i++)
		{
			if(!sheet_regions[i].size()) continue;

			SurfaceMesh::Model * m = new SurfaceMesh::Model("", QString("%1_sheet%2").arg(mesh()->name).arg(sid++));
			setMeshFromRegion(sheet_regions[i], m);
			m->updateBoundingBox();
			document()->addModel(m);
		}

		document()->popBusy();
	}

	// Debug
	if(isVisualize){
		double e = mesh()->bbox().diagonal().norm() * 0.05 * 1;
		Eigen::Vector3d shift(e,e,e);
		static std::vector< std::vector<double> > rclr = randomColors(curve_regions.size());
		
		// Draw curves
		for(int r = 0; r < (int)curve_regions.size(); r++){
			foreach(Face f, curve_regions[r]){
				QVector<QVector3> p;
				VertFaceIter vit = mesh()->vertices(f), vend = vit;
				do{ p.push_back( QVector3(points[vit] + shift)  ); } while(++vit != vend);
				for(int i = 0; i < p.size(); i++)
					lines->addLine(p[i], p[(i+1) % p.size()], QColor::fromRgbF(rclr[r][0], rclr[r][1], rclr[r][2]));
			}
		}
		// Draw sheets
		foreach(Region region, sheet_regions){
			foreach(Face f, region){
				QVector<QVector3> p;
				VertFaceIter vit = mesh()->vertices(f), vend = vit;
				do{ p.push_back( QVector3(points[vit] - shift) ); } while(++vit != vend);
				poly_soup->addPoly(p);
			}
		}

		drawArea()->addRenderObject(poly_soup);
		drawArea()->addRenderObject(lines);
		drawArea()->addRenderObject(point_soup);
	}

	qDebug() << (QString("Total = %1  |  Sheets found = %2  |  Curves = %3")
		.arg(sheet_regions.size() + curve_regions.size()).arg(sheet_regions.size()).arg(curve_regions.size()));

	// Experiments:
	if(isExtract)
		doGraph();

}

void segment::doGraph()
{
	QMap<QString, int> id;
	Graph<int,int> g;

	// Assign target IDs
	foreach(Face f, mesh()->faces())
	{
		id[ faceTarget[f] ] ++;
	}

	// Add edges
	foreach(Edge e, mesh()->edges())
	{
		Face f1 = mesh()->face(mesh()->halfedge(e,0));
		Face f2 = mesh()->face(mesh()->halfedge(e,1));

		QString target1 = faceTarget[f1];
		QString target2 = faceTarget[f2];

		if(target1 == target2) continue;

		g.AddEdge(id[target1], id[target2], 1);
	}

	std::set< int > nodes = g.GetNodes();
	std::vector< Graph<int,int>::Edge > edges = g.GetEdges();

	QFile out(QString("%1_graph.gexf").arg(mesh()->name));
	out.open(QIODevice::WriteOnly|QIODevice::Text);
	out.write("<?xml version='1.0' encoding='UTF-8'?> <gexf xmlns='http://www.gexf.net/1.2draft' version='1.2'> <graph mode='static' defaultedgetype='undirected'>");

	out.write("<nodes>"); // Start nodes
	foreach(int n, nodes)
	{
		out.write( qPrintable(QString("<node id='%1' label='%2'/>").arg(n).arg(id.key(n).split('_').last())) );
	}
	out.write("</nodes>"); // End nodes

	out.write("<edges>"); // Start edges
	int eid = 0;
	for(std::vector< Graph<int,int>::Edge >::iterator e = edges.begin(); e != edges.end(); e++)
	{
		out.write( qPrintable(QString("<edge id='%1' source='%2' target='%3'/>").arg(eid++).arg(e->index).arg(e->target)) );
	}
	out.write("</edges>"); // End edges

	out.write("</graph></gexf>");
}

void segment::classifyVertsFromFaces()
{
	foreach(Vertex v, mesh()->vertices()){
		Surface_mesh::Face_around_vertex_circulator adjF(mesh(), v), fend = adjF;
		do { if(fclass[adjF] == SHEET) { vclass[v] = 1.0; break; } } while(++adjF != fend);
	}
}

void segment::GrowRegions()
{
	// Clear regions
	curve_regions.clear();
	sheet_regions.clear();

	resetVisitFlag();

	foreach(Face f, mesh()->faces())
	{
		// Skip visited
		if(fvisited[f]) 
			continue;

		int currClass = fclass[f];

		HalfedgeFaceIter h(mesh(), f), eend = h;
		do{ 
			// Check if class of adjacent face doesn't match current face
			if(fclass[ mesh()->face(mesh()->opposite_halfedge(h)) ] != currClass)
			{
				if(currClass == CURVE) curve_regions.push_back( growRegion(f) );
				if(currClass == SHEET) sheet_regions.push_back( growRegion(f) );
				break;
			}
		} while(++h != eend);
	}

	// For single class classification of entire mesh
	if( !curve_regions.size() ){
		Face f(0); 
		int currClass = fclass[f];

		if(currClass == CURVE) curve_regions.push_back( growRegion(f) );
		if(currClass == SHEET) sheet_regions.push_back( growRegion(f) );
	}
}

Region segment::growRegion( Face seedFace )
{
	Region region;

	QStack<Face> toVisit;
	toVisit.push(seedFace);

	int currClass = fclass[seedFace];

	// Recursive grow:
	while( !toVisit.isEmpty() )
	{
		// Get first item, add to growing region
		Face f = toVisit.pop();
		region.insert(f);

		// Check adjacent faces
		HalfedgeFaceIter h(mesh(), f), eend = h;
		do{ 
			Face adj = mesh()->face(mesh()->opposite_halfedge(h));

			// If adjacent is not visited and has same class, push to stack
			if( !fvisited[adj] && fclass[adj] == currClass )
				toVisit.push(adj);
		} while(++h != eend);

		// Mark as visited
		fvisited[f] = true;
	}

	return region;
}

void segment::invertRegion( Region & r )
{
	int currClass = fclass[*r.begin()];
	int toClass = (currClass == SHEET) ? CURVE : SHEET;

	foreach(Face f, r)
		fclass[f] = toClass;
}

void segment::setMeshFromRegion( Region & r, SurfaceMesh::Model * m)
{
	// Get set of vertices
	QSet<Vertex> verts; QMap< Face, QVector<Vertex> > adjV;
	foreach(Face f, r){
		VertFaceIter v(mesh(), f), vend = v;
		do{ verts.insert(v); adjV[f].push_back(v); } while (++v != vend);
	}

	// VERTICES: Map them to ordered numbers, and add to new mesh
	QMap<Vertex,Vertex> vmap;
	QMap<Vertex,Vertex> inv_vmap;
	QSetIterator<Vertex> vi(verts);

	while(vi.hasNext()){
		Vertex v = vi.next();
		Vertex newV = Vertex(vmap.size());
		vmap[v] = newV;
		m->add_vertex(points[v]);
		inv_vmap[newV] = v;
	}

	// FACES
	QMap<Face,Face> fmap;
	foreach(Face f, r){
		if(mesh()->valence(f) == 3)
		{
			fmap[Face(fmap.size())] = f;
			m->add_triangle(vmap[adjV[f][0]], vmap[adjV[f][1]], vmap[adjV[f][2]]);
			faceTarget[f] = m->name;
		}
	}

	// Mapping to original surface
	SurfaceMesh::Model::Vertex_property<Vertex> surface_vmap = m->vertex_property<Vertex>("v:original", Vertex());
	SurfaceMesh::Model::Face_property<Face> surface_fmap = m->face_property<Face>("f:original", Face());

	foreach(Vertex v, m->vertices())
		surface_vmap[v] = inv_vmap[v];
	
	foreach(Face f, m->faces())
		surface_fmap[f] = fmap[f];
}

void segment::collectRing( Vertex v, QSet<Vertex> & set, int level )
{
	if(level > 0){
		VertIter vi(mesh(), v), vend = vi;
		do{	collectRing(vi, set, level - 1); set.insert(vi); } while(++vi != vend);
	}
	else
		set.insert(v);
}

double segment::median( QVector<double> vec )
{
	typedef vector<int>::size_type vec_sz;
	vec_sz size = vec.size();
	if (size == 0) return DBL_MAX;
	qSort(vec.begin(), vec.end());
	vec_sz mid = size / 2;
	return size % 2 == 0 ? (vec[mid] + vec[mid-1]) / 2 : vec[mid];
}

double segment::minAngle(Face f)
{
	double minAngle(DBL_MAX);

	HalfedgeFaceIter h(mesh(), f), eend = h;
	do{ 
		Vector3 a = points[mesh()->to_vertex(h)];
		Vector3 b = points[mesh()->from_vertex(h)];
		Vector3 c = points[mesh()->to_vertex(mesh()->next_halfedge(h))];

		double d = dot((b-a).normalized(), (c-a).normalized());
		double angle = acos(qRanged(-1.0, d, 1.0));

		minAngle = qMin(angle, minAngle);
	} while(++h != eend);

	return minAngle;
}

Vector3 segment::center( Face f )
{
	HalfedgeFaceIter h(mesh(), f), eend = h;

	Vector3 a = points[mesh()->to_vertex(h)];
	Vector3 b = points[mesh()->from_vertex(h)];
	Vector3 c = points[mesh()->to_vertex(mesh()->next_halfedge(h))];

	return (a + b + c) / 3.0;
}

#include "CurveskelHelper.h"
#include "MyPriorityQueue.h"
using namespace CurveskelTypes;

void segment::splitCurveRegion(Region & r)
{
	SurfaceMesh::Model m;
	setMeshFromRegion(r, &m);
	SurfaceMesh::Vector3VertexProperty m_points = m.vertex_property<SurfaceMesh::Vector3>(SurfaceMesh::VPOINT);
	SurfaceMesh::Model::Face_property<Face> original_face = m.face_property<Face>("f:original", Face());

	// Perform edge collapses until we get 1D curves:
	CurveskelModel skel("");

	// 1) Transfer vertices
	foreach(SurfaceMesh::Vertex v, m.vertices()){
		SurfaceMesh::Point p = m_points[v];
		skel.add_vertex(CurveskelTypes::Point(p[0], p[1], p[2]) );
	}

	// 2) Faces
	foreach(SurfaceMesh::Face f, m.faces()){
		std::vector<CurveskelTypes::Vertex> m_vertices;
        Surface_mesh::Vertex_around_face_circulator vit = m.vertices(f),vend=vit;
		do{ m_vertices.push_back(CurveskelTypes::Vertex( SurfaceMesh::Vertex(vit).idx() )); } while(++vit != vend);
		skel.add_face( m_vertices );
	}

	CurveskelHelper h( &skel );
	CurveskelTypes::ScalarEdgeProperty elen = h.computeEdgeLengths();
	CurveskelModel::Vertex_property< std::set<CurveskelTypes::Vertex> > vrecord = skel.vertex_property< 
			std::set<CurveskelTypes::Vertex> >("v:collapse-from", std::set<CurveskelTypes::Vertex>());
	
	// 3) Add to priority queue
	MyPriorityQueue queue( &skel );
	foreach(CurveskelTypes::Edge edge, skel.edges())
		queue.insert(edge, elen[edge]);

	// first add yourself to the set
	foreach(CurveskelTypes::Vertex v, skel.vertices())
		vrecord[v].insert(v);

	/// 4) Collapse cycle
	while (!queue.empty()){

		/// Retrieve shortest edge
		CurveskelModel::Edge e = queue.pop();

		/// Make sure edge was not already dealt with by previous collapses
		if(!skel.has_faces(e) || skel.is_deleted(e) || !skel.is_valid(e))
			continue;

		CurveskelModel::Vertex v1 = skel.vertex(e, 0); // 'v1' will be deleted
		CurveskelModel::Vertex v2 = skel.vertex(e, 1);

		/// Do collapse
		skel.collapse(e);

		/// record collapsed vertex
		vrecord[v2].insert(v1);

		// carry its records too
		vrecord[v2].insert(vrecord[v1].begin(), vrecord[v1].end());

		/// Update length of edges incident to remaining vertex
		CurveskelModel::Edge_around_vertex eit (&skel, v2);

		while(!eit.end())
		{
			CurveskelModel::Edge edge = eit;

			double newLength = skel.edge_length(edge);

			// If edge still in queue, update its position
			if(queue.has(edge))
				queue.update(edge, newLength);

			++eit;
		}
	}

	// Save mapping between the region's surface and underlying skeleton curve graph
	QMap< int, std::set<int> > vmap;
	int skel_vidx = 0;
	foreach(CurveskelModel::Vertex v, skel.vertices()){
		if(!skel.is_deleted(v)){
			foreach(CurveskelModel::Vertex vj, vrecord[v])
				vmap[ skel_vidx ].insert( vj.idx() );
			skel_vidx++;
		}
	}
	skel.garbage_collection();

	// Prepare variables for visiting branches and assigning IDs
	CurveskelTypes::BoolVertexProperty visited = skel.vertex_property<bool>("v:visited", false);
	CurveskelTypes::IntVertexProperty branchID = skel.vertex_property<int>("v:branch", false);
	std::set<CurveskelTypes::Vertex> junctions = skel.junctions();

	// Mark junctions as visited
	foreach(CurveskelTypes::Vertex v, junctions)
		visited[v] = true;

	int branch_id = 0;

	// Go over separate individual branches and assign IDs
	foreach(CurveskelTypes::Vertex vi, skel.vertices())
	{
		if(visited[vi]) continue;

		QStack< CurveskelTypes::Vertex > stack;
		stack.push(vi);

		while(!stack.empty()){
			CurveskelTypes::Vertex cur = stack.pop();

			foreach(CurveskelTypes::Vertex vj, skel.adjacent_set(cur)){
				branchID[vj] = branch_id;

				if( !visited[vj] ){
					stack.push(vj);
					visited[vj] = true;
				}
			}
		}

		branch_id++;
	}

	// Propagate branch ID to original structure
	RegionVector sub_regions;
	sub_regions.resize( branch_id );

	SurfaceMesh::Model::Face_property<bool> face_assigned = m.face_property<bool>("f:assignedID", false);

	foreach(CurveskelModel::Vertex skel_v, skel.vertices()){
		int curr_id = branchID[ skel_v ];

		// For each surface vertex belonging to skeleton vertex 'skel_v'
		foreach(int vi, vmap[ skel_v.idx() ])
		{
			// For each adjacent face of vi
			Surface_mesh::Face_around_vertex_circulator adjF( &m, Surface_mesh::Vertex(vi) ), fend = adjF;
			do {
				if(!face_assigned[ adjF ])
				{
					sub_regions[ curr_id ].insert( original_face[adjF] ); 
					face_assigned[ adjF ] = true;
				}
			} while(++adjF != fend);
		}
	}

	// Empty out the original region
	r.clear();

	// Add its sub-regions
	foreach(Region r, sub_regions)
		curve_regions.push_back(r);


	// DEBUG
	/*static std::vector< std::vector<double> > rc = randomColors(branch_id + 1);
	foreach(CurveskelTypes::Vertex v, skel.vertices()){
		int i = branchID[v];

		CurveskelTypes::Vector3 pnt = skel.get_vertex_property<CurveskelTypes::Vector3>(CurveskelTypes::VPOINT)[v];
		Eigen::Vector3d p (pnt.x(), pnt.y(), pnt.z());
		
		QColor color = QColor::fromRgbF(rc[i][0],rc[i][1],rc[i][2]);
		drawArea()->drawPoint(p,10, color);
	}*/

	// DEUBG:
	/*CurveskelModel * s = new CurveskelModel;
	foreach(CurveskelModel::Vertex v, skel.vertices()) s->add_vertex(skel.get_vertex_property<CurveskelTypes::Vector3>(CurveskelTypes::VPOINT)[v]);
	foreach(CurveskelModel::Edge e, skel.edges()) s->add_edge(skel.vertex(e, 0), skel.vertex(e, 1));

	foreach(CurveskelTypes::Vertex v, s->junctions())
	{
		CurveskelTypes::Vector3 pnt = skel.get_vertex_property<CurveskelTypes::Vector3>(CurveskelTypes::VPOINT)[v];
		Eigen::Vector3d p (pnt.x(), pnt.y(), pnt.z());
		drawArea()->drawPoint(p,10);
	}

	document()->addModel(s);*/
}


Q_EXPORT_PLUGIN(segment)
back to top