/* -*- mia-c++  -*-
 *
 * This file is part of MIA - a toolbox for medical image analysis
 * Copyright (c) Leipzig, Madrid 1999-2017 Gert Wollny
 *
 * MIA is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with MIA; if not, see <http://www.gnu.org/licenses/>.
 *
 */

#include <limits>
#include <mia/2d/filter.hh>
#include <libmia/filter2dimage.hh>


namespace kuwahara_2dimage_filter
{
NS_MIA_USE;
using namespace std;

static char const *plugin_name = "kuwahara";
static const CIntOption param_hw("w", "filter parameter", 1, 1, numeric_limits<int>::max());

class C2DKuwahara : public C2DFilter
{
public:
       C2DKuwahara(const CParsedOptions& options);

       template <class Data2D>
       typename C2DKuwahara::result_type operator () (const Data2D& data) const ;
private:
       template <class Data2D>
       typename Data2D::value_type run(const Data2D& image, int x, int y)const;

       template <class Data2D>
       void run_sub(const Data2D& image, int cx, int cy, float& mu, float& sigma)const;

       int m_l;
       int m_kh;


};

typedef C2DImageFilter<C2DKuwahara> C2DKuwaharaImageFilter;
//template class mia::C2DImageFilter<C2DKuwahara>;


class C2DKuwaharaImageFilterFactory: public C2DFilterPlugin
{
public:
       C2DKuwaharaImageFilterFactory();
       virtual C2DFilter *create(const CParsedOptions& options) const;
       virtual const string do_get_descr()const;
};

C2DKuwahara::C2DKuwahara(const CParsedOptions& options):
       m_l(param_hw.get_value(options)),
       m_kh(2 * m_l + 1)
{
}

template <class Data2D>
void C2DKuwahara::run_sub(const Data2D& image, int cx, int cy, float& mu, float& sigma)const
{
       int n = 0;
       const int ymax = min(cy +  m_kh, (int)image.get_size().y);
       const int xmax = min(cx +  m_kh, (int)image.get_size().x);
       mu = 0.0f;
       sigma = 0.0f;

       for (int y = max(cy, 0);  y < ymax ; ++y)
              for (int x = max(cx, 0); x < xmax; ++x) {
                     ++n;
                     const float val = image(x, y);
                     mu += val;
                     sigma += val * val;
              }

       if (n > 1) {
              mu /= n;
              sigma = (sigma - n * mu * mu) / (n - 1);
       } else
              sigma = 0;
}

template <class Data2D>
typename Data2D::value_type C2DKuwahara::run(const Data2D& image, int x, int y)const
{
       float mu[5];
       float sigma[5];
       run_sub(image, x - m_kh + 1, y - m_kh + 1, mu[0], sigma[0]);
       run_sub(image, x - m_kh + 1, y, mu[1], sigma[1]);
       run_sub(image, x, y - m_kh + 1, mu[2], sigma[2]);
       run_sub(image, x, y, mu[3], sigma[3]);
       run_sub(image, x - m_l, y - m_l, mu[4], sigma[4]);
       int best = 0;

       for (int i = 1; i < 4; ++i) {
              if (sigma[best] > sigma[i]) {
                     best = i;
              }
       }

       return static_cast<typename Data2D::value_type>(mu[best]);
}


template <class Data2D>
typename C2DKuwahara::result_type C2DKuwahara::operator () (const Data2D& data) const
{
       cvdebug() << "C2DKuwahara::operator () begin\n";
       Data2D *result = new Data2D(data.get_size(), data);
       typename Data2D::iterator i = result->begin();

       for (int y = 0; y < (int)data.get_size().y; ++y) {
              for (int x = 0; x < (int)data.get_size().x; ++x, ++i)
                     *i = run(data, x, y);
       }

       cvdebug() << "C2DKuwahara::operator () end\n";
       return P2DImage(result);
}

C2DKuwaharaImageFilterFactory::C2DKuwaharaImageFilterFactory():
       C2DFilterPlugin(plugin_name)
{
       add_help(param_hw);
}

C2DFilter *C2DKuwaharaImageFilterFactory::create(const CParsedOptions& options) const
{
       return new C2DKuwaharaImageFilter(options);
}

const string C2DKuwaharaImageFilterFactory::do_get_descr()const
{
       return "2D image kuwahara filter";
}

extern "C" EXPORT CPluginBase *get_plugin_interface()
{
       return new C2DKuwaharaImageFilterFactory();
}

} // end namespace kuwahara_2dimage_filter
