/* -*- 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 <iomanip>
#include <ostream>
#include <fstream>
#include <map>
#include <boost/filesystem.hpp>


//#include <mia/core/fft1d_r2c.hh>
#include <queue>

#include <mia/core.hh>
#include <mia/core/meanvar.hh>
#include <mia/core/tools.hh>
#include <mia/core/ica.hh>
#include <mia/2d/imageio.hh>
#include <mia/2d/filter.hh>
#include <mia/2d/ica.hh>
#include <mia/2d/segsetwithimages.hh>
#include <mia/2d/fuzzyseg.hh>
#include <mia/2d/perfusion.hh>
#include <mia/2d/transformfactory.hh>
#include <mia/2d/datafield.cxx>

#include <numeric> 

NS_MIA_USE;
using namespace std; 

const SProgramDescription g_description = {
	{pdi_group, "Work in progress"}, 
	{pdi_short, "Work in progress"}, 
	{pdi_description, "This program is work in progress"}, 
	{pdi_example_descr, "Example"}, 
	{pdi_example_code, "Example"}
}; 

namespace bfs=boost::filesystem; 

class CEvaluateSeriesCorrelationToMask: public TFilter<bool> {
public: 
	CEvaluateSeriesCorrelationToMask(const C2DBitImage& mask, size_t len); 
	
	template <typename T> 
	bool operator () (const T2DImage<T>& image); 


	template <typename Iterator>
	C2DFImage get_correlation_image(Iterator begin, Iterator end); 	


private: 
	C2DBounds m_size; 
	const C2DBitImage& m_mask; 

	int m_mask_points; 
	vector<float> m_mask_mean_series; 
	T2DDatafield< vector<float> > m_series; 
}; 


CEvaluateSeriesCorrelationToMask::CEvaluateSeriesCorrelationToMask(const C2DBitImage& mask, size_t len):
	m_size(mask.get_size()), 
	m_mask(mask), 
	m_mask_points(0), 
	m_series(mask.get_size())
{
	m_mask_mean_series.reserve(len); 

	// count the mask pixels
	for_each(mask.begin(), mask.end(), [this](bool pixel){if (pixel) ++m_mask_points; }); 
	

	// fill the image representation of the series 
	for_each(m_series.begin(), m_series.end(), [&len](vector<float>& v){v.reserve(len);}); 
	
	if (!m_mask_points) 
		throw invalid_argument("CEvaluateSeriesCorrelationToMask: got empty mask"); 
	

}

template <typename T> 
bool CEvaluateSeriesCorrelationToMask::operator () (const T2DImage<T>& image)
{
	float mask_mean = 0.0; 
	if (image.get_size() != m_size) {
		throw create_exception<invalid_argument>( "CEvaluateSeriesCorrelationToMask: got image of size ", image.get_size(), 
						" but expected ", m_size); 
	}

	auto ii = image.begin(); 
	auto ie = image.end(); 
	
	auto im = m_mask.begin(); 
	auto is = m_series.begin(); 

	while (ii != ie) {
		if (*im)
			mask_mean += *ii; 
		is->push_back(*ii); 
		++im; 
		++is; 
		++ii; 
	}
	m_mask_mean_series.push_back(mask_mean / m_mask_points); 
	return true; 
}

float correlation(const vector<float>& x, const vector<float>& y) 
{
	assert(x.size() == y.size()); 
	assert(x.size() > 1); 

	float sxx = inner_product(x.begin(),x.end(),x.begin(),0.0f); 
	float syy = inner_product(y.begin(),y.end(),y.begin(),0.0f); 
	float sxy = inner_product(y.begin(),y.end(),x.begin(),0.0f); 
	float sx = accumulate(x.begin(),x.end(), 0.0f); 
	float sy = accumulate(y.begin(),y.end(), 0.0f); 

	auto value = (x.size() * sxy - sx * sy) / 
		(sqrt(x.size() * sxx - sx *sx) * sqrt(x.size() * syy - sy *sy)); 
	return value; 
}

template <typename Iterator>
C2DFImage CEvaluateSeriesCorrelationToMask::get_correlation_image(Iterator begin, Iterator end) 
{

	CEvaluateSeriesCorrelationToMask *self = this; 
	for_each(begin, end, [&self](P2DImage x) {mia::accumulate(*self, *x);});
	
	C2DFImage result(m_size); 

	transform(m_series.begin(), m_series.end(), result.begin(), 
		  [this](const vector<float>& v){ return correlation(m_mask_mean_series, v); });
	return result; 
}



class CHasNonzeroPixels: public TFilter<bool>  {
public: 
	template <typename T> 
	bool operator () (const T2DImage<T>& image) const {
		for (auto i = image.begin(); i != image.end(); ++i) {
			if (*i) 
				return true; 
		}
		return false; 
	}
}; 

class FGetMaxIntensity: public TFilter<float>  {
public: 
	template <typename T> 
	float operator () (const T2DImage<T>& image) const {
		auto max_it = max_element(image.begin(), image.end()); 
		return *max_it; 
	}
}; 


class FAcuumulateGradients: public TFilter<int>  {
public:
	FAcuumulateGradients(const C2DBounds& size); 

	template <typename T> 
	int operator () (const T2DImage<T>& image); 

	C2DFImage get_result() const; 
private: 
	C2DFImage m_sum; 
}; 

FAcuumulateGradients::FAcuumulateGradients(const C2DBounds& size):
	m_sum(size)
{
}

template <typename T> 
int FAcuumulateGradients::operator () (const T2DImage<T>& image)
{
	if (m_sum.get_size() != image.get_size()) {
		throw create_exception<invalid_argument>( "Input image has size ", image.get_size(), 
						" but expect ", m_sum.get_size()); 
	}
	auto vf = get_gradient(image);
	transform(m_sum.begin(), m_sum.end(), vf.begin(), m_sum.begin(), 
		  [](float s, const C2DFVector& v) -> float {float vn = v.norm(); 
			  return s + vn;});
	return 0;
}

C2DFImage FAcuumulateGradients::get_result() const
{
	return m_sum; 
}


class FMaxGradients: public TFilter<int>  {
public:
	FMaxGradients(const C2DBounds& size); 

	template <typename T> 
	int operator () (const T2DImage<T>& image); 

	C2DFImage get_result() const; 
private: 
	C2DFImage m_sum; 
}; 

FMaxGradients::FMaxGradients(const C2DBounds& size):
	m_sum(size)
{
}

template <typename T> 
int FMaxGradients::operator () (const T2DImage<T>& image)
{
	if (m_sum.get_size() != image.get_size()) {
		throw create_exception<invalid_argument>( "Input image has size ", image.get_size(), 
						" but expect ", m_sum.get_size()); 
	}
	auto vf = get_gradient(image);
	transform(m_sum.begin(), m_sum.end(), vf.begin(), m_sum.begin(), 
		  [](float s, const C2DFVector& v) -> float {float vn = v.norm(); 
			  return s > vn ? s : vn;});
	return 0;
}

C2DFImage FMaxGradients::get_result() const
{
	return m_sum; 
}

class FMaxIntensity: public TFilter<int>  {
public:
	FMaxIntensity(const C2DBounds& size); 

	template <typename T> 
	int operator () (const T2DImage<T>& image); 

	C2DFImage get_result() const; 
private: 
	C2DFImage m_sum; 
}; 

FMaxIntensity::FMaxIntensity(const C2DBounds& size):
	m_sum(size)
{
}

template <typename T> 
int FMaxIntensity::operator () (const T2DImage<T>& image)
{
	if (m_sum.get_size() != image.get_size()) {
		throw create_exception<invalid_argument>( "Input image has size ", image.get_size(), 
						" but expect ", m_sum.get_size()); 
	}

	transform(m_sum.begin(), m_sum.end(), image.begin(), m_sum.begin(), 
		  [](float s, T i){return s > i ? s : i;});
	return 0;
}

C2DFImage FMaxIntensity::get_result() const
{
	return m_sum; 
}




P2DImage evaluate_bridge_mask(P2DImage RV_mask, P2DImage LV_mask)
{
	assert(RV_mask); 
	assert(LV_mask); 
	assert(LV_mask->get_size() == RV_mask->get_size()); 
	
	const C2DBitImage& RV_bin = dynamic_cast<const C2DBitImage&>(*RV_mask); 
	const C2DBitImage& LV_bin = dynamic_cast<const C2DBitImage&>(*LV_mask); 

	C2DBitImage tmp(RV_bin.get_size()); 
	transform(LV_bin.begin(), LV_bin.end(), RV_bin.begin(), tmp.begin(), logical_or<bool>()); 
	
	P2DImage closed  = run_filter(tmp, "close:shape=[sphere:r=10]");
	auto closed_bin = dynamic_cast<const C2DBitImage&>(*closed); 
	
	// substract the original from the closed mask 
	transform(closed_bin.begin(), closed_bin.end(), tmp.begin(), tmp.begin(), 
		  [](bool c, bool org){ return (c && !org);}); 
	
	closed = run_filter(tmp, "label"); 
	return run_filter(*closed, "selectbig");
}

template <typename Iterator1, typename Iterator2, typename Function>
void for_each_pair(Iterator1 begin1, Iterator1 end1, Iterator2 begin2, Function f)
{
	while (begin1 != end1)
		f(*begin1++, *begin2++); 
}

/*
   Combine input mask ad add use distinct label for each mask. 
   In addition add the boundary of the image as an additional label 
*/
P2DImage combine_with_boundary(const C2DImageSeries& images)
{
	assert(!images.empty()); 
	assert(images.size() < 254); 
	C2DBounds size = images[0]->get_size(); 

	unsigned char nimages = static_cast<unsigned char>(images.size()); 
	unsigned char boundary_label = nimages + 1; 
	
	cvinfo() << "boundary_label = " << (int)boundary_label << "\n"; 

	vector<C2DBitImage::const_iterator> it(images.size());
	transform(images.begin(), images.end(), it.begin(), 
		  [] (P2DImage pimage) { 
			  return dynamic_cast<C2DBitImage&> (*pimage).begin(); 
		  });
	
	C2DUBImage *seed = new C2DUBImage(size); 
	auto is = seed->begin(); 

	for (size_t y = 0; y < size.y; ++y) 
		for (size_t x = 0; x < size.x; ++x, ++is) {
			if ( y == 0  || x == 0 || y == size.y - 1 || x == size.x - 1) {
				*is = boundary_label;
			}else{
				for (unsigned char k = 0; k < nimages; ++k) {
					if ( *it[k] )
						*is = k + 1; 
				}
			}
			for_each(it.begin(), it.end(), [](C2DBitImage::const_iterator& i){++i;}); 
		}
	
	return P2DImage(seed); 
}

/**
   Evaluate the cavity aof a ventricle basd on an enhancement feature image 
   first run a series of filters to extract a seed, and then use a seeded 
   watershed to obtain a better aproximation of the cavity. 
 */

P2DImage evaluate_cavity(P2DImage feature, const C2DImageFilterChain& seed_chain)
{
	auto seed = seed_chain.run(feature); 
	C2DImageSeries prep(1); 
	prep[0] = seed;
	P2DImage new_seed = combine_with_boundary(prep); 
	save_image("prep_seed.@", new_seed); 
	return run_filter_chain(feature, {"sws:seed=prep_seed.@", "binarize:min=1,max=1"});
}

static void create_and_save_evaluation_shapes(const string& save_feature, P2DImage final_myocard_mask)
{
	auto inverse_labeled =  run_filter_chain(final_myocard_mask, {"invert", "label", "sort-label"});
	auto label1 = run_filter(inverse_labeled, "binarize:min=1,max=1"); 
	auto label2 = run_filter(inverse_labeled, "binarize:min=2,max=2");
	
	// the "outside" label must have the pixel (0,0) set 
	auto label1_bit = dynamic_cast<const C2DBitImage&>( *label1 ); 
	if ( !label1_bit(0,0) ) 
		swap(label1, label2); 
	
	save_image(save_feature+"-2-LV-endo.png", label2);
	save_image(save_feature+"-2-LV-exo.png", run_filter(label1, "invert")); 

	save_image("LV-endo.@", label2);
	auto endo_line = run_filter_chain(label2, {"dilate:shape=4n", "mask:input=LV-endo.@,inverse=1"}); 
	save_image(save_feature+"-2-LV-endo-line.png", endo_line);
	save_image(save_feature+"-2-LV-endo-distance.v", run_filter(endo_line, "distance"));

	save_image("LV-exo.@", label1);
	auto exo_line = run_filter_chain(label1, {"dilate:shape=4n", "mask:input=LV-exo.@,inverse=1"}); 
	save_image(save_feature+"-2-LV-exo-line.png", exo_line);
	save_image(save_feature+"-2-LV-exo-distance.v", run_filter(exo_line, "distance"));
	
}

int do_main( int argc, char *argv[] )
{
	// IO parameters 
	string in_filename;
	string out_filename; 
	string save_feature; 

	// this parameter is currently not exported - reading the image data is 
	// therefore done from the path given in the segmentation set 
	bool override_src_imagepath = true;

	// ICA parameters 
	size_t components = 0;
	bool normalize = false; 
	bool no_meanstrip = false; 
	size_t skip_images = 2; 
	size_t max_ica_iterations = 400;
	PIndepCompAnalysisFactory icatool;

	const auto& imageio = C2DImageIOPluginHandler::instance();

	CCmdOptionList options(g_description);
	options.set_group("File-IO"); 
	options.add(make_opt( in_filename, "in-file", 'i', "input perfusion data set", 
			      CCmdOptionFlags::required_input));
	options.add(make_opt( out_filename, "out-file", 'o', "output myocardial mask", 
			      CCmdOptionFlags::required_output, &imageio));
	options.add(make_opt( save_feature, "save-features", 'f', "save ICA features to files with this name base", 
			      CCmdOptionFlags::output)); 

	options.set_group("ICA");
	options.add(make_opt( icatool, "internal", "fastica", 0, "FastICA implementationto be used"));
	options.add(make_opt( components, "components", 'C', "ICA components 0 = automatic estimation testing 4 and 5"));
	options.add(make_opt( normalize, "normalize", 0, "normalized ICs"));
	options.add(make_opt( no_meanstrip, "no-meanstrip", 0, 
				    "don't strip the mean from the mixing curves"));
	options.add(make_opt( skip_images, "skip", 'k', "skip images at the beginning of the series "
				    "as they are of other modalities")); 
	options.add(make_opt( max_ica_iterations, "max-ica-iter", 'm', "maximum number of iterations in ICA")); 

	if (options.parse(argc, argv) != CCmdOptionList::hr_no) 
		return EXIT_SUCCESS; 

	// load input data set
	CSegSetWithImages  input_set(in_filename, override_src_imagepath);
	C2DImageSeries input_images = input_set.get_images(); 
	
	vector<C2DFImage> series(input_images.size() - skip_images); 
	transform(input_images.begin() + skip_images, input_images.end(), 
		  series.begin(), FCopy2DImageToFloatRepn()); 
	
	int rv_idx = -1; 
	int lv_idx = -1; 
	int perf_idx = -1; 

	unique_ptr<C2DPerfusionAnalysis> ica; 
	int test_components = components ? components - 1 : 3; 
	do {
		++test_components; 
		ica.reset(new C2DPerfusionAnalysis(test_components, normalize, !no_meanstrip)); 
		
		if (max_ica_iterations) 
			ica->set_max_ica_iterations(max_ica_iterations); 
		
        if (!ica->run(series, *icatool)) {
            ica->set_approach(CIndepCompAnalysis::appr_symm);
            ica->run(series, *icatool);
		}
		
		rv_idx = ica->get_RV_idx(); 
		lv_idx = ica->get_LV_idx(); 
		perf_idx = ica->get_perfusion_idx(); 
		if (perf_idx >= 0) 
			components = test_components; 
		
	} while (!components && (rv_idx < 0 || lv_idx < 0 || perf_idx < 0) && test_components < 6); 

	auto conv2ubyte = produce_2dimage_filter("convert");
	
	if (!save_feature.empty()) {
		ica->save_coefs(save_feature + ".txt"); 
		ica->save_feature_images(save_feature); 
	}
	
	if (rv_idx < 0) 
		throw runtime_error("RV could not be identified"); 

	if (lv_idx < 0) 
		throw runtime_error("LV could not be identified"); 

	if (perf_idx < 0) 
		throw runtime_error("perfusion could not be identified"); 

	cvmsg() << "Using " << test_components << " independend components\n"; 
	

	// after running ICA get the related feature images 
	auto RV_feature = ica->get_feature_image(rv_idx); 
	auto LV_feature = ica->get_feature_image(lv_idx); 
	auto perf_feature = ica->get_feature_image(perf_idx); 
	auto mean_feature = ica->get_feature_image(-1); 


	// evaluate the RV and LV cavities 
	C2DImageFilterChain seed_chain({"kmeans:c=3", "binarize:min=2", "close:shape=[sphere:r=4]", 
				"label",  "selectbig" });


	auto LV_mask = evaluate_cavity(LV_feature, seed_chain); 
	save_image("LV_mask.@", LV_mask); 

	auto RV_mask = evaluate_cavity(RV_feature, seed_chain); 
	save_image("RV_mask.@", RV_mask); 

	
	
	// evaluate the bridge between RV and LV cavity and store it in the data pool 
	auto RV_LV_bridge_mask = evaluate_bridge_mask(RV_mask, LV_mask); 
	save_image("bridge.@", RV_LV_bridge_mask); 
	
	// evaluate the true myocardial seeds based on the bridge. 
	auto image_subtractor = C2DImageCombinerPluginHandler::instance().produce("sub"); 
	auto perf_feature_minus_cavities = image_subtractor->combine(*perf_feature, *RV_feature); 
	perf_feature_minus_cavities = image_subtractor->combine(*perf_feature_minus_cavities, *LV_feature); 
	auto myocard_seeds = run_filter_chain(perf_feature_minus_cavities, 
					      {	"mask:input=bridge.@,fill=min,inverse=0", 
							      "kmeans:c=5","tee:file=feature_bridge_kmeans.@",
							      "binarize:min=4,max=4", "close:shape=4n" });


	// prepare the seed image for a 4-class seeded watershed 
	C2DImageSeries myo_prep(3); 
	myo_prep[0] = myocard_seeds; 
	myo_prep[1] = LV_mask; 
	myo_prep[2] = RV_mask; 
	P2DImage seed = combine_with_boundary(myo_prep); 
	save_image("seed.@", seed); 

	// prepare the gradient images used for initial watershed segmentation 
	// of the myocardium and the RV-LV cavities. 
	auto image_adder = C2DImageCombinerPluginHandler::instance().produce("add"); 

	auto evalgrad = produce_2dimage_filter("gradnorm:normalize=1"); 
	auto perf_grad = evalgrad->filter(*perf_feature); 
	auto mean_grad = evalgrad->filter(*mean_feature); 
	auto perf_plus_mean_grad = image_adder->combine(*perf_grad, *mean_grad); 

	
	// prepare the filter chain for circle evaluation and test
	C2DImageFilterChain make_circle({"binarize:min=1,max=2", 
				"mask:input=LV_mask.@,inverse=1", 
				"thinning", "pruning", "thinning", "pruning"}); 
	CHasNonzeroPixels count_pixels; 

	// we will run the watershed first on the mean image, then if this fails, on the 
	// perfusion feature image, if if that also fails, we will try the 
	// gradient sum of the both. 
	vector<P2DImage> test_images = {mean_grad, perf_grad, perf_plus_mean_grad}; 
	bool circle_pixels = 0; 
	
	P2DImage ws_from_mean_grad_1; 
	P2DImage myo_binmask_circle; 
	

	for(auto ti = test_images.begin(); !circle_pixels && ti != test_images.end(); ++ti) {
		ws_from_mean_grad_1 = run_filter(**ti, "sws:seed=seed.@,grad=1");
		myo_binmask_circle = make_circle.run(ws_from_mean_grad_1); 
		circle_pixels = mia::filter(count_pixels, *myo_binmask_circle); 
	}

	if (!save_feature.empty()) {
		save_image(save_feature+"-0-RV.png", conv2ubyte->filter(*RV_feature)); 
		save_image(save_feature+"-0-LV.png", conv2ubyte->filter(*LV_feature)); 
		save_image(save_feature+"-0-perf.png", conv2ubyte->filter(*perf_feature)); 
		save_image(save_feature+"-0-mean.png", conv2ubyte->filter(*mean_feature)); 
		
		save_image(save_feature+"-1-RV_mask.png", RV_mask); 
		save_image(save_feature+"-1-LV_mask.png", LV_mask); 
		save_image(save_feature+"-1-RV-LV_bridge.png", RV_LV_bridge_mask); 
		save_image(save_feature+"-1-myocard_seed.png", myocard_seeds); 
		save_image(save_feature+"-1-ws-seed.png", conv2ubyte->filter(*seed)); 
		
		save_image(save_feature+"-1-ws.png", conv2ubyte->filter(*ws_from_mean_grad_1)); 
		save_image(save_feature+"-1-circle.png",  myo_binmask_circle); 
	}

	// we didn't get the circle, so bye bye 
	if (!circle_pixels)
		throw runtime_error("Basic estimation of the myocardial shape failed"); 

	//
	// end of 1st pass, we got the circular shape of the myocardium 
	// 

	//
	// 2nd pass 
	// 

	// 
	// first we improve the LV cavity segmentation by using the time-intensity correlation 
	
	// evaluate the average time-intensity curve for the given LV mask 
	auto LV_bit_mask = dynamic_cast<const C2DBitImage&>(*LV_mask); 
	
	// evaluate a correlation image representing the correlation of each pixel 
	// w.r.t. the average time-intensity curve above 
	CEvaluateSeriesCorrelationToMask correval_lv(LV_bit_mask, input_images.size() - skip_images); 
	C2DFImage LV_corr_image = correval_lv.get_correlation_image(input_images.begin() + skip_images, 
								    input_images.end()); 
	
	// 
	// now do a two-class watershed to improve the LV segmentation
	// use the original LV mask as LV seed, and the myocard-circle as outer seed
	// 
	C2DImageSeries LV_prep(2); 
	LV_prep[0] = LV_mask; 
	LV_prep[1] = myo_binmask_circle; 
	P2DImage LV_seed = combine_with_boundary(LV_prep); 
	
	save_image("lv_seed.@", LV_seed); 
	save_image("lv_mask.@", LV_mask); 
	auto ws_LV_cavity_from_corr = run_filter(LV_corr_image, "sws:seed=lv_seed.@,grad=0");

	// 
	// use the new seed for the LV and the circle for the 2nd pass watershed segmentation 
	// of the myocardium
	// 
	myo_prep[1] = LV_seed = run_filter(*ws_LV_cavity_from_corr, "binarize:min=1,max=1");
	save_image("lv_seed2.@", LV_seed); 
	myo_prep[0] = myo_binmask_circle; 

	seed = combine_with_boundary(myo_prep); 
	save_image("seed.@", seed); 	
	

	// run the watershed on the mean feature and on the perfusion feature 
	auto ws_from_mean_grad = run_filter(*mean_grad, "sws:seed=seed.@,grad=1");
	auto from_mean_binmask = run_filter(*ws_from_mean_grad, "binarize:min=1,max=1");
	auto ws_from_perf_grad = run_filter(*perf_grad, "sws:seed=seed.@,grad=1");

	
	// 
	// final step, clean out the fat at the outer border of the myocardium 
	// 
	// to do so, we use the RV peak image to identify the fatty tissue 
	// 
	auto rv_peak_index = ica->get_RV_peak_time(); 
	if (rv_peak_index < 0) 
		throw runtime_error("For some reason the RV peak index could not be identified"); 
	auto RV_peak_image = input_images[rv_peak_index]; 


	// now we try to get a good segmentation of the fat and subsequently of the 
	// myocardium 
	// we cluster the RV feature image by using a 10-class k-means, 
	// and then we create a mask by trying out various numbers of 
	// classes to be considered background. 

	auto outer_mask_kmeans = run_filter_chain(RV_peak_image, {"kmeans:c=10"});
	
	P2DImage final_myocard_mask; 
	P2DImage test_mask; 
	P2DImage outer_mask; 
	P2DImage from_perf_binmask; 
	int max_class = 2; 

	C2DImageFilterChain final_mask_chain({"binarize:min=1,max=2", 
				"open:shape=[sphere:r=5]", 
				"mask:input=outer_mask.@,inverse=0", "open", "label", "selectbig", 
				"close:shape=[sphere:r=3]"}); 

	do  {
		// first create the background mask by binarizing the k-means classification 
		stringstream mask_binarize; 
		mask_binarize <<  "binarize:min=0,max=" << max_class; 
		outer_mask = run_filter(*outer_mask_kmeans, mask_binarize.str().c_str()); 
		save_image("outer_mask.@", outer_mask); 
		
		// from the  watershed based on the perfusion we create a mask consisting on the 
		// LV and perfusion label, combine it with the above RV-peak enhancement background mask 
		// and clean this mask 
		from_perf_binmask = final_mask_chain.run(ws_from_perf_grad); 
		save_image("mask.@", from_mean_binmask); 	

		// evaluate the myocardium mask by combining the watershed segmentation obtained from 
		// the mean feature image with above mask
		// if the circle obtained from this operation is not empty 
		// the segmentation was successfull
		final_myocard_mask = run_filter(*from_perf_binmask, "mask:input=mask.@,inverse=0");
		test_mask = make_circle.run(final_myocard_mask); 
		max_class++; 	
	}while(!mia::filter(count_pixels, *test_mask) && max_class < 6); 

	

	if (!save_feature.empty()) {
		create_and_save_evaluation_shapes(save_feature, final_myocard_mask); 

		save_image(save_feature+"-2-LV-corr.png", conv2ubyte->filter(LV_corr_image)); 
		save_image(save_feature+"-2-LV_cavity_from_corr.png", LV_seed);
		save_image(save_feature+"-2-seed.png", conv2ubyte->filter(*seed));
		save_image(save_feature+"-2-ws_from_mean_grad.png",  conv2ubyte->filter(*ws_from_mean_grad)); 
		save_image(save_feature+"-2-myocard-from_mean_binmask.png", from_mean_binmask); 
		save_image(save_feature+"-2-ws_from_perf_grad.png", conv2ubyte->filter(*ws_from_perf_grad));
		
		
		save_image(save_feature+"-2-RV_peak_image.png", conv2ubyte->filter(*RV_peak_image)); 
		save_image(save_feature+"-2-RV_peak_kmeans.png", conv2ubyte->filter(*outer_mask_kmeans)); 
		save_image(save_feature+"-2a-outer_mask.png", outer_mask); 
		save_image(save_feature+"-2a-from_perf_binmask.png",  from_perf_binmask); 
		save_image(save_feature+"-2a-myomask-using-kmeans.png",  final_myocard_mask); 

		auto lv_peak_index = ica->get_LV_peak_time(); 
		if (lv_peak_index >= 0) 
			save_image(save_feature+"-2-LV-peak.png", run_filter(*input_images[lv_peak_index], "convert:map=linear,b=20"));
	}

	return save_image(out_filename, final_myocard_mask) ?  EXIT_SUCCESS : EXIT_FAILURE; 
}

#include <mia/internal/main.hh>
MIA_MAIN(do_main); 

