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

stdconvolution.hxx |

1 /************************************************************************/
2 /* */
3 /* Copyright 1998-2002 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
37 #ifndef VIGRA_STDCONVOLUTION_HXX
38 #define VIGRA_STDCONVOLUTION_HXX
39
40 #include <cmath>
41 #include "stdimage.hxx"
42 #include "bordertreatment.hxx"
43 #include "separableconvolution.hxx"
44 #include "utilities.hxx"
45 #include "sized_int.hxx"
46 #include "multi_iterator.hxx"
47 #include "multi_shape.hxx"
48
49 namespace vigra {
50
51 template <class ARITHTYPE>
52 class Kernel2D;
53
54 /** \addtogroup CommonConvolutionFilters
55 */
56 //@{
57
58 // documentation is in convolution.hxx
59 template <class SrcIterator, class SrcAccessor,
60 class DestIterator, class DestAccessor,
61 class KernelIterator, class KernelAccessor>
62 void convolveImage(SrcIterator src_ul, SrcIterator src_lr, SrcAccessor src_acc,
63 DestIterator dest_ul, DestAccessor dest_acc,
64 KernelIterator ki, KernelAccessor ak,
65 Diff2D kul, Diff2D klr, BorderTreatmentMode border)
66 {
67 vigra_precondition((border == BORDER_TREATMENT_CLIP ||
68 border == BORDER_TREATMENT_AVOID ||
69 border == BORDER_TREATMENT_REFLECT ||
70 border == BORDER_TREATMENT_REPEAT ||
71 border == BORDER_TREATMENT_WRAP ||
72 border == BORDER_TREATMENT_ZEROPAD),
73 "convolveImage():\n"
74 " Border treatment must be one of follow treatments:\n"
75 " - BORDER_TREATMENT_CLIP\n"
76 " - BORDER_TREATMENT_AVOID\n"
77 " - BORDER_TREATMENT_REFLECT\n"
78 " - BORDER_TREATMENT_REPEAT\n"
79 " - BORDER_TREATMENT_WRAP\n"
80 " - BORDER_TREATMENT_ZEROPAD\n");
81
82 vigra_precondition(kul.x <= 0 && kul.y <= 0,
83 "convolveImage(): coordinates of "
84 "kernel's upper left must be <= 0.");
85 vigra_precondition(klr.x >= 0 && klr.y >= 0,
86 "convolveImage(): coordinates of "
87 "kernel's lower right must be >= 0.");
88
89 // use traits to determine SumType as to prevent possible overflow
90 typedef typename
91 PromoteTraits<typename SrcAccessor::value_type,
92 typename KernelAccessor::value_type>::Promote SumType;
93 typedef typename
94 NumericTraits<typename KernelAccessor::value_type>::RealPromote KernelSumType;
95 typedef typename DestAccessor::value_type DestType;
96
97 // calculate width and height of the image
98 int w = src_lr.x - src_ul.x;
99 int h = src_lr.y - src_ul.y;
100
101 // calculate width and height of the kernel
102 int kernel_width = klr.x - kul.x + 1;
103 int kernel_height = klr.y - kul.y + 1;
104
105 vigra_precondition(w >= std::max(klr.x, -kul.x) + 1 && h >= std::max(klr.y, -kul.y) + 1,
106 "convolveImage(): kernel larger than image.");
107
108 KernelSumType norm = KernelSumType();
109 if(border == BORDER_TREATMENT_CLIP)
110 {
111 // calculate the sum of the kernel elements for renormalization
112 KernelIterator yk = ki + klr;
113
114 // determine sum within kernel (= norm)
115 for(int y = 0; y < kernel_height; ++y, --yk.y)
116 {
117 KernelIterator xk = yk;
118 for(int x = 0; x < kernel_width; ++x, --xk.x)
119 {
120 norm += ak(xk);
121 }
122 }
123 vigra_precondition(norm != NumericTraits<KernelSumType>::zero(),
124 "convolveImage(): Cannot use BORDER_TREATMENT_CLIP with a DC-free kernel");
125 }
126
127 DestIterator yd = dest_ul;
128 SrcIterator ys = src_ul;
129
130 // iterate over the interior part
131 for(int y=0; y<h; ++y, ++ys.y, ++yd.y)
132 {
133 // create x iterators
134 DestIterator xd(yd);
135 SrcIterator xs(ys);
136
137 for(int x=0; x < w; ++x, ++xs.x, ++xd.x)
138 {
139 // init the sum
140 SumType sum = NumericTraits<SumType>::zero();
141 KernelIterator ykernel = ki + klr;
142
143 if(x >= klr.x && y >= klr.y && x < w + kul.x && y < h + kul.y)
144 {
145 // kernel is entirely inside the image
146 SrcIterator yys = xs - klr;
147 SrcIterator yyend = xs - kul;
148
149 for(; yys.y <= yyend.y; ++yys.y, --ykernel.y)
150 {
151 typename SrcIterator::row_iterator xxs = yys.rowIterator();
152 typename SrcIterator::row_iterator xxe = xxs + kernel_width;
153 typename KernelIterator::row_iterator xkernel= ykernel.rowIterator();
154
155 for(; xxs < xxe; ++xxs, --xkernel)
156 {
157 sum += ak(xkernel) * src_acc(xxs);
158 }
159 }
160 }
161 else if(border == BORDER_TREATMENT_REPEAT)
162 {
163 Diff2D diff;
164 for(int yk = klr.y; yk >= kul.y; --yk, --ykernel.y)
165 {
166 diff.y = std::min(std::max(y - yk, 0), h-1);
167 typename KernelIterator::row_iterator xkernel = ykernel.rowIterator();
168
169 for(int xk = klr.x; xk >= kul.x; --xk, --xkernel)
170 {
171 diff.x = std::min(std::max(x - xk, 0), w-1);
172 sum += ak(xkernel) * src_acc(src_ul, diff);
173 }
174 }
175 }
176 else if(border == BORDER_TREATMENT_REFLECT)
177 {
178 Diff2D diff;
179 for(int yk = klr.y; yk >= kul.y; --yk , --ykernel.y)
180 {
181 diff.y = abs(y - yk);
182 if(diff.y >= h)
183 diff.y = 2*h - 2 - diff.y;
184 typename KernelIterator::row_iterator xkernel = ykernel.rowIterator();
185
186 for(int xk = klr.x; xk >= kul.x; --xk, --xkernel)
187 {
188 diff.x = abs(x - xk);
189 if(diff.x >= w)
190 diff.x = 2*w - 2 - diff.x;
191 sum += ak(xkernel) * src_acc(src_ul, diff);
192 }
193 }
194 }
195 else if(border == BORDER_TREATMENT_WRAP)
196 {
197 Diff2D diff;
198 for(int yk = klr.y; yk >= kul.y; --yk, --ykernel.y)
199 {
200 diff.y = (y - yk + h) % h;
201 typename KernelIterator::row_iterator xkernel = ykernel.rowIterator();
202
203 for(int xk = klr.x; xk >= kul.x; --xk, --xkernel)
204 {
205 diff.x = (x - xk + w) % w;
206 sum += ak(xkernel) * src_acc(src_ul, diff);
207 }
208 }
209 }
210 else if(border == BORDER_TREATMENT_CLIP)
211 {
212 KernelSumType ksum = NumericTraits<KernelSumType>::zero();
213 Diff2D diff;
214 for(int yk = klr.y; yk >= kul.y; --yk, --ykernel.y)
215 {
216 diff.y = y - yk;
217 if(diff.y < 0 || diff.y >= h)
218 continue;
219 typename KernelIterator::row_iterator xkernel = ykernel.rowIterator();
220
221 for(int xk = klr.x; xk >= kul.x; --xk, --xkernel)
222 {
223 diff.x = x - xk;
224 if(diff.x < 0 || diff.x >= w)
225 continue;
226 ksum += ak(xkernel);
227 sum += ak(xkernel) * src_acc(src_ul, diff);
228 }
229 }
230
231 sum *= norm / ksum;
232 }
233 else if(border == BORDER_TREATMENT_ZEROPAD)
234 {
235 Diff2D diff;
236 for(int yk = klr.y; yk >= kul.y; --yk, --ykernel.y)
237 {
238 diff.y = y - yk;
239 if(diff.y < 0 || diff.y >= h)
240 continue;
241 typename KernelIterator::row_iterator xkernel = ykernel.rowIterator();
242
243 for(int xk = klr.x; xk >= kul.x; --xk, --xkernel)
244 {
245 diff.x = x - xk;
246 if(diff.x < 0 || diff.x >= w)
247 continue;
248 sum += ak(xkernel) * src_acc(src_ul, diff);
249 }
250 }
251 }
252 else if(border == BORDER_TREATMENT_AVOID)
253 {
254 continue;
255 }
256
257 // store convolution result in destination pixel
258 dest_acc.set(detail::RequiresExplicitCast<DestType>::cast(sum), xd);
259 }
260 }
261 }
262
263 template <class SrcIterator, class SrcAccessor,
264 class DestIterator, class DestAccessor,
265 class KernelIterator, class KernelAccessor>
266 inline void
267 convolveImage(triple<SrcIterator, SrcIterator, SrcAccessor> src,
268 pair<DestIterator, DestAccessor> dest,
269 tuple5<KernelIterator, KernelAccessor, Diff2D, Diff2D,
270 BorderTreatmentMode> kernel)
271 {
272 convolveImage(src.first, src.second, src.third,
273 dest.first, dest.second,
274 kernel.first, kernel.second, kernel.third,
275 kernel.fourth, kernel.fifth);
276 }
277
278 template <class T1, class S1,
279 class T2, class S2,
280 class T3>
281 inline void
282 convolveImage(MultiArrayView<2, T1, S1> const & src,
283 MultiArrayView<2, T2, S2> dest,
284 Kernel2D<T3> const & kernel)
285 {
286 vigra_precondition(src.shape() == dest.shape(),
287 "convolveImage(): shape mismatch between input and output.");
288 convolveImage(srcImageRange(src),
289 destImage(dest),
290 kernel2d(kernel));
291 }
292
293 /** \brief Performs a 2-dimensional normalized convolution, i.e. convolution with a mask image.
294
295 This functions computes
296 <a href ="http://homepages.inf.ed.ac.uk/rbf/CVonline/LOCAL_COPIES/PIRODDI1/NormConv/NormConv.html">normalized
297 convolution</a> as defined in
298 Knutsson, H. and Westin, C-F.: <i>Normalized and differential convolution:
299 Methods for Interpolation and Filtering of incomplete and uncertain data</i>.
300 Proc. of the IEEE Conf. on Computer Vision and Pattern Recognition, 1993, 515-523.
301
302 The mask image must be binary and encodes which pixels of the original image
303 are valid. It is used as follows:
304 Only pixel under the mask are used in the calculations. Whenever a part of the
305 kernel lies outside the mask, it is ignored, and the kernel is renormalized to its
306 original norm (analogous to the CLIP \ref BorderTreatmentMode). Thus, a useful convolution
307 result is computed whenever <i>at least one valid pixel is within the current window</i>
308 Thus, destination pixels not under the mask still receive a value if they are <i>near</i>
309 the mask. Therefore, this algorithm is useful as an interpolator of sparse input data.
310 If you are only interested in the destination values under the mask, you can perform
311 a subsequent \ref copyImageIf().
312
313 The KernelIterator must point to the center of the kernel, and
314 the kernel's size is given by its upper left (x and y of distance <= 0) and
315 lower right (distance >= 0) corners. The image must always be larger than the
316 kernel. At those positions where the kernel does not completely fit
317 into the image, the specified \ref BorderTreatmentMode is
318 applied. Only BORDER_TREATMENT_CLIP and BORDER_TREATMENT_AVOID are currently
319 supported.
320
321 The images's pixel type (SrcAccessor::value_type) must be a
322 linear space over the kernel's value_type (KernelAccessor::value_type),
323 i.e. addition of source values, multiplication with kernel values,
324 and NumericTraits must be defined.
325 The kernel's value_type must be an algebraic field,
326 i.e. the arithmetic operations (+, -, *, /) and NumericTraits must
327 be defined.
328
329 <b> Declarations:</b>
330
331 pass 2D array views:
332 \code
333 namespace vigra {
334 template <class T1, class S1,
335 class T2, class S2,
336 class TM, class SM,
337 class T3>
338 void
339 normalizedConvolveImage(MultiArrayView<2, T1, S1> const & src,
340 MultiArrayView<2, TM, SM> const & mask,
341 MultiArrayView<2, T2, S2> dest,
342 Kernel2D<T3> const & kernel);
343 }
344 \endcode
345
346 \deprecatedAPI{normalizedConvolveImage}
347 pass \ref ImageIterators and \ref DataAccessors :
348 \code
349 namespace vigra {
350 template <class SrcIterator, class SrcAccessor,
351 class MaskIterator, class MaskAccessor,
352 class DestIterator, class DestAccessor,
353 class KernelIterator, class KernelAccessor>
354 void
355 normalizedConvolveImage(SrcIterator src_ul, SrcIterator src_lr, SrcAccessor src_acc,
356 MaskIterator mul, MaskAccessor am,
357 DestIterator dest_ul, DestAccessor dest_acc,
358 KernelIterator ki, KernelAccessor ak,
359 Diff2D kul, Diff2D klr, BorderTreatmentMode border);
360 }
361 \endcode
362 use argument objects in conjunction with \ref ArgumentObjectFactories :
363 \code
364 namespace vigra {
365 template <class SrcIterator, class SrcAccessor,
366 class MaskIterator, class MaskAccessor,
367 class DestIterator, class DestAccessor,
368 class KernelIterator, class KernelAccessor>
369 void normalizedConvolveImage(triple<SrcIterator, SrcIterator, SrcAccessor> src,
370 pair<MaskIterator, MaskAccessor> mask,
371 pair<DestIterator, DestAccessor> dest,
372 tuple5<KernelIterator, KernelAccessor, Diff2D, Diff2D,
373 BorderTreatmentMode> kernel);
374 }
375 \endcode
376 \deprecatedEnd
377
378 <b> Usage:</b>
379
380 <b>\#include</b> <vigra/stdconvolution.hxx><br>
381 Namespace: vigra
382
383 \code
384 MultiArray<2, float> src(w,h), dest(w,h);
385 MultiArray<2, unsigned char> mask(w,h);
386 ...
387 // define 3x3 binomial filter
388 vigra::Kernel2D<float> binom;
389 binom.initExplicitly(Diff2D(-1,-1), Diff2D(1,1)) = // upper left and lower right
390 0.0625, 0.125, 0.0625,
391 0.125, 0.25, 0.125,
392 0.0625, 0.125, 0.0625;
393
394 normalizedConvolveImage(src, mask, dest, binom);
395 \endcode
396
397 \deprecatedUsage{normalizedConvolveImage}
398 \code
399 vigra::FImage src(w,h), dest(w,h);
400 vigra::CImage mask(w,h);
401 ...
402
403 // define 3x3 binomial filter
404 vigra::Kernel2D<float> binom;
405
406 binom.initExplicitly(Diff2D(-1,-1), Diff2D(1,1)) = // upper left and lower right
407 0.0625, 0.125, 0.0625,
408 0.125, 0.25, 0.125,
409 0.0625, 0.125, 0.0625;
410
411 vigra::normalizedConvolveImage(srcImageRange(src), maskImage(mask), destImage(dest), kernel2d(binom));
412 \endcode
413 <b> Required Interface:</b>
414 \code
415 ImageIterator src_ul, src_lr;
416 ImageIterator mul;
417 ImageIterator dest_ul;
418 ImageIterator ik;
419
420 SrcAccessor src_accessor;
421 MaskAccessor mask_accessor;
422 DestAccessor dest_accessor;
423 KernelAccessor kernel_accessor;
424
425 NumericTraits<SrcAccessor::value_type>::RealPromote s = src_accessor(src_ul);
426
427 s = s + s;
428 s = kernel_accessor(ik) * s;
429 s -= s;
430
431 if(mask_accessor(mul)) ...;
432
433 dest_accessor.set(
434 NumericTraits<DestAccessor::value_type>::fromRealPromote(s), dest_ul);
435
436 NumericTraits<KernelAccessor::value_type>::RealPromote k = kernel_accessor(ik);
437
438 k += k;
439 k -= k;
440 k = k / k;
441
442 \endcode
443 \deprecatedEnd
444
445 <b> Preconditions:</b>
446
447 <ul>
448 <li> The image must be longer than the kernel radius: <tt>w > std::max(kernel.lowerRight().x, -kernel.upperLeft().x)</tt> and
449 <tt>h > std::max(kernel.lowerRight().y, -kernel.upperLeft().y)</tt>.
450 <li> The sum of kernel elements must be != 0.
451 <li> <tt>border == BORDER_TREATMENT_CLIP || border == BORDER_TREATMENT_AVOID</tt>
452 </ul>
453 */
454 doxygen_overloaded_function(template <...> void normalizedConvolveImage)
455
456 template <class SrcIterator, class SrcAccessor,
457 class DestIterator, class DestAccessor,
458 class MaskIterator, class MaskAccessor,
459 class KernelIterator, class KernelAccessor>
460 void
461 normalizedConvolveImage(SrcIterator src_ul, SrcIterator src_lr, SrcAccessor src_acc,
462 MaskIterator mul, MaskAccessor am,
463 DestIterator dest_ul, DestAccessor dest_acc,
464 KernelIterator ki, KernelAccessor ak,
465 Diff2D kul, Diff2D klr, BorderTreatmentMode border)
466 {
467 vigra_precondition((border == BORDER_TREATMENT_CLIP ||
468 border == BORDER_TREATMENT_AVOID),
469 "normalizedConvolveImage(): "
470 "Border treatment must be BORDER_TREATMENT_CLIP or BORDER_TREATMENT_AVOID.");
471
472 vigra_precondition(kul.x <= 0 && kul.y <= 0,
473 "normalizedConvolveImage(): left borders must be <= 0.");
474 vigra_precondition(klr.x >= 0 && klr.y >= 0,
475 "normalizedConvolveImage(): right borders must be >= 0.");
476
477 // use traits to determine SumType as to prevent possible overflow
478 typedef typename
479 NumericTraits<typename SrcAccessor::value_type>::RealPromote SumType;
480 typedef typename
481 NumericTraits<typename KernelAccessor::value_type>::RealPromote KSumType;
482 typedef
483 NumericTraits<typename DestAccessor::value_type> DestTraits;
484
485 // calculate width and height of the image
486 int w = src_lr.x - src_ul.x;
487 int h = src_lr.y - src_ul.y;
488 int kernel_width = klr.x - kul.x + 1;
489 int kernel_height = klr.y - kul.y + 1;
490
491 int x,y;
492 int ystart = (border == BORDER_TREATMENT_AVOID) ? klr.y : 0;
493 int yend = (border == BORDER_TREATMENT_AVOID) ? h+kul.y : h;
494 int xstart = (border == BORDER_TREATMENT_AVOID) ? klr.x : 0;
495 int xend = (border == BORDER_TREATMENT_AVOID) ? w+kul.x : w;
496
497 // create y iterators
498 DestIterator yd = dest_ul + Diff2D(xstart, ystart);
499 SrcIterator ys = src_ul + Diff2D(xstart, ystart);
500 MaskIterator ym = mul + Diff2D(xstart, ystart);
501
502 KSumType norm = ak(ki);
503 int xx, yy;
504 KernelIterator yk = ki + klr;
505 for(yy=0; yy<kernel_height; ++yy, --yk.y)
506 {
507 KernelIterator xk = yk;
508
509 for(xx=0; xx<kernel_width; ++xx, --xk.x)
510 {
511 norm += ak(xk);
512 }
513 }
514 norm -= ak(ki);
515
516
517 for(y=ystart; y < yend; ++y, ++ys.y, ++yd.y, ++ym.y)
518 {
519 // create x iterators
520 DestIterator xd(yd);
521 SrcIterator xs(ys);
522 MaskIterator xm(ym);
523
524 for(x=xstart; x < xend; ++x, ++xs.x, ++xd.x, ++xm.x)
525 {
526 // how much of the kernel fits into the image ?
527 int x0, y0, x1, y1;
528
529 y0 = (y<klr.y) ? -y : -klr.y;
530 y1 = (h-y-1<-kul.y) ? h-y-1 : -kul.y;
531 x0 = (x<klr.x) ? -x : -klr.x;
532 x1 = (w-x-1<-kul.x) ? w-x-1 : -kul.x;
533
534 bool first = true;
535 // init the sum
536 SumType sum = NumericTraits<SumType>::zero();
537 KSumType ksum = NumericTraits<KSumType>::zero();
538
539 SrcIterator yys = xs + Diff2D(x0, y0);
540 MaskIterator yym = xm + Diff2D(x0, y0);
541 KernelIterator yk = ki - Diff2D(x0, y0);
542
543 int kernel_width, kernel_height;
544 kernel_width = x1 - x0 + 1;
545 kernel_height = y1 - y0 + 1;
546 for(yy=0; yy<kernel_height; ++yy, ++yys.y, --yk.y, ++yym.y)
547 {
548 typename SrcIterator::row_iterator xxs = yys.rowIterator();
549 typename SrcIterator::row_iterator xxend = xxs + kernel_width;
550 typename MaskIterator::row_iterator xxm = yym.rowIterator();
551 typename KernelIterator::row_iterator xk = yk.rowIterator();
552
553 for(xx=0; xxs < xxend; ++xxs, --xk, ++xxm)
554 {
555 if(!am(xxm)) continue;
556
557 if(first)
558 {
559 sum = detail::RequiresExplicitCast<SumType>::cast(ak(xk) * src_acc(xxs));
560 ksum = ak(xk);
561 first = false;
562 }
563 else
564 {
565 sum = detail::RequiresExplicitCast<SumType>::cast(sum + ak(xk) * src_acc(xxs));
566 ksum += ak(xk);
567 }
568 }
569 }
570 // store average in destination pixel
571 if(ksum != NumericTraits<KSumType>::zero())
572 {
573 dest_acc.set(DestTraits::fromRealPromote(
574 detail::RequiresExplicitCast<SumType>::cast((norm / ksum) * sum)), xd);
575 }
576 }
577 }
578 }
579
580
581 template <class SrcIterator, class SrcAccessor,
582 class DestIterator, class DestAccessor,
583 class MaskIterator, class MaskAccessor,
584 class KernelIterator, class KernelAccessor>
585 inline void
586 normalizedConvolveImage(triple<SrcIterator, SrcIterator, SrcAccessor> src,
587 pair<MaskIterator, MaskAccessor> mask,
588 pair<DestIterator, DestAccessor> dest,
589 tuple5<KernelIterator, KernelAccessor, Diff2D, Diff2D,
590 BorderTreatmentMode> kernel)
591 {
592 normalizedConvolveImage(src.first, src.second, src.third,
593 mask.first, mask.second,
594 dest.first, dest.second,
595 kernel.first, kernel.second, kernel.third,
596 kernel.fourth, kernel.fifth);
597 }
598
599 template <class T1, class S1,
600 class T2, class S2,
601 class TM, class SM,
602 class T3>
603 inline void
604 normalizedConvolveImage(MultiArrayView<2, T1, S1> const & src,
605 MultiArrayView<2, TM, SM> const & mask,
606 MultiArrayView<2, T2, S2> dest,
607 Kernel2D<T3> const & kernel)
608 {
609 vigra_precondition(src.shape() == mask.shape() && src.shape() == dest.shape(),
610 "normalizedConvolveImage(): shape mismatch between input and output.");
611 normalizedConvolveImage(srcImageRange(src),
612 maskImage(mask),
613 destImage(dest),
614 kernel2d(kernel));
615 }
616
617 /** \brief Deprecated name of 2-dimensional normalized convolution, i.e. convolution with a mask image.
618
619 See \ref normalizedConvolveImage() for documentation.
620
621 <b> Declarations:</b>
622
623 pass 2D array views:
624 \code
625 namespace vigra {
626 template <class SrcIterator, class SrcAccessor,
627 class MaskIterator, class MaskAccessor,
628 class DestIterator, class DestAccessor,
629 class KernelIterator, class KernelAccessor>
630 void
631 convolveImageWithMask(SrcIterator src_ul, SrcIterator src_lr, SrcAccessor src_acc,
632 MaskIterator mul, MaskAccessor am,
633 DestIterator dest_ul, DestAccessor dest_acc,
634 KernelIterator ki, KernelAccessor ak,
635 Diff2D kul, Diff2D klr, BorderTreatmentMode border);
636 }
637 \endcode
638
639 \deprecatedAPI{convolveImageWithMask}
640 pass \ref ImageIterators and \ref DataAccessors :
641 \code
642 namespace vigra {
643 template <class SrcIterator, class SrcAccessor,
644 class MaskIterator, class MaskAccessor,
645 class DestIterator, class DestAccessor,
646 class KernelIterator, class KernelAccessor>
647 void
648 convolveImageWithMask(SrcIterator src_ul, SrcIterator src_lr, SrcAccessor src_acc,
649 MaskIterator mul, MaskAccessor am,
650 DestIterator dest_ul, DestAccessor dest_acc,
651 KernelIterator ki, KernelAccessor ak,
652 Diff2D kul, Diff2D klr, BorderTreatmentMode border);
653 }
654 \endcode
655 use argument objects in conjunction with \ref ArgumentObjectFactories :
656 \code
657 namespace vigra {
658 template <class SrcIterator, class SrcAccessor,
659 class MaskIterator, class MaskAccessor,
660 class DestIterator, class DestAccessor,
661 class KernelIterator, class KernelAccessor>
662 void convolveImageWithMask(triple<SrcIterator, SrcIterator, SrcAccessor> src,
663 pair<MaskIterator, MaskAccessor> mask,
664 pair<DestIterator, DestAccessor> dest,
665 tuple5<KernelIterator, KernelAccessor, Diff2D, Diff2D,
666 BorderTreatmentMode> kernel);
667 }
668 \endcode
669 \deprecatedEnd
670 */
671 doxygen_overloaded_function(template <...> void convolveImageWithMask)
672
673 template <class SrcIterator, class SrcAccessor,
674 class DestIterator, class DestAccessor,
675 class MaskIterator, class MaskAccessor,
676 class KernelIterator, class KernelAccessor>
677 inline void
678 convolveImageWithMask(SrcIterator src_ul, SrcIterator src_lr, SrcAccessor src_acc,
679 MaskIterator mul, MaskAccessor am,
680 DestIterator dest_ul, DestAccessor dest_acc,
681 KernelIterator ki, KernelAccessor ak,
682 Diff2D kul, Diff2D klr, BorderTreatmentMode border)
683 {
684 normalizedConvolveImage(src_ul, src_lr, src_acc,
685 mul, am,
686 dest_ul, dest_acc,
687 ki, ak, kul, klr, border);
688 }
689
690 template <class SrcIterator, class SrcAccessor,
691 class DestIterator, class DestAccessor,
692 class MaskIterator, class MaskAccessor,
693 class KernelIterator, class KernelAccessor>
694 inline
695 void convolveImageWithMask(
696 triple<SrcIterator, SrcIterator, SrcAccessor> src,
697 pair<MaskIterator, MaskAccessor> mask,
698 pair<DestIterator, DestAccessor> dest,
699 tuple5<KernelIterator, KernelAccessor, Diff2D, Diff2D,
700 BorderTreatmentMode> kernel)
701 {
702 normalizedConvolveImage(src.first, src.second, src.third,
703 mask.first, mask.second,
704 dest.first, dest.second,
705 kernel.first, kernel.second, kernel.third,
706 kernel.fourth, kernel.fifth);
707 }
708
709 //@}
710
711 /********************************************************/
712 /* */
713 /* Kernel2D */
714 /* */
715 /********************************************************/
716
717 /** \brief Generic 2 dimensional convolution kernel.
718
719 This kernel may be used for convolution of 2 dimensional signals.
720
721 Convolution functions access the kernel via an ImageIterator
722 which they get by calling \ref center(). This iterator
723 points to the center of the kernel. The kernel's size is given by its upperLeft()
724 (upperLeft().x <= 0, upperLeft().y <= 0)
725 and lowerRight() (lowerRight().x >= 0, lowerRight().y >= 0) methods.
726 The desired border treatment mode is returned by borderTreatment().
727
728 The different init functions create a kernel with the specified
729 properties. The requirements for the kernel's value_type depend
730 on the init function used. At least NumericTraits must be defined.
731
732 <b> Usage:</b>
733
734 <b>\#include</b> <vigra/stdconvolution.hxx><br>
735 Namespace: vigra
736
737 \code
738 MultiArray<2, float> src(w,h), dest(w,h);
739 ...
740
741 // define horizontal Sobel filter
742 vigra::Kernel2D<float> sobel;
743 sobel.initExplicitly(Diff2D(-1,-1), Diff2D(1,1)) = // upper left and lower right
744 0.125, 0.0, -0.125,
745 0.25, 0.0, -0.25,
746 0.125, 0.0, -0.125;
747
748 convolveImage(src, dest, sobel);
749 \endcode
750
751 <b> Required Interface:</b>
752
753 \code
754 value_type v = NumericTraits<value_type>::one();
755 \endcode
756
757 See also the init functions.
758 */
759 template <class ARITHTYPE = double>
760 class Kernel2D
761 {
762 public:
763 /** the kernel's value type
764 */
765 typedef ARITHTYPE value_type;
766
767 /** 2D random access iterator over the kernel's values
768 */
769 typedef typename BasicImage<value_type>::traverser Iterator;
770
771 /** const 2D random access iterator over the kernel's values
772 */
773 typedef typename BasicImage<value_type>::const_traverser ConstIterator;
774
775 /** the kernel's accessor
776 */
777 typedef typename BasicImage<value_type>::Accessor Accessor;
778
779 /** the kernel's const accessor
780 */
781 typedef typename BasicImage<value_type>::ConstAccessor ConstAccessor;
782
783 struct InitProxy
784 {
785 typedef typename
786 BasicImage<value_type>::ScanOrderIterator Iterator;
787
788 InitProxy(Iterator i, int count, value_type & norm)
789 : iter_(i), base_(i),
790 count_(count), sum_(count),
791 norm_(norm)
792 {}
793
794 ~InitProxy()
795 {
796 vigra_precondition(count_ == 1 || count_ == sum_,
797 "Kernel2D::initExplicitly(): "
798 "Too few init values.");
799 }
800
801 InitProxy & operator,(value_type const & v)
802 {
803 if(count_ == sum_) norm_ = *iter_;
804
805 --count_;
806 vigra_precondition(count_ > 0,
807 "Kernel2D::initExplicitly(): "
808 "Too many init values.");
809
810 norm_ += v;
811
812 ++iter_;
813 *iter_ = v;
814
815 return *this;
816 }
817
818 Iterator iter_, base_;
819 int count_, sum_;
820 value_type & norm_;
821 };
822
823 static value_type one() { return NumericTraits<value_type>::one(); }
824
825 /** Default constructor.
826 Creates a kernel of size 1x1 which would copy the signal
827 unchanged.
828 */
829 Kernel2D()
830 : kernel_(1, 1, one()),
831 left_(0, 0),
832 right_(0, 0),
833 norm_(one()),
834 border_treatment_(BORDER_TREATMENT_REFLECT)
835 {}
836
837 /** Copy constructor.
838 */
839 Kernel2D(Kernel2D const & k)
840 : kernel_(k.kernel_),
841 left_(k.left_),
842 right_(k.right_),
843 norm_(k.norm_),
844 border_treatment_(k.border_treatment_)
845 {}
846
847 /** Copy assignment.
848 */
849 Kernel2D & operator=(Kernel2D const & k)
850 {
851 if(this != &k)
852 {
853 kernel_ = k.kernel_;
854 left_ = k.left_;
855 right_ = k.right_;
856 norm_ = k.norm_;
857 border_treatment_ = k.border_treatment_;
858 }
859 return *this;
860 }
861
862 /** Initialization.
863 This initializes the kernel with the given constant. The norm becomes
864 v*width()*height().
865
866 Instead of a single value an initializer list of length width()*height()
867 can be used like this:
868
869 \code
870 vigra::Kernel2D<float> binom;
871
872 binom.initExplicitly(Diff2D(-1,-1), Diff2D(1,1)) =
873 0.0625, 0.125, 0.0625,
874 0.125, 0.25, 0.125,
875 0.0625, 0.125, 0.0625;
876 \endcode
877
878 In this case, the norm will be set to the sum of the init values.
879 An initializer list of wrong length will result in a run-time error.
880 */
881 InitProxy operator=(value_type const & v)
882 {
883 int size = (right_.x - left_.x + 1) *
884 (right_.y - left_.y + 1);
885 kernel_ = v;
886 norm_ = (double)size*v;
887
888 return InitProxy(kernel_.begin(), size, norm_);
889 }
890
891 /** Destructor.
892 */
893 ~Kernel2D()
894 {}
895
896 /** Init the 2D kernel as the cartesian product of two 1D kernels
897 of type \ref Kernel1D. The norm becomes the product of the two original
898 norms.
899
900 <b> Required Interface:</b>
901
902 The kernel's value_type must be a linear algebra.
903
904 \code
905 vigra::Kernel2D<...>::value_type v;
906 v = v * v;
907 \endcode
908 */
909 void initSeparable(Kernel1D<value_type> const & kx,
910 Kernel1D<value_type> const & ky)
911 {
912 left_ = Diff2D(kx.left(), ky.left());
913 right_ = Diff2D(kx.right(), ky.right());
914 int w = right_.x - left_.x + 1;
915 int h = right_.y - left_.y + 1;
916 kernel_.resize(w, h);
917
918 norm_ = kx.norm() * ky.norm();
919
920 typedef typename Kernel1D<value_type>::const_iterator KIter;
921 typename Kernel1D<value_type>::Accessor ka;
922
923 KIter kiy = ky.center() + left_.y;
924 Iterator iy = center() + left_;
925
926 for(int y=left_.y; y<=right_.y; ++y, ++kiy, ++iy.y)
927 {
928 KIter kix = kx.center() + left_.x;
929 Iterator ix = iy;
930 for(int x=left_.x; x<=right_.x; ++x, ++kix, ++ix.x)
931 {
932 *ix = ka(kix) * ka(kiy);
933 }
934 }
935 }
936
937 /** Init the 2D kernel as the cartesian product of two 1D kernels
938 given explicitly by iterators and sizes. The norm becomes the
939 sum of the resulting kernel values.
940
941 <b> Required Interface:</b>
942
943 The kernel's value_type must be a linear algebra.
944
945 \code
946 vigra::Kernel2D<...>::value_type v;
947 v = v * v;
948 v += v;
949 \endcode
950
951 <b> Preconditions:</b>
952
953 \code
954 xleft <= 0;
955 xright >= 0;
956 yleft <= 0;
957 yright >= 0;
958 \endcode
959 */
960 template <class KernelIterator>
961 void initSeparable(KernelIterator kxcenter, int xleft, int xright,
962 KernelIterator kycenter, int yleft, int yright)
963 {
964 vigra_precondition(xleft <= 0 && yleft <= 0,
965 "Kernel2D::initSeparable(): left borders must be <= 0.");
966 vigra_precondition(xright >= 0 && yright >= 0,
967 "Kernel2D::initSeparable(): right borders must be >= 0.");
968
969 left_ = Point2D(xleft, yleft);
970 right_ = Point2D(xright, yright);
971
972 int w = right_.x - left_.x + 1;
973 int h = right_.y - left_.y + 1;
974 kernel_.resize(w, h);
975
976 KernelIterator kiy = kycenter + left_.y;
977 Iterator iy = center() + left_;
978
979 for(int y=left_.y; y<=right_.y; ++y, ++kiy, ++iy.y)
980 {
981 KernelIterator kix = kxcenter + left_.x;
982 Iterator ix = iy;
983 for(int x=left_.x; x<=right_.x; ++x, ++kix, ++ix.x)
984 {
985 *ix = *kix * *kiy;
986 }
987 }
988
989 typename BasicImage<value_type>::iterator i = kernel_.begin();
990 typename BasicImage<value_type>::iterator iend = kernel_.end();
991 norm_ = *i;
992 ++i;
993
994 for(; i!= iend; ++i)
995 {
996 norm_ += *i;
997 }
998 }
999
1000 /** \brief Init as a 2D box filter with given radius.
1001
1002 The function returns a reference to the kernel.
1003 */
1004 void initAveraging(int radius)
1005 {
1006 Kernel1D<value_type> avg;
1007 avg.initAveraging(radius);
1008 return initSeparable(avg, avg);
1009 }
1010
1011 /** \brief Init as a 2D Gaussian function with given standard deviation and norm.
1012
1013 The function returns a reference to the kernel.
1014 */
1015 void initGaussian(double std_dev, value_type norm)
1016 {
1017 Kernel1D<value_type> gauss;
1018 gauss.initGaussian(std_dev, norm);
1019 return initSeparable(gauss, gauss);
1020 }
1021
1022 /** \brief Init as a 2D Gaussian function with given standard deviation and unit norm.
1023
1024 The function returns a reference to the kernel.
1025 */
1026 void initGaussian(double std_dev)
1027 {
1028 return initGaussian(std_dev, NumericTraits<value_type>::one());
1029 }
1030
1031 /** Init the 2D kernel as a circular averaging filter. The norm will be
1032 calculated as
1033 <TT>NumericTraits<value_type>::one() / (number of non-zero kernel values)</TT>.
1034 The kernel's value_type must be a linear space.
1035
1036 <b> Required Interface:</b>
1037
1038 \code
1039 value_type v = vigra::NumericTraits<value_type>::one();
1040
1041 double d;
1042 v = d * v;
1043 \endcode
1044
1045 <b> Precondition:</b>
1046
1047 \code
1048 radius > 0;
1049 \endcode
1050 */
1051 void initDisk(int radius)
1052 {
1053 vigra_precondition(radius > 0,
1054 "Kernel2D::initDisk(): radius must be > 0.");
1055
1056 left_ = Point2D(-radius, -radius);
1057 right_ = Point2D(radius, radius);
1058 int w = right_.x - left_.x + 1;
1059 int h = right_.y - left_.y + 1;
1060 kernel_.resize(w, h);
1061 norm_ = NumericTraits<value_type>::one();
1062
1063 kernel_ = NumericTraits<value_type>::zero();
1064 double count = 0.0;
1065
1066 Iterator k = center();
1067 double r2 = (double)radius*radius;
1068
1069 int i;
1070 for(i=0; i<= radius; ++i)
1071 {
1072 double r = (double) i - 0.5;
1073 int w = (int)(VIGRA_CSTD::sqrt(r2 - r*r) + 0.5);
1074 for(int j=-w; j<=w; ++j)
1075 {
1076 k(j, i) = NumericTraits<value_type>::one();
1077 k(j, -i) = NumericTraits<value_type>::one();
1078 count += (i != 0) ? 2.0 : 1.0;
1079 }
1080 }
1081
1082 count = 1.0 / count;
1083
1084 for(int y=-radius; y<=radius; ++y)
1085 {
1086 for(int x=-radius; x<=radius; ++x)
1087 {
1088 k(x,y) = count * k(x,y);
1089 }
1090 }
1091 }
1092
1093 /** Init the kernel by an explicit initializer list.
1094 The upper left and lower right corners (inclusive) of the kernel must be passed
1095 either as <tt>Shape2</tt> or <tt>Diff2D</tt> objects. A comma-separated initializer
1096 list for the kernel's weights is given after the assignment operator like this:
1097
1098 \code
1099 // define horizontal Sobel filter
1100 vigra::Kernel2D<float> sobel;
1101
1102 sobel.initExplicitly(Diff2D(-1,-1), Diff2D(1,1)) =
1103 0.125, 0.0, -0.125,
1104 0.25, 0.0, -0.25,
1105 0.125, 0.0, -0.125;
1106 \endcode
1107
1108 The norm is set to the sum of the initializer values. If the wrong number of
1109 values is given, a run-time error results. It is, however, possible to give
1110 just one initializer. This creates an averaging filter with the given constant:
1111
1112 \code
1113 vigra::Kernel2D<float> average3x3;
1114
1115 average3x3.initExplicitly(Shape2(-1,-1), Shape2(1,1)) = 1.0/9.0;
1116 \endcode
1117
1118 Here, the norm is set to value*width()*height().
1119
1120 <b> Preconditions:</b>
1121
1122 \code
1123 1. upperleft.x <= 0;
1124 2. upperleft.y <= 0;
1125 3. lowerright.x >= 0;
1126 4. lowerright.y >= 0;
1127 5. the number of values in the initializer list
1128 is 1 or equals the size of the kernel.
1129 \endcode
1130 */
1131 Kernel2D & initExplicitly(Shape2 const & upperleft, Shape2 const & lowerright)
1132 {
1133 vigra_precondition(upperleft[0] <= 0 && upperleft[1] <= 0,
1134 "Kernel2D::initExplicitly(): left borders must be <= 0.");
1135 vigra_precondition(lowerright[0] >= 0 && lowerright[1] >= 0,
1136 "Kernel2D::initExplicitly(): right borders must be >= 0.");
1137
1138 left_ = Point2D(upperleft[0], upperleft[1]);
1139 right_ = Point2D(lowerright[0], lowerright[1]);
1140
1141 int w = right_.x - left_.x + 1;
1142 int h = right_.y - left_.y + 1;
1143 kernel_.resize(w, h);
1144
1145 return *this;
1146 }
1147
1148 Kernel2D & initExplicitly(Diff2D const & upperleft, Diff2D const & lowerright)
1149 {
1150 return initExplicitly(Shape2(upperleft), Shape2(lowerright));
1151 }
1152
1153 /** Coordinates of the upper left corner of the kernel.
1154 */
1155 Point2D upperLeft() const { return left_; }
1156
1157 /** Coordinates of the lower right corner of the kernel.
1158 */
1159 Point2D lowerRight() const { return right_; }
1160
1161 /** Width of the kernel.
1162 */
1163 int width() const { return right_.x - left_.x + 1; }
1164
1165 /** Height of the kernel.
1166 */
1167 int height() const { return right_.y - left_.y + 1; }
1168
1169 /** ImageIterator that points to the center of the kernel (coordinate (0,0)).
1170 */
1171 Iterator center() { return kernel_.upperLeft() - left_; }
1172
1173 /** ImageIterator that points to the center of the kernel (coordinate (0,0)).
1174 */
1175 ConstIterator center() const { return kernel_.upperLeft() - left_; }
1176
1177 /** Access kernel entry at given position.
1178 */
1179 value_type & operator()(int x, int y)
1180 { return kernel_[Diff2D(x,y) - left_]; }
1181
1182 /** Read kernel entry at given position.
1183 */
1184 value_type operator()(int x, int y) const
1185 { return kernel_[Diff2D(x,y) - left_]; }
1186
1187 /** Access kernel entry at given position.
1188 */
1189 value_type & operator[](Diff2D const & d)
1190 { return kernel_[d - left_]; }
1191
1192 /** Read kernel entry at given position.
1193 */
1194 value_type operator[](Diff2D const & d) const
1195 { return kernel_[d - left_]; }
1196
1197 /** Norm of the kernel (i.e. sum of its elements).
1198 */
1199 value_type norm() const { return norm_; }
1200
1201 /** The kernels default accessor.
1202 */
1203 Accessor accessor() { return Accessor(); }
1204
1205 /** The kernels default const accessor.
1206 */
1207 ConstAccessor accessor() const { return ConstAccessor(); }
1208
1209 /** Normalize the kernel to the given value. (The norm is the sum of all kernel
1210 elements.) The kernel's value_type must be a division algebra or
1211 algebraic field.
1212
1213 <b> Required Interface:</b>
1214
1215 \code
1216 value_type v = vigra::NumericTraits<value_type>::one(); // if norm is not
1217 // given explicitly
1218
1219 v += v;
1220 v = v * v;
1221 v = v / v;
1222 \endcode
1223 */
1224 void normalize(value_type norm)
1225 {
1226 typename BasicImage<value_type>::iterator i = kernel_.begin();
1227 typename BasicImage<value_type>::iterator iend = kernel_.end();
1228 typename NumericTraits<value_type>::RealPromote sum = *i;
1229 ++i;
1230
1231 for(; i!= iend; ++i)
1232 {
1233 sum += *i;
1234 }
1235
1236 sum = norm / sum;
1237 i = kernel_.begin();
1238 for(; i != iend; ++i)
1239 {
1240 *i = *i * sum;
1241 }
1242
1243 norm_ = norm;
1244 }
1245
1246 /** Normalize the kernel to norm 1.
1247 */
1248 void normalize()
1249 {
1250 normalize(one());
1251 }
1252
1253 /** current border treatment mode
1254 */
1255 BorderTreatmentMode borderTreatment() const
1256 { return border_treatment_; }
1257
1258 /** Set border treatment mode.
1259 Only <TT>BORDER_TREATMENT_CLIP</TT> and <TT>BORDER_TREATMENT_AVOID</TT> are currently
1260 allowed.
1261 */
1262 void setBorderTreatment( BorderTreatmentMode new_mode)
1263 {
1264 vigra_precondition((new_mode == BORDER_TREATMENT_CLIP ||
1265 new_mode == BORDER_TREATMENT_AVOID ||
1266 new_mode == BORDER_TREATMENT_REFLECT ||
1267 new_mode == BORDER_TREATMENT_REPEAT ||
1268 new_mode == BORDER_TREATMENT_WRAP),
1269 "convolveImage():\n"
1270 " Border treatment must be one of follow treatments:\n"
1271 " - BORDER_TREATMENT_CLIP\n"
1272 " - BORDER_TREATMENT_AVOID\n"
1273 " - BORDER_TREATMENT_REFLECT\n"
1274 " - BORDER_TREATMENT_REPEAT\n"
1275 " - BORDER_TREATMENT_WRAP\n");
1276
1277 border_treatment_ = new_mode;
1278 }
1279
1280
1281 private:
1282 BasicImage<value_type> kernel_;
1283 Point2D left_, right_;
1284 value_type norm_;
1285 BorderTreatmentMode border_treatment_;
1286 };
1287
1288 /**************************************************************/
1289 /* */
1290 /* Argument object factories for Kernel2D */
1291 /* */
1292 /* (documentation: see vigra/convolution.hxx) */
1293 /* */
1294 /**************************************************************/
1295
1296 template <class KernelIterator, class KernelAccessor>
1297 inline
1298 tuple5<KernelIterator, KernelAccessor, Diff2D, Diff2D, BorderTreatmentMode>
1299 kernel2d(KernelIterator ik, KernelAccessor ak, Diff2D kul, Diff2D klr,
1300 BorderTreatmentMode border)
1301
1302 {
1303 return
1304 tuple5<KernelIterator, KernelAccessor, Diff2D, Diff2D, BorderTreatmentMode> (
1305 ik, ak, kul, klr, border);
1306 }
1307
1308 template <class T>
1309 inline
1310 tuple5<typename Kernel2D<T>::ConstIterator,
1311 typename Kernel2D<T>::ConstAccessor,
1312 Diff2D, Diff2D, BorderTreatmentMode>
1313 kernel2d(Kernel2D<T> const & k)
1314
1315 {
1316 return
1317 tuple5<typename Kernel2D<T>::ConstIterator,
1318 typename Kernel2D<T>::ConstAccessor,
1319 Diff2D, Diff2D, BorderTreatmentMode>(
1320 k.center(),
1321 k.accessor(),
1322 k.upperLeft(), k.lowerRight(),
1323 k.borderTreatment());
1324 }
1325
1326 template <class T>
1327 inline
1328 tuple5<typename Kernel2D<T>::ConstIterator,
1329 typename Kernel2D<T>::ConstAccessor,
1330 Diff2D, Diff2D, BorderTreatmentMode>
1331 kernel2d(Kernel2D<T> const & k, BorderTreatmentMode border)
1332
1333 {
1334 return
1335 tuple5<typename Kernel2D<T>::ConstIterator,
1336 typename Kernel2D<T>::ConstAccessor,
1337 Diff2D, Diff2D, BorderTreatmentMode>(
1338 k.center(),
1339 k.accessor(),
1340 k.upperLeft(), k.lowerRight(),
1341 border);
1342 }
1343
1344
1345 } // namespace vigra
1346
1347 #endif // VIGRA_STDCONVOLUTION_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)