/* -*- 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/3d/fullcost/taggedssd.hh>
#include <mia/3d/transformmock.hh>
#include <mia/3d/imageio.hh>
#include <mia/3d/filter.hh>

#include <mia/internal/autotest.hh>

NS_MIA_USE;
NS_USE(taggedssd_3d);

BOOST_AUTO_TEST_CASE( test_taggedssd)
{
       // create two images
       const unsigned char src_data_x[64] = {
              0, 0, 0, 0,   0, 0, 0,  0,   0,  0,  0,  0,   0, 0, 0, 0,
              0, 0, 0, 0,   0, 0, 0,  0,   0,  0,  0,  0,   0, 0, 0, 0,
              0, 0, 0, 0,   0, 255, 255, 0,   0, 255, 255,  0,   0, 0, 0, 0,
              0, 0, 0, 0,   0, 255, 0, 0,   0,  0,  0,  0,   0, 0, 0, 0

       };
       const unsigned char ref_data_x[64] = {
              0, 0, 0, 0,   0, 0, 0, 0,    0, 0, 0, 0,      0, 0, 0, 0,
              0, 0, 0, 0,   0, 0, 0, 0,    0, 0, 0, 0,      0, 0, 0, 0,
              0, 0, 0, 0,   0, 0, 0, 0,    0, 0, 0, 0,      0, 0, 0, 0,
              0, 0, 0, 0,   0, 0, 0, 0,    0, 0, 0, 0,      0, 0, 0, 0

       };
       C3DBounds size(4, 4, 4);
       P3DImage src(new C3DUBImage(size, src_data_x ));
       P3DImage ref(new C3DUBImage(size, ref_data_x ));
       BOOST_REQUIRE(save_image("src.@", src));
       BOOST_REQUIRE(save_image("ref.@", ref));
       C3DTaggedSSDCost cost("src.@", "ref.@",  "src.@",  "ref.@",  "src.@",  "ref.@",  1.0);
       cost.reinit();
       cost.set_size(size);
       C3DTransformMock t(size, C3DInterpolatorFactory("bspline:d=3", "mirror"));
       CDoubleVector gradient(t.degrees_of_freedom());
       double cost_value = cost.evaluate(t, gradient);
       BOOST_CHECK_EQUAL(gradient.size(), 3u * 64u);
       const double v = 255.0 * 0.0202146;
       double test_cost = (0.5 * v * v  * 5.0) / 64.0;
       BOOST_CHECK_CLOSE(cost_value, test_cost, 0.1);
       double value = cost.cost_value(t);
       BOOST_CHECK_CLOSE(value, test_cost, 0.1);
       BOOST_CHECK_CLOSE(gradient[111], v * v * 0.5f / 64, 0.1);
       BOOST_CHECK_CLOSE(gradient[112], v * v * 0.5f / 64, 0.1);
       BOOST_CHECK_CLOSE(gradient[113], v * v * 0.5f / 64, 0.1);
}


BOOST_AUTO_TEST_CASE( test_taggedssd_separate )
{
       // create two images
       const unsigned char src_data_x[64] = {
              1, 3, 1, 1,   1, 1, 5, 1,   1, 1, 1, 5,   7, 1, 1, 1,
              1, 5, 1, 1,   1, 1, 7, 1,   1, 1, 1, 3,   9, 1, 1, 1,
              1, 7, 1, 1,   1, 1, 9, 1,   1, 1, 1, 9,   3, 1, 1, 1,
              1, 9, 1, 1,   1, 1, 3, 1,   1, 1, 1, 7,   5, 1, 1, 1

       };
       const unsigned char ref_data_x[64] = {
              0, 1, 0, 0,   0, 0, 1, 0,   0, 0, 0, 1,   1, 0, 0, 0,
              0, 1, 0, 0,   0, 0, 1, 0,   0, 0, 0, 1,   1, 0, 0, 0,
              0, 1, 0, 0,   0, 0, 1, 0,   0, 0, 0, 1,   1, 0, 0, 0,
              0, 1, 0, 0,   0, 0, 1, 0,   0, 0, 0, 1,   1, 0, 0, 0

       };
       const float grad_x[64] = {
              0, 0, -1, 0,   0, 2, 0, 0,   0, 0, 2, 0,  0, -3, 0, 0,
              0, 0, -2, 0,   0, 3, 0, 0,   0, 0, 1, 0,  0, -4, 0, 0,
              0, 0, -3, 0,   0, 4, 0, 0,   0, 0, 4, 0,  0, -1, 0, 0,
              0, 0, -4, 0,   0, 1, 0, 0,   0, 0, 3, 0,  0, -2, 0, 0

       };
       const unsigned char src_data_z[64] = {
              2, 4, 6, 8,   0, 0, 0, 0,    0, 0, 0, 0,   0, 0, 0, 0,
              0, 0, 0, 0,   6, 8, 4, 2,    0, 0, 0, 0,   0, 0, 0, 0,
              0, 0, 0, 0,   0, 0, 0, 0,    4, 2, 6, 8,   0, 0, 0, 0,
              0, 0, 0, 0,   0, 0, 0, 0,    0, 0, 0, 0,   8, 4, 2, 6

       };
       const unsigned char ref_data_z[64] = {
              1, 1, 1, 1,   1, 1, 1, 1,    1, 1, 1, 1,   1, 1, 1, 1,
              1, 1, 1, 1,   1, 1, 1, 1,    1, 1, 1, 1,   1, 1, 1, 1,
              1, 1, 1, 1,   1, 1, 1, 1,    1, 1, 1, 1,   1, 1, 1, 1,
              1, 1, 1, 1,   1, 1, 1, 1,    1, 1, 1, 1,   1, 1, 1, 1

       };
       const float grad_z[64] = {
              0, 0, 0, 0,   0, 0, 0, 0,    0, 0, 0, 0,   0, 0, 0, 0,
              1, 2, 3, 4,   0, 0, 0, 0,   -2, -1, -3, -4,   0, 0, 0, 0,
              0, 0, 0, 0,   3, 4, 2, 1,    0, 0, 0, 0,  -4, -2, -1, -3,
              0, 0, 0, 0,   0, 0, 0, 0,    0, 0, 0, 0,   0, 0, 0, 0
       };
       const unsigned char src_data_y[64] = {
              0, 2, 0, 0,   0, 0, 2, 0,   0,  0,  0,  4,   8, 0, 0, 0,
              0, 4, 0, 0,   0, 0, 4, 0,   0,  0,  0,  6,   6, 0, 0, 0,
              0, 6, 0, 0,   0, 0, 6, 0,   0,  0,  0,  8,   4, 0, 0, 0,
              0, 8, 0, 0,   0, 0, 8, 0,   0,  0,  0,  2,   2, 0, 0, 0

       };
       const unsigned char ref_data_y[64] = {
              1, 1, 1, 1,   1, 1, 1, 1,    1, 1, 1, 1,   1, 1, 1, 1,
              1, 1, 1, 1,   1, 1, 1, 1,    1, 1, 1, 1,   1, 1, 1, 1,
              1, 1, 1, 1,   1, 1, 1, 1,    1, 1, 1, 1,   1, 1, 1, 1,
              1, 1, 1, 1,   1, 1, 1, 1,    1, 1, 1, 1,   1, 1, 1, 1
       };
       const float grad_y[64] = {
              0, 0, 0, 0,   0, 1, 0, -2,  -4,  0,  1,  0,   0, 0, 0, 0,
              0, 0, 0, 0,   0, 2, 0, -3,  -3,  0,  2,  0,   0, 0, 0, 0,
              0, 0, 0, 0,   0, 3, 0, -4,  -2,  0,  3,  0,   0, 0, 0, 0,
              0, 0, 0, 0,   0, 4, 0, -1,  -1,  0,  4,  0,   0, 0, 0, 0
       };
       C3DBounds size(4, 4, 4);
       P3DImage srcx(new C3DUBImage(size, src_data_x ));
       P3DImage refx(new C3DUBImage(size, ref_data_x ));
       P3DImage srcy(new C3DUBImage(size, src_data_y ));
       P3DImage refy(new C3DUBImage(size, ref_data_y ));
       P3DImage srcz(new C3DUBImage(size, src_data_z ));
       P3DImage refz(new C3DUBImage(size, ref_data_z ));
       BOOST_REQUIRE(save_image("srcx.@", srcx));
       BOOST_REQUIRE(save_image("refx.@", refx));
       BOOST_REQUIRE(save_image("srcy.@", srcy));
       BOOST_REQUIRE(save_image("refy.@", refy));
       BOOST_REQUIRE(save_image("refz.@", refz));
       BOOST_REQUIRE(save_image("srcz.@", srcz));
       C3DTaggedSSDCost cost("srcx.@", "refx.@",  "srcy.@",  "refy.@",  "srcz.@",  "refz.@",  1.0);
       cost.reinit();
       cost.set_size(size);
       C3DTransformMock t(size, C3DInterpolatorFactory("bspline:d=3", "mirror"));
       CDoubleVector gradient(t.degrees_of_freedom());
       double cost_value = cost.evaluate(t, gradient);
       BOOST_CHECK_EQUAL(gradient.size(), 3u * 64u);
       const double test_cost = (3 * 48 + 8 * ( 1 + 9 + 25 + 49 ) +
                                 4 * ( 4 + 16 + 36 + 64)) / 6.0 / 64.0 * 0.545873 * 0.545873;
       BOOST_CHECK_CLOSE(cost_value, test_cost, 0.1);
       double value = cost.cost_value(t);
       BOOST_CHECK_CLOSE(value, test_cost, 0.1);
       double scale = 0.545873 * 0.545873 / 64.0;

       for (int i = 0; i < 64; ++i) {
              BOOST_CHECK_CLOSE(gradient[3 * i  ], grad_x[i] * scale, 0.1);
              BOOST_CHECK_CLOSE(gradient[3 * i + 1], grad_y[i] * scale, 0.1);
              BOOST_CHECK_CLOSE(gradient[3 * i + 2], grad_z[i] * scale, 0.1);
       }
}
