vigra - vigra/orientedtensorfilters.hxx Source File

[ VIGRA Homepage | Function Index | Class Index | Namespaces | File List | Main Page ]

orientedtensorfilters.hxx |

1 /************************************************************************/
2 /* */
3 /* Copyright 2002-2004 by Ullrich Koethe */
4 /* */
5 /* This file is part of the VIGRA computer vision library. */
6 /* The VIGRA Website is */
7 /* http://hci.iwr.uni-heidelberg.de/vigra/ */
8 /* Please direct questions, bug reports, and contributions to */
9 /* ullrich.koethe@iwr.uni-heidelberg.de or */
10 /* vigra@informatik.uni-hamburg.de */
11 /* */
12 /* Permission is hereby granted, free of charge, to any person */
13 /* obtaining a copy of this software and associated documentation */
14 /* files (the "Software"), to deal in the Software without */
15 /* restriction, including without limitation the rights to use, */
16 /* copy, modify, merge, publish, distribute, sublicense, and/or */
17 /* sell copies of the Software, and to permit persons to whom the */
18 /* Software is furnished to do so, subject to the following */
19 /* conditions: */
20 /* */
21 /* The above copyright notice and this permission notice shall be */
22 /* included in all copies or substantial portions of the */
23 /* Software. */
24 /* */
25 /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND */
26 /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES */
27 /* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND */
28 /* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT */
29 /* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, */
30 /* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING */
31 /* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR */
32 /* OTHER DEALINGS IN THE SOFTWARE. */
33 /* */
34 /************************************************************************/
35
36 #ifndef VIGRA_ORIENTEDTENSORFILTERS_HXX
37 #define VIGRA_ORIENTEDTENSORFILTERS_HXX
38
39 #include <cmath>
40 #include "utilities.hxx"
41 #include "initimage.hxx"
42 #include "stdconvolution.hxx"
43 #include "multi_shape.hxx"
44
45 namespace vigra {
46
47 /** \addtogroup TensorImaging Tensor Image Processing
48 */
49 //@{
50
51 /********************************************************/
52 /* */
53 /* hourGlassFilter */
54 /* */
55 /********************************************************/
56
57 /** \brief Anisotropic tensor smoothing with the hourglass filter.
58
59 This function implements anisotropic tensor smoothing by an
60 hourglass-shaped filters as described in
61
62 U. K&ouml;the: <a href="http://hci.iwr.uni-heidelberg.de/people/ukoethe/papers/index.php#cite_structureTensor">
63 <i>"Edge and Junction Detection with an Improved Structure Tensor"</i></a>,
64 in: Proc. of 25th DAGM Symposium, Magdeburg 2003, Lecture Notes in Computer Science 2781,
65 pp. 25-32, Heidelberg: Springer, 2003
66
67 It is closely related to the structure tensor (see \ref structureTensor()), but
68 replaces the linear tensor smoothing with a smoothing along edges only.
69 Smoothing across edges is largely suppressed. This means that the
70 image structure is preserved much better because nearby features
71 such as parallel edges are not blended into each other.
72
73 The hourglass filter is typically applied to a gradient tensor, i.e. the
74 Euclidean product of the gradient with itself, which can be obtained by a
75 gradient operator followed with \ref vectorToTensor(), see example below.
76 The hourglass shape of the filter can be interpreted as indicating the likely
77 continuations of a local edge element. The parameter <tt>sigma</tt> determines
78 the radius of the hourglass (i.e. how far the influence of the edge element
79 reaches), and <tt>rho</tt> controls its opening angle (i.e. how narrow the
80 edge orientation os followed). Recommended values are <tt>sigma = 1.4</tt>
81 (or, more generally, two to three times the scale of the gradient operator
82 used in the first step), and <tt>rho = 0.4</tt> which corresponds to an
83 opening angle of 22.5 degrees to either side of the edge.
84
85 <b> Declarations:</b>
86
87 pass 2D array views:
88 \code
89 namespace vigra {
90 template <class T1, class S1,
91 class T2, class S2>
92 void
93 hourGlassFilter(MultiArrayView<2, T1, S1> const & src,
94 MultiArrayView<2, T2, S2> dest,
95 double sigma, double rho);
96 }
97 \endcode
98
99 \deprecatedAPI{hourGlassFilter}
100 pass \ref ImageIterators and \ref DataAccessors :
101 \code
102 namespace vigra {
103 template <class SrcIterator, class SrcAccessor,
104 class DestIterator, class DestAccessor>
105 void hourGlassFilter(SrcIterator sul, SrcIterator slr, SrcAccessor src,
106 DestIterator dul, DestAccessor dest,
107 double sigma, double rho);
108 }
109 \endcode
110 use argument objects in conjunction with \ref ArgumentObjectFactories :
111 \code
112 namespace vigra {
113 template <class SrcIterator, class SrcAccessor,
114 class DestIterator, class DestAccessor>
115 inline
116 void hourGlassFilter(triple<SrcIterator, SrcIterator, SrcAccessor> s,
117 pair<DestIterator, DestAccessor> d,
118 double sigma, double rho);
119 }
120 \endcode
121 \deprecatedEnd
122
123 <b> Usage:</b>
124
125 <b>\#include</b> <vigra/orientedtensorfilters.hxx><br/>
126 Namespace: vigra
127
128 \code
129 MultiArray<2, float> img(w,h);
130 MultiArray<2, TinyVector<float, 2> > gradient(w,h);
131 MultiArray<2, TinyVector<float, 3> > tensor(w,h), smoothedTensor(w,h);
132
133 gaussianGradient(img, gradient, 1.0);
134 vectorToTensor(gradient, tensor);
135 hourGlassFilter(tensor, smoothedTensor, 2.0, 0.4);
136 \endcode
137
138 \deprecatedUsage{hourGlassFilter}
139 \code
140 FImage img(w,h);
141 FVector2Image gradient(w,h);
142 FVector3Image tensor(w,h), smoothedTensor(w,h);
143
144 gaussianGradient(srcImageRange(img), destImage(gradient), 1.0);
145 vectorToTensor(srcImageRange(gradient), destImage(tensor));
146 hourGlassFilter(srcImageRange(tensor), destImage(smoothedTensor), 2.0, 0.4);
147 \endcode
148 \deprecatedEnd
149
150 \see vectorToTensor()
151 */
152 doxygen_overloaded_function(template <...> void hourGlassFilter)
153
154 template <class SrcIterator, class SrcAccessor,
155 class DestIterator, class DestAccessor>
156 void hourGlassFilter(SrcIterator sul, SrcIterator slr, SrcAccessor src,
157 DestIterator dul, DestAccessor dest,
158 double sigma, double rho)
159 {
160 vigra_precondition(sigma >= 0.0 && rho >= 0.0,
161 "hourGlassFilter(): sigma and rho must be >= 0.0");
162 vigra_precondition(src.size(sul) == 3,
163 "hourGlassFilter(): input image must have 3 bands.");
164 vigra_precondition(dest.size(dul) == 3,
165 "hourGlassFilter(): output image must have 3 bands.");
166
167 // TODO: normalization
168
169 int w = slr.x - sul.x;
170 int h = slr.y - sul.y;
171
172 double radius = VIGRA_CSTD::floor(3.0*sigma + 0.5);
173 double sigma2 = -0.5 / sigma / sigma;
174 double rho2 = -0.5 / rho / rho;
175 double norm = 1.0 / (2.0 * M_PI * sigma * sigma);
176
177 initImage(dul, dul+Diff2D(w,h), dest, NumericTraits<typename DestAccessor::value_type>::zero());
178
179 for(int y=0; y<h; ++y, ++sul.y, ++dul.y)
180 {
181 SrcIterator s = sul;
182 DestIterator d = dul;
183 for(int x=0; x<w; ++x, ++s.x, ++d.x)
184 {
185 double phi = 0.5 * VIGRA_CSTD::atan2(
186 2.0*src.getComponent(s,1),
187 (double)src.getComponent(s,0) - src.getComponent(s,2));
188 double u = VIGRA_CSTD::sin(phi);
189 double v = VIGRA_CSTD::cos(phi);
190
191 double x0 = x - radius < 0 ? -x : -radius;
192 double y0 = y - radius < 0 ? -y : -radius;
193 double x1 = x + radius >= w ? w - x - 1 : radius;
194 double y1 = y + radius >= h ? h - y - 1 : radius;
195
196 DestIterator dwul = d + Diff2D((int)x0, (int)y0);
197
198 for(double yy=y0; yy <= y1; ++yy, ++dwul.y)
199 {
200 typename DestIterator::row_iterator dw = dwul.rowIterator();
201 for(double xx=x0; xx <= x1; ++xx, ++dw)
202 {
203 double r2 = xx*xx + yy*yy;
204 double p = u*xx - v*yy;
205 double q = v*xx + u*yy;
206 double kernel = (p == 0.0) ?
207 (q == 0.0) ?
208 norm :
209 0.0 :
210 norm * VIGRA_CSTD::exp(sigma2*r2 + rho2*q*q/p/p);
211 dest.set(dest(dw) + kernel*src(s), dw);
212 }
213 }
214 }
215 }
216 }
217
218 template <class SrcIterator, class SrcAccessor,
219 class DestIterator, class DestAccessor>
220 inline void
221 hourGlassFilter(triple<SrcIterator, SrcIterator, SrcAccessor> s,
222 pair<DestIterator, DestAccessor> d,
223 double sigma, double rho)
224 {
225 hourGlassFilter(s.first, s.second, s.third, d.first, d.second, sigma, rho);
226 }
227
228 template <class T1, class S1,
229 class T2, class S2>
230 inline void
231 hourGlassFilter(MultiArrayView<2, T1, S1> const & src,
232 MultiArrayView<2, T2, S2> dest,
233 double sigma, double rho)
234 {
235 vigra_precondition(src.shape() == dest.shape(),
236 "hourGlassFilter(): shape mismatch between input and output.");
237 hourGlassFilter(srcImageRange(src), destImage(dest), sigma, rho);
238 }
239
240 /********************************************************/
241 /* */
242 /* ellipticGaussian */
243 /* */
244 /********************************************************/
245
246 template <class SrcIterator, class SrcAccessor,
247 class DestIterator, class DestAccessor>
248 void ellipticGaussian(SrcIterator sul, SrcIterator slr, SrcAccessor src,
249 DestIterator dul, DestAccessor dest,
250 double sigmax, double sigmin)
251 {
252 vigra_precondition(sigmax >= sigmin && sigmin >= 0.0,
253 "ellipticGaussian(): "
254 "sigmax >= sigmin and sigmin >= 0.0 required");
255
256 int w = slr.x - sul.x;
257 int h = slr.y - sul.y;
258
259 double radius = VIGRA_CSTD::floor(3.0*sigmax + 0.5);
260 double sigmin2 = -0.5 / sigmin / sigmin;
261 double norm = 1.0 / (2.0 * M_PI * sigmin * sigmax);
262
263 initImage(dul, dul+Diff2D(w,h), dest, NumericTraits<typename DestAccessor::value_type>::zero());
264
265 for(int y=0; y<h; ++y, ++sul.y, ++dul.y)
266 {
267 SrcIterator s = sul;
268 DestIterator d = dul;
269 for(int x=0; x<w; ++x, ++s.x, ++d.x)
270 {
271 typedef typename
272 NumericTraits<typename SrcAccessor::component_type>::RealPromote TmpType;
273 TmpType d1 = src.getComponent(s,0) + src.getComponent(s,2);
274 TmpType d2 = src.getComponent(s,0) - src.getComponent(s,2);
275 TmpType d3 = 2.0 * src.getComponent(s,1);
276 TmpType d4 = VIGRA_CSTD::sqrt(sq(d2) + sq(d3));
277 TmpType excentricity = 1.0 - (d1 - d4) / (d1 + d4);
278 double sigmax2 = -0.5 / sq((sigmax - sigmin)*excentricity + sigmin);
279
280 double phi = 0.5 * VIGRA_CSTD::atan2(d3, d2);
281 double u = VIGRA_CSTD::sin(phi);
282 double v = VIGRA_CSTD::cos(phi);
283
284 double x0 = x - radius < 0 ? -x : -radius;
285 double y0 = y - radius < 0 ? -y : -radius;
286 double x1 = x + radius >= w ? w - x - 1 : radius;
287 double y1 = y + radius >= h ? h - y - 1 : radius;
288
289 DestIterator dwul = d + Diff2D((int)x0, (int)y0);
290
291 for(double yy=y0; yy <= y1; ++yy, ++dwul.y)
292 {
293 typename DestIterator::row_iterator dw = dwul.rowIterator();
294 for(double xx=x0; xx <= x1; ++xx, ++dw)
295 {
296 double p = u*xx - v*yy;
297 double q = v*xx + u*yy;
298 double kernel = norm * VIGRA_CSTD::exp(sigmax2*p*p + sigmin2*q*q);
299 dest.set(dest(dw) + kernel*src(s), dw);
300 }
301 }
302 }
303 }
304 }
305
306 template <class SrcIterator, class SrcAccessor,
307 class DestIterator, class DestAccessor>
308 inline void
309 ellipticGaussian(triple<SrcIterator, SrcIterator, SrcAccessor> src,
310 pair<DestIterator, DestAccessor> dest,
311 double sigmax, double sigmin)
312 {
313 ellipticGaussian(src.first, src.second, src.third, dest.first, dest.second, sigmax, sigmin);
314 }
315
316 template <class T1, class S1,
317 class T2, class S2>
318 inline void
319 ellipticGaussian(MultiArrayView<2, T1, S1> const & src,
320 MultiArrayView<2, T2, S2> dest,
321 double sigmax, double sigmin)
322 {
323 vigra_precondition(src.shape() == dest.shape(),
324 "ellipticGaussian(): shape mismatch between input and output.");
325 ellipticGaussian(srcImageRange(src), destImage(dest), sigmax, sigmin);
326 }
327
328 /********************************************************/
329 /* */
330 /* kernels for orientedTrigonometricFilter */
331 /* */
332 /********************************************************/
333
334 class FoerstnerKernelBase
335 {
336 public:
337 typedef double ResultType;
338 typedef double WeightType;
339 typedef DVector2Image::value_type VectorType;
340 typedef VectorType::value_type ValueType;
341
342 FoerstnerKernelBase(double scale, bool ringShaped = false)
343 : radius_((int)(3.0*scale+0.5)),
344 weights_(2*radius_+1, 2*radius_+1),
345 vectors_(2*radius_+1, 2*radius_+1)
346 {
347 double norm = 1.0 / (2.0 * M_PI * scale * scale);
348 double s2 = -0.5 / scale / scale;
349
350 for(int y = -radius_; y <= radius_; ++y)
351 {
352 for(int x = -radius_; x <= radius_; ++x)
353 {
354 double d2 = x*x + y*y;
355 double d = VIGRA_CSTD::sqrt(d2);
356 vectors_(x+radius_,y+radius_) = d != 0.0 ?
357 VectorType(x/d, -y/d) :
358 VectorType(ValueType(0), ValueType(0));
359 weights_(x+radius_,y+radius_) = ringShaped ?
360 norm * d2 * VIGRA_CSTD::exp(d2 * s2) :
361 norm * VIGRA_CSTD::exp(d2 * s2);
362 }
363 }
364 }
365
366 ResultType operator()(int x, int y, VectorType const &) const
367 {
368 // isotropic filtering
369 return weights_(radius_, radius_);
370 }
371
372 int radius_;
373 DImage weights_;
374 DVector2Image vectors_;
375 };
376
377 class FoerstnerRingKernelBase
378 : public FoerstnerKernelBase
379 {
380 public:
381 FoerstnerRingKernelBase(double scale)
382 : FoerstnerKernelBase(scale, true)
383 {}
384 };
385
386 class Cos2RingKernel
387 : public FoerstnerKernelBase
388 {
389 public:
390 typedef double ResultType;
391 typedef double WeightType;
392 typedef DVector2Image::value_type VectorType;
393
394 Cos2RingKernel(double scale)
395 : FoerstnerKernelBase(scale, true)
396 {}
397
398 ResultType operator()(int x, int y, VectorType const & v) const
399 {
400 if(x == 0 && y == 0)
401 return weights_(radius_, radius_);
402 double d = dot(vectors_(x+radius_, y+radius_), v);
403 return d * d * weights_(x+radius_, y+radius_);
404 }
405 };
406
407 class Cos2Kernel
408 : public FoerstnerKernelBase
409 {
410 public:
411 typedef double ResultType;
412 typedef double WeightType;
413 typedef DVector2Image::value_type VectorType;
414
415 Cos2Kernel(double scale)
416 : FoerstnerKernelBase(scale, false)
417 {}
418
419 ResultType operator()(int x, int y, VectorType const & v) const
420 {
421 if(x == 0 && y == 0)
422 return weights_(radius_, radius_);
423 double d = dot(vectors_(x+radius_, y+radius_), v);
424 return d * d * weights_(x+radius_, y+radius_);
425 }
426 };
427
428 class Sin2RingKernel
429 : public FoerstnerKernelBase
430 {
431 public:
432 typedef double ResultType;
433 typedef double WeightType;
434 typedef DVector2Image::value_type VectorType;
435
436 Sin2RingKernel(double scale)
437 : FoerstnerKernelBase(scale, true)
438 {}
439
440 ResultType operator()(int x, int y, VectorType const & v) const
441 {
442 if(x == 0 && y == 0)
443 return weights_(radius_, radius_);
444 double d = dot(vectors_(x+radius_, y+radius_), v);
445 return (1.0 - d * d) * weights_(x+radius_, y+radius_);
446 }
447 };
448
449 class Sin2Kernel
450 : public FoerstnerKernelBase
451 {
452 public:
453 typedef double ResultType;
454 typedef double WeightType;
455 typedef DVector2Image::value_type VectorType;
456
457 Sin2Kernel(double scale)
458 : FoerstnerKernelBase(scale, false)
459 {}
460
461 ResultType operator()(int x, int y, VectorType const & v) const
462 {
463 if(x == 0 && y == 0)
464 return weights_(radius_, radius_);
465 double d = dot(vectors_(x+radius_, y+radius_), v);
466 return (1.0 - d * d) * weights_(x+radius_, y+radius_);
467 }
468 };
469
470 class Sin6RingKernel
471 : public FoerstnerKernelBase
472 {
473 public:
474 typedef double ResultType;
475 typedef double WeightType;
476 typedef DVector2Image::value_type VectorType;
477
478 Sin6RingKernel(double scale)
479 : FoerstnerKernelBase(scale, true)
480 {}
481
482 ResultType operator()(int x, int y, VectorType const & v) const
483 {
484 if(x == 0 && y == 0)
485 return weights_(radius_, radius_);
486 double d = dot(vectors_(x+radius_, y+radius_), v);
487 return VIGRA_CSTD::pow(1.0 - d * d, 3) * weights_(x+radius_, y+radius_);
488 }
489 };
490
491 class Sin6Kernel
492 : public FoerstnerKernelBase
493 {
494 public:
495 typedef double ResultType;
496 typedef double WeightType;
497 typedef DVector2Image::value_type VectorType;
498
499 Sin6Kernel(double scale)
500 : FoerstnerKernelBase(scale, false)
501 {}
502
503 ResultType operator()(int x, int y, VectorType const & v) const
504 {
505 if(x == 0 && y == 0)
506 return weights_(radius_, radius_);
507 double d = dot(vectors_(x+radius_, y+radius_), v);
508 return VIGRA_CSTD::pow(1.0 - d * d, 3) * weights_(x+radius_, y+radius_);
509 }
510 };
511
512 class Cos6RingKernel
513 : public FoerstnerKernelBase
514 {
515 public:
516 typedef double ResultType;
517 typedef double WeightType;
518 typedef DVector2Image::value_type VectorType;
519
520 Cos6RingKernel(double scale)
521 : FoerstnerKernelBase(scale, true)
522 {}
523
524 ResultType operator()(int x, int y, VectorType const & v) const
525 {
526 if(x == 0 && y == 0)
527 return weights_(radius_, radius_);
528 double d = dot(vectors_(x+radius_, y+radius_), v);
529 return (1.0 - VIGRA_CSTD::pow(1.0 - d * d, 3)) * weights_(x+radius_, y+radius_);
530 }
531 };
532
533 class Cos6Kernel
534 : public FoerstnerKernelBase
535 {
536 public:
537 typedef double ResultType;
538 typedef double WeightType;
539 typedef DVector2Image::value_type VectorType;
540
541 Cos6Kernel(double scale)
542 : FoerstnerKernelBase(scale, false)
543 {}
544
545 ResultType operator()(int x, int y, VectorType const & v) const
546 {
547 if(x == 0 && y == 0)
548 return weights_(radius_, radius_);
549 double d = dot(vectors_(x+radius_, y+radius_), v);
550 return (1.0 - VIGRA_CSTD::pow(1.0 - d * d, 3)) * weights_(x+radius_, y+radius_);
551 }
552 };
553
554 /********************************************************/
555 /* */
556 /* orientedTrigonometricFilter */
557 /* */
558 /********************************************************/
559
560 template <class SrcIterator, class SrcAccessor,
561 class DestIterator, class DestAccessor,
562 class Kernel>
563 void orientedTrigonometricFilter(SrcIterator sul, SrcIterator slr, SrcAccessor src,
564 DestIterator dul, DestAccessor dest,
565 Kernel const & kernel)
566 {
567 vigra_precondition(src.size(sul) == 2,
568 "orientedTrigonometricFilter(): input image must have 2 bands.");
569 vigra_precondition(dest.size(dul) == 3,
570 "orientedTrigonometricFilter(): output image must have 3 bands.");
571
572 int w = slr.x - sul.x;
573 int h = slr.y - sul.y;
574 int radius = kernel.radius_;
575
576 typedef typename SrcAccessor::value_type VectorType;
577 typedef typename DestAccessor::value_type TensorType;
578
579 initImage(dul, dul+Diff2D(w,h), dest, NumericTraits<TensorType>::zero());
580
581 for(int y=0; y<h; ++y, ++sul.y, ++dul.y)
582 {
583 SrcIterator s = sul;
584 DestIterator d = dul;
585 for(int x=0; x<w; ++x, ++s.x, ++d.x)
586 {
587 int x0 = x - radius < 0 ? -x : -radius;
588 int y0 = y - radius < 0 ? -y : -radius;
589 int x1 = x + radius >= w ? w - x - 1 : radius;
590 int y1 = y + radius >= h ? h - y - 1 : radius;
591
592 VectorType v(src(s));
593 TensorType t(sq(v[0]), v[0]*v[1], sq(v[1]));
594 double sqMag = t[0] + t[2];
595 double mag = VIGRA_CSTD::sqrt(sqMag);
596 if(mag != 0.0)
597 v /= mag;
598 else
599 v *= 0.0;
600 Diff2D dd;
601 for(dd.y = y0; dd.y <= y1; ++dd.y)
602 {
603 for(dd.x = x0; dd.x <= x1; ++dd.x)
604 {
605 dest.set(dest(d, dd) + kernel(dd.x, dd.y, v) * t, d, dd);
606 }
607 }
608 }
609 }
610 }
611
612 template <class SrcIterator, class SrcAccessor,
613 class DestIterator, class DestAccessor,
614 class Kernel>
615 inline void
616 orientedTrigonometricFilter(triple<SrcIterator, SrcIterator, SrcAccessor> src,
617 pair<DestIterator, DestAccessor> dest,
618 Kernel const & kernel)
619 {
620 orientedTrigonometricFilter(src.first, src.second, src.third, dest.first, dest.second, kernel);
621 }
622
623 template <class T1, class S1,
624 class T2, class S2,
625 class Kernel>
626 inline void
627 orientedTrigonometricFilter(MultiArrayView<2, T1, S1> const & src,
628 MultiArrayView<2, T2, S2> dest,
629 Kernel const & kernel)
630 {
631 vigra_precondition(src.shape() == dest.shape(),
632 "orientedTrigonometricFilter(): shape mismatch between input and output.");
633 orientedTrigonometricFilter(srcImageRange(src), destImage(dest), kernel);
634 }
635
636 //@}
637
638 } // namespace vigra
639
640 #endif /* VIGRA_ORIENTEDTENSORFILTERS_HXX */

© Ullrich Köthe (ullrich.koethe@iwr.uni-heidelberg.de) Heidelberg Collaboratory for Image Processing, University of Heidelberg, Germany | html generated using doxygen and Python vigra 1.10.0 (Mon Nov 18 2013)