core/vil/algo/vil_convolve_1d.h
Go to the documentation of this file.
00001 // This is core/vil/algo/vil_convolve_1d.h
00002 #ifndef vil_convolve_1d_h_
00003 #define vil_convolve_1d_h_
00004 //:
00005 // \file
00006 // \brief 1D Convolution with cunning boundary options
00007 // \author Tim Cootes, Ian Scott (based on work by fsm)
00008 //
00009 // \note  The convolution operation is defined by
00010 //    $(f*g)(x) = \int f(x-y) g(y) dy$
00011 // i.e. the kernel g is reflected before the integration is performed.
00012 // If you don't want this to happen, the behaviour you want is not
00013 // called "convolution". So don't break the convolution routines in
00014 // that particular way.
00015 
00016 #include <vcl_compiler.h>
00017 #include <vcl_algorithm.h>
00018 #include <vcl_cstdlib.h> // for vcl_abort()
00019 #include <vcl_cstring.h>
00020 #include <vcl_cassert.h>
00021 #include <vcl_iostream.h>
00022 #include <vil/vil_image_view.h>
00023 #include <vil/vil_image_resource.h>
00024 #include <vil/vil_property.h>
00025 
00026 
00027 //: Available options for boundary behavior
00028 // When convolving a finite signal the boundaries may be
00029 // treated in various ways which can often be expressed in terms
00030 // of ways to extend the signal outside its original range.
00031 enum vil_convolve_boundary_option
00032 {
00033   //: Do not fill destination edges at all.
00034   // i.e. leave them unchanged.
00035   vil_convolve_ignore_edge,
00036 
00037   //: Do not to extend the signal, but pad with zeros.
00038   // \verbatim
00039   //     |                               |
00040   // K                       ----*-------
00041   // in   ... ---------------------------
00042   // out  ... --------------------0000000
00043   // \endverbatim
00044   vil_convolve_no_extend,
00045 
00046   //: Zero-extend the input signal beyond the boundary.
00047   // \verbatim
00048   //     |                               |
00049   // K                              ----*--------
00050   // in   ... ---------------------------000000000000...
00051   // out  ... ---------------------------
00052   // \endverbatim
00053   vil_convolve_zero_extend,
00054 
00055   //: Extend the signal to be constant beyond the boundary.
00056   // \verbatim
00057   //     |                               |
00058   // K                              ----*--------
00059   // in   ... --------------------------aaaaaaaaaaaaa...
00060   // out  ... ---------------------------
00061   // \endverbatim
00062   vil_convolve_constant_extend,
00063 
00064   //: Extend the signal periodically beyond the boundary.
00065   // \verbatim
00066   //     |                               |
00067   // K                              ----*--------
00068   // in   abc...-------------------------abc...------..
00069   // out  ... ---------------------------
00070   // \endverbatim
00071   vil_convolve_periodic_extend,
00072 
00073   //: Extend the signal by reflection about the boundary.
00074   // \verbatim
00075   //     |                               |
00076   // K                              ----*--------
00077   // in   ... -------------------...edcbabcde...
00078   // out  ... ---------------------------
00079   // \endverbatim
00080   vil_convolve_reflect_extend,
00081 
00082   //: Kernel is trimmed and reweighed, to allow convolution up to boundary.
00083   // This one is slightly different. The input signal is not
00084   // extended in any way, but the kernel is trimmed to allow
00085   // convolution to proceed up to the boundary and reweighed
00086   // to keep the total area the same.
00087   // \note may not work with kernels which take negative values.
00088   vil_convolve_trim
00089 };
00090 
00091 //: Convolve edge with kernel[x*kstep] x in [k_lo,k_hi] (k_hi>=0)
00092 //  Fills only edge: dest[i], i=0..(k_hi-1)
00093 template <class srcT, class destT, class kernelT, class accumT>
00094 inline void vil_convolve_edge_1d(const srcT* src, unsigned n, vcl_ptrdiff_t s_step,
00095                                  destT* dest, vcl_ptrdiff_t d_step,
00096                                  const kernelT* kernel,
00097                                  vcl_ptrdiff_t k_lo, vcl_ptrdiff_t k_hi,
00098                                  vcl_ptrdiff_t kstep, accumT,
00099                                  vil_convolve_boundary_option option)
00100 {
00101   switch (option)
00102   {
00103    case vil_convolve_ignore_edge:
00104     return;
00105    case vil_convolve_no_extend:
00106     // Initialise first elements of row to zero
00107     for (vcl_ptrdiff_t i=-k_hi;i<0;++i,dest+=d_step)
00108       *dest = 0;
00109     return;
00110    case vil_convolve_zero_extend:
00111     // Assume src[i]==0 for i<0
00112 //    for (vcl_ptrdiff_t i=-k_hi+1;i<=0;++i,dest+=d_step,src+=s_step)
00113     for (vcl_ptrdiff_t i=0;i<k_hi;++i,dest+=d_step)
00114     {
00115       accumT sum = 0;
00116       const srcT* s = src;
00117       const kernelT* k = kernel+i*kstep;
00118       for (vcl_ptrdiff_t j=i;j>=k_lo;--j,s+=s_step,k-=kstep)
00119         sum+= (accumT)((*s)*(*k));
00120       *dest=(destT)sum;
00121     }
00122     return;
00123    case vil_convolve_constant_extend:
00124    {
00125     // Assume src[i]=src[0] for i<0
00126     vcl_ptrdiff_t i_max = k_hi-1;
00127     for (vcl_ptrdiff_t i=0;i<=i_max;++i)
00128     {
00129       accumT sum=0;
00130       for (vcl_ptrdiff_t j=-k_hi;j<=-k_lo;++j)
00131       {
00132         if ((i+j)<0) sum+=(accumT)(src[0]*kernel[j*(-kstep)]);
00133         else         sum+=(accumT)(src[(i+j)*s_step]*kernel[j*(-kstep)]);
00134       }
00135       dest[i*d_step]=(destT)sum;
00136     }
00137     return;
00138    }
00139    case vil_convolve_reflect_extend:
00140    {
00141     // Assume src[i]=src[0] for i<0
00142     vcl_ptrdiff_t i_max = k_hi-1;
00143     for (vcl_ptrdiff_t i=0;i<=i_max;++i)
00144     {
00145       accumT sum=0;
00146       for (vcl_ptrdiff_t j=-k_hi;j<=-k_lo;++j)
00147       {
00148         if ((i+j)<0) sum+=(accumT)(src[-(i+j)*s_step]*kernel[j*(-kstep)]);
00149         else         sum+=(accumT)(src[(i+j)*s_step]*kernel[j*(-kstep)]);
00150       }
00151       dest[i*d_step]=(destT)sum;
00152     }
00153     return;
00154    }
00155    case vil_convolve_periodic_extend:
00156    {
00157     // Assume src[i]=src[n+i] for i<0
00158     vcl_ptrdiff_t i_max = k_hi-1;
00159     for (int i=0;i<=i_max;++i)
00160     {
00161       accumT sum=0;
00162       for (vcl_ptrdiff_t j=k_hi;j>=k_lo;--j)
00163         sum+=(accumT)(src[((i-j+n)%n)*s_step]*kernel[j*kstep]);
00164       dest[i*d_step]=(destT)sum;
00165     }
00166     return;
00167    }
00168    case vil_convolve_trim:
00169    {
00170     // Truncate and reweight kernel
00171     accumT k_sum_all=0;
00172     for (vcl_ptrdiff_t j=-k_hi;j<=-k_lo;++j) k_sum_all+=(accumT)(kernel[j*(-kstep)]);
00173 
00174     vcl_ptrdiff_t i_max = k_hi-1;
00175     for (vcl_ptrdiff_t i=0;i<=i_max;++i)
00176     {
00177       accumT sum=0;
00178       accumT k_sum=0;
00179       // Sum elements which overlap src
00180       // ie i+j>=0  (so j starts at -i)
00181       for (vcl_ptrdiff_t j=-i;j<=-k_lo;++j)
00182       {
00183         sum+=(accumT)(src[(i+j)*s_step]*kernel[j*(-kstep)]);
00184         k_sum += (accumT)(kernel[j*(-kstep)]);
00185       }
00186       dest[i*d_step]=(destT)(sum*k_sum_all/k_sum);
00187     }
00188     return;
00189    }
00190    default:
00191     vcl_cout<<"ERROR: vil_convolve_edge_1d: "
00192             <<"Sorry, can't deal with supplied edge option.\n";
00193     vcl_abort();
00194   }
00195 }
00196 
00197 //: Convolve kernel[x] (x in [k_lo,k_hi]) with srcT
00198 // Assumes dest and src same size (nx)
00199 // Kernel must not be larger than nx;
00200 template <class srcT, class destT, class kernelT, class accumT>
00201 inline void vil_convolve_1d(const srcT* src0, unsigned nx, vcl_ptrdiff_t s_step,
00202                             destT* dest0, vcl_ptrdiff_t d_step,
00203                             const kernelT* kernel,
00204                             vcl_ptrdiff_t k_lo, vcl_ptrdiff_t k_hi,
00205                             accumT ac,
00206                             vil_convolve_boundary_option start_option,
00207                             vil_convolve_boundary_option end_option)
00208 {
00209   assert(k_hi - k_lo < int(nx));
00210 
00211   // Deal with start (fill elements 0..1+k_hi of dest)
00212   vil_convolve_edge_1d(src0,nx,s_step,dest0,d_step,kernel,k_lo,k_hi,1,ac,start_option);
00213 
00214   const kernelT* k_rbegin = kernel+k_hi;
00215   const kernelT* k_rend   = kernel+k_lo-1;
00216   assert(k_rbegin >= k_rend);
00217   const srcT* src = src0;
00218 
00219   for (destT       * dest = dest0 + d_step*k_hi,
00220        * const   end_dest = dest0 + d_step*(int(nx)+k_lo);
00221        dest!=end_dest;
00222        dest+=d_step,src+=s_step)
00223   {
00224     accumT sum = 0;
00225     const srcT* s= src;
00226     for (const kernelT *k = k_rbegin;k!=k_rend;--k,s+=s_step)
00227       sum+= (accumT)((*k)*(*s));
00228     *dest = destT(sum);
00229   }
00230 
00231   // Deal with end  (reflect data and kernel!)
00232   vil_convolve_edge_1d(src0+(nx-1)*s_step,nx,-s_step,
00233                        dest0+(nx-1)*d_step,-d_step,
00234                        kernel,-k_hi,-k_lo,-1,ac,end_option);
00235 }
00236 
00237 //: Convolve kernel[i] (i in [k_lo,k_hi]) with srcT in i-direction
00238 // On exit dest_im(i,j) = sum src(i-x,j)*kernel(x)  (x=k_lo..k_hi)
00239 // \note  This function reverses the kernel. If you don't want the
00240 // kernel reversed, use vil_correlate_1d instead. The kernel must
00241 // not be larger than src_im.ni()
00242 // \param kernel should point to tap 0.
00243 // \param dest_im will be resized to size of src_im.
00244 // \relatesalso vil_image_view
00245 template <class srcT, class destT, class kernelT, class accumT>
00246 inline void vil_convolve_1d(const vil_image_view<srcT>& src_im,
00247                             vil_image_view<destT>& dest_im,
00248                             const kernelT* kernel,
00249                             vcl_ptrdiff_t k_lo, vcl_ptrdiff_t k_hi,
00250                             accumT ac,
00251                             vil_convolve_boundary_option start_option,
00252                             vil_convolve_boundary_option end_option)
00253 {
00254   unsigned n_i = src_im.ni();
00255   unsigned n_j = src_im.nj();
00256   assert(k_hi - k_lo +1 <= (int) n_i);
00257   vcl_ptrdiff_t s_istep = src_im.istep(), s_jstep = src_im.jstep();
00258 
00259   dest_im.set_size(n_i,n_j,src_im.nplanes());
00260   vcl_ptrdiff_t d_istep = dest_im.istep(),d_jstep = dest_im.jstep();
00261 
00262   for (unsigned int p=0;p<src_im.nplanes();++p)
00263   {
00264     // Select first row of p-th plane
00265     const srcT*  src_row  = src_im.top_left_ptr()+p*src_im.planestep();
00266     destT* dest_row = dest_im.top_left_ptr()+p*dest_im.planestep();
00267 
00268     // Apply convolution to each row in turn
00269     // First check if either istep is 1 for speed optimisation.
00270 
00271     if (s_istep == 1)
00272     {
00273       if (d_istep == 1)
00274         for (unsigned int j=0;j<n_j;++j,src_row+=s_jstep,dest_row+=d_jstep)
00275           vil_convolve_1d(src_row,n_i,1,  dest_row,1,
00276                           kernel,k_lo,k_hi,ac,start_option,end_option);
00277       else
00278         for (unsigned int j=0;j<n_j;++j,src_row+=s_jstep,dest_row+=d_jstep)
00279           vil_convolve_1d(src_row,n_i,1,  dest_row,d_istep,
00280                           kernel,k_lo,k_hi,ac,start_option,end_option);
00281     }
00282     else
00283     {
00284       if (d_istep == 1)
00285         for (unsigned int j=0;j<n_j;++j,src_row+=s_jstep,dest_row+=d_jstep)
00286           vil_convolve_1d(src_row,n_i,s_istep,  dest_row,1,
00287                           kernel,k_lo,k_hi,ac,start_option,end_option);
00288       else
00289         for (unsigned int j=0;j<n_j;++j,src_row+=s_jstep,dest_row+=d_jstep)
00290           vil_convolve_1d(src_row,n_i,s_istep,  dest_row,d_istep,
00291                           kernel,k_lo,k_hi,ac,start_option,end_option);
00292     }
00293   }
00294 }
00295 
00296 template <class destT, class kernelT, class accumT>
00297 vil_image_resource_sptr vil_convolve_1d(
00298                const vil_image_resource_sptr& src_im,
00299                const destT dt,
00300                const kernelT* kernel, int k_lo, int k_hi,
00301                const accumT ac,
00302                vil_convolve_boundary_option start_option,
00303                vil_convolve_boundary_option end_option);
00304 
00305 //: A resource adaptor that behaves like a convolved version of its input
00306 template <class kernelT, class accumT, class destT>
00307 class vil_convolve_1d_resource : public vil_image_resource
00308 {
00309   //: Construct a convolve filter.
00310   // You can't create one of these directly, use vil_convolve_1d instead
00311   vil_convolve_1d_resource(const vil_image_resource_sptr& src,
00312                            const kernelT* kernel, int k_lo, int k_hi,
00313                            vil_convolve_boundary_option start_option,
00314                            vil_convolve_boundary_option end_option)  :
00315       src_(src), kernel_(kernel), klo_(k_lo), khi_(k_hi),
00316       start_option_(start_option), end_option_(end_option)
00317     {
00318       // Can't do period extension yet.
00319       assert (start_option != vil_convolve_periodic_extend ||
00320               end_option != vil_convolve_periodic_extend);
00321     }
00322 
00323   friend vil_image_resource_sptr vil_convolve_1d VCL_NULL_TMPL_ARGS (
00324     const vil_image_resource_sptr& src_im, const destT dt, const kernelT* kernel,
00325     int k_lo, int k_hi, const accumT ac,
00326     vil_convolve_boundary_option start_option,
00327     vil_convolve_boundary_option end_option);
00328 
00329  public:
00330   virtual vil_image_view_base_sptr get_copy_view(unsigned i0, unsigned n_i,
00331                                                  unsigned j0, unsigned n_j) const
00332   {
00333     if (i0 + n_i > src_->ni() || j0 + n_j > src_->nj())  return 0;
00334     const unsigned lsrc = (unsigned) vcl_max(0,(int)i0 + klo_); // lhs of input window
00335     const unsigned hsrc = vcl_min(src_->ni(),i0 + n_i - klo_ + khi_); // 1+rhs of input window.
00336     const unsigned lboundary = vcl_min((unsigned) -klo_, i0); // width of lhs boundary area.
00337     assert (hsrc > lsrc);
00338     vil_image_view_base_sptr vs = src_->get_view(lsrc, hsrc-lsrc, j0, n_j);
00339     vil_image_view<destT> dest(vs->ni(), vs->nj(), vs->nplanes());
00340     switch (vs->pixel_format())
00341     {
00342 #define macro( F , T ) \
00343      case F : \
00344       vil_convolve_1d(static_cast<vil_image_view<T >&>(*vs),dest, \
00345                       kernel_, klo_, khi_, accumT(), start_option_, end_option_); \
00346       return new vil_image_view<destT>(vil_crop(dest, lboundary, n_i, 0, n_j));
00347 
00348       macro(VIL_PIXEL_FORMAT_BYTE , vxl_byte )
00349       macro(VIL_PIXEL_FORMAT_SBYTE , vxl_sbyte )
00350       macro(VIL_PIXEL_FORMAT_UINT_32 , vxl_uint_32 )
00351       macro(VIL_PIXEL_FORMAT_UINT_16 , vxl_uint_16 )
00352       macro(VIL_PIXEL_FORMAT_INT_32 , vxl_int_32 )
00353       macro(VIL_PIXEL_FORMAT_INT_16 , vxl_int_16 )
00354       macro(VIL_PIXEL_FORMAT_BOOL , bool )
00355       macro(VIL_PIXEL_FORMAT_FLOAT , float )
00356       macro(VIL_PIXEL_FORMAT_DOUBLE , double )
00357 // complex<float> should work - but causes all manner of compiler template errors.
00358 // maybe need a better compiler, maybe there is a code fix - IMS
00359 #undef macro
00360      default:
00361       return 0;
00362     }
00363   }
00364 
00365   virtual unsigned nplanes() const { return src_->nplanes(); }
00366   virtual unsigned ni() const { return src_->ni(); }
00367   virtual unsigned nj() const { return src_->nj(); }
00368 
00369   virtual enum vil_pixel_format pixel_format() const
00370   { return vil_pixel_format_of(accumT()); }
00371 
00372 
00373   //: Put the data in this view back into the image source.
00374   virtual bool put_view(const vil_image_view_base&  /*im*/, unsigned  /*i0*/, unsigned  /*j0*/)
00375   {
00376     vcl_cerr << "WARNING: vil_convolve_1d_resource::put_back\n"
00377              << "\tYou can't push data back into a convolve filter.\n";
00378     return false;
00379   }
00380 
00381   //: Extra property information
00382   virtual bool get_property(char const* tag, void* property_value = 0) const
00383   {
00384     if (0==vcl_strcmp(tag, vil_property_read_only))
00385       return property_value ? (*static_cast<bool*>(property_value)) = true : true;
00386 
00387     return src_->get_property(tag, property_value);
00388   }
00389 
00390  protected:
00391   vil_image_resource_sptr src_;
00392   const kernelT* kernel_;
00393   int klo_, khi_;
00394   vil_convolve_boundary_option start_option_, end_option_;
00395 };
00396 
00397 //: Create an image_resource object which convolve kernel[x] x in [k_lo,k_hi] with srcT
00398 // \note  This function reverses the kernel. If you don't want the
00399 // kernel reversed, use vil_correlate_1d instead.
00400 // \param kernel should point to tap 0.
00401 // \relatesalso vil_image_resource
00402 template <class destT, class kernelT, class accumT>
00403 vil_image_resource_sptr vil_convolve_1d(
00404                          const vil_image_resource_sptr& src_im,
00405                          const destT  /*dt*/,
00406                          const kernelT* kernel, int k_lo, int k_hi,
00407                          const accumT,
00408                          vil_convolve_boundary_option start_option,
00409                          vil_convolve_boundary_option end_option)
00410 {
00411   return new vil_convolve_1d_resource<kernelT, accumT, destT>(src_im, kernel, k_lo, k_hi, start_option, end_option);
00412 }
00413 
00414 #endif // vil_convolve_1d_h_
00415