DGtal-team / DGtal

Digital Geometry Tools and Algorithm Library
https://dgtal.org
GNU Lesser General Public License v3.0
370 stars 115 forks source link

Is there a way to write the border of a digital set to an OBJ? #1409

Closed psarahdactyl closed 5 years ago

psarahdactyl commented 5 years ago

I want to avoid marching cube type artifacts in my obj files, where some of the surface is missing on the boundaries of the voxel grid. I get these when I make a DigitalSurface or a TrianglulatedSurface from a BinaryImage of my DigitalSet.

In this example, which I've gotten running on my own DigitalSet, I want to write out just the border voxels to an OBJ. Is there a way to do this?

copyme commented 5 years ago

@psarahdactyl If you want to write the border points in cases like in one from the example you refer to then you can use something like this

#include <iostream>
#include "DGtal/base/Common.h"

#include "DGtal/io/readers/VolReader.h"
#include "DGtal/io/Color.h"
#include "DGtal/images/ImageSelector.h"
#include "DGtal/helpers/StdDefs.h"
#include "ConfigExamples.h"
#include "DGtal/io/boards/Board3D.h"

///////////////////////////////////////////////////////////////////////////////

using namespace std;
using namespace DGtal;

///////////////////////////////////////////////////////////////////////////////

int main( int argc, char** argv )
{
 typedef SpaceND< 3,int > Z3;
 typedef MetricAdjacency< Z3, 1 > Adj6;
 typedef MetricAdjacency< Z3, 2 > Adj18;
 typedef DigitalTopology< Adj6, Adj18 > DT6_18;

 Adj6 adj6;
 Adj18 adj18;
 DT6_18 dt6_18( adj6, adj18, JORDAN_DT );

 typedef Z3::Point Point;
 typedef HyperRectDomain< Z3 > Domain;
 typedef Domain::ConstIterator DomainConstIterator;
 typedef DigitalSetSelector< Domain, BIG_DS+HIGH_BEL_DS >::Type DigitalSet;
 typedef Object<DT6_18, DigitalSet> ObjectType;

 Point p1( -50, -50, -50 );
 Point p2( 50, 50, 50 );
 Domain domain( p1, p2 );
 Point c( 0, 0 );
 // diamond of radius 30
 DigitalSet diamond_set( domain );
 for ( DomainConstIterator it = domain.begin(); it != domain.end(); ++it )
   {
     if ( (*it - c ).norm1() <= 30 ) diamond_set.insertNew( *it );
   }
 ObjectType diamond( dt6_18, diamond_set );
 // The following line takes almost no time.
 ObjectType diamond_clone( diamond );
 // Since one of the objects is modified, the set is duplicated at the following line
 diamond_clone.pointSet().erase( c );
 ObjectType bdiamond = diamond.border(); // one component
 ObjectType bdiamond_clone = diamond_clone.border(); // two components

 Board3D<> board;
 board << SetMode3D(domain.className(), "Paving");
 board << CustomColors3D(Color(250, 0,0),Color(250, 0,0));

 for(auto it : bdiamond_clone.pointSet())
   board << it;

 board.saveOBJ("/home/kacper/dgtalBoard3D-1bis-points.obj");

 return 0;

}

image

It is basically the example with some lines changed at the very end.

psarahdactyl commented 5 years ago

thank you so much! I wasn't quite sure how to use the Board class.

copyme commented 5 years ago

@psarahdactyl Great! Could you please close the issue if the solution helped you out.

copyme commented 5 years ago

@psarahdactyl By the way, you do not need the loop over points. You can send the object directly, i.e., replace

 for(auto it : bdiamond_clone.pointSet())
   board << it; 

with board << bdiamond_clone; Board3D more or less works like Viewer3D – you can send an object to it and control its appearance by modifiers: modes like Paving or colors, etc. I am not the best person to explain this stuff but I am sure that we can help you with your further questions. So do not hesitate to ask. Also, examples are a good starting point. Good luck!

psarahdactyl commented 5 years ago

Sorry to ask so many questions, but say now I want to get the vertices and faces of the voxel geometry. I have been looking around the documentation and the inline header files for Display3D and Board3D and see that there is an addCube function which seems to deal with the actual geometry. Is there an easier way exposed to the user where I can access this? Maybe through the PointSet?

dcoeurjo commented 5 years ago

You should have a look to the brand new Shortcuts https://dgtal.org/doc/stable/moduleShortcuts.html

There are direct methods to export a digital surface to OBJ (either primal or dual surface) or to obtain easy vertices/faces accessors.

dcoeurjo commented 5 years ago

Like getSurfelRange or getPointelRange

psarahdactyl commented 5 years ago

Do I first need to make the DigitalTopology into a DigitalSurface?

typedef DigitalSetSelector< Domain, BIG_DS+HIGH_BEL_DS >::Type DigitalSet;

typedef MetricAdjacency< Z3, 2 > Adj18;
typedef DigitalTopology< Adj6, Adj18 > DT6_18;
Adj6 adj6;
Adj18 adj18;
DT6_18 dt6_18( adj6, adj18, JORDAN_DT );
typedef Object<DT6_18, DigitalSet> ObjectType;

typedef DigitalSetBoundary< Z3i::KSpace, DigitalSet > Boundary;

ObjectType b_scene = sc.border();
DigitalSet border = b_scene.pointSet();
Boundary boundary(Z3, border, Adj18);

I was trying to make a DigitalSetBoundary because it has a surfel iterator but I can't figure out how to use the constructor on line 192 of DigitalSetBoundary.h https://dgtal.org/doc/stable/classDGtal_1_1DigitalSetBoundary.html#aa8c18e7161085386f2aa9328a8382f11

psarahdactyl commented 5 years ago

I'm having trouble understanding what aKSpace is. in the documentation it says it's a cellular grid space. Is this the same this as a Khalimsky space typedef KhalimskySpaceND<3,int> KSpace;?

DigitalSetBoundary( ConstAlias<KSpace> aKSpace,
                         const DigitalSet & aSet,
                         const Adjacency & adj = Adjacency( true ) );
dcoeurjo commented 5 years ago

Yes it is. If you want to construct a digital surface from a set, a binary image, or a point predicate (all these objects are really close), the makeLightDigitalSurface or makeDigitalSurface in the Shortcuts doc would do the job.

In most situations, no need to manually construct the KSpace, it is usually given by the container.

psarahdactyl commented 5 years ago

Thank you!

gaoalexander commented 3 months ago

Yes it is. If you want to construct a digital surface from a set, a binary image, or a point predicate (all these objects are really close), the makeLightDigitalSurface or makeDigitalSurface in the Shortcuts doc would do the job.

In most situations, no need to manually construct the KSpace, it is usually given by the container.

Hi @dcoeurjo,

I had a short follow-up question to this: it appears that makeDigitalSurface as documented here can accept either an "indexed digital surface" or a "binary image" as input types. It's still not clear to me how I can use these functions with the "digital set" type as input.

Wonder if you're able to clarify (i.e. how can I obtain a digital surface given a digital set)? I am basically trying to import a triangle mesh from an OBJ file, voxelize it, obtain its digital surface (boundary), and save the result back as a quadrilateral mesh OBJ file.

Thank you very much.

JacquesOlivierLachaud commented 3 months ago

Hello If you wish to construct the digital surface that is the boundary of a digital set, you can have a look at this doc https://dgtal-team.github.io/doc-pr/pr1736/moduleDigitalSurfaces.html#dgtal_digsurf_sec1_1 A DigitalSetBoundary class correspond to your purpose.

JacquesOlivierLachaud commented 3 months ago

Otherwise there are helper functions that simply builds the set of cells forming the boundary (2-cells in 3D). This can be done by scanning the whole image as:

KSpace::SCellSet boundary;
Surfaces<KSpace>::sMakeBoundary( boundary, ks, set3d,
                                   image.domain().lowerBound(),
                                   image.domain().upperBound() );

where ks is your KSpace and set3d is a DigitalSet.

dcoeurjo commented 3 months ago

Alternatively, you can use the mesh2vol, volFillInterior and volBoundary2obj tools from https://github.com/DGtal-team/DGtalTools if you want to from the command line.