2 * This program is free software: you can redistribute it and/or modify
3 * it under the terms of the GNU Lesser General Public License as
4 * published by the Free Software Foundation, either version 3 of the
5 * License, or (at your option) any later version.
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
12 * You should have received a copy of the GNU General Public License
13 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 * @file VoxelComplexFunctions.ih
19 * @author Pablo Hernandez-Cerdan (\c pablo.hernandez.cerdan@outlook.com)
20 * Institute of Fundamental Sciences. Massey University.
21 * Palmerston North, New Zealand
25 * Implementation of inline methods defined in VoxelComplexFunctions.h
27 * This file is part of the DGtal library.
31 //////////////////////////////////////////////////////////////////////////////
33 #include <DGtal/topology/DigitalTopology.h>
35 //////////////////////////////////////////////////////////////////////////////
37 ///////////////////////////////////////////////////////////////////////////////
38 // IMPLEMENTATION of inline methods.
39 ///////////////////////////////////////////////////////////////////////////////
41 //-----------------------------------------------------------------------------
42 template < typename TComplex >
45 asymetricThinningScheme(
48 std::pair<typename TComplex::Cell, typename TComplex::Data>(
49 const typename TComplex::Clique &)
54 const typename TComplex::Cell & )
58 if(verbose) trace.beginBlock("Asymetric Thinning Scheme");
60 using Cell = typename TComplex::Cell;
61 using Data = typename TComplex::Data;
63 // Initialize K set, and VoxelComplex containers;
64 struct FirstComparator
66 bool operator() (const std::pair<Cell, Data>& lhs, const std::pair<Cell, Data>& rhs) const
68 return lhs.first < rhs.first;
71 std::set<std::pair<Cell, Data>, FirstComparator> selected_voxels_per_dim;
72 TComplex already_selected_voxels(vc.space());
73 TComplex constraint_set(vc.space());
75 auto & Z = selected_voxels_per_dim;
76 auto & Y = already_selected_voxels;
77 auto & K = constraint_set;
79 Y.copySimplicityTable(vc);
80 K.copySimplicityTable(vc);
81 typename TComplex::Parent x_y(vc.space());
82 typename TComplex::Parent x_k(vc.space());
84 bool stability{false};
85 uint64_t generation{0};
86 auto xsize = X.nbCells(3);
87 auto xsize_old = xsize;
88 typename TComplex::CliqueContainer critical_cliques;
91 trace.info() << "generation: " << generation <<
92 " ; X.nbCells(3): " << xsize << std::endl;
98 /* Select voxels from critical d-cliques,
99 * having priority d=3 down to 0 using Select(clique)
100 * which returns the selected voxel. */
101 // X.close(); // Close K-Space from current voxels.
102 for (int d = 3 ; d >= 0 ; --d) {
104 /* Y is the already_selected voxels (in this generation)
105 * Search critical cliques only in the cells of X-Y,
106 * but which are critical for X. */
109 critical_cliques = X.criticalCliquesForD(d, x_y);
110 for(auto & clique : critical_cliques){
112 // if (! (clique <= (x_y)) ) continue ;
113 auto yinclude = std::find_if(
116 [&Y]( const Cell& c) {
119 if (yinclude != std::end(clique)) continue ;
120 Z.insert(Select(clique));
121 } // critical_cliques
122 // Y = Y union Z. From set to VoxelComplex
123 for( const auto & selected_celldata_pair : Z)
124 Y.insertVoxelCell(selected_celldata_pair);
127 // X - K is equal to X-Y, which is equal to a Ynew - Yold
129 for (auto it = x_k.begin(3), itE = x_k.end(3) ; it != itE ; ++it ){
130 auto new_voxel = it->first ;
131 if( Skel(X, new_voxel) == true){
132 K.insertVoxelCell(new_voxel);
133 // K.insertCell(3, new_voxel);
134 // K.objectSet().insert(K.space().uCoords(new_voxel));
139 xsize = X.nbCells(3);
140 if(xsize == xsize_old) stability = true;
144 trace.info() << "generation: " << generation <<
145 " ; X.nbCells(3): " << xsize <<
146 " ; K (constrain set): " << K.nbCells(3) <<
147 " ; Y (selected in this generation): " << Y.nbCells(3) <<
148 " ; X - K ie. Ynew - Yold : " << x_k.nbCells(3) <<
149 " ; X - Y : " << x_y.nbCells(3) << std::endl;
151 } while( !stability );
160 template < typename TComplex >
163 persistenceAsymetricThinningScheme(
166 std::pair<typename TComplex::Cell, typename TComplex::Data> (
167 const typename TComplex::Clique &)
172 const typename TComplex::Cell & )
174 uint32_t persistence,
177 if(verbose) trace.beginBlock("Persistence asymetricThinningScheme");
179 using Cell = typename TComplex::Cell;
180 using Data = typename TComplex::Data;
182 // Initialize Z set, and VoxelComplex containers;
183 struct FirstComparator
185 bool operator() (const std::pair<Cell, Data>& lhs, const std::pair<Cell, Data>& rhs) const
187 return lhs.first < rhs.first;
190 TComplex already_selected_voxels(vc.space());
191 TComplex constraint_set(vc.space());
193 auto & Y = already_selected_voxels;
194 auto & K = constraint_set;
196 TComplex x_y(vc.space());
197 Y.copySimplicityTable(vc);
198 K.copySimplicityTable(vc);
199 x_y.copySimplicityTable(vc);
201 bool stability{false};
202 unsigned int generation{0};
203 auto xsize = X.nbCells(3);
204 auto xsize_old = xsize;
205 const bool close_it = true;
206 typename TComplex::CliqueContainer critical_cliques;
209 trace.info() << "Initial Voxels at generation: " << generation <<
210 " ; X.nbCells(3): " << xsize << std::endl;
212 // cubical cell data (aka, the birth_date) is initialized to zero already.
215 // Update birth_date for our Skel function. (isIsthmus for example)
216 for (auto it = X.begin(3), itE = X.end(3) ; it != itE ; ++it ){
217 // Ignore voxels existing in K set.(ie: X-K)
218 if (K.findCell(3, it->first) != K.end(3))
220 auto & voxel = it->first ;
221 auto & ccdata = it->second.data ;
222 if( Skel(X, voxel) == true && ccdata == 0)
226 x_y = X; //optimization instead of x_y = X-Y, use x_y -= Y;
227 // d-cliques: From voxels (d=3) to pointels (d=0)
228 for (int d = 3 ; d >= 0 ; --d) {
229 // Search only critical cliques of X in the set X \ Y
230 x_y -= Y; // Y is growing in this d-loop.
231 critical_cliques = X.criticalCliquesForD(d, x_y);
233 for(auto & clique : critical_cliques)
235 // Find the selected voxel in x to get associated Data.
236 auto selected_voxelpair = Select(clique);
237 selected_voxelpair.second = X.findCell(3,selected_voxelpair.first)->second;
238 Y.insertVoxelCell(selected_voxelpair, close_it);
241 // Y is now populated with some selected critical cliques for this generation,
242 // plus with the fixed points in K.
243 // Store result (final or for the next generation)
246 /*** Modify K (the set of voxels to conserve) ***/
247 // Insert in K (inmutable set) only if the NEW critical cells of this generation (Y-K) are:
248 // a) Part of the Skeleton (Skel) user wants to preserve.
249 // b) persistent enough.
253 for (auto it = Y.begin(3), itE = Y.end(3) ; it != itE ; ++it ){
254 auto & voxel = it->first;
255 auto & ccdata = it->second.data;
256 bool is_skel = Skel(X, voxel);
257 bool is_persistent_enough = (generation + 1 - ccdata) >= persistence;
258 if (is_skel && is_persistent_enough)
259 K.insertVoxelCell(voxel, close_it, ccdata);
263 trace.info() << "generation: " << generation <<
264 " ; X at start generation: " << xsize_old <<
265 " ; K (constraint set): " << K.nbCells(3) <<
266 " ; Y - K: " << Y.nbCells(3) <<
267 " ; Y (X at the end): " << X.nbCells(3) <<
271 /***** Stability Update: ****/
272 xsize = X.nbCells(3);
273 if(xsize == xsize_old)
277 } while( !stability );
279 if(verbose) trace.endBlock();
284 //////////////////////////////////////////////////////////////////////////////
286 //////////////////////////////////////////////////////////////////////////////
287 template < typename TComplex >
288 std::pair<typename TComplex::Cell, typename TComplex::Data>
289 DGtal::functions::selectFirst(
290 const typename TComplex::Clique & clique)
292 return *clique.begin(3);
295 template < typename TComplex >
296 std::pair<typename TComplex::Cell, typename TComplex::Data>
297 DGtal::functions::selectRandom(
298 const typename TComplex::Clique & clique)
300 static std::mt19937 gen( std::random_device{}() );
301 return selectRandom<TComplex>(clique, gen);
304 template < typename TComplex, typename TRandomGenerator >
305 std::pair<typename TComplex::Cell, typename TComplex::Data>
306 DGtal::functions::selectRandom(
307 const typename TComplex::Clique & clique,
308 TRandomGenerator & gen
311 auto size = clique.nbCells(3);
312 std::uniform_int_distribution<> dis(0, size - 1);
313 auto it = clique.begin(3);
314 std::advance(it, dis(gen));
318 template < typename TDistanceTransform, typename TComplex >
319 std::pair<typename TComplex::Cell, typename TComplex::Data>
320 DGtal::functions::selectMaxValue(
321 const TDistanceTransform & dist_map,
322 const typename TComplex::Clique & clique)
324 typename TDistanceTransform::Value max_value{0};
325 typename TDistanceTransform::Value value{0};
326 using ComplexConstIterator = typename TComplex::CellMapConstIterator;
327 ComplexConstIterator selected_pair;
329 for(ComplexConstIterator it = clique.begin(3), itE = clique.end(3);
331 value = dist_map(clique.space().uCoords(it->first));
332 // trace.info() << "P: " << it->first << " V: " << value << std::endl;
333 if(value > max_value){
338 if(value == max_value){
339 // max_value = value;
340 // selected_pair = it;
341 continue; // TODO choose one wisely when they have same DM value.
345 return *selected_pair;
347 //////////////////////////////////////////////////////////////////////////////
348 // Skeleton Functions
349 //////////////////////////////////////////////////////////////////////////////
351 template < typename TComplex >
353 DGtal::functions::skelUltimate( const TComplex &,
354 const typename TComplex::Cell &)
358 /*--------------------------------------------------------------------------*/
360 template < typename TComplex >
362 DGtal::functions::skelEnd( const TComplex & vc,
363 const typename TComplex::Cell & cell)
365 const auto pnsize = vc.properNeighborhoodVoxels(cell).size();
366 return (pnsize == 1);
369 template < typename TComplex >
371 DGtal::functions::skelSimple( const TComplex & vc,
372 const typename TComplex::Cell & cell)
374 return vc.isSimple(cell);
377 template < typename TComplex >
379 DGtal::functions::skelIsthmus(const TComplex & vc,
380 const typename TComplex::Cell & cell)
382 if(twoIsthmus<TComplex>(vc,cell))
384 else if(oneIsthmus<TComplex>(vc,cell))
390 template < typename TComplex >
392 DGtal::functions::oneIsthmus(const TComplex & vc,
393 const typename TComplex::Cell & cell)
396 auto &ks = vc.space();
397 auto point_cell = ks.uCoords(cell);
398 // Create an object from the set.
399 using DigitalTopology = DGtal::Z3i::DT26_6;
400 using DigitalSet = DGtal::DigitalSetByAssociativeContainer<
402 std::unordered_set<typename DGtal::Z3i::Domain::Point>>;
403 using Object = DGtal::Object<DigitalTopology, DigitalSet>;
404 DigitalTopology::ForegroundAdjacency adjF;
405 DigitalTopology::BackgroundAdjacency adjB;
406 auto domain = DGtal::Z3i::Domain(ks.lowerBound(), ks.upperBound());
407 DigitalTopology topo(
408 adjF, adjB, DGtal::DigitalTopologyProperties::JORDAN_DT);
409 Object object(topo, domain);
410 for(auto & properNeighborhoodVoxel : vc.properNeighborhoodVoxels(cell))
412 object.pointSet().insertNew(ks.uCoords(properNeighborhoodVoxel));
415 // auto spN = vc.object().properNeighborhood(point_cell);
416 if (isZeroSurface(object)) return true;
419 TComplex pre_thin(ks);
420 pre_thin.construct(object.pointSet());
422 auto after_thin = asymetricThinningScheme<TComplex>( pre_thin,
423 selectFirst<TComplex>,
424 skelUltimate<TComplex>);
426 object.pointSet().clear();
427 for (auto it = after_thin.begin(3), itE = after_thin.end(3) ; it != itE ; ++it ){
428 object.pointSet().insertNew(ks.uCoords(it->first));
431 return isZeroSurface(object);
434 template < typename TComplex >
436 DGtal::functions::twoIsthmus( const TComplex & vc,
437 const typename TComplex::Cell & cell)
439 auto &ks = vc.space();
440 auto point_cell = ks.uCoords(cell);
441 // Create an object from the set.
442 using DigitalTopology = DGtal::Z3i::DT26_6;
443 using DigitalSet = DGtal::DigitalSetByAssociativeContainer<
445 std::unordered_set<typename DGtal::Z3i::Domain::Point>>;
446 using Object = DGtal::Object<DigitalTopology, DigitalSet>;
447 DigitalTopology::ForegroundAdjacency adjF;
448 DigitalTopology::BackgroundAdjacency adjB;
449 auto domain = DGtal::Z3i::Domain(ks.lowerBound(), ks.upperBound());
450 DigitalTopology topo(
451 adjF, adjB, DGtal::DigitalTopologyProperties::JORDAN_DT);
452 Object object(topo, domain);
453 for(auto & properNeighborhoodVoxel : vc.properNeighborhoodVoxels(cell))
455 object.pointSet().insertNew(ks.uCoords(properNeighborhoodVoxel));
457 if (isOneSurface(object)) return true;
460 TComplex pre_thin(ks);
461 pre_thin.construct(object.pointSet());
463 auto after_thin = asymetricThinningScheme<TComplex>( pre_thin,
464 selectFirst<TComplex>,
465 skelUltimate<TComplex>);
467 object.pointSet().clear();
468 for (auto it = after_thin.begin(3), itE = after_thin.end(3) ; it != itE ; ++it ){
469 object.pointSet().insertNew(ks.uCoords(it->first));
472 return isOneSurface(object);
475 template < typename TComplex >
477 DGtal::functions::skelWithTable(
478 const boost::dynamic_bitset<> & table,
479 const std::unordered_map<typename TComplex::Point, unsigned int> & pointToMaskMap,
481 const typename TComplex::Cell & cell)
483 auto conf = functions::getSpelNeighborhoodConfigurationOccupancy(
485 vc.space().uCoords(cell),
489 ///////////////////////////////////////////////////////////////////////////////
491 template < typename TObject >
493 DGtal::functions::isZeroSurface(const TObject & small_obj)
495 if (small_obj.size() != 2) return false;
496 auto connectedness = small_obj.computeConnectedness();
497 if( connectedness == DGtal::Connectedness::CONNECTED ) return false;
502 template < typename TObject >
504 DGtal::functions::isOneSurface(const TObject & small_obj)
506 auto connectedness = small_obj.computeConnectedness();
507 if( connectedness == DGtal::Connectedness::DISCONNECTED )
510 for (const auto & p : small_obj.pointSet()){
511 auto pN = small_obj.properNeighborhood(p);
512 if(!isZeroSurface(pN) ) return false;
518 template <typename TObject >
519 std::vector< TObject >
521 connectedComponents(const TObject & input_obj, bool verbose)
524 // Graph related alias
525 using Graph = TObject ;
526 using vertex_descriptor =
527 typename boost::graph_traits<Graph>::vertex_descriptor ; // Object::Vertex
528 using edge_descriptor =
529 typename boost::graph_traits<Graph>::edge_descriptor ; // Object::Edge
530 using vertices_size_type =
531 typename boost::graph_traits<Graph>::vertices_size_type ; // Object::Size
533 using StdColorMap = std::map< vertex_descriptor, boost::default_color_type > ;
534 StdColorMap colorMap;
535 boost::associative_property_map< StdColorMap > propColorMap( colorMap );
537 using StdComponentMap = std::map< vertex_descriptor, vertices_size_type > ;
538 StdComponentMap componentMap;
539 boost::associative_property_map< StdComponentMap > propComponentMap( componentMap );
541 if(verbose) trace.beginBlock( "Boost connected_components");
542 vertices_size_type nbComp =
543 boost::connected_components // boost graph connected components algorithm.
544 ( input_obj, // the graph
545 propComponentMap, // the mapping vertex -> label
546 boost::color_map( propColorMap ) // this map is used internally when computing connected components.
548 if(verbose) trace.info() << "num_components = " << nbComp << std::endl;
550 if(verbose) trace.beginBlock( "Filter graph and get components");
552 using ComponentGraph =
553 boost::filtered_graph<
555 std::function<bool(edge_descriptor)>,
556 std::function<bool(vertex_descriptor)>
560 std::vector<ComponentGraph> component_graphs;
562 for (size_t i = 0; i < nbComp; i++)
563 component_graphs.emplace_back(g,
564 [componentMap,i,&g](edge_descriptor e) {
565 return componentMap.at(boost::source(e,g) )==i
566 || componentMap.at(boost::target(e,g))==i;
568 [componentMap,i](vertex_descriptor v) {
569 return componentMap.at(v)==i;
571 if(verbose) trace.endBlock();
573 std::vector<TObject> obj_components;
574 for (auto && c : component_graphs){
575 // Create empty but valid object.
576 obj_components.emplace_back(
577 TObject( input_obj.topology(),input_obj.domain() ));
578 for (auto && vp = boost::vertices(c); vp.first != vp.second ; ++vp.first){
579 obj_components.back().pointSet().insertNew(*vp.first);
582 if(verbose) trace.endBlock();
584 return obj_components;
586 ///////////////////////////////////////////////////////////////////////////////
588 ///////////////////////////////////////////////////////////////////////////////