DGtalTools  0.9.4
3dVolMarchingCubes.cpp
1 
29 #include <iostream>
32 #include <queue>
33 #include <boost/program_options/options_description.hpp>
34 #include <boost/program_options/parsers.hpp>
35 #include <boost/program_options/variables_map.hpp>
36 #include <DGtal/kernel/sets/SetPredicate.h>
37 #include <DGtal/io/readers/VolReader.h>
38 #include <DGtal/images/ImageSelector.h>
39 #include <DGtal/images/SimpleThresholdForegroundPredicate.h>
40 #include <DGtal/images/ImageLinearCellEmbedder.h>
41 #include <DGtal/shapes/Shapes.h>
42 #include <DGtal/kernel/CanonicEmbedder.h>
43 #include <DGtal/helpers/StdDefs.h>
44 #include <DGtal/topology/helpers/Surfaces.h>
45 #include <DGtal/topology/DigitalSurface.h>
46 #include <DGtal/topology/SetOfSurfels.h>
47 #include <DGtal/geometry/volumes/KanungoNoise.h>
49 
51 
52 using namespace DGtal;
53 using namespace Z3i;
54 namespace po = boost::program_options;
55 
57 
115 int main( int argc, char** argv )
116 {
118  // parse command line ----------------------------------------------
119  po::options_description general_opt("Allowed options are ");
120  general_opt.add_options()
121  ("help,h", "display this message")
122  ("input,i", po::value<std::string>(), "the volume file (.vol)" )
123  ("threshold,t", po::value<unsigned int>()->default_value(1), "the value that defines the isosurface in the image (an integer between 0 and 255)." )
124  ("adjacency,a", po::value<unsigned int>()->default_value(0), "0: interior adjacency, 1: exterior adjacency")
125  ("output,o", po::value<std::string>()->default_value( "marching-cubes.off" ), "the output OFF file that represents the geometry of the isosurface")
126  ("noise,n", po::value<double>(), "Kanungo noise level in ]0,1[. Note that only the largest connected component is considered and that no specific embedder is used.");
127 
128 
129  bool parseOK=true;
130  po::variables_map vm;
131  try{
132  po::store(po::parse_command_line(argc, argv, general_opt), vm);
133  }catch(const std::exception& ex){
134  parseOK=false;
135  trace.info()<< "Error checking program options: "<< ex.what()<< std::endl;
136  }
137  po::notify(vm);
138  if ( !parseOK || vm.count("help") || ( argc <= 1 ) )
139  {
140  std::cout << "Usage: " << argv[0]
141  << " [-i <fileName.vol>] [-t <threshold>] [-a <adjacency>] [-o <output.off>]" << std::endl
142  << "Outputs the isosurface of value <threshold> of the volume <fileName.vol> as an OFF file <output.off>. The <adjacency> (0/1) allows to choose between interior (6,18) and exterior (18,6) adjacency." << std::endl
143  << general_opt << std::endl;
144  return 0;
145  }
146  if ( ! vm.count("input") )
147  {
148  trace.error() << "The input file name was defined." << std::endl;
149  return 1;
150  }
151  std::string inputFilename = vm["input"].as<std::string>();
152  unsigned int threshold = vm["threshold"].as<unsigned int>();
153  bool intAdjacency = ( vm["adjacency"].as<unsigned int>() == 0 );
154  std::string outputFilename = vm["output"].as<std::string>();
155  double noise ;
156  bool addNoise=false;
157  if (vm.count("noise") )
158  {
159  addNoise=true;
160  noise = vm["noise"].as<double>();
161  }
163 
165  trace.beginBlock( "Reading vol file into an image." );
167  Image image = VolReader<Image>::importVol(inputFilename);
168 
170  ThresholdedImage thresholdedImage( image, threshold );
171  trace.endBlock();
173 
174 
176  trace.beginBlock( "Construct the Khalimsky space from the image domain." );
177  KSpace ks;
178  bool space_ok =
179  ks.init( image.domain().lowerBound(), image.domain().upperBound(), true );
180  if (!space_ok)
181  {
182  trace.error() << "Error in the Khamisky space construction."<<std::endl;
183  return 2;
184  }
185  trace.endBlock();
187 
189  typedef SurfelAdjacency<KSpace::dimension> MySurfelAdjacency;
190  MySurfelAdjacency surfAdj( intAdjacency ); // interior in all directions.
192 
194  trace.beginBlock( "Extracting boundary by scanning the space. " );
195  typedef KSpace::SurfelSet SurfelSet;
196  typedef SetOfSurfels< KSpace, SurfelSet > MySetOfSurfels;
197  typedef DigitalSurface< MySetOfSurfels > MyDigitalSurface;
198  MySetOfSurfels theSetOfSurfels( ks, surfAdj );
199 
200  if (addNoise)
201  {
202  trace.info()<<"Adding some noise."<<std::endl;
203  KanungoNoise<ThresholdedImage, Z3i::Domain> kanungo(thresholdedImage, image.domain(), noise);
204  std::vector< std::vector<SCell > > vectConnectedSCell;
205  trace.info()<<"Extracting all connected components."<<std::endl;
206  Surfaces<KSpace>::extractAllConnectedSCell( vectConnectedSCell, ks, surfAdj,
207  kanungo, false);
208  if( vectConnectedSCell.size() == 0 )
209  {
210  trace.error()<< "No surface component exists. Please check the vol file --min and --max parameters." << std::endl;
211  return 3;
212  }
213 
214  trace.info()<<vectConnectedSCell.size()<<" components."<<std::endl;
215 
216  trace.info()<<"Extracting the largest one."<<std::endl;
217  int cc_max_size_idx = -1;
218  auto it_max = std::max_element( vectConnectedSCell.begin(), vectConnectedSCell.end(),
219  [] (std::vector<SCell >& v1, std::vector<SCell >& v2)
220  { return v1.size() < v2.size(); } );
221  cc_max_size_idx = std::distance( vectConnectedSCell.begin(), it_max );
222  theSetOfSurfels.surfelSet().insert( vectConnectedSCell[ cc_max_size_idx ].begin(),
223  vectConnectedSCell[ cc_max_size_idx ].end() );
224  }
225  else
227  theSetOfSurfels.surfelSet(), ks, thresholdedImage,
228  image.domain().lowerBound(), image.domain().upperBound() );
229 
230  MyDigitalSurface digSurf( theSetOfSurfels );
231  trace.info() << "Digital surface has " << digSurf.size() << " surfels."
232  << std::endl;
233  trace.endBlock();
235 
236 
238  trace.beginBlock( "Making OFF surface. " );
239  // Describes how voxels are embedded into Euclidean space.
240  typedef CanonicEmbedder< Space > MyEmbedder;
241  // Describes how the centroid surface elements is placed in-between embedded voxels.
243  CellEmbedder cellEmbedder;
244  MyEmbedder trivialEmbedder;
245 
246  // The +0.5 is to avoid isosurface going exactly through a voxel
247  // center, especially for binary volumes.
248 
249  //Making sure that we probe inside the domain.
250  Image largerImage( Domain( image.domain().lowerBound() - KSpace::Vector::diagonal(2),
251  image.domain().upperBound() + KSpace::Vector::diagonal(2)));
252  for(auto p: image.domain())
253  largerImage.setValue( p, image(p));
254 
255  std::ofstream out( outputFilename.c_str() );
256  if (addNoise)
257  {
258  if ( out.good() )
259  digSurf.exportSurfaceAs3DOFF( out );
260  else
261  {
262  trace.error()<<"IO error while opening the output file"<<std::endl;
263  exit(2);
264  }
265  }
266  else
267  {
268  cellEmbedder.init( ks, image, trivialEmbedder,
269  ( (double) threshold ) + 0.5 );
270 
271  if ( out.good() )
272  digSurf.exportEmbeddedSurfaceAs3DOFF( out, cellEmbedder );
273  {
274  trace.error()<<"IO error while opening the output file"<<std::endl;
275  exit(2);
276  }
277  }
278 
279  out.close();
280  trace.endBlock();
282  return 0;
283 }
284 
void beginBlock(const std::string &keyword="")
static Self diagonal(Component val=1)
HyperRectDomain< Space > Domain
std::set< SCell > SurfelSet
double endBlock()
static void sMakeBoundary(SCellSet &aBoundary, const KSpace &aKSpace, const PointPredicate &pp, const Point &aLowerBound, const Point &aUpperBound)
static ImageContainer importVol(const std::string &filename, const Functor &aFunctor=Functor())
bool init(const Point &lower, const Point &upper, bool isClosed)
static void extractAllConnectedSCell(std::vector< std::vector< SCell > > &aVectConnectedSCell, const KSpace &aKSpace, const SurfelAdjacency< KSpace::dimension > &aSurfelAdj, const PointPredicate &pp, bool forceOrientCellExterior=false)
const Point & upperBound() const
Trace trace(traceWriterTerm)
std::ostream & info()
const Domain & domain() const
const Point & lowerBound() const
std::ostream & error()