DGtal  0.6.devel
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Groups Pages
Analysis of one-dimensional discrete structures

Table of Contents

Authors:
Tristan Roussillon
Date:
2011/08/31

This part of the manual describes how to extract patterns from one-dimensional discrete structures (basically digital curves).

One-dimensional discrete structures

The goal is to provide tools that help in analysing any one-dimensional discrete structures in a generic framework. These structures are assumed to be constant, not mutable. This is a (not exhaustive) list of such structures used in digital geometry:

Iterators/Circulators and Ranges.

Since these structures are one-dimensional and discrete, they can be viewed as a locally ordered set of elements, like a string of pearls. Two notions are thus important: the one of element and the one of local order, which means that all the elements (except maybe at the ends) have a previous and next element. The concept of iterator is heavily used in our framework because it encompasses these two notions at the same time: like a pointer, it provides a way of moving along the structure (operator++, operator–) and provides a way of getting the elements (operator*).

In the following, iterators are assumed to be constant (because the structures are assumed to be constant) and to be at least bidirectionnal (ie. they are either bidirectionnal iterators or random access iterators). You can read the STL documentation about iterators: http://www.sgi.com/tech/stl/Iterators.html to learn more about the different kind of iterators.

The notion of reachability is very important. An iterator j is reachable from an iterator i if and only if i can be made equal to j with finitely many applications of the operator++. If j is reachable from i, one can iterate over the range of elements bounded by i and j, from the one pointed to by i and up to but not including the one pointed to by j. Such a range is valid and is denoted by [i,j).

Let i and j be two iterators that point to two elements of a same structure. For open (or linear) structures, [i,j) is not always a valid range (ie. j is not always reachable from i) because if j has not been reached and if i points to the last element, one application of the operator++ makes i to be equal to the past-the-end value, ie. an iterator value that points past the last element (just as a regular pointer to an array guarantees that there is a pointer value pointing past the last element of the array). If i turns out to be equal to the past-the-end value, then j cannot be reached from i. If an iterator denoted by begin points to the first element of a given structure and an iterator denoted by end is the past-the-end value, iterating over the range [begin,end) is a way of iterating over all the elements of the underlying structure. If the underlying structure is empty, it only has a past-the-end value. As a consequence, a range [i, i) denotes an empty range. A range of a linear structure is illustrated below (normal values are depicted with a small straight segment, whereas the past-the-end value is depicted with a cross). In this example, [i,j) is not a valid range because j cannot be reached from i and the whole range may be denoted by [begin,end).

linearRange.png
Linear range
However, for closed (or circular) structures, [i,j) should always be a valid range (ie. j should always be reachable from i) and there should be no past-the-end value. As a consequence, iterating over all the elements or dealing with empty ranges is different. The chosen solution is the same as the one used in CGAL. Circular iterators (or circulators for short) are used instead of classic iterators. They behave like classic iterators but they have a specific state in the empty range case. As long as i != j, the range [i,j) behaves like a classic iterator range and could be used in STL algorithms. The range [i,i) is used to iterate over all the elements in DGtal algorithms (each element is visited only once). Such a range is however considered as empty in STL algorithms. A range of a circular structure is illustrated below. In this example, [i,j) is a valid range.

circularRange.png
Circular range
Either an iterator or a circulator may have a reverse counterpart, ie. an adaptor that enables a backward scanning. The operator++ of the adaptor calls the operator– of the underlying (circular)iterator and conversely. You can use the STL reverse iterator for that: http://www.sgi.com/tech/stl/ReverseIterator.html

    @subsection geometryGridCurve GridCurve and FreemanChain.

Two objects are provided in DGtal to deal with digital curves: GridCurve and FreemanChain.

GridCurve is an (open or closed) n-dimensional oriented grid curve. It stores a list of alternated (signed) 0-cells and 1-cells, but provides many ranges to iterate over different kinds of elements:

You can get an access to these nine ranges through the following methods:

The different ranges for a grid curve whose chain code is 1110002223333 are depicted below.

0cellsRange.png
Range of 0-cells
1cellsRange.png
Range of 1-cells
PointsRange.png
Points of integer coordinates associated to 0-cells
MidPointsRange.png
Points of half-integer coordinates accociated to 1-cells
InnerPointsRange.png
Points of integer coordinates associated to the 2-cells directly incident to the 1-cells
OuterPointsRange.png
Points of integer coordinates associated to the 2-cells indirectly incident to the 1-cells
IncidentPointsRange.png
Points of integer coordinates associated to the 2-cells incident to the 1-cells
FreemanChain is 2-dimensional and 4-connected digital curve stored as a string of codes {0,1,2,3} as follows:

As GridCurve, it provides a CodesRange.

Each range has the following inner types:

And each range provides these (circular)iterator services:

You can use these services to iterate over the elements of a given range as follows:

trace.info() << "\t iterate over the range" << endl;
Range::ConstIterator it = r.begin();
Range::ConstIterator itEnd = r.end();
for ( ; it != itEnd; ++it)
{
trace.info() << *it;
}
trace.info() << endl;
trace.info() << "\t iterate over the range in the reverse way" << endl;
Range::ConstReverseIterator rit = r.rbegin();
Range::ConstReverseIterator ritEnd = r.rend();
for ( ; rit != ritEnd; ++rit)
{
trace.info() << *rit;
}
trace.info() << endl;
trace.info() << "\t iterate over the range in a circular way" << endl;
Range::ConstCirculator c = r.c();
//set the starting element wherever you want...
for (unsigned i = 0; i < 20; ++i) ++c;
//... and circulate
Range::ConstCirculator cend( c );
do
{
trace.info() << *c;
c++;
} while (c!=cend);
trace.info() << endl;

Since GridCurve and FreemanChain have both a method isClosed(), you can decide to use a classic iterator or a circulator at running time as follows:

//c is a grid curve, r is an instance of Range
//myProcessing is a template function where
//the range r is processed through (circular)iterators
if ( c.isClosed() ) {
myProcessing<Range::ConstCirculator>( r.c(), r.c() );
} else {
myProcessing<Range::ConstIterator>( r.begin(), r.end() );
}

Segments and on-line detection of segments.

In this section, we focus on parts of one-dimensional structures, called segment. More precisely, a segment is a valid and not empty range. Any model of the concept CSegment, should have the following inner types:

and the following methods:

Moreover, a segment can be defined from a property P, which can be built from conjunctions and disjunctions of other properties. A given property P is valid iff it is true for any valid range of only one element and true for any valid range of any segment for which P is true. For instance, the properties "to be a 4-connected DSS" or "to be a balanced word" can define segments, but "to contain at least k elements (k > 1)" cannot define segments because it does not hold for ranges of strictly less than k elements.

In digital geometry, a primitive is usually defined as a class of digital objects: the Gauss digitization of a Euclidean disk is an example of primitive. In this framework, a primitive is the class of segments verifying the same valid property P.

The detection problem consists in deciding whether a given segment belongs to a class of segments defined from a valid property P or not. If P is valid, the detection of a segment can be performed in an incremental way: a segment is initialized at a starting element and then can be extended to the neighbors elements if the property P still holds. A segment that can control its own extension (so that P remains true) is a segment computer. A segment computer is not a single concept, but actually five concepts that form a hierarchy: the first one can only extend itself in one direction, while others define additional functionality. The five concepts that are actually used in segmentation algorithms are CTrivialSegmentComputer, CForwardSegmentComputer, CBidirectionalSegmentComputer, CDynamicSegmentComputer and CDynamicBidirectionalSegmentComputer.

segmentComputersHierarchy.png
Hierarchy
The less restrictive kind of segment computer is CTrivialSegmentComputer, which is only presented to smoothly introduce the next concepts. CTrivialSegmentComputer is a refinement of CSegment and should provide in addition the following two methods:

Detecting a segment in a range looks like this:

//s is a segment computer
//[begin,end) is a range
s.init( begin );
while ( (s.end() != end) && (s.extendForward()) ) {}

If the underlying structure is closed, infinite loops are avoided as follows:

//s is a segment computer
//c is a circulator
s.init( c );
while ( (s.end() != s.begin()) && (s.extendForward()) ) {}

Segment computers are based on iterators. This is a useful feature since it provides a way to iterate over the segment if it is required at some points of the detection process. However the extension of a segment can be performed in only one direction (given by operator++). To cope with this problem, the models of CForwardSegmentComputer, which is a refinement of CTrivialSegmentComputer, should define the following nested type:

Reverse is a type that behaves like Self but with reverse iterators. Moreover, in order to build an instance of Self (resp. Reverse) from a segment computer, the following method should be defined:

The returned objects may not be full copies of this, may not have the same internal state as this, but must be constructed from the same input parameters so that they can detect segments verifying the same property. For instance, in digital geometry, k-arcs detection may be implemented as a segment computer constructed from the input parameter k indicating the adjacency to use. In this case, getSelf and getReverse methods must return segment computers based on the k-adjacency too. These segment computers may be not valid before calling the init method.

As the name suggests, forward segment computers can only extend themselves in the forward direction, but this direction is relative to the direction of the underlying iterators (the direction given by operator++ for instances of Self, but the direction given by operator– for instances of Reverse) They cannot extend themselves in two directions at the same time around an element contrary to bidirectional segment computers.

Any model of CBidirectionalSegmentComputer, which is a refinement of CForwardSegmentComputer, should define the following methods:

GeometricalDSS and GeometricalDCA are both models of CBidirectionalSegmentComputer.

The concept CDynamicSegmentComputer is another refinement of CForwardSegmentComputer. Any model of this concept should define the following method:

Finally, the concept CDynamicBidirectionalSegmentComputer is a refinement of both CBidirectionalSegmentComputer and CDynamicSegmentComputer and the define this extra method:

A model of CDynamicBidirectionalSegmentComputer is the class ArithmeticalDSS, devoted to the dynamic recognition of DSSs, defined as a sequence of connected points \( (x,y) \) such that \( \mu \leq ax - by < \mu + \omega \) (see Debled and Reveilles, 1995).

Here is a short example of how to use this class in the 4-connected case:

typedef PointVector<2,int> Point;
typedef std::vector<Point> Range;
typedef std::vector<Point>::const_iterator ConstIterator;
typedef ArithmeticalDSS<ConstIterator,int,4> DSS4;
// Input points
Range contour;
contour.push_back(Point(0,0));
contour.push_back(Point(1,0));
contour.push_back(Point(1,1));
contour.push_back(Point(2,1));
contour.push_back(Point(3,1));
contour.push_back(Point(3,2));
contour.push_back(Point(4,2));
contour.push_back(Point(5,2));
contour.push_back(Point(6,2));
contour.push_back(Point(6,3));
contour.push_back(Point(6,4));
// Add points while it is possible
DSS4 theDSS4;
theDSS4.init( contour.begin() );
while ( ( theDSS4.end() != contour.end() )
&&( theDSS4.extendForward() ) ) {}
// Output parameters
cout << theDSS4 << endl;

Here is a short example of how to use this class in the 8-connected case:

typedef PointVector<2,int> Point;
typedef std::vector<Point> Range;
typedef std::vector<Point>::const_iterator ConstIterator;
typedef ArithmeticalDSS<ConstIterator,int,8> DSS8;
// Input points
Range boundary;
boundary.push_back(Point(0,0));
boundary.push_back(Point(1,1));
boundary.push_back(Point(2,1));
boundary.push_back(Point(3,2));
boundary.push_back(Point(4,2));
boundary.push_back(Point(5,2));
boundary.push_back(Point(6,3));
boundary.push_back(Point(6,4));
// Add points while it is possible
DSS8 theDSS8;
theDSS8.init( boundary.begin() );
while ( ( theDSS8.end() != boundary.end() )
&&( theDSS8.extendForward() ) ) {}
// Output parameters
cout << theDSS8 << endl;

These snippets are drawn from ArithmeticalDSS.cpp.

The resulting DSSs of the two previous pieces of code are drawing below:

DSS8.png
8-connected DSS drawn with the paving mode
DSS4.png
4-connected DSS drawn with the grid mode

See Board2D: a stream mechanism for displaying 2D digital objects for the drawing mechanism.

As seen above, the code can be different if an iterator or a circulator is used as the nested ConstIterator type. Moreover, some tasks can be made faster for a given kind of segment computer than for another kind of segment computer. That's why many generic functions are provided in SegmentComputerUtils.h:

These functions are used in the segmentation algorithms introduced below.

Segments Extraction.

A given range contains a finite set of segments verifying a given valid property P. A segmentation is a subset of the whole set of segments, such that:

i. each element of the range belongs to a segment of the subset and

ii. no segment contains another segment of the subset.

Due to (ii), the segments of a segmentation can be ordered without ambiguity (according to the position of their first element for instance).

Segmentation algorithms should verify the concept CSegmentation. A CSegmentation model should define the following nested type:

It should also define a constructor taking as input parameters:

Note that a model of CSegmentComputerIterator should define the following methods :

The first and simplest segmentation is the greedy one: from a starting element, extend a segment while it is possible, get the last element of the resulting segment and iterate. This segmentation algorithm is implemented in the class GreedySegmentation.

 In the short example below, a digital curve stored in a STL vector
 is decomposed into 8-connected DSSs whose parameters are sent to 
 the standard output.
 @code 

//types definition typedef PointVector<2,int> Point; typedef std::vector<Point> Range; typedef Range::const_iterator ConstIterator; typedef ArithmeticalDSS<ConstIterator,int,8> SegmentComputer; typedef GreedySegmentation<SegmentComputer> Segmentation;

//input points Range curve; curve.push_back(Point(1,1)); curve.push_back(Point(2,1)); curve.push_back(Point(3,2)); curve.push_back(Point(4,2)); curve.push_back(Point(5,2)); curve.push_back(Point(6,2)); curve.push_back(Point(7,2)); curve.push_back(Point(8,1)); curve.push_back(Point(9,1));

//Segmentation SegmentComputer recognitionAlgorithm; Segmentation theSegmentation(curve.begin(), curve.end(), recognitionAlgorithm);

Segmentation::SegmentComputerIterator i = theSegmentation.begin(); Segmentation::SegmentComputerIterator end = theSegmentation.end(); for ( ; i != end; ++i) { SegmentComputer current(*i); trace.info() << current << std::endl; //standard output }

If you want to get the DSSs segmentation of the digital curve when it is scanned in the reverse way, you can use the reverse iterator of the STL vector:

...
typedef Range::const_reverse_iterator ConstReverseIterator;
...
Segmentation theSegmentation(curve.rbegin(), curve.rend(), recognitionAlgorithm);
...

The resulting segmentations are shown in the figures below:

left_right.png
segmented from left to right
right_left.png
segmented from right to left
If you want to get the DSSs segmentation of a part of the 
digital curve (not the whole digital curve), you can give 
the range to process as a pair of iterators when calling 
the setSubRange() method as follow: 
@code 

theSegmentation.setSubRange(beginIt, endIt);

Obviously, [beginIt, endIt) has to be a valid range included in the wider range [curve.begin(), curve.end()).

Moreover, a part of a digital curve may be processed either as an independant (open) digital curve or as a part whose segmentation at the ends depends of the underlying digital curve. That's why 3 processing modes are available:

In order to set a mode (before getting a SegmentComputerIterator), use the setMode() method as follow:

theSegmentation.setMode("DoNotTruncate");

Note that the default mode will be used for any unknown modes.

The complexity of the greedy segmentation algorithm relies on the complexity of the extendForward() method of the segment computer. If it runs in (possibly amortized) constant time, then the complexity of the segmentation is linear in the length of the range.

Saturated segmentation.

A unique and richer segmentation, called saturated segmentation, is the whole set of maximal segments (a maximal segment is a segment that cannot be contained in a greater segment). This segmentation algorithm is implemented in the class SaturatedSegmentation.

In the previous segmentation code, instead of the line:

typedef GreedySegmentation<SegmentComputer> Segmentation;

it is enough to write the following line:

typedef SaturatedSegmentation<SegmentComputer> Segmentation;

to get the following figure:

maxseg.png
maximal segments

See convex-and-concave-parts.cpp for an example of how to use maximal DSSs to decompose a digital curve into convex and concave parts.

If you want to get the saturated segmentation of a part of the digital curve (not the whole digital curve), you can give the range to process as a pair of iterators when calling the setSubRange() method as follow:

theSegmentation.setSubRange(beginIt, endIt);

Obviously, [beginIt, endIt) has to be a valid range included in the wider range [curve.begin(), curve.end()).

Moreover, the segmentation at the ends depends of the underlying digital curve. Among the whole set of maximal segments that pass through the first (resp. last) element of the range, one maximal segment must be chosen as the first (resp. last) retrieved maximal segments. Several processing modes are therefore available:

The mode i indicates that the segmentation begins with the i-th maximal segment passing through the first element and ends with the i maximal segment passing through the last element.

In order to set a mode (before getting a SegmentComputerIterator), use the setMode() method as follow:

theSegmentation.setMode("First");

Note that the default mode will be used for any unknown modes.

The complexity of the saturated segmentation algorithm relies on the complexity of the functions available for computing maximal segments (firstMaximalSegment, lastMaximalSegment, mostCenteredMaximalSegment, previousMaximalSegment and nextMaximalSegment), which are specialized according to the type of segment computer (forward, bidirectional and dynamic).

Let \( l \) be the length of the range and \( n \) the number of maximal segments. Let \( L_i \) be the length of the i-th maximal segments. During the segmentation, the current segment is extended:

Moreover, note that \( \Sigma_{1 \leq i \leq n} L_i \) may be equal to \( O(l) \) (for instance for DSSs).