forked from InsightSoftwareConsortium/ITK
-
Notifications
You must be signed in to change notification settings - Fork 0
/
NeighborhoodIterators.dox
251 lines (217 loc) · 9.74 KB
/
NeighborhoodIterators.dox
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
/**
\page NeighborhoodIteratorsPage Neighborhood Iterators
\section IntroductionSection Introduction
This document provides a general overview of the intended use of the
NeighborhoodIterator classes and their associated objects for low-level image
processing in Itk. For a more detailed description of the API, please refer to
the inline Itk documentation and manual.
\par
Neighborhood iterators are the abstraction of the concept of \em locality in
image processing. They are designed to be used in algorithms that perform
calculations on a neighborhood of pixel values around a point in an image. For
example, a classic neighborhood-based operation is to compute the mean of a set
of pixels in order to reduce noise in an image. This is typically done by
taking a region of 3x3 pixels and computing the average of their values. A new
image with fewer narrow high frequency spikes (noise) is produced using the
resulting means.
\par
This code in classical \em texbook notation can look like:
\code
int nx = 512;
int ny = 512;
ImageType image(nx,ny);
for(int x=1; x<nx-1; x++)
{
float sum = 0;
for(int y=1; y<ny-1; y++)
{
sum += image(x-1,y-1) + image(x ,y-1) + image(x+1,y-1);
sum += image(x-1,y ) + image(x ,y ) + image(x+1,y );
sum += image(x-1,y+1) + image(x ,y+1) + image(x+1,y+1);
}
}
\endcode
\par
The code above is readable and straightforward to implement. But its
efficiency is limited to the speed of random access into the image. It also
assumes a two dimensional image. We can eliminate the random access bottleneck
by using iterators to dereference pixels, and for a two-dimensional image,
code-readability may not suffer much. But what if we want code for three
or more dimensions? Before long, the code size and complexity will increase to
unmanageable levels. In Itk, we have encapsulated this functionality for
efficiency, readability, and reusability.
\section NeighborhoodIteratorsSection Neighborhood iterators
itk::Image neighborhood iteration and dereferencing is encapsulated in the
itk::NeighborhoodIterator classes. ITK NeighborhoodIterators allow for code
that is closer to the algorithmic abstraction,
\code
ForAllTheIndicies i in Image
GetTheNeighbors n in a 3x3 region around i
Compute the mean of all pixels n
Write the value to the output at i
\endcode
Here is how the pseudocode above can be rewritten in ITK using neighborhood
iterators: ( In the code examples that follow, some template parameters may
have been omitted for clarity. )
\code
1 using ImageType = itk::Image<float, 2>;
2 using NeighborhoodIterator = itk::SmartNeighborhoodIterator<ImageType>;
3 using ImageIterator = itk::ImageRegionIterator<ImageType>;
4
5 ImageType::Pointer input_image = GetImageSomehow();
6 ImageType::Pointer output_image = GetImageSomehow();
7
8 // A radius of 1 in all axial directions gives a 3x3x3x3x... neighborhood.
9 NeighborhoodIterator::RadiusType radius;
10 for (unsigned int i = 0; i < ImageType::ImageDimension; ++i) radius[i] = 1;
11
12 // Initializes the iterators on the input & output image regions
13 NeighborhoodIterator it(radius, input_image,
14 output_image->GetRequestedRegion());
15 ImageIterator out(output_image, output_image->GetRequestedRegion());
16
17 // Iterates over the input and output
18 for (it.SetToBegin(), out = out.Begin(); ! it.IsAtEnd(); ++it, ++out )
19 {
20 float accum = 0.0;
21 for (unsigned int i = 0; i < it.Size(); ++i)
22 {
23 accum += it.GetPixel(i);
24 }
25 out.Set(accum/(float)(it.Size()));
26 }
\endcode
\par
Note that the computational work is confined to lines 18-26. The code is also
completely generalized for multiple dimensions. For example, changing line 1
to:
\code
1 using ImageType = itk::Image<float, 5>;
\endcode
produces an averaging filter for five-dimensional images.
\par
The values in the neighborhood are dereferenced through the GetPixel(n) method
of the iterator. Think of the iterator as a C array, storing neighborhood
values in the same order that they are stored in the image, with the lowest
dimension as the fastest increasing dimension.
\section OperatorsOperationsSection Neighborhood operators and operations
NeighborhoodOperators are container classes for generating and storing
computational kernels such as LaPlacian, Gaussian, derivative, and
morphological operators. They provide a generalized interface for creation and
access of the operator coefficients.
\par
Applying a NeighborhoodOperator to a NeighborhoodIterator requires defining
some sort of operation. One common case is that of convolution with a kernel
of coefficients. In Itk, convolution filtering of an image can be done in just
a few lines of code using an inner product operation between a neighborhood
iterator and an operator. The following convolution with a LaPlacian kernel
demonstrates this concept.
\code
1 LaPlacianOperator<double, 3> OP;
2 ImageIterator out( outputImage, regionToProcess );
3 NeighborhoodIterator it ( OP.GetRadius(), inputImage, regionToProcess );
4 NeighborhoodInnerProduct IP;
5
6 out = out.Begin();
7 for (it.SetToBegin(); it != it.End(); ++it, ++out)
8 {
9 out.Set( IP( it, OP) );
10 }
\endcode
\par
Sometimes it may be more appropriate and efficient to work outside of the
operator/operation model. The mean calculation above is one example. A
``half'' directional derivative, shown below, is another example.
\code
1 NeighborhoodIterator::RadiusType radius = {1, 0, 0};
2 NeighborhoodIterator it(radius, inputImage, regionToProcess)
3 ImageRegionIterator out( outputImage, regionToProcess );
4 for (it.SetToBegin(); it != it.End(); ++it, ++out)
5 {
6 out.Set( it.GetPixel(3) - it.GetPixel(2) );
7 }
\endcode
\par
In this example the neighborhood is defined as a three pixel strip
with width along only the first axis. Mapping
the spatial orientation of neighborhood pixels to the array location is the
responsibility of the code writer. Some methods such as GetStride(n), which
returns the stride length in pixels along an axis n, have been provided to help
in coding algorithms for arbitrary dimensionality. The index of the center
pixel in a neighborhood is always Size()/2.
\section ImageBoundariesSection Calculations at image boundaries
For any neighborhood operation, conditions at data set boundaries become
important. SmartNeighborhoodIterator defines a special class of neighborhood
iterators that transparently handle boundary conditions. These iterators store
a boundary condition object that is used to calculate a value of requested
pixel indicies that lie outside the data set boundary. New boundary condition
objects can be defined by a user and plugged into SmartNeighboroodIterators as
is appropriate for their algorithm.
\par
A SmartNeighborhoodIterator can be used in place of NeighborhoodIterator to
iterate over an entire image region, but it will incur a penalty on
performance. For this reason, it is desirable to process the image differently
over distinct boundary and non-boundary regions. Itk's definition of image regions
makes this easy to manage. The process is as follows: first apply the
algorithm over all neighborhoods not on the image boundary using the fast
NeighborhoodIterator, then process each region on the boundary using the
SmartNeighborhoodIterator. The size of the boundary regions are defined by the
radius of the neighborhood that you are using.
\par
Rewriting the inner product code using this approach looks like the following.
(Here we are using the default SmartNeighborhoodIterator boundary condition and
omitting some template parameters for simplicity.)
\code
using RegionFinderType = NeighborhoodAlgorithm::ImageBoundaryFacesCalculator;
LaPlacianOperator<double, InputImageType::ImageDimension> OP;
RegionFinderType regionCalculator;
RegionFinderType::FaceListType regions;
RegionFinderType::FaceListType::iterator regions_iterator;
regions = regionCalculator( inputImage, regionToProcess, it.GetRadius() );
regions_iterator = regions.begin();
ImageIterator out;
//
// Process non-boundary regions normally.
//
out = ImageIterator(outputImage, *regions_iterator);
NeighborhoodIterator it (OP.GetRadius(), inputImage, *regions_iterator);
NeighborhoodInnerProduct IP;
out = out.Begin();
for (it.SetToBegin(); it != it.End(); ++it, ++out)
{
out.Set( IP( it, OP) );
}
//
// Process each boundary region with boundary conditions.
//
SmartNeighborhoodInnerProduct SIP;
SmartNeighborhoodIterator sit;
for (regions_iterator++ ; regions_iterator != regions.end(); regions_iterator++)
{
out = ImageIterator(outputImage, *regions_iterator);
sit = SmartNeighborhoodIterator(OP.GetRadius(), inputImage, *regions_iterator);
for (sit.SetToBegin(); sit != sit.End(); ++sit, ++out)
{
out.Set( SIP( sit, OP) );
}
}
\endcode
\par
The NeighborhoodAlgorithm::ImageBoundaryFacesCalculator is a special function
object that returns a list of sub-regions, or faces, of an image region. The
first region in the list corresponds to all the non-boundary pixels in the
input image region. Subseqent regions in the list represent all of the boundary
faces of the image (because an image region is defined only by a single index
and size, no single composite boundary region is possible). The list is
traversed with an iterator.
\section FurtherInformationSection Further information
Many filters in Itk use the neighborhood iterators and operators. Some
canonical examples of their use include the class of
AnisotropicDiffusionFunctions and the morphological image filters.
itk::WatershedSegmenter also makes extensive use of the neighborhood
iterators.
\par
The best documentation of the API for these objects is are the class
definitions themselves, since the API is subject to change as the tookit
matures and is refined.
*/