1 #ifndef vtol_extract_topology_hxx_
2 #define vtol_extract_topology_hxx_
3 
4 #include <iostream>
5 #include <iosfwd>
6 #include "vtol_extract_topology.h"
7 //:
8 // \file
9 
10 #ifdef _MSC_VER
11 #  include <vcl_msvc_warnings.h>
12 #endif
13 #include <cassert>
14 
15 #include <vgl/vgl_point_2d.h>
16 #include <vgl/vgl_vector_2d.h>
17 #include <vgl/algo/vgl_line_2d_regression.h>
18 
19 #include <vtol/vtol_vertex_2d.h>
20 #include <vtol/vtol_edge_2d.h>
21 #include <vtol/vtol_edge_2d_sptr.h>
22 #include <vtol/vtol_intensity_face.h>
23 
24 #include <vsol/vsol_curve_2d_sptr.h>
25 
26 #include <vdgl/vdgl_edgel.h>
27 #include <vdgl/vdgl_edgel_chain.h>
28 #include <vdgl/vdgl_interpolator_linear.h>
29 #include <vdgl/vdgl_digital_curve.h>
30 
31 #ifndef NDEBUG
32 #  define DBG( x ) x;
33 #else
34 #  define DBG( x ) /*debugging removed*/ do {} while (false)
35 #endif
36 
37 // =============================================================================
38 //                                                              EXTRACT TOPOLOGY
39 // =============================================================================
40 
41 typedef vtol_extract_topology_vertex_node vertex_node;
42 
43 // ---------------------------------------------------------------------------
44 //                                              static variables and constants
45 constexpr unsigned vtol_extract_topology_vertex_node::null_index;
46 constexpr unsigned vtol_extract_topology_vertex_node::done_index;
47 
48 
49 // ---------------------------------------------------------------------------
50 //                                                                 constructor
51 
52 
53 template< typename T >
54 vtol_extract_topology<T>::
vtol_extract_topology(label_image_type const & in_image,vtol_extract_topology_params const & in_params)55 vtol_extract_topology( label_image_type const& in_image,
56                        vtol_extract_topology_params const& in_params )
57   : label_img_( in_image ),
58     params_( in_params )
59 {
60   compute_label_range();
61   construct_topology();
62 }
63 
64 
65 // ---------------------------------------------------------------------------
66 //                                                                    vertices
67 
68 template< typename T >
69 std::vector< vtol_vertex_2d_sptr >
70 vtol_extract_topology<T>::
vertices() const71 vertices() const
72 {
73   typedef std::vector< vertex_node >::const_iterator vertex_iterator_t;
74 
75   std::vector< vtol_vertex_2d_sptr > verts;
76 
77   for ( vertex_iterator_t i = node_list_.begin();
78         i != node_list_.end(); ++i ) {
79     verts.push_back( i->vertex );
80   }
81 
82   return verts;
83 }
84 
85 
86 // ---------------------------------------------------------------------------
87 //                                                                       faces
88 
89 template< typename T >
90 std::vector< vtol_intensity_face_sptr >
91 vtol_extract_topology<T>::
faces(data_image_type const & data_img) const92 faces( data_image_type const& data_img ) const
93 {
94   region_collection region_list;
95   collect_regions( region_list );
96 
97   // Generate faces for each label. A given label may generate more
98   // than one face based on containment, etc.
99   //
100   std::vector< vtol_intensity_face_sptr > faces;
101   for (auto & i : region_list) {
102     if ( ! i.empty() ) {
103       compute_faces( i, faces, &data_img );
104     }
105   }
106 
107   return faces;
108 }
109 
110 
111 template< typename T >
112 std::vector< vtol_intensity_face_sptr >
113 vtol_extract_topology<T>::
faces() const114 faces( ) const
115 {
116   region_collection region_list;
117   collect_regions( region_list );
118 
119   // Generate faces for each label. A given label may generate more
120   // than one face based on containment, etc.
121   //
122   std::vector< vtol_intensity_face_sptr > faces;
123   for (auto & i : region_list) {
124     if ( ! i.empty() ) {
125       compute_faces( i, faces, nullptr );
126     }
127   }
128 
129   return faces;
130 }
131 
132 
133 // ---------------------------------------------------------------------------
134 //                                                                        init
135 
136 template< typename T >
137 void
138 vtol_extract_topology<T>::
compute_label_range()139 compute_label_range()
140 {
141   assert( label_img_.ni() >= 1 && label_img_.nj() >= 1 );
142 
143   // Determine the label ranges
144   //
145   min_label_ = label_img_(0,0);
146   max_label_ = min_label_;
147   for ( unsigned j = 0; j < label_img_.nj(); ++j ) {
148     for ( unsigned i = 0; i < label_img_.ni(); ++i ) {
149       if ( min_label_ > label_img_(i,j) )
150         min_label_ = label_img_(i,j);
151       if ( max_label_ < label_img_(i,j) )
152         max_label_ = label_img_(i,j);
153     }
154   }
155 }
156 
157 // ---------------------------------------------------------------------------
158 //                                                                       label
159 
160 template< typename LABEL_TYPE >
161 typename vtol_extract_topology< LABEL_TYPE >::LabelPoint
162 vtol_extract_topology< LABEL_TYPE >::
label(unsigned i,unsigned j) const163 label( unsigned i, unsigned j ) const
164 {
165   if ( i < label_img_.ni() && j < label_img_.nj() ) {
166     return LabelPoint(label_img_( i, j ), true);
167   }
168   else {
169     return LabelPoint(0, false);
170   }
171 }
172 
173 
174 // ---------------------------------------------------------------------------
175 //                                                          is junction vertex
176 
177 template< typename T >
178 bool
179 vtol_extract_topology<T>::
is_junction_vertex(unsigned i,unsigned j) const180 is_junction_vertex( unsigned i, unsigned j ) const
181 {
182   // A junction must have at least three incident boundary edges
183 
184   unsigned edge_count = 0;
185   for ( unsigned dir = 0; dir < 4; ++dir ) {
186     if ( is_edge( i, j, dir ) ) {
187       ++edge_count;
188     }
189   }
190 
191   return edge_count >= 3;
192 }
193 
194 
195 // ---------------------------------------------------------------------------
196 //                                                          is boundary vertex
197 
198 template< typename LABEL_TYPE >
199 bool
200 vtol_extract_topology< LABEL_TYPE >::
is_boundary_vertex(unsigned i,unsigned j) const201 is_boundary_vertex( unsigned i, unsigned j ) const
202 {
203   // A non-boundary (interior) point is surrounded by pixels of the
204   // same value.
205 
206   LabelPoint pixel1( label( i  , j   ) );
207   LabelPoint pixel2( label( i  , j-1 ) );
208   LabelPoint pixel3( label( i-1, j   ) );
209   LabelPoint pixel4( label( i-1, j-1 ) );
210 
211   return pixel1 != pixel2 || pixel2 != pixel3 || pixel3 != pixel4;
212 }
213 
214 
215 // ---------------------------------------------------------------------------
216 //                                                                     is edge
217 
218 template< typename LABEL_TYPE >
219 bool
220 vtol_extract_topology< LABEL_TYPE >::
is_edge(unsigned i,unsigned j,unsigned dir) const221 is_edge( unsigned i, unsigned j, unsigned dir ) const
222 {
223   LabelPoint left, right;
224 
225   edge_labels( i, j, dir, left, right );
226 
227   return left != right;
228 }
229 
230 
231 // ---------------------------------------------------------------------------
232 //                                                                 edge labels
233 
234 template< typename LABEL_TYPE >
235 void
236 vtol_extract_topology< LABEL_TYPE >::
edge_labels(unsigned i,unsigned j,unsigned dir,LabelPoint & left,LabelPoint & right) const237 edge_labels( unsigned i, unsigned j, unsigned dir,
238              LabelPoint& left, LabelPoint& right ) const
239 {
240   assert( dir <= 3 );
241 
242   // These are the offsets to get the "left" pixel position for each
243   // direction given the vertex location (i,j).  The vertices occur at
244   // the corners of the pixels, so that vertex (0,0) is at the
245   // top-left corner of pixel (0,0).
246   //
247   // The offsets for the "right" pixel in direction d is the offset
248   // for the left pixel in direction (d+1).
249   //
250   static const int offsets[4][2] =
251     { { 0, -1 }, { 0, 0 }, { -1, 0 }, { -1, -1 } };
252 
253   unsigned dir2 = (dir+1) % 4;
254 
255   left  = label( i+offsets[dir ][0], j+offsets[dir ][1] );
256   right = label( i+offsets[dir2][0], j+offsets[dir2][1] );
257 }
258 
259 
260 // ---------------------------------------------------------------------------
261 //                                                                vertex index
262 
263 template< typename T >
264 unsigned
265 vtol_extract_topology<T>::
vertex_index(unsigned i,unsigned j) const266 vertex_index( unsigned i, unsigned j ) const
267 {
268   assert( i < index_img_.ni() &&
269           j < index_img_.nj() );
270 
271   return index_img_( i, j );
272 }
273 
274 
275 // ---------------------------------------------------------------------------
276 //                                                            set vertex index
277 
278 template< typename T >
279 void
280 vtol_extract_topology<T>::
set_vertex_index(unsigned i,unsigned j,unsigned index)281 set_vertex_index( unsigned i, unsigned j, unsigned index )
282 {
283   assert( i < index_img_.ni() &&
284           j < index_img_.nj() );
285 
286   index_img_( i, j ) = index;
287 }
288 
289 
290 // ---------------------------------------------------------------------------
291 //                                                                        node
292 
293 template< typename T >
294 vtol_extract_topology_vertex_node&
295 vtol_extract_topology<T>::
node(unsigned index)296 node( unsigned index )
297 {
298   assert( index < node_list_.size() );
299 
300   return node_list_[index];
301 }
302 
303 
304 template< typename T >
305 vtol_extract_topology_vertex_node const&
306 vtol_extract_topology<T>::
node(unsigned index) const307 node( unsigned index ) const
308 {
309   assert( index < node_list_.size() );
310 
311   return node_list_[index];
312 }
313 
314 
315 // ---------------------------------------------------------------------------
316 //                                                                        move
317 
318 template< typename T >
319 void
320 vtol_extract_topology<T>::
move(unsigned dir,unsigned & i,unsigned & j)321 move( unsigned dir, unsigned& i, unsigned& j )
322 {
323   assert( dir < 4 );
324 
325   switch ( dir )
326   {
327    case 0: // right
328     ++i;
329     break;
330    case 1: // down
331     ++j;
332     break;
333    case 2: // left
334     --i;
335     break;
336    case 3: // up
337     --j;
338     break;
339    default: break; // never reached
340   }
341 }
342 
343 
344 // ---------------------------------------------------------------------------
345 //                                                                    set mark
346 
347 template< typename T >
348 void
349 vtol_extract_topology<T>::
set_mark(unsigned & marker,unsigned dir) const350 set_mark( unsigned& marker, unsigned dir ) const
351 {
352   marker |= ( 1 << dir );
353 }
354 
355 
356 // ---------------------------------------------------------------------------
357 //                                                                   is marked
358 
359 template< typename T >
360 bool
361 vtol_extract_topology<T>::
is_marked(unsigned marker,unsigned dir) const362 is_marked( unsigned marker, unsigned dir ) const
363 {
364   return ( marker & ( 1 << dir ) ) != 0;
365 }
366 
367 
368 // ---------------------------------------------------------------------------
369 //                                                            trace edge chain
370 
371 template< typename T >
372 void
373 vtol_extract_topology<T>::
trace_edge_chain(unsigned i,unsigned j,unsigned dir)374 trace_edge_chain( unsigned i, unsigned j, unsigned dir )
375 {
376   // Quick exit if there is nothing to trace in this direction.
377   //
378   if ( ! is_edge( i, j, dir ) )
379     return;
380 
381 
382   // When constructing the spatial and topological objects from the
383   // "pixel corner" vertices, we add the (-.5,-.5) offset required to
384   // bring everything into pixel coordinates.
385 
386   unsigned start_index = vertex_index( i, j );
387   unsigned start_dir = dir;
388 
389   // The chain of "pixel corners" from one vertex to the other.
390   //
391   vdgl_edgel_chain_sptr chain = new vdgl_edgel_chain();
392 
393   // Start vertex
394   chain->add_edgel( vdgl_edgel( i-0.5, j-0.5, -1, dir*90 ) );
395   move( dir, i, j );
396 
397   // If we have a complete cycle (i.e. start==end), we have to step
398   // back one because vtol doesn't like edges with the same start and
399   // end. When we step back, we need to keep track of the direction
400   // from which we came to that point. We'll only ever need to step
401   // back once, so it is sufficient to keep the current direction
402   // (direction of the last step) in dir) and the previous direction
403   // in prev_dir.
404   unsigned int prev_dir = (unsigned int)(-1);
405 
406   while ( vertex_index( i, j ) == vertex_node::null_index )
407   {
408     set_vertex_index( i, j, vertex_node::done_index );
409 
410     chain->add_edgel( vdgl_edgel( i-0.5, j-0.5, -1, dir*90 ) );
411 
412     // find and move in the outgoing direction. There should be
413     // exactly one, since this is not a vertex.
414     //
415     DBG( unsigned count = 0 );
416     prev_dir = dir;
417     dir = (dir+3) % 4; // same as dir = dir - 1
418     while ( ! is_edge( i, j, dir ) ) {
419       dir = (dir+1) % 4;
420       DBG( ++count );
421       DBG( assert( count < 3 ) );
422     }
423 
424     move( dir, i, j );
425 
426     // The same non-junction vertex should not appear on multiple
427     // traces. (The "reverse trace" is will be done below by just
428     // reversing the start and end.)
429     //
430     assert( vertex_index( i, j ) != vertex_node::done_index );
431   }
432 
433   unsigned end_index = vertex_index( i, j );
434 
435   if ( end_index == start_index ) {
436     // Construct a new vertex at the just-before-last point to avoid
437     // having a chain with identical end-points. Move backwards one
438     // unit and create a vertex.
439     move( (dir+2)%4, i, j );
440     set_vertex_index( i, j, node_list_.size() );
441     node_list_.emplace_back(i, j);
442     end_index = vertex_index( i, j );
443     dir = prev_dir; // the direction we came from to the new end point.
444     // The new end point is already in the edgel chain; no need to add again.
445   }
446   else {
447     // Add the end vertex
448     chain->add_edgel( vdgl_edgel( i-0.5, j-0.5 ) );
449   }
450 
451   chain = smooth_chain( chain, params_.num_for_smooth );
452 
453   vertex_node& start_node = node( start_index );
454   vertex_node& end_node   = node( end_index );
455 
456   vdgl_interpolator_sptr interp = new vdgl_interpolator_linear( chain );
457   vsol_curve_2d_sptr curve = new vdgl_digital_curve( interp );
458   vtol_edge_2d_sptr edge = new vtol_edge_2d( start_node.vertex,
459                                              end_node.vertex,
460                                              curve );
461 
462   edgel_chain_sptr chain2 = new edgel_chain;
463   chain2->chain = chain;
464   chain2->edge  = edge;
465 
466   // Turn around, because the that is the direction we would begin in
467   // from the end node for a reverse trace.
468   //
469   dir = (dir+2) % 4;
470 
471   start_node.link[ start_dir ] = end_index;
472   start_node.back_dir[ start_dir ] = dir;
473   start_node.edgel_chain[ start_dir ] = chain2;
474 
475   end_node.link[ dir ] = start_index;
476   end_node.back_dir[ dir ] = start_dir;
477   end_node.edgel_chain[ dir ] = chain2;
478 }
479 
480 
481 // ---------------------------------------------------------------------------
482 //                                                          construct topology
483 
484 template< typename T >
485 void
486 vtol_extract_topology<T>::
construct_topology()487 construct_topology( )
488 {
489   // Construct the list of vertex nodes from the junctions and
490   // initialize the vertex index array.
491   //
492   index_img_.set_size( label_img_.ni()+1, label_img_.nj()+1 );
493 
494   node_list_.clear();
495   index_img_.fill( vertex_node::null_index );
496 
497   for ( unsigned j = 0; j <= label_img_.nj(); ++j ) {
498     for ( unsigned i = 0; i <= label_img_.ni(); ++i ) {
499       if ( is_junction_vertex( i, j ) ) {
500         set_vertex_index( i, j, node_list_.size() );
501         node_list_.emplace_back(i, j);
502       }
503     }
504   }
505 
506   // Construct the edge graph by following each edge from
507   // each vertex.
508   //
509   for ( unsigned index = 0; index < node_list_.size(); ++index ) {
510     for ( unsigned dir = 0; dir < 4; ++dir ) {
511       if ( node(index).link[dir] == vertex_node::null_index ) {
512         trace_edge_chain( node(index).i,
513                           node(index).j,
514                           dir );
515       }
516     }
517   }
518 
519   // Look for untouched boundary vertices, and make them vertices
520   // too. Tracing from one of these must lead back to itself. The
521   // topology classes don't like edges that are themselves
522   // cycles. They expect that each edge has two distinct
523   // vertices. Instead of creating two vertices at the same point, we
524   // create two vertices adjacent to each other. Thus, each of these
525   // faces (created later) will be bounded by two edges, one
526   // potentially long edge, and one edge with length one pixel.
527   //
528   for ( unsigned j = 0; j <= label_img_.nj(); ++j ) {
529     for ( unsigned i = 0; i <= label_img_.ni(); ++i ) {
530       if ( vertex_index( i, j ) == vertex_node::null_index &&
531            is_boundary_vertex( i, j ) )
532       {
533         // Find the two outgoing directions
534         //
535         unsigned dir = 0;
536         while ( dir < 4 && ! is_edge( i, j, dir ) ) {
537           ++dir;
538         }
539         assert( dir < 4 );
540         unsigned dir2 = dir+1;
541         while ( dir2 < 4 && ! is_edge( i, j, dir2 ) ) {
542           ++dir2;
543         }
544         assert( dir2 < 4 );
545 
546         // Create a vertex at this point
547         //
548         set_vertex_index( i, j, node_list_.size() );
549         node_list_.emplace_back(i, j);
550 
551         // Create a vertex at a neighbour
552         //
553         unsigned i2 = i, j2 = j;
554         move( dir, i2, j2 );
555         assert( vertex_index( i2, j2 ) == vertex_node::null_index &&
556                 is_boundary_vertex( i2, j2 ) );
557         set_vertex_index( i2, j2, node_list_.size() );
558         node_list_.emplace_back(i2, j2);
559 
560         // Trace from here, going both ways
561         //
562         trace_edge_chain( i, j, dir );
563         trace_edge_chain( i, j, dir2 );
564       }
565     }
566   }
567 
568 #ifndef NDEBUG
569   // Verify the integrity of the vertex graph
570   bool good = true;
571   for ( unsigned index = 0; index < node_list_.size(); ++index ) {
572     for ( unsigned dir = 0; dir < 4; ++dir ) {
573       if ( node(index).link[dir] != vertex_node::null_index ) {
574         unsigned nbr = node(index).link[dir];
575         unsigned back_dir = node(index).back_dir[dir];
576         if ( node(nbr).link[back_dir] != index ) {
577           std::cerr << "Bad back link on vertex " << index << " ("<<node(index).i
578                    << ',' << node(index).j << " in dir " << dir << '\n'
579                    << "  link     " << dir << " = " << node(index).link[dir] << ";\n"
580                    << "  back_dir " << dir << " = " << node(index).back_dir[dir] << '\n';
581         }
582       }
583     }
584     assert( good );
585   }
586 #endif
587 }
588 
589 
590 // ---------------------------------------------------------------------------
591 //                                                         trace face boundary
592 
593 template< typename LABEL_TYPE >
594 bool
595 vtol_extract_topology< LABEL_TYPE >::
trace_face_boundary(std::vector<unsigned> & markers,unsigned index,unsigned dir,region_type & chain_list,LabelPoint & region_label) const596 trace_face_boundary( std::vector<unsigned>& markers,
597                      unsigned index,
598                      unsigned dir,
599                      region_type& chain_list,
600                      LabelPoint& region_label ) const
601 {
602   unsigned start_index = index;
603   LabelPoint start_left;
604   edge_labels( node(index).i, node(index).j, dir,
605                start_left, region_label );
606 
607 #ifdef DEBUG
608   std::cout << "start left, region label: " << (int) start_left << ' '
609            << (int)region_label << " ; i,j: " << node(index).i << ' '
610            << node(index).j << " ; index,dir " << index << ' '
611            << dir << " ; label(i,j) = "
612            << (int)label_img_(node(index).i, node(index).j) << std::endl;
613   if ( region_label + 1 == min_label_ ) {
614     std::cout << "exiting" << std::endl;
615     return false;
616   }
617 #endif
618   if (!region_label.valid) {
619     return false;
620   }
621 
622   // Find an interior pixel of this face based on the first edge we
623   // encounter. The vertex (i,j) corresponds to pixel-coordinate-space
624   // (i-0.5,j-0.5). Use this and the direction to get a point on the
625   // right of the first edge. The delta_* store the appropriate
626   // offsets for each direction.
627   //
628   static const int delta_i[4] = {  0, -1, -1,  0 };
629   static const int delta_j[4] = {  0,  0, -1, -1 };
630   chain_list.i = node(index).i + delta_i[dir];
631   chain_list.j = node(index).j + delta_j[dir];
632 
633 #ifdef DEBUG
634   std::cout << "index " << index << " dir " << dir << "  node " << node(index).i
635            << " delta " << delta_i[dir] << std::endl;
636 #endif
637   assert( chain_list.i < label_img_.ni() );
638   assert( chain_list.j < label_img_.nj() );
639 
640   do {
641     // Mark the current direction of the current node as travelled,
642     // and go to the next node and find the outgoing direction there.
643 
644     assert( ! is_marked( markers[index], dir ) );
645     set_mark( markers[index], dir );
646     chain_list.push_back( node(index).edgel_chain[dir] );
647 
648     unsigned back_dir = node(index).back_dir[ dir ];
649     index = node(index).link[ dir ];
650     assert( index != vertex_node::null_index );
651 
652     // Look from right to left, so that we are more conservative at
653     // corner touches. That is, we will take
654     //
655     //     +----+
656     //     |    |
657     //     +----+----+
658     //          |    |
659     //          +----+
660     //
661     // as two regions instead of one figure-8 region.
662     //
663     // We keep the object on the right, which actually corresponds to
664     // the standard "keep the object on the left" in a right-handed
665     // coordinate system.
666     //
667     unsigned i = node(index).i;
668     unsigned j = node(index).j;
669     LabelPoint left, right;
670     dir = back_dir;
671     DBG( unsigned old_dir = dir % 4 );
672     do {
673       dir = (dir+3) % 4; // same as dir = dir - 1
674 
675       // Make sure we haven't done a full cycle.
676       DBG( assert( dir != old_dir ) );
677 
678       edge_labels( i, j, dir, left, right );
679     } while ( left == right || right != region_label );
680 
681   } while ( index != start_index );
682 
683   return true;
684 }
685 
686 
687 // ---------------------------------------------------------------------------
688 //                                                             collect regions
689 
690 template< typename LABEL_TYPE >
691 void
692 vtol_extract_topology< LABEL_TYPE >::
collect_regions(region_collection & region_list) const693 collect_regions( region_collection& region_list ) const
694 {
695   // Use put marks about which nodes (vertices) and directions have
696   // been processed.
697   //
698   std::vector< unsigned > markers( node_list_.size(), 0 );
699 
700   region_list.resize( max_label_ - min_label_ + 1 );
701 
702   // Process each vertex, generating the boundary chains
703   //
704   for ( unsigned i = 0; i < node_list_.size(); ++i ) {
705     for ( unsigned dir = 0; dir < 4; ++dir ) {
706       if ( ! is_marked( markers[i], dir ) &&
707            node(i).link[dir] != vertex_node::null_index ) {
708         region_type_sptr chain = new region_type;
709         LabelPoint label;
710         if ( trace_face_boundary( markers, i, dir, *chain, label ) ) {
711           assert( (label.valid) && (label.label >= min_label_) );
712           region_list[ label.label - min_label_ ].push_back( chain );
713         }
714       }
715     }
716   }
717 
718 #ifndef NDEBUG
719   // After we extract all the chains, we must have gone through every
720   // vertex in every available direction except for the
721   // counter-clockwise chain at the boundary (which is the border of
722   // the "outside" region).
723   //
724   for ( unsigned i = 0; i < node_list_.size(); ++i ) {
725     for ( unsigned dir = 0; dir < 4; ++dir ) {
726       assert( node(i).link[dir] == vertex_node::null_index ||
727               ( dir==0 && node(i).j==label_img_.nj() ) ||
728               ( dir==1 && node(i).i==0 ) ||
729               ( dir==2 && node(i).j==0 ) ||
730               ( dir==3 && node(i).i==label_img_.ni() ) ||
731               is_marked( markers[i], dir ) );
732     }
733   }
734 #endif
735 }
736 
737 
738 // ---------------------------------------------------------------------------
739 //                                                               compute faces
740 
741 template< typename T >
742 void
743 vtol_extract_topology<T>::
compute_faces(std::vector<region_type_sptr> const & chains,std::vector<vtol_intensity_face_sptr> & faces,data_image_type const * data_img) const744 compute_faces( std::vector< region_type_sptr > const& chains,
745                std::vector< vtol_intensity_face_sptr >& faces,
746                data_image_type const* data_img ) const
747 {
748   assert( chains.size() > 0 );
749 
750   // Determine the containment tree by repeatedly adding the chains
751   // into a tree. The adding process makes sure that the chain falls
752   // into the appropriate place in the containment hierarchy.
753 
754   chain_tree_node universe( nullptr );
755   for (const auto & chain : chains) {
756     universe.add( chain );
757   }
758 
759   // If we have a data image, use it to add digital region information
760   // to the face
761   //
762   if ( data_img ) {
763     finder_type* finder = new finder_type( label_img_, vil_region_finder_4_conn );
764     add_faces( faces, finder, data_img, &universe );
765     delete finder;
766   }
767   else {
768     add_faces( faces, nullptr, nullptr, &universe );
769   }
770 }
771 
772 // ---------------------------------------------------------------------------
773 //                                                                   add faces
774 
775 template <typename T>
776 void
777 vtol_extract_topology<T>::
add_faces(std::vector<vtol_intensity_face_sptr> & faces,typename vtol_extract_topology<T>::finder_type * find,data_image_type const * img,typename vtol_extract_topology<T>::chain_tree_node * node,bool even_level) const778 add_faces( std::vector<vtol_intensity_face_sptr>& faces,
779            typename vtol_extract_topology<T>::finder_type* find,
780            data_image_type const* img,
781            typename vtol_extract_topology<T>::chain_tree_node* node,
782            bool even_level ) const
783 {
784   if ( even_level ) {
785     faces.push_back( node->make_face( find, img ) );
786   }
787   for ( unsigned i = 0; i < node->children.size(); ++i ) {
788     add_faces( faces, find, img, node->children[i], !even_level );
789   }
790 }
791 
792 // ---------------------------------------------------------------------------
793 //                                                                    contains
794 
795 
796 template <typename T>
797 bool
798 vtol_extract_topology<T>::
contains(region_type_sptr a,region_type_sptr b)799 contains( region_type_sptr a, region_type_sptr b )
800 {
801   assert( b );
802   if ( !a ) {
803     return true;
804   }
805   else {
806     // Odd number of crossings => inside.
807     //
808     unsigned num_crossings = 0;
809     for ( unsigned i = 0; i < a->size(); ++i ) {
810       num_crossings += num_crosses_x_pos_ray( b->i, b->j, *(*a)[i] );
811     }
812     return ( num_crossings % 2 ) == 1;
813   }
814 }
815 
816 
817 // ---------------------------------------------------------------------------
818 //                                                       num crosses x pos ray
819 
820 template <typename T>
821 unsigned
822 vtol_extract_topology<T>::
num_crosses_x_pos_ray(double x,double y,vdgl_edgel_chain const & chain)823 num_crosses_x_pos_ray( double x, double y, vdgl_edgel_chain const& chain )
824 {
825   if ( chain.size() < 2 )
826     return 0;
827 
828   // The edges are vertical or horizontal line segments. Leverage that
829   // in doing the intersection check. Assume that the ray will *not*
830   // cross on a vertex.
831   //
832   unsigned count = 0;
833   for ( unsigned int i = 0; i+1 < chain.size(); ++i ) {
834     vdgl_edgel const* e0 = &chain[i];
835     vdgl_edgel const* e1 = &chain[i+1];
836     assert( e0->y() != y && e1->y() != y );
837     if ( ( e0->y() < y && y < e1->y() ) ||
838         ( e1->y() < y && y < e0->y() ) ) {
839       assert( e0->x() == e1->x() );
840       assert( e0->x() != x );
841       if ( e0->x() > x ) {
842         ++count;
843       }
844     }
845   }
846 
847   return count;
848 }
849 
850 
851 // ---------------------------------------------------------------------------
852 //                                                                smooth chain
853 
854 template <typename T>
855 vdgl_edgel_chain_sptr
856 vtol_extract_topology<T>::
smooth_chain(vdgl_edgel_chain_sptr chain,unsigned int num_pts) const857 smooth_chain( vdgl_edgel_chain_sptr chain,
858               unsigned int num_pts ) const
859 {
860   // Can't smooth over more points than we have
861   if ( num_pts > chain->size() ) {
862     num_pts = chain->size();
863   }
864 
865   // Need at least two points to fit a line
866   if ( num_pts < 2)
867     return chain;
868 
869   vdgl_edgel_chain_sptr new_chain = new vdgl_edgel_chain;
870 
871   vgl_line_2d_regression<double> reg;
872 
873   // These store the indices of the edgel points used to estimate the
874   // line. The points in [fit_start,fit_end) are used.
875   //
876   unsigned fit_start;
877   unsigned fit_end;
878 
879   // This is the index of the edgel point we are smoothing.
880   //
881   unsigned curr_ind = 0;
882 
883   vgl_point_2d<double> pt1, pt2;
884   vgl_vector_2d<double> dir;
885   double slope;
886 
887   // Add the first few points to get the first line. We'll use this
888   // line as the smoothing estimate for the first few edgel pixels. We
889   // don't add the first point, because we will constrain the line to
890   // pass through it. This will make sure that the vertices don't move
891   // because of the smoothing.
892   //
893   for ( fit_end = 1; fit_end < num_pts; ++fit_end ) {
894     reg.increment_partial_sums( chain->edgel(fit_end).x(),
895                                 chain->edgel(fit_end).y() );
896   }
897   assert( reg.get_n_pts() + 1 == num_pts );
898   if ( !reg.fit_constrained( chain->edgel(0).x(), chain->edgel(0).y() ) ) {
899     std::cerr << "line fit failed at start\n";
900   }
901 
902   // Project the first half of the points used in estimating the line
903   // onto the line to get the smoothed positions.
904   //
905   reg.get_line().get_two_points( pt1, pt2 );
906   dir = reg.get_line().direction();
907   slope = reg.get_line().slope_degrees();
908   for ( ; curr_ind < (fit_end+1)/2; ++curr_ind ) {
909     pt2.set( chain->edgel(curr_ind).x(), chain->edgel(curr_ind).y() );
910     pt2 = pt1 + dot_product( pt2-pt1, dir ) * dir;
911     new_chain->add_edgel( vdgl_edgel( pt2.x(), pt2.y(), -1, slope ) );
912   }
913 
914   // We have all the points from [1,fit_end_] in the regression object.
915   //
916   fit_start = 1;
917 
918   while ( fit_end + 1 < chain->size() ) {
919     // Add one more point to get num_pts points
920     //
921     reg.increment_partial_sums( chain->edgel(fit_end).x(),
922                                 chain->edgel(fit_end).y() );
923     ++fit_end;
924 
925     assert( reg.get_n_pts() == num_pts );
926 
927     // Estimate a new line
928     //
929     if ( !reg.fit() ) {
930       std::cerr << "line fit failed at " << fit_start << '-' << fit_end << '\n';
931     }
932 
933     // Project the current point onto the line to get the smoothed position
934     //
935     reg.get_line().get_two_points( pt1, pt2 );
936     dir = reg.get_line().direction();
937     slope = reg.get_line().slope_degrees();
938     pt2.set( chain->edgel(curr_ind).x(), chain->edgel(curr_ind).y() );
939     pt2 = pt1 + dot_product( pt2-pt1, dir ) * dir;
940     new_chain->add_edgel( vdgl_edgel( pt2.x(), pt2.y(), -1, slope ) );
941     ++curr_ind;
942 
943     // Remove the start point in preparation for the next line segment
944     //
945     reg.decrement_partial_sums( chain->edgel(fit_start).x(),
946                                 chain->edgel(fit_start).y() );
947     ++fit_start;
948   }
949 
950   assert( reg.get_n_pts() + 1 == num_pts );
951 
952   // The special case when we are using all the points to fit a line,
953   // the end point is already in the regression object when it
954   // normally wouldn't be. We have to replace it with the starting
955   // point (which is not in the regression object) so we can do a
956   // constrained fit.
957   //
958   if ( num_pts == chain->size() ) {
959     assert( fit_end == chain->size() );
960     assert( fit_start == 1 );
961     --fit_end;
962     reg.decrement_partial_sums( chain->edgel(fit_end).x(),
963                                 chain->edgel(fit_end).y() );
964     reg.increment_partial_sums( chain->edgel(0).x(),
965                                 chain->edgel(0).y() );
966   }
967 
968   // We have num_pts-1 points in the regression object. We do a
969   // constrained fit to make sure we interpolate the end vertex, and
970   // use this line for the final few edgel points.
971   //
972   if ( !reg.fit_constrained( chain->edgel(fit_end).x(),
973                             chain->edgel(fit_end).y() ) ) {
974     std::cerr << "line fit failed at end\n";
975   }
976 
977   dir = reg.get_line().direction();
978   reg.get_line().get_two_points( pt1, pt2 );
979   slope = reg.get_line().slope_degrees();
980   for ( ; curr_ind <= fit_end; ++curr_ind ) {
981     pt2.set( chain->edgel(curr_ind).x(), chain->edgel(curr_ind).y() );
982     pt2 = pt1 + dot_product( pt2-pt1, dir )*dir;
983     new_chain->add_edgel( vdgl_edgel( pt2.x(), pt2.y(), -1, slope ) );
984   }
985 
986   return new_chain;
987 }
988 
989 #endif // vtol_extract_topology_hxx_
990