DGtal  0.9.3beta
Cubical Complex

Table of Contents

Author(s) of this documentation:
Jacques-Olivier Lachaud

Part of the Topology package.

This part of the manual describes how to represent and process arbitrary cubical complexes.

Collapse operation is a backport from ImaGene. [37]

The following programs are related to this documentation: cubical-complex-collapse.cpp, cubical-complex-illustrations.cpp, testCubicalComplex.cpp,

Introduction to cubical complexes

We define a cubical complex C as a collection of cells living in some Khalimsky space. Two cells of C are incident if and only if they are incident in the Khalimsky space. A cubical complex provides many services related to set of (unsigned) cells living in a Khalimsky space: incidence operations, closure, star, link, cell set operations, cell set relations, collapse operation.

In opposition to the usual definition of simplical complex, we do not require that all faces of a cell of C belong also to C.
A digital surface is not a cubical complex, because it does not contain explicitely cells of dimension lower than n-1 and because it requires some sort of orientation. However, it is easy to build an ExplicitDigitalSurface or a LightExplicitDigitalSurface from a CubicalComplex that stores a set of d-1-cells. You just need to create a functor Surfel -> bool from the complex.

To create a cubical complex, we need to specify in which Khalimsky space it lives and also, optionally, the type of container used for storing cells. By default it is std::map but boost::unordered_map or std::unordered_map is also possible.

using namespace DGtal;
typedef KhalimskySpaceND<2,int> KSpace; // ZxZ
typedef std::map<Cell, CubicalCellData> Map; // each cell must store at least a CubicalCellData
typedef CubicalComplex< KSpace, Map > CC; // the type of cubical complex
KSpace K; // The cellular grid space
K.init( Point( 0,0 ), Point( 100,100 ), true ); // Limits of the grid space
CC complex( K ); // The complex

Cells may be inserted through methods CubicalComplex::insertCell and CubicalComplex::insertCells. Cells are easily created with a model of CCellularGridSpaceND, for instance by specifying Khalimsky coordinates (see Cellular grid space and topology, unoriented and oriented cells, incidence). The small piece of code below creates a ring around a pixel.

typedef KSpace::Point Point; // the type of each digital point.
complex.insertCell( 0, K.uCell( Point( 0, 0 ) ) ); // it is optional (but slightly faster)
complex.insertCell( 1, K.uCell( Point( 1, 0 ) ) ); // to specify the dimension of the cell
complex.insertCell( 0, K.uCell( Point( 2, 0 ) ) ); // at insertion.
complex.insertCell( 1, K.uCell( Point( 2, 1 ) ) );
complex.insertCell( 0, K.uCell( Point( 2, 2 ) ) );
complex.insertCell( 1, K.uCell( Point( 1, 2 ) ) );
complex.insertCell( 0, K.uCell( Point( 0, 2 ) ) );
complex.insertCell( 1, K.uCell( Point( 0, 1 ) ) );
trace.info() << "Euler number is " << complex.euler() << std::endl; // Should be 0

Method CubicalComplex::insertCells accepts any range of iterators on cells as input, hence you may for instance directly create a cubical complex from a digital surface (see Digital surfaces).

You have also methods to remove some cells (CubicalComplex::eraseCell, CubicalComplex::eraseCells) and methods to check if a cell belongs to the complex (CubicalComplex::belongs).

You may use std::unordered_map or boost::unordered_map instead of std::map. It is sometimes slightly faster. In this case you need to define a hash function for cells. Either you provide one, or you just include the file KhalimskyCellHashFunctions.h with:
#include "DGtal/topology/KhalimskyCellHashFunctions.h"

Last, there is a data associated with each cell of a complex. The data type must either be CubicalCellData or a type that derives from CubicalCellData. This data is used by the functions::collapse operation, a function that is used to make a homotopic thinning of the cubical complex. It may also be used by the user for other purposes, like storing flags, coordinates or anything else associated to a cell. Look at the documentation of CubicalCellData to see the default stored flags and data.

Displaying a 2D cubical complex

2D cubical complex are displayable on a Board (see Board2D: a stream mechanism for displaying 2D digital objects). You just have to output the complex with the stream operator of a board. You may adjust the default style before. The following snippet creates and outputs a 2D complex.

using namespace DGtal::Z2i;
KSpace K;
K.init( Point( 0,0 ), Point( 5,3 ), true );
trace.beginBlock( "Creating Cubical Complex" );
CC X( K );
Domain domain( Point( 0,0 ), Point( 5,3 ) );
X.insertCell( K.uSpel( Point(1,1) ) );
X.insertCell( K.uSpel( Point(2,1) ) );
X.insertCell( K.uSpel( Point(3,1) ) );
X.insertCell( K.uSpel( Point(2,2) ) );
X.insertCell( K.uSpel( Point(3,2) ) );
X.insertCell( K.uSpel( Point(4,2) ) );
trace.beginBlock( "Displays Cubical Complex" );
Board2D board;
board << domain;
board << CustomStyle( X.className(),
new CustomColors( Color(80,80,100), Color(180,180,200) ) )
<< X;
board.saveTikZ( "cubical-complex-illustrations-X.tikz" );
Displaying a 2D cubical complex on a Board.

Incidence within a cubical complex

Two cells of a cubical complex are incident if and only if they are incident in the Khalimsky space. From this relation, one can define faces, co-faces, as well as the boundary and co-boundary of a cell.

You have methods to get the faces, the co-faces, the direct faces or co-faces of a cell c, which are outputed with an output iterator:

There are also versions of these methods that return the iterators on these cells, if you need to access them directly in the complex afterwards.

If you wish to get a vector of cells that contains all the proper faces of some cell, then CubicalComplex::cellBoundary does the job. It is in general slightly faster than using CubicalComplex::faces.

If you wish to get a vector of cells that contains all the proper co-faces of some cell, then CubicalComplex::cellCoBoundary does the job. Again it is in general slightly faster than using CubicalComplex::coFaces.

Closure, star and link of cells of a complex

You have three methods to compute these "subcomplexes" of a complex X. In each case, you must give a subcomplex S of X which defines the cells for which you wish to compute the closure, star or link.

Cubical complex X in light grey and subcomplex S in dark grey.

Interior, boundary of a complex

You have three methods for computing the interior or the boundary of a complex. Note that the boundary and the interior of a complex have an empty intersection:

Set operations on complexes

A cubical complex can be seen as a generic container (it is a model of boost::Container). Remember though that it is not a model of boost::DefaultConstructible, because it must know a cellular grid space to be valid. You could thus use operations defined in DGtal::functions::setops (see Set operations on arbitrary containers) to perform set operations and relations on complexes. However, to use the specificity of the templated parameter CellContainer of CubicalComplex, it is better to use operations and relations defined in namespace DGtal::function, for instance as follows:

using namespace DGtal::functions;
CC X( K ); // creates the cubical complex
CC Xc = ~X; // closing of X
CC Xco = *Xc; // opening of Xc (interior of X).
CC Xbd = Xc - Xco; // boundary of X.
CC Xbd2= X.boundary();// same result for pure complex.

You have the following operations:

And the following relations:

All these operations are at most linear in the sum of the number of cells of input complexes, but they are generally exponential in the dimension of complexes.

Topological operations: closing, opening, collapsing a complex

You may close a complex (CubicalComplex::close), i.e. adding cells such that the faces of every cell belongs to the complex. The complex is then said to be closed. The updated complex corresponds to the smallest closed complex containing it.

You may open a complex (CubicalComplex::open), i.e. removing cells such that the co-faces of every cell belongs to the complex. The complex is then said to be open. The updated complex corresponds to the greatest open complex contained in it.

A more difficult topological operation is the collapse (functions::collapse). A collapse transforms a complex by removing cells, but at the end the complex has the same homotopy type as the initial complex. The topology is preserved since only free pairs are removed. The following snippet shows how to collapse complex X, while keeping two cells fixed.

board << domain;
board << CustomStyle( X.className(),
new CustomColors( Color(80,80,100), Color(180,180,200) ) )
<< X;
Cell p1 = K.uCell( Point(2,2) );
Cell p2 = K.uCell( Point(10,6) );
X[ p1 ] = CC::FIXED;
X[ p2 ] = CC::FIXED;
functions::collapse( X, X.begin(), X.end(), P, true, true, true );
board << CustomStyle( X.className(),
new CustomColors( Color(255,120,20), Color(255,150,50) ) )
<< X
<< CustomStyle( p1.className(),
new CustomColors( Color::Blue, Color(120,120,255) ) )
<< p1 << p2;
board.saveTikZ( "cubical-complex-illustrations-collapse.tikz" );

As you can see, when collapsing a complex X, you precise:

Example topology/cubical-complex-collapse.cpp shows that it works also in 3D (in fact, it is nD).

Homotopic collapse of X, displayed in orange, with fixed points marked in blue.