DGtal  1.2.0
Helpers for digital surfaces
Author(s) of this documentation:
Bertrand Kerautret and Jacques-Olivier Lachaud.

Part of the Topology package.

This part of the manual describes how to use the helper class Surfaces to build digital surfaces, closed or open, or contours within digital surfaces. A lot of the ideas, concepts, algorithms, documentation and code is a backport from ImaGene.

All the code presented here require:

#include "DGtal/topology/helpers/Surfaces.h"
Note
This class is useful if you wish only to obtain the set of surfels of a given digital surface or if you wish to obtain 2D contours (in 2D or 3D). If you require more advanced operations on surfaces (for instance using afterwards the topology defined on the surface), it is better to wrap a DigitalSurface object around your surface of interest, see Digital surfaces.

The 2D case: the boundary is a sequence of cells

The surfaces classes offers the possibility to extract an open or closed contour as a sequence of surfels obtained from a DigitalSet and a starting surfel. The full code of this example is available in file ctopo-2.cpp .

The first step to extract the surfel boudary of a 2D digital set is to obtain an initial boundary surfel:

aCell = Surfaces<Z2i::KSpace>::findABel(ks, set2d);
static SCell findABel(const KSpace &K, const PointPredicate &pp, unsigned int nbtries=1000)

The first surfel can also be displayed in red with Board2D:

Board2D board;
board << image.domain() << set2d; // display domain and set
board << CustomStyle( aCell.className(), new CustomColors( Board2D::Color( 255, 0, 0 ),
Board2D::Color( 192, 0, 0 ) ));
board << aCell;


Start surfel before a tracking (in red).
Then you can extract the sequence of consecutive surfels:
@code 
 std::vector<Z2i::SCell> vectBdrySCell;
 SurfelAdjacency<2> SAdj( true );
 Surfaces<Z2i::KSpace>::track2DBoundary( vectBdrySCell,
                                         ks, SAdj, set2d, aCell );
@endcode
and display it:
@code
 GradientColorMap<int> cmap_grad( 0, vectBdrySCell.size() );
 cmap_grad.addColor( Board2D::Color( 50, 50, 255 ) );
 cmap_grad.addColor( Board2D::Color( 255, 0, 0 ) );
 cmap_grad.addColor( Board2D::Color( 255, 255, 10 ) );

 unsigned int d=0;
 std::vector<Z2i::SCell>::iterator it;
 for ( it=vectBdrySCell.begin() ; it != vectBdrySCell.end(); it++ ){
      board<< CustomStyle((*it).className() ,
                          new CustomColors( Board2D::Color::Black,
                                            cmap_grad( d )))<< *it;
      d++;
}


You will obtain the following ordered sequence of surfels:

Tracking of a closed 2D contour.

The resulting sequence of surfels does not necessary present an open contour (try for instance image "samples/circleR10modif.pgm"):

Tracking of an open 2D contour.

Tracking a 3D boundary to build a surface.

With only few modifications we can apply the same extraction on 3D surfel set. The file ctopo-2-3d.cpp shows the same previous example adapted in 3D.

with the same code we can get a surfel boundary:

KSpace::SCell SCell
Definition: StdDefs.h:149

From this SCell all the surfel connected sets can be extracted:

// Extracting all boundary surfels which are connected to the initial boundary Cell.
ks,SAdj, set3d, aCell );
static void trackBoundary(SCellSet &surface, const KSpace &K, const SurfelAdjacency< KSpace::dimension > &surfel_adj, const PointPredicate &pp, const SCell &start_surfel)

To see both initial surfel and the surfel set, we can use the transparent mode:

viewer << SetMode3D((*(vectBdrySCellALL.begin())).className(), "Transparent");

To avoid surfel superposition we need to increase with a small shift the surfel size, for this purpose you can add the following key:

viewer << Viewer3D<>::shiftSurfelVisu;

or use the special mode "Highlighted" which increase automaticly the surfel size.

You can obtain for instance the following visualisation:

Tracking surfaces in 3D (start surfel in green).

Since in 3D there are several choice for the direction used to exctract surfel boundary, we can specify the constant direction need to drive the surfel extraction:

// Extract the boundary contour associated to the initial surfel in its first direction
ks, *(ks.sDirs( aCell )),SAdj, set3d, aCell );
static void track2DBoundary(std::vector< SCell > &aSCellContour2D, const KSpace &K, const SurfelAdjacency< KSpace::dimension > &surfel_adj, const PointPredicate &pp, const SCell &start_surfel)

After extracting the two surfels cut you may obtain the following visualisation:

Tracking surfaces and slices in 3D (start surfel in green).

Extracting surface of connected components

The class Surfaces provides other useful function to extract connected boundary surfels from a digital set and given a surfel adjacency definition. The example 3dKSSurfaceExtraction.cpp shows an example of such an extraction.

From a domain we construct a DigitalSet inserting points under given conditions (see. 3dKSSurfaceExtraction.cpp for more details)

#include "DGtal/helpers/Surfaces.h"
#include "DGtal/topology/KhalimskySpaceND.h"
...
Domain domain( p1, p2);
DigitalSet diamond_set( domain );
...
diamond_set.insertNew( *it );
....
DigitalSetSelector< Domain, BIG_DS+HIGH_BEL_DS >::Type DigitalSet
Definition: StdDefs.h:100
Domain domain

With this domain bounding points (p1, p2), a KhalimskySpace is constructed and a SurfelAdjacency definition is introduced.

K.init(p1, p2, true);
SurfelAdjacency<3> SAdj( true );
KSpace K

Then we can extract all connected surfels from the digitalSet surface :

SetPredicate<DigitalSet> shape_set_predicate( diamond_set );
Surfaces<KSpace>::extractAllConnectedSCell(vectConnectedSCell,K, SAdj, shape_set_predicate);
static void extractAllConnectedSCell(std::vector< std::vector< SCell > > &aVectConnectedSCell, const KSpace &aKSpace, const SurfelAdjacency< KSpace::dimension > &aSurfelAdj, const PointPredicate &pp, bool forceOrientCellExterior=false)

After processing a simple display of each resulting connecting component you can obtain such a visualisation:

Visualisation of connected set of SignedKhalimskySpaceND

Here since the last argument is set to true, the resulting SignedKhalimskySpaceND are signed in order to indicate the direction of exterior. You can also get the SignedKhalimskySpaceND with default sign:

Surfaces<KSpace>::extractAllConnectedSCell(vectConnectedSCell,K, SAdj, shape_set_predicate, false);

and you will get the resulting cell display:

Visualisation of connected set of oriented surfels of a KhalimskySpaceND.

Filling oriented digital contours

The helper class Surface also proposes some methods to fill the interior/exterior of a digital set of 1-SCell which can be defined from an oriented FreemanChain. These methods can be usefull in particular to reconstruct images (as given for instance in the tool of freeman2pgm given in DGtalTools.).

The example ctopo-fillContours.cpp illustrates a basic filling with a shape containing a hole. The main steps to fill a contour represented by a FreemanChain are the following:

First we construct two FreemanChain to illustrate the fill with two different orientations:

FreemanChain<int> fc1 ("001001001001001111101111011222222223222222322233333330301033333003", 6, 14);
FreemanChain<int> fc2 ("1111000033332222", 6, 20);

To construct the set of signed SCell, we use the method getInterPixelLinels of FreemanChain class:

std::set<DGtal::KhalimskySpaceND< 2, int >::SCell> boundarySCell;
FreemanChain<int>::getInterPixelLinels(K, fc1, boundarySCell, false);
static void getInterPixelLinels(const KhalimskySpaceND< 2, TInteger > &aKSpace, const FreemanChain &fc, typename KhalimskySpaceND< 2, TInteger >::SCellSet &aSCellContour, bool aFlagForAppend=false)
SignedKhalimskyCell< dim, Integer > SCell
Note
The that the resulting Cell orientation is defined by convention such that a contour defined in the direct orientation will generate the fill of the interior of the shape (with the uComputeInterior method) while the indirect orientation should be used to define a hole (see example below).

Afterwards the region associated with the direct oriented contour (fc1) can be filled by using the method uComputeInterior with the parameter empty_is_inside set to false. This parameter choice is justified since here a line which does not contain any boundary element is necessary at the outside of the contour.

typedef ImageContainerBySTLMap< Z2i::Domain, bool> BoolImage2D;
BoolImage2D::Domain imageDomain( Z2i::Point(0,10), Z2i::Point(20,30) );
BoolImage2D interiorCellImage( imageDomain );
Surfaces<DGtal::KhalimskySpaceND< 2, int > >::uFillInterior(K, functors::SurfelSetPredicate<std::set<SCell>,SCell>(boundarySCell),
interiorCellImage, 1, false);
Space::Point Point
Definition: StdDefs.h:95
HyperRectDomain< Space > Domain

The interior Cells obtained from such a contour are illustrated here:

Filling interior of direct oriented contour.

We can also reconstruct region with hole by using a contour in the indirect direction. For instance we can define

std::set<DGtal::KhalimskySpaceND< 2, int >::SCell> boundarySCellhole;
FreemanChain<int>::getInterPixelLinels(K, fc2, boundarySCellhole, false);

And after adding it to the previous set boundarySCell you will obtain such a display:

Filling interior of direct oriented contour.
Note
From an indirect oriented contour you can also fill both the interior and exterior:
BoolImage2D interiorCellHoleImage( imageDomain );
BoolImage2D exteriorCellHoleImage( imageDomain );
Surfaces<DGtal::KhalimskySpaceND< 2, int > >::uFillInterior(K, functors::SurfelSetPredicate<std::set<SCell>, SCell>(boundarySCellhole),
interiorCellHoleImage, 1, true);
Surfaces<DGtal::KhalimskySpaceND< 2, int > >::uFillExterior(K, functors::SurfelSetPredicate<std::set<SCell>, SCell>(boundarySCellhole),
exteriorCellHoleImage, 1, false);
For the interior region, the parameter empty_is_inside parameter is set to true since a line without boundary element will correspond to the interior of the shape (since the contour is given the indirect orientation). In the same way, the parameter empty_is_outside is set to false. You will obtain such a representation:
Filling interior (lightgray) and exterior (dark gray) of indirect oriented contour.

Your challenge

Now you are able both to extract a set of contours and to fill contours. The exercise is now to reconstruct an image from a given set of contours.

Exercise

You challenge is to code:

  1. The load the set of all the Freeman Chains given in a text file.
  2. Transform Freeman Chains into signed cells of KhalimskySpaceND (SCell).
  3. Reconstruct an image associated to its Freeman Chains by filling.
  4. Identify image hole and fill it with specific gray level.
  5. Exporting resulting image in pgm format.

Hints

Result

If you succeed the exercise you will obtain the following pgm image:

Awaited result of exercice with the main region filled in white and holes represented in gray.