Вы находитесь на странице: 1из 17

Connected components

labeling - algorithms in
Mathematica, Java, C++
and C#.

Mariusz Jankowski
Department of Electrical Engineering,
University of Southern Maine, USA
mjankowski@usm.maine.edu

Jens-Peer Kuska
Department for Computer Graphics and
Image Processing, University of Leipzig,
Germany
kuska@informatik.uni-leipzig.de
2 Chapter Title

1. Abstract

Image segmentation is an important processing step in numerous image processing applications. A


multitude of techniques and algorithms fall into this broad category from simple thresholding, to
edge-detection, connected components labeling and watershed transformations. Of all these
techniques, connected components labeling and analysis is a relatively simple grouping algorithm
that has been used for many years to isolate, measure and identify potential object regions in an
image. It is widely used in industrial and biomedical applications where an image often consists of
objects against a contrasting background. Such images may be binarized yielding data that retains
useful shape and size information of the objects under observation. The labeling operation assigns a
unique name or number to all 1-pixels that belong to the same connected component of the image.
As a result of the labeling, individual components can be extracted from the image
programmatically and therefore be available for further processing and analysis.

A variety of region labeling algorithms have been described in the literature. In this paper we will
demonstrate Mathematica implementations of selected algorithms. Several alternative
implementations will be presented. The unique contribution of this paper lies in a detailed
discussion of realizations of a selected algorithm in several compiled languages and their
performance comparison. The languages include C++, Java, and C#.

2. Component Labeling Algorithms

This paper discusses several alternative implementations of connected components labeling


algorithms in Mathematica. The main objective of the paper is to illustrate with a useful image
processing example, the many implementation alternatives available to the Mathematica user and
discuss their advantages and disadvantages. Particular attention will be paid to the problem of
computational speed.

Finding the connected components in a binary image can be done in several different ways [ Haralick
1992, Glassner 2001, Rosenfeld 1966]. The simplest method is to iteratively replace each label with the
minimum of it's 8-connected neighborhood [Haralick 1992]. The algorithm begins with an initial
labeling of all 1-pixels and ends when no more replacements can be made. It has a very simple
Mathematica implementation.
Chapter Title 3

$nncell = 88−1, 0<, 80, −1<, 8−1, −1<,


8−1, 1<, 80, 0<, 80, 1<, 81, 1<, 81, 0<, 81, −1<<;

H∗ define auxiliary functions to scan the image and replace


labelComponentsFixedPoint@img_D := Module@8p, z = img<,

each labeled pixel with

newlabel@8r_, c_<D := Hz@@r, cDD =


the minimum label of it' s neighborhood ∗L

Min@Select@Extract@z, 8r, c< + # & ê@ $nncellD, PositiveDDL;


newimage@___D := HScan@newlabel, pD; zL;
H∗ find positions of 1−pixels in img ∗L

H∗ assign unique label to each 1−pixel ∗L


p = Position@img, 1D;

Do@8r, c< = p@@iDD; z@@r, cDD = i, 8i, Length@pD<D;


H∗ iterate until the result no longer changes ∗L
FixedPoint@newimage, zDD

The next algorithm is basically a float-fill algorithm [Glassner 2001]. The image is scanned until a
foreground pixel is found. An unlabeled foreground pixel is marked with a new label and its
position is pushed on a stack. While the stack is not empty the pixels on the stack are marked with
the label and the neighbor pixels are pushed on the stack. When the stack is empty the search
continues for the next seed point for the float-fill. The implementation in Mathematica is simple.
The labelComponentsStack[] function takes the binary image img (with zero boundary to
avoid the tests for the boundary in the inner loops) and the neighborhood positions nncell as
arguments.
4 Chapter Title

$nncell = 88−1, −1<, 8−1, 0<, 8−1, 1<,


80, −1<, 80, 1<, 81, −1<, 81, 0<, 81, 1<<;

8stack, stackPos = 0, nr, nc, r, c, g, pos = 81, 1<, label = 1<,


labelComponentsStack@img_, nncell_D := Module@

8nr, nc< = Dimensions@imgD;


H∗ generate a empty image for the labels ∗L
g = Table@0, 8nr<, 8nc<D;
H∗ allocate the stack for the pixel positions ∗L
stack = Table@80, 0<, 8Total@Flatten@imgDD<D;
H∗ scan the image ∗L
Do@

H∗ no foreground pixel ∗L
Do@

H∗ pixel is already labeled ∗L


If@0 img@@i, jDD, Continue@DD;

H∗ push the current position on the stack ∗L


If@0 < g@@i, jDD, Continue@DD;

stack@@++stackPosDD = 8i, j<;


H∗ label the position with the actual label ∗L

H∗ Start the float−fill: ∗L


g@@i, jDD = label;

H∗ pop the position from the stack ∗L


While@stackPos > 0,

H∗ loop over the neighborhood ∗L


pos = stack@@stackPos −−DD;

H∗ if it is a foreground pixel and not labeled yet,


Do@8r, c< = pos + nncell@@kDD;

push it on the stack ∗L

stack@@++stackPosDD = 8r, c<; g@@r, cDD = labelD,


If@0 ≠ img@@r, cDD && 0 == g@@r, cDD,

8k, Length@nncellD<D;
D;
H∗ increment the label ∗L

8j, 2, nc − 1<D,
label ++,

8i, 2, nr − 1<
D;

D
g

The main disadvantage of this algorithm is the fact, that the stack can become very large. For
two-dimensional pictures this is typical not a problem but for three-dimensional binary data it can
be. However, the algorithm is simple to implement in most programming languages.

The third algorithm [Rosenfeld 1966] avoids the usage of a stack and the global operation of the
float-filling. The algorithm needs two scans over the image. In the first scan one looks for
foreground pixels. If a foreground pixel is found the two previously scanned pixels in a 4-connected
Chapter Title 5

neighborhood or four pixels in an 8-connected neighborhood (Figure 1) are looked up for an already
assigned label.

pi-1, j pi, j

pi-1, j-1 pi, j-1 pi+1, j-1


Figure 1. Causal 8-connected pixel neighborhood used in region labeling algorithm.

If the neighborhood contains no labeled pixels a new label is generated and assigned to the actual
pixel. If the neighborhood contains a single labeled pixel the found label is assigned to the pixel. If
the neighborhood contain more than a single label, one of the labels is used for the pixel and the
equivalence relation is recorded.

$nncell = 88−1, −1<, 8−1, 0<, 8−1, 1<, 80, −1<<;

labelComponentsTwoPass@img_, nncell_D :=

8nr, nc< = Dimensions@bmD;


Module@8g, eq, nr, nc, nn, tmp, label = 1, k = 1, u<,

g = Table@0, 8nr<, 8nc<D;


nn = Length@nncellD;

eq = Table@0, 8Total@Flatten@bmDD<, 8nn<D;


H∗ scan the image ∗L

H∗ no foreground pixel ∗L
Do@

H∗ extract neighboring labels ∗L


If@bm@@i, jDD 0, Continue@DD;

u = Union@Extract@g, H8i, j< + # &L ê@ nncellDD;

H∗ zero or one labeled pixels in neighborhood ∗L


g@@i, jDD =

If@Length@uD 1,

H∗ more than one label in neighborhood,


If@u@@1DD 0, label ++, u@@1DDD,

Htmp = Select@u, PositiveD;


record equivalences ∗L

If@Length@tmpD ≠ 1, eq@@k ++DD = PadRight@tmp, nnDD;

8i, 2, nr<, 8j, 2, nc<D;


tmp@@1DDLD,

gD
6 Chapter Title

With the binary image in Figure 2, the first pass generates four labels and produces the labeled
image shown together with the equivalence relations 2 ñ 3 and 3 ñ 4.

Figure 2. A binary image (left) and labeled image (right), after initial labeling.

The next step is to resolve the equivalence relations to the rules that label 3 can be replaced with
label 2 and that label 4 can be replaced by label 2. When the replacement rules are found we can
apply these rules in the second pass and obtain the complete labeled image. The main problem here
is to resolve the equivalence relations. There are two standard ways to represent equivalence
relations: as a list of equivalence pairs (adjacency list) or as a matrix (adjacency matrix). The former
representation leads to a solution based on elementary graph search algorithms, as the equivalence
relations are simply a form of an undirected graph [Cormen 1990]. Several very fast Mathematica
realizations of a such an algorithm were recently posted on the Mathematica newsgroup [MathGroup].
The adjacency matrix algorithm is as follows [Gonzalez 1992]. Given equivalence relations with n
labels, a boolean n µ n matrix B is created with a True value at each position defined by an
equivalence relation k ñ l and False otherwise. The matrix is symmetric because the equivalence is
transitive and has True on the main diagonal because every label is equivalent to itself. To resolve
all equivalences one has to build the transitive closure matrix B+ which is given by

B+ = B fi B B fi B B B fi … fi Bn (Eq. 1)

The multiplication is standard matrix multiplication with element-wise logical fl. The most efficient
way to compute B+ is the Warshall algorithm [Warshall 1962]. Using Mathematica's sparse matrix
technique, with 1 for True and 0 for False, the Warshall algorithm can be implemented as

transitiveClosure@b_D :=

8n, m< = Dimensions@closureD;


Module@8n, m, i, j, k, closure = b<,

Do@
If@1 closure@@i, jDD,

Max@closure@@i, kDD, closure@@k, jDDD, 8k, n<DD


Do@closure@@i, kDD = closure@@k, iDD =

, 8i, m<, 8j, n<D;

D
closure

The equivalences are extracted from the transient closure B+ with the function
findEquivalence [2].
Chapter Title 7

findEquivalence@closure_D :=

8n, m< = Dimensions@closureD;


Module@8cc = closure, n, m, i, j, k, eq<,

eq = Table@i, 8i, n<D;


Do@
If@ cc@@i, jDD 1,

Do@cc@@j, kDD = 0, 8k, n<D;


eq@@iDD = eq@@jDD;

D, 8j, n<, 8i, m<D;


Select@MapIndexed@#2@@1DD → #1 &, eqD, Not@ Equal @@ ## D &D
D

The result will be a list of Mathematica replacement rules that can be used to update the labels in the
image form the first pass to the final labeled image. The final image is shown in Figure 3 (also in
pseudo-color).

Figure 3. Labeled image with two disjoint regions.

3. Fast Implementation Methods

In this section we turn our attention to the stack-based implementation in order to demonstrate and
compare fast realizations using pure Mathematica code and three compiled languages: C++, C#, and
Java.

The Mathematica Compile function can be used very effectively to speed up computations of the
type presented here. The code is translated into a simple register-based assembly language for a
virtual machine that is interpreted by the kernel. This code executes faster because it avoids the
main evaluation loop and is particularly effective when machine-precision numerical calculations
need to be executed a large number of times [Wagner 1996]. Many built-in Mathematica functions
themselves use Compile.

To obtain a compiled version of the stack-based labelComponents algorithm presented in the


previous section we simply invoke the Mathematica compiler with the following expression.
8 Chapter Title

Compile@88bm, _Integer, 2<, 8nncell, _Integer, 2<<,


labelComponentsCompiled =

88g, _Integer, 2<, 8stack, _Integer, 2<<D;


Module@8 ..<, ...D,

Compilation may improve processing speed by about a factor of 10, but sometimes even this is not
sufficient. To achieve greater processing speeds it is necessary to make calls to functions written in
compiled languages, such as C. Historically this has been accomplished by utilizing MathLink, the
cross-platform communication protocol defined by Wolfram Research, Inc [Wagner 1996, Wolfram
1999]. There is an extensive body of literature on the subject of using MathLink to call user-written
C or C++ functions or pre-existing scientific application libraries. We will not repeat the details here
due to space limitations and the fact that recent MathLink extensions have significantly simplified
the traditional process of calling external functions from Mathematica. These extensions, built on a
MathLink foundation are J/Link and .NET/Link introduced in versions 4 and 5 of Mathematica,
respectively. These add-ons give the Mathematica user unprecedented freedom in selecting the best
possible implementation for their high performance computing needs.

We begin with a Java implementation of the stack-based labeling algorithm. This loads the J/Link
add-on, launches the Java runtime and appends the Java class path with the location of our
user-defined classes.

Needs@"JLink`"D

InstallJava@D;

8$HomeDirectory, "My Documents", "Java", "jlink"<DD;


AddToClassPath@ToFileName@

The following loads the Java class and instantiates the LabelImage object. This makes all the
public methods (i.e., functions) of the class available to the user and the Mathematica kernel.

javaObj = JavaNew@"LabelImage"D;

The class has a single method, labelImage that takes two arguments, the source image in the
form of a 2D array of integers and an integer specifying the desired size of the stack. It returns the
labeled image in the form of a 2D array of integers. The Java source code for the LabelImage
class is shown below.

/*
* LabelImage.java
*
* Created on April 16, 2004, 1:59 PM
*/
Chapter Title 9

public class LabelImage {

int [][] label ;


Stack stack ;

/** Creates a new instance of LabelImage */


public LabelImage() {
}

public int[][] labelImage(int[][] img, int stackSize) {

int nrow = img.length ;


int ncol = img[0].length ;
int lab = 1 ;
int [] pos ;
stack = new Stack( stackSize ) ;
label = new int[nrow][ncol] ;

for (int r = 1; r < nrow-1; r++)


for (int c = 1; c < ncol-1; c++) {

if (img[r][c] == 0) continue ;
if (label[r][c] > 0) continue ;
/* encountered unlabeled foreground pixel at position r, c */
/* push the position on the stack and assign label */
stack.push(new int [] {r, c}) ;
label[r][c] = lab ;

/* start the float fill */


while ( !stack.isEmpty()) {
pos = stack.pop() ;
int i = pos[0]; int j = pos[1];

if (img[i-1][j-1] == 1 && label[i-1][j-1] == 0) {


stack.push( new int[] {i-1,j-1} );
label[i-1][j-1] = lab ;
}
if (img[i-1][j] == 1 && label[i-1][j] == 0) {
stack.push( new int[] {i-1,j} );
label[i-1][j] = lab ;
}
if (img[i-1][j+1] == 1 && label[i-1][j+1] == 0) {
stack.push( new int[] {i-1,j+1} );
label[i-1][j+1] = lab ;
}
if (img[i][j-1] == 1 && label[i][j-1] == 0) {
stack.push( new int[] {i,j-1} );
label[i][j-1] = lab ;
}
if (img[i][j+1] == 1 && label[i][j+1] == 0) {
10 Chapter Title

stack.push( new int[] {i,j+1} );


label[i][j+1] = lab ;
}
if (img[i+1][j-1] == 1 && label[i+1][j-1] == 0) {
stack.push( new int[] {i+1,j-1} );
label[i+1][j-1] = lab ;
}
if (img[i+1][j] == 1 && label[i+1][j] == 0) {
stack.push( new int[] {i+1,j} );
label[i+1][j] = lab ;
}
if (img[i+1][j+1] == 1 && label[i+1][j+1] == 0) {
stack.push( new int[] {i+1,j+1} );
label[i+1][j+1] = lab ;
}

} /* end while */
lab++ ;
}
return label ;
}

The method labelImage is now available for use as any Mathematica built-in or user-defined
function. Here is an example binary array.

i
j y
z
j
j z
z
j z
0 0 0 0 0
j
j z
z
j
j z
z
MatrixFormAimg = j z
0 0 1 1 0
j
j z
zE;
j
j z
z
j z
0 0 0 1 0
j
j z
z
j z
0 1 0 1 0
k0 0 0 0 0 {

The following returns a labeled image.

javaObj @ labelImage@img, 4D êê MatrixForm

i
j y
z
j
j z
z
j z
0 0 0 0 0
j
j z
z
j
j z
z
j z
0 0 1 1 0
j
j z
z
j
j z
z
j z
0 0 0 1 0
j z
k0 {
0 2 0 1 0
0 0 0 0

.NET/Link integrates Mathematica with Microsoft's .NET platform in much the same way J/Link
integrates with Java. .NET is a new development platform for Windows programming. It's
attractiveness to the Mathematica user comes from the fact that it supports code development in any
Chapter Title 11

language with a .NET compiler, including the widely used C++ and C#, a new language that is
similar in many ways to Java.

Needs@"NETLink`"D

This launches the .NET runtime.

InstallNET@D;

This loads a .NET assembly.

netPath = $HomeDirectory <>


"\\My Documents\\csharp\\LabelImage\\bin\\Release\\";
labelimg = LoadNETAssembly@netPath <> "LabelImage.dll"D

NETAssembly@LabelImage, 1D

This creates the a new .NET object.

netObj = NETNew@"LabelImage.LabelImage"D;

A fragment of the C# source code is shown next.

using System;

namespace LabelImage
{
/// <summary>
/// Summary description for LabelImage.
/// </summary>
///
.
.
.
public class LabelImage
{
public static Wolfram.NETLink.Expr floatfill(int ny,int nx,int []
data,int thres)
{ IntegerImage src, label;
int i,j,k,ii,jj;
Stack stack;
int labelNo=1;
Wolfram.NETLink.Expr toMath, symbolList;

src= new IntegerImage(nx+2,ny+2);


label=new IntegerImage(nx+2,ny+2);
for(k=j=0; j<ny; j++)
for(i=0; i<nx; i++)
12 Chapter Title

src.set(i+1,j+1,data[k++]);
src.binarize(thres);

stack= new Stack(nx*ny);


for(j=1; j<ny; j++)
for(i=1; i<nx; i++){
if(src.backgroundQ(i,j) || label.foregroundQ(i,j))
continue;
// push (i,j) on the stack
stack.push(i,j);
label.set(i,j,labelNo);
while(!stack.emptyQ()) {
stack.pop(out ii,out jj);
if(src.foregroundQ(ii-1,jj) &&
label.backgroundQ(ii-1,jj)) {
stack.push(ii-1,jj);
label.set(ii-1,jj,labelNo);
}
if(src.foregroundQ(ii+1,jj) &&
label.backgroundQ(ii+1,jj)) {
stack.push(ii+1,jj);
label.set(ii+1,jj,labelNo);
}
if(src.foregroundQ(ii,jj-1) &&
label.backgroundQ(ii,jj-1)) {
stack.push(ii,jj-1);
label.set(ii,jj-1,labelNo);
}
if(src.foregroundQ(ii,jj+1) &&
label.backgroundQ(ii,jj+1)) {
stack.push(ii,jj+1);
label.set(ii,jj+1,labelNo);
}
if(src.foregroundQ(ii-1,jj-1) &&
label.backgroundQ(ii-1,jj-1)) {
stack.push(ii-1,jj-1);
label.set(ii-1,jj-1,labelNo);
}
if(src.foregroundQ(ii-1,jj+1) &&
label.backgroundQ(ii-1,jj+1)) {
stack.push(ii-1,jj+1);
label.set(ii-1,jj+1,labelNo);
}
if(src.foregroundQ(ii+1,jj+1) &&
label.backgroundQ(ii+1,jj+1)) {
stack.push(ii+1,jj+1);
label.set(ii+1,jj+1,labelNo);
}
if(src.foregroundQ(ii-1,jj+1) &&
label.backgroundQ(ii-1,jj+1)) {
Chapter Title 13

stack.push(ii-1,jj+1);
label.set(ii-1,jj+1,labelNo);
}
} // end while
labelNo++;
}
symbolList=new
Wolfram.NETLink.Expr(Wolfram.NETLink.ExpressionType.Symbol,"List");
toMath=new
Wolfram.NETLink.Expr(symbolList,labelNo-1,label.toMatrix());
return toMath;
}
}

The following returns a labeled image.

netObj @ floatfill@5, 5, Flatten@imgD, 1D êê Last êê MatrixForm

i
j y
z
j
j z
z
j
j z
z
0 0 0 0 0
j
j z
z
j
j z
z
0 0 1 1 0
j
j z
z
j
j z
z
j z
0 0 0 1 0
j z
k0 {
0 2 0 1 0
0 0 0 0

4. Results

In this section, we present timing data for all the realizations of the stack-based algorithm discussed
in the previously. This loads the Digital Image Processing application package.

<< ImageProcessing`

Here we import an example image and binarize using function Threshold.

dataPath =
ToFileName@8$HomeDirectory, "My Documents", "Data"<D;
img = Threshold@ImageRead@
ToFileName@dataPath, "Einstein.tif"DD, 127D;

This compares the speed of un-compiled and compiled evaluations of the labeling algorithm written
in Mathematica.
14 Chapter Title

tmp = labelComponentsStack@img@@1DD, $nncellD;D êê First


AbsoluteTiming@

Out[6]= 2.2343750 Second

The compiled version runs approximately an order of magnitude faster.

tmp = labelComponentsCompiled@img@@1DD, $nncellD;D êê First


AbsoluteTiming@

0.2187500 Second

This creates a "look-up table" from a list of the primary and secondary colors. The list lut is
simply a list of replacement rules formed by a cyclic repetition of the selected colors. The length of
the list is determined by the number of labels.

colors = 881, 0, 0<, 80, 1, 0<,


80, 0, 1<, 81, 1, 0<, 80, 1, 1<, 81, 0, 1<<;
In[7]:=

lut = Thread@Rule@Range@0, Max@tmpDD,


Join@880, 0, 0<<, PadRight@colors, Max@tmpD, colorsDDDD;

Here we show the original binary image and the labeled image, pseudo-colored using look-up table
lut.

<< Graphics`

8Show@Graphics@imgDD, Show@Graphics@Raster@tmp ê. lut,


DisplayTogetherArray@

ColorFunction → RGBColorD, AspectRatio → AutomaticDD<D;

In what follows we present timing results obtained by running four versions of the connected
components stack-based algorithm for four different image sizes. The four code versions include
compiled Mathematica, C++, C#, and Java, while the chosen data dimensions are: 256 µ 256,
512 µ 512, 1 K µ 1 K, and 2 K µ 2 K. Each recorded timing value reflects an average of 20 runs for
each combination of language and image dimension. This shows the raw results.
Chapter Title 15

timings = 880.01640478`, 0.06405758`, 0.26091746`, 1.0311708`<,


80.03593727`, 0.138280365`, 0.56562138`, 2.30779773`<,
80.0203112`, 0.093744`, 0.41013`, 1.6631748`<,
80.26247984`, 1.04601341`, 4.13327629`, 15.96715119`<<;

Here is the TableForm of the collected timing results.

2562 5122 1k2 2k2


C++ 0.0164048 0.0640576 0.2609175 1.0
C# 0.0359373 0.1382804 0.5656214 2.3
Java 0.0203112 0.093744 0.41013 1.6
Mathematica 0.26248 1.0460134 4.1332763 15.
Table 1. Timing results (in seconds) of four different realizations of region labeling algorithm.

Here is a graphical representation of the timing results.

2562 5122 10242 20482

15000

Intel C 7.01
12500

Intel C#
10000
Java

7500
Mathematica

5000

2500

0
2562 5122 10242 20482

Figure 4. Timing results (in milliseconds) of four different realizations of region labeling algorithm.

Here is an alternative view of the same timing data. The following bar chart shows timing results
normalized with respect to compiled Mathematica speed and averaged over the four dimensions of
the example image.
16 Chapter Title

C++ C# Java Mathematica


1

0.8

0.6

0.4

0.2

0
C++ C# Java Mathematica

Figure 5. Relative timing results for four different realizations of region labeling algorithm (Mathematica = 1.0).

5. Summary

In this paper we investigated the execution speed of three compiled languages and Mathematica
using a non-trivial algorithm of practical importance in digital image processing. The region
labeling algorithm we present is interesting for two reasons, it plays a prominent role in image
analysis and it has behavioral features that make it difficult for interpreted languages such as
Mathematica to implement efficiently. The chosen compiled languages are all naturally supported
by MathLink and its recent extensions, J/Link and NET/Link. As has been demonstrated, linking to
external code has been made especially simple in the recent editions of Mathematica, therefore
allowing any user to benefit from the performance advantages of compiled languages. Our results
confirm that the C++/MathLink combination remains the fastest alternative to pure Mathematica
development. However, we also found Java and J/Link to be only 20% - 60% slower then C++. This
peformance difference should be carefully weighed against the benefits of a significantly simpler
interface between Mathematica and Java and greater portability.

6. References

[Haralick 1992] Haralick, R.M., Shapiro, L.G., "Computer and Robot Vision," Volume 1,
Addison-Wesley, 1992.
[Glassner 2001] Glassner, A., "Fill 'Er Up!", IEEE Computer Graphics and Applications, 2001, Vol.
21 No. 1; Jan. / Feb. 2001, pp. 78-85.
Chapter Title 17

[Rosenfeld 1966] Rosenfeld, A., Pfalz, J. L., "Sequential Operations in Digital Picture Processing,"
Journal of the ACM, Vol. 13, No. 4, October 1966, pp. 471-494.
[Cormen 1990] Cormen, H., Leiserson, C., and Rivest, R., "Introduction to Algorithms," MIT Press,
1990.
[MathGroup] http://forums.wolfram.com/mathgroup/archive/2004/Feb/ - see thread: "Re:
Computing sets of equivalences".
[Gonzalez 1992] Gonzales, R. C., Woods, R. E., "Digital Image Processing," Addison Wesley, 1992.
[Warshall 1962] Warshall, S., "A Theorem on Boolean Matrices," Journal of the ACM, Vol. 9, No.
1, Jan 1962, pp.11-12.
[Wagner 1996] Wagner, D., "Power programming with Mathematica," McGraw-Hill, 1996.
[Wolfram 1999] Wolfram, S., "The Mathematica Book," Fourth Edition, Wolfram Research, 1999.

Вам также может понравиться