/* -*- 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 <mia/2d/filter/thinning.hh>

NS_BEGIN(thinning_2dimage_filter) 
using namespace mia;
using namespace std;

C2DThinningImageFilter::C2DThinningImageFilter(int max_iterations):
	C2D2MaskMorphImageFilter(max_iterations)
{

	C2DMorphShape s1; 
	s1.add_pixel(-1, -1, false); 
	s1.add_pixel( 0, -1, false); 
	s1.add_pixel( 1, -1, false); 
	s1.add_pixel( 0,  0, true); 
	s1.add_pixel(-1,  1, true); 
	s1.add_pixel( 0,  1, true); 
	s1.add_pixel( 1,  1, true); 

	C2DMorphShape s2; 
	s2.add_pixel( 0, -1, false); 
	s2.add_pixel( 1, -1, false); 
	s2.add_pixel( 1,  0, false); 
	s2.add_pixel( 0,  0, true); 
	s2.add_pixel(-1,  0, true); 
	s2.add_pixel(-1,  1, true); 
	s2.add_pixel( 0,  1, true); 

	set_shapes(s1,s2); 
	
}

C2DPruningImageFilter::C2DPruningImageFilter(int max_iterations):
	C2D2MaskMorphImageFilter(max_iterations)
{

	cvdebug() << "Initialize pruning with " << max_iterations << " iterations\n"; 

	C2DMorphShape s1; 
	s1.add_pixel(-1, -1, false); 
	s1.add_pixel( 0, -1, false); 
	s1.add_pixel( 1, -1, false); 
	s1.add_pixel(-1,  0, false); 
	s1.add_pixel( 0,  0, true ); 
	s1.add_pixel( 1,  0, false); 
	s1.add_pixel(-1,  1, false); 

	C2DMorphShape s2; 
	s2.add_pixel(-1, -1, false); 
	s2.add_pixel( 0, -1, false); 
	s2.add_pixel( 1, -1, false); 

	s2.add_pixel(-1,  0, false); 
	s2.add_pixel( 0,  0, true ); 
	s2.add_pixel( 1,  0, false); 

	s2.add_pixel( 1,  1, false); 

	set_shapes(s1,s2); 
	
}


C2D2MaskMorphImageFilter::C2D2MaskMorphImageFilter(int max_iterations):
	m_shape(8), 
	m_max_iterations(max_iterations)
{
}

void C2D2MaskMorphImageFilter::set_shapes(const mia::C2DMorphShape& s1, const mia::C2DMorphShape& s2)
{
	m_shape[0] = s1; 
	m_shape[1] = s2;
	
	for(int i = 0; i < 3; ++i) {
		m_shape[2*i + 2] = m_shape[2*i].rotate_by_90(); 
		m_shape[2*i + 3] = m_shape[2*i+1].rotate_by_90(); 
	}
}


template <typename Image> 
struct __dispatch_thinning{
	static P2DImage apply(const Image& , const vector<C2DMorphShape>& , int ) {
		throw invalid_argument("Thinning can only be applied to binary images"); 
	}
}; 

template <> 
struct __dispatch_thinning<C2DBitImage> {
	
	static size_t run_pass(C2DBitImage& target, const C2DBitImage& source, 
			       const vector<C2DMorphShape>& shape) {
		size_t changed_pixels = 0; 
		C2DBitImage tmp(source.get_size()); 

		changed_pixels += morph_thinning_2d(target, source, shape[0]);
		cvdebug() << "0: changed_pixels = " <<changed_pixels << "\n"; 
		for (int i = 1; i < 8; ++i) {
			copy(target.begin(), target.end(), tmp.begin()); 
			changed_pixels += morph_thinning_2d(target, tmp, shape[i]);
			cvdebug() << i << ": changed_pixels = " <<changed_pixels << "\n"; 
		}
		
		return changed_pixels; 
	}

	static P2DImage apply(const C2DBitImage& image, const vector<C2DMorphShape>& shapes, int m_max_iterations) {
		C2DBitImage temp1(image); 
		C2DBitImage *temp2 = new C2DBitImage(image.get_size(), image); 

		int i = 0; 
		while ((!m_max_iterations  || i < m_max_iterations) && run_pass(*temp2, temp1, shapes)) {
			++i; 
			copy(temp2->begin(), temp2->end(), temp1.begin()); 
			cvdebug() << "run pass " << i << " of " << m_max_iterations << "\n"; 
		}
		return P2DImage(temp2); 
	}
}; 

template <typename T>
typename C2DFilter::result_type C2D2MaskMorphImageFilter::operator () (const T2DImage<T>& result)const
{
	return __dispatch_thinning<T2DImage<T>>::apply(result, m_shape, m_max_iterations); 

}

P2DImage C2D2MaskMorphImageFilter::do_filter(const C2DImage& src) const
{
	return mia::filter(*this, src); 
}


C2DThinningFilterFactory::C2DThinningFilterFactory():
	C2DFilterPlugin("thinning"),
	m_max_iterations(0)
{
	add_parameter("iter", make_ci_param(m_max_iterations, 1, 1000000, false,  
					    "Number of iterations to run, 0=until convergence"));
}

mia::C2DFilter *C2DThinningFilterFactory::do_create()const
{
	return new C2DThinningImageFilter(m_max_iterations);
}

const std::string C2DThinningFilterFactory::do_get_descr()const
{
	return "Morphological thinning. Thinning until convergence will result in a 8-connected skeleton"; 
}


C2DPruningFilterFactory::C2DPruningFilterFactory():
	C2DFilterPlugin("pruning"),
	m_max_iterations(0)
{
	add_parameter("iter", make_ci_param(m_max_iterations, 1, 1000000, false,  
					    "Number of iterations to run, 0=until convergence"));
}

mia::C2DFilter *C2DPruningFilterFactory::do_create()const
{
	return new C2DPruningImageFilter(m_max_iterations);
}

const std::string C2DPruningFilterFactory::do_get_descr()const
{
	return "Morphological pruning. Pruning until convergence will erase all pixels but closed loops.";
}


extern "C" EXPORT CPluginBase *get_plugin_interface()
{
	auto result = new C2DThinningFilterFactory(); 
	result->append_interface(new C2DPruningFilterFactory()); 
	return result; 
}


NS_END

