//
// Generic image functions
//
// Bala Amavasai (bala@amavasai.org)
// Mon Oct  1 15:41:44 BST 2001
//
// $Header: /cvs/mimas2/include/image_funcs.h,v 1.1.1.1 2005/08/09 15:37:45 engmb Exp $
//

#ifndef IMAGE_FUNCS_H
#define IMAGE_FUNCS_H

#include <cassert>
#include <float.h>
#include <iostream>
#include <vector>
#include <algorithm>
#include <cstdlib>
#include <values.h>
#include <cmath>
#include "image.h"
#include "image_conv.h"
#include "image_op.h"
#include "rgba.h"

namespace mimas {

template< typename T, typename TPtr >
T min_val( const const_image_ref< T, TPtr > &image )
{
  T retVal;
  if ( image.initialised() ) {
    int size = image.getWidth() * image.getHeight();
    retVal = *std::min_element( image.rawData(),
                                image.rawData() + size );
  } else
    retVal = T();
  return retVal;
}

template< typename T, typename TPtr >
T max_val( const const_image_ref< T, TPtr > &image )
{
  T retVal;
  if ( image.initialised() ) {
    int size = image.getWidth() * image.getHeight();
    retVal = *std::max_element( image.rawData(),
                                image.rawData() + size );
  } else
    retVal = T();
  return retVal;
}

template< typename T >
image_ref< T > &normaliseIt( image_ref< T > &im,
                             const T &val1, const T &val2 )
{
  _normalise< T > f( min_val( im ), max_val( im ), val1, val2 );
  return image_apply( im, im,
                      _multi_help1< T, T, _normalise< T > >( f ) );
}

template< typename T, typename TPtr >
image< T > normalise( const const_image_ref< T, TPtr > &im,
                      const T &val1, const T &val2 )
{
  if ( im.initialised() ) {// Image must not be empty.
    return image_func< T >( im,
                            _normalise< T >( min_val( im ),
                                             max_val( im ), val1, val2 ) );
  } else
    return image< T >();
}

template<typename T>
void equaliseIt( image_ref<T> &imagein )
{

    int H[256], TR[256];

    for (int count=0; count<256; count++)
        H[count]=0;

    int intensity;
    for (int j=0; j<imagein.getHeight(); j++)
        for (int i=0; i<imagein.getWidth(); i++)
        {
            intensity=imagein.pixel(i,j);
       assert( intensity <= 255 );
            H[intensity]+=1;
        }
    
   // convert to cumulative histogram 
    int imagesize=imagein.getWidth()*imagein.getHeight();
    for (int count=1; count<256; count++)
    {
        H[count]=H[count]+H[count-1];
        TR[count-1]=(255*H[count-1])/imagesize;
    }
    TR[255]=(255*H[255])/imagesize;
     
    for (int j=0; j<imagein.getHeight(); j++)
        for (int i=0; i<imagein.getWidth(); i++)
          imagein.pixel(i,j)=(T)TR[imagein.pixel(i,j)];
}

template< typename T >
image_ref< T > &bilevelIt( image_ref< T > &im, T threshval,
                           T val1, T val2 )
{
  std::binder2nd< _bilevel< T > > f( _bilevel< T >( val1, val2 ),
                                     threshval );
  return image_apply
    ( im, im,
      _multi_help1< T, T, std::binder2nd< _bilevel< T > > >( f ) );
}

template< typename T, typename TPtr >
image< T > bilevel( const const_image_ref< T, TPtr > &im,
                    T threshval, T val1, T val2 )
{
  return image_func< T >( im,
                          std::bind2nd( _bilevel< T >( val1, val2 ),
                                        threshval ) );
}

template< typename T >
image_ref< T > &thresholdIt( image_ref< T > &im, T threshval )
{
  std::binder2nd< _threshold< T > > f( _threshold< T >(), threshval );
  return image_apply
    ( im, im,
      _multi_help1< T, T, std::binder2nd< _threshold< T > > >( f ) );
}

template< typename T, typename TPtr >
image< T > threshold( const const_image_ref< T, TPtr > &im, T threshval )
{
  return image_func< T >( im, std::bind2nd( _threshold< T >(),
                                            threshval ) );
}

template< typename T >
image_ref< T > &bilevel_doubleIt( image_ref< T > &im,
                                  T min, T max, T val1, T val2 )
{
  _bilevel_double< T > f( val1, val2, min, max );
  return image_apply( im, im,
                      _multi_help1< T, T, _bilevel_double< T > >( f ) );
}

template< typename T, typename TPtr >
image< T > bilevel_double( const const_image_ref< T, TPtr > &im,
                           T min, T max, T val1, T val2 )
{
  return multi_func< T >( im.rawData(),
                          _bilevel_double< T >( val1, val2, min, max ) );
}

template<typename T, typename TPtr, typename U>
void despeckleKuwahara( const const_image_ref< T, TPtr > &imagein,
                        image< U > &imageout )
{
  if ( &imageout != &imagein )
    imageout.init(imagein.getWidth(),imagein.getHeight());
    
    U intensity;
    float m_ul, m_ur, m_ll, m_lr;
    float v_ul, v_ur, v_ll, v_lr;
    
    for (int y=0; y<imagein.getHeight(); y++)
        for (int x=0; x<imagein.getWidth(); x++)
        {
            if (y<2 || y>imagein.getHeight()-3 || x<2 || x>imagein.getWidth()-3)
            {
         imageout.pixel(x,y)=imagein.pixel(x,y);
                continue;

            }


            // mean
            m_ul=0.0; m_ur=0.0; m_ll=0.0; m_lr=0.0;

            for (int j=0; j<3; j++)
                for (int i=0; i<3; i++)
                {
                    m_ul+=(float)imagein.pixel(x-2+i,y-2+j);
                    m_ur+=(float)imagein.pixel(x+i,y-2+j);
                    m_ll+=(float)imagein.pixel(x-2+i,y+j);
                    m_lr+=(float)imagein.pixel(x+i,y+j);
                }

            m_ul=m_ul/9.0;
            m_ur=m_ur/9.0;
            m_ll=m_ll/9.0;
            m_lr=m_lr/9.0;

            
            // estimated variance
            v_ul=0.0; v_ur=0.0; v_ll=0.0; v_lr=0.0;
            
            for (int j=0; j<3; j++)
                for (int i=0; i<3; i++)
                {
                    v_ul+=(((float)imagein.pixel(x-2+i,y-2+j)-m_ul)*((float)imagein.pixel(x-2+i,y-2+j)-m_ul));
                    v_ur+=(((float)imagein.pixel(x+i,y-2+j)-m_ur)*((float)imagein.pixel(x+i,y-2+j)-m_ur));
                    v_ll+=(((float)imagein.pixel(x-2+i,y+j)-m_ll)*((float)imagein.pixel(x-2+i,y+j)-m_ll));
                    v_lr+=(((float)imagein.pixel(x+i,y+j)-m_lr)*((float)imagein.pixel(x+i,y+j)-m_lr));
                }

            intensity=(U)m_ul;
            intensity=v_ur<v_ul?(U)m_ur:intensity;
            intensity=v_ll<v_ur?(U)m_ll:intensity;
            intensity=v_lr<v_ll?(U)m_lr:intensity;

            imageout.pixel(x,y)=intensity;
        }  
}


template<typename T>
void despeckleKuwaharaIt( image_ref<T> &imagein )
{
    image<T> tempimage;

    despeckleKuwahara(imagein, tempimage);

    imagein = tempimage;
}

template< typename T, typename TPtr >
image< T > gradSobelX( const const_image_ref< T, TPtr > &im )
{
  const T sobelx1[1][3]={ {-1, 0, 1} };
  const T sobelx2[3][1]={ { 1 },
                          { 2 },
                          { 1 } };
  const_image_ref< T >
    filter1( &sobelx1[0][0], 3, 1 ),
    filter2( &sobelx2[0][0], 1, 3 );
  return correlate( correlate( im, filter1 ), filter2 );
}


template< typename T, typename TPtr >
image< T > gradSobelY( const const_image_ref< T, TPtr > &im )
{
  const T sobely1[3][1]={ { -1 },
                          {  0 },
                          {  1 } };
  const T sobely2[1][3]={ { 1, 2, 1} };
  const_image_ref< T >
    filter1( &sobely1[0][0], 1, 3 ),
    filter2( &sobely2[0][0], 3, 1 );
  return correlate( correlate( im, filter1 ), filter2 );
}

template< typename T, typename TPtr >
image< T > edgeSobelSqr( const const_image_ref< T, TPtr > &im )
{
  image< T >
    gradX( gradSobelX( im ) ),
    gradY( gradSobelY( im ) );
  return gradX * gradX + gradY * gradY;
}

template< typename T, typename TPtr >
image< T > edgeSobelNorm( const const_image_ref< T, TPtr > &im )
{
  return squareRoot( edgeSobelSqr( im ) );
}

// template<typename T, typename U>
// void edgeSobelOrientation(const image<T> &imagein, image<U> &imageout)
// {
//     int      x,y, sx,sy;
//     int      height, width;
//     image<U> tempimage;
//     float    sxIntensity, syIntensity;


//     static float sobelx[3][3]={      { -1.0, -2.0,-1.0},
//                                 {  0.0,  0.0, 0.0},
//                                 {  1.0,  2.0, 1.0}};

//     static float sobely[3][3]={      {-1.0, 0.0, 1.0},
//                                  {-2.0, 0.0, 2.0},
//                                  {-1.0, 0.0, 1.0}};

//     height=imagein.getHeight();
//     width=imagein.getWidth();
//     tempimage.init(width,height);

//     for(y=1;y<height-1;y++)
//         for (x=1;x<width-1;x++)
//         {
//             sxIntensity=0.0; syIntensity=0.0;
//             for(sx=0;sx<3;sx++)
//                 for(sy=0;sy<3;sy++)
//                 {
//                     sxIntensity+=sobelx[sy][sx]*(float)imagein.pixel(x+sx-1,y+sy-1);
//                     syIntensity+=sobely[sy][sx]*(float)imagein.pixel(x+sx-1,y+sy-1);
//                 }
//             if (sxIntensity==0.0)
//               tempimage.pixel(x,y)=0.0;
//             else
//               tempimage.pixel(x,y)=atan(syIntensity/sxIntensity);


//         }

//     imageout = tempimage;
// }

template< typename T, typename TPtr >
image< T > edgeLaplacian( const const_image_ref< T, TPtr > &im )
{
  const T laplacian[3][3]={ {  0, -1,  0 },
                            { -1,  4, -1 },
                            {  0, -1,  0 } };
  const_image_ref< T > f( &laplacian[0][0], 3, 3 );
  return correlate( im, f );
}

/** Laplacian of Gaussian filter.
    @todo Determine zero-crossings to have a complete edge-detector. */
template< typename T, typename TPtr >
image< T > edgeLoG( const const_image_ref< T, TPtr > &imagein )
{
  const T LoG[9][9]= { {   0,  0,  3,  2,  2,  2,  3,  0,  0},
                       {   0,  2,  3,  5,  5,  5,  3,  2,  0},
                       {   3,  3,  5,  3,  0,  3,  5,  3,  3},
                       {   2,  5,  3,-12,-23,-12,  3,  5,  2},
                       {   2,  5,  0,-23,-40,-23,  0,  5,  2},
                       {   2,  5,  3,-12,-23,-12,  3,  5,  2},
                       {   3,  3,  5,  3,  0,  3,  5,  3,  3},
                       {   0,  2,  3,  5,  5,  5,  3,  2,  0},
                       {   0,  0,  3,  2,  2,  2,  3,  0,  0} };

  const_image_ref< T > filter( &LoG[0][0], 9, 9 );
  image< T > tempimage( correlate( imagein, filter ) );
    
  /*static float LoG[5][5]={{  0.0, 0.0,-1.0, 0.0, 0.0},
                            {  0.0,-1.0,-2.0,-1.0, 0.0},
                            { -1.0,-2.0,16.0,-2.0,-1.0},
                            {  0.0,-1.0,-2.0,-1.0, 0.0},
                            {  0.0, 0.0,-1.0, 0.0, 0.0}};
  */

  bilevelIt( tempimage, (T)0, (T)255, (T)0 );
  
  image< T > imageout;
  imageout.init( imagein.getWidth(), imagein.getHeight() );

  // Shouldn't actually threshold - must check zero crossings
  for ( int y=4; y<imagein.getHeight()-4; y++ )
    for ( int x=4; x<imagein.getWidth()-4; x++ ) {
      if (tempimage.pixel(x,y)<=127) continue;
      if ( ( tempimage.pixel( x-1, y-1 ) > 127 ) ||
           ( tempimage.pixel( x-1, y   ) > 127 ) ||
           ( tempimage.pixel( x-1, y+1 ) > 127 ) ||
           ( tempimage.pixel( x  , y-1 ) > 127 ) ||
           ( tempimage.pixel( x  , y+1 ) > 127 ) ||
           ( tempimage.pixel( x+1, y-1 ) > 127 ) ||
           ( tempimage.pixel( x+1, y   ) > 127 ) ||
           ( tempimage.pixel( x+1, y+1 ) > 127 ) )
        imageout.pixel( x  , y   ) = 255;
    }
  return imageout;
}

// IMPORTANT NOTE
// imagein1 is image at time t-1
// imagein2 is image at time t
template< typename T, typename T1Ptr, typename T2Ptr >
image< T > edgeHaynesJain( const const_image_ref< T, T1Ptr > &imagein1,
                           const const_image_ref< T, T2Ptr > &imagein2 )
{
    MMERROR( imagein2.getWidth () == imagein1.getWidth () &&
             imagein2.getHeight() == imagein1.getHeight(),
             mimasexception, , "image_funcs::edgeHaynesJain - "
             "size of input images do not match" );

    return absolute<T>( edgeSobelNorm<T>( imagein2 ) * ( imagein2 -
                                                         imagein1 ) );
}


template<typename T, typename TPtr, typename U>
void halfResolution( const const_image_ref< T, TPtr > &imagein,
                     image_ref<U> &imageout)
{
    // note that this is only an estimate!
    static float gauss[3][3]={  {  1.0,  2.0, 1.0},
                               {  2.0,  4.0, 2.0},
                               {  1.0,  2.0, 1.0}};
    static float gaussfac=(1.0/16.0);

    assert( &imageout != &imagein );
    imageout.init(imagein.getWidth()/2, imagein.getHeight()/2);

    for (int j=1; j<imagein.getHeight(); j+=2)
        for (int i=1; i<imagein.getWidth(); i+=2)
        {
            float tempval=0.0;
            for (int y=-1; y<=1; y++)
                for (int x=-1; x<=1; x++)
                    tempval+=(imagein.pixel(i+x,j+y)*gauss[y+1][x+1]);
            imageout.pixel(i/2,j/2)=(U)(tempval*gaussfac);
        }
}


template<typename T>
void halfResolutionIt( image_ref<T> &imagein )
{
    image<T> tempimage;

    halfResolution(imagein, tempimage);

    imagein = tempimage;
}

// Note this assumes black edges on white background unlike halfResolution
// above
template<typename T, typename TPtr, typename U>
void halfResolutionEdgePyramid( const const_image_ref< T, TPtr > &imagein,
                                image<U> &imageout)
{
  // set background colour of object to be white
  assert( &imageout != &imagein );
  imageout.init(imagein.getWidth()/2, imagein.getHeight()/2);
  imageout.fill(255);

    for (int j=1; j<imagein.getHeight(); j+=2)
        for (int i=1; i<imagein.getWidth(); i+=2)
        {
            int tempval=0;
            for (int y=-1; y<=1; y++)
                for (int x=-1; x<=1; x++)
                    if (imagein.getPixel(i+x,j+y)>127)
                        tempval=255;
            
            imageout.pixel(i/2,j/2)=(U)tempval;
        }
}


template<typename T>
void halfResolutionEdgePyramidIt( image_ref<T> &imagein )
{
    image<T> tempimage;

    halfResolutionEdgePyramid(imagein, tempimage);

    imagein = tempimage;
}

// 3x3 median despecking filter
template< typename T, typename TPtr >
void despeckleMedian( const const_image_ref<T,TPtr> &imagein,
                      image<T> &imageout)
{
  std::vector<T> pixvals;
  assert( &imageout != &imagein );
  imageout.init(imagein.getWidth(),imagein.getHeight());
 
  for (int j=1; j<imagein.getHeight()-1; j++)
        for (int i=1; i<imagein.getWidth()-1; i++)
        {
            if (!pixvals.empty()) pixvals.clear();
            for (int y=-1; y<=1; y++)
                for (int x=-1; x<=1; x++)
                    pixvals.push_back(imagein.pixel(i+x,j+y));
            
            sort(pixvals.begin(), pixvals.end());
            imageout.pixel(i,j)=pixvals[pixvals.size()/2];
        }
 
    for (int j=0; j<imagein.getHeight(); j++)
    {
      imageout.pixel(0,j) = imagein.pixel(0,j);
      imageout.pixel(imagein.getWidth()-1,j)=imagein.pixel(imagein.getWidth()-1,j);
    }
    
    for (int i=0; i<imagein.getWidth(); i++)
    {
        imageout.pixel(i,0)=imagein.pixel(i,0);
      imageout.pixel(i,imagein.getHeight()-1)=imagein.pixel(i,imagein.getHeight()-1);
    }
}
 
template<typename T>
void despeckleMedianIt( image_ref<T> &imagein )
{
    image<T> tempimage;

    despeckleMedian(imagein, tempimage);

    imagein = tempimage;
}

template<typename T>
void chamfer(image_ref<T> &inimage)
{
    int nw,n,ne,w,e,sw,s,se;
    int fp;

    for (int j=1; j<inimage.getHeight()-1; j++)
        for (int i=1; i<inimage.getWidth(); i++)
        {
            fp=inimage.pixel(i,j);
            nw=4+inimage.pixel(i-1,j-1);
            n=3+inimage.pixel(i,j-1);
            w=3+inimage.pixel(i-1,j);
            sw=4+inimage.pixel(i-1,j+1);

            fp=fp<nw?fp:nw;
            fp=fp<n?fp:n;
            fp=fp<w?fp:w;
            fp=fp<sw?fp:sw;
            inimage.pixel(i,j)=fp;

        }

    for (int j=inimage.getHeight()-2; j>0; j--)
        for (int i=inimage.getWidth()-2; i>=0; i--)
        {
            fp=inimage.pixel(i,j);
            ne=4+inimage.pixel(i+1,j-1);
            e=3+inimage.pixel(i+1,j);
            s=3+inimage.pixel(i,j+1);
            se=4+inimage.pixel(i+1,j+1);

            fp=fp<ne?fp:ne;
            fp=fp<e?fp:e;
            fp=fp<s?fp:s;
            fp=fp<se?fp:se;
            inimage.pixel(i,j)=fp;

        }
    
    for (int i=0; i<inimage.getWidth(); i++)
    {
      inimage.pixel(i,0)=inimage.pixel(i,1);
      inimage.pixel(i,inimage.getHeight()-1)=inimage.pixel(i,inimage.getHeight()-2);
    }
    
    for (int j=0; j<inimage.getHeight(); j++)
    {
      inimage.pixel(0,j)=inimage.pixel(1,j);
      inimage.pixel(inimage.getWidth()-1,j)=inimage.pixel(inimage.getWidth()-2,j);
    }
 }


template<typename T,typename TPtr>
void paethRotateRadian(const const_image_ref<T,TPtr> &imagein,
                       image<T> &imageout,
                       float radian)
{
  assert( &imageout != &imagein );
  imageout.init(imagein.getWidth(),imagein.getHeight());
  imageout.fill(255);

    if (fabs(radian-M_PI)<1e-3) radian=radian<M_PI?M_PI-1e-3:M_PI+1e-3; // Quick hack to fix the problem when tan goes to infinity
    float s1=-tan(-radian/2.0);
    float s2=sin(-radian);

    float xr, yr;
    int x,y;
    for (int j=0; j<imageout.getHeight(); j++)
        for (int i=0; i<imageout.getWidth(); i++)
        {
            x=i-imageout.getWidth()/2;
            y=j-imageout.getHeight()/2;
            //x=i;
            //y=j;

            xr = (float)x + s1 * (float)y;
            yr = (float)y + s2 * xr;
            xr = xr + s1 * yr;

            xr+=(float)imageout.getWidth()/2;
            yr+=(float)imageout.getHeight()/2;
            imageout.pixel(i, j)=imagein.getPixel((int)xr,(int)yr);
            //imageout.setPixel(i,j, imagein.getPixel(i,j));
        }
}

template< typename T, typename T1Ptr, typename T2Ptr >
void multiLevelCorrelation( const const_image_ref< T, T1Ptr > &edgemap,
                            const const_image_ref< T, T2Ptr > &objectedgemap,
                            int levels, int &x, int &y, float &corrval,
                            bool chamferObject)
{
    image<T> object[levels], image[levels];
    
    image[0] = edgemap;
   // invert(image[0]);

    object[0] = objectedgemap;
   // invert(object[0]);
  
     for (int l=1; l<levels; l++)
    {
        halfResolutionEdgePyramid(image[l-1],image[l]);
        halfResolutionEdgePyramid(object[l-1],object[l]);
    }
    
    for (int l=0; l<levels; l++)
    {
      image[l] = (T)255 - image[l];
      object[l] = (T)255 - object[l];
        //image[l].display();
        //object[l].display();
        chamfer(image[l]);
        if (chamferObject) chamfer(object[l]);
    }
 
     int xmin = INT_MIN, ymin = INT_MIN;
    int xminsearch, xmaxsearch, yminsearch, ymaxsearch;
    float corr, mincorr;

    x=0; y=0;
    xminsearch=0;
    yminsearch=0;
    xmaxsearch=image[levels-1].getWidth();
    ymaxsearch=image[levels-1].getHeight();

    for (int l=levels-1; l>=0; l--)
    {
        //object[l].display();
        //image[l].display();

        mincorr=FLT_MAX;
        for (int j=yminsearch; j<ymaxsearch-object[l].getHeight(); j++)
            for (int i=xminsearch; i<xmaxsearch-object[l].getWidth(); i++)
            {
                corr=0.0;
                int count=0;
                for (int b=0; b<object[l].getHeight(); b++)
                    for (int a=0; a<object[l].getWidth(); a++)
                    {
                        if (!chamferObject) if (object[l].pixel(a,b)>127) continue;
                        corr+=(float)abs((int)image[l].pixel(i+a,j+b)-(int)object[l].pixel(a,b));
                        count++;
                    }

                corr/=count;
                if (corr<mincorr)
                {
                    xmin=i; ymin=j;
                    mincorr=corr;
                }
            }

        if (l!=0)
        {
            xminsearch=(xmin*2)-1-20;
            yminsearch=(ymin*2)-1-20;

            xmaxsearch=(xmin*2)-1+object[l-1].getWidth()+20;
            ymaxsearch=(ymin*2)-1+object[l-1].getHeight()+20;

            if (xminsearch<0) xminsearch=0;
            if (yminsearch<0) yminsearch=0;

            if (xmaxsearch>image[l-1].getWidth()) xmaxsearch=image[l-1].getWidth();
            if (ymaxsearch>image[l-1].getHeight()) ymaxsearch=image[l-1].getHeight();
        }


    }

    assert( xmin != INT_MIN && ymin != INT_MIN );
    x=xmin;
    y=ymin;
    corrval=mincorr;

}

template< typename T, typename T1Ptr, typename T2Ptr >
void correlation( const const_image_ref<T> &imagein,
                  const const_image_ref<T> &templatein,
                  int &x, int &y, float &corrval)
{

    int xmin = INT_MIN, ymin = INT_MIN;
    int xminsearch, xmaxsearch, yminsearch, ymaxsearch;
    float corr, mincorr;

    x=0; y=0;
    xminsearch=0;
    yminsearch=0;
    xmaxsearch=imagein.getWidth();
    ymaxsearch=imagein.getHeight();

    mincorr=FLT_MAX;
    for (int j=yminsearch; j<ymaxsearch-templatein.getHeight(); j++)
        for (int i=xminsearch; i<xmaxsearch-templatein.getWidth(); i++)
        {
            corr=0.0;
            for (int b=0; b<templatein.getHeight(); b++)
                for (int a=0; a<templatein.getWidth(); a++)
                {
                    //if (object[l].getPixel(a,b)>127) continue;
                    corr+=(float)abs((int)imagein.pixel(i+a,j+b)-(int)templatein.pixel(a,b));
                }

            if (corr<mincorr)
            {
                xmin=i; ymin=j;
                mincorr=corr;
            }
        }

    assert( xmin != INT_MIN && ymin != INT_MIN );
    x=xmin;
    y=ymin;
    corrval=mincorr;

}

template<typename T, typename T1Ptr, typename T2Ptr >
void correlationChamfer( const const_image_ref< T, T1Ptr > &imagein,
                         const const_image_ref< T, T2Ptr > &templatein,
                        int &x, int &y, float &corrval)
{

    int xmin = INT_MIN, ymin = INT_MIN;
    int xminsearch, xmaxsearch, yminsearch, ymaxsearch;
    float corr, mincorr;

    x=0; y=0;
    xminsearch=0;
    yminsearch=0;
    xmaxsearch=imagein.getWidth();
    ymaxsearch=imagein.getHeight();

    mincorr=FLT_MAX;
    for (int j=yminsearch; j<ymaxsearch-templatein.getHeight(); j++)
        for (int i=xminsearch; i<xmaxsearch-templatein.getWidth(); i++)
        {
            corr=0.0;
            for (int b=0; b<templatein.getHeight(); b++)
                for (int a=0; a<templatein.getWidth(); a++)
                {
                    if (templatein.pixel(a,b)>127) continue;
                    corr+=(float)abs((int)imagein.pixel(i+a,j+b)-(int)templatein.pixel(a,b));
                }

            if (corr<mincorr)
            {
                xmin=i; ymin=j;
                mincorr=corr;
            }
        }

    assert( xmin != INT_MIN && ymin != INT_MIN );
    x=xmin;
    y=ymin;
    corrval=mincorr;

}

template<typename T>
void rotationChamferCorrelationEdgeMap(image<T> &edgemap, image<T>
        &objectedgemap, int angle1, int angle2, int anglestep, int &x, int &y, int &outangle, float &corrval)
{
    image<T> chamferedImage, rotatedObject, chamferedObject;

    chamferedImage = (T)255 - edgemap;
    chamfer(chamferedImage);

  
    objectedgemap = (T)255 - objectedgemap;
    // Make sure that object boundaries are white:
    drawLine(objectedgemap, 0,0,0,objectedgemap.getHeight()-1,255);
    drawLine(objectedgemap, objectedgemap.getWidth()-1,0,objectedgemap.getWidth()-1,objectedgemap.getHeight()-1,255);
    drawLine(objectedgemap, 0,0,objectedgemap.getWidth()-1,objectedgemap.getHeight()-1,255);
    drawLine(objectedgemap, 0,objectedgemap.getHeight()-1,objectedgemap.getWidth()-1,objectedgemap.getHeight()-1,255);
    //chamfer(objectedgemap);

    float cval;
    int tx,ty;
    corrval=FLT_MAX;
    for (int angle=angle1; angle<=angle2; angle+=anglestep)
    {
   rotatedObject = objectedgemap;
        //invert(rotatedObject);
        paethRotateRadian(rotatedObject,chamferedObject,(float)angle*M_PI/180.0);
        //chamfer(chamferedObject);
        correlationChamfer(chamferedImage, chamferedObject, tx, ty, cval);

        if (cval<corrval)
        {
            corrval=cval;
            x=tx;
            y=ty;
            outangle=angle;
        }
    }
}

template< typename T, typename TPtr >
image< T > focusEnhance( const const_image_ref< T, TPtr > &im )
{
  const T enhance[3][3]={ { -1,  0, -1 },
                          {  0,  7,  0 },
                          { -1,  0, -1 } };
  const_image_ref< T > filter( &enhance[0][0], 3, 3 );
  return normalise( correlate( im, filter ), (T)0, (T)255 );
}

template< typename T, typename TPtr >
image< T > featureEnhance( const const_image_ref< T, TPtr > &im )
{
  const T enhance[3][3]={ {  0, -1,  0 },
                          { -1, 10, -1 },
                          {  0, -1,  0 } };
  const_image_ref< T > filter( &enhance[0][0], 3, 3 );
  return normalise( correlate( im, filter ), (T)0, (T)255 );
}

template< typename T, typename TPtr >
image< T > emboss( const const_image_ref< T, TPtr > &im )
{
  const T diff[3][3]={ { -1,  0,  0 },
                       {  0,  0,  0 },
                       {  0,  0,  1 } };
  const_image_ref< T > filter( &diff[0][0], 3, 3 );
  return normalise( correlate( im, filter ), (T)0, (T)255 );
}

template< typename T, typename TPtr >
image< T > softenHeavy( const const_image_ref< T, TPtr > &im )
{
  const T median[3]={ 1, 1, 1 };
  const_image_ref< T >
    filterx( &median[0], 3, 1 ),
    filtery( &median[0], 1, 3 );
 return
   normalise( correlate( correlate( im, filterx ), filtery ),
              (T)0, (T)255 );
}

template< typename T, typename TPtr >
image< T > softenMedium( const const_image_ref< T, TPtr > &im )
{
  const T soften[3][3]={ {  1,  1,  1 },
                         {  1,  2,  1 },
                         {  1,  1,  1 } };
  const_image_ref< T > filter( &soften[0][0], 3, 3 );
  return normalise( correlate( im, filter ), (T)0, (T)255 );
}

template< typename T, typename TPtr >
image< T > softenLight( const const_image_ref< T, TPtr > &im )
{
  const T soften[3][3]={ {  6,  12,  6 },
                         { 12,  25, 12 },
                         {  6,  12,  6 } };
  const_image_ref< T > filter( &soften[0][0], 3, 3 );
  return normalise( correlate( im, filter ), (T)0, (T)255 );
}

template< typename T, typename TPtr >
void oilPainting( const const_image_ref< T, TPtr > &imagein,
                  image<T> &imageout,
                  int regiondim)
{
   const int fy=regiondim;
    const int fx=regiondim;

    assert( &imageout != &imagein );
    imageout.init(imagein.getWidth(),imagein.getHeight());
    
    for (int y=0; y<imagein.getHeight(); y++)
        for (int x=0; x<imagein.getWidth(); x++)
        {
            if (y-fy/2<0 || y+fy/2>imagein.getHeight()-1 || x-fx/2<0 || x+fx/2>imagein.getWidth()-1)
            {
         imageout.pixel(x,y)=imagein.pixel(x,y);
                continue;
            }

       std::vector<int> pixcolor(regiondim*regiondim);
       std::vector<int> graylevel(256);
    
            int count=0;
            for (int j=-fy/2; j<=fy/2; j++)
                for (int i=-fx/2; i<=fx/2; i++)
                {
                    pixcolor[count]=imagein.pixel(x+i,y+j);
                    graylevel[pixcolor[count]]+=1;
                    
                    count++;
                }
            
            count=0;
            int intensity=0, maxuse=0;
            for (int j=-fy/2; j<=fy/2; j++)
                for (int i=-fx/2; i<=fx/2; i++)
                {
                    if (graylevel[pixcolor[count]]>maxuse)
                    {
                        maxuse=graylevel[pixcolor[count]];
                        intensity=pixcolor[count];
                    }

                    count++;
                }
            
            imageout.pixel(x,y)=(T)(intensity);
        }

    
}

//some common features here with vector class ...
//a cleverer man could probably use the same underlying base class
//but for now it doesnt matter
 template< typename T, typename T1Ptr, typename T2Ptr >
double dotProduct( const const_image_ref<T,T1Ptr> &im1,
                   const const_image_ref<T,T2Ptr> &im2 ) throw (mimasexception)
{
  MMERROR( im1.getWidth() == im2.getWidth() &&
           im1.getHeight() == im2.getHeight(),
           mimasexception, ,
           "Can't (won't) compute the dot product of two images of "
           "differing sizes." );

  double result = 0.0;

  const int size = (signed)im1.getSize();
  const T
    *p = im1.rawData(),
    *q = im2.rawData();

  for ( int i=0; i<size; i++ )
    result += *p++ * *q++;

  return result;
}

/** Fast non-maxima suppression.
    This function provides fast non-maxima suppression.
    The function is provided separately, so that one can use the
    Sobel-operator as well as the Gauss-gradient together with this
    method.
    @author Jan Wedekind (jan@wedesoft.de)    
    @date Mon Apr 10 13:53:00 2006 */
template<
  typename T1, typename T2,
  typename TAPtr, typename TBPtr, typename TCPtr
>
image< bool > nonMaximaSuppression
  ( const const_image_ref< T1, TAPtr > &gradientX,
    const const_image_ref< T1, TBPtr > &gradientY,
    const const_image_ref< T2, TCPtr > &gradientSqr,
    T2 threshold );

}

#include "image_funcs.tcc"

#endif