Skip to main content
  • Home
  • Development
  • Documentation
  • Donate
  • Operational login
  • Browse the archive

swh logo
SoftwareHeritage
Software
Heritage
Archive
Features
  • Search

  • Downloads

  • Save code now

  • Add forge now

  • Help

  • adfc2e5
  • /
  • segment
  • /
  • segment.cpp
Raw File Download

To reference or cite the objects present in the Software Heritage archive, permalinks based on SoftWare Hash IDentifiers (SWHIDs) must be used.
Select below a type of object currently browsed in order to display its associated SWHID and permalink.

  • content
  • directory
content badge Iframe embedding
swh:1:cnt:3f756f4431292e743d339c4975270fc0f240e3e5
directory badge Iframe embedding
swh:1:dir:1d6653f9f764314e8029f2afc6f043463d3f9d49

This interface enables to generate software citations, provided that the root directory of browsed objects contains a citation.cff or codemeta.json file.
Select below a type of object currently browsed in order to generate citations for them.

  • content
  • directory
Generate software citation in BibTex format (requires biblatex-software package)
Generating citation ...
Generate software citation in BibTex format (requires biblatex-software package)
Generating citation ...
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

Software Heritage — Copyright (C) 2015–2025, The Software Heritage developers. License: GNU AGPLv3+.
The source code of Software Heritage itself is available on our development forge.
The source code files archived by Software Heritage are available under their own copyright and licenses.
Terms of use: Archive access, API— Content policy— Contact— JavaScript license information— Web API