// The contents of this file are in the public domain. See LICENSE_FOR_EXAMPLE_PROGRAMS.txt
/*
    This example shows how to train a CNN based object detector using dlib's
    loss_mmod loss layer.  This loss layer implements the Max-Margin Object
    Detection loss as described in the paper:
        Max-Margin Object Detection by Davis E. King (http://arxiv.org/abs/1502.00046).
    This is the same loss used by the popular SVM+HOG object detector in dlib
    (see fhog_object_detector_ex.cpp) except here we replace the HOG features
    with a CNN and train the entire detector end-to-end.  This allows us to make
    much more powerful detectors.

    It would be a good idea to become familiar with dlib's DNN tooling before reading this
    example.  So you should read dnn_introduction_ex.cpp and dnn_introduction2_ex.cpp
    before reading this example program.  You should also read the introductory DNN+MMOD
    example dnn_mmod_ex.cpp as well before proceeding.

    This example is essentially a more complex version of dnn_mmod_ex.cpp.  In it we train
    a detector that finds the rear ends of motor vehicles.  I will also discuss some
    aspects of data preparation useful when training this kind of detector.

*/

#include <iostream>
#include <dlib/dnn.h>
#include <dlib/data_io.h>

using namespace std;
using namespace dlib;

template <long num_filters, typename SUBNET> using con5d = con<num_filters,5,5,2,2,SUBNET>;
template <long num_filters, typename SUBNET> using con5  = con<num_filters,5,5,1,1,SUBNET>;
template <typename SUBNET> using downsampler  = relu<bn_con<con5d<32, relu<bn_con<con5d<32, relu<bn_con<con5d<16,SUBNET>>>>>>>>>;
template <typename SUBNET> using rcon5  = relu<bn_con<con5<55,SUBNET>>>;
using net_type = loss_mmod<con<1,9,9,1,1,rcon5<rcon5<rcon5<downsampler<input_rgb_image_pyramid<pyramid_down<6>>>>>>>>;

// ----------------------------------------------------------------------------------------

int ignore_overlapped_boxes(
    std::vector<mmod_rect>& boxes,
    const test_box_overlap& overlaps
)
/*!
    ensures
        - Whenever two rectangles in boxes overlap, according to overlaps(), we set the
          smallest box to ignore.
        - returns the number of newly ignored boxes.
!*/
{
    int num_ignored = 0;
    for (size_t i = 0; i < boxes.size(); ++i)
    {
        if (boxes[i].ignore)
            continue;
        for (size_t j = i+1; j < boxes.size(); ++j)
        {
            if (boxes[j].ignore)
                continue;
            if (overlaps(boxes[i], boxes[j]))
            {
                ++num_ignored;
                if(boxes[i].rect.area() < boxes[j].rect.area())
                    boxes[i].ignore = true;
                else
                    boxes[j].ignore = true;
            }
        }
    }
    return num_ignored;
}

// ----------------------------------------------------------------------------------------

int main(int argc, char** argv) try
{
    if (argc != 2)
    {
        cout << "Give the path to a folder containing training.xml and testing.xml files." << endl;
        cout << "This example program is specifically designed to run on the dlib vehicle " << endl;
        cout << "detection dataset, which is available at this URL: " << endl;
        cout << "   http://dlib.net/files/data/dlib_rear_end_vehicles_v1.tar" << endl;
        cout << endl;
        cout << "So download that dataset, extract it somewhere, and then run this program" << endl;
        cout << "with the dlib_rear_end_vehicles folder as an argument.  E.g. if you extract" << endl;
        cout << "the dataset to the current folder then you should run this example program" << endl;
        cout << "by typing: " << endl;
        cout << "   ./dnn_mmod_train_find_cars_ex dlib_rear_end_vehicles" << endl;
        cout << endl;
        cout << "It takes about a day to finish if run on a high end GPU like a 1080ti." << endl;
        cout << endl;
        return 0;
    }
    const std::string data_directory = argv[1];

    std::vector<matrix<rgb_pixel>> images_train, images_test;
    std::vector<std::vector<mmod_rect>> boxes_train, boxes_test;
    load_image_dataset(images_train, boxes_train, data_directory+"/training.xml");
    load_image_dataset(images_test,  boxes_test,  data_directory+"/testing.xml");

    // When I was creating the dlib vehicle detection dataset I had to label all the cars
    // in each image.  MMOD requires all cars to be labeled, since any unlabeled part of an
    // image is implicitly assumed to be not a car, and the algorithm will use it as
    // negative training data.  So every car must be labeled, either with a normal
    // rectangle or an "ignore" rectangle that tells MMOD to simply ignore it (i.e. neither
    // treat it as a thing to detect nor as negative training data).
    //
    // In our present case, many images contain very tiny cars in the distance, ones that
    // are essentially just dark smudges.  It's not reasonable to expect the CNN
    // architecture we defined to detect such vehicles.  However, I erred on the side of
    // having more complete annotations when creating the dataset.  So when I labeled these
    // images I labeled many of these really difficult cases as vehicles to detect.
    //
    // So the first thing we are going to do is clean up our dataset a little bit.  In
    // particular, we are going to mark boxes smaller than 35*35 pixels as ignore since
    // only really small and blurry cars appear at those sizes.  We will also mark boxes
    // that are heavily overlapped by another box as ignore.  We do this because we want to
    // allow for stronger non-maximum suppression logic in the learned detector, since that
    // will help make it easier to learn a good detector.
    //
    // To explain this non-max suppression idea further it's important to understand how
    // the detector works.  Essentially, sliding window detectors scan all image locations
    // and ask "is there a care here?".  If there really is a car in a specific location in
    // an image then usually many slightly different sliding window locations will produce
    // high detection scores, indicating that there is a car at those locations.  If we
    // just stopped there then each car would produce multiple detections.  But that isn't
    // what we want.  We want each car to produce just one detection.  So it's common for
    // detectors to include "non-maximum suppression" logic which simply takes the
    // strongest detection and then deletes all detections "close to" the strongest.  This
    // is a simple post-processing step that can eliminate duplicate detections.  However,
    // we have to define what "close to" means.  We can do this by looking at your training
    // data and checking how close the closest target boxes are to each other, and then
    // picking a "close to" measure that doesn't suppress those target boxes but is
    // otherwise as tight as possible.  This is exactly what the mmod_options object does
    // by default.
    //
    // Importantly, this means that if your training dataset contains an image with two
    // target boxes that really overlap a whole lot, then the non-maximum suppression
    // "close to" measure will be configured to allow detections to really overlap a whole
    // lot.  On the other hand, if your dataset didn't contain any overlapped boxes at all,
    // then the non-max suppression logic would be configured to filter out any boxes that
    // overlapped at all, and thus would be performing a much stronger non-max suppression.
    //
    // Why does this matter?  Well, remember that we want to avoid duplicate detections.
    // If non-max suppression just kills everything in a really wide area around a car then
    // the CNN doesn't really need to learn anything about avoiding duplicate detections.
    // However, if non-max suppression only suppresses a tiny area around each detection
    // then the CNN will need to learn to output small detection scores for those areas of
    // the image not suppressed.  The smaller the non-max suppression region the more the
    // CNN has to learn and the more difficult the learning problem will become.  This is
    // why we remove highly overlapped objects from the training dataset.  That is, we do
    // it so the non-max suppression logic will be able to be reasonably effective.  Here
    // we are ensuring that any boxes that are entirely contained by another are
    // suppressed.  We also ensure that boxes with an intersection over union of 0.5 or
    // greater are suppressed.  This will improve the resulting detector since it will be
    // able to use more aggressive non-max suppression settings.

    int num_overlapped_ignored_test = 0;
    for (auto& v : boxes_test)
        num_overlapped_ignored_test += ignore_overlapped_boxes(v, test_box_overlap(0.50, 0.95));

    int num_overlapped_ignored = 0;
    int num_additional_ignored = 0;
    for (auto& v : boxes_train)
    {
        num_overlapped_ignored += ignore_overlapped_boxes(v, test_box_overlap(0.50, 0.95));
        for (auto& bb : v)
        {
            if (bb.rect.width() < 35 && bb.rect.height() < 35)
            {
                if (!bb.ignore)
                {
                    bb.ignore = true;
                    ++num_additional_ignored;
                }
            }

            // The dlib vehicle detection dataset doesn't contain any detections with
            // really extreme aspect ratios.  However, some datasets do, often because of
            // bad labeling.  So it's a good idea to check for that and either eliminate
            // those boxes or set them to ignore.  Although, this depends on your
            // application.
            //
            // For instance, if your dataset has boxes with an aspect ratio
            // of 10 then you should think about what that means for the network
            // architecture.  Does the receptive field even cover the entirety of the box
            // in those cases?  Do you care about these boxes?  Are they labeling errors?
            // I find that many people will download some dataset from the internet and
            // just take it as given.  They run it through some training algorithm and take
            // the dataset as unchallengeable truth.  But many datasets are full of
            // labeling errors.  There are also a lot of datasets that aren't full of
            // errors, but are annotated in a sloppy and inconsistent way.  Fixing those
            // errors and inconsistencies can often greatly improve models trained from
            // such data.  It's almost always worth the time to try and improve your
            // training dataset.
            //
            // In any case, my point is that there are other types of dataset cleaning you
            // could put here.  What exactly you need depends on your application.  But you
            // should carefully consider it and not take your dataset as a given.  The work
            // of creating a good detector is largely about creating a high quality
            // training dataset.
        }
    }

    // When modifying a dataset like this, it's a really good idea to print a log of how
    // many boxes you ignored.  It's easy to accidentally ignore a huge block of data, so
    // you should always look and see that things are doing what you expect.
    cout << "num_overlapped_ignored: "<< num_overlapped_ignored << endl;
    cout << "num_additional_ignored: "<< num_additional_ignored << endl;
    cout << "num_overlapped_ignored_test: "<< num_overlapped_ignored_test << endl;

    cout << "num training images: " << images_train.size() << endl;
    cout << "num testing images: " << images_test.size() << endl;

    // Our vehicle detection dataset has basically 3 different types of boxes.  Square
    // boxes, tall and skinny boxes (e.g. semi trucks), and short and wide boxes (e.g.
    // sedans).  Here we are telling the MMOD algorithm that a vehicle is recognizable as
    // long as the longest box side is at least 70 pixels long and the shortest box side is
    // at least 30 pixels long.  mmod_options will use these parameters to decide how large
    // each of the sliding windows needs to be so as to be able to detect all the vehicles.
    // Since our dataset has basically these 3 different aspect ratios, it will decide to
    // use 3 different sliding windows.  This means the final con layer in the network will
    // have 3 filters, one for each of these aspect ratios.
    //
    // Another thing to consider when setting the sliding window size is the "stride" of
    // your network.  The network we defined above downsamples the image by a factor of 8x
    // in the first few layers.  So when the sliding windows are scanning the image, they
    // are stepping over it with a stride of 8 pixels.  If you set the sliding window size
    // too small then the stride will become an issue.  For instance, if you set the
    // sliding window size to 4 pixels, then it means a 4x4 window will be moved by 8
    // pixels at a time when scanning. This is obviously a problem since 75% of the image
    // won't even be visited by the sliding window.  So you need to set the window size to
    // be big enough relative to the stride of your network.  In our case, the windows are
    // at least 30 pixels in length, so being moved by 8 pixel steps is fine.
    mmod_options options(boxes_train, 70, 30);

    // This setting is very important and dataset specific.  The vehicle detection dataset
    // contains boxes that are marked as "ignore", as we discussed above.  Some of them are
    // ignored because we set ignore to true in the above code.  However, the xml files
    // also contained a lot of ignore boxes.  Some of them are large boxes that encompass
    // large parts of an image and the intention is to have everything inside those boxes
    // be ignored.  Therefore, we need to tell the MMOD algorithm to do that, which we do
    // by setting options.overlaps_ignore appropriately.
    //
    // But first, we need to understand exactly what this option does.  The MMOD loss
    // is essentially counting the number of false alarms + missed detections produced by
    // the detector for each image.  During training, the code is running the detector on
    // each image in a mini-batch and looking at its output and counting the number of
    // mistakes.  The optimizer tries to find parameters settings that minimize the number
    // of detector mistakes.
    //
    // This overlaps_ignore option allows you to tell the loss that some outputs from the
    // detector should be totally ignored, as if they never happened.  In particular, if a
    // detection overlaps a box in the training data with ignore==true then that detection
    // is ignored.  This overlap is determined by calling
    // options.overlaps_ignore(the_detection, the_ignored_training_box).  If it returns
    // true then that detection is ignored.
    //
    // You should read the documentation for test_box_overlap, the class type for
    // overlaps_ignore for full details.  However, the gist is that the default behavior is
    // to only consider boxes as overlapping if their intersection over union is > 0.5.
    // However, the dlib vehicle detection dataset contains large boxes that are meant to
    // mask out large areas of an image.  So intersection over union isn't an appropriate
    // way to measure "overlaps with box" in this case.  We want any box that is contained
    // inside one of these big regions to be ignored, even if the detection box is really
    // small.  So we set overlaps_ignore to behave that way with this line.
    options.overlaps_ignore = test_box_overlap(0.5, 0.95);

    net_type net(options);

    // The final layer of the network must be a con layer that contains
    // options.detector_windows.size() filters.  This is because these final filters are
    // what perform the final "sliding window" detection in the network.  For the dlib
    // vehicle dataset, there will be 3 sliding window detectors, so we will be setting
    // num_filters to 3 here.
    net.subnet().layer_details().set_num_filters(options.detector_windows.size());

    dnn_trainer<net_type> trainer(net,sgd(0.0001,0.9));
    trainer.set_learning_rate(0.1);
    trainer.be_verbose();

    // While training, we are going to use early stopping.  That is, we will be checking
    // how good the detector is performing on our test data and when it stops getting
    // better on the test data we will drop the learning rate.  We will keep doing that
    // until the learning rate is less than 1e-4.   These two settings tell the trainer to
    // do that.  Essentially, we are setting the first argument to infinity, and only the
    // test iterations without progress threshold will matter.  In particular, it says that
    // once we observe 1000 testing mini-batches where the test loss clearly isn't
    // decreasing we will lower the learning rate.
    trainer.set_iterations_without_progress_threshold(50000);
    trainer.set_test_iterations_without_progress_threshold(1000);

    const string sync_filename = "mmod_cars_sync";
    trainer.set_synchronization_file(sync_filename, std::chrono::minutes(5));

    std::vector<matrix<rgb_pixel>> mini_batch_samples;
    std::vector<std::vector<mmod_rect>> mini_batch_labels;
    random_cropper cropper;
    cropper.set_seed(time(0));
    cropper.set_chip_dims(350, 350);
    // Usually you want to give the cropper whatever min sizes you passed to the
    // mmod_options constructor, or very slightly smaller sizes, which is what we do here.
    cropper.set_min_object_size(69,28);
    cropper.set_max_rotation_degrees(2);
    dlib::rand rnd;

    // Log the training parameters to the console
    cout << trainer << cropper << endl;

    int cnt = 1;
    // Run the trainer until the learning rate gets small.
    while(trainer.get_learning_rate() >= 1e-4)
    {
        // Every 30 mini-batches we do a testing mini-batch.
        if (cnt%30 != 0 || images_test.size() == 0)
        {
            cropper(87, images_train, boxes_train, mini_batch_samples, mini_batch_labels);
            // We can also randomly jitter the colors and that often helps a detector
            // generalize better to new images.
            for (auto&& img : mini_batch_samples)
                disturb_colors(img, rnd);

            // It's a good idea to, at least once, put code here that displays the images
            // and boxes the random cropper is generating.  You should look at them and
            // think about if the output makes sense for your problem.  Most of the time
            // it will be fine, but sometimes you will realize that the pattern of cropping
            // isn't really appropriate for your problem and you will need to make some
            // change to how the mini-batches are being generated.  Maybe you will tweak
            // some of the cropper's settings, or write your own entirely separate code to
            // create mini-batches.  But either way, if you don't look you will never know.
            // An easy way to do this is to create a dlib::image_window to display the
            // images and boxes.

            trainer.train_one_step(mini_batch_samples, mini_batch_labels);
        }
        else
        {
            cropper(87, images_test, boxes_test, mini_batch_samples, mini_batch_labels);
            // We can also randomly jitter the colors and that often helps a detector
            // generalize better to new images.
            for (auto&& img : mini_batch_samples)
                disturb_colors(img, rnd);

            trainer.test_one_step(mini_batch_samples, mini_batch_labels);
        }
        ++cnt;
    }
    // wait for training threads to stop
    trainer.get_net();
    cout << "done training" << endl;

    // Save the network to disk
    net.clean();
    serialize("mmod_rear_end_vehicle_detector.dat") << net;

    // It's a really good idea to print the training parameters.  This is because you will
    // invariably be running multiple rounds of training and should be logging the output
    // to a file.  This print statement will include many of the training parameters in
    // your log.
    cout << trainer << cropper << endl;

    cout << "\nsync_filename: " << sync_filename << endl;
    cout << "num training images: "<< images_train.size() << endl;
    cout << "training results: " << test_object_detection_function(net, images_train, boxes_train, test_box_overlap(), 0, options.overlaps_ignore);
    // Upsampling the data will allow the detector to find smaller cars.  Recall that
    // we configured it to use a sliding window nominally 70 pixels in size.  So upsampling
    // here will let it find things nominally 35 pixels in size.  Although we include a
    // limit of 1800*1800 here which means "don't upsample an image if it's already larger
    // than 1800*1800".  We do this so we don't run out of RAM, which is a concern because
    // some of the images in the dlib vehicle dataset are really high resolution.
    upsample_image_dataset<pyramid_down<2>>(images_train, boxes_train, 1800*1800);
    cout << "training upsampled results: " << test_object_detection_function(net, images_train, boxes_train, test_box_overlap(), 0, options.overlaps_ignore);

    cout << "num testing images: "<< images_test.size() << endl;
    cout << "testing results: " << test_object_detection_function(net, images_test, boxes_test, test_box_overlap(), 0, options.overlaps_ignore);
    upsample_image_dataset<pyramid_down<2>>(images_test, boxes_test, 1800*1800);
    cout << "testing upsampled results: " << test_object_detection_function(net, images_test, boxes_test, test_box_overlap(), 0, options.overlaps_ignore);

    /*
        This program takes many hours to execute on a high end GPU.  It took about a day to
        train on a NVIDIA 1080ti.  The resulting model file is available at
            http://dlib.net/files/mmod_rear_end_vehicle_detector.dat.bz2
        It should be noted that this file on dlib.net has a dlib::shape_predictor appended
        onto the end of it (see dnn_mmod_find_cars_ex.cpp for an example of its use).  This
        explains why the model file on dlib.net is larger than the
        mmod_rear_end_vehicle_detector.dat output by this program.

        You can see some videos of this vehicle detector running on YouTube:
            https://www.youtube.com/watch?v=4B3bzmxMAZU
            https://www.youtube.com/watch?v=bP2SUo5vSlc

        Also, the training and testing accuracies were:
            num training images: 2217
            training results: 0.990738 0.736431 0.736073
            training upsampled results: 0.986837 0.937694 0.936912
            num testing images: 135
            testing results: 0.988827 0.471372 0.470806
            testing upsampled results: 0.987879 0.651132 0.650399
    */

    return 0;

}
catch(std::exception& e)
{
    cout << e.what() << endl;
}


//
// Copyright (c) 2009 Rutger ter Borg
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//

#ifndef BOOST_NUMERIC_BINDINGS_DETAIL_POD_HPP
#define BOOST_NUMERIC_BINDINGS_DETAIL_POD_HPP

#include <boost/numeric/bindings/is_numeric.hpp>
#include <boost/numeric/bindings/detail/adaptor.hpp>

namespace boost {
namespace numeric {
namespace bindings {
namespace detail {

template< typename T, typename Id >
struct adaptor< T, Id, typename boost::enable_if< is_numeric<T> >::type > {

    typedef typename copy_const< Id, T >::type value_type;
    typedef mpl::map<
        mpl::pair< tag::value_type, value_type >,
        mpl::pair< tag::entity, tag::scalar >,
        mpl::pair< tag::size_type<1>, mpl::int_<1> >,
        mpl::pair< tag::data_structure, tag::linear_array >
    > property_map;

    static value_type* begin_value( Id& t ) {
        return &t;
    }

};

template< typename T, std::size_t N, typename Id >
struct adaptor< T[N], Id, typename boost::enable_if< is_numeric<T> >::type > {

    typedef typename copy_const< Id, T >::type value_type;
    typedef mpl::map<
        mpl::pair< tag::value_type, value_type >,
        mpl::pair< tag::entity, tag::vector >,
        mpl::pair< tag::size_type<1>, mpl::int_<N> >,
        mpl::pair< tag::data_structure, tag::linear_array >,
        mpl::pair< tag::stride_type<1>, tag::contiguous >
    > property_map;

    static value_type* begin_value( Id& t ) {
        return &t[0];
    }

    static value_type* end_value( Id& t ) {
        return &t[N];
    }

};

template< typename T, std::size_t M, std::size_t N, typename Id >
struct adaptor< T[M][N], Id, typename boost::enable_if< is_numeric<T> >::type > {

    typedef typename copy_const< Id, T >::type value_type;
    typedef mpl::map<
        mpl::pair< tag::value_type, value_type >,
        mpl::pair< tag::entity, tag::matrix >,
        mpl::pair< tag::size_type<1>, mpl::int_<M> >,
        mpl::pair< tag::size_type<2>, mpl::int_<N> >,
        mpl::pair< tag::matrix_type, tag::general >,
        mpl::pair< tag::data_structure, tag::linear_array >,
        mpl::pair< tag::data_order, tag::row_major >,
        mpl::pair< tag::stride_type<1>, mpl::int_<N> >,
        mpl::pair< tag::stride_type<2>, tag::contiguous >
    > property_map;

    static value_type* begin_value( Id& t ) {
        return &t[0][0];
    }

    static value_type* end_value( Id& t ) {
        return &t[M][N];
    }

};

} // namespace detail
} // namespace bindings
} // namespace numeric
} // namespace boost

#endif


//          Copyright Carl Philipp Reh 2009 - 2021.
// Distributed under the Boost Software License, Version 1.0.
//    (See accompanying file LICENSE_1_0.txt or copy at
//          http://www.boost.org/LICENSE_1_0.txt)

#ifndef FCPPT_MAKE_LITERAL_BOOST_UNITS_HPP_INCLUDED
#define FCPPT_MAKE_LITERAL_BOOST_UNITS_HPP_INCLUDED

#include <fcppt/check_literal_conversion.hpp>
#include <fcppt/make_literal_fwd.hpp>
#include <fcppt/config/external_begin.hpp>
#include <boost/units/dimensionless_type.hpp>
#include <boost/units/quantity.hpp>
#include <boost/units/unit.hpp>
#include <fcppt/config/external_end.hpp>

namespace fcppt
{
/**
\brief Literal specialization for boost.units

\ingroup fcpptboost
*/
template <typename Unit, typename Type>
struct make_literal<boost::units::quantity<Unit, Type>>
{
  using decorated_type = boost::units::quantity<
      boost::units::unit<boost::units::dimensionless_type, typename Unit::system_type>,
      Type>;

  template <typename Fundamental>
  static decorated_type get(Fundamental const _fundamental)
  {
    FCPPT_CHECK_LITERAL_CONVERSION(Type, Fundamental);

    return decorated_type::from_value(static_cast<Type>(_fundamental));
  }
};

}

#endif


#include <boost/graph/vector_as_graph.hpp>


// Bring in my package's API, which is what I'm testing

#include <eigen3/Eigen/Core>
// Bring in gtest
#include <gtest/gtest.h>
#include <boost/cstdint.hpp>

// Serialization
#include <fstream>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/archive/binary_iarchive.hpp>
#include <boost/archive/xml_oarchive.hpp>
#include <boost/archive/xml_iarchive.hpp>
#include <boost/filesystem.hpp>

#include <eigen3/Eigen/Geometry>
#include <sm/eigen/serialization.hpp>

template<typename T, int A, int B, int C, int D>
void saveLoadMatrixTest()
{
  Eigen::Matrix<T,A,B> T1, T2;

  T1.resize(C,D);

  for(int r = 0; r < T1.rows(); r++)
    {
      for(int c = 0; c < T1.cols(); c++)
	{
	  T1(r,c) = (T)(((r-1)*T1.cols()) + c);
	}
    }

  {
    std::ofstream ofs("test.ba", std::ios::binary);
    boost::archive::binary_oarchive oa(ofs);

    oa << T1;

  }

  {
    std::ifstream ifs("test.ba", std::ios::binary);
    boost::archive::binary_iarchive ia(ifs);

    ia >> T2;
  }

  for(int r = 0; r < T1.rows(); r++)
    {
      for(int c = 0; c < T1.cols(); c++)
	{
	  ASSERT_EQ(T1(r,c),T2(r,c)) << "Value at row " << r << " and col " << c << " is not exactly equal";
	}
    }

}

template<typename T, int A, int B, int C, int D>
void saveLoadXmlMatrixTest()
{
  Eigen::Matrix<T,A,B> T1, T2;

  T1.resize(C,D);

  for(int r = 0; r < T1.rows(); r++)
    {
      for(int c = 0; c < T1.cols(); c++)
	{
	  T1(r,c) = (T)(((r-1)*T1.cols()) + c);
	}
    }

  {
    std::ofstream ofs("test.xml", std::ios::binary);
    boost::archive::xml_oarchive oa(ofs);

    oa << ::boost::serialization::make_nvp("top",T1);

  }

  {
    std::ifstream ifs("test.xml", std::ios::binary);
    boost::archive::xml_iarchive ia(ifs);

    ia >> ::boost::serialization::make_nvp("top",T2);
  }

  ASSERT_EQ(T1.rows(), T2.rows());
  ASSERT_EQ(T1.cols(), T2.cols());

  for(int r = 0; r < T1.rows(); r++)
    {
      for(int c = 0; c < T1.cols(); c++)
	{
	  ASSERT_EQ(T1(r,c),T2(r,c)) << "Value at row " << r << " and col " << c << " is not exactly equal";
	}
    }

}

TEST(EigenSerializationTestSuite, testMatrix)
{

  saveLoadMatrixTest<double,6,6,6,6>();
  saveLoadMatrixTest<float,6,6,6,6>();
  saveLoadMatrixTest<int,6,6,6,6>();
  saveLoadMatrixTest<unsigned,6,6,6,6>();
  saveLoadMatrixTest<char,6,6,6,6>();

  saveLoadMatrixTest<double,1,64,1,64>();
  saveLoadMatrixTest<double,64,1,64,1>();
  saveLoadMatrixTest<float,1,64,1,64>();
  saveLoadMatrixTest<float,64,1,64,1>();

  saveLoadMatrixTest<double,Eigen::Dynamic,6,100,6>();
  saveLoadMatrixTest<double,6,Eigen::Dynamic,6,100>();
  saveLoadMatrixTest<double,Eigen::Dynamic,Eigen::Dynamic,100,100>();

}

TEST(EigenSerializationTestSuite, testMatrixXml)
{
  try{
	saveLoadXmlMatrixTest<double,6,6,6,6>();
	saveLoadXmlMatrixTest<float,6,6,6,6>();
	saveLoadXmlMatrixTest<int,6,6,6,6>();
	saveLoadXmlMatrixTest<unsigned,6,6,6,6>();
	saveLoadXmlMatrixTest<char,6,6,6,6>();

	saveLoadXmlMatrixTest<double,1,64,1,64>();
	saveLoadXmlMatrixTest<double,64,1,64,1>();
	saveLoadXmlMatrixTest<float,1,64,1,64>();
	saveLoadXmlMatrixTest<float,64,1,64,1>();

	saveLoadXmlMatrixTest<double,Eigen::Dynamic,6,100,6>();
	saveLoadXmlMatrixTest<double,6,Eigen::Dynamic,6,100>();
	saveLoadXmlMatrixTest<double,Eigen::Dynamic,Eigen::Dynamic,100,100>();
  }
  catch(const std::exception & e)
	{
	  FAIL() << e.what();
	}
}

template<typename Transform_t>
void saveLoadTransformTest()
{

  Transform_t M1,M2;
  typedef typename Transform_t::Scalar Scalar;
  typename Transform_t::MatrixType & T1a = M1.matrix();

  for(int r = 0; r < T1a.rows(); r++)
    {
      for(int c = 0; c < T1a.cols(); c++)
	{
	  T1a(r,c) = (Scalar)(((r-1)*T1a.cols()) + c);
	}
    }

  {
    std::ofstream ofs("test.ba", std::ios::binary);
    boost::archive::binary_oarchive oa(ofs);

    oa << M1;

  }

  {
    std::ifstream ifs("test.ba", std::ios::binary);
    boost::archive::binary_iarchive ia(ifs);

    ia >> M2;
  }

  typename Transform_t::MatrixType & T1 = M1.matrix();
  typename Transform_t::MatrixType & T2 = M2.matrix();

  for(int r = 0; r < T1.rows(); r++)
    {
      for(int c = 0; c < T1.cols(); c++)
	{
	  ASSERT_EQ(T1(r,c),T2(r,c)) << "Value at row " << r << " and col " << c << " is not exactly equal";
	}
    }

}

TEST(EigenSerializationTestSuite, testTransform)
{
  saveLoadTransformTest<Eigen::Affine2d>();
  saveLoadTransformTest<Eigen::Affine2f>();
  saveLoadTransformTest<Eigen::Affine3d>();
  saveLoadTransformTest<Eigen::Affine3f>();
  saveLoadTransformTest<Eigen::AffineCompact2d>();
  saveLoadTransformTest<Eigen::AffineCompact2f>();
  saveLoadTransformTest<Eigen::AffineCompact3d>();
  saveLoadTransformTest<Eigen::AffineCompact3f>();
  saveLoadTransformTest<Eigen::Isometry2f>();
  saveLoadTransformTest<Eigen::Isometry2d>();
  saveLoadTransformTest<Eigen::Isometry3f>();
  saveLoadTransformTest<Eigen::Isometry3d>();
  saveLoadTransformTest<Eigen::Projective2f>();
  saveLoadTransformTest<Eigen::Projective2d>();
  saveLoadTransformTest<Eigen::Projective3f>();
  saveLoadTransformTest<Eigen::Projective3d>();

}

// /// Run all the tests that were declared with TEST()
// int main(int argc, char **argv){
//   testing::InitGoogleTest(&argc, argv);
//   return RUN_ALL_TESTS();
// }


#ifndef STAN_MATH_TORSTEN_SOLVE_ODE_HPP
#define STAN_MATH_TORSTEN_SOLVE_ODE_HPP

#include <Eigen/Dense>
#include <stan/math/torsten/to_array_2d.hpp>
#include <stan/math/torsten/ev_manager.hpp>
#include <stan/math/torsten/pmx_population_check.hpp>
#include <stan/math/torsten/ev_solver.hpp>
#include <stan/math/torsten/pmx_ode_model.hpp>
#include <stan/math/torsten/pmx_check.hpp>
#include <vector>

namespace torsten {
/**
 * simpily pmx function declare
 *
 */
#define TORSTEN_PMX_FUNC_EVENTS_ARGS const std::vector<T0>& time,\
    const std::vector<T1>& amt,                                  \
    const std::vector<T2>& rate,                                 \
    const std::vector<T3>& ii,                                   \
    const std::vector<int>& evid,                                \
    const std::vector<int>& cmt,                                 \
    const std::vector<int>& addl,                                \
    const std::vector<int>& ss

  template<typename integrator_type>
  struct PMXSolveODE {

    /// default tolerances & max steps for
    /// differential eq(DE) and algebra solver(AS)
    static constexpr double RTOL_DE = 1.e-6;
    static constexpr double ATOL_DE = 1.e-6;
    static constexpr int MAXSTEP_DE = 1e6;
    static constexpr double RTOL_AS = 1.e-6;
    static constexpr double ATOL_AS = 1.e-6;
    static constexpr int MAXSTEP_AS = 1e2;

    /**
     * Computes the predicted amounts in each compartment at each event
     * for a general compartment model, defined by a system of ordinary
     * differential equations.
     *
     * <b>Warning:</b> This prototype does not handle steady state events.
     *
     * @tparam T0 type of scalar for time of events.
     * @tparam T1 type of scalar for amount at each event.
     * @tparam T2 type of scalar for rate at each event.
     * @tparam T3 type of scalar for inter-dose inteveral at each event.
     * @tparam T4 type of scalars for the model parameters.
     * @tparam T5 type of scalars for the bio-variability parameters.
     * @tparam T6 type of scalars for the model tlag parameters.
     * @tparam F type of ODE system function.
     * @param[in] f functor for base ordinary differential equation that defines
     *            compartment model.
     * @param[in] nCmt number of compartments in model
     * @param[in] pMatrix parameters at each event
     * @param[in] time times of events
     * @param[in] amt amount at each event
     * @param[in] rate rate at each event
     * @param[in] ii inter-dose interval at each event
     * @param[in] evid event identity:
     *                    (0) observation
     *                    (1) dosing
     *                    (2) other
     *                    (3) reset
     *                    (4) reset AND dosing
     * @param[in] cmt compartment number at each event
     * @param[in] addl additional dosing at each event
     * @param[in] ss steady state approximation at each event (0: no, 1: yes)
     * @param[in] rel_tol relative tolerance for the Boost ode solver
     * @param[in] abs_tol absolute tolerance for the Boost ode solver
     * @param[in] max_num_steps maximal number of steps to take within
     *            the Boost ode solver
     * @param[in] as_rel_tol relative tolerance for the algebra solver
     * @param[in] as_abs_tol absolute tolerance for the algebra solver
     * @param[in] as_max_num_steps maximal number of steps to take within
     *            the algebra solver
     * @return a matrix with predicted amount in each compartment
     *         at each event.
     *
     * FIX ME: currently have a dummy msgs argument. Makes it easier
     * to expose to stan grammar files, because I can follow more closely
     * what was done for the ODE integrator. Not ideal.
     */
    template <typename T0, typename T1, typename T2, typename T3, typename T4,
              typename T5, typename T6, typename F>
    static stan::matrix_return_t<T0, T1, T2, T3, T4, T5, T6>
    solve(const F& f,
          const int nCmt,
          TORSTEN_PMX_FUNC_EVENTS_ARGS,
          const std::vector<std::vector<T4> >& pMatrix,
          const std::vector<std::vector<T5> >& biovar,
          const std::vector<std::vector<T6> >& tlag,
          double rel_tol,
          double abs_tol,
          long int max_num_steps,
          double as_rel_tol,
          double as_abs_tol,
          long int as_max_num_steps,
          std::ostream* msgs) {
      using std::vector;
      using Eigen::Dynamic;
      using Eigen::Matrix;

      // check arguments
      static const char* function("PMX SOLVE ODE");
      torsten::pmx_check(time, amt, rate, ii, evid, cmt, addl, ss, pMatrix, function);
      torsten::pmx_check(time.size(), nCmt, biovar, tlag);

      using ER = NONMENEventsRecord<T0, T1, T2, T3>;
      using EM = EventsManager<ER, NonEventParameters<T0, T4, std::vector, std::tuple<T5, T6> >>;
      const ER events_rec(nCmt, time, amt, rate, ii, evid, cmt, addl, ss);

      Matrix<typename EM::T_scalar, -1, -1> pred(EM::nCmt(events_rec), events_rec.num_event_times());

      using model_type = torsten::PKODEModel<typename EM::T_par, F>;

      integrator_type integrator(rel_tol, abs_tol, max_num_steps, as_rel_tol, as_abs_tol, as_max_num_steps, msgs);
      EventSolver<model_type, EM> pr;

      pr.pred(0, events_rec, pred, integrator, pMatrix, biovar, tlag, nCmt, f);
      return pred;
    }

    /*
     * Overload with default ODE & algebra solver controls
     */
    template <typename T0, typename T1, typename T2, typename T3, typename T4,
              typename T5, typename T6, typename F>
    static stan::matrix_return_t<T0, T1, T2, T3, T4, T5, T6>
    solve(const F& f,
               const int nCmt,
               TORSTEN_PMX_FUNC_EVENTS_ARGS,
               const std::vector<std::vector<T4> >& pMatrix,
               const std::vector<std::vector<T5> >& biovar,
               const std::vector<std::vector<T6> >& tlag,
               std::ostream* msgs) {
      return solve(f, nCmt,
                   time, amt, rate, ii, evid, cmt, addl, ss,
                   pMatrix, biovar, tlag,
                   RTOL_DE, ATOL_DE, MAXSTEP_DE,
                   RTOL_AS, ATOL_AS, MAXSTEP_AS,
                   msgs);
    }

    /*
     * Overload with default algebra solver controls
     */
    template <typename T0, typename T1, typename T2, typename T3, typename T4,
              typename T5, typename T6, typename F>
    static stan::matrix_return_t<T0, T1, T2, T3, T4, T5, T6>
    solve(const F& f,
               const int nCmt,
               TORSTEN_PMX_FUNC_EVENTS_ARGS,
               const std::vector<std::vector<T4> >& pMatrix,
               const std::vector<std::vector<T5> >& biovar,
               const std::vector<std::vector<T6> >& tlag,
               double rel_tol,
               double abs_tol,
               long int max_num_steps,
               std::ostream* msgs) {
      return solve(f, nCmt,
                   time, amt, rate, ii, evid, cmt, addl, ss,
                   pMatrix, biovar, tlag,
                   rel_tol, abs_tol, max_num_steps,
                   RTOL_AS, ATOL_AS, MAXSTEP_AS,
                   msgs);
    }

    /**
     * Overload function to allow user to pass an std::vector for
     * pMatrix/bioavailability/tlag
     */
    template <typename T0, typename T1, typename T2, typename T3,
              typename T_par, typename T_biovar, typename T_tlag,
              typename F,
              typename... Ts,
              typename = require_any_not_std_vector_t<T_par, T_biovar, T_tlag>,
              typename = require_all_not_std_vector_t<Ts...> >
    static stan::matrix_return_t<T0, T1, T2, T3, T_par, T_biovar, T_tlag>
    solve(const F& f,
          const int nCmt,
          TORSTEN_PMX_FUNC_EVENTS_ARGS,
          const std::vector<T_par>& pMatrix,
          const std::vector<T_biovar>& biovar,
          const std::vector<T_tlag>& tlag,
          Ts... solver_ctrl) {
      return solve(f, nCmt,
                   time, amt, rate, ii, evid, cmt, addl, ss,
                   torsten::to_array_2d(pMatrix),
                   torsten::to_array_2d(biovar),
                   torsten::to_array_2d(tlag),
                   solver_ctrl...);
    }

    /**
     * Overload: omitting lag time, with full tolerance spec
     *
     */
    template <typename T0, typename T1, typename T2, typename T3, typename T4,
              typename T5, typename F>
    static stan::matrix_return_t<T0, T1, T2, T3, T4, T5>
    solve(const F& f,
               const int nCmt,
               TORSTEN_PMX_FUNC_EVENTS_ARGS,
               const std::vector<std::vector<T4> >& pMatrix,
               const std::vector<std::vector<T5> >& biovar,
               double rel_tol,
               double abs_tol,
               long int max_num_steps,
               double as_rel_tol,
               double as_abs_tol,
               long int as_max_num_steps,
               std::ostream* msgs) {
      static const char* function("solve");
      torsten::pmx_check(time, amt, rate, ii, evid, cmt, addl, ss, pMatrix, function);
      torsten::pmx_check(time.size(), nCmt, biovar);

      using ER = NONMENEventsRecord<T0, T1, T2, T3>;
      using EM = EventsManager<ER, NonEventParameters<T0, T4, std::vector, std::tuple<T5>>>;
      const ER events_rec(nCmt, time, amt, rate, ii, evid, cmt, addl, ss);

      Matrix<typename EM::T_scalar, -1, -1> pred(EM::nCmt(events_rec), events_rec.num_event_times());

      using model_type = torsten::PKODEModel<typename EM::T_par, F>;

      integrator_type integrator(rel_tol, abs_tol, max_num_steps, as_rel_tol, as_abs_tol, as_max_num_steps, msgs);
      EventSolver<model_type, EM> pr;

      pr.pred(0, events_rec, pred, integrator, pMatrix, biovar, nCmt, f);
      return pred;
    }

    /**
     * Overload: omitting lag time, with default ode & algebra solver spec
     *
     */
    template <typename T0, typename T1, typename T2, typename T3, typename T4,
              typename T5, typename F>
    static stan::matrix_return_t<T0, T1, T2, T3, T4, T5>
    solve(const F& f,
          const int nCmt,
          TORSTEN_PMX_FUNC_EVENTS_ARGS,
          const std::vector<std::vector<T4> >& pMatrix,
          const std::vector<std::vector<T5> >& biovar,
          std::ostream* msgs) {
      return solve(f, nCmt,
                   time, amt, rate, ii, evid, cmt, addl, ss,
                   pMatrix, biovar,
                   RTOL_DE, ATOL_DE, MAXSTEP_DE,
                   RTOL_AS, ATOL_AS, MAXSTEP_AS,
                   msgs);
    }

    /**
     * Overload: omitting lag time, with default algebra solver spec
     *
     */
    template <typename T0, typename T1, typename T2, typename T3, typename T4,
              typename T5, typename F>
    static stan::matrix_return_t<T0, T1, T2, T3, T4, T5>
    solve(const F& f,
          const int nCmt,
          TORSTEN_PMX_FUNC_EVENTS_ARGS,
          const std::vector<std::vector<T4> >& pMatrix,
          const std::vector<std::vector<T5> >& biovar,
          double rel_tol,
          double abs_tol,
          long int max_num_steps,
          std::ostream* msgs) {
      return solve(f, nCmt,
                   time, amt, rate, ii, evid, cmt, addl, ss,
                   pMatrix, biovar,
                   rel_tol, abs_tol, max_num_steps,
                   RTOL_AS, ATOL_AS, MAXSTEP_AS,
                   msgs);
    }

    /**
     * Overload: omitting lag time, allow population-wise 1d array,
     * with full ode & algebra solver spec
     *
     */
    template <typename T0, typename T1, typename T2, typename T3,
              typename T_par, typename T_biovar, typename F,
              typename... Ts,
              typename = require_any_not_std_vector_t<T_par, T_biovar>,
              typename = require_all_not_std_vector_t<Ts...> >
    static stan::matrix_return_t<T0, T1, T2, T3, T_par, T_biovar>
    solve(const F& f,
          const int nCmt,
          TORSTEN_PMX_FUNC_EVENTS_ARGS,
          const std::vector<T_par>& pMatrix,
          const std::vector<T_biovar>& biovar,
          Ts... solver_ctrl) {
      return solve(f, nCmt,
                   time, amt, rate, ii, evid, cmt, addl, ss,
                   torsten::to_array_2d(pMatrix), torsten::to_array_2d(biovar),
                   solver_ctrl...);
    }

    /**
     * Overload: omitting bioavailability & lag time,
     * with full ode & algebra solver spec
     *
     */
    template <typename T0, typename T1, typename T2, typename T3, typename T4,
              typename F>
    static stan::matrix_return_t<T0, T1, T2, T3, T4>
    solve(const F& f,
               const int nCmt,
               TORSTEN_PMX_FUNC_EVENTS_ARGS,
               const std::vector<std::vector<T4> >& pMatrix,
               double rel_tol,
               double abs_tol,
               long int max_num_steps,
               double as_rel_tol,
               double as_abs_tol,
               long int as_max_num_steps,
               std::ostream* msgs) {
      // check arguments
      static const char* function("solve");
      torsten::pmx_check(time, amt, rate, ii, evid, cmt, addl, ss, pMatrix, function);

      using ER = NONMENEventsRecord<T0, T1, T2, T3>;
      using EM = EventsManager<ER, NonEventParameters<T0, T4, std::vector, std::tuple<>>>;
      const ER events_rec(nCmt, time, amt, rate, ii, evid, cmt, addl, ss);

      Matrix<typename EM::T_scalar, -1, -1> pred(EM::nCmt(events_rec), events_rec.num_event_times());

      using model_type = torsten::PKODEModel<typename EM::T_par, F>;

      integrator_type integrator(rel_tol, abs_tol, max_num_steps, as_rel_tol, as_abs_tol, as_max_num_steps, msgs);
      EventSolver<model_type, EM> pr;

      pr.pred(0, events_rec, pred, integrator, pMatrix, nCmt, f);
      return pred;
    }

    /**
     * Overload: omitting bioavailability & lag time,
     * with default ode & algebra solver spec
     *
     */
    template <typename T0, typename T1, typename T2, typename T3, typename T4,
              typename F>
    static stan::matrix_return_t<T0, T1, T2, T3, T4>
    solve(const F& f,
          const int nCmt,
          TORSTEN_PMX_FUNC_EVENTS_ARGS,
          const std::vector<std::vector<T4> >& pMatrix,
          std::ostream* msgs) {
      return solve(f, nCmt,
                   time, amt, rate, ii, evid, cmt, addl, ss,
                   pMatrix,
                   RTOL_DE, ATOL_DE, MAXSTEP_DE,
                   RTOL_AS, ATOL_AS, MAXSTEP_AS,
                   msgs);
    }

    /**
     * Overload: omitting bioavailability & lag time,
     * with default algebra solver spec
     *
     */
    template <typename T0, typename T1, typename T2, typename T3, typename T4,
              typename F>
    static stan::matrix_return_t<T0, T1, T2, T3, T4>
    solve(const F& f,
          const int nCmt,
          TORSTEN_PMX_FUNC_EVENTS_ARGS,
          const std::vector<std::vector<T4> >& pMatrix,
          double rel_tol,
          double abs_tol,
          long int max_num_steps,
          std::ostream* msgs) {
      return solve(f, nCmt,
                   time, amt, rate, ii, evid, cmt, addl, ss,
                   pMatrix,
                   rel_tol, abs_tol, max_num_steps,
                   RTOL_AS, ATOL_AS, MAXSTEP_AS,
                   msgs);
    }

    /**
     * Overload: omitting bioavailability & lag time, allow
     * population-wise 1d array,
     * with full algebra solver spec
     *
     */
    template <typename T0, typename T1, typename T2, typename T3,
              typename T_par, typename F,
              typename... Ts,
              typename = require_any_not_std_vector_t<T_par>,
              typename = require_all_not_std_vector_t<Ts...> >
    static stan::matrix_return_t<T0, T1, T2, T3, T_par>
    solve(const F& f,
          const int nCmt,
          TORSTEN_PMX_FUNC_EVENTS_ARGS,
          const std::vector<T_par>& pMatrix,
          Ts... solver_ctrl) {
      return solve(f, nCmt,
                   time, amt, rate, ii, evid, cmt, addl, ss,
                   torsten::to_array_2d(pMatrix),
                   solver_ctrl...);
    }

    /**
     * Overload: additional real data for ODE, with full ode & algebra
     * solver spec
     *
     */
    template <typename T0, typename T1, typename T2, typename T3, typename T4,
              typename T5, typename T6, typename F>
    static stan::matrix_return_t<T0, T1, T2, T3, T4, T5, T6>
    solve(const F& f,
          const int nCmt,
          TORSTEN_PMX_FUNC_EVENTS_ARGS,
          const std::vector<std::vector<T4> >& pMatrix,
          const std::vector<std::vector<T5> >& biovar,
          const std::vector<std::vector<T6> >& tlag,
          const std::vector<std::vector<double> >& x_r,
          double rel_tol,
          double abs_tol,
          long int max_num_steps,
          double as_rel_tol,
          double as_abs_tol,
          long int as_max_num_steps,
          std::ostream* msgs) {
      // check arguments
      static const char* function("solve");
      torsten::pmx_check(time, amt, rate, ii, evid, cmt, addl, ss, pMatrix, function);
      torsten::pmx_check(time.size(), nCmt, biovar, tlag);

      using ER = NONMENEventsRecord<T0, T1, T2, T3>;
      using EM = EventsManager<ER, NonEventParameters<T0, T4, std::vector, std::tuple<T5, T6>, double>>;
      const ER events_rec(nCmt, time, amt, rate, ii, evid, cmt, addl, ss);

      Matrix<typename EM::T_scalar, -1, -1> pred(EM::nCmt(events_rec), events_rec.num_event_times());

      using model_type = torsten::PKODEModel<typename EM::T_par, F>;

      integrator_type integrator(rel_tol, abs_tol, max_num_steps, as_rel_tol, as_abs_tol, as_max_num_steps, msgs);
      EventSolver<model_type, EM> pr;

      pr.pred(0, events_rec, pred, integrator, pMatrix, biovar, tlag, x_r, nCmt, f);
      return pred;
    }

    /**
     * Overload: additional real data for ODE, with default ode & algebra
     * solver spec
     *
     */
    template <typename T0, typename T1, typename T2, typename T3, typename T4,
              typename T5, typename T6, typename F>
    static stan::matrix_return_t<T0, T1, T2, T3, T4, T5, T6>
    solve(const F& f,
          const int nCmt,
          TORSTEN_PMX_FUNC_EVENTS_ARGS,
          const std::vector<std::vector<T4> >& pMatrix,
          const std::vector<std::vector<T5> >& biovar,
          const std::vector<std::vector<T6> >& tlag,
          const std::vector<std::vector<double> >& x_r,
          std::ostream* msgs) {
      return solve(f, nCmt,
                   time, amt, rate, ii, evid, cmt, addl, ss,
                   pMatrix, biovar, tlag, x_r,
                   RTOL_DE, ATOL_DE, MAXSTEP_DE,
                   RTOL_AS, ATOL_AS, MAXSTEP_AS,
                   msgs);
    }

    /**
     * Overload: additional real data for ODE, with default algebra
     * solver spec
     *
     */
    template <typename T0, typename T1, typename T2, typename T3, typename T4,
              typename T5, typename T6, typename F>
    static stan::matrix_return_t<T0, T1, T2, T3, T4, T5, T6>
    solve(const F& f,
          const int nCmt,
          TORSTEN_PMX_FUNC_EVENTS_ARGS,
          const std::vector<std::vector<T4> >& pMatrix,
          const std::vector<std::vector<T5> >& biovar,
          const std::vector<std::vector<T6> >& tlag,
          const std::vector<std::vector<double> >& x_r,
          double rel_tol,
          double abs_tol,
          long int max_num_steps,
          std::ostream* msgs) {
      return solve(f, nCmt,
                   time, amt, rate, ii, evid, cmt, addl, ss,
                   pMatrix, biovar, tlag, x_r,
                   rel_tol, abs_tol, max_num_steps,
                   RTOL_AS, ATOL_AS, MAXSTEP_AS,
                   msgs);
    }

    // Overload: with real data, PMX params can be either 1d or 2d arrays.
    template <typename T0, typename T1, typename T2, typename T3,
              typename T_par, typename T_biovar, typename T_tlag,
              typename F,
              typename... Ts,
              typename = require_any_not_std_vector_t<T_par, T_biovar, T_tlag>,
              typename = require_all_not_std_vector_t<Ts...> >
    static stan::matrix_return_t<T0, T1, T2, T3, T_par, T_biovar, T_tlag>
    solve(const F& f,
          const int nCmt,
          TORSTEN_PMX_FUNC_EVENTS_ARGS,
          const std::vector<T_par>& pMatrix,
          const std::vector<T_biovar>& biovar,
          const std::vector<T_tlag>& tlag,
          const std::vector<std::vector<double> >& x_r,
          Ts... solver_ctrl) {
      return solve(f, nCmt,
                   time, amt, rate, ii, evid, cmt, addl, ss,
                   torsten::to_array_2d(pMatrix),
                   torsten::to_array_2d(biovar),
                   torsten::to_array_2d(tlag), x_r,
                   solver_ctrl...);
    }

    /**
     * Overload: additional real & int data for ODE, with full ode & algebra
     * solver spec
     *
     */
    template <typename T0, typename T1, typename T2, typename T3, typename T4,
              typename T5, typename T6, typename F>
    static stan::matrix_return_t<T0, T1, T2, T3, T4, T5, T6>
    solve(const F& f,
          const int nCmt,
          TORSTEN_PMX_FUNC_EVENTS_ARGS,
          const std::vector<std::vector<T4> >& pMatrix,
          const std::vector<std::vector<T5> >& biovar,
          const std::vector<std::vector<T6> >& tlag,
          const std::vector<std::vector<double> >& x_r,
          const std::vector<std::vector<int> >& x_i,
          double rel_tol,
          double abs_tol,
          long int max_num_steps,
          double as_rel_tol,
          double as_abs_tol,
          long int as_max_num_steps,
          std::ostream* msgs) {
      // check arguments
      static const char* function("solve");
      torsten::pmx_check(time, amt, rate, ii, evid, cmt, addl, ss, pMatrix, function);
      torsten::pmx_check(time.size(), nCmt, biovar, tlag);

      using ER = NONMENEventsRecord<T0, T1, T2, T3>;
      using EM = EventsManager<ER, NonEventParameters<T0, T4, std::vector, std::tuple<T5, T6>, double, int>>;
      const ER events_rec(nCmt, time, amt, rate, ii, evid, cmt, addl, ss);

      Matrix<typename EM::T_scalar, -1, -1> pred(EM::nCmt(events_rec), events_rec.num_event_times());

      using model_type = torsten::PKODEModel<typename EM::T_par, F>;

      integrator_type integrator(rel_tol, abs_tol, max_num_steps, as_rel_tol, as_abs_tol, as_max_num_steps, msgs);
      EventSolver<model_type, EM> pr;

      pr.pred(0, events_rec, pred, integrator, pMatrix, biovar, tlag, x_r, x_i, nCmt, f);
      return pred;
    }

    /**
     * Overload: additional real & int data for ODE, with default ode & algebra
     * solver spec
     *
     */
    template <typename T0, typename T1, typename T2, typename T3, typename T4,
              typename T5, typename T6, typename F>
    static stan::matrix_return_t<T0, T1, T2, T3, T4, T5, T6>
    solve(const F& f,
          const int nCmt,
          TORSTEN_PMX_FUNC_EVENTS_ARGS,
          const std::vector<std::vector<T4> >& pMatrix,
          const std::vector<std::vector<T5> >& biovar,
          const std::vector<std::vector<T6> >& tlag,
          const std::vector<std::vector<double> >& x_r,
          const std::vector<std::vector<int> >& x_i,
          std::ostream* msgs) {
      return solve(f, nCmt,
                   time, amt, rate, ii, evid, cmt, addl, ss,
                   pMatrix, biovar, tlag, x_r, x_i,
                   RTOL_DE, ATOL_DE, MAXSTEP_DE,
                   RTOL_AS, ATOL_AS, MAXSTEP_AS,
                   msgs);
    }

    /**
     * Overload: additional real & int data for ODE, with default algebra
     * solver spec
     *
     */
    template <typename T0, typename T1, typename T2, typename T3, typename T4,
              typename T5, typename T6, typename F>
    static stan::matrix_return_t<T0, T1, T2, T3, T4, T5, T6>
    solve(const F& f,
               const int nCmt,
               TORSTEN_PMX_FUNC_EVENTS_ARGS,
               const std::vector<std::vector<T4> >& pMatrix,
               const std::vector<std::vector<T5> >& biovar,
               const std::vector<std::vector<T6> >& tlag,
               const std::vector<std::vector<double> >& x_r,
               const std::vector<std::vector<int> >& x_i,
               double rel_tol,
               double abs_tol,
               long int max_num_steps,
               std::ostream* msgs) {
      return solve(f, nCmt,
                   time, amt, rate, ii, evid, cmt, addl, ss,
                   pMatrix, biovar, tlag, x_r, x_i,
                   rel_tol, abs_tol, max_num_steps,
                   RTOL_AS, ATOL_AS, MAXSTEP_AS,
                   msgs);
    }

    // Overload: with real & int data, PMX params can be either 1d or 2d arrays.
    template <typename T0, typename T1, typename T2, typename T3,
              typename T_par, typename T_biovar, typename T_tlag,
              typename F,
              typename... Ts,
              typename = require_any_not_std_vector_t<T_par, T_biovar, T_tlag>,
              typename = require_all_not_std_vector_t<Ts...> >
    static stan::matrix_return_t<T0, T1, T2, T3, T_par, T_biovar, T_tlag>
    solve(const F& f,
          const int nCmt,
          TORSTEN_PMX_FUNC_EVENTS_ARGS,
          const std::vector<T_par>& pMatrix,
          const std::vector<T_biovar>& biovar,
          const std::vector<T_tlag>& tlag,
          const std::vector<std::vector<double> >& x_r,
          const std::vector<std::vector<int> >& x_i,
          Ts... solver_ctrl) {
      return solve(f, nCmt,
                   time, amt, rate, ii, evid, cmt, addl, ss,
                   torsten::to_array_2d(pMatrix),
                   torsten::to_array_2d(biovar),
                   torsten::to_array_2d(tlag), x_r, x_i,
                   solver_ctrl...);
    }
  };
}
#endif


//==============================================================================
//         Copyright 2003 - 2011   LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2011   LRI    UMR 8623 CNRS/Univ Paris Sud XI
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#ifndef NT2_SDK_COMPLEX_HIERARCHY_HPP_INCLUDED
#define NT2_SDK_COMPLEX_HIERARCHY_HPP_INCLUDED

#include <boost/dispatch/meta/hierarchy_of.hpp>
#include <boost/dispatch/meta/property_of.hpp>
#include <boost/dispatch/meta/fusion.hpp>
#include <nt2/sdk/complex/meta/real_of.hpp>
#include <complex>

namespace boost { namespace dispatch { namespace meta
{
  template<class T>
  struct complex_ : complex_<typename T::parent>
  {
    typedef complex_<typename T::parent>  parent;
  };

  template<class T>
  struct complex_< unspecified_<T> > : fusion_sequence_<T>
  {
    typedef fusion_sequence_<T> parent;
  };

  // FIXME: find a way to do this so that it works with nested types too
  template<class T>
  struct generic_< unspecified_< std::complex<T> > > : fusion_sequence_< std::complex<T> >
  {
    typedef fusion_sequence_< std::complex<T> > parent;
  };

  template<class T>
  struct imaginary_ : imaginary_<typename T::parent>
  {
    typedef imaginary_<typename T::parent>  parent;
  };

  template<class T>
  struct imaginary_< unspecified_<T> > : complex_< typename property_of<typename nt2::meta::real_of<T>::type, T>::type >
  {
    typedef complex_< typename property_of<typename nt2::meta::real_of<T>::type, T>::type > parent;
  };

  template<class T>
  struct dry_ : dry_<typename T::parent>
  {
    typedef dry_<typename T::parent>  parent;
  };

  template<class T>
  struct dry_< unspecified_<T> > : complex_< typename property_of<typename nt2::meta::real_of<T>::type, T>::type >
  {
    typedef complex_< typename property_of<typename nt2::meta::real_of<T>::type, T>::type > parent;
  };

} } }

namespace nt2 { namespace ext
{
  using boost::dispatch::meta::complex_;
  using boost::dispatch::meta::imaginary_;
  using boost::dispatch::meta::dry_;
} }

#endif


//==============================================================================
//         Copyright 2003 - 2012 LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2012 LRI    UMR 8623 CNRS/Univ Paris Sud XI
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#ifndef BOOST_SIMD_SWAR_FUNCTIONS_SIMD_SSE_AVX_INTERLEAVE_SECOND_HPP_INCLUDED
#define BOOST_SIMD_SWAR_FUNCTIONS_SIMD_SSE_AVX_INTERLEAVE_SECOND_HPP_INCLUDED
#ifdef BOOST_SIMD_HAS_AVX_SUPPORT

#include <boost/simd/swar/functions/interleave_second.hpp>
#include <boost/simd/include/functions/simd/bitwise_cast.hpp>
#include <boost/dispatch/meta/as_floating.hpp>

namespace boost { namespace simd { namespace ext
{
  BOOST_DISPATCH_IMPLEMENT         ( interleave_second_
                                   , boost::simd::tag::avx_
                                   , (A0)
                                   , ((simd_<single_<A0>,boost::simd::tag::avx_>))
                                     ((simd_<single_<A0>,boost::simd::tag::avx_>))
                                   )
  {
    typedef A0 result_type;

    BOOST_FORCEINLINE result_type operator()(A0 const& a0, A0 const& a1) const
    {
      // 0x31 is SCR1[128:255]|SRC2[128:255] according to Intel AVX manual
      // The result of unpack_*_ps puts parts in the proper pairs beforehand
      return _mm256_permute2f128_ps ( _mm256_unpacklo_ps(a0,a1)
                                    , _mm256_unpackhi_ps(a0,a1)
                                    , 0x31
                                    );
    }
  };

  BOOST_DISPATCH_IMPLEMENT         ( interleave_second_
                                   , boost::simd::tag::avx_
                                   , (A0)
                                   , ((simd_<double_<A0>,boost::simd::tag::avx_>))
                                     ((simd_<double_<A0>,boost::simd::tag::avx_>))
                                   )
  {
    typedef A0 result_type;

    BOOST_FORCEINLINE result_type operator()(A0 const& a0, A0 const& a1) const
    {
      // workaround for bad ICC optimisation
      #ifdef __INTEL_COMPILER
      __m256d volatile lo = _mm256_unpacklo_pd(a0,a1);
      #else
      __m256d lo = _mm256_unpacklo_pd(a0,a1);
      #endif

      // 0x31 is SCR1[128:255]|SRC2[128:255] according to Intel AVX manual
      // The result of unpack_*_pd puts parts in the proper pairs beforehand
      return _mm256_permute2f128_pd ( lo
                                    , _mm256_unpackhi_pd(a0,a1)
                                    , 0x31
                                    );
    }
  };

  BOOST_DISPATCH_IMPLEMENT          ( interleave_second_
                                    , boost::simd::tag::avx_
                                    , (A0)
                                    , ((simd_ < floating_sized_<A0>
                                              , boost::simd::tag::avx_
                                              >
                                      ))
                                      ((simd_ < floating_sized_<A0>
                                              , boost::simd::tag::avx_
                                              >
                                      ))
                                   )
  {
    typedef A0 result_type;

    BOOST_FORCEINLINE result_type operator()(A0 const& a0, A0 const& a1) const
    {
      typedef typename boost::dispatch::meta::as_floating<A0>::type  ftype;
      return  bitwise_cast<result_type>
              ( interleave_second ( bitwise_cast<ftype>(a0)
                                  , bitwise_cast<ftype>(a1)
                                  )
              );
    }
  };
} } }

#endif
#endif


#include "analyzer.h"
#include "ui_analyzer.h"

#include "comparewidget.h"

#include <QFileDialog>
#include <QDir>
#include <QTreeWidgetItem>
#include <QSettings>
#include <QTextStream>

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <algorithm>
#include <numeric>
#include <iomanip>
#include <memory>
#include <iostream>

#include "disparity_toolkit/genericfunctions.h"
#include "disparity_toolkit/taskanalysis.h"
#include "disparity_toolkit/disparity_utils.h"
#include "disparity_toolkit/stereotask.h"
#include "detailviewer.h"

#include <cvio/hdf5internals.h>

/*#include <Eigen/StdVector>
#include <pcl/point_cloud.h>
#include <pcl/point_types.h>
#include <pcl/visualization/cloud_viewer.h>*/

Analyzer::Analyzer(QWidget *parent) :
	QWidget(parent),
	ui(new Ui::Analyzer)
{
	ui->setupUi(this);

	QStringList header;
	header << "Name" << "Task" << "Config" << "window" << "quantizer" << "Segmentation" << "runtime" << "optimization" << "dilate" << "dilate_step" << "refinement" << "damping" << "color_var" << "spatial var" << "size" << "compact" << "deltaDisp";

	ui->twOverview->setHeaderLabels(header);
	ui->twOverview->setColumnCount(header.size());

	ui->pbSave->setVisible(false);
	ui->teNotes->setVisible(false);

	QDir dir;
	QSettings settings;
	ui->lePath->setText(settings.value("path", dir.absolutePath()).toString());

	m_basePath = QDir(ui->lePath->text());
	m_resultsPath = m_basePath;
	m_resultsPath.cd("results");

	on_pbRefresh_clicked();
}

Analyzer::~Analyzer()
{
	delete ui;
}

void optimizeWidth(QTreeWidget *widget)
{
	for(int i = 0; i < widget->columnCount(); ++i)
		widget->resizeColumnToContents(i);
}

void Analyzer::on_toolButton_clicked()
{
	QString path = QFileDialog::getExistingDirectory(this, "Choose dir", ui->lePath->text());
	if(!path.isEmpty())
	{
		QSettings settings;
		settings.setValue("path", path);

		ui->lePath->setText(path);
		on_pbRefresh_clicked();
	}
}

template<typename T, typename storage_type>
QString read_number(const std::string& elem, const storage_type& fs)
{
	T temp;
	fs[elem] >> temp;
	return QString::number(temp);
}

template<typename T, typename storage_type>
void read_numbers(const storage_type& fs, QStringList& list, std::initializer_list<std::string> elems)
{
	for(const std::string& elem : elems)
		list << read_number<T>(elem, fs);
}

template<typename storage_type>
QString read_string(const std::string& elem, const storage_type& fs)
{
	std::string temp;
	fs[elem] >> temp;
	return QString(temp.c_str());
}

void addHist(QStringList& item, std::vector<int>& hist, bool bcumulative)
{
	int sum = std::accumulate(hist.begin(), hist.end(), 0);

	float mean = 0.0f;
	float cumulative = 0.0f;
	for(std::size_t j = 0; j < hist.size(); ++j)
	{
		float percent = (float)hist[j]/sum;
		cumulative += percent;

		if(bcumulative)
			item << QString::number(cumulative*100, 'g', 3) + "%";
		else
			item << QString::number(percent*100, 'g', 3) + "%";

		mean += j*percent;
	}
	item << QString::number(mean, 'g', 5);
}

void addSubItem(QStringList& subitem, const cv::FileNode& node)
{
	subitem << read_string("taskname", node);
	subitem << "" << "" << "";
	subitem << read_number<int>("total_runtime", node);

	for(int j = 0; j < 4; ++j)
		subitem << "";
}

void Analyzer::on_pbRefresh_clicked()
{
	m_basePath = QDir(ui->lePath->text());
	m_resultsPath = m_basePath;
	m_resultsPath.cd("results");

	ui->twOverview->clear();

	QStringList filter;
	filter << "*.yml";

	QStringList files = m_resultsPath.entryList(filter);

	for(int i = 0; i < files.size(); ++i)
	{
		//qDebug(dir.absoluteFilePath(files.at(i)).toAscii());
		try{
			QStringList item;
			QString filenameYML = files.at(i);
			item << filenameYML.left(filenameYML.size() - 4);

			cv::FileStorage fs(m_resultsPath.absoluteFilePath(files.at(i)).toStdString(), cv::FileStorage::READ);
			if(!fs.isOpened())
				qDebug("not");
			//item << readString("left", fs) + ", " + readString("right", fs);
			item << read_string("testset", fs);
			item << read_string("configname", fs);
			QString segm = read_string("segmentation", fs);
			if(!segm.isEmpty())
			{
				QString subitem = read_number<int>("min_windowsize", fs) + "-" + read_number<int>("max_windowsize", fs);
				item << subitem;
			}
			else
				item << read_number<int>("windowsize", fs);
			item << read_number<int>("quantizer", fs);
			item << segm;
			//item << readInt("total_runtime", fs);
			item << "";
			read_numbers<int>(fs, item, {"optimization_rounds", "dilate", "dilate_step", "refinement", "enable_damping"});
			item << read_number<double>("color_var", fs);
			item << read_number<int>("spatial_var", fs);
			item << read_number<int>("superpixel_size" ,fs);
			item << read_number<double>("superpixel_compactness", fs);
			item << read_number<int>("deltaDisp", fs);

			int total_runtime = 0;
			QTreeWidgetItem* parent = new QTreeWidgetItem(item);

			cv::FileNode node = fs["analysis"];
			for(cv::FileNodeIterator it = node.begin(); it != node.end(); ++it)
			{
				QStringList subitem_left;
				QStringList subitem_right;

				addSubItem(subitem_left, *it);
				addSubItem(subitem_right, *it);

				int runtime;
				(*it)["total_runtime"] >> runtime;
				total_runtime += runtime;

				subitem_left[1] = "forward";
				subitem_right[1] = "backward";
				new QTreeWidgetItem(parent, subitem_left);
				new QTreeWidgetItem(parent, subitem_right);
			}
			parent->setText(6, QString::number(total_runtime));

			ui->twOverview->addTopLevelItem(parent);
			//parent->setExpanded(true);
		}
		catch(cv::Exception ex)
		{
			qDebug("exception caught:");
			qDebug(ex.msg.c_str());
		}
	}
	optimizeWidth(ui->twOverview);
}

disparity_map load_disparity(cvio::hdf5file& hfile, const std::string& path, int default_subsampling)
{
	cvio::hdf5dataset dataset_right(hfile, path);
	int subsampling = dataset_right.get_attribute("sampling", default_subsampling);
	std::cout << "subsampling_right: " << subsampling << std::endl;

	return disparity_map(hfile.load(path), subsampling);
}

void show_detail_window(stereo_task& task, task_analysis& analysis, disparity_map& disp_left, disparity_map& disp_right)
{
	cv::Mat warpedLeft   = disparity::warp_image<cv::Vec3b>(task.left,  disp_left,  cv::Vec3b(0,0,0), 1.0f/disp_left.sampling);
	cv::Mat warpedRight  = disparity::warp_image<cv::Vec3b>(task.right, disp_right, cv::Vec3b(0,0,0), 1.0f/disp_right.sampling);

	cv::Mat disp_left_img  = disparity::create_image(disp_left);
	cv::Mat disp_right_img = disparity::create_image(disp_right);
	cv::Mat disp_left_img_color, disp_right_img_color;
	cv::cvtColor(disp_left_img, disp_left_img_color, CV_GRAY2BGR);
	cv::cvtColor(disp_right_img, disp_right_img_color, CV_GRAY2BGR);

	//PCL
	/*pcl::PointCloud<pcl::PointXYZRGB>::Ptr pc (new pcl::PointCloud<pcl::PointXYZRGB>());
	pc->height = disp_left.rows;
	pc->width = disp_left.cols;
	pc->is_dense = true;
std::stringstream stream = generate_latex(rows, setnames);

	int total_points = task.left.total();
	pc->reserve(total_points);

	for(int y = 0; y < disp_left.rows; ++y)
	{
		for(int x = 0; x < disp_left.cols; ++x)
		{
			cv::Vec3b ccolor = task.left.at<cv::Vec3b>(y,x);
			short disp = disp_left.at<short>(y,x);

			pcl::PointXYZRGB point(ccolor[2], ccolor[1], ccolor[0]);
			point.x = x;
			point.y = y;
			point.z = (task.dispRange-std::abs(disp))*3;
			pc->push_back(point);
		}
	}

	pcl::visualization::CloudViewer viewerpc("Cloud Viewer");
	viewerpc.showCloud(pc);

	while (!viewerpc.wasStopped ())
		;
	*/
	//PCL-END

	std::vector<std::pair<QString, cv::Mat>> images;
	images.push_back(std::make_pair("left", task.left));
	images.push_back(std::make_pair("right", task.right));
	images.push_back(std::make_pair("left disp", disp_left_img));
	images.push_back(std::make_pair("right disp", disp_right_img));
	images.push_back(std::make_pair("left error", analysis.diff_mat_left));
	images.push_back(std::make_pair("right error", analysis.diff_mat_right));
	images.push_back(std::make_pair("left warped to right", warpedLeft));
	images.push_back(std::make_pair("right warped to left", warpedRight));

	bool mix = false;
	cv::Mat mixedLeft, mixedRight;
	if(mix)
	{
		mixedLeft  = 0.5*task.left  + 0.5*warpedRight;
		mixedRight = 0.5*task.right + 0.5*warpedLeft;
	}
	else
	{
		mixedLeft  = task.left  + warpedRight;
		mixedRight = task.right + warpedLeft;
	}
	images.push_back(std::make_pair("left mix", mixedLeft));
	images.push_back(std::make_pair("right mix", mixedRight));

	cv::Mat img_disp_left_overlay  = 0.7*disp_left_img_color  + 0.3*task.left;
	cv::Mat img_disp_right_overlay = 0.7*disp_right_img_color + 0.3*task.right;
	images.push_back(std::make_pair("left disp image overlay", img_disp_left_overlay));
	images.push_back(std::make_pair("right disp image overlay", img_disp_right_overlay));

	std::cout << "images added " << images.size() << std::endl;

	DetailViewer *viewer = new DetailViewer();
	viewer->setMatList(images);
	viewer->show();
}

void Analyzer::setSubTask(const QString& base, const QString& name)
{
	QString m_currentFilename = base + ".yml";

	std::cout << m_currentFilename.toStdString() << std::endl;

	cv::FileStorage fs(m_resultsPath.absoluteFilePath(m_currentFilename).toStdString(), cv::FileStorage::READ);

	ui->compare->reset(std::vector<QString>{base});

	int windowsize;
	fs["windowsize"] >> windowsize;

	cv::FileNode node = fs["analysis"];
	for(cv::FileNodeIterator it = node.begin(); it != node.end(); ++it)
	{
		QString cname = read_string("taskname", *it);
		if(cname == name)
		{
			std::cout << "found" << std::endl;

			std::string hdf_abspath = m_resultsPath.absoluteFilePath(base + ".hdf5").toStdString();

			stereo_task task = stereo_task::load_from_filestorage(*it, m_basePath.absolutePath().toStdString());

			std::cout << hdf_abspath << std::endl;
			cvio::hdf5file hfile(hdf_abspath);

			disparity_map disp_left  = load_disparity(hfile, "/results/" + cname.toStdString() + "/left_disparity",  task.ground_truth_sampling);
			disparity_map disp_right = load_disparity(hfile, "/results/" + cname.toStdString() + "/right_disparity", task.ground_truth_sampling);

			task_analysis analysis(task, disp_left, disp_right, windowsize/2);

			show_detail_window(task, analysis, disp_left, disp_right);

			//mainwindow
			std::vector<cv::Mat> imagesLeft {disparity::create_image(disp_left), analysis.diff_mat_left};
			std::vector<cv::Mat> imagesRight {disparity::create_image(disp_right), analysis.diff_mat_right};
			ui->compare->addRow(name + " (forward)", imagesLeft);
			ui->compare->addRow(name + " (backward)", imagesRight);
		}
	}
}

void Analyzer::setTask(const QString& base)
{
	ui->compare->reset(std::vector<QString>{base});

	m_notesFilename = base + ".txt";

	ui->teNotes->setText(tasknotes(base));

	ui->pbSave->setVisible(true);
	ui->teNotes->setVisible(true);
}

class CompareRow
{
public:
	CompareRow(const QString& pname) : name(pname) {}
	QString name;
	std::vector<std::vector<int>> hist;
	std::vector<cv::Mat> images;
};

CompareRow* getElement(std::vector<std::unique_ptr<CompareRow>>&rows, const QString& name)
{
	auto it = std::find_if(rows.begin(), rows.end(), [&](const std::unique_ptr<CompareRow>& row){return name == row->name;});

	if(it == rows.end())
	{
		rows.push_back(std::unique_ptr<CompareRow>(new CompareRow(name)));
		return rows.back().get();
	}
	else
		return it->get();
}

void Analyzer::on_pbSave_clicked()
{
	if(!m_notesFilename.isEmpty())
	{
		QFile file(m_resultsPath.absoluteFilePath(m_notesFilename));
		file.open(QFile::WriteOnly | QFile::Truncate);
		QTextStream stream(&file);
		stream << ui->teNotes->toPlainText();
	}
}

void Analyzer::on_cbCumulative_clicked()
{
	on_pbRefresh_clicked();
}

std::string generate_latex(const std::vector<std::unique_ptr<CompareRow>>& rows, const std::vector<QString>& setnames)
{
	assert(rows.size() > 0);
	assert(rows[0]->hist.size() > 0);
	std::stringstream stream;
	std::size_t trunc = 6;
	int cols = std::min(rows[0]->hist[0].size(), trunc);
	std::string header = "|l||";
	for(int i = 0; i < cols + 2; ++i)
		header += "l|";
	stream << "\\begin{tabular}{" << header << "}\\hline\n";
	stream << "~ & ";
	for(int i = 0; i < cols-1; ++i)
		stream << i << " & ";
	stream << "$\\geq$ " << (cols-1) << " & $\\mu$ & ~ \\\\ \\hline \n";

	for(const std::unique_ptr<CompareRow>& crow : rows)
	{
		stream << "\\hline\\multicolumn{" << cols+3 << "}{|l|}{" << crow->name.toStdString() << "}\\\\ \\hline \n";
		std::vector<std::stringstream> rowstream(crow->hist.size());
		std::vector<float> rowmeans(crow->hist.size());
		for(std::size_t i = 0; i < crow->hist.size(); ++i)
		{
			int sum = std::accumulate(crow->hist[i].begin(), crow->hist[i].end(), 0);
			int sum_trunc = std::accumulate(crow->hist[i].begin() + trunc, crow->hist[i].end(), 0);

			std::string name = setnames[i].toStdString();
			while(name.find('_') != std::string::npos)
				name.replace(name.find('_'), 1, "-");
			rowstream[i] << name;

			rowstream[i] << std::setprecision(3);
			float mean = 0.0f;
			for(int j = 0; j < cols; ++j)
			{
				float cval = (j == cols-1) ? (float)sum_trunc/sum :(float)crow->hist[i][j]/sum;
				rowstream[i] << " & " << cval*100 << "\\%";
				mean += cval*j;
			}
			rowmeans[i] = mean;
			rowstream[i] << std::setprecision(5);
			rowstream[i] << " & " << mean;
			//rowstream[i] << "\\\\ \\hline \n";
		}

		float min_mean = *(std::min_element(rowmeans.begin(), rowmeans.end()));
		for(std::size_t i = 0; i < crow->hist.size(); ++i)
		{
			if(rowmeans[i] == min_mean)
				rowstream[i] << " & - ";
			else
				rowstream[i] << " & +" << ((rowmeans[i]/min_mean)-1)*100 << " \\%";
			rowstream[i] << "\\\\ \\hline \n";
		}

		for(std::stringstream& cstream : rowstream)
			stream << cstream.str();
	}
	stream << "\\end{tabular}";

	return stream.str();
}

QString Analyzer::tasknotes(QString base)
{
	QString notesFilename = base + ".txt";
	if(m_resultsPath.exists(notesFilename))
	{
		QFile file(m_resultsPath.absoluteFilePath(notesFilename));
		file.open(QFile::ReadOnly);
		QTextStream noteStream(&file);
		return noteStream.readAll();
	}
	else
		return QString();
}

void Analyzer::setTasks(QList<QTreeWidgetItem*> items)
{
	std::vector<std::unique_ptr<CompareRow>> rows;
	std::vector<QString> setnames;
	std::vector<QString> notes;

	for(int k = 0; k < items.size(); ++k)
	{
		QTreeWidgetItem *item = items.at(k);

		if(!item->parent())
		{
			QString base = item->text(0);
			QString m_currentFilename = base + ".yml";

			notes.push_back(tasknotes(base));
			setnames.push_back(base);

			cv::FileStorage fs(m_resultsPath.absoluteFilePath(m_currentFilename).toStdString(), cv::FileStorage::READ);

			int windowsize;
			fs["windowsize"] >> windowsize;

			cv::FileNode node = fs["analysis"];
			for(cv::FileNodeIterator it = node.begin(); it != node.end(); ++it)
			{
				QString name = read_string("taskname", *it);

				std::string hdf_abs_path = m_resultsPath.absoluteFilePath(base + ".hdf5").toStdString();

				stereo_task task = stereo_task::load_from_filestorage(*it, m_basePath.absolutePath().toStdString());

				std::cout << hdf_abs_path << std::endl;

				cvio::hdf5file hfile(hdf_abs_path);

				disparity_map disp_left  = load_disparity(hfile, "/results/" + task.name + "/left_disparity",  task.ground_truth_sampling);
				disparity_map disp_right = load_disparity(hfile, "/results/" + task.name + "/right_disparity", task.ground_truth_sampling);

				task_analysis analysis(task, disp_left, disp_right, windowsize/2);

				CompareRow* crow_left  = getElement(rows, name +" (left)");
				crow_left->images.push_back(disparity::create_image(disp_left));
				crow_left->images.push_back(analysis.diff_mat_left);
				crow_left->hist.push_back(analysis.error_hist_left);

				CompareRow* crow_right = getElement(rows, name +" (right)");
				crow_right->images.push_back(disparity::create_image(disp_right));
				crow_right->images.push_back(analysis.diff_mat_right);
				crow_right->hist.push_back(analysis.error_hist_right);
			}
		}
	}

	ui->compare->reset(setnames);
	ui->compare->insertNotesRow(notes);
	for(std::unique_ptr<CompareRow>& crow : rows)
	{
		ui->compare->addRow(crow->name, crow->hist, crow->images);
	}

	std::cout << generate_latex(rows, setnames) << std::endl;
}

void Analyzer::on_twOverview_itemSelectionChanged()
{
	on_pbSave_clicked();
	m_notesFilename = "";
	ui->pbSave->setVisible(false);
	ui->teNotes->setVisible(false);

	if(ui->twOverview->selectedItems().size() == 1)
	{
		QTreeWidgetItem *item = ui->twOverview->selectedItems().at(0);
		if(item->parent())
		{
			//Subtask-Mode
			QString base = item->parent()->text(0);
			setSubTask(base, item->text(0));
		}
		else
		{
			//TaskMode
			setTask(item->text(0));
			setTasks(ui->twOverview->selectedItems());
		}
	}
	else if(ui->twOverview->selectedItems().size() >= 1)
	{
		if(ui->twOverview->selectedItems().size() <= 6)
			setTasks(ui->twOverview->selectedItems());
		else
			std::cout << "to much tasks" << std::endl;
	}
}

void Analyzer::on_horizontalSlider_valueChanged(int value)
{
	ui->compare->setScaling(value/8.0f);
}


//---------------------------------------------------------------------------//
// Copyright (c) 2013 Kyle Lutz <kyle.r.lutz@gmail.com>
//
// Distributed under the Boost Software License, Version 1.0
// See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt
//
// See http://boostorg.github.com/compute for more information.
//---------------------------------------------------------------------------//

#ifndef BOOST_COMPUTE_ALGORITHM_SERIAL_MERGE_HPP
#define BOOST_COMPUTE_ALGORITHM_SERIAL_MERGE_HPP

#include <iterator>

#include <boost/compute/command_queue.hpp>
#include <boost/compute/detail/meta_kernel.hpp>
#include <boost/compute/detail/iterator_range_size.hpp>

namespace boost {
namespace compute {
namespace detail {

template<class InputIterator1,
         class InputIterator2,
         class OutputIterator,
         class Compare>
inline OutputIterator serial_merge(InputIterator1 first1,
                                   InputIterator1 last1,
                                   InputIterator2 first2,
                                   InputIterator2 last2,
                                   OutputIterator result,
                                   Compare comp,
                                   command_queue &queue)
{
    typedef typename
        std::iterator_traits<InputIterator1>::value_type
        input_type1;
    typedef typename
        std::iterator_traits<InputIterator2>::value_type
        input_type2;
    typedef typename
        std::iterator_traits<OutputIterator>::difference_type
        result_difference_type;

    std::ptrdiff_t size1 = std::distance(first1, last1);
    std::ptrdiff_t size2 = std::distance(first2, last2);

    meta_kernel k("serial_merge");
    k.add_set_arg<uint_>("size1", static_cast<uint_>(size1));
    k.add_set_arg<uint_>("size2", static_cast<uint_>(size2));

    k <<
        "uint i = 0;\n" << // index in result range
        "uint j = 0;\n" << // index in first input range
        "uint k = 0;\n" << // index in second input range

        // fetch initial values from each range
        k.decl<input_type1>("j_value") << " = " << first1[0] << ";\n" <<
        k.decl<input_type2>("k_value") << " = " << first2[0] << ";\n" <<

        // merge values from both input ranges to the result range
        "while(j < size1 && k < size2){\n" <<
        "    if(" << comp(k.var<input_type1>("j_value"),
                          k.var<input_type2>("k_value")) << "){\n" <<
        "        " << result[k.var<uint_>("i++")] << " = j_value;\n" <<
        "        j_value = " << first1[k.var<uint_>("++j")] << ";\n" <<
        "    }\n" <<
        "    else{\n"
        "        " << result[k.var<uint_>("i++")] << " = k_value;\n"
        "        k_value = " << first2[k.var<uint_>("++k")] << ";\n" <<
        "    }\n"
        "}\n"

        // copy any remaining values from first range
        "while(j < size1){\n" <<
            result[k.var<uint_>("i++")] << " = " <<
               first1[k.var<uint_>("j++")] << ";\n" <<
        "}\n"

        // copy any remaining values from second range
        "while(k < size2){\n" <<
            result[k.var<uint_>("i++")] << " = " <<
               first2[k.var<uint_>("k++")] << ";\n" <<
        "}\n";

    // run kernel
    k.exec(queue);

    return result + static_cast<result_difference_type>(size1 + size2);
}

} // end detail namespace
} // end compute namespace
} // end boost namespace

#endif // BOOST_COMPUTE_ALGORITHM_SERIAL_MERGE_HPP