/** 
 * @file LocalFeatureCentrist.cpp
 * @brief Implementation of the LocalFeatureCentrist feature published in  "CENTRIST: A Visual Descriptor for Scene Categorization" (PAMI 2011)
 * @author Alexander Freytag
 * @date 12/06/2011
*/

// STL includes
#include <iostream>

// nice-vislearning includes
#include "vislearning/features/localfeatures/LocalFeatureCentrist.h"


using namespace OBJREC;
using namespace std;
using namespace NICE;

/* protected methods*/

/**
 * @brief: perform centrist transformation for a single pixel 
 * @author Alexander Freytag
 * @date 12/06/2011
 */
int LocalFeatureCentrist::CensusTransform(const NICE::Image & img, const int & x, const int & y) const
{
  int index(0);
  if (! img.isWithinImage(x,y)) return index;
  
  if( (img.isWithinImage(x-1,y-1)) && (img.getPixelInt(x,y)<=img.getPixelInt(x-1,y-1)) ) index |= 0x80;
  if( (img.isWithinImage(x-1,y))   && (img.getPixelInt(x,y)<=img.getPixelInt(x-1,y)) )   index |= 0x40;
  if( (img.isWithinImage(x-1,y+1)) && (img.getPixelInt(x,y)<=img.getPixelInt(x-1,y+1)) ) index |= 0x20;
  if( (img.isWithinImage(x,y-1))   && (img.getPixelInt(x,y)<=img.getPixelInt(x,y-1)) )   index |= 0x10;
  if( (img.isWithinImage(x,y+1))   && (img.getPixelInt(x,y)<=img.getPixelInt(x,y+1)) )   index |= 0x08;
  if( (img.isWithinImage(x+1,y-1)) && (img.getPixelInt(x,y)<=img.getPixelInt(x+1,y-1)) ) index |= 0x04;
  if( (img.isWithinImage(x+1,y))   && (img.getPixelInt(x,y)<=img.getPixelInt(x+1,y)) )   index |= 0x02;
  if( (img.isWithinImage(x+1,y+1)) && (img.getPixelInt(x,y)<=img.getPixelInt(x+1,y+1)) ) index |= 0x01;
  return index;
}

/**
 * @brief: perform centrist transformation for a single pixel 
 * @author Alexander Freytag
 * @date 12/06/2011
 */
int LocalFeatureCentrist::CensusTransform(const NICE::ColorImage & img, const int & x, const int & y, const int & channel) const
{
  int index(0);
  if (! img.isWithinImage(x,y)) return index;
  
  if( (img.isWithinImage(x-1,y-1)) && (img.getPixelInt(x,y, channel)<=img.getPixelInt(x-1,y-1, channel)) ) index |= 0x80;
  if( (img.isWithinImage(x-1,y))   && (img.getPixelInt(x,y, channel)<=img.getPixelInt(x-1,y, channel)) )   index |= 0x40;
  if( (img.isWithinImage(x-1,y+1)) && (img.getPixelInt(x,y, channel)<=img.getPixelInt(x-1,y+1, channel)) ) index |= 0x20;
  if( (img.isWithinImage(x,y-1))   && (img.getPixelInt(x,y, channel)<=img.getPixelInt(x,y-1, channel)) )   index |= 0x10;
  if( (img.isWithinImage(x,y+1))   && (img.getPixelInt(x,y, channel)<=img.getPixelInt(x,y+1, channel)) )   index |= 0x08;
  if( (img.isWithinImage(x+1,y-1)) && (img.getPixelInt(x,y, channel)<=img.getPixelInt(x+1,y-1, channel)) ) index |= 0x04;
  if( (img.isWithinImage(x+1,y))   && (img.getPixelInt(x,y, channel)<=img.getPixelInt(x+1,y, channel)) )   index |= 0x02;
  if( (img.isWithinImage(x+1,y+1)) && (img.getPixelInt(x,y, channel)<=img.getPixelInt(x+1,y+1, channel)) ) index |= 0x01;
  return index;
}

/**
 * @brief: generate CT histogram for a given rectangle: pixels in [xi yi]-(xa,ya) -- including (xi,yi) but excluding (xa,ya)
 * @author Alexander Freytag
 * @date 12/06/2011
 */
void LocalFeatureCentrist::GenerateHistForOneRect(const NICE::Image & img, const int & xi, const int & xa, const int & yi, const int & ya, NICE::Vector & feature) const
{
// 	double pixelSum(0.0);
// 	double pixelSquare(0.0);
  feature.resize(256);
  feature.set(0.0);
  
  for (int j = yi; j < ya; j++)
  {
    for (int i = xi; i < xa; i++)
    {
// 			pixelSum += img.getPixelInt(i,j);
// 			pixelSquare += img.getPixelInt(i,j)*img.getPixelInt(i,j);
      feature[CensusTransform(img,i,j)]++;
    }
  }
  
  // normalize histogram to have 0 mean, remove the first and last entry, and normalize to have unit norm
  double sum(feature.Sum()/256.0);//shift more efficient?
  
  // normalize histogram to have 0 mean
  //but why?
  feature -= sum;
  
  //remove the first and last entry
  feature[0] = 0.0;
  feature[255] = 0.0;

  //normalization - here done using unit L2-norm
  feature.normalizeL2();
}

/**
 * @brief: generate CT histogram for a given rectangle: pixels in [xi yi]-(xa,ya) -- including (xi,yi) but excluding (xa,ya)
 * @author Alexander Freytag
 * @date 12/06/2011
 */
void LocalFeatureCentrist::GenerateHistForOneRect(const NICE::ColorImage & img, const int & xi, const int & xa, const int & yi, const int & ya, NICE::Vector & feature) const
{
// 	double pixelSum(0.0);
// 	double pixelSquare(0.0);
  int nrChannel (img.channels());
  feature.resize(256*nrChannel);
  feature.set(0.0);
  
  for (int channel = 0; channel < nrChannel; channel++)
  {
    for (int j = yi; j < ya; j++)
    {
      for (int i = xi; i < xa; i++)
      {
// 				pixelSum += img.getPixelInt(i,j, channel);
// 				pixelSquare += img.getPixelInt(i,j, channel)*img.getPixelInt(i,j, channel);
        feature[256*channel+this->CensusTransform(img,i,j,channel)]++;
      }
    }
  }

  // normalize histogram to have 0 mean, remove the first and last entry, and normalize to have unit norm
  double sum(feature.Sum()/256.0);//shift more efficient?
  
  // normalize histogram to have 0 mean
  //but why?
  feature -= sum;
  
  //remove the first and last entry
  feature[0] = 0.0;
  feature[255] = 0.0;

  //normalization - here done using unit L2-norm
  feature.normalizeL2();
}

/**
 * @brief Computes several CENTRIST descriptors for each of the given positions om a local neighborhood
 * @author Alexander Freytag
 * @date 12/06/2011
 */
void LocalFeatureCentrist::computeDesc( const NICE::Image & img, NICE::VVector & positions, NICE::VVector & descriptors ) const
{
  descriptors.clear();
  for (NICE::VVector::const_iterator it = positions.begin(); it != positions.end(); it++)
  {
    NICE::Vector descriptor;
    
    this->GenerateHistForOneRect(img, (*it)[0]-i_sizeNeighborhood/2, (*it)[0]+i_sizeNeighborhood/2, (*it)[1]-i_sizeNeighborhood/2, (*it)[1]+i_sizeNeighborhood/2, descriptor);
    
    descriptors.push_back(descriptor);
  }
}

/**
 * @brief Computes several CENTRIST descriptors for each of the given positions om a local neighborhood
 * @author Alexander Freytag
 * @date 12/06/2011
 */
void LocalFeatureCentrist::computeDesc( const NICE::ColorImage & img, NICE::VVector & positions, NICE::VVector & descriptors ) const
{
  descriptors.clear();
  for (NICE::VVector::const_iterator it = positions.begin(); it != positions.end(); it++)
  {
    NICE::Vector descriptor;
    
    this->GenerateHistForOneRect(img, (*it)[0]-i_sizeNeighborhood/2, (*it)[0]+i_sizeNeighborhood/2, (*it)[1]-i_sizeNeighborhood/2, (*it)[1]+i_sizeNeighborhood/2, descriptor);
    
    descriptors.push_back(descriptor);
  }
}

/* public methods*/

///////////////////// ///////////////////// /////////////////////
//                   CONSTRUCTORS / DESTRUCTORS
///////////////////// ///////////////////// /////////////////////

LocalFeatureCentrist::LocalFeatureCentrist() : LocalFeature () 
{
  this->i_sizeNeighborhood = 16;
}


LocalFeatureCentrist::LocalFeatureCentrist( const NICE::Config * _conf )
{
  this->initFromConfig( _conf );
}

LocalFeatureCentrist::~LocalFeatureCentrist()
{
}

void OBJREC::LocalFeatureCentrist::initFromConfig( const NICE::Config* _conf, const std::string& _confSection )
{
  this->i_sizeNeighborhood = _conf->gI(_confSection, "i_sizeNeighborhood", 16);
}

///////////////////// ///////////////////// /////////////////////
//                      FEATURE STUFF
///////////////////// ///////////////////// ////////////////// 

int LocalFeatureCentrist::getDescSize() const
{
  // we have 8 neighbors, so the size is always 256 with the given implementation (without spatial levels, ...)
  return 256;
}

/**
 * @brief Computes several CENTRIST descriptors for each of the given positions on a local neighborhood
 * @author Alexander Freytag
 * @date 12/06/2011
 */
int LocalFeatureCentrist::getDescriptors ( const NICE::Image & img, VVector & positions, VVector & descriptors ) const
{
  this->computeDesc( img, positions, descriptors );
  return 0;
}

/**
 * @brief Computes several CENTRIST descriptors for each of the given positions on a local neighborhood
 * @author Alexander Freytag
 * @date 12/06/2011
 */
int LocalFeatureCentrist::getDescriptors ( const NICE::ColorImage & img, NICE::VVector & positions, NICE::VVector & descriptors) const
{
  this->computeDesc( img, positions, descriptors );
  return 0;
}

/**
 * @brief Visualizes CENTRIST descriptors, not implemented yet!
 * @author Alexander Freytag
 * @date 12/06/2011
 */
void LocalFeatureCentrist::visualizeFeatures ( NICE::Image & mark,
                                               const VVector & positions,
                                               size_t color ) const
{
  throw NICE::Exception ( "LocalFeatureCentrist::visualizeFeatures -- currently not implemented." );
}

///////////////////// INTERFACE PERSISTENT /////////////////////
// interface specific methods for store and restore
///////////////////// INTERFACE PERSISTENT ///////////////////// 

void LocalFeatureCentrist::restore ( std::istream & is, int format )
{
  //delete everything we knew so far...
  this->clear();
  
  
  if ( is.good() )
  {
    
    std::string tmp;
    is >> tmp; //class name 
    
    if ( ! this->isStartTag( tmp, "LocalFeatureCentrist" ) )
    {
      std::cerr << " WARNING - attempt to restore LocalFeatureCentrist, but start flag " << tmp << " does not match! Aborting... " << std::endl;
      throw;
    }   
    
    bool b_endOfBlock ( false ) ;
    
    while ( !b_endOfBlock )
    {
      is >> tmp; // start of block 
      
      if ( this->isEndTag( tmp, "LocalFeatureCentrist" ) )
      {
        b_endOfBlock = true;
        continue;
      }      
      
      tmp = this->removeStartTag ( tmp );    
      
      if ( tmp.compare("i_sizeNeighborhood") == 0 )
      {
        is >> this->i_sizeNeighborhood;
        is >> tmp; // end of block 
        tmp = this->removeEndTag ( tmp );
      }      
      else
      {
      std::cerr << "WARNING -- unexpected LocalFeatureCentrist object -- " << tmp << " -- for restoration... aborting" << std::endl;
      throw;
      }
    }
  }
  else
  {
    std::cerr << "LocalFeatureCentrist::restore -- InStream not initialized - restoring not possible!" << std::endl;
    throw;
  }
}

void LocalFeatureCentrist::store ( std::ostream & os, int format ) const
{ 
  if (os.good())
  {
    // show starting point
    os << this->createStartTag( "LocalFeatureCentrist" ) << std::endl;    
    
    os << this->createStartTag( "i_sizeNeighborhood" ) << std::endl;
    os << this->i_sizeNeighborhood << std::endl;
    os << this->createEndTag( "i_sizeNeighborhood" ) << std::endl;     
    
    // done
    os << this->createEndTag( "LocalFeatureCentrist" ) << std::endl;    
  }
  else
  {
    std::cerr << "OutStream not initialized - storing not possible!" << std::endl;
  }
}

void LocalFeatureCentrist::clear ()
{
}

