/************************************************************************/
/*                                                                      */
/*    vspline - a set of generic tools for creation and evaluation      */
/*              of uniform b-splines                                    */
/*                                                                      */
/*            Copyright 2015 - 2023 by Kay F. Jahnke                    */
/*                                                                      */
/*    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.                                   */
/*                                                                      */
/************************************************************************/

/// \file metafilter.cc
///
/// \brief filter by weighted summation of spline values
///
/// convolution forms a weighted sum of a window of values from
/// a raster. But some filters are easier to formulate by the formation
/// of a weighted sum over a pattern of 'pickup' points which do not
/// coincide with raster points. To realize such a filter, we first
/// create an interpolator over the raster data which provides a
/// contiguous signal. Then we can pick up values at arbitrary
/// offsets (delta) and form a weighted sum from them. Obviously this
/// is computationally intensive, as we need to evaluate the interpolator
/// for every contributing pickup point, but the method is extremely
/// flexible, and with fast, multithreaded, vectorized evaluation
/// it's still very fast.
///
/// This file has a simple example implementing an x-shaped filter.
///
/// The filter mechanism introduced here provides a reasonably efficient
/// way to code stuff like 'perceptrons' liberated from the corset of
/// the grid of the incoming raster data. It allows arbitrary patterns,
/// among them concentric polygons, cross shapes and many more, and
/// when using b-splines as 'underlying' interpolators, it's also an
/// option to not look at the spline itsef, but at it's derivative(s),
/// which opens up yet more interesting detectors. And with the meta
/// filter 'looking at' a continuous signal, we can pick up filtered
/// values from every valid continuous coordinate, not just from a
/// unit-spaced raster of discrete coordinates as we would when doing
/// a convolution, so we can throw in decimation/upsampling, geometric
/// transformations etc. pp.

#include <iostream>

#include <vspline/vspline.h>

#include <vigra/stdimage.hxx>
#include <vigra/imageinfo.hxx>
#include <vigra/impex.hxx>

// we silently assume we have a colour image
typedef vigra::RGBValue < float , 0 , 1 , 2 > pixel_type; 

// coordinate_type has a 2D coordinate
typedef vigra::TinyVector < float , 2 > coordinate_type ;

// type of b-spline object
typedef vspline::bspline < pixel_type , 2 > spline_type ;

// target_type is a 2D array of pixels  
typedef vigra::MultiArray < 2 , pixel_type > target_type ;

// struct meta_filter holds two MultiArrayViews: the first one,
// 'delta', holds a sequence of coordinate offsets. When the filter
// is applied at a location (x,y), the interpolator is evaluated at
// all points (x,y) + delta[i], and the resulting values are multipled
// with corresponding weights from the second MultiArrayView 'weight'
// and summed up to form the output of the filter.

struct meta_filter
{
  vigra::MultiArrayView < 1 , coordinate_type > delta ;
  vigra::MultiArrayView < 1 , float > weight ;
} ;

// the filter is built by functor composition. 'inner_type' is the
// type of the interpolator producing the pickup values. 

template < class inner_type >
struct ev_meta
: public vspline::unary_functor < coordinate_type , pixel_type >
{
  // the filter holds these values:

  const inner_type inner ;
  const meta_filter mf ;
  const size_t sz ;

  // which are initialized in it's constructor:

  ev_meta ( const inner_type & _inner ,
            const meta_filter & _mf )
  : inner ( _inner ) ,
    mf ( _mf ) ,
    sz ( _mf.delta.size() )
  {
    // just to make sure
    assert ( mf.delta.size() == mf.weight.size() ) ;
  } ;
  
  // since the code is the same for vectorized and unvectorized
  // operation, we can write a template:
  
  template < class IN , class OUT >
  void eval ( const IN & c ,
                    OUT & result ) const
  {
    // clear 'result'
    result = 0.0f ;

    // iterate over the deltas/weights
    for ( size_t i = 0 ; i < sz ; i++ )
    {
      OUT pickup ;
      auto cc ( c ) ;

      // apply the current delta to the coordinate

      cc[0] += mf.delta[i][0] ;
      cc[1] += mf.delta[i][1] ;

      // evaluate the interpolator to yield the pickup value

      inner.eval ( cc , pickup ) ;

      // apply the current weight to the pickup value and sum up

      result[0] += mf.weight [ i ] * pickup[0] ;
      result[1] += mf.weight [ i ] * pickup[1] ;
      result[2] += mf.weight [ i ] * pickup[2] ;
    }
  } 
  
} ;

int main ( int argc , char * argv[] )
{
  if ( argc < 2 )
  {
    std::cerr << "pass a colour image file as argument" << std::endl ;
    exit( -1 ) ;
  }

  // get the image file name

  vigra::ImageImportInfo imageInfo ( argv[1] ) ;

  // create cubic 2D b-spline object containing the image data

  spline_type bspl ( imageInfo.shape() ) ;

  // load the image data into the b-spline's core.

  vigra::importImage ( imageInfo , bspl.core ) ;
  
  // prefilter the b-spline

  bspl.prefilter() ;

  // create a 'safe' evaluator from the b-spline

  auto ev = make_safe_evaluator ( bspl ) ;

  // now we set up the meta filter. first the deltas:

  coordinate_type delta[]
   {
     { -1.6f , -1.6f } ,
     {  1.6f , -1.6f } ,
     { -0.7f , -0.7f } ,
     {  0.7f , -0.7f } ,
     {  0.0f ,  0.0f } ,
     {  0.7f , -0.7f } ,
     {  0.7f ,  0.7f } ,
     {  1.6f , -1.6f } ,
     {  1.6f ,  1.6f }     
   } ;

  // then the weights

  float weight[]
   { .05f ,
     .05f ,
     .1f ,
     .1f ,
     .4f ,
     .1f ,
     .1f ,
     .05f ,
     .05f
  } ;

  // both are now packaged in the meta_filter struct

  meta_filter mf ;

  mf.delta = vigra::MultiArrayView < 1 , coordinate_type >
    ( vigra::Shape1 ( 9 ) , delta ) ;

  mf.weight = vigra::MultiArrayView < 1 , float >
    ( vigra::Shape1 ( 9 ) , weight ) ;

  // now we create the actual meta filter function from the
  // interpolator and the filter data

  auto mev = ev_meta < decltype ( ev ) >
    ( ev , mf ) ;

  // this is where the result should go:

  target_type target ( imageInfo.shape() ) ;

  // for the sake of this demonstration, we simply use vspline::transform
  // with no source argument, which evaluates 'mev' for every coordinate
  // in 'target', an 'index-based transform'.

  vspline::transform ( mev , target ) ;

  // store the result with vigra impex

  vigra::ImageExportInfo eximageInfo ( "metafilter.tif" );
  
  std::cout << "storing the target image as 'metafilter.tif'" << std::endl ;
  
  vigra::exportImage ( target ,
                       eximageInfo
                       .setPixelType("UINT8") ) ;
  
  exit ( 0 ) ;
}
