/************************************************************************/
/*                                                                      */
/*                 Copyright 2004 by Ullrich Koethe                     */
/*                                                                      */
/*    This file is part of the VIGRA computer vision library.           */
/*    The VIGRA Website is                                              */
/*        http://hci.iwr.uni-heidelberg.de/vigra/                       */
/*    Please direct questions, bug reports, and contributions to        */
/*        ullrich.koethe@iwr.uni-heidelberg.de    or                    */
/*        vigra@informatik.uni-hamburg.de                               */
/*                                                                      */
/*    Permission is hereby granted, free of charge, to any person       */
/*    obtaining a copy of this software and associated documentation    */
/*    files (the "Software"), to deal in the Software without           */
/*    restriction, including without limitation the rights to use,      */
/*    copy, modify, merge, publish, distribute, sublicense, and/or      */
/*    sell copies of the Software, and to permit persons to whom the    */
/*    Software is furnished to do so, subject to the following          */
/*    conditions:                                                       */
/*                                                                      */
/*    The above copyright notice and this permission notice shall be    */
/*    included in all copies or substantial portions of the             */
/*    Software.                                                         */
/*                                                                      */
/*    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND    */
/*    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES   */
/*    OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND          */
/*    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT       */
/*    HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,      */
/*    WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING      */
/*    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR     */
/*    OTHER DEALINGS IN THE SOFTWARE.                                   */
/*                                                                      */
/************************************************************************/

#include <iostream>
#include <cmath>
#include <cstdio>
#include "vigra/convolution.hxx"
#include "vigra/unittest.hxx"
#include "vigra/stdimage.hxx"
#include "vigra/multi_array.hxx"
#include "vigra/impex.hxx"
#include "vigra/combineimages.hxx"
#include "vigra/resampling_convolution.hxx"
#include "vigra/imagecontainer.hxx"
#include "vigra/tv_filter.hxx"
#include "tv_test_data.hxx"

using namespace vigra;

vigra::DImage getSymmetricLine(bool transposed = false){
    vigra::DImage src;

    if(transposed)
        src.resize(1, 40);
    else
        src.resize(40, 1);

    vigra::DImage::Accessor acc_src = src.accessor();
    vigra::DImage::iterator iter_src = src.begin();

    int i = 0;
    for ( ; i < 20 ; i++, iter_src++){
      acc_src.set(i + 0.25, iter_src);
    }
    i--;
    for ( ; i >= 0 ; i--, iter_src++){
      acc_src.set(i + 0.25, iter_src);
    }
    return src;
}
vigra::DImage getSymmetricImage(){
    vigra::DImage src(10, 6);

    vigra::DImage::Accessor acc_src = src.accessor();
    vigra::DImage::iterator iter_src = src.begin();

    int i = 0;
    for(int y = 0; y < 6; y++){
        int x = 0;
        for ( ; x < 5 ; i++, x++, iter_src++){
            acc_src.set(i + 0.25, iter_src);
        }
        i--;
        for ( ; x < 10 ; i--, x++, iter_src++){
            acc_src.set(i + 0.25, iter_src);
        }
        (y%2 == 0)? i += 3 : i+= 2;
    }
    return src;
}

vigra::DImage getUnsymmetricImage(){
    vigra::DImage src(10, 6);

    vigra::DImage::Accessor acc_src = src.accessor();
    vigra::DImage::iterator iter_src = src.begin();

    int i = 0;
    for(int y = 0; y < 6; y++){
        for (int x = 0 ; x < 10 ; i++, x++, iter_src++){
            acc_src.set(i + 0.25, iter_src);
        }
        (y%2 == 0)? i++ : i+= 2;
    }
    return src;
}

struct ConvolutionTest
{
    typedef vigra::DImage Image;
    typedef vigra::MultiArrayView<2, double> View;
    typedef vigra::DRGBImage RGBImage;

    ConvolutionTest()
    : constimg(5,5),
      rampimg(5,1),
      sym_image(getSymmetricImage()),
      unsym_image(getUnsymmetricImage())
    {
        constimg.init(1.0);

        vigra::Kernel1D<double> binom1;
        binom1.initBinomial(1);
        sym_kernel.initSeparable(binom1, binom1);

        unsym_kernel.initExplicitly(Diff2D(-1,-1), Diff2D(1,1)) = 1,   2,   4,
                                                                  5,   11,  3,
                                                                  6,   8,   7 ;
        unsym_kernel.normalize(1);

        line_kernel.initExplicitly(Diff2D(-2,0), Diff2D(2,0)) = 1, 4,   12,   4, 1 ;
        line_kernel.normalize(1);

        ImageImportInfo info("lenna128.xv");

        lenna.resize(info.width(), info.height());
        importImage(info, destImage(lenna));

        Image::ScanOrderIterator i = rampimg.begin();
        Image::ScanOrderIterator end = rampimg.end();
        Image::Accessor acc = rampimg.accessor();

        for(int k=0; i != end; ++i, ++k)
        {
            acc.set(k, i);
        }
    }

    void borderCopyTest()
    {
        static const int size = 6, kleft = -2, kright = 3,
                         ksize = kright - kleft,
                         outsize = size + ksize;

        int data[size] = {1, 2, 3, 4, 5, 6};
        int out[outsize];

        vigra::StandardValueAccessor<int> a;

        // copy entire array
        {
            vigra::detail::copyLineWithBorderTreatment(data, data+size, a, out, a,
                                               0, size, kleft, kright, vigra::BORDER_TREATMENT_WRAP);
            int r[] = {4, 5, 6, 1, 2, 3, 4, 5, 6, 1, 2};
            shouldEqualSequence(out, out+outsize, r);
        }
        {
            vigra::detail::copyLineWithBorderTreatment(data, data+size, a, out, a,
                                               0, size, kleft, kright, vigra::BORDER_TREATMENT_REFLECT);
            int r[] = {4, 3, 2, 1, 2, 3, 4, 5, 6, 5, 4};
            shouldEqualSequence(out, out+outsize, r);
        }
        {
            vigra::detail::copyLineWithBorderTreatment(data, data+size, a, out, a,
                                               0, size, kleft, kright, vigra::BORDER_TREATMENT_REPEAT);
            int r[] = {1, 1, 1, 1, 2, 3, 4, 5, 6, 6, 6};
            shouldEqualSequence(out, out+outsize, r);
        }
        {
            vigra::detail::copyLineWithBorderTreatment(data, data+size, a, out, a,
                                               0, size, kleft, kright, vigra::BORDER_TREATMENT_AVOID);
            int r[] = {1, 2, 3, 4, 5, 6};
            shouldEqualSequence(out+kright, out+outsize+kleft, r);
        }
        {
            vigra::detail::copyLineWithBorderTreatment(data, data+size, a, out, a,
                                               0, size, kleft, kright, vigra::BORDER_TREATMENT_ZEROPAD);
            int r[] = {0, 0, 0, 1, 2, 3, 4, 5, 6, 0, 0};
            shouldEqualSequence(out, out+outsize, r);
        }

        // copy with partial border treatment
        {
            vigra::detail::copyLineWithBorderTreatment(data, data+size, a, out, a,
                                               1, size-1, kleft, kright, vigra::BORDER_TREATMENT_WRAP);
            int r[] = {5, 6, 1, 2, 3, 4, 5, 6, 1};
            shouldEqualSequence(out, out+outsize-2, r);
        }
        {
            vigra::detail::copyLineWithBorderTreatment(data, data+size, a, out, a,
                                               1, size-1, kleft, kright, vigra::BORDER_TREATMENT_REFLECT);
            int r[] = {3, 2, 1, 2, 3, 4, 5, 6, 5};
            shouldEqualSequence(out, out+outsize-2, r);
        }
        {
            vigra::detail::copyLineWithBorderTreatment(data, data+size, a, out, a,
                                               1, size-1, kleft, kright, vigra::BORDER_TREATMENT_REPEAT);
            int r[] = {1, 1, 1, 2, 3, 4, 5, 6, 6};
            shouldEqualSequence(out, out+outsize-2, r);
        }
        {
            vigra::detail::copyLineWithBorderTreatment(data, data+size, a, out, a,
                                               1, size-1, kleft, kright, vigra::BORDER_TREATMENT_AVOID);
            int r[] = {2, 3, 4, 5};
            shouldEqualSequence(out+kright, out+outsize+kleft-2, r);
        }
        {
            vigra::detail::copyLineWithBorderTreatment(data, data+size, a, out, a,
                                               1, size-1, kleft, kright, vigra::BORDER_TREATMENT_ZEROPAD);
            int r[] = {0, 0, 1, 2, 3, 4, 5, 6, 0};
            shouldEqualSequence(out, out+outsize-2, r);
        }

        // copy interior, no border treatment necessary
        {
            vigra::detail::copyLineWithBorderTreatment(data, data+size, a, out, a,
                                               kright, size+kleft, kleft, kright, vigra::BORDER_TREATMENT_WRAP);
            int r[] = {1, 2, 3, 4, 5, 6};
            shouldEqualSequence(out, out+outsize-ksize, r);
        }
        {
            vigra::detail::copyLineWithBorderTreatment(data, data+size, a, out, a,
                                               kright, size+kleft, kleft, kright, vigra::BORDER_TREATMENT_REFLECT);
            int r[] = {1, 2, 3, 4, 5, 6};
            shouldEqualSequence(out, out+outsize-ksize, r);
        }
        {
            vigra::detail::copyLineWithBorderTreatment(data, data+size, a, out, a,
                                               kright, size+kleft, kleft, kright, vigra::BORDER_TREATMENT_REPEAT);
            int r[] = {1, 2, 3, 4, 5, 6};
            shouldEqualSequence(out, out+outsize-ksize, r);
        }
        {
            vigra::detail::copyLineWithBorderTreatment(data, data+size, a, out, a,
                                               kright, size+kleft, kleft, kright, vigra::BORDER_TREATMENT_AVOID);
            int r[] = {4};
            shouldEqualSequence(out+kright, out+outsize+kleft-ksize, r);
        }
        {
            vigra::detail::copyLineWithBorderTreatment(data, data+size, a, out, a,
                                               kright, size+kleft, kleft, kright, vigra::BORDER_TREATMENT_ZEROPAD);
            int r[] = {1, 2, 3, 4, 5, 6};
            shouldEqualSequence(out, out+outsize-ksize, r);
        }
    }

    void initExplicitlyTest()
    {
        vigra::Kernel1D<double> k;
        k.initExplicitly(-1,2) = 1,2,3,4;

        shouldEqual(k.left(), -1);
        shouldEqual(k.right(), 2);
        shouldEqual(k[-1], 1);
        shouldEqual(k[0], 2);
        shouldEqual(k[1], 3);
        shouldEqual(k[2], 4);

        k.initExplicitly(-2,1) = -2;
        shouldEqual(k.left(), -2);
        shouldEqual(k.right(), 1);
        shouldEqual(k[-2], -2);
        shouldEqual(k[-1], -2);
        shouldEqual(k[0], -2);
        shouldEqual(k[1], -2);

        try
        {
            k.initExplicitly(-1,1) = 1, 2;
            failTest("no exception thrown");
        }
        catch(vigra::PreconditionViolation & c)
        {
            std::string expected("\nPrecondition violation!\nKernel1D::initExplicitly(): Wrong number of init values.");
            std::string message(c.what());
            should(0 == expected.compare(message.substr(0,expected.size())));
        }

        try
        {
            k.initExplicitly(-1,1) = 1, 2, 3, 4;
            failTest("no exception thrown");
        }
        catch(vigra::PreconditionViolation & c)
        {
            std::string expected("\nPrecondition violation!\nKernel1D::initExplicitly(): Wrong number of init values.");
            std::string message(c.what());
            should(0 == expected.compare(message.substr(0,expected.size())));
        }
        try
        {
            k.initExplicitly(1,1) = 1, 2, 3, 4;
            failTest("no exception thrown");
        }
        catch(vigra::PreconditionViolation & c)
        {
            std::string expected("\nPrecondition violation!\nKernel1D::initExplicitly(): left border must be <= 0.");
            std::string message(c.what());
            should(0 == expected.compare(message.substr(0,expected.size())));
        }
        try
        {
            k.initExplicitly(-1,-1) = 1, 2, 3, 4;
            failTest("no exception thrown");
        }
        catch(vigra::PreconditionViolation & c)
        {
            std::string expected("\nPrecondition violation!\nKernel1D::initExplicitly(): right border must be >= 0.");
            std::string message(c.what());
            should(0 == expected.compare(message.substr(0,expected.size())));
        }

        double data[] = { 1,   2,   4,
                          5,   11,  3,
                          6,   8,   7 };
        BasicImage<double> kernel_image(3,3, data);
        Kernel2D<double> kernel_from_image;
        kernel_from_image.initExplicitly(kernel_image);
        unsym_kernel.normalize(kernel_from_image.norm());

        for(int y=-1; y<=1; ++y)
            for(int x=-1; x<=1; ++x)
                shouldEqualTolerance(unsym_kernel(x,y), kernel_from_image(x,y), 1e-14);

    }

    void simpleSharpeningTest()
    {
        Image dest_lenna(lenna);
        simpleSharpening(srcImageRange(lenna), destImage(dest_lenna), 0.8);

        Image dest_correct;
        ImageImportInfo info_correct("lenna_simple_sharpening_orig.xv");
        dest_correct.resize(info_correct.width(), info_correct.height());
        importImage(info_correct, destImage(dest_correct));

        Image::iterator i_dest = dest_lenna.begin();
        Image::iterator end_dest = dest_lenna.end();
        Image::Accessor acc_dest = dest_lenna.accessor();
        Image::iterator i_dest_correct = dest_correct.begin();
        Image::Accessor acc_dest_correct = dest_correct.accessor();

        for (; i_dest != end_dest; i_dest++, i_dest_correct++ ){
            shouldEqualTolerance(acc_dest(i_dest) , acc_dest_correct(i_dest_correct), 1e-15);
        }

        dest_lenna.init(0.0);
        simpleSharpening(View(lenna), View(dest_lenna), 0.8);

        using namespace multi_math;
        should(all(abs(View(dest_lenna) - View(dest_correct)) < 3e-13));
    }

    void gaussianSharpeningTest()
    {
        Image dest_lenna(lenna);
        gaussianSharpening(srcImageRange(lenna), destImage(dest_lenna), 3., 0.7);

        Image dest_correct;
        ImageImportInfo info_correct("lenna_gaussian_sharpening_orig.xv");
        dest_correct.resize(info_correct.width(), info_correct.height());
        importImage(info_correct, destImage(dest_correct));

        Image::iterator i_dest = dest_lenna.begin();
        Image::iterator end_dest = dest_lenna.end();
        Image::Accessor acc_dest = dest_lenna.accessor();
        Image::iterator i_dest_correct = dest_correct.begin();
        Image::Accessor acc_dest_correct = dest_correct.accessor();

        for (; i_dest != end_dest; i_dest++, i_dest_correct++ )
        {
            shouldEqualTolerance(acc_dest(i_dest) , acc_dest_correct(i_dest_correct), 1e-6);
        }

        dest_lenna.init(0.0);
        gaussianSharpening(View(lenna), View(dest_lenna), 3., 0.7);

        using namespace multi_math;
        should(all(abs(View(dest_lenna) - View(dest_correct)) < 1e-6));
    }

    void stdConvolutionTestOnConstImage()
    {
        Image tmp_clip(constimg);
        tmp_clip = 0.0;
        Image tmp_wrap(constimg);
        tmp_wrap = 0.0;
        Image tmp_avoid(constimg);
        tmp_avoid = 0.0;
        Image tmp_repeat(constimg);
        tmp_repeat = 0.0;
        Image tmp_reflect(constimg);
        tmp_reflect = 0.0;

        sym_kernel.setBorderTreatment(BORDER_TREATMENT_CLIP);
        convolveImage(View(constimg), View(tmp_clip), sym_kernel);

        sym_kernel.setBorderTreatment(BORDER_TREATMENT_AVOID);
        convolveImage(View(constimg), View(tmp_avoid), sym_kernel);

        sym_kernel.setBorderTreatment(BORDER_TREATMENT_WRAP);
        convolveImage(View(constimg), View(tmp_wrap), sym_kernel);

        sym_kernel.setBorderTreatment(BORDER_TREATMENT_REPEAT);
        convolveImage(View(constimg), View(tmp_repeat), sym_kernel);

        sym_kernel.setBorderTreatment(BORDER_TREATMENT_REFLECT);
        convolveImage(View(constimg), View(tmp_reflect), sym_kernel);

        Image::ScanOrderIterator i_src = constimg.begin();
        Image::ScanOrderIterator i_src_end = constimg.end();
        Image::ScanOrderIterator i_clip = tmp_clip.begin();
        Image::ScanOrderIterator i_avoid = tmp_avoid.begin();
        Image::ScanOrderIterator i_wrap = tmp_wrap.begin();
        Image::ScanOrderIterator i_repeat = tmp_repeat.begin();
        Image::ScanOrderIterator i_reflect = tmp_reflect.begin();
        Image::Accessor acc = constimg.accessor();

        for(int y = 0; i_src != i_src_end; y++){
            for(int x = 0; x < constimg.size().x; x++, ++i_src, ++i_clip, ++i_wrap, ++i_repeat, ++i_reflect, ++i_avoid){
                should(acc(i_src) == acc(i_clip));
                should(acc(i_src) == acc(i_wrap));
                should(acc(i_src) == acc(i_repeat));
                should(acc(i_src) == acc(i_reflect));
                if(x != 0 && y != 0 && x != 4 && y != 4){
                    should(acc(i_src) == acc(i_avoid));
                }else{
                    should(acc(i_avoid) == 0);
                }
            }
        }


        //@todo Import<->Export funktioniert nicht wie frueher???
//         Image dest_lenna(lenna);
//         convolveImage(srcImageRange(lenna), destImage(dest_lenna), kernel2d(sym_kernel, BORDER_TREATMENT_REFLECT));
//         exportImage(srcImageRange(dest_lenna), ImageExportInfo("lenna_convolve_128x120.xv"));

    }


    void stdConvolutionTestWithAvoid()
    {
        Image dest(sym_image);
        dest.init(42.1);

        convolveImage(srcImageRange(sym_image), destImage(dest), kernel2d(sym_kernel, BORDER_TREATMENT_AVOID));

        Image::Iterator i_dest_2D = dest.upperLeft();
        Image::ScanOrderIterator i_dest = dest.begin();
        Image::ScanOrderIterator i_dest_end = dest.end();
        Image::Accessor acc = dest.accessor();

        //Kontrollierung der Randbehandlung und ein paar Pixel
        //aus der Mitte.
        should (acc(i_dest_2D + Diff2D(1,1)) == 3);
        should (acc(i_dest_2D + Diff2D(8,2)) == 4.5);
        for(int y = 0; i_dest != i_dest_end; y++){
            for(int x = 0; x < dest.size().x; x++, ++i_dest){
                if(x == 0 || y == 0 || x == 9 || y == 9){
                    shouldEqual(acc(i_dest), 42.1);
                }
            }
        }

    }


    void stdConvolutionTestWithZeropad()
    {
        Image dest(sym_image.size()-Size2D(2,2)), ref(sym_image);
        dest.init(42.1);
        ref.init(33.2);

        convolveImage(srcImageRange(sym_image, Rect2D(Point2D(1,1), sym_image.size()-Size2D(2,2))),
                      destImage(dest), kernel2d(sym_kernel, BORDER_TREATMENT_ZEROPAD));

        initImageBorder(destImageRange(sym_image), 1, 0);
        convolveImage(srcImageRange(sym_image), destImage(ref), kernel2d(sym_kernel, BORDER_TREATMENT_AVOID));

        Image::Iterator i_dest_2D = dest.upperLeft();
        Image::Iterator i_dest_end = dest.lowerRight();
        Image::Iterator i_ref_2D = ref.upperLeft()+Size2D(1,1);
        Image::Accessor acc = dest.accessor();

        for(; i_dest_2D.y != i_dest_end.y; ++i_dest_2D.y, ++i_ref_2D.y)
        {
            Image::Iterator rd = i_dest_2D;
            Image::Iterator rr = i_ref_2D;
            for(; rd.x != i_dest_end.x; ++rd.x, ++rr.x)
            {
                shouldEqual(acc(rd), acc(rr));
            }
        }
    }

    void stdConvolutionTestWithClip()
    {
        Image dest(sym_image);
        dest.init(42.1);

        convolveImage(srcImageRange(sym_image), destImage(dest), kernel2d(unsym_kernel, BORDER_TREATMENT_CLIP));

        Image::Iterator i_dest_2D = dest.upperLeft();
        Image::Accessor acc = dest.accessor();

        //Kontrollierung der Randbehandlung und ein paar Pixel
        //aus der Mitte.
        shouldEqualTolerance (acc(i_dest_2D + Diff2D(0,0)), 0.88157,   1E-4);
        shouldEqualTolerance (acc(i_dest_2D + Diff2D(3,0)), 3.75,      1E-4);
        shouldEqualTolerance (acc(i_dest_2D + Diff2D(9,0)), 1.199999,  1E-4);
        shouldEqualTolerance (acc(i_dest_2D + Diff2D(9,2)), 3.5642913, 1E-4);
        shouldEqualTolerance (acc(i_dest_2D + Diff2D(9,5)), 7.5603463, 1E-4);
        shouldEqualTolerance (acc(i_dest_2D + Diff2D(3,5)), 10.225,    1E-4);
        shouldEqualTolerance (acc(i_dest_2D + Diff2D(0,5)), 7.6833271, 1E-4);
        shouldEqualTolerance (acc(i_dest_2D + Diff2D(0,2)), 3.3712039, 1E-4);

        shouldEqualTolerance (acc(i_dest_2D + Diff2D(3,2)), 6.05852,   1E-4);
        shouldEqualTolerance (acc(i_dest_2D + Diff2D(6,4)), 9.14363,   1E-4);
    }

    void stdConvolutionTestWithWrap()
    {
        Image dest(unsym_image);
        dest.init(42.1);

        convolveImage(srcImageRange(unsym_image), destImage(dest), kernel2d(unsym_kernel, BORDER_TREATMENT_WRAP));

        Image::Iterator i_dest_2D = dest.upperLeft();
        Image::Accessor acc = dest.accessor();

        //Kontrollierung der Randbehandlung und ein paar Pixel
        //aus der Mitte.
        shouldEqualTolerance (acc(i_dest_2D + Diff2D(0,0)), 30.2926,   1E-5);
        shouldEqualTolerance (acc(i_dest_2D + Diff2D(3,0)), 30.3138,   1E-5);
        shouldEqualTolerance (acc(i_dest_2D + Diff2D(9,0)), 33.7606,   1E-5);
        shouldEqualTolerance (acc(i_dest_2D + Diff2D(9,2)), 25.9309,   1E-5);
        shouldEqualTolerance (acc(i_dest_2D + Diff2D(9,5)), 50.25,     1E-5);
        shouldEqualTolerance (acc(i_dest_2D + Diff2D(3,5)), 46.8032,   1E-5);
        shouldEqualTolerance (acc(i_dest_2D + Diff2D(0,5)), 46.7819,   1E-5);
        shouldEqualTolerance (acc(i_dest_2D + Diff2D(0,2)), 22.4628,   1E-5);

        shouldEqualTolerance (acc(i_dest_2D + Diff2D(2,3)), 33.0798,   1E-5);
        shouldEqualTolerance (acc(i_dest_2D + Diff2D(6,4)), 48.4841,   1E-5);
    }

    void stdConvolutionTestWithReflect()
    {
        Image dest(unsym_image);
        dest.init(42.1);

        convolveImage(srcImageRange(unsym_image), destImage(dest), kernel2d(unsym_kernel, BORDER_TREATMENT_REFLECT));

        Image::Iterator i_dest_2D = dest.upperLeft();
        Image::Accessor acc = dest.accessor();

        //Kontrollierung der Randbehandlung und ein paar Pixel
        //aus der Mitte.
        shouldEqualTolerance (acc(i_dest_2D + Diff2D(0,0)), 7.35638,   1E-5);
        shouldEqualTolerance (acc(i_dest_2D + Diff2D(3,0)), 9.76064,   1E-5);
        shouldEqualTolerance (acc(i_dest_2D + Diff2D(9,0)), 15.25,     1E-5);
        shouldEqualTolerance (acc(i_dest_2D + Diff2D(9,2)), 27.9734,   1E-5);
        shouldEqualTolerance (acc(i_dest_2D + Diff2D(9,5)), 59.1436,   1E-5);
        shouldEqualTolerance (acc(i_dest_2D + Diff2D(3,5)), 53.6543,   1E-5);
        shouldEqualTolerance (acc(i_dest_2D + Diff2D(0,5)), 51.25,     1E-5);
        shouldEqualTolerance (acc(i_dest_2D + Diff2D(0,2)), 20.0798,   1E-5);

        shouldEqualTolerance (acc(i_dest_2D + Diff2D(2,3)), 33.0798,   1E-5);
        shouldEqualTolerance (acc(i_dest_2D + Diff2D(6,4)), 48.4841,   1E-5);
    }

    void stdConvolutionTestWithRepeat()
    {
        Image dest(unsym_image);
        dest.init(42.1);

        convolveImage(srcImageRange(unsym_image), destImage(dest), kernel2d(unsym_kernel, BORDER_TREATMENT_REPEAT));

        Image::Iterator i_dest_2D = dest.upperLeft();
        Image::Accessor acc = dest.accessor();

        //Kontrollierung der Randbehandlung und ein paar Pixel
        //aus der Mitte.
        shouldEqualTolerance (acc(i_dest_2D + Diff2D(0,0)), 2.14362,   1E-5);
        shouldEqualTolerance (acc(i_dest_2D + Diff2D(3,0)), 4.84574,   1E-5);
        shouldEqualTolerance (acc(i_dest_2D + Diff2D(9,0)), 10.5904,   1E-5);
        shouldEqualTolerance (acc(i_dest_2D + Diff2D(9,2)), 28.2287,   1E-5);
        shouldEqualTolerance (acc(i_dest_2D + Diff2D(9,5)), 61.0372,   1E-5);
        shouldEqualTolerance (acc(i_dest_2D + Diff2D(3,5)), 55.2926,   1E-5);
        shouldEqualTolerance (acc(i_dest_2D + Diff2D(0,5)), 52.5904,   1E-5);
        shouldEqualTolerance (acc(i_dest_2D + Diff2D(0,2)), 19.7819,   1E-5);

        shouldEqualTolerance (acc(i_dest_2D + Diff2D(2,3)), 33.0798,   1E-5);
        shouldEqualTolerance (acc(i_dest_2D + Diff2D(6,4)), 48.4841,   1E-5);
    }

    void stdConvolutionTestFromWrapWithReflect()
    {

        Image src_wrap(78, 1);
        Image src_reflect(40, 1);
        Image dest_wrap(src_wrap);
        Image dest_reflect(src_reflect);

        Image::Accessor acc_src_wrap = src_wrap.accessor();
        Image::iterator iter_src_wrap = src_wrap.begin();
        Image::Accessor acc_src_reflect = src_reflect.accessor();
        Image::iterator iter_src_reflect = src_reflect.begin();

        for (int i = 0 ; i < 40 ; i++, iter_src_wrap++, iter_src_reflect++){
            acc_src_wrap.set(i + 0.25, iter_src_wrap);
            acc_src_reflect.set(i + 0.25, iter_src_reflect);
        }
        for (int j = 38 ; j >= 1 ; j--, iter_src_wrap++){
            acc_src_wrap.set( j + 0.25, iter_src_wrap);
        }

        convolveImage(srcImageRange(src_wrap), destImage(dest_wrap), kernel2d(line_kernel, BORDER_TREATMENT_WRAP));
        convolveImage(srcImageRange(src_reflect), destImage(dest_reflect), kernel2d(line_kernel, BORDER_TREATMENT_REFLECT));

        Image::iterator iter_dest_wrap = dest_wrap.begin();
        Image::Accessor acc_dest_wrap = dest_wrap.accessor();
        Image::iterator iter_dest_reflect = dest_reflect.begin();
        Image::iterator end_dest_reflect = dest_reflect.end();
        Image::Accessor acc_dest_reflect = dest_reflect.accessor();

        while(iter_dest_reflect != end_dest_reflect){
            shouldEqualTolerance(acc_dest_wrap(iter_dest_wrap), acc_dest_reflect(iter_dest_reflect), 1e-6);
            iter_dest_wrap++;
            iter_dest_reflect++;
        }
    }

    void stdConvolutionTestFromRepeatWithAvoid()
    {
        Image src_avoid(40, 1);
        src_avoid.init(2.47);
        Image src_repeat(36, 1);

        Image dest_repeat(src_repeat);
        Image dest_avoid(src_avoid);

        Image::Accessor acc_src_avoid = src_avoid.accessor();
        Image::iterator iter_src_avoid = src_avoid.begin();
        Image::Accessor acc_src_repeat = src_repeat.accessor();
        Image::iterator iter_src_repeat = src_repeat.begin();

        int i = 0;
        for ( ; i < 20 ; i++, iter_src_avoid++){
            if(i > 1){
                acc_src_repeat.set(i + 0.47, iter_src_repeat);
                acc_src_avoid.set(i + 0.47, iter_src_avoid);
                iter_src_repeat++;
            }
        }
        i--;

        for ( ; i >= 0 ; i--, iter_src_avoid++){
            if(i > 1){
                acc_src_repeat.set(i + 0.47, iter_src_repeat);
                acc_src_avoid.set(i + 0.47, iter_src_avoid);
                iter_src_repeat++;
            }
        }

        convolveImage(srcImageRange(src_repeat), destImage(dest_repeat), kernel2d(line_kernel, BORDER_TREATMENT_REPEAT));
        convolveImage(srcImageRange(src_avoid), destImage(dest_avoid), kernel2d(line_kernel, BORDER_TREATMENT_AVOID));

        Image::Accessor acc_dest_repeat = dest_repeat.accessor();
        Image::iterator dest_iter_repeat = dest_repeat.begin();

        Image::Accessor acc_dest_avoid = dest_avoid.accessor();
        Image::iterator dest_iter_avoid = dest_avoid.begin();

        for (int i = 0 ; i < 39 ; i++, dest_iter_avoid++){
            if (i < 2 || i > 37){
                should(acc_dest_avoid(dest_iter_avoid) == 2.47);
            }else{
                shouldEqualTolerance(acc_dest_avoid(dest_iter_avoid) , acc_dest_repeat(dest_iter_repeat), 1e-15);
                dest_iter_repeat++;
            }
        }
    }

    /**
     * Es wird die Positionierung der einzelnen
     * Punkte relativ zueinander getestet.
     */
    void stdConvolutionTestOfAllTreatmentsRelatively(){
        Image src(40, 1);

        Image::Accessor acc_src = src.accessor();
        Image::iterator iter_src = src.begin();

        int i = 0;
        for ( ; i < 20 ; i++, iter_src++){
            acc_src.set(i + 0.25, iter_src);
        }
        i--;
        for ( ; i >= 0 ; i--, iter_src++){
            acc_src.set(i + 0.25, iter_src);
        }

        Image dest_avoid(src);
        Image dest_repeat(src);
        Image dest_reflect(src);
        Image dest_wrap(src);
        Image dest_clip(src);

        convolveImage(srcImageRange(src), destImage(dest_avoid), kernel2d(line_kernel, BORDER_TREATMENT_AVOID));
        convolveImage(srcImageRange(src), destImage(dest_repeat), kernel2d(line_kernel, BORDER_TREATMENT_REPEAT));
        convolveImage(srcImageRange(src), destImage(dest_reflect), kernel2d(line_kernel, BORDER_TREATMENT_REFLECT));
        convolveImage(srcImageRange(src), destImage(dest_wrap), kernel2d(line_kernel, BORDER_TREATMENT_WRAP));
        convolveImage(srcImageRange(src), destImage(dest_clip), kernel2d(line_kernel, BORDER_TREATMENT_CLIP));

        iter_src = src.begin();
        Image::iterator iter_dest_avoid = dest_avoid.begin();
        Image::iterator iter_dest_repeat = dest_repeat.begin();
        Image::iterator iter_dest_reflect = dest_reflect.begin();
        Image::iterator iter_dest_wrap = dest_wrap.begin();
        Image::iterator iter_dest_clip = dest_clip.begin();

        for (int x = 0 ;  x < 40 ; x++){
            if(x > 1 && x < 38 ){
                shouldEqualTolerance(iter_dest_avoid[x], iter_dest_repeat[x], 1e-5);
                shouldEqualTolerance(iter_dest_avoid[x], iter_dest_reflect[x], 1e-5);
                shouldEqualTolerance(iter_dest_avoid[x], iter_dest_wrap[x], 1e-5);
                shouldEqualTolerance(iter_dest_avoid[x], iter_dest_clip[x], 1e-5);
            }else{
                should(iter_dest_avoid[x] == iter_src[x]);
                should(iter_dest_repeat[x] < iter_dest_reflect[x]);
                should(iter_dest_repeat[x] < iter_dest_clip[x]);
                if (x < 1 || x > 38){
                    should(iter_dest_clip[x] < iter_dest_reflect[x]);
                }
                else{
                    should(iter_dest_clip[x] > iter_dest_reflect[x]);
                }
            }
        }

    }

    void separableConvolutionTest()
    {
        vigra::Kernel1D<double> binom;
        binom.initBinomial(2);

        vigra::Kernel1D<double>::Iterator center = binom.center();

        should(center[0] == 0.375);
        should(center[-1] == 0.25);
        should(center[1] == 0.25);
        should(center[-2] == 0.0625);
        should(center[2] == 0.0625);

        binom.initBinomial(1);

        center = binom.center();

        should(center[0] == 0.5);
        should(center[-1] == 0.25);
        should(center[1] == 0.25);

        Image tmp1(constimg);
        Image tmp2(constimg);
        tmp2 = 0.0;

        separableConvolveX(srcImageRange(constimg), destImage(tmp1), kernel1d(binom));
        separableConvolveY(srcImageRange(tmp1), destImage(tmp2), kernel1d(binom));

        Image::ScanOrderIterator i1 = constimg.begin();
        Image::ScanOrderIterator i1end = constimg.end();
        Image::ScanOrderIterator i2 = tmp2.begin();
        Image::Accessor acc = constimg.accessor();

        for(; i1 != i1end; ++i1, ++i2)
        {
            should(acc(i1) == acc(i2));
        }
    }

    void separableDerivativeRepeatTest()
    {
        vigra::Kernel1D<double> grad;
        grad.initSymmetricGradient();

        Image tmp1(rampimg);
        tmp1 = 0.0;
        Image tmp2(constimg);

        separableConvolveX(srcImageRange(rampimg), destImage(tmp1), kernel1d(grad));
        separableConvolveX(srcImageRange(constimg), destImage(tmp2), kernel1d(grad));

        Image::ScanOrderIterator i1 = tmp1.begin();
        Image::ScanOrderIterator i2 = tmp2.begin();
        Image::Accessor acc = tmp1.accessor();

        should(acc(i1) == 0.5);
        should(acc(i2) == 0.0);
        ++i1;
        ++i2;
        should(acc(i1) == 1.0);
        should(acc(i2) == 0.0);
        ++i1;
        ++i2;
        should(acc(i1) == 1.0);
        should(acc(i2) == 0.0);
        ++i1;
        ++i2;
        should(acc(i1) == 1.0);
        should(acc(i2) == 0.0);
        ++i1;
        ++i2;
        should(acc(i1) == 0.5);
        should(acc(i2) == 0.0);
    }

    void separableDerivativeReflectTest()
    {
        vigra::Kernel1D<double> grad;
        grad.initSymmetricGradient();
        grad.setBorderTreatment(vigra::BORDER_TREATMENT_REFLECT);

        Image tmp1(rampimg);
        tmp1 = 1000.0;

        separableConvolveX(srcImageRange(rampimg), destImage(tmp1), kernel1d(grad));

        Image::ScanOrderIterator i1 = tmp1.begin();
        Image::Accessor acc = tmp1.accessor();

        should(acc(i1) == 0.0);
        ++i1;
        should(acc(i1) == 1.0);
        ++i1;
        should(acc(i1) == 1.0);
        ++i1;
        should(acc(i1) == 1.0);
        ++i1;
        should(acc(i1) == 0.0);
    }

    void separableDerivativeAvoidTest()
    {
        vigra::Kernel1D<double> grad;
        grad.initSymmetricGradient();
        grad.setBorderTreatment(vigra::BORDER_TREATMENT_AVOID);

        Image tmp1(rampimg);
        tmp1 = 1000.0;

        separableConvolveX(srcImageRange(rampimg), destImage(tmp1), kernel1d(grad));

        Image::ScanOrderIterator i1 = tmp1.begin();
        Image::Accessor acc = tmp1.accessor();

        should(acc(i1) == 1000.0);
        ++i1;
        should(acc(i1) == 1.0);
        ++i1;
        should(acc(i1) == 1.0);
        ++i1;
        should(acc(i1) == 1.0);
        ++i1;
        should(acc(i1) == 1000.0);
    }

    void separableSmoothClipTest()
    {
        vigra::Kernel1D<double> binom;
        binom.initBinomial(1);
        binom.setBorderTreatment(vigra::BORDER_TREATMENT_CLIP);

        Image tmp1(rampimg);
        tmp1 = 1000.0;

        separableConvolveX(srcImageRange(rampimg), destImage(tmp1), kernel1d(binom));

        Image::ScanOrderIterator i1 = tmp1.begin();
        Image::Accessor acc = tmp1.accessor();

        should(acc(i1) == 1.0/3.0);
        ++i1;
        should(acc(i1) == 1.0);
        ++i1;
        should(acc(i1) == 2.0);
        ++i1;
        should(acc(i1) == 3.0);
        ++i1;
        should(acc(i1) == 11.0/3.0);
    }

    void separableSmoothZeropadTest()
    {
        vigra::Kernel1D<double> binom;
        binom.initBinomial(1);
        binom.setBorderTreatment(vigra::BORDER_TREATMENT_ZEROPAD);

        Image tmp1(rampimg);
        tmp1 = 1000.0;

        separableConvolveX(srcImageRange(rampimg), destImage(tmp1), kernel1d(binom));

        Image::ScanOrderIterator i1 = tmp1.begin();
        Image::Accessor acc = tmp1.accessor();

        shouldEqual(acc(i1), 0.25);
        ++i1;
        shouldEqual(acc(i1), 1.0);
        ++i1;
        shouldEqual(acc(i1), 2.0);
        ++i1;
        shouldEqual(acc(i1), 3.0);
        ++i1;
        shouldEqual(acc(i1), 2.75);
    }

    void separableSmoothWrapTest()
    {
        vigra::Kernel1D<double> binom;
        binom.initBinomial(1);
        binom.setBorderTreatment(vigra::BORDER_TREATMENT_WRAP);

        Image tmp1(rampimg);
        tmp1 = 1000.0;

        separableConvolveX(srcImageRange(rampimg), destImage(tmp1), kernel1d(binom));

        Image::ScanOrderIterator i1 = tmp1.begin();
        Image::Accessor acc = tmp1.accessor();

        should(acc(i1) == 1.25);
        ++i1;
        should(acc(i1) == 1.0);
        ++i1;
        should(acc(i1) == 2.0);
        ++i1;
        should(acc(i1) == 3.0);
        ++i1;
        should(acc(i1) == 2.75);
    }

    void gaussianSmoothingTest()
    {
        double scale = 1.0;
        vigra::Kernel1D<double> gauss;
        gauss.initGaussian(scale);
        gauss.setBorderTreatment(BORDER_TREATMENT_REFLECT);

        Image tmp1(lenna.size()), tmp2(lenna.size()), tmp3(lenna.size());

        separableConvolveX(srcImageRange(lenna), destImage(tmp1), kernel1d(gauss));
        separableConvolveY(srcImageRange(tmp1), destImage(tmp2), kernel1d(gauss));

        gaussianSmoothing(srcImageRange(lenna), destImage(tmp1), scale);

        Image::ScanOrderIterator i1 = tmp1.begin();
        Image::ScanOrderIterator i1end = tmp1.end();
        Image::ScanOrderIterator i2 = tmp2.begin();
        Image::Accessor acc = constimg.accessor();

        for(; i1 != i1end; ++i1, ++i2)
        {
            should(acc(i1) == acc(i2));
        }

        tmp1 = 0.0;
        separableConvolveX(View(lenna), View(tmp1), gauss);
        separableConvolveY(View(tmp1), View(tmp3),  gauss);
        gaussianSmoothing(View(lenna), View(tmp1), scale);
        should(View(tmp1) == View(tmp2));
        should(View(tmp1) == View(tmp3));

        // test recursive approximation
        Image recursive(lenna.size());
        recursiveGaussianFilterX(srcImageRange(lenna), destImage(tmp2), scale);
        recursiveGaussianFilterY(srcImageRange(tmp2), destImage(recursive), scale);

        i1 = tmp1.begin();
        i1end = tmp1.end();
        i2 = recursive.begin();

        double sum = 0.0;
        for(; i1 != i1end; ++i1, ++i2)
        {
            double diff = abs(acc(i1) - acc(i2));
            sum += diff;
            should(diff < 6.0);
        }
        should(sum / lenna.width() / lenna.height() < 0.5);

        recursiveGaussianFilterX(View(lenna), View(tmp1), scale);
        recursiveGaussianFilterY(View(tmp1), View(tmp2), scale);
        should(View(recursive) == View(tmp2));
    }

    void optimalSmoothing3Test()
    {
        vigra::Kernel1D<double> smooth3;
        smooth3.initOptimalSmoothing3();

        Image tmp1(lenna.size());
        Image tmp2(lenna.size());

        separableConvolveX(srcImageRange(lenna), destImage(tmp1), kernel1d(smooth3));
        separableConvolveY(srcImageRange(tmp1), destImage(tmp2), kernel1d(smooth3));

        gaussianSmoothing(srcImageRange(lenna), destImage(tmp1), 0.68);

        Image::ScanOrderIterator i1 = tmp1.begin();
        Image::ScanOrderIterator i1end = tmp1.end();
        Image::ScanOrderIterator i2 = tmp2.begin();
        Image::Accessor acc = constimg.accessor();

        for(; i1 != i1end; ++i1, ++i2)
        {
            shouldEqualTolerance(acc(i1), acc(i2), 1e-2);
        }
    }

    void optimalSmoothing5Test()
    {
        vigra::Kernel1D<double> smooth5;
        smooth5.initOptimalSmoothing5();

        Image tmp1(lenna.size());
        Image tmp2(lenna.size());

        separableConvolveX(srcImageRange(lenna), destImage(tmp1), kernel1d(smooth5));
        separableConvolveY(srcImageRange(tmp1), destImage(tmp2), kernel1d(smooth5));

        gaussianSmoothing(srcImageRange(lenna), destImage(tmp1), 0.867);

        Image::ScanOrderIterator i1 = tmp1.begin();
        Image::ScanOrderIterator i1end = tmp1.end();
        Image::ScanOrderIterator i2 = tmp2.begin();
        Image::Accessor acc = constimg.accessor();

        for(; i1 != i1end; ++i1, ++i2)
        {
            shouldEqualTolerance(acc(i1), acc(i2), 1e-2);
        }
    }

    void separableGradientTest()
    {
        Image sepgrad(lenna.size());
        importImage(vigra::ImageImportInfo("lenna128sepgrad.xv"), destImage(sepgrad));

        vigra::Kernel1D<double> gauss;
        gauss.initGaussian(1.0);
        vigra::Kernel1D<double> grad;
        grad.initGaussianDerivative(1.0, 1);

        Image tmp1(lenna);
        tmp1.init(0.0);
        Image tmp2(lenna);
        tmp2.init(0.0);
        Image tmp3(lenna);

        separableConvolveX(srcImageRange(lenna), destImage(tmp3), kernel1d(grad));
        separableConvolveY(srcImageRange(tmp3), destImage(tmp1), kernel1d(gauss));

        separableConvolveX(srcImageRange(lenna), destImage(tmp3), kernel1d(gauss));
        separableConvolveY(srcImageRange(tmp3), destImage(tmp2), kernel1d(grad));

        Image::ScanOrderIterator i1 = tmp1.begin();
        Image::ScanOrderIterator i1end = tmp1.end();
        Image::ScanOrderIterator i2 = tmp2.begin();
        Image::ScanOrderIterator i = sepgrad.begin();
        Image::Accessor acc = constimg.accessor();

        for(; i1 != i1end; ++i1, ++i2, ++i)
        {
            double grad = VIGRA_CSTD::sqrt(acc(i1)*acc(i1)+acc(i2)*acc(i2));

            shouldEqualTolerance(grad-acc(i), 0.0, 1e-12);
        }

        // compare with 2D convolution
        vigra::Kernel2D<double> gradx;
        gradx.initSeparable(grad, gauss);
        Image nsgrad(lenna.size());
        convolveImage(srcImageRange(lenna), destImage(nsgrad), kernel2d(gradx));

        using namespace vigra::functor;

        combineTwoImages(srcImageRange(nsgrad), srcImage(tmp1), destImage(nsgrad), Arg1() - Arg2());
        Image zero(lenna.size(), 0.0);
        shouldEqualSequenceTolerance(nsgrad.data(), nsgrad.data()+nsgrad.width()*nsgrad.height(),
                                     zero.data(), 1e-12);
    }

    void gradientTest()
    {
        Image sepgrad(lenna.size());
        importImage(vigra::ImageImportInfo("lenna128sepgrad.xv"), destImage(sepgrad));


        Image tmpx(lenna.size());
        Image tmpy(lenna.size());
        Image mag(lenna.size());

        gaussianGradient(View(lenna), View(tmpx), View(tmpy), 1.0);
        gaussianGradientMagnitude(srcImageRange(lenna), destImage(mag), 1.0);

        Image::ScanOrderIterator i1 = tmpx.begin();
        Image::ScanOrderIterator i1end = tmpx.end();
        Image::ScanOrderIterator i2 = tmpy.begin();
        Image::ScanOrderIterator ig = mag.begin();
        Image::ScanOrderIterator i = sepgrad.begin();
        Image::Accessor acc = constimg.accessor();

        for(; i1 != i1end; ++i1, ++i2, ++ig, ++i)
        {
            double grad = VIGRA_CSTD::sqrt(acc(i1)*acc(i1)+acc(i2)*acc(i2));
            shouldEqualTolerance(grad-acc(i), 0.0, 1e-12);
            shouldEqualTolerance(acc(ig)-acc(i), 0.0, 1e-12);
        }
    }

    void optimalGradient3Test()
    {
        Image tmp(lenna.size());
        Image tmpx(lenna.size());
        Image tmpy(lenna.size());

        vigra::Kernel1D<double> diff;
        diff.initSymmetricDifference();
        vigra::Kernel1D<double> smooth3;
        smooth3.initOptimalFirstDerivativeSmoothing3();

        separableConvolveX(srcImageRange(lenna), destImage(tmp), kernel1d(diff));
        separableConvolveY(srcImageRange(tmp), destImage(tmpx), kernel1d(smooth3));

        separableConvolveX(srcImageRange(lenna), destImage(tmp), kernel1d(smooth3));
        separableConvolveY(srcImageRange(tmp), destImage(tmpy), kernel1d(diff));

        double sum = 0.0, mi = 0.0, ma = 0.0;

        Image::ScanOrderIterator i1 = tmpx.begin();
        Image::ScanOrderIterator i1end = tmpx.end();
        Image::ScanOrderIterator i2 = tmpy.begin();
        Image::Accessor acc = constimg.accessor();

        for(; i1 != i1end; ++i1, ++i2)
        {
            double grad = VIGRA_CSTD::sqrt(acc(i1)*acc(i1)+acc(i2)*acc(i2));
            sum += grad;
            mi = std::min(mi, grad);
            ma = std::max(ma, grad);
        }
        should(std::fabs(sum- 130000.0) < 1000.0);
        shouldEqual(mi, 0.0);
        should(std::fabs(ma - 68.0) < 1.0);
    }

    void optimalLaplacian3Test()
    {
        Image tmp(lenna.size());
        Image tmpx(lenna.size());
        Image tmpy(lenna.size());

        vigra::Kernel1D<double> diff;
        diff.initSecondDifference3();
        vigra::Kernel1D<double> smooth3;
        smooth3.initOptimalSecondDerivativeSmoothing3();

        separableConvolveX(srcImageRange(lenna), destImage(tmp), kernel1d(diff));
        separableConvolveY(srcImageRange(tmp), destImage(tmpx), kernel1d(smooth3));

        separableConvolveX(srcImageRange(lenna), destImage(tmp), kernel1d(smooth3));
        separableConvolveY(srcImageRange(tmp), destImage(tmpy), kernel1d(diff));

        double sum = 0.0, mi = 0.0, ma = 0.0;

        Image::ScanOrderIterator i1 = tmpx.begin();
        Image::ScanOrderIterator i1end = tmpx.end();
        Image::ScanOrderIterator i2 = tmpy.begin();
        Image::Accessor acc = constimg.accessor();

        for(; i1 != i1end; ++i1, ++i2)
        {
            double lap = acc(i1) + acc(i2);
            sum += lap;
            mi = std::min(mi, lap);
            ma = std::max(ma, lap);
        }
        should(std::fabs(sum) < 90.0);
        should(std::fabs(mi + 120.0) < 1.0);
        should(std::fabs(ma - 117.0) < 1.0);
    }

    void optimalGradient5Test()
    {
        Image tmp(lenna.size());
        Image tmpx(lenna.size());
        Image tmpy(lenna.size());
        Image mag(lenna.size());

        vigra::Kernel1D<double> diff;
        diff.initOptimalFirstDerivative5();
        vigra::Kernel1D<double> smooth5;
        smooth5.initOptimalFirstDerivativeSmoothing5();

        separableConvolveX(srcImageRange(lenna), destImage(tmp), kernel1d(diff));
        separableConvolveY(srcImageRange(tmp), destImage(tmpx), kernel1d(smooth5));

        separableConvolveX(srcImageRange(lenna), destImage(tmp), kernel1d(smooth5));
        separableConvolveY(srcImageRange(tmp), destImage(tmpy), kernel1d(diff));

        gaussianGradientMagnitude(srcImageRange(lenna), destImage(mag), 0.906);

        for(int y=1; y<lenna.height()-1; ++y)
        {
            for(int x=1; x<lenna.width()-1; ++x)
            {
                double grad = vigra::hypot(tmpx(x,y), tmpy(x,y));
                should(std::fabs(grad-mag(x,y)) < 2.0);
            }
        }
    }

    void optimalLaplacian5Test()
    {
        Image tmp(lenna.size());
        Image tmpx(lenna.size());
        Image tmpy(lenna.size());
        Image lap(lenna.size());
        Image lap1(lenna.size());

        vigra::Kernel1D<double> diff;
        diff.initOptimalSecondDerivative5();
        vigra::Kernel1D<double> smooth5;
        smooth5.initOptimalSecondDerivativeSmoothing5();

        separableConvolveX(srcImageRange(lenna), destImage(tmp), kernel1d(diff));
        separableConvolveY(srcImageRange(tmp), destImage(tmpx), kernel1d(smooth5));

        separableConvolveX(View(lenna), View(tmp), smooth5);
        separableConvolveY(View(tmp), View(tmpy), diff);

        laplacianOfGaussian(srcImageRange(lenna), destImage(lap), 0.817);
        laplacianOfGaussian(View(lenna), View(lap1), 0.817);

        for(int y=2; y<lenna.height()-2; ++y)
        {
            for(int x=2; x<lenna.width()-2; ++x)
            {
                double l = tmpx(x,y) + tmpy(x,y);
                should(std::fabs(l-lap(x,y)) < 4.0);
                shouldEqual(lap(x,y), lap1(x,y));
            }
        }
    }

    void gradientRGBTest()
    {
        RGBImage input(lenna.size());
        importImage(vigra::ImageImportInfo("lenna128rgb.xv"), destImage(input));

        Image sepgrad(lenna.size());
        importImage(vigra::ImageImportInfo("lenna128rgbsepgrad.xv"), destImage(sepgrad));


        RGBImage tmpx(lenna.size());
        RGBImage tmpy(lenna.size());
        Image mag(lenna.size());
        Image resmag(lenna.size());

        gaussianGradient(srcImageRange(input), destImage(tmpx), destImage(tmpy), 1.0);
        gaussianGradientMagnitude(srcImageRange(input), destImage(mag), 1.0);

        RGBImage::ScanOrderIterator i1 = tmpx.begin();
        RGBImage::ScanOrderIterator i1end = tmpx.end();
        RGBImage::ScanOrderIterator i2 = tmpy.begin();
        Image::ScanOrderIterator ig = mag.begin();
        Image::ScanOrderIterator i = sepgrad.begin();
        RGBImage::Accessor rgb = tmpx.accessor();
        Image::Accessor acc = constimg.accessor();

        for(; i1 != i1end; ++i1, ++i2, ++ig, ++i)
        {
            double grad = VIGRA_CSTD::sqrt(squaredNorm(rgb(i1))+squaredNorm(rgb(i2)));
            shouldEqualTolerance(grad-acc(i), 0.0, 1e-12);
            shouldEqualTolerance(acc(ig)-acc(i), 0.0, 1e-12);
        }

   }

    void hessianTest()
    {
        Image refxx(lenna.size());
        Image refxy(lenna.size());
        Image refyy(lenna.size());

        importImage(vigra::ImageImportInfo("lennahessxx.xv"), destImage(refxx));
        importImage(vigra::ImageImportInfo("lennahessyy.xv"), destImage(refyy));
        importImage(vigra::ImageImportInfo("lennahessxy.xv"), destImage(refxy));
        {
            Image resxx(lenna.size());
            Image resxy(lenna.size());
            Image resyy(lenna.size());

            hessianMatrixOfGaussian(srcImageRange(lenna),
                destImage(resxx), destImage(resxy), destImage(resyy), 1.0);

            Image::ScanOrderIterator i1 = resxx.begin();
            Image::ScanOrderIterator i1end = resxx.end();
            Image::ScanOrderIterator i2 = resyy.begin();
            Image::ScanOrderIterator i3 = resxy.begin();
            Image::ScanOrderIterator r1 = refxx.begin();
            Image::ScanOrderIterator r2 = refyy.begin();
            Image::ScanOrderIterator r3 = refxy.begin();
            Image::Accessor acc = constimg.accessor();


            for(; i1 != i1end; ++i1, ++i2, ++i3, ++r1, ++r2, ++r3)
            {
                shouldEqualTolerance(acc(i1)-acc(r1), 0.0, 1e-12);
                shouldEqualTolerance(acc(i2)-acc(r2), 0.0, 1e-12);
                shouldEqualTolerance(acc(i3)-acc(r3), 0.0, 1e-12);
            }
        }
        {
            Image resxx(lenna.size());
            Image resxy(lenna.size());
            Image resyy(lenna.size());

            hessianMatrixOfGaussian(View(lenna),
                                    View(resxx), View(resxy), View(resyy), 1.0);

            Image::ScanOrderIterator i1 = resxx.begin();
            Image::ScanOrderIterator i1end = resxx.end();
            Image::ScanOrderIterator i2 = resyy.begin();
            Image::ScanOrderIterator i3 = resxy.begin();
            Image::ScanOrderIterator r1 = refxx.begin();
            Image::ScanOrderIterator r2 = refyy.begin();
            Image::ScanOrderIterator r3 = refxy.begin();
            Image::Accessor acc = constimg.accessor();


            for(; i1 != i1end; ++i1, ++i2, ++i3, ++r1, ++r2, ++r3)
            {
                shouldEqualTolerance(acc(i1)-acc(r1), 0.0, 1e-12);
                shouldEqualTolerance(acc(i2)-acc(r2), 0.0, 1e-12);
                shouldEqualTolerance(acc(i3)-acc(r3), 0.0, 1e-12);
            }
        }
    }

    void structureTensorTest()
    {
        Image resxx(lenna.size());
        Image resxy(lenna.size());
        Image resyy(lenna.size());
        Image refxx(lenna.size());
        Image refxy(lenna.size());
        Image refyy(lenna.size());

        typedef BasicImage<TinyVector<double, 3> > VectorImage;
        VectorImage resst(lenna.size());

        structureTensor(View(lenna),
                        View(resxx), View(resxy), View(resyy), 1.0, 2.0);

        structureTensor(View(lenna), MultiArrayView<2, TinyVector<double, 3> >(resst), 1.0, 2.0);

        importImage(vigra::ImageImportInfo("lennastxx.xv"), destImage(refxx));
        importImage(vigra::ImageImportInfo("lennastyy.xv"), destImage(refyy));
        importImage(vigra::ImageImportInfo("lennastxy.xv"), destImage(refxy));

        Image::ScanOrderIterator i1 = resxx.begin();
        Image::ScanOrderIterator i1end = resxx.end();
        Image::ScanOrderIterator i2 = resxy.begin();
        Image::ScanOrderIterator i3 = resyy.begin();
        VectorImage::ScanOrderIterator i4 = resst.begin();
        Image::ScanOrderIterator r1 = refxx.begin();
        Image::ScanOrderIterator r2 = refxy.begin();
        Image::ScanOrderIterator r3 = refyy.begin();
        Image::Accessor acc = constimg.accessor();
        VectorImage::Accessor vacc = resst.accessor();

        for(; i1 != i1end; ++i1, ++i2, ++i3, ++i4, ++r1, ++r2, ++r3)
        {
            shouldEqualTolerance(acc(i1), acc(r1), 1e-7);
            shouldEqualTolerance(acc(i2), acc(r2), 1e-7);
            shouldEqualTolerance(acc(i3), acc(r3), 1e-7);
            shouldEqualTolerance(vacc(i4)[0], acc(r1), 1e-7);
            shouldEqualTolerance(vacc(i4)[1], acc(r2), 1e-7);
            shouldEqualTolerance(vacc(i4)[2], acc(r3), 1e-7);
        }
    }

    void structureTensorRGBTest()
    {
        RGBImage input(lenna.size());
        importImage(vigra::ImageImportInfo("lenna128rgb.xv"), destImage(input));

        typedef BasicImage<TinyVector<double, 3> > VectorImage;
        VectorImage resst(lenna.size()), refst(lenna.size());

        structureTensor(srcImageRange(lenna), destImage(resst), 1.0, 2.0);

        importImage(vigra::ImageImportInfo("lennargbst.xv"), destImage(refst));

        VectorImage::ScanOrderIterator i1 = resst.begin();
        VectorImage::ScanOrderIterator i1end = resst.end();
        VectorImage::ScanOrderIterator r1 = refst.begin();
        VectorImage::Accessor vacc = resst.accessor();

        for(; i1 != i1end; ++i1, ++r1)
        {
            shouldEqualTolerance(vacc(i1)[0], vacc(r1)[0], 1e-7);
            shouldEqualTolerance(vacc(i1)[1], vacc(r1)[1], 1e-7);
            shouldEqualTolerance(vacc(i1)[2], vacc(r1)[2], 1e-7);
        }
    }

    void stdConvolutionTest()
    {
        vigra::Kernel1D<double> binom1;
        binom1.initBinomial(1);

        vigra::Kernel2D<double> binom2;
        binom2.initSeparable(binom1, binom1);

        Image tmp1(constimg);
        tmp1 = 0.0;

        convolveImage(srcImageRange(constimg), destImage(tmp1), kernel2d(binom2));

        Image::ScanOrderIterator i1 = constimg.begin();
        Image::ScanOrderIterator i1end = constimg.end();
        Image::ScanOrderIterator i2 = tmp1.begin();
        Image::Accessor acc = constimg.accessor();

        for(; i1 != i1end; ++i1, ++i2)
        {
            should(acc(i1) == acc(i2));
        }
    }

    void stdVersusSeparableConvolutionTest()
    {

        vigra::Kernel1D<double> gauss1;
        gauss1.initGaussian(2.0);

        vigra::Kernel2D<double> gauss2;
        gauss2.initSeparable(gauss1, gauss1);

        Image tmp1(lenna);
        tmp1 = 0.0;

        convolveImage(srcImageRange(lenna), destImage(tmp1), kernel2d(gauss2));

        Image tmp2(lenna);
        Image tmp3(lenna);
        tmp3 = 0.0;

        separableConvolveX(srcImageRange(lenna), destImage(tmp2), kernel1d(gauss1));
        separableConvolveY(srcImageRange(tmp2), destImage(tmp3), kernel1d(gauss1));

        Image::Iterator y1 = tmp1.upperLeft() - gauss2.upperLeft();
        Image::Iterator end = tmp1.lowerRight() - gauss2.lowerRight();
        Image::Iterator y2 = tmp3.upperLeft() - gauss2.upperLeft();
        Image::Accessor acc = tmp1.accessor();

        for(; y1.y != end.y; ++y1.y, ++y2.y)
        {
            Image::Iterator x1 = y1;
            Image::Iterator x2 = y2;
            for(; x1.x != end.x; ++x1.x, ++x2.x)
            {
                shouldEqualTolerance(acc(x1), acc(x2), 1e-7);
            }
        }
    }

    void recursiveFilterTestWithAvoid()
    {
        Image src_const(25, 25);
        Image dest(src_const);
        src_const.init(42.1);
        dest.init(1.12);

        recursiveFilterX(srcImageRange(src_const), destImage(dest), VIGRA_CSTD::exp(-1.0), BORDER_TREATMENT_AVOID);

        Image::Iterator is = src_const.upperLeft();
        Image::Iterator tmp_src = src_const.upperLeft();
        Image::Iterator isend = src_const.lowerRight();
        Image::Iterator id = dest.upperLeft();
        Image::Iterator tmp_dest = dest.upperLeft();
        Image::Accessor acc = src_const.accessor();

        for(int y = 0; is.y != isend.y; is.y++, id.y++, y++)
        {
            for (int x = 0 ; is.x != isend.x ; is.x++, id.x++, x++)
            {
                if (x < 11 || x > 13)
                {
                    should (acc(id) == 1.12);
                }
                else
                {
                    shouldEqualTolerance (acc(id) , acc(is), 1e-15);
                }
            }
            is.x = tmp_src.x;
            id.x = tmp_dest.x;
        }

        recursiveFilterY(srcImageRange(src_const), destImage(dest),
                         VIGRA_CSTD::exp(-1.0), BORDER_TREATMENT_AVOID);

        is = src_const.upperLeft();
        tmp_src = src_const.upperLeft();
        isend = src_const.lowerRight();
        id = dest.upperLeft();
        tmp_dest = dest.upperLeft();
        acc = src_const.accessor();

        for(int y = 0; is.y != isend.y; is.y++, id.y++, y++)
        {
            for (int x = 0 ; is.x != isend.x ; is.x++, id.x++, x++)
            {
                if ((x > 10 && x < 14) || (y > 10 && y < 14))
                {
                    shouldEqualTolerance(acc(is) , acc(id), 1e-15);
                }
                else
                {
                    should(acc(id) == 1.12);
                }
            }
            is.x = tmp_src.x;
            id.x = tmp_dest.x;
        }

        // Hier wird an einem symmetrischen Bild /\ getestet
        // ob die korrekten Daten eingehalten wurden.

        Image src(getSymmetricLine()), srct(getSymmetricLine(true));
        dest = src;
        Image destt(srct);

        Image::value_type correct_data[40] =
            {0.25, 1.25, 2.25, 3.25, 4.25, 5.25, 6.25, 7.25, 8.25, 9.25,
             10.25, 11.249812, 12.249472, 13.248558, 14.246079, 15.239341,
             16.221025, 17.171238, 18.035903, 18.668023,
             18.668023, 18.035903, 17.171238, 16.221025, 15.239341,
             14.246079, 13.248558, 12.249472, 11.249812, 10.25, 9.25,
             8.25, 7.25, 6.25, 5.25, 4.25, 3.25, 2.25, 1.25, 0.25};

        recursiveFilterX(View(src), View(dest),
                         VIGRA_CSTD::exp(-1.0), BORDER_TREATMENT_AVOID);
        recursiveFilterY(View(srct), View(destt),
                         VIGRA_CSTD::exp(-1.0), BORDER_TREATMENT_AVOID);
        Image::iterator dest_iter = dest.begin();
        Image::iterator destt_iter = destt.begin();

        for (int i = 0 ; i < 40; i++)
        {
            shouldEqualTolerance (correct_data[i], dest_iter[i], 1e-7);
            shouldEqualTolerance (correct_data[i], destt_iter[i], 1e-7);
        }

    }// end of recursiveFilterTestWithAvoid

    void recursiveFilterTestWithReflect()
    {
        Image src_const(25, 25);
        Image dest(src_const);
        src_const.init(42.1);
        dest.init(1.12);

        recursiveFilterX(srcImageRange(src_const), destImage(dest),
                     VIGRA_CSTD::exp(-1.0), BORDER_TREATMENT_REFLECT);

        Image::Iterator is = src_const.upperLeft();
        Image::Iterator tmp_src = src_const.upperLeft();
        Image::Iterator isend = src_const.lowerRight();
        Image::Iterator id = dest.upperLeft();
        Image::Iterator tmp_dest = dest.upperLeft();
        Image::Accessor acc = src_const.accessor();

        for(int y = 0; is.y != isend.y; is.y++, id.y++, y++)
        {
            for (int x = 0 ; is.x != isend.x ; is.x++, id.x++, x++)
            {
                shouldEqualTolerance (acc(id) , acc(is), 1e-15);
            }
            is.x = tmp_src.x;
            id.x = tmp_dest.x;
        }

        recursiveFilterY(srcImageRange(dest), destImage(dest),
                   VIGRA_CSTD::exp(-1.0), BORDER_TREATMENT_REFLECT);

        is = src_const.upperLeft();
        tmp_src = src_const.upperLeft();
        isend = src_const.lowerRight();
        id = dest.upperLeft();
        tmp_dest = dest.upperLeft();
        acc = src_const.accessor();

        for(int y = 0; is.y != isend.y; is.y++, id.y++, y++)
        {
            for (int x = 0 ; is.x != isend.x ; is.x++, id.x++, x++)
            {
                shouldEqualTolerance(acc(is) , acc(id), 1e-15);
            }
            is.x = tmp_src.x;
            id.x = tmp_dest.x;
        }

        // Hier wird an einem symmetrischen Bild /\ (Groesse 40x1)  getestet
        // ob die korrekten Daten eingehalten wurden.

        Image src(getSymmetricLine());
        dest = src;
        recursiveFilterX(srcImageRange(src), destImage(dest),
                VIGRA_CSTD::exp(-1.0), BORDER_TREATMENT_REFLECT);
        Image::value_type correct_data[40] =
           {1.10091101909, 1.56303266253, 2.36515826013, 3.29236429975, 4.26558480098,
            5.25573290944, 6.25210788209, 7.25077235463, 8.25027572885, 9.25007858906,
            10.2499668097, 11.2498189802, 12.2494745341, 13.2485593473, 14.2460793794,
            15.2393409851, 16.2210251818, 17.171238053, 18.0359027479, 18.6680232997,
            18.6680232997, 18.0359027479, 17.171238053, 16.2210251818, 15.2393409851,
            14.2460793794, 13.2485593473, 12.2494745342, 11.2498189803, 10.24996681,
            9.25007858994, 8.25027573123, 7.2507723611, 6.2521078997, 5.2557329573,
            4.26558493107, 3.29236465337, 2.36515922136, 1.56303527544, 1.10091812172};

        Image::iterator dest_iter = dest.begin();
        for (int i = 0 ; i < 40; i++)
        {
            shouldEqualTolerance (correct_data[i], dest_iter[i], 1e-10);
        }
    }// end of recursiveFilterTestWithReflect


    void recursiveFilterTestWithClipOnConstImage()
    {
        Image src_const(25, 25);
        Image dest(src_const);
        src_const.init(42.1);
        dest.init(1.12);
        recursiveFilterX(srcImageRange(src_const), destImage(dest),
                           VIGRA_CSTD::exp(-1.0), BORDER_TREATMENT_CLIP);

        Image::Iterator is = src_const.upperLeft();
        Image::Iterator tmp_src = src_const.upperLeft();
        Image::Iterator isend = src_const.lowerRight();
        Image::Iterator id = dest.upperLeft();
        Image::Iterator tmp_dest = dest.upperLeft();
        Image::Accessor acc = src_const.accessor();

        for(int y = 0; is.y != isend.y; is.y++, id.y++, y++)
        {
            for (int x = 0 ; is.x != isend.x ; is.x++, id.x++, x++)
            {
                shouldEqualTolerance (acc(id), acc(is), 0.000001);
            }
            is.x = tmp_src.x;
            id.x = tmp_dest.x;
        }

        recursiveFilterY(srcImageRange(src_const), destImage(dest),
                VIGRA_CSTD::exp(-1.0), BORDER_TREATMENT_CLIP);

        is = src_const.upperLeft();
        tmp_src = src_const.upperLeft();
        isend = src_const.lowerRight();
        id = dest.upperLeft();
        tmp_dest = dest.upperLeft();
        acc = src_const.accessor();

        for(int y = 0; is.y != isend.y; is.y++, id.y++, y++)
        {
            for (int x = 0 ; is.x != isend.x ; is.x++, id.x++, x++)
            {
                shouldEqualTolerance (acc(id), acc(is), 0.000001);
            }
            is.x = tmp_src.x;
            id.x = tmp_dest.x;
        }
    }// end of recursiveFilterTestWithClipOnConstImage()

    void recursiveFilterTestWithClipOnNonConstImage(){
        Image src(40, 1);
        Image dest(src);
        Image::value_type correct_data[40] =
            {0.831977, 1.53351, 2.3853, 3.31218, 4.27763, 5.26195,
            6.25506, 7.25211, 8.25086, 9.25035, 10.2501, 11.2501,
            12.25, 13.25, 14.25, 15.25, 16.25, 17.25, 18.25, 19.25,
            20.25, 21.25, 22.25, 23.25, 24.25, 25.25, 26.25, 27.25,
            28.2499, 29.2499, 30.2496, 31.2491, 32.2479, 33.2449,
            34.2381, 35.2224, 36.1878, 37.1147, 37.9665, 38.668};

        Image::Accessor acc_src = src.accessor();
        Image::iterator iter_src = src.begin();

        for (int i = 0 ; i < 40 ; i++, iter_src++)
        {
            acc_src.set(i + 0.25, iter_src);
        }

        recursiveFilterX(srcImageRange(src), destImage(dest),
                VIGRA_CSTD::exp(-1.0), BORDER_TREATMENT_CLIP);

        Image::iterator idest = dest.begin();
        Image::iterator idest_end = dest.end();
        Image::Accessor dest_acc = dest.accessor();

        for(int i = 0; idest != idest_end; idest++, i++)
        {
            shouldEqualTolerance (dest_acc(idest), correct_data[i], 0.00001);
        }

    }// end of recursiveFilterTestWithClipOnNonConstImage

    void recursiveFilterTestWithWrap()
    {
        Image src_const(25, 25);
        Image dest(src_const);
        src_const.init(42.1);
        dest.init(1.12);

        recursiveFilterX(srcImageRange(src_const), destImage(dest),
                VIGRA_CSTD::exp(-1.0), BORDER_TREATMENT_WRAP);

        Image::Iterator is = src_const.upperLeft();
        Image::Iterator tmp_src = src_const.upperLeft();
        Image::Iterator isend = src_const.lowerRight();
        Image::Iterator id = dest.upperLeft();
        Image::Iterator tmp_dest = dest.upperLeft();
        Image::Accessor acc = src_const.accessor();

        for(int y = 0; is.y != isend.y; is.y++, id.y++, y++)
        {
            for (int x = 0 ; is.x != isend.x ; is.x++, id.x++, x++)
            {
                shouldEqualTolerance (acc(id) , acc(is), 1e-15);
            }
            is.x = tmp_src.x;
            id.x = tmp_dest.x;
        }

        recursiveFilterY(srcImageRange(src_const), destImage(dest),
                    VIGRA_CSTD::exp(-1.0), BORDER_TREATMENT_WRAP);

        is = src_const.upperLeft();
        tmp_src = src_const.upperLeft();
        isend = src_const.lowerRight();
        id = dest.upperLeft();
        tmp_dest = dest.upperLeft();
        acc = src_const.accessor();

        for(int y = 0; is.y != isend.y; is.y++, id.y++, y++)
        {
            for (int x = 0 ; is.x != isend.x ; is.x++, id.x++, x++)
            {
                shouldEqualTolerance(acc(is) , acc(id), 1e-15);
            }
            is.x = tmp_src.x;
            id.x = tmp_dest.x;
        }


        // Hier wird an einem symmetrischen Bild /\ (Groesse 40x1)  getestet
        // ob die korrekten Daten eingehalten wurden.

        Image src(getSymmetricLine());
        dest = src;
        recursiveFilterX(srcImageRange(src), destImage(dest),
                    VIGRA_CSTD::exp(-1.0), BORDER_TREATMENT_WRAP);
        Image::value_type correct_data[40] =
            {0.8319696, 1.4640946, 2.328761, 3.2789745, 4.260659,
            5.2539208, 6.2514412, 7.2505271, 8.2501855, 9.2500454,
            10.249955, 11.249814, 12.249473, 13.248559, 14.246079,
            15.239341, 16.221025, 17.171238, 18.035903, 18.668023,
            18.668023, 18.035903, 17.171238, 16.221025, 15.239341,
            14.246079, 13.248559, 12.249473, 11.249814, 10.249955,
            9.2500454, 8.2501855, 7.2505271, 6.2514412, 5.2539208,
            4.260659, 3.2789745, 2.328761, 1.4640946, 0.8319696};

        Image::iterator dest_iter = dest.begin();

        for (int i = 0 ; i < 40; i++)
        {
            shouldEqualTolerance (correct_data[i], dest_iter[i], 1e-7);
        }

    }// end of recursiveFilterTestWithWrap

    void recursiveFilterTestWithRepeat()
    {
        Image src_const(25, 25);
        Image dest(src_const);
        src_const.init(42.1);
        dest.init(1.12);

        recursiveFilterX(srcImageRange(src_const), destImage(dest),
                    VIGRA_CSTD::exp(-1.0), BORDER_TREATMENT_REPEAT);

        Image::Iterator is = src_const.upperLeft();
        Image::Iterator tmp_src = src_const.upperLeft();
        Image::Iterator isend = src_const.lowerRight();
        Image::Iterator id = dest.upperLeft();
        Image::Iterator tmp_dest = dest.upperLeft();
        Image::Accessor acc = src_const.accessor();

        for(int y = 0; is.y != isend.y; is.y++, id.y++, y++)
        {
            for (int x = 0 ; is.x != isend.x ; is.x++, id.x++, x++)
            {
                shouldEqualTolerance (acc(id) , acc(is), 1e-15);
            }
            is.x = tmp_src.x;
            id.x = tmp_dest.x;
        }
        recursiveFilterY(srcImageRange(src_const), destImage(dest),
                VIGRA_CSTD::exp(-1.0), BORDER_TREATMENT_REPEAT);

        is = src_const.upperLeft();
        tmp_src = src_const.upperLeft();
        isend = src_const.lowerRight();
        id = dest.upperLeft();
        tmp_dest = dest.upperLeft();
        acc = src_const.accessor();

        for(int y = 0; is.y != isend.y; is.y++, id.y++, y++)
        {
            for (int x = 0 ; is.x != isend.x ; is.x++, id.x++, x++)
            {
                shouldEqualTolerance(acc(is) , acc(id), 1e-15);
            }
            is.x = tmp_src.x;
            id.x = tmp_dest.x;
        }

        // Hier wird an einem symmetrischen Bild /\ (Groesse 40x1)  getestet
        // ob die korrekten Daten eingehalten wurden.

        Image src(getSymmetricLine());
        dest = src;
        recursiveFilterX(srcImageRange(src), destImage(dest),
                VIGRA_CSTD::exp(-1.0), BORDER_TREATMENT_REPEAT);
        Image::value_type correct_data[40] =
            {0.67545906, 1.4065176, 2.3075796, 3.2711823, 4.2577924,
            5.2528662, 6.2510533, 7.2503844, 8.250133, 9.2500261,
            10.249947, 11.249812, 12.249472, 13.248558, 14.246079,
            15.239341, 16.221025, 17.171238, 18.035903, 18.668023,
            18.668023, 18.035903, 17.171238, 16.221025, 15.239341,
            14.246079, 13.248558, 12.249472, 11.249812, 10.249947,
            9.2500261, 8.250133, 7.2503844, 6.2510533, 5.2528662,
            4.2577924, 3.2711823, 2.3075796, 1.4065176, 0.67545906};

        Image::iterator dest_iter = dest.begin();

        for (int i = 0 ; i < 40; i++)
        {
            shouldEqualTolerance (correct_data[i], dest_iter[i], 1e-7);
        }

    }// end of recursiveFilterTestWithRepeat

    void recursiveFilterTestFromWrapWithReflect()
    {

        Image src_wrap(78, 1);
        Image src_reflect(40, 1);
        Image dest_wrap(src_wrap);
        Image dest_reflect(src_reflect);

        Image::Accessor acc_src_wrap = src_wrap.accessor();
        Image::iterator iter_src_wrap = src_wrap.begin();
        Image::Accessor acc_src_reflect = src_reflect.accessor();
        Image::iterator iter_src_reflect = src_reflect.begin();

        for (int i = 0 ; i < 40 ; i++, iter_src_wrap++, iter_src_reflect++)
        {
            acc_src_wrap.set(i + 0.25, iter_src_wrap);
            acc_src_reflect.set(i + 0.25, iter_src_reflect);
        }
        for (int j = 38 ; j > 0 ; j--, iter_src_wrap++)
        {
            acc_src_wrap.set( j + 0.25, iter_src_wrap);
        }

        recursiveFilterX(srcImageRange(src_wrap), destImage(dest_wrap),
                VIGRA_CSTD::exp(-1.0), BORDER_TREATMENT_WRAP);

        recursiveFilterX(srcImageRange(src_reflect), destImage(dest_reflect),
                VIGRA_CSTD::exp(-1.0), BORDER_TREATMENT_REFLECT);

        Image::iterator iter_dest_wrap = dest_wrap.begin();
        Image::Accessor acc_dest_wrap = dest_wrap.accessor();
        Image::iterator iter_dest_reflect = dest_reflect.begin();
        Image::iterator end_dest_reflect = dest_reflect.end();
        Image::Accessor acc_dest_reflect = dest_reflect.accessor();

        while(iter_dest_reflect != end_dest_reflect)
        {
            shouldEqualTolerance(acc_dest_wrap(iter_dest_wrap),
                           acc_dest_reflect(iter_dest_reflect), 1e-6);
            iter_dest_wrap++;
            iter_dest_reflect++;
        }
    }

    void recursiveFilterTestFromRepeatWithAvoid()
    {
        Image src_avoid(40, 1);
        src_avoid.init(11.47);
        Image src_repeat(18, 1);

        Image dest_repeat(src_repeat);
        Image dest_avoid(src_avoid);

        Image::Accessor acc_src_avoid = src_avoid.accessor();
        Image::iterator iter_src_avoid = src_avoid.begin();
        Image::Accessor acc_src_repeat = src_repeat.accessor();
        Image::iterator iter_src_repeat = src_repeat.begin();

        int i = 0;
        for ( ; i < 20 ; i++, iter_src_avoid++)
        {
            if(i > 10)
            {
                acc_src_repeat.set(i + 0.47, iter_src_repeat);
                acc_src_avoid.set(i + 0.47, iter_src_avoid);
                iter_src_repeat++;
            }
        }
        i--;

        for ( ; i >= 0 ; i--, iter_src_avoid++)
        {
            if(i > 10)
            {
                acc_src_repeat.set(i + 0.47, iter_src_repeat);
                acc_src_avoid.set(i + 0.47, iter_src_avoid);
                iter_src_repeat++;
            }
        }

        recursiveFilterX(srcImageRange(src_repeat), destImage(dest_repeat), VIGRA_CSTD::exp(-1.0), BORDER_TREATMENT_REPEAT);
        recursiveFilterX(srcImageRange(src_avoid), destImage(dest_avoid), VIGRA_CSTD::exp(-1.0), BORDER_TREATMENT_AVOID);

        Image::Accessor acc_dest_repeat = dest_repeat.accessor();
        Image::iterator dest_iter_repeat = dest_repeat.begin();

        Image::Accessor acc_dest_avoid = dest_avoid.accessor();
        Image::iterator dest_iter_avoid = dest_avoid.begin();

        for (int i = 0 ; i < 39 ; i++, dest_iter_avoid++)
        {
            if (i < 11 || i > 28)
            {
                should(acc_dest_avoid(dest_iter_avoid) == 11.47);
            }
            else
            {
                shouldEqualTolerance(acc_dest_avoid(dest_iter_avoid), acc_dest_repeat(dest_iter_repeat), 1e-15);
                dest_iter_repeat++;
            }
        }
    }

    /**
   * Es wird die Positionierung der einzelnen
   * Punkte relativ zueinander getestet.
   */
    void recursiveFilterTestOfAllTreatmentsRelatively()
    {
        Image src(40, 1);

        Image::Accessor acc_src = src.accessor();
        Image::iterator iter_src = src.begin();

        int i = 0;
        for ( ; i < 20 ; i++, iter_src++)
        {
            acc_src.set(i + 0.25, iter_src);
        }
        i--;
        for ( ; i >= 0 ; i--, iter_src++)
        {
            acc_src.set(i + 0.25, iter_src);
        }

        Image dest_avoid(src);
        Image dest_repeat(src);
        Image dest_reflect(src);
        Image dest_wrap(src);
        Image dest_clip(src);

        recursiveFilterX(srcImageRange(src), destImage(dest_avoid),
                VIGRA_CSTD::exp(-1.0), BORDER_TREATMENT_AVOID);
        recursiveFilterX(srcImageRange(src), destImage(dest_repeat),
                VIGRA_CSTD::exp(-1.0), BORDER_TREATMENT_REPEAT);
        recursiveFilterX(srcImageRange(src), destImage(dest_reflect),
                VIGRA_CSTD::exp(-1.0), BORDER_TREATMENT_REFLECT);
        recursiveFilterX(srcImageRange(src), destImage(dest_wrap),
                VIGRA_CSTD::exp(-1.0), BORDER_TREATMENT_WRAP);
        recursiveFilterX(srcImageRange(src), destImage(dest_clip),
                VIGRA_CSTD::exp(-1.0), BORDER_TREATMENT_CLIP);

        iter_src = src.begin();
        Image::iterator iter_dest_avoid = dest_avoid.begin();
        Image::iterator iter_dest_repeat = dest_repeat.begin();
        Image::iterator iter_dest_reflect = dest_reflect.begin();
        Image::iterator iter_dest_wrap = dest_wrap.begin();
        Image::iterator iter_dest_clip = dest_clip.begin();

        for (int x = 0 ;  x < 40 ; x++)
        {
            if(x > 9 && x < 30 )
            {
                shouldEqualTolerance(iter_dest_avoid[x], iter_dest_repeat[x], 1e-5);
                shouldEqualTolerance(iter_dest_avoid[x], iter_dest_reflect[x], 1e-5);
                shouldEqualTolerance(iter_dest_avoid[x], iter_dest_wrap[x], 1e-5);
                shouldEqualTolerance(iter_dest_avoid[x], iter_dest_clip[x], 1e-5);
            }
            else
            {
                should(iter_dest_avoid[x] == iter_src[x]);
                should(iter_dest_repeat[x] < iter_dest_reflect[x]);
                should(iter_dest_repeat[x] < iter_dest_clip[x]);
                if (x < 2 || x > 37)
                {
                    should(iter_dest_clip[x] < iter_dest_reflect[x]);
                }
                else
                {
                    should(iter_dest_clip[x] > iter_dest_reflect[x]);
                }
            }
        }
    }

    void recursiveSmoothTest()
    {
        Image tmp1(constimg);
        tmp1 = 0.0;

        recursiveSmoothX(View(constimg), View(tmp1), 1.0);
        recursiveSmoothY(View(tmp1), View(tmp1), 1.0);

        Image::ScanOrderIterator i1 = constimg.begin();
        Image::ScanOrderIterator i1end = constimg.end();
        Image::ScanOrderIterator i2 = tmp1.begin();
        Image::Accessor acc = constimg.accessor();

        for(; i1 != i1end; ++i1, ++i2)
        {
            should(acc(i1) == acc(i2));
        }
    }

    void recursiveGradientTest()
    {
        ImageImportInfo info("lenna128recgrad.xv");

        Image recgrad(info.width(), info.height());
        importImage(info, destImage(recgrad));

        Image tmp1(lenna);
        tmp1 = 0.0;
        Image tmp2(lenna);
        tmp2 = 0.0;

        recursiveFirstDerivativeX(View(lenna), View(tmp1), 1.0);
        recursiveSmoothY(View(tmp1), View(tmp1), 1.0);

        recursiveSmoothX(View(lenna), View(tmp2), 1.0);
        recursiveFirstDerivativeY(View(tmp2), View(tmp2), 1.0);

        Image::ScanOrderIterator i1 = tmp1.begin();
        Image::ScanOrderIterator i1end = tmp1.end();
        Image::ScanOrderIterator i2 = tmp2.begin();
        Image::ScanOrderIterator i = recgrad.begin();
        Image::Accessor acc = constimg.accessor();

        for(; i1 != i1end; ++i1, ++i2, ++i)
        {
            double grad = sqrt(acc(i1)*acc(i1)+acc(i2)*acc(i2));

            shouldEqualTolerance(grad, acc(i), 1e-7);
        }
    }

    void recursiveSecondDerivativeTest()
    {
        double b = VIGRA_CSTD::exp(-1.0);
        double factor = (1.0 - b) * (1.0 - b) / b;

        Image tmp1(rampimg);
        tmp1 = 0.0;
        Image tmp2(rampimg);
        tmp2 = 0.0;

        recursiveSmoothX(View(rampimg), View(tmp1), 1.0);
        recursiveSecondDerivativeX(View(rampimg), View(tmp2), 1.0);

        Image::ScanOrderIterator i1 = rampimg.begin();
        Image::ScanOrderIterator i1end = i1 + rampimg.width();
        Image::ScanOrderIterator i2 = tmp1.begin();
        Image::ScanOrderIterator i3 = tmp2.begin();
        Image::Accessor acc = constimg.accessor();

        for(; i1 != i1end; ++i1, ++i2, ++i3)
        {
            double diff = factor * (acc(i2) - acc(i1));
            shouldEqualTolerance(diff, acc(i3), 1e-7);
        }
    }

    void nonlinearDiffusionTest()
    {

        Image res(lenna.size());

        Image comp(lenna.size());
        importImage(vigra::ImageImportInfo("lenna128nonlinear.xv"), destImage(comp));

        nonlinearDiffusion(srcImageRange(lenna), destImage(res),
                           vigra::DiffusivityFunctor<double>(4.0), 4.0);
        shouldEqualSequenceTolerance(res.begin(), res.end(), comp.begin(), 1e-7);

        res = 0;
        nonlinearDiffusion(View(lenna), View(res),
                           vigra::DiffusivityFunctor<double>(4.0), 4.0);
        shouldEqualSequenceTolerance(res.begin(), res.end(), comp.begin(), 1e-7);
    }

    Image constimg, lenna, rampimg, sym_image, unsym_image;
    vigra::Kernel2D<double> sym_kernel, unsym_kernel, line_kernel;

};

struct ResamplingConvolutionTest
{
    typedef MultiArrayView<2, float> View;

    void testKernelsSpline()
    {
        BSpline<3, double> spline, dspline(1);
        ArrayVector<Kernel1D<double> > kernels(4);
        Rational<int> samplingRatio(4), offset(1,8);
        resampling_detail::MapTargetToSourceCoordinate
                   mapCoordinate(samplingRatio, offset);
        createResamplingKernels(spline, mapCoordinate, kernels);

        for(unsigned int i = 0; i<kernels.size(); ++i)
        {
            double sum = 0.0;
            for(int k = kernels[i].left(); k <= kernels[i].right(); ++k)
            {
                double x = rational_cast<double>(k + i / samplingRatio + offset);
                shouldEqualTolerance(kernels[i][k], spline(x), 1e-14);
                sum += kernels[i][k];
            }
            shouldEqualTolerance(sum, 1.0, 1e-14);
        }

        createResamplingKernels(dspline, mapCoordinate, kernels);

        for(unsigned int i = 0; i<kernels.size(); ++i)
        {
            double sum = 0.0;
            for(int k = kernels[i].left(); k <= kernels[i].right(); ++k)
            {
                double x = rational_cast<double>(k + i / samplingRatio + offset);
                shouldEqualTolerance(kernels[i][k], dspline(x), 1e-14);
                sum += -x*kernels[i][k];
            }
            shouldEqualTolerance(sum, 1.0, 1e-14);
        }
    }

    void testKernelsGauss()
    {
        Gaussian<double> gauss(0.7), dgauss(0.7, 1);
        ArrayVector<Kernel1D<double> > kernels(4);
        Rational<int> samplingRatio(4), offset(1,8);
        resampling_detail::MapTargetToSourceCoordinate
                   mapCoordinate(samplingRatio, offset);
        createResamplingKernels(gauss, mapCoordinate, kernels);

        for(unsigned int i = 0; i<kernels.size(); ++i)
        {
            double sum = 0.0;
            for(int k = kernels[i].left(); k <= kernels[i].right(); ++k)
            {
                double x = rational_cast<double>(k + i / samplingRatio + offset);
                shouldEqualTolerance(kernels[i][k], gauss(x), 1e-4);
                sum += kernels[i][k];
            }
            shouldEqualTolerance(sum, 1.0, 1e-14);
       }

        createResamplingKernels(dgauss, mapCoordinate, kernels);

        for(unsigned int i = 0; i<kernels.size(); ++i)
        {
            double sum = 0.0;
            double f = i == 0 || i == 3 ? 1.00218 : 0.99851;
            for(int k = kernels[i].left(); k <= kernels[i].right(); ++k)
            {
                double x = rational_cast<double>(k + i / samplingRatio + offset);
                shouldEqualTolerance(kernels[i][k], f*dgauss(x), 1e-5);
                sum += -x*kernels[i][k];
            }
            shouldEqualTolerance(sum, 1.0, 1e-14);
        }
    }

    void testOversamplingConstant()
    {
        BSpline<3, double> spline, dspline(1);
        Rational<int> samplingRatio(4,1), offset(1,8);

        FImage img(100, 100);
        img.init(1.0);

        int wnew = rational_cast<int>((img.width() - 1 - offset) * samplingRatio + 1);
        int hnew = rational_cast<int>((img.height() - 1 - offset) * samplingRatio + 1);

        FImage res(wnew, hnew);

        resamplingConvolveImage(srcImageRange(img), destImageRange(res),
             spline, samplingRatio, offset, spline, samplingRatio, offset);
        for(FImage::iterator i = res.begin(); i < res.end(); ++i)
            shouldEqual(*i, 1.0);

        resamplingConvolveImage(View(img), View(res),
                                dspline, samplingRatio, offset, spline, samplingRatio, offset);
        for(FImage::iterator i = res.begin(); i < res.end(); ++i)
            shouldEqual(*i, 0.0);
    }

    void testOversamplingReal()
    {
        Gaussian<double> gauss(0.7);
        Rational<int> samplingRatio(2,1), offset(1,4);

        ImageImportInfo info("lenna128.xv");
        FImage img(info.size());
        importImage(info, destImage(img));

        int wnew = rational_cast<int>((info.width() - 1 - offset) * samplingRatio + 1);
        int hnew = rational_cast<int>((info.height() - 1 - offset) * samplingRatio + 1);

        FImage res(wnew, hnew);
        resamplingConvolveImage(View(img), View(res),
             gauss, samplingRatio, offset, gauss, samplingRatio, offset);

        ImageImportInfo rinfo("resampling.xv");
        shouldEqual(rinfo.width(), wnew);
        shouldEqual(rinfo.height(), hnew);
        FImage ref(wnew, hnew);
        importImage(rinfo, destImage(ref));

        for(FImage::iterator i = res.begin(), j = ref.begin(); i < res.end(); ++i, ++j)
            shouldEqualTolerance(*i, *j, 1e-6);
    }
};

struct ImagePyramidTest
{
    typedef vigra::DImage Image;
    Image img;
    int w, h;

    ImagePyramidTest()
    {
        ImageImportInfo ginfo("lenna128.xv");
        w = ginfo.width();
        h = ginfo.height();
        img.resize(w, h);
        importImage(ginfo, destImage(img));
    }

    void testPyramidConstruction()
    {
        vigra::ImagePyramid<Image> pyramid(-2, 2, img);

        shouldEqual(pyramid[-2].size(), Size2D(509, 477));
        shouldEqual(pyramid[-1].size(), Size2D(255, 239));
        shouldEqual(pyramid[0].size(), Size2D(128, 120));
        shouldEqual(pyramid[1].size(), Size2D(64, 60));
        shouldEqual(pyramid[2].size(), Size2D(32, 30));
    }

    void testBurtReduceExpand()
    {
        vigra::ImagePyramid<Image> pyramid(-2, 3, img), laplacian(-2,3, img);

        pyramidExpandBurtFilter(pyramid, 0, -2);
        pyramidReduceBurtFilter(pyramid, 0,  3);

        pyramidReduceBurtLaplacian(laplacian, 0, 3);

        char buf[100];

        for(int i=-2; i<=2; ++i)
        {
            if(i==0)
                continue;

            std::sprintf(buf, "lenna_level%d.xv", i);
            ImageImportInfo info(buf);
            shouldEqual(info.size(), pyramid[i].size());

            Image ref(info.size());
            importImage(info, destImage(ref));
            shouldEqualSequenceTolerance(ref.begin(), ref.end(), pyramid[i].begin(), 1e-12);
        }

        for(int i=0; i<=2; ++i)
        {
            std::sprintf(buf, "lenna_levellap%d.xv", i);
            ImageImportInfo info(buf);
            shouldEqual(info.size(), laplacian[i].size());

            Image ref(info.size());
            importImage(info, destImage(ref));
            for(int k=0; k<info.width()*info.height(); ++k)
                shouldEqualTolerance(ref.data()[k]-laplacian[i].data()[k], 0.0, 1e-12);
        }

        shouldEqualSequenceTolerance(pyramid[3].begin(), pyramid[3].end(), laplacian[3].begin(), 1e-14);

        pyramidExpandBurtLaplacian(laplacian, 3, -2);

        for(int i=3; i>=-2; --i)
        {
            shouldEqualSequenceTolerance(pyramid[i].begin(), pyramid[i].end(), laplacian[i].begin(), 1e-14);
        }
    }

};

struct TotalVariationTest{

  const int width,height;
  MultiArray<2,double> data;
  MultiArray<2,double> out;
  MultiArray<2,double> weight;
  TotalVariationTest():width(50),height(50),data(Shape2(50,50)),out(Shape2(50,50)),weight(Shape2(50,50)){
    for (int y=0;y<height;y++){
      for (int x=0;x<width;x++){
        weight(x,y)=1;
        data(x,y)=testdata[x+width*y]/255.;
      }
    }
  }

  void testTotalVariation(){

    totalVariationFilter(data,out,0.5,1000,0.01);
    //exportImage(srcImageRange(out), vigra::ImageExportInfo("test_tv.pgm"));
    shouldEqualSequenceTolerance(out.begin(), out.end(), result_std_tv, 1e-12);
  }
  void testWeightedTotalVariation(){

    totalVariationFilter(data,weight,out,.5,1000,0.01);
    //exportImage(srcImageRange(out), vigra::ImageExportInfo("test_tvw.pgm"));
    shouldEqualSequenceTolerance(out.begin(), out.end(), result_std_tv_weight, 1e-12);
  }

  void testAnisotropicTotalVariation(){

    double alpha0=0.05,beta0=0.5,sigma=0.1,rho=.5,K=10;
    int inner_steps=200,outer_steps=5;

    MultiArray<2,double> phi(Shape2(width,height));
    MultiArray<2,double> alpha(Shape2(width,height));
    MultiArray<2,double> beta(Shape2(width,height));
    MultiArray<2,double> xedges(Shape2(width,height));
    MultiArray<2,double> yedges(Shape2(width,height));

    for (int y=0;y<height;y++){
      for (int x=0;x<width;x++){
        alpha(x,y)=alpha0;
        beta(x,y)=beta0;
        xedges(x,y)=1;
        yedges(x,y)=1;
      }
    }
    out=data;
    for (int i=0;i<outer_steps;i++){
      getAnisotropy(out,phi,alpha,beta,alpha0,beta0,sigma,rho,K);
      anisotropicTotalVariationFilter(data,weight,phi,alpha,beta,out,inner_steps);
    }
    //exportImage(srcImageRange(out), vigra::ImageExportInfo("test_aniso.pgm"));
    shouldEqualSequenceTolerance(out.begin(), out.end(), result_aniso_tv, 1e-8);
  }

  void testSecondOrderTotalVariation(){

    double alpha0=0.05,beta0=0.5,sigma=0.1,rho=.5,K=10,gamma0=0.1;
    int inner_steps=200,outer_steps=5;

    MultiArray<2,double> phi(Shape2(width,height));
    MultiArray<2,double> alpha(Shape2(width,height));
    MultiArray<2,double> beta(Shape2(width,height));
    MultiArray<2,double> gamma(Shape2(width,height));
    MultiArray<2,double> xedges(Shape2(width,height));
    MultiArray<2,double> yedges(Shape2(width,height));

    for (int y=0;y<height;y++){
      for (int x=0;x<width;x++){
        alpha(x,y)=alpha0;
        beta(x,y)=beta0;
        gamma(x,y)=gamma0;
        xedges(x,y)=1;
        yedges(x,y)=1;
      }
    }
    out=data;
    for (int i=0;i<outer_steps;i++){
      getAnisotropy(out,phi,alpha,beta,alpha0,beta0,sigma,rho,K);
      secondOrderTotalVariationFilter(data,weight,phi,alpha,beta,gamma,xedges,yedges,out,inner_steps);
    }
    //exportImage(srcImageRange(out), vigra::ImageExportInfo("test_htv.pgm"));
    shouldEqualSequenceTolerance(out.begin(), out.end(), result_higher_order_tv,1e-12);
  }
};


struct ConvolutionTestSuite
: public vigra::test_suite
{
    ConvolutionTestSuite()
    : vigra::test_suite("ConvolutionTestSuite")
    {
        add( testCase( &ConvolutionTest::borderCopyTest));

#if 1
        add( testCase( &ConvolutionTest::initExplicitlyTest));

        add( testCase( &ConvolutionTest::simpleSharpeningTest));
        add( testCase( &ConvolutionTest::gaussianSharpeningTest));
        add( testCase( &ConvolutionTest::stdConvolutionTestOnConstImage));
        add( testCase( &ConvolutionTest::stdConvolutionTestWithAvoid));
        add( testCase( &ConvolutionTest::stdConvolutionTestWithZeropad));
        add( testCase( &ConvolutionTest::stdConvolutionTestWithClip));
        add( testCase( &ConvolutionTest::stdConvolutionTestWithWrap));
        add( testCase( &ConvolutionTest::stdConvolutionTestWithReflect));
        add( testCase( &ConvolutionTest::stdConvolutionTestWithRepeat));
        add( testCase( &ConvolutionTest::stdConvolutionTestFromWrapWithReflect));
        add( testCase( &ConvolutionTest::stdConvolutionTestFromRepeatWithAvoid));
        add( testCase( &ConvolutionTest::stdConvolutionTestOfAllTreatmentsRelatively));

        add( testCase( &ConvolutionTest::separableConvolutionTest));
        add( testCase( &ConvolutionTest::separableDerivativeRepeatTest));
        add( testCase( &ConvolutionTest::separableDerivativeReflectTest));
        add( testCase( &ConvolutionTest::separableDerivativeAvoidTest));
        add( testCase( &ConvolutionTest::separableSmoothClipTest));
        add( testCase( &ConvolutionTest::separableSmoothZeropadTest));
        add( testCase( &ConvolutionTest::separableSmoothWrapTest));
        add( testCase( &ConvolutionTest::gaussianSmoothingTest));
        add( testCase( &ConvolutionTest::optimalSmoothing3Test));
        add( testCase( &ConvolutionTest::optimalSmoothing5Test));
        add( testCase( &ConvolutionTest::optimalGradient3Test));
        add( testCase( &ConvolutionTest::optimalGradient5Test));
        add( testCase( &ConvolutionTest::optimalLaplacian3Test));
        add( testCase( &ConvolutionTest::optimalLaplacian5Test));
        add( testCase( &ConvolutionTest::separableGradientTest));
        add( testCase( &ConvolutionTest::gradientTest));
        add( testCase( &ConvolutionTest::gradientRGBTest));
        add( testCase( &ConvolutionTest::hessianTest));
        add( testCase( &ConvolutionTest::structureTensorTest));
        add( testCase( &ConvolutionTest::structureTensorRGBTest));
        add( testCase( &ConvolutionTest::stdConvolutionTest));
        add( testCase( &ConvolutionTest::stdVersusSeparableConvolutionTest));
        add( testCase( &ConvolutionTest::recursiveFilterTestWithAvoid));
        add( testCase( &ConvolutionTest::recursiveFilterTestWithClipOnConstImage));
        add( testCase( &ConvolutionTest::recursiveFilterTestWithClipOnNonConstImage));
        add( testCase( &ConvolutionTest::recursiveFilterTestWithReflect));
        add( testCase( &ConvolutionTest::recursiveFilterTestWithWrap));
        add( testCase( &ConvolutionTest::recursiveFilterTestWithRepeat));
        add( testCase( &ConvolutionTest::recursiveFilterTestFromWrapWithReflect));
        add( testCase( &ConvolutionTest::recursiveFilterTestFromRepeatWithAvoid));
        add( testCase( &ConvolutionTest::recursiveFilterTestOfAllTreatmentsRelatively));
        add( testCase( &ConvolutionTest::recursiveSmoothTest));
        add( testCase( &ConvolutionTest::recursiveGradientTest));
        add( testCase( &ConvolutionTest::recursiveSecondDerivativeTest));
        add( testCase( &ConvolutionTest::nonlinearDiffusionTest));

        add( testCase( &ResamplingConvolutionTest::testKernelsSpline));
        add( testCase( &ResamplingConvolutionTest::testKernelsGauss));
        add( testCase( &ResamplingConvolutionTest::testOversamplingConstant));
        add( testCase( &ResamplingConvolutionTest::testOversamplingReal));

        add( testCase( &ImagePyramidTest::testPyramidConstruction));
        add( testCase( &ImagePyramidTest::testBurtReduceExpand));

        add( testCase( &TotalVariationTest::testTotalVariation));
        add( testCase( &TotalVariationTest::testWeightedTotalVariation));
        add( testCase( &TotalVariationTest::testAnisotropicTotalVariation));
        add( testCase( &TotalVariationTest::testSecondOrderTotalVariation));
#endif
    }
};

int main(int argc, char ** argv)
{
    ConvolutionTestSuite test;

    int failed = test.run(vigra::testsToBeExecuted(argc, argv));

    std::cout << test.report() << std::endl;

    return (failed != 0);
}

