/*********************************************************************
 * Software License Agreement (BSD License)
 *
 * Copyright (c) 2009
 * Engin Tola
 * web : http://www.engintola.com
 * email : engin.tola+libdaisy@gmail.com
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions
 *  are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above
 *     copyright notice, this list of conditions and the following
 *     disclaimer in the documentation and/or other materials provided
 *     with the distribution.
 *   * Neither the name of the Willow Garage nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 *  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 *  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 *  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 *  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *  POSSIBILITY OF SUCH DAMAGE.
 *********************************************************************/

/*
 "DAISY: An Efficient Dense Descriptor Applied to Wide Baseline Stereo"
 by Engin Tola, Vincent Lepetit and Pascal Fua. IEEE Transactions on
 Pattern Analysis and achine Intelligence, 31 Mar. 2009.
 IEEE computer Society Digital Library. IEEE Computer Society,
 http:doi.ieeecomputersociety.org/10.1109/TPAMI.2009.77

 "A fast local descriptor for dense matching" by Engin Tola, Vincent
 Lepetit, and Pascal Fua. Intl. Conf. on Computer Vision and Pattern
 Recognition, Alaska, USA, June 2008

 OpenCV port by: Cristian Balint <cristian dot balint at gmail dot com>
 */

#include "precomp.hpp"

#include <fstream>
#include <stdlib.h>

namespace cv
{
namespace xfeatures2d
{

// constants
const double g_sigma_0 = 1;
const double g_sigma_1 = sqrt(2.0);
const double g_sigma_step = std::pow(2,1.0/2);
const int g_scale_st = int( (log(g_sigma_1/g_sigma_0)) / log(g_sigma_step) );
static int g_scale_en = 1;

const double g_sigma_init = 1.6;
const static int g_grid_orientation_resolution = 360;

static const int MAX_CUBE_NO = 64;
static const int MAX_NORMALIZATION_ITER = 5;

int g_selected_cubes[MAX_CUBE_NO]; // m_rad_q_no < MAX_CUBE_NO

void DAISY::compute( InputArrayOfArrays images,
                     std::vector<std::vector<KeyPoint> >& keypoints,
                     OutputArrayOfArrays descriptors )
{
    DescriptorExtractor::compute(images, keypoints, descriptors);
}

/*
 !DAISY implementation
 */
class DAISY_Impl CV_FINAL : public DAISY
{

public:
    /** Constructor
     * @param radius radius of the descriptor at the initial scale
     * @param q_radius amount of radial range divisions
     * @param q_theta amount of angular range divisions
     * @param q_hist amount of gradient orientations range divisions
     * @param norm normalization type
     * @param H optional 3x3 homography matrix used to warp the grid of daisy but sampling keypoints remains unwarped on image
     * @param interpolation switch to disable interpolation at minor costs of quality (default is true)
     * @param use_orientation sample patterns using keypoints orientation, disabled by default.
     */
    explicit DAISY_Impl(float radius=15, int q_radius=3, int q_theta=8, int q_hist=8,
                        DAISY::NormalizationType norm = DAISY::NRM_NONE, InputArray H = noArray(),
                        bool interpolation = true, bool use_orientation = false);

    virtual ~DAISY_Impl() CV_OVERRIDE;

    void read( const FileNode& fn) CV_OVERRIDE;
    void write( FileStorage& fs) const CV_OVERRIDE;

    void setRadius(float radius) CV_OVERRIDE { m_rad = radius; }
    float getRadius() const CV_OVERRIDE { return m_rad; }

    void setQRadius(int q_radius) CV_OVERRIDE { m_rad_q_no = q_radius; }
    int getQRadius() const CV_OVERRIDE { return m_rad_q_no; }

    void setQTheta(int q_theta) CV_OVERRIDE { m_th_q_no = q_theta; }
    int getQTheta() const CV_OVERRIDE { return m_th_q_no; }

    void setQHist(int q_hist) CV_OVERRIDE { m_hist_th_q_no = q_hist; }
    int getQHist() const CV_OVERRIDE { return m_hist_th_q_no; }

    void setNorm(int norm) CV_OVERRIDE {
      switch(norm)
      {
        case NRM_NONE:
        case NRM_PARTIAL:
        case NRM_FULL:
        case NRM_SIFT:
          break;
        default:
          CV_Error(cv::Error::StsBadArg, "norm should be one of {NRM_NONE, NRM_PARTIAL, NRM_FULL, NRM_SIFT}");
          return;
      }
      m_nrm_type = (DAISY::NormalizationType)norm;
    }
    int getNorm() const CV_OVERRIDE { return (int)m_nrm_type; }

    void setH(InputArray H) CV_OVERRIDE { m_h_matrix = H.getMat(); }
    cv::Mat getH() const CV_OVERRIDE { return m_h_matrix; }

    void setInterpolation(bool interpolation) CV_OVERRIDE { m_enable_interpolation = interpolation; }
    bool getInterpolation() const CV_OVERRIDE { return m_enable_interpolation; }

    void setUseOrientation(bool use_orientation) CV_OVERRIDE { m_use_orientation = use_orientation; }
    bool getUseOrientation() const CV_OVERRIDE { return m_use_orientation; }

    /** returns the descriptor length in bytes */
    virtual int descriptorSize() const CV_OVERRIDE {
        // +1 is for center pixel
        return ( (m_rad_q_no * m_th_q_no + 1) * m_hist_th_q_no );
    };

    /** returns the descriptor type */
    virtual int descriptorType() const CV_OVERRIDE { return CV_32F; }

    /** returns the default norm type */
    virtual int defaultNorm() const CV_OVERRIDE { return NORM_L2; }

    /**
     * @param image image to extract descriptors
     * @param keypoints of interest within image
     * @param descriptors resulted descriptors array
     */
    virtual void compute( InputArray image, std::vector<KeyPoint>& keypoints, OutputArray descriptors ) CV_OVERRIDE;

    /** @overload
     * @param image image to extract descriptors
     * @param roi region of interest within image
     * @param descriptors resulted descriptors array
     */
    virtual void compute( InputArray image, Rect roi, OutputArray descriptors ) CV_OVERRIDE;

    /** @overload
     * @param image image to extract descriptors
     * @param descriptors resulted descriptors array
     */
    virtual void compute( InputArray image, OutputArray descriptors ) CV_OVERRIDE;

    /**
     * @param y position y on image
     * @param x position x on image
     * @param orientation orientation on image (0->360)
     * @param descriptor supplied array for descriptor storage
     */
    virtual void GetDescriptor( double y, double x, int orientation, float* descriptor ) const CV_OVERRIDE;

    /**
     * @param y position y on image
     * @param x position x on image
     * @param orientation orientation on image (0->360)
     * @param descriptor supplied array for descriptor storage
     * @param H homography matrix for warped grid
     */
    virtual bool GetDescriptor( double y, double x, int orientation, float* descriptor, double* H ) const CV_OVERRIDE;

    /**
     * @param y position y on image
     * @param x position x on image
     * @param orientation orientation on image (0->360)
     * @param descriptor supplied array for descriptor storage
     */
    virtual void GetUnnormalizedDescriptor( double y, double x, int orientation, float* descriptor ) const CV_OVERRIDE;

    /**
     * @param y position y on image
     * @param x position x on image
     * @param orientation orientation on image (0->360)
     * @param descriptor supplied array for descriptor storage
     * @param H homography matrix for warped grid
     */
    virtual bool GetUnnormalizedDescriptor( double y, double x, int orientation, float* descriptor, double* H ) const CV_OVERRIDE;

protected:

    /*
     * DAISY parameters
     */

    // maximum radius of the descriptor region.
    float m_rad;

    // the number of quantizations of the radius.
    int m_rad_q_no;

    // the number of quantizations of the angle.
    int m_th_q_no;

    // the number of quantizations of the gradient orientations.
    int m_hist_th_q_no;

    // holds the type of the normalization to apply; equals to NRM_PARTIAL by
    // default. change the value using set_normalization() function.
    DAISY::NormalizationType m_nrm_type;

    // the size of the descriptor vector
    int m_descriptor_size;

    // the number of grid locations
    int m_grid_point_number;

    // number of bins in the histograms while computing orientation
    int m_orientation_resolution;


    /*
     * DAISY switches
     */

    // if set to true, descriptors are scale invariant
    bool m_scale_invariant;

    // if set to true, descriptors are rotation invariant
    bool m_rotation_invariant;

    // if enabled, descriptors are computed with casting non-integer locations
    // to integer positions otherwise we use interpolation.
    bool m_enable_interpolation;

    // switch to enable sample by keypoints orientation
    bool m_use_orientation;

    /*
     * DAISY arrays
     */

    // holds optional H matrix
    Mat m_h_matrix;

    // internal float image.
    Mat m_image;

    // image roi
    Rect m_roi;

    // stores the layered gradients in successively smoothed form :
    // layer[n] = m_gradient_layers * gaussian( sigma_n );
    // n>= 1; layer[0] is the layered_gradient
    std::vector<Mat> m_smoothed_gradient_layers;

    // hold the scales of the pixels
    Mat m_scale_map;

    // holds the orientaitons of the pixels
    Mat m_orientation_map;

    // Holds the oriented coordinates (y,x) of the grid points of the region.
    Mat m_oriented_grid_points;

    // holds the gaussian sigmas for radius quantizations for an incremental
    // application
    Mat m_cube_sigmas;

    // Holds the coordinates (y,x) of the grid points of the region.
    Mat m_grid_points;

    // holds the amount of shift that's required for histogram computation
    double m_orientation_shift_table[360];


private:

    /*
     * DAISY functions
     */

    // initializes the class: computes gradient and structure-points
    inline void initialize();

    // initializes for get_descriptor(double, double, int) mode: pre-computes
    // convolutions of gradient layers in m_smoothed_gradient_layers
    inline void initialize_single_descriptor_mode();

    // set & precompute parameters
    inline void set_parameters();

    // image set image as working
    inline void set_image( InputArray image );

    // releases all the used memory; call this if you want to process
    // multiple images within a loop.
    inline void reset();

    // releases unused memory after descriptor computation is completed.
    inline void release_auxiliary();

    // computes the descriptors for every pixel in the image.
    inline void compute_descriptors( Mat* m_dense_descriptors );

    // computes scales for every pixel and scales the structure grid so that the
    // resulting descriptors are scale invariant.  you must set
    // m_scale_invariant flag to 1 for the program to call this function
    inline void compute_scales();

    // compute the smoothed gradient layers.
    inline void compute_smoothed_gradient_layers();

    // computes pixel orientations and rotates the structure grid so that
    // resulting descriptors are rotation invariant. If the scales is also
    // detected, then orientations are computed at the computed scales. you must
    // set m_rotation_invariant flag to 1 for the program to call this function
    inline void compute_orientations();

    // computes the histogram at yx; the size of histogram is m_hist_th_q_no
    inline void compute_histogram( float* hcube, int y, int x, float* histogram );

    // reorganizes the cube data so that histograms are sequential in memory.
    inline void compute_histograms();

    // computes the sigma's of layers from descriptor parameters if the user did
    // not sets it. these define the size of the petals of the descriptor.
    inline void compute_cube_sigmas();

    // Computes the locations of the unscaled unrotated points where the
    // histograms are going to be computed according to the given parameters.
    inline void compute_grid_points();

    // Computes the locations of the unscaled rotated points where the
    // histograms are going to be computed according to the given parameters.
    inline void compute_oriented_grid_points();

    // applies one of the normalizations (partial,full,sift) to the desciptors.
    inline void normalize_descriptors( Mat* m_dense_descriptors );

    inline void update_selected_cubes();

}; // END DAISY_Impl CLASS


// -------------------------------------------------
/* DAISY computation routines */

inline void DAISY_Impl::reset()
{
    m_image.release();

    m_scale_map.release();
    m_orientation_map.release();

    for (size_t i=0; i<m_smoothed_gradient_layers.size(); i++)
      m_smoothed_gradient_layers[i].release();
    m_smoothed_gradient_layers.clear();
}

inline void DAISY_Impl::release_auxiliary()
{
    reset();

    m_cube_sigmas.release();
    m_grid_points.release();
    m_oriented_grid_points.release();
}

static int filter_size( double sigma, double factor )
{
    int fsz = (int)( factor * sigma );
    // kernel size must be odd
    if( fsz%2 == 0 ) fsz++;
    // kernel size cannot be smaller than 3
    if( fsz < 3 ) fsz = 3;

    return fsz;
}

// transform a point via the homography
static void pt_H( double* H, double x, double y, double &u, double &v )
{
    double kxp = H[0]*x + H[1]*y + H[2];
    double kyp = H[3]*x + H[4]*y + H[5];
    double kp  = H[6]*x + H[7]*y + H[8];
    u = kxp / kp; v = kyp / kp;
}


static float interpolate_peak( float left, float center, float right )
{
    if( center < 0.0 )
    {
      left = -left;
      center = -center;
      right = -right;
    }
    CV_Assert(center >= left  &&  center >= right);

    float den = (float) (left - 2.0 * center + right);

    if( den == 0 ) return 0;
    else           return (float) (0.5*(left -right)/den);
}

static void smooth_histogram( Mat* hist, int hsz )
{
    int i;
    float prev, temp;

    prev = hist->at<float>(hsz - 1);
    for (i = 0; i < hsz; i++)
    {
      temp = hist->at<float>(i);
      hist->at<float>(i) = (prev + hist->at<float>(i) + hist->at<float>( (i + 1 == hsz) ? 0 : i + 1) ) / 3.0f;
      prev = temp;
    }
}

struct LayeredGradientInvoker : ParallelLoopBody
{
    LayeredGradientInvoker( Mat* _layers, Mat& _dy, Mat& _dx )
    {
      dy = _dy;
      dx = _dx;
      layers = _layers;
      layer_no = layers->size[0];
    }

    void operator ()(const cv::Range& range) const CV_OVERRIDE
    {
      for (int l = range.start; l < range.end; ++l)
      {
        double angle = l * 2 * (float)CV_PI / layer_no;
        Mat layer( dx.rows, dx.cols, CV_32F, layers->ptr<float>(l,0,0) );
        addWeighted( dx, cos( angle ), dy, sin( angle ), 0.0f, layer, CV_32F );
        max( layer, 0.0f, layer );
      }
    }

    Mat dy, dx;
    Mat *layers;
    int layer_no;
};

static void layered_gradient( Mat& data, Mat* layers )
{
    Mat cvO, dx, dy;
    int layer_no = layers->size[0];

    GaussianBlur( data, cvO, Size(5, 5), 0.5f, 0.5f, BORDER_REPLICATE );
    Sobel( cvO, dx, CV_32F, 1, 0, 1, 0.5f, 0.0f, BORDER_REPLICATE );
    Sobel( cvO, dy, CV_32F, 0, 1, 1, 0.5f, 0.0f, BORDER_REPLICATE );

    parallel_for_( Range(0, layer_no), LayeredGradientInvoker( layers, dy, dx ) );
}

struct SmoothLayersInvoker : ParallelLoopBody
{
    SmoothLayersInvoker( Mat* _layers, const float _sigma )
    {
      layers = _layers;
      sigma = _sigma;

      h = layers->size[1];
      w = layers->size[2];
      ks = filter_size( sigma, 5.0f );
    }

    void operator ()(const cv::Range& range) const CV_OVERRIDE
    {
      for (int l = range.start; l < range.end; ++l)
      {
        Mat layer( h, w, CV_32FC1, layers->ptr<float>(l,0,0) );
        GaussianBlur( layer, layer, Size(ks, ks), sigma, sigma, BORDER_REPLICATE );
      }
    }

    float sigma;
    int ks, h, w;
    Mat *layers;
};

static void smooth_layers( Mat* layers, float sigma )
{
    int layer_no = layers->size[0];
    parallel_for_( Range(0, layer_no), SmoothLayersInvoker( layers, sigma ) );
}

static int quantize_radius( float rad, const int _rad_q_no, const Mat& _cube_sigmas )
{
    if( rad <= _cube_sigmas.at<double>(0) )
        return 0;
    if( rad >= _cube_sigmas.at<double>(_rad_q_no-1) )
        return _rad_q_no-1;

    int idx_min[2];
    minMaxIdx( abs( _cube_sigmas - rad ), NULL, NULL, idx_min );

    return idx_min[1];
}

static void normalize_partial( float* desc, const int _grid_point_number, const int _hist_th_q_no )
{
    for( int h=0; h<_grid_point_number; h++ )
    {
      // l2 norm
      double sum = 0.0f;
      for( int i=0; i<_hist_th_q_no; i++ )
      {
          sum += desc[h*_hist_th_q_no + i]
               * desc[h*_hist_th_q_no + i];
      }

      float norm = (float)sqrt( sum );

      if( norm != 0.0 )
      // divide with norm
      for( int i=0; i<_hist_th_q_no; i++ )
      {
          desc[h*_hist_th_q_no + i] /= norm;
      }
    }
}

static void normalize_sift_way( float* desc, const int _descriptor_size )
{
    int h;
    int iter = 0;
    bool changed = true;
    while( changed && iter < MAX_NORMALIZATION_ITER )
    {
      iter++;
      changed = false;

      double sum = 0.0f;
      for( int i=0; i<_descriptor_size; i++ )
      {
          sum += desc[i] * desc[i];
      }

      float norm = (float)sqrt( sum );

      if( norm > 1e-5 )
      // divide with norm
      for( int i=0; i<_descriptor_size; i++ )
      {
          desc[i] /= norm;
      }

      for( h=0; h<_descriptor_size; h++ )
      {  // sift magical number
         if( desc[ h ] > 0.154f )
         {
            desc[ h ] = 0.154f;
            changed = true;
         }
      }
    }
}

static void normalize_full( float* desc, const int _descriptor_size )
{
    // l2 norm
    double sum = 0.0f;
    for( int i=0; i<_descriptor_size; i++ )
    {
        sum += desc[i] * desc[i];
    }

    float norm = (float)sqrt( sum );

    if( norm != 0.0 )
    // divide with norm
    for( int i=0; i<_descriptor_size; i++ )
    {
        desc[i] /= norm;
    }
}

static void normalize_descriptor( float* desc, const DAISY::NormalizationType nrm_type, const int _grid_point_number,
                                  const int _hist_th_q_no, const int _descriptor_size  )
{
    if( nrm_type == DAISY::NRM_NONE ) return;
    else if( nrm_type == DAISY::NRM_PARTIAL ) normalize_partial(desc,_grid_point_number,_hist_th_q_no);
    else if( nrm_type == DAISY::NRM_FULL    ) normalize_full(desc,_descriptor_size);
    else if( nrm_type == DAISY::NRM_SIFT    ) normalize_sift_way(desc,_descriptor_size);
    else
        CV_Error( Error::StsInternal, "No such normalization" );
}

static void ni_get_histogram( float* histogram, const int y, const int x, const int shift, const Mat* hcube )
{

    if ( ! Point( x, y ).inside(
           Rect( 0, 0, hcube->size[1]-1, hcube->size[0]-1 ) )
       ) return;

    int _hist_th_q_no = hcube->size[2];
    const float* hptr = hcube->ptr<float>(y,x,0);
    for( int h=0; h<_hist_th_q_no; h++ )
    {
      int hi = h+shift;
      if( hi >= _hist_th_q_no ) hi -= _hist_th_q_no;
      histogram[h] = hptr[hi];
    }
}

static void bi_get_histogram( float* histogram, const double y, const double x, const int shift, const Mat* hcube )
{
    int mnx = int( x );
    int mny = int( y );
    int _hist_th_q_no = hcube->size[2];
    if( mnx >= hcube->size[1]-2  || mny >= hcube->size[0]-2 )
    {
      memset(histogram, 0, sizeof(float)*_hist_th_q_no);
      return;
    }

    // A C --> pixel positions
    // B D
    const float* A = hcube->ptr<float>( mny   ,  mnx   , 0);
    const float* B = hcube->ptr<float>((mny+1),  mnx   , 0);
    const float* C = hcube->ptr<float>( mny   , (mnx+1), 0);
    const float* D = hcube->ptr<float>((mny+1), (mnx+1), 0);

    double alpha = mnx+1-x;
    double beta  = mny+1-y;

    float w0 = (float) ( alpha * beta );
    float w1 = (float) ( beta - w0    );         // (1-alpha)*beta;
    float w2 = (float) ( alpha - w0   );         // (1-beta)*alpha;
    float w3 = (float) ( 1 + w0 - alpha - beta); // (1-beta)*(1-alpha);

    int h;

    for( h=0; h<_hist_th_q_no; h++ ) {
      if( h+shift < _hist_th_q_no ) histogram[h] = w0 * A[h+shift];
      else                          histogram[h] = w0 * A[h+shift-_hist_th_q_no];
    }
    for( h=0; h<_hist_th_q_no; h++ ) {
      if( h+shift < _hist_th_q_no ) histogram[h] += w1 * C[h+shift];
      else                          histogram[h] += w1 * C[h+shift-_hist_th_q_no];
    }
    for( h=0; h<_hist_th_q_no; h++ ) {
      if( h+shift < _hist_th_q_no ) histogram[h] += w2 * B[h+shift];
      else                          histogram[h] += w2 * B[h+shift-_hist_th_q_no];
    }
    for( h=0; h<_hist_th_q_no; h++ ) {
      if( h+shift < _hist_th_q_no ) histogram[h] += w3 * D[h+shift];
      else                          histogram[h] += w3 * D[h+shift-_hist_th_q_no];
    }
}

static void ti_get_histogram( float* histogram, const double y, const double x, const double shift, const Mat* hcube )
{
    int ishift = int( shift );
    double layer_alpha  = shift - ishift;

    float thist[MAX_CUBE_NO];
    bi_get_histogram( thist, y, x, ishift, hcube );

    int _hist_th_q_no = hcube->size[2];
    for( int h=0; h<_hist_th_q_no-1; h++ )
      histogram[h] = (float) ((1-layer_alpha)*thist[h]+layer_alpha*thist[h+1]);
    histogram[_hist_th_q_no-1] = (float) ((1-layer_alpha)*thist[_hist_th_q_no-1]+layer_alpha*thist[0]);
}

static void i_get_histogram( float* histogram, const double y, const double x, const double shift, const Mat* hcube )
{
    int ishift = (int)shift;
    double fshift = shift-ishift;
    if     ( fshift < 0.01 ) bi_get_histogram( histogram, y, x, ishift  , hcube );
    else if( fshift > 0.99 ) bi_get_histogram( histogram, y, x, ishift+1, hcube );
    else                     ti_get_histogram( histogram, y, x,  shift  , hcube );
}

static void ni_get_descriptor( const double y, const double x, const int orientation, float* descriptor, const std::vector<Mat>* layers,
                               const Mat* _oriented_grid_points, const double* _orientation_shift_table, const int _th_q_no )
{
    CV_Assert( y >= 0 && y < layers->at(0).size[0] );
    CV_Assert( x >= 0 && x < layers->at(0).size[1] );
    CV_Assert( orientation >= 0 && orientation < 360 );
    CV_Assert( !layers->empty() );
    CV_Assert( !_oriented_grid_points->empty() );
    CV_Assert( descriptor != NULL );

    int _rad_q_no = (int) layers->size();
    int _hist_th_q_no = layers->at(0).size[2];
    double shift = _orientation_shift_table[orientation];
    int ishift = (int)shift;
    if( shift - ishift > 0.5  ) ishift++;

    int iy = (int)y; if( y - iy > 0.5 ) iy++;
    int ix = (int)x; if( x - ix > 0.5 ) ix++;

    // center
    ni_get_histogram( descriptor, iy, ix, ishift, &layers->at(g_selected_cubes[0]) );

    double yy, xx;
    float* histogram=0;
    // petals of the flower
    int r, rdt, region;
    Mat grid = _oriented_grid_points->row( orientation );
    for( r=0; r<_rad_q_no; r++ )
    {
      rdt = r*_th_q_no+1;
      for( region=rdt; region<rdt+_th_q_no; region++ )
      {
         yy = y + grid.at<double>(2*region  );
         xx = x + grid.at<double>(2*region+1);
         iy = (int)yy; if( yy - iy > 0.5 ) iy++;
         ix = (int)xx; if( xx - ix > 0.5 ) ix++;

         if ( ! Point2f( (float)xx, (float)yy ).inside(
                Rect( 0, 0, layers->at(0).size[1]-1, layers->at(0).size[0]-1 ) )
            ) continue;

         histogram = descriptor + region*_hist_th_q_no;
         ni_get_histogram( histogram, iy, ix, ishift, &layers->at(g_selected_cubes[r]) );
      }
    }
}

static void i_get_descriptor( const double y, const double x, const int orientation, float* descriptor, const std::vector<Mat>* layers,
                              const Mat* _oriented_grid_points, const double *_orientation_shift_table, const int _th_q_no )
{
    CV_Assert( y >= 0 && y < layers->at(0).size[0] );
    CV_Assert( x >= 0 && x < layers->at(0).size[1] );
    CV_Assert( orientation >= 0 && orientation < 360 );
    CV_Assert( !layers->empty() );
    CV_Assert( !_oriented_grid_points->empty() );
    CV_Assert( descriptor != NULL );

    int _rad_q_no = (int) layers->size();
    int _hist_th_q_no = layers->at(0).size[2];
    double shift = _orientation_shift_table[orientation];

    i_get_histogram( descriptor, y, x, shift, &layers->at(g_selected_cubes[0]) );

    int r, rdt, region;
    double yy, xx;
    float* histogram = 0;

    Mat grid = _oriented_grid_points->row( orientation );

    // petals of the flower
    for( r=0; r<_rad_q_no; r++ )
    {
      rdt  = r*_th_q_no+1;
      for( region=rdt; region<rdt+_th_q_no; region++ )
      {
         yy = y + grid.at<double>(2*region    );
         xx = x + grid.at<double>(2*region + 1);

         if ( ! Point2f( (float)xx, (float)yy ).inside(
                Rect( 0, 0, layers->at(0).size[1]-1, layers->at(0).size[0]-1 ) )
            ) continue;

         histogram = descriptor + region*_hist_th_q_no;
         i_get_histogram( histogram, yy, xx, shift, &layers->at(r) );
      }
    }
}

static bool ni_get_descriptor_h( const double y, const double x, const int orientation, double* H, float* descriptor, const std::vector<Mat>* layers,
                                 const Mat& _cube_sigmas, const Mat* _grid_points, const double* _orientation_shift_table, const int _th_q_no )
{
    CV_Assert( orientation >= 0 && orientation < 360 );
    CV_Assert( !layers->empty() );
    CV_Assert( descriptor != NULL );

    int hradius[MAX_CUBE_NO];

    double hy, hx, ry, rx;

    pt_H(H, x, y, hx, hy );

    if ( ! Point2f( (float)hx, (float)hy ).inside(
                Rect( 0, 0, layers->at(0).size[1]-1, layers->at(0).size[0]-1 ) )
       ) return false;

    int _rad_q_no = (int) layers->size();
    int _hist_th_q_no = layers->at(0).size[2];
    double shift = _orientation_shift_table[orientation];
    int  ishift = (int)shift; if( shift - ishift > 0.5  ) ishift++;

    pt_H(H, x+_cube_sigmas.at<double>(g_selected_cubes[0]), y, rx, ry);
    double d0 = rx - hx; double d1 = ry - hy;
    double radius = sqrt( d0*d0 + d1*d1 );
    hradius[0] = quantize_radius( (float) radius, _rad_q_no, _cube_sigmas );

    int ihx = (int)hx; if( hx - ihx > 0.5 ) ihx++;
    int ihy = (int)hy; if( hy - ihy > 0.5 ) ihy++;

    int r, rdt, th, region;
    double gy, gx;
    float* histogram=0;
    ni_get_histogram( descriptor, ihy, ihx, ishift, &layers->at(hradius[0]) );
    for( r=0; r<_rad_q_no; r++)
    {
      rdt = r*_th_q_no + 1;
      for( th=0; th<_th_q_no; th++ )
      {
         region = rdt + th;

         gy = y + _grid_points->at<double>(region,0);
         gx = x + _grid_points->at<double>(region,1);

         pt_H(H, gx, gy, hx, hy);
         if( th == 0 )
         {
            pt_H(H, gx+_cube_sigmas.at<double>(g_selected_cubes[r]), gy, rx, ry);
            d0 = rx - hx; d1 = ry - hy;
            radius = sqrt( d0*d0 + d1*d1 );
            hradius[r] = quantize_radius( (float) radius, _rad_q_no, _cube_sigmas );
         }

         ihx = (int)hx; if( hx - ihx > 0.5 ) ihx++;
         ihy = (int)hy; if( hy - ihy > 0.5 ) ihy++;

         if ( ! Point( ihx, ihy ).inside(
                Rect( 0, 0, layers->at(0).size[1]-1, layers->at(0).size[0]-1 ) )
            ) continue;

         histogram = descriptor + region*_hist_th_q_no;
         ni_get_histogram( histogram, ihy, ihx, ishift, &layers->at(hradius[r]) );
      }
    }
    return true;
}

static bool i_get_descriptor_h( const double y, const double x, const int orientation, double* H, float* descriptor, const std::vector<Mat>* layers,
                                const Mat _cube_sigmas, const Mat* _grid_points, const double* _orientation_shift_table, const int _th_q_no )
{
    CV_Assert( orientation >= 0 && orientation < 360 );
    CV_Assert( !layers->empty() );
    CV_Assert( descriptor != NULL );

    int hradius[MAX_CUBE_NO];

    double hy, hx, ry, rx;
    pt_H( H, x, y, hx, hy );

    if ( ! Point2f( (float)hx, (float)hy ).inside(
                Rect( 0, 0, layers->at(0).size[1]-1, layers->at(0).size[0]-1 ) )
       ) return false;

    int _rad_q_no = (int) layers->size();
    int _hist_th_q_no = layers->at(0).size[0];
    pt_H( H, x+_cube_sigmas.at<double>(g_selected_cubes[0]), y, rx, ry);
    double d0 = rx - hx; double d1 = ry - hy;
    double radius = sqrt( d0*d0 + d1*d1 );
    hradius[0] = quantize_radius( (float) radius, _rad_q_no, _cube_sigmas );

    double shift = _orientation_shift_table[orientation];
    i_get_histogram( descriptor, hy, hx, shift, &layers->at(hradius[0]) );

    double gy, gx;
    int r, rdt, th, region;
    float* histogram=0;
    for( r=0; r<_rad_q_no; r++)
    {
      rdt = r*_th_q_no + 1;
      for( th=0; th<_th_q_no; th++ )
      {
         region = rdt + th;

         gy = y + _grid_points->at<double>(region,0);
         gx = x + _grid_points->at<double>(region,1);

         pt_H(H, gx, gy, hx, hy);
         if( th == 0 )
         {
            pt_H(H, gx+_cube_sigmas.at<double>(g_selected_cubes[r]), gy, rx, ry);
            d0 = rx - hx; d1 = ry - hy;
            radius = sqrt( d0*d0 + d1*d1 );
            hradius[r] = quantize_radius( (float) radius, _rad_q_no, _cube_sigmas );
         }

         if ( ! Point2f( (float)hx, (float)hy ).inside(
                Rect( 0, 0, layers->at(0).size[1]-1, layers->at(0).size[0]-1 ) )
            ) continue;

         histogram = descriptor + region*_hist_th_q_no;
         i_get_histogram( histogram, hy, hx, shift, &layers->at(hradius[r]) );
      }
    }
    return true;
}

static void get_unnormalized_descriptor( const double y, const double x, const int orientation, float* descriptor,
            const std::vector<Mat>* m_smoothed_gradient_layers, const Mat* m_oriented_grid_points,
            const double* m_orientation_shift_table, const int m_th_q_no, const bool m_enable_interpolation )
{
    if( m_enable_interpolation )
      i_get_descriptor( y, x, orientation, descriptor, m_smoothed_gradient_layers,
                        m_oriented_grid_points, m_orientation_shift_table, m_th_q_no );
    else
     ni_get_descriptor( y, x, orientation, descriptor, m_smoothed_gradient_layers,
                        m_oriented_grid_points, m_orientation_shift_table, m_th_q_no);
}

static void get_descriptor( const double y, const double x, const int orientation, float* descriptor,
            const std::vector<Mat>* m_smoothed_gradient_layers, const Mat* m_oriented_grid_points,
            const double* m_orientation_shift_table, const int m_th_q_no, const int m_hist_th_q_no,
            const int m_grid_point_number, const int m_descriptor_size, const bool m_enable_interpolation,
            const DAISY::NormalizationType m_nrm_type)
{
    get_unnormalized_descriptor( y, x, orientation, descriptor, m_smoothed_gradient_layers,
                                 m_oriented_grid_points, m_orientation_shift_table, m_th_q_no, m_enable_interpolation );
    normalize_descriptor( descriptor, m_nrm_type, m_grid_point_number, m_hist_th_q_no, m_descriptor_size );
}

static bool get_unnormalized_descriptor_h( const double y, const double x, const int orientation, float* descriptor, double* H,
            const std::vector<Mat>* m_smoothed_gradient_layers, const Mat& m_cube_sigmas,
            const Mat* m_grid_points, const double* m_orientation_shift_table, const int m_th_q_no, const bool m_enable_interpolation )

{
    if( m_enable_interpolation )
      return  i_get_descriptor_h( y, x, orientation, H, descriptor, m_smoothed_gradient_layers, m_cube_sigmas,
                                  m_grid_points, m_orientation_shift_table, m_th_q_no );
    else
      return ni_get_descriptor_h( y, x, orientation, H, descriptor, m_smoothed_gradient_layers, m_cube_sigmas,
                                  m_grid_points, m_orientation_shift_table, m_th_q_no );
}

static bool get_descriptor_h( const double y, const double x, const int orientation, float* descriptor, double* H,
            const std::vector<Mat>* m_smoothed_gradient_layers, const Mat& m_cube_sigmas,
            const Mat* m_grid_points, const double* m_orientation_shift_table, const int m_th_q_no,
            const int m_hist_th_q_no, const int m_grid_point_number, const int m_descriptor_size,
            const bool m_enable_interpolation, const DAISY::NormalizationType m_nrm_type)

{
    bool rval =
      get_unnormalized_descriptor_h( y, x, orientation, descriptor, H, m_smoothed_gradient_layers, m_cube_sigmas,
                                   m_grid_points, m_orientation_shift_table, m_th_q_no, m_enable_interpolation );

    if( rval )
      normalize_descriptor( descriptor, m_nrm_type, m_grid_point_number, m_hist_th_q_no, m_descriptor_size );

    return rval;
}

void DAISY_Impl::GetDescriptor( double y, double x, int orientation, float* descriptor ) const
{
    get_descriptor( y, x, orientation, descriptor, &m_smoothed_gradient_layers,
                    &m_oriented_grid_points, m_orientation_shift_table, m_th_q_no,
                    m_hist_th_q_no, m_grid_point_number, m_descriptor_size, m_enable_interpolation,
                    m_nrm_type );
}

bool DAISY_Impl::GetDescriptor( double y, double x, int orientation, float* descriptor, double* H ) const
{
  return
  get_descriptor_h( y, x, orientation, descriptor, H, &m_smoothed_gradient_layers,
                    m_cube_sigmas, &m_grid_points, m_orientation_shift_table, m_th_q_no,
                    m_hist_th_q_no, m_grid_point_number, m_descriptor_size, m_enable_interpolation,
                    m_nrm_type );
}

void DAISY_Impl::GetUnnormalizedDescriptor( double y, double x, int orientation, float* descriptor ) const
{
    get_unnormalized_descriptor( y, x, orientation, descriptor, &m_smoothed_gradient_layers,
                                 &m_oriented_grid_points, m_orientation_shift_table, m_th_q_no,
                                 m_enable_interpolation );
}

bool DAISY_Impl::GetUnnormalizedDescriptor( double y, double x, int orientation, float* descriptor, double* H ) const
{
  return
  get_unnormalized_descriptor_h( y, x, orientation, descriptor, H, &m_smoothed_gradient_layers,
                                 m_cube_sigmas, &m_grid_points, m_orientation_shift_table, m_th_q_no,
                                 m_enable_interpolation );
}

inline void DAISY_Impl::compute_grid_points()
{
    double r_step = m_rad / (double)m_rad_q_no;
    double t_step = 2*CV_PI / m_th_q_no;

    m_grid_points.release();
    m_grid_points = Mat( m_grid_point_number, 2, CV_64F );

    for( int y=0; y<m_grid_point_number; y++ )
    {
      m_grid_points.at<double>(y,0) = 0;
      m_grid_points.at<double>(y,1) = 0;
    }

    for( int r=0; r<m_rad_q_no; r++ )
    {
      int region = r*m_th_q_no+1;
      for( int t=0; t<m_th_q_no; t++ )
      {
         m_grid_points.at<double>(region+t,0) = (r+1)*r_step * sin( t*t_step );
         m_grid_points.at<double>(region+t,1) = (r+1)*r_step * cos( t*t_step );
      }
    }

    compute_oriented_grid_points();
}

struct ComputeDescriptorsInvoker : ParallelLoopBody
{
    ComputeDescriptorsInvoker( Mat* _descriptors, Mat* _image, Rect* _roi,
                               std::vector<Mat>* _layers, Mat* _orientation_map,
                               Mat* _oriented_grid_points, double* _orientation_shift_table,
                               int _th_q_no, bool _enable_interpolation )
    {
      x_off = _roi->x;
      x_end = _roi->x + _roi->width;
      image = _image;
      layers = _layers;
      th_q_no = _th_q_no;
      descriptors = _descriptors;
      orientation_map = _orientation_map;
      enable_interpolation = _enable_interpolation;
      oriented_grid_points = _oriented_grid_points;
      orientation_shift_table = _orientation_shift_table;
    }

    void operator ()(const cv::Range& range) const CV_OVERRIDE
    {
      int index, orientation;
      for (int y = range.start; y < range.end; ++y)
      {
        for( int x = x_off; x < x_end; x++ )
        {
          index = y*image->cols + x;
          orientation = 0;
          if( !orientation_map->empty() )
              orientation = (int) orientation_map->at<ushort>( y, x );
          if( !( orientation >= 0 && orientation < g_grid_orientation_resolution ) )
              orientation = 0;
          get_unnormalized_descriptor( y, x, orientation, descriptors->ptr<float>( index ),
                                       layers, oriented_grid_points, orientation_shift_table,
                                       th_q_no, enable_interpolation );
        }
      }
    }

    int th_q_no;
    int x_off, x_end;
    std::vector<Mat>* layers;
    Mat *descriptors;
    Mat *orientation_map;
    bool enable_interpolation;
    double* orientation_shift_table;
    Mat *image, *oriented_grid_points;
};

// Computes the descriptor by sampling convoluted orientation maps.
inline void DAISY_Impl::compute_descriptors( Mat* m_dense_descriptors )
{
    int y_off = m_roi.y;
    int y_end = m_roi.y + m_roi.height;

    if( m_scale_invariant    ) compute_scales();
    if( m_rotation_invariant ) compute_orientations();

    m_dense_descriptors->setTo( Scalar(0) );

    parallel_for_( Range(y_off, y_end),
        ComputeDescriptorsInvoker( m_dense_descriptors, &m_image, &m_roi, &m_smoothed_gradient_layers,
                                   &m_orientation_map, &m_oriented_grid_points, m_orientation_shift_table,
                                   m_th_q_no, m_enable_interpolation )
    );

}

struct NormalizeDescriptorsInvoker : ParallelLoopBody
{
    NormalizeDescriptorsInvoker( Mat* _descriptors, DAISY::NormalizationType _nrm_type, int _grid_point_number,
                                 int _hist_th_q_no, int _descriptor_size )
    {
      descriptors = _descriptors;
      nrm_type = _nrm_type;
      grid_point_number = _grid_point_number;
      hist_th_q_no = _hist_th_q_no;
      descriptor_size = _descriptor_size;
    }

    void operator ()(const cv::Range& range) const CV_OVERRIDE
    {
      for (int d = range.start; d < range.end; ++d)
      {
        normalize_descriptor( descriptors->ptr<float>(d), nrm_type,
                              grid_point_number, hist_th_q_no, descriptor_size );
      }
    }

    Mat *descriptors;
    DAISY::NormalizationType nrm_type;
    int grid_point_number;
    int hist_th_q_no;
    int descriptor_size;
};

inline void DAISY_Impl::normalize_descriptors( Mat* m_dense_descriptors )
{
    CV_Assert( !m_dense_descriptors->empty() );
    int number_of_descriptors =  m_roi.width * m_roi.height;

    parallel_for_( Range(0, number_of_descriptors),
        NormalizeDescriptorsInvoker( m_dense_descriptors, m_nrm_type, m_grid_point_number, m_hist_th_q_no, m_descriptor_size )
    );
}

inline void DAISY_Impl::initialize()
{
    // no image ?
    CV_Assert(m_image.rows != 0);
    CV_Assert(m_image.cols != 0);

    // (m_rad_q_no + 1) cubes
    // 3 dims tensor (idhist, img_y, img_x);
    m_smoothed_gradient_layers.resize( m_rad_q_no + 1 );

    int dims[3] = { m_hist_th_q_no, m_image.rows, m_image.cols };
    for ( int c=0; c<=m_rad_q_no; c++)
      m_smoothed_gradient_layers[c] = Mat( 3, dims, CV_32F );

    layered_gradient( m_image, &m_smoothed_gradient_layers[0] );

    // assuming a 0.5 image smoothness, we pull this to 1.6 as in sift
    smooth_layers( &m_smoothed_gradient_layers[0], (float)sqrt(g_sigma_init*g_sigma_init-0.25f) );

}

inline void DAISY_Impl::compute_cube_sigmas()
{
    if( m_cube_sigmas.empty() )
    {

      m_cube_sigmas = Mat(1, m_rad_q_no, CV_64F);

      double r_step = (double)m_rad / m_rad_q_no / 2;
      for( int r=0; r<m_rad_q_no; r++ )
      {
        m_cube_sigmas.at<double>(r) = (r+1) * r_step;
      }
    }
    update_selected_cubes();
}

inline void DAISY_Impl::update_selected_cubes()
{
    double scale = m_rad/m_rad_q_no/2.0;
    for( int r=0; r<m_rad_q_no; r++ )
    {
      double seed_sigma = ((double)r+1) * scale;
      g_selected_cubes[r] = quantize_radius( (float)seed_sigma, m_rad_q_no, m_cube_sigmas );
    }
}

struct ComputeHistogramsInvoker : ParallelLoopBody
{
    ComputeHistogramsInvoker( std::vector<Mat>* _layers, int _r )
    {
      r = _r;
      layers = _layers;
      _hist_th_q_no = layers->at(r).size[2];
    }

    void operator ()(const cv::Range& range) const CV_OVERRIDE
    {
      for (int y = range.start; y < range.end; ++y)
      {
        for( int x = 0; x < layers->at(r).size[1]; x++ )
        {
          float* hist = layers->at(r).ptr<float>(y,x,0);
          for( int h = 0; h < _hist_th_q_no; h++ )
          {
            hist[h] = layers->at(r+1).at<float>(h,y,x);
          }
        }
      }
    }

    int r, _hist_th_q_no;
    std::vector<Mat> *layers;
};

inline void DAISY_Impl::compute_histograms()
{
    for( int r=0; r<m_rad_q_no; r++ )
    {
      // remap cubes from Mat(h,y,x) -> Mat(y,x,h)
      // final sampling is speeded up by aligned h dim
      int m_h = m_smoothed_gradient_layers.at(r).size[0];
      int m_y = m_smoothed_gradient_layers.at(r).size[1];
      int m_x = m_smoothed_gradient_layers.at(r).size[2];

      // empty targeted cube
      m_smoothed_gradient_layers.at(r).release();

      // recreate cube space
      int dims[3] = { m_y, m_x, m_h };
      m_smoothed_gradient_layers.at(r) = Mat( 3, dims, CV_32F );

      // copy backward all cubes and realign structure
      parallel_for_( Range(0, m_image.rows), ComputeHistogramsInvoker( &m_smoothed_gradient_layers, r ) );
    }
    // trim unused region from collection of cubes
    m_smoothed_gradient_layers[m_rad_q_no].release();
    m_smoothed_gradient_layers.pop_back();
}

inline void DAISY_Impl::compute_smoothed_gradient_layers()
{
    double sigma;
    for( int r=0; r<m_rad_q_no; r++ )
    {
      // incremental smoothing
      if( r == 0 )
        sigma = m_cube_sigmas.at<double>(0);
      else
        sigma = sqrt( m_cube_sigmas.at<double>(r  ) * m_cube_sigmas.at<double>(r  )
                    - m_cube_sigmas.at<double>(r-1) * m_cube_sigmas.at<double>(r-1) );

      int ks = filter_size( sigma, 5.0f );

      for( int th=0; th<m_hist_th_q_no; th++ )
      {
        Mat cvI( m_image.rows, m_image.cols, CV_32F, m_smoothed_gradient_layers[r  ].ptr<float>(th,0,0) );
        Mat cvO( m_image.rows, m_image.cols, CV_32F, m_smoothed_gradient_layers[r+1].ptr<float>(th,0,0) );
        GaussianBlur( cvI, cvO, Size(ks, ks), sigma, sigma, BORDER_REPLICATE );
      }
    }
    compute_histograms();
}

inline void DAISY_Impl::compute_oriented_grid_points()
{
    m_oriented_grid_points =
        Mat( g_grid_orientation_resolution, m_grid_point_number*2, CV_64F );

    for( int i=0; i<g_grid_orientation_resolution; i++ )
    {
      double angle = -i*2.0*CV_PI/g_grid_orientation_resolution;

      double kos = cos( angle );
      double zin = sin( angle );

      Mat point_list = m_oriented_grid_points.row( i );

      for( int k=0; k<m_grid_point_number; k++ )
      {
         double y = m_grid_points.at<double>(k,0);
         double x = m_grid_points.at<double>(k,1);

         point_list.at<double>(2*k+1) =  x*kos + y*zin; // x
         point_list.at<double>(2*k  ) = -x*zin + y*kos; // y
      }
    }
}

struct MaxDoGInvoker : ParallelLoopBody
{
    MaxDoGInvoker( Mat* _next_sim, Mat* _sim, Mat* _max_dog, Mat* _scale_map, int _i, int _r )
    {
      i = _i;
      r = _r;
      sim = _sim;
      max_dog = _max_dog;
      next_sim = _next_sim;
      scale_map = _scale_map;
    }

    void operator ()(const cv::Range& range) const CV_OVERRIDE
    {
      for (int c = range.start; c < range.end; ++c)
      {
        float dog = (float) fabs( next_sim->at<float>(r,c) - sim->at<float>(r,c) );
        if( dog > max_dog->at<float>(r,c) )
        {
          max_dog->at<float>(r,c) = dog;
          scale_map->at<float>(r,c) = (float) i;
        }
      }
    }
    int i, r;
    Mat* max_dog;
    Mat* scale_map;
    Mat *sim, *next_sim;
};

struct RoundingInvoker : ParallelLoopBody
{
    RoundingInvoker( Mat* _scale_map, int _r )
    {
      r = _r;
      scale_map = _scale_map;
    }

    void operator ()(const cv::Range& range) const CV_OVERRIDE
    {
      for (int c = range.start; c < range.end; ++c)
      {
        scale_map->at<float>(r,c) = (float) cvRound( scale_map->at<float>(r,c) );
      }
    }
    int r;
    Mat* scale_map;
};

inline void DAISY_Impl::compute_scales()
{
    //###############################################################################
    //# scale detection is work-in-progress! do not use it if you're not Engin Tola #
    //###############################################################################

    Mat sim, next_sim;
    float sigma = (float) ( pow( g_sigma_step, g_scale_st)*g_sigma_0 );

    int ks = filter_size( sigma, 3.0f );
    GaussianBlur( m_image, sim, Size(ks, ks), sigma, sigma, BORDER_REPLICATE );

    Mat max_dog( m_image.rows, m_image.cols, CV_32F, Scalar(0) );
    m_scale_map = Mat( m_image.rows, m_image.cols, CV_32F, Scalar(0) );


    float sigma_prev;
    float sigma_new;
    float sigma_inc;

    sigma_prev = (float) g_sigma_0;
    for( int i=0; i<g_scale_en; i++ )
    {
      sigma_new  = (float) ( pow( g_sigma_step, g_scale_st + i  ) * g_sigma_0 );
      sigma_inc  = sqrt( sigma_new*sigma_new - sigma_prev*sigma_prev );
      sigma_prev = sigma_new;

      ks = filter_size( sigma_inc, 3.0f );

      GaussianBlur( sim, next_sim, Size(ks, ks), sigma_inc, sigma_inc, BORDER_REPLICATE );

      for( int r=0; r<m_image.rows; r++ )
      {
        parallel_for_( Range(0, m_image.cols), MaxDoGInvoker( &next_sim, &sim, &max_dog, &m_scale_map, i, r ) );
      }
      sim.release();
      sim = next_sim;
    }

    ks = filter_size( 10.0f, 3.0f );
    GaussianBlur( m_scale_map, m_scale_map, Size(ks, ks), 10.0f, 10.0f, BORDER_REPLICATE );

    for( int r=0; r<m_image.rows; r++ )
    {
      parallel_for_( Range(0, m_image.cols), RoundingInvoker( &m_scale_map, r ) );
    }
}


inline void DAISY_Impl::compute_orientations()
{
    //#####################################################################################
    //# orientation detection is work-in-progress! do not use it if you're not Engin Tola #
    //#####################################################################################

    CV_Assert( !m_image.empty() );

    int dims[4] = { 1, m_orientation_resolution, m_image.rows, m_image.cols };
    Mat rotation_layers(4, dims, CV_32F);
    layered_gradient( m_image, &rotation_layers );

    m_orientation_map = Mat(m_image.rows, m_image.cols, CV_16U, Scalar(0));

    int ori, max_ind;
    float max_val;

    int next, prev;
    float peak, angle;

    int x, y, kk;

    Mat hist;

    float sigma_inc;
    float sigma_prev = 0.0f;
    float sigma_new;

    for( int scale=0; scale<g_scale_en; scale++ )
    {

      sigma_new  = (float)( pow( g_sigma_step, scale  ) * m_rad / 3.0f );
      sigma_inc  = sqrt( sigma_new*sigma_new - sigma_prev*sigma_prev );
      sigma_prev = sigma_new;

      smooth_layers( &rotation_layers, sigma_inc );

      for( y=0; y<m_image.rows; y ++ )
      {
         hist = Mat(1, m_orientation_resolution, CV_32F);

         for( x=0; x<m_image.cols; x++ )
         {
            if( m_scale_invariant && m_scale_map.at<float>(y,x) != scale ) continue;

            for (ori = 0; ori < m_orientation_resolution; ori++)
            {
              hist.at<float>(ori) = rotation_layers.at<float>(ori, y, x);
            }
            for( kk=0; kk<6; kk++ )
               smooth_histogram( &hist, m_orientation_resolution );

            max_val = -1;
            max_ind =  0;
            for( ori=0; ori<m_orientation_resolution; ori++ )
            {
               if( hist.at<float>(ori) > max_val )
               {
                  max_val = hist.at<float>(ori);
                  max_ind = ori;
               }
            }

            prev = max_ind-1;
            if( prev < 0 )
               prev += m_orientation_resolution;

            next = max_ind+1;
            if( next >= m_orientation_resolution )
               next -= m_orientation_resolution;

            peak = interpolate_peak(hist.at<float>(prev), hist.at<float>(max_ind), hist.at<float>(next));
            angle = (float)( ((float)max_ind + peak)*360.0/m_orientation_resolution );

            int iangle = int(angle);

            if( iangle <    0 ) iangle += 360;
            if( iangle >= 360 ) iangle -= 360;

            if( !(iangle >= 0.0 && iangle < 360.0) )
            {
               angle = 0;
            }
            m_orientation_map.at<float>(y,x) = (float)iangle;
         }
         hist.release();
      }
    }
    compute_oriented_grid_points();
}


inline void DAISY_Impl::initialize_single_descriptor_mode( )
{
    initialize();
    compute_smoothed_gradient_layers();
}

inline void DAISY_Impl::set_parameters( )
{
    m_grid_point_number = m_rad_q_no * m_th_q_no + 1; // +1 is for center pixel
    m_descriptor_size = m_grid_point_number * m_hist_th_q_no;

    for( int i=0; i<360; i++ )
    {
      m_orientation_shift_table[i] = i/360.0 * m_hist_th_q_no;
    }

    compute_cube_sigmas();
    compute_grid_points();
}

// set/convert image array for daisy internal routines
// daisy internals use CV_32F image with norm to 1.0f
inline void DAISY_Impl::set_image( InputArray _image )
{
    // release previous image
    // and previous workspace
    reset();
    // fetch new image
    Mat image = _image.getMat();
    // image cannot be empty
    CV_Assert( ! image.empty() );
    // clone image for conversion
    if ( image.depth() != CV_32F ) {

      m_image = image.clone();
      // convert to gray inplace
      if( m_image.channels() > 1 )
          cvtColor( m_image, m_image, COLOR_BGR2GRAY );
      // convert and normalize
      m_image.convertTo( m_image, CV_32F );
      m_image /= 255.0f;
    } else
      // use original user supplied CV_32F image
      // should be a normalized one (cannot check)
      m_image = image;
}


// -------------------------------------------------
/* DAISY interface implementation */

// keypoint scope
void DAISY_Impl::compute( InputArray _image, std::vector<KeyPoint>& keypoints, OutputArray _descriptors )
{
    // do nothing if no image
    if( _image.getMat().empty() )
      return;

    set_image( _image );

    // whole image
    m_roi = Rect( 0, 0, m_image.cols, m_image.rows );

    // get homography
    Mat H = m_h_matrix;

    // convert to double if case
    if ( H.depth() != CV_64F )
        H.convertTo( H, CV_64F );

    set_parameters();

    initialize_single_descriptor_mode();

    // allocate array
    _descriptors.create( (int) keypoints.size(), m_descriptor_size, CV_32F );

    // prepare descriptors
    Mat descriptors = _descriptors.getMat();
    descriptors.setTo( Scalar(0) );

    // iterate over keypoints
    // and fill computed descriptors
    if ( H.empty() )
      for (int k = 0; k < (int) keypoints.size(); k++)
      {
          get_descriptor( keypoints[k].pt.y, keypoints[k].pt.x,
                          m_use_orientation ? (int) keypoints[k].angle : 0,
                          &descriptors.at<float>( k, 0 ), &m_smoothed_gradient_layers,
                          &m_oriented_grid_points, m_orientation_shift_table, m_th_q_no,
                          m_hist_th_q_no, m_grid_point_number, m_descriptor_size, m_enable_interpolation,
                          m_nrm_type );
      }
    else
      for (int k = 0; k < (int) keypoints.size(); k++)
      {
        get_descriptor_h( keypoints[k].pt.y, keypoints[k].pt.x,
                          m_use_orientation ? (int) keypoints[k].angle : 0,
                          &descriptors.at<float>( k, 0 ), &H.at<double>( 0 ), &m_smoothed_gradient_layers,
                          m_cube_sigmas, &m_grid_points, m_orientation_shift_table, m_th_q_no,
                          m_hist_th_q_no, m_grid_point_number, m_descriptor_size, m_enable_interpolation,
                          m_nrm_type );
      }

}

// full scope with roi
void DAISY_Impl::compute( InputArray _image, Rect roi, OutputArray _descriptors )
{
    // do nothing if no image
    if( _image.getMat().empty() )
      return;

    CV_Assert( m_h_matrix.empty() );
    CV_Assert( ! m_use_orientation );

    set_image( _image );

    m_roi = roi;

    set_parameters();
    initialize_single_descriptor_mode();

    _descriptors.create( m_roi.width*m_roi.height, m_descriptor_size, CV_32F );

    Mat descriptors = _descriptors.getMat();

    // compute full desc
    compute_descriptors( &descriptors );
    normalize_descriptors( &descriptors );
}

// full scope
void DAISY_Impl::compute( InputArray _image, OutputArray _descriptors )
{
    // do nothing if no image
    if( _image.getMat().empty() )
      return;

    CV_Assert( m_h_matrix.empty() );
    CV_Assert( ! m_use_orientation );

    set_image( _image );

    // whole image
    m_roi = Rect( 0, 0, m_image.cols, m_image.rows );

    set_parameters();
    initialize_single_descriptor_mode();

    _descriptors.create( m_roi.width*m_roi.height, m_descriptor_size, CV_32F );

    Mat descriptors = _descriptors.getMat();

    // compute full desc
    compute_descriptors( &descriptors );
    normalize_descriptors( &descriptors );
}

// constructor
DAISY_Impl::DAISY_Impl( float _radius, int _q_radius, int _q_theta, int _q_hist,
             DAISY::NormalizationType _norm, InputArray _H, bool _interpolation, bool _use_orientation )
           : m_rad(_radius), m_rad_q_no(_q_radius), m_th_q_no(_q_theta), m_hist_th_q_no(_q_hist),
             m_nrm_type(_norm), m_enable_interpolation(_interpolation), m_use_orientation(_use_orientation)
{

    m_descriptor_size = 0;
    m_grid_point_number = 0;

    m_scale_invariant = false;
    m_rotation_invariant = false;
    m_orientation_resolution = 36;

    m_h_matrix = _H.getMat();
}

// destructor
DAISY_Impl::~DAISY_Impl()
{
    release_auxiliary();
}

void DAISY_Impl::read( const FileNode& fn)
{
  fn["radius"] >> m_rad;
  fn["q_radius"] >> m_rad_q_no;
  fn["q_theta"] >> m_th_q_no;
  fn["q_hist"] >> m_hist_th_q_no;
  int norm_type;
  fn["norm_type"] >> norm_type;
  setNorm(norm_type);
  fn["enable_interpolation"] >> m_enable_interpolation;
  fn["use_orientation"] >> m_use_orientation;
}
void DAISY_Impl::write( FileStorage& fs) const
{
  if(fs.isOpened())
  {
    fs << "name" << getDefaultName();
    fs << "radius" << m_rad;
    fs << "q_radius" << m_rad_q_no;
    fs << "q_theta" << m_th_q_no;
    fs << "q_hist" << m_hist_th_q_no;
    fs << "norm_type" << (int)m_nrm_type;
    fs << "enable_interpolation" << m_enable_interpolation;
    fs << "use_orientation" << m_use_orientation;
  }
}

Ptr<DAISY> DAISY::create( float radius, int q_radius, int q_theta, int q_hist,
             DAISY::NormalizationType norm, InputArray H, bool interpolation, bool use_orientation)
{
    return makePtr<DAISY_Impl>(radius, q_radius, q_theta, q_hist, norm, H, interpolation, use_orientation);
}

String DAISY::getDefaultName() const
{
  return (Feature2D::getDefaultName() + ".DAISY");
}


} // END NAMESPACE XFEATURES2D
} // END NAMESPACE CV
