<|start_filename|>higra/accumulator/common.hpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#pragma once

#include "py_accumulators.hpp"
#include "../py_common.hpp"

template<typename functor_t>
auto dispatch_accumulator(const functor_t & fun, const hg::accumulators & accumulator){
    switch (accumulator) {
        case hg::accumulators::min:
            return fun(hg::accumulator_min());
        case hg::accumulators::max:
            return fun(hg::accumulator_max());
        case hg::accumulators::mean:
            return fun(hg::accumulator_mean());
        case hg::accumulators::counter:
            return fun(hg::accumulator_counter());
        case hg::accumulators::sum:
            return fun(hg::accumulator_sum());
        case hg::accumulators::prod:
            return fun(hg::accumulator_prod());
        case hg::accumulators::first:
            return fun(hg::accumulator_first());
        case hg::accumulators::last:
            return fun(hg::accumulator_last());
        case hg::accumulators::argmin:
            return fun(hg::accumulator_argmin());
        case hg::accumulators::argmax:
            return fun(hg::accumulator_argmax());
    }
}


<|start_filename|>lib/lib/cmake/xsimd/xsimdConfig.cmake<|end_filename|>
############################################################################
# Copyright (c) <NAME>, <NAME>, <NAME> and         #
# <NAME>                                                             #
# Copyright (c) QuantStack                                                 #
#                                                                          #
# Distributed under the terms of the BSD 3-Clause License.                 #
#                                                                          #
# The full license is in the file LICENSE, distributed with this software. #
############################################################################

# xsimd cmake module
# This module sets the following variables in your project::
#
#   xsimd_FOUND - true if xsimd found on the system
#   xsimd_INCLUDE_DIRS - the directory containing xsimd headers
#   xsimd_LIBRARY - empty


####### Expanded from @PACKAGE_INIT@ by configure_package_config_file() #######
####### Any changes to this file will be overwritten by the next CMake run ####
####### The input file was xsimdConfig.cmake.in                            ########

get_filename_component(PACKAGE_PREFIX_DIR "${CMAKE_CURRENT_LIST_DIR}/../../../" ABSOLUTE)

macro(set_and_check _var _file)
  set(${_var} "${_file}")
  if(NOT EXISTS "${_file}")
    message(FATAL_ERROR "File or directory ${_file} referenced by variable ${_var} does not exist !")
  endif()
endmacro()

macro(check_required_components _NAME)
  foreach(comp ${${_NAME}_FIND_COMPONENTS})
    if(NOT ${_NAME}_${comp}_FOUND)
      if(${_NAME}_FIND_REQUIRED_${comp})
        set(${_NAME}_FOUND FALSE)
      endif()
    endif()
  endforeach()
endmacro()

####################################################################################

if(NOT TARGET xsimd)
    include("${CMAKE_CURRENT_LIST_DIR}/xsimdTargets.cmake")
    get_target_property(xsimd_INCLUDE_DIRS xsimd INTERFACE_INCLUDE_DIRECTORIES)
endif()


<|start_filename|>higra/detail/py_log.cpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#include "py_log.hpp"
#include "../py_common.hpp"
#include "higra/config.hpp"
#include "higra/detail/log.hpp"


void py_init_log(pybind11::module &m) {

    m.def("version",
          []() { return HG_XSTR(HIGRA_VERSION_MAJOR) "." HG_XSTR(HIGRA_VERSION_MINOR) "." HG_XSTR(HIGRA_VERSION_PATCH); },
          "Gives the version number of higra.");

    m.def("set_trace", [](bool enabled) { hg::logger::trace_enabled() = enabled; },
          "Define if function call tracing is enabled.",
          pybind11::arg("enabled"));

    m.def("get_trace", []() { return hg::logger::trace_enabled(); },
          "Get the state of function call tracing.");

    /*m.def("add_logger_callback",
    [](std::function<void(const std::string &)>  fun){
        hg::logger::callbacks().push_back(fun);
        },
    "Add a new callback for Higra logger.",
    pybind11::arg("callback"));*/


    m.def("logger_register_print_callback",
          []() {
              hg::logger::callbacks().push_back([](const std::string &msg) {
                  pybind11::object buildins = pybind11::module::import("builtins");
                  pybind11::object print = buildins.attr("print");
                  print(msg);
              });
          },
          "Register the builtin print function as a logger output."
    );

}


<|start_filename|>test/cpp/algo/test_watershed.cpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#include "../test_utils.hpp"
#include "higra/algo/watershed.hpp"
#include "higra/image/graph_image.hpp"

using namespace hg;

namespace test_watershed {

    TEST_CASE("watershed cut simple", "[watershed_cut]") {

        // Fig 4 of Watershed Cuts: Minimum Spanning Forests and the
        // Drop of Water Principle
        // <NAME>, <NAME>, <NAME>, <NAME>
        auto g = hg::get_4_adjacency_graph({4, 4});
        array_1d<int> edge_weights{1, 2, 5, 5, 5, 8, 1, 4, 3, 4, 4, 1, 5, 2, 6, 3, 5, 4, 0, 7, 0, 3, 4, 0};

        auto labels = hg::labelisation_watershed(g, edge_weights);

        array_1d<index_t> expected{1, 1, 1, 2,
                                   1, 1, 2, 2,
                                   1, 1, 3, 3,
                                   1, 1, 3, 3};
        REQUIRE((labels == expected));
    }

    TEST_CASE("watershed cut simple 2", "[watershed_cut]") {
        auto g = hg::get_4_adjacency_graph({3, 3});
        array_1d<int> edge_weights{1, 1, 0, 0, 0, 1, 0, 0, 2, 2, 0, 2};

        auto labels = hg::labelisation_watershed(g, edge_weights);

        array_1d<index_t> expected{1, 1, 1,
                                   2, 1, 1,
                                   2, 2, 1};
        REQUIRE((labels == expected));
    }

    TEST_CASE("seeded watersed 1", "[seeded_watersed_cut]") {
        auto g = hg::get_4_adjacency_graph({4, 4});
        array_1d<int> edge_weights{1, 2, 5, 5, 4, 8, 1, 4, 3, 4, 4, 1, 5, 2, 6, 2, 5, 2, 0, 7, 0, 3, 4, 0};
        array_1d<int> seeds{1, 1, 9, 9,
                            1, 9, 9, 9,
                            9, 9, 9, 9,
                            1, 1, 2, 2};
        auto labels = hg::labelisation_seeded_watershed(g, edge_weights, seeds, 9);

        array_1d<int> expected{1, 1, 2, 2,
                               1, 1, 2, 2,
                               1, 1, 2, 2,
                               1, 1, 2, 2};
        REQUIRE((labels == expected));
    }

    TEST_CASE("seeded watersed 2", "[seeded_watersed_cut]") {
        auto g = hg::get_4_adjacency_graph({4, 4});
        array_1d<int> edge_weights{1, 2, 5, 5, 4, 8, 1, 4, 3, 4, 4, 1, 5, 2, 6, 2, 5, 2, 0, 7, 0, 3, 4, 0};
        array_1d<int> seeds{1, 1, 0, 0,
                            1, 0, 0, 0,
                            0, 0, 0, 0,
                            2, 2, 3, 3};
        auto labels = hg::labelisation_seeded_watershed(g, edge_weights, seeds);

        array_1d<int> expected{1, 1, 3, 3,
                               1, 1, 3, 3,
                               2, 2, 3, 3,
                               2, 2, 3, 3};
        REQUIRE((labels == expected));
    }

    TEST_CASE("seeded watersed split minima", "[seeded_watersed_cut]") {
        auto g = hg::get_4_adjacency_graph({2, 4});
        array_1d<int> edge_weights{0, 1, 0, 2, 0, 2, 0, 1, 2, 1};
        /* x0x0x0x
         * 1 2 2 0
         * x1x2x1x */
        array_1d<int> seeds{1, 0, 0, 2,
                            0, 0, 0, 0};
        auto labels = hg::labelisation_seeded_watershed(g, edge_weights, seeds);
        /*
        * other possible results:
        * ((1, 1, 2, 2),
        * (1, 1, 2, 2))
        * or
        * ((1, 2, 2, 2),
        * (1, 1, 2, 2)) */
        array_1d<int> expected{1, 1, 1, 2,
                               1, 1, 2, 2};
        REQUIRE((labels == expected));
    }

    TEST_CASE("seeded watersed disconnected seed", "[seeded_watersed_cut]") {
        auto g = hg::get_4_adjacency_graph({2, 3});
        array_1d<int> edge_weights{1, 0, 2, 0, 0, 1, 2};
        /* x1x2x
         * 0 0 0
         * x1x2x */
        array_1d<int> seeds{5, 7, 5,
                            0, 0, 0};
        auto labels = hg::labelisation_seeded_watershed(g, edge_weights, seeds);

        array_1d<int> expected{5, 7, 5,
                               5, 7, 5};
        REQUIRE((labels == expected));
    }

    TEST_CASE("seeded watersed seed not in minima", "[seeded_watersed_cut]") {
        auto g = hg::get_4_adjacency_graph({2, 4});
        array_1d<int> edge_weights{0, 2, 0, 2, 1, 2, 2, 1, 0, 0};
        /* x0x0x1x
         * 2 2 2 2
         * x1x0x0x */
        array_1d<int> seeds{0, 0, 0, 1,
                            2, 0, 0, 0};
        auto labels = hg::labelisation_seeded_watershed(g, edge_weights, seeds);

        array_1d<int> expected{1, 1, 1, 1,
                               2, 2, 2, 2};
        REQUIRE((labels == expected));
    }

}


<|start_filename|>test/cpp/test.cpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#define HG_DEBUG

#define CATCH_CONFIG_MAIN


#include "test_utils.hpp"

using namespace hg;
using namespace xt;
using namespace std;

TEST_CASE("unused", "[experimental]") {

}




<|start_filename|>test/cpp/attribute/test_tree_attribute.cpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#include "../test_utils.hpp"
#include "higra/attribute/tree_attribute.hpp"
#include "higra/image/graph_image.hpp"
#include "higra/hierarchy/component_tree.hpp"
#include "higra/io/tree_io.hpp"

namespace tree_attributes {

    using namespace hg;
    using namespace std;

    struct _data {

        hg::tree t;

        _data() : t(xt::xarray<index_t>{5, 5, 6, 6, 6, 7, 7, 7}) {
        }

    } data;

    TEST_CASE("tree attribute area", "[tree_attributes]") {
        auto t = data.t;

        array_1d<index_t> ref{1, 1, 1, 1, 1, 2, 3, 5};
        auto res = attribute_area(t);
        REQUIRE((ref == res));

        array_1d<index_t> leaf_area{2, 1, 1, 3, 2};
        array_1d<index_t> ref2{2, 1, 1, 3, 2, 3, 6, 9};
        auto res2 = attribute_area(t, leaf_area);
        REQUIRE((ref2 == res2));
    }

    TEST_CASE("tree attribute volume", "[tree_attributes]") {
        auto t = data.t;

        array_1d<index_t> node_area{2, 1, 1, 3, 2, 3, 6, 9};
        array_1d<double> node_altitude{0, 0, 0, 0, 0, 2, 1, 4};
        array_1d<index_t> ref{0, 0, 0, 0, 0, 6, 18, 24};
        auto res = attribute_volume(t, node_altitude, node_area);
        REQUIRE((ref == res));
    }

    TEST_CASE("tree attribute depth", "[tree_attributes]") {
        auto t = data.t;

        array_1d<index_t> ref{2, 2, 2, 2, 2, 1, 1, 0};
        auto res = attribute_depth(t);
        REQUIRE((ref == res));
    }

    TEST_CASE("tree attribute height", "[tree_attributes]") {
        hg::tree t(xt::xarray<index_t>{7, 7, 8, 8, 8, 9, 9, 10, 10, 11, 11, 11});
        SECTION("increasing") {
            array_1d<double> node_altitude{0, 0, 0, 0, 0, 0, 0, 3, 2, 1, 5, 8};
            array_1d<index_t> ref{0, 0, 0, 0, 0, 0, 0, 2, 3, 7, 6, 7};
            auto res = attribute_height(t, node_altitude, true);
            REQUIRE((ref == res));
        }SECTION("decreasing") {
            array_1d<double> node_altitude{0, 0, 0, 0, 0, 0, 0, 8, 5, 9, 4, 1};
            array_1d<index_t> ref{0, 0, 0, 0, 0, 0, 0, 4, 1, 8, 7, 8};
            auto res = attribute_height(t, node_altitude, false);
            REQUIRE((ref == res));
        }
    }

    TEST_CASE("tree attribute extrema", "[tree_attributes]") {
        tree t(xt::xarray<index_t>{11, 11, 9, 9, 8, 8, 13, 13, 10, 10, 12, 12, 14, 14, 14});

        array_1d<double> node_altitude{0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 1, 4, 8, 10};
        array_1d<bool> ref{0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0};
        auto res = attribute_extrema(t, node_altitude);
        REQUIRE((ref == res));
    }

    TEST_CASE("tree attribute extrema2", "[tree_attributes]") {
        auto graph = get_4_adjacency_implicit_graph({4, 4});
        array_1d<double> vertex_weights({0, 1, 4, 4,
                                         7, 5, 6, 8,
                                         2, 3, 4, 1,
                                         9, 8, 6, 7});

        auto res = component_tree_max_tree(graph, vertex_weights);
        auto &tree = res.tree;
        auto &altitudes = res.altitudes;

        auto extrema = attribute_extrema(tree, altitudes);
        array_1d<bool> expected_extrema({0, 0, 0, 0,
                                         0, 0, 0, 0,
                                         0, 0, 0, 0,
                                         0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0});
        REQUIRE((expected_extrema == extrema));
    }

    TEST_CASE("tree attribute extinction value", "[tree_attributes]") {
        // same as dynamics
        tree t(xt::xarray<index_t>{8, 8, 9, 7, 7, 11, 11, 9, 10, 10, 12, 12, 12});

        array_1d<double> altitudes{0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 4, 8, 10};
        array_1d<double> attribute{0, 0, 0, 0, 0, 0, 0, 0, 3, 2, 4, 2, 10};

        array_1d<index_t> ref{3, 3, 0, 10, 10, 2, 2, 10, 3, 10, 10, 2, 10};
        auto res = attribute_extinction_value(t, altitudes, attribute, true);
        REQUIRE((ref == res));
    }

    TEST_CASE("tree attribute extinction value2", "[tree_attributes]") {
        // area extinction on a max tree
        auto graph = get_4_adjacency_implicit_graph({4, 4});
        array_1d<double> vertex_weights({0, 1, 4, 4,
                                         7, 5, 6, 8,
                                         2, 3, 4, 1,
                                         9, 8, 6, 7});

        auto res = component_tree_max_tree(graph, vertex_weights);
        auto &tree = res.tree;
        auto &altitudes = res.altitudes;

        auto area = attribute_area(tree);
        auto ext = attribute_extinction_value(tree, altitudes, area, false);

        array_1d<double> expected_ext({0, 0, 0, 0,
                                     1, 0, 0, 4,
                                     0, 0, 0, 0,
                                     16, 0, 0, 1,
                                     16, 16, 4, 1, 1, 16, 4, 4, 16, 16, 16, 16, 16});
        REQUIRE((ext == expected_ext));
    }

    TEST_CASE("tree attribute dynamics", "[tree_attributes]") {
        tree t(xt::xarray<index_t>{8, 8, 9, 7, 7, 11, 11, 9, 10, 10, 12, 12, 12});

        array_1d<double> node_altitude{0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 4, 8, 10};
        array_1d<index_t> ref{3, 3, 0, 10, 10, 2, 2, 10, 3, 10, 10, 2, 10};
        auto res = attribute_dynamics(t, node_altitude, true);
        REQUIRE((ref == res));
    }

    TEST_CASE("tree attribute dynamics2", "[tree_attributes]") {
        tree t(xt::xarray<index_t>{11, 11, 9, 9, 8, 8, 13, 13, 10, 10, 12, 12, 14, 14, 14});

        array_1d<double> node_altitude{0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 1, 4, 8, 10};
        array_1d<index_t> ref{3, 3, 0, 0, 10, 10, 2, 2, 10, 0, 10, 3, 10, 2, 10};
        auto res = attribute_dynamics(t, node_altitude, true);
        REQUIRE((ref == res));
    }

    TEST_CASE("tree attribute siblings", "[tree_attributes]") {
        auto t = data.t;

        array_1d<index_t> ref{1, 0, 3, 4, 2, 6, 5, 7};
        auto res = attribute_sibling(t);
        REQUIRE((ref == res));

        array_1d<index_t> ref2{1, 0, 4, 2, 3, 6, 5, 7};
        auto res2 = attribute_sibling(t, -1);
        REQUIRE((ref2 == res2));
    }

    TEST_CASE("tree attribute contour length component tree", "[tree_attributes]") {
        auto g = get_4_adjacency_graph({4, 4});

        /* for reference, tree is a max tree on the following image
        array_1d<double> vertex_weights({0, 1, 4, 4,
                                         7, 5, 6, 8,
                                         2, 3, 4, 1,
                                         9, 8, 6, 7});
        */

        array_1d<index_t> parents({28, 27, 24, 24,
                                   20, 23, 22, 18,
                                   26, 25, 24, 27,
                                   16, 17, 21, 19,
                                   17, 21, 22, 21, 23, 24, 23, 24, 25, 26, 27, 28, 28});
        /*
         array_1d<double> altitudes({0, 1, 4, 4,
                                     7, 5, 6, 8,
                                     2, 3, 4, 1,
                                     9, 8, 6, 7, 
                                     9, 8, 8, 7, 7, 6, 6, 5, 4, 3, 2, 1, 0});
         */

        tree t(parents, tree_category::component_tree);

        array_1d<double> vertex_perimeters({num_vertices(g)}, 4);
        array_1d<double> edge_length({num_edges(g)}, 1);

        auto res = attribute_contour_length_component_tree(t, g, vertex_perimeters, edge_length);

        array_1d<double> ref{4, 4, 4, 4,
                             4, 4, 4, 4,
                             4, 4, 4, 4,
                             4, 4, 4, 4,
                             4, 6, 4, 4, 4, 10, 6, 10, 22, 20, 18, 16, 16};

        REQUIRE(xt::allclose(ref, res));
    }

    TEST_CASE("tree attribute child number", "[tree_attributes]") {
        auto t = data.t;

        array_1d<index_t> ref{0, 1, 0, 1, 2, 0, 1, -1};
        auto res = attribute_child_number(t);
        REQUIRE((ref == res));
    }

    TEST_CASE("tree attribute smallest enclosing shape ", "[tree_attributes]") {
        array_1d<index_t> pt1{8, 8, 9, 9, 9, 10, 10, 11, 13, 12, 11, 12, 13, 13};
        tree t1(pt1);

        array_1d<index_t> pt2{11, 11, 8, 8, 10, 9, 9, 9, 10, 12, 11, 12, 12};
        tree t2(pt2);

        array_1d<index_t> ref{0, 1, 2, 3, 4, 5, 6, 7, 11, 10, 9, 9, 12, 12};
        auto res = attribute_smallest_enclosing_shape(t1, t2);
        REQUIRE((ref == res));
    }

    TEST_CASE("tree attribute children pair sum product scalar", "[tree_attributes]") {
        auto t = data.t; //{5, 5, 6, 6, 6, 7, 7, 7}

        array_1d<double> node_weights{2, 3, 4, 5, 6, 7, 8, 9};
        auto res = attribute_children_pair_sum_product(t, node_weights);
        array_1d<double> ref{0, 0, 0, 0, 0, 6, 74, 56};
        REQUIRE(xt::allclose(ref, res));
    }

    TEST_CASE("tree attribute children pair sum product vectorial", "[tree_attributes]") {
        auto t = data.t; //{5, 5, 6, 6, 6, 7, 7, 7}

        array_nd<double> node_weights{2, 9, 3, 8, 4, 7, 5, 6, 6, 5, 7, 4, 8, 3, 9, 2};
        node_weights.reshape({8, 2});
        auto res = attribute_children_pair_sum_product(t, node_weights);
        array_nd<double> ref{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 72, 74, 107, 56, 12};
        ref.reshape({8, 2});
        REQUIRE(xt::allclose(ref, res));
    }

}

<|start_filename|>test/cpp/image/test_graph_image.cpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#include "higra/image/graph_image.hpp"
#include "../test_utils.hpp"

namespace graph_image {

    using namespace hg;
    using namespace std;

    TEST_CASE("4 adjacency graph explicit", "[graph_image]") {
        auto g = get_4_adjacency_graph({2, 3});

        vector<vector<pair<unsigned long, unsigned long>>> outListsRef{
                {{0, 1}, {0, 3}},
                {{1, 0}, {1, 2}, {1, 4}},
                {{2, 1}, {2, 5}},
                {{3, 0}, {3, 4}},
                {{4, 1}, {4, 3}, {4, 5}},
                {{5, 2}, {5, 4}}
        };
        vector<vector<pair<unsigned long, unsigned long>>> outListsTest;

        for (size_t v = 0; v < 6; v++) {
            outListsTest.push_back({});
            for (auto e: hg::out_edge_iterator(v, g))
                outListsTest[v].push_back({source(e, g), target(e, g)});
            REQUIRE(vectorEqual(outListsRef[v], outListsTest[v]));
            REQUIRE(out_degree(v, g) == outListsRef[v].size());
        }
    }

    TEST_CASE("4 adjacency graph implicit", "[graph_image]") {
        auto g = get_4_adjacency_implicit_graph({2, 3});

        vector<vector<pair<unsigned long, unsigned long>>> outListsRef{
                {{0, 1}, {0, 3}},
                {{1, 0}, {1, 2}, {1, 4}},
                {{2, 1}, {2, 5}},
                {{3, 0}, {3, 4}},
                {{4, 1}, {4, 3}, {4, 5}},
                {{5, 2}, {5, 4}}
        };
        vector<vector<pair<unsigned long, unsigned long>>> outListsTest;

        for (size_t v = 0; v < 6; v++) {
            outListsTest.push_back({});
            for (auto e: hg::out_edge_iterator(v, g))
                outListsTest[v].push_back({source(e, g), target(e, g)});
            REQUIRE(vectorEqual(outListsRef[v], outListsTest[v]));
            REQUIRE(out_degree(v, g) == outListsRef[v].size());
        }
    }

    TEST_CASE("8 adjacency graph explicit", "[graph_image]") {
        auto g = get_8_adjacency_graph({2, 3});

        vector<vector<pair<unsigned long, unsigned long>>> outListsRef{
                {{0, 1}, {0, 3}, {0, 4}},
                {{1, 0}, {1, 2}, {1, 3}, {1, 4}, {1, 5}},
                {{2, 1}, {2, 4}, {2, 5}},
                {{3, 0}, {3, 1}, {3, 4}},
                {{4, 0}, {4, 1}, {4, 2}, {4, 3}, {4, 5}},
                {{5, 1}, {5, 2}, {5, 4}}
        };
        vector<vector<pair<unsigned long, unsigned long>>> outListsTest;

        for (size_t v = 0; v < 6; v++) {
            outListsTest.push_back({});
            for (auto e: hg::out_edge_iterator(v, g))
                outListsTest[v].push_back({source(e, g), target(e, g)});
            REQUIRE(vectorEqual(outListsRef[v], outListsTest[v]));
            REQUIRE(out_degree(v, g) == outListsRef[v].size());
        }
    }

    TEST_CASE("8 adjacency graph implicit", "[graph_image]") {
        auto g = get_8_adjacency_implicit_graph({2, 3});

        vector<vector<pair<unsigned long, unsigned long>>> outListsRef{
                {{0, 1}, {0, 3}, {0, 4}},
                {{1, 0}, {1, 2}, {1, 3}, {1, 4}, {1, 5}},
                {{2, 1}, {2, 4}, {2, 5}},
                {{3, 0}, {3, 1}, {3, 4}},
                {{4, 0}, {4, 1}, {4, 2}, {4, 3}, {4, 5}},
                {{5, 1}, {5, 2}, {5, 4}}
        };
        vector<vector<pair<unsigned long, unsigned long>>> outListsTest;

        for (size_t v = 0; v < 6; v++) {
            outListsTest.push_back({});
            for (auto e: hg::out_edge_iterator(v, g))
                outListsTest[v].push_back({source(e, g), target(e, g)});
            REQUIRE(vectorEqual(outListsRef[v], outListsTest[v]));
            REQUIRE(out_degree(v, g) == outListsRef[v].size());
        }
    }

    TEST_CASE("4 adjacency graph to Khalimsky 2d", "[graph_image]") {
        auto g = get_4_adjacency_graph({4, 5});

        xt::xarray<int> data{
                0, 0, 1, 0, 2, 0, 3, 0, 0, 0, 0, 1, 0, 2, 0, 3, 0, 0, 0, 1, 1, 1, 2, 0, 3, 0, 0, 0, 0, 2,
                3
        };

        xt::xarray<int> ref{
                {0, 0, 0, 1, 0, 2, 0, 3, 0},
                {0, 0, 0, 1, 0, 2, 0, 3, 0},
                {0, 0, 0, 1, 0, 2, 0, 3, 0},
                {0, 0, 0, 1, 0, 2, 0, 3, 0},
                {0, 0, 0, 1, 0, 2, 0, 3, 0},
                {1, 1, 1, 1, 0, 2, 0, 3, 0},
                {0, 0, 0, 0, 0, 2, 0, 3, 0}
        };
        auto r = graph_4_adjacency_2_khalimsky(g, {4, 5}, data);
        REQUIRE(xt::allclose(ref, r));

        xt::xarray<int> ref2{
                {0, 0, 0, 0, 1, 0, 2, 0, 3, 0, 0},
                {0, 0, 0, 0, 1, 0, 2, 0, 3, 0, 0},
                {0, 0, 0, 0, 1, 0, 2, 0, 3, 0, 0},
                {0, 0, 0, 0, 1, 0, 2, 0, 3, 0, 0},
                {0, 0, 0, 0, 1, 0, 2, 0, 3, 0, 0},
                {0, 0, 0, 0, 1, 0, 2, 0, 3, 0, 0},
                {1, 1, 1, 1, 1, 0, 2, 0, 3, 0, 0},
                {0, 0, 0, 0, 0, 0, 2, 0, 3, 0, 0},
                {0, 0, 0, 0, 0, 0, 2, 0, 3, 0, 0}
        };
        auto r2 = graph_4_adjacency_2_khalimsky(g, {4, 5}, data, true);
        REQUIRE(xt::allclose(ref2, r2));
    }

    TEST_CASE("Khalimsky 2d to 4 adjacency graph to ", "[graph_image]") {
        array_1d<index_t> ref_shape{4, 5};

        xt::xarray<int> data{
                0, 0, 1, 0, 2, 0, 3, 0, 0, 0, 0, 1, 0, 2, 0, 3, 0, 0, 0, 1, 1, 1, 2, 0, 3, 0, 0, 0, 0, 2,
                3
        };

        xt::xarray<int> ref{
                {0, 0, 0, 1, 0, 2, 0, 3, 0},
                {0, 0, 0, 1, 0, 2, 0, 3, 0},
                {0, 0, 0, 1, 0, 2, 0, 3, 0},
                {0, 0, 0, 1, 0, 2, 0, 3, 0},
                {0, 0, 0, 1, 0, 2, 0, 3, 0},
                {1, 1, 1, 1, 0, 2, 0, 3, 0},
                {0, 0, 0, 0, 0, 2, 0, 3, 0}
        };
        auto r = khalimsky_2_graph_4_adjacency(ref);
        //auto & graph = std::get<0>(r);
        auto &embedding = std::get<1>(r);
        auto &weights = std::get<2>(r);
        REQUIRE(xt::allclose(embedding.shape(), ref_shape));
        REQUIRE(xt::allclose(data, weights));

        xt::xarray<int> ref2{
                {0, 0, 0, 0, 1, 0, 2, 0, 3, 0, 0},
                {0, 0, 0, 0, 1, 0, 2, 0, 3, 0, 0},
                {0, 0, 0, 0, 1, 0, 2, 0, 3, 0, 0},
                {0, 0, 0, 0, 1, 0, 2, 0, 3, 0, 0},
                {0, 0, 0, 0, 1, 0, 2, 0, 3, 0, 0},
                {0, 0, 0, 0, 1, 0, 2, 0, 3, 0, 0},
                {1, 1, 1, 1, 1, 0, 2, 0, 3, 0, 0},
                {0, 0, 0, 0, 0, 0, 2, 0, 3, 0, 0},
                {0, 0, 0, 0, 0, 0, 2, 0, 3, 0, 0}
        };
        auto r2 = khalimsky_2_graph_4_adjacency(ref2, true);
        //auto & graph2 = std::get<0>(r2);
        auto &embedding2 = std::get<1>(r2);
        auto &weights2 = std::get<2>(r2);
        REQUIRE(xt::allclose(embedding2.shape(), ref_shape));
        REQUIRE(xt::allclose(data, weights2));
    }
}

<|start_filename|>test/cpp/structure/test_point.cpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#include "../test_utils.hpp"
#include "higra/structure/point.hpp"

namespace point {

    using namespace hg;
    using namespace std;

    TEST_CASE("point 2d float create and arithmetic", "[point]") {
        point_2d_f p1{{1.5, 2.3}};
        point_2d_f p2{{2, 1}};

        point_2d_f ref{{2.5, 2.3}};

        REQUIRE(xt::allclose(ref, p1 + p2 - 1));
    }

    TEST_CASE("point 2d integer create and arithmetic", "[point]") {
        point_2d_i p1{{4, 2}};
        point_2d_i p2{{2, 3}};

        point_2d_i ref{{5, 4}};

        REQUIRE(xt::allclose(ref, p1 + p2 - 1));
    }
}

<|start_filename|>test/cpp/assessment/test_partition.cpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#include "higra/assessment/partition.hpp"
#include "../test_utils.hpp"

using namespace hg;

namespace assessment_partition {
 
    TEST_CASE("cardinal of intersections", "[assessment_partition]") {
            array_1d<int> candidate{ 0, 0, 0, 1, 1, 1, 2, 2, 2 };
            array_1d<int> gt1{ 0, 0, 1, 1, 1, 2, 2, 3, 3 };
            array_1d<int> gt2{ 0, 0, 0, 0, 1, 1, 1, 1, 1 };

            auto r = card_intersections(candidate, xt::stack(xt::xtuple(gt1, gt2)));

            std::vector<array_2d<index_t>> ref =
            {
                {
                    { 2, 1, 0, 0 },
                    { 0, 2, 1, 0 },
                    { 0, 0, 1, 2 }
                },
                {
                    { 3, 0 },
                    { 1, 2 },
                    { 0, 3 }
                }
            };
            REQUIRE(r.size() == ref.size());
            for (int i = 0; i < (int)ref.size(); i++) {
                REQUIRE((ref[i] == r[i]));
            }
    }

    TEST_CASE("assess partition BCE", "[assessment_partition]") {
            array_1d<int> candidate{ 0, 0, 0, 1, 1, 1, 2, 2, 2 };
            array_1d<int> gt1{ 0, 0, 1, 1, 1, 2, 2, 3, 3 };
            array_1d<int> gt2{ 0, 0, 0, 0, 1, 1, 1, 1, 1 };

            auto bce1 = assess_partition(candidate, gt1, scorer_partition_BCE());
            double s1 = 5.0 / 9;
            REQUIRE(s1 == bce1);

            auto bce2 = assess_partition(candidate, gt2, scorer_partition_BCE());
            double s2 = (9.0 / 4 + 1.0 / 4 + 4.0 / 5 + 9.0 / 5) / 9;
            REQUIRE(s2 == bce2);

            auto bce = assess_partition(candidate, xt::stack(xt::xtuple(gt1, gt2)), scorer_partition_BCE());
            REQUIRE((s1 + s2) / 2.0 == bce);
    }

    TEST_CASE("assess partition DHaming", "[assessment_partition]") {
            array_1d<int> candidate{ 0, 0, 0, 1, 1, 1, 2, 2, 2 };
            array_1d<int> gt1{ 0, 0, 1, 1, 1, 2, 2, 3, 3 };
            array_1d<int> gt2{ 0, 0, 0, 0, 1, 1, 1, 1, 1 };

            auto dh1 = assess_partition(candidate, gt1, scorer_partition_DHamming());
            double s1 = 6.0 / 9;
            REQUIRE(s1 == dh1);

            auto dh2 = assess_partition(candidate, gt2, scorer_partition_DHamming());
            double s2 = 8.0 / 9;
            REQUIRE(s2 == dh2);

            auto dh = assess_partition(candidate, xt::stack(xt::xtuple(gt1, gt2)), scorer_partition_DHamming());
            REQUIRE(almost_equal((s1 + s2) / 2.0, dh));
    }

    TEST_CASE("assess partition DCovering", "[assessment_partition]") {
            array_1d<int> candidate{ 0, 0, 0, 1, 1, 1, 2, 2, 2 };
            array_1d<int> gt1{ 0, 0, 1, 1, 1, 2, 2, 3, 3 };
            array_1d<int> gt2{ 0, 0, 0, 0, 1, 1, 1, 1, 1 };

            auto cov1 = assess_partition(candidate, gt1, scorer_partition_DCovering());
            double s1 = (2.0 + 1.5 + 2.0) / 9;
            REQUIRE(s1 == cov1);

            auto cov2 = assess_partition(candidate, gt2, scorer_partition_DCovering());
            double s2 = (9.0 / 4 + 1 + 9.0 / 5) / 9;
            REQUIRE(s2 == cov2);

            auto cov = assess_partition(candidate, xt::stack(xt::xtuple(gt1, gt2)), scorer_partition_DCovering());
            REQUIRE(almost_equal((s1 + s2) / 2.0, cov));
    }

}


<|start_filename|>include/higra/algo/alignment.hpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#pragma once

#include "rag.hpp"
#include "tree.hpp"
#include "higra/hierarchy/hierarchy_core.hpp"
#include "xtensor/xsort.hpp"

namespace hg {

    /**
   * Given two labelisations, a fine and a coarse one, of a same set of elements.
   * Find for each label (ie. region) of the fine labelisation, the label of the region in the
   * coarse labelisation that maximises the intersection with the "fine" region.
   *
   * Pre-condition:
   *  range(xlabelisation_fine) = [0..num_regions_fine[
   *  range(xlabelisation_coarse) = [0..num_regions_coarse[
   *
   * If num_regions_fine or num_regions_coarse are not provided, they will
   * be determined as max(xlabelisation_fine) + 1 and max(xlabelisation_coarse) + 1
   * @tparam T1
   * @tparam T2
   * @param xlabelisation_fine
   * @param num_regions_fine
   * @param xlabelisation_coarse
   * @param num_regions_coarse
   * @return a 1d array of size num_regions_fine
   */
    template<typename T1, typename T2>
    auto project_fine_to_coarse_labelisation
            (const xt::xexpression<T1> &xlabelisation_fine,
             const xt::xexpression<T2> &xlabelisation_coarse,
             size_t num_regions_fine = 0,
             size_t num_regions_coarse = 0) {
        HG_TRACE();
        auto &labelisation_fine = xlabelisation_fine.derived_cast();
        auto &labelisation_coarse = xlabelisation_coarse.derived_cast();

        hg_assert_integral_value_type(labelisation_fine);
        hg_assert_integral_value_type(labelisation_coarse);
        hg_assert_1d_array(labelisation_fine);
        hg_assert_1d_array(labelisation_coarse);
        hg_assert(labelisation_fine.size() == labelisation_coarse.size(),
                  "Labelisations must have the same size.");

        if (num_regions_fine == 0) {
            num_regions_fine = xt::amax(labelisation_fine)(0) + 1;
        }

        if (num_regions_coarse == 0) {
            num_regions_coarse = xt::amax(labelisation_coarse)(0) + 1;
        }

        array_2d<size_t> intersections = xt::zeros<size_t>({num_regions_fine, num_regions_coarse});

        for (index_t i = 0; i < (index_t)labelisation_fine.size(); i++) {
            intersections(labelisation_fine(i), labelisation_coarse(i))++;
        }

        // cast size_t -> index_t
        array_1d<index_t> res = xt::argmax(intersections, 1);
        return res;
    }

    /**
     * Given two region adjacency graphs, a fine and a coarse one, of a same set of elements.
     * Find for each region of the fine rag, the region of the
     * coarse rag that maximises the intersection with the "fine" region.
     *
     * @param fine_rag
     * @param coarse_rag
     * @return a 1d array of size num_vertices(fine_rag.rag)
     */
    inline
    auto project_fine_to_coarse_rag
            (const region_adjacency_graph &fine_rag,
             const region_adjacency_graph &coarse_rag) {
        return project_fine_to_coarse_labelisation(fine_rag.vertex_map,
                                                   coarse_rag.vertex_map,
                                                   num_vertices(fine_rag.rag),
                                                   num_vertices(coarse_rag.rag));
    }

    namespace alignment_internal {

        template<typename rag_t, typename T, typename tree_t, typename T2>
        auto project_hierarchy(const rag_t &rag_fine, const T &coarse_supervertices, const tree_t &tree_coarse,
                               const T2 &tree_coarse_node_altitudes) {
            HG_TRACE();
            hg_assert_node_weights(tree_coarse, tree_coarse_node_altitudes);
            hg_assert_1d_array(tree_coarse_node_altitudes);
            hg_assert_1d_array(coarse_supervertices);
            hg_assert(rag_fine.vertex_map.size() == coarse_supervertices.size(),
                      "Dimensions of the two labelisations do not match.");

            auto &fine_supervertices = rag_fine.vertex_map;
            auto &rag = rag_fine.rag;

            auto fine_to_coarse_map = project_fine_to_coarse_labelisation(fine_supervertices, coarse_supervertices);
            array_1d<typename T2::value_type> coarse_sm_on_fine_rag = xt::empty<typename T2::value_type>(
                    {num_edges(rag_fine.rag)});

            lca_fast lca(tree_coarse);

            for (auto e: edge_iterator(rag)) {
                auto projected_lca = lca.lca(fine_to_coarse_map[source(e, rag)], fine_to_coarse_map[target(e, rag)]);
                coarse_sm_on_fine_rag(index(e, rag)) = tree_coarse_node_altitudes(projected_lca);
            }

            return coarse_sm_on_fine_rag;
        }
    }

    /**
     * This class allows to project hierarchies build from coarse supervertices
     * onto fine supervertices.
     *
     * The class is contructed by providing a fine supervertices decomposition of a graph.
     * Then the functions align_hierarchy allows to project a hierarchy, given as a tree or as a saliency map,
     * onto the fine supervertices.
     *
     * Given:
     *  - a graph g
     *  - a fine labelisation l1 of the vertices of g;
     *  - a tree t on g whose supervertices corresponds to the coarse labelisation l2 of the vertices of g; and
     *  - the altitudes a of the nodes of t.
     * Let us denote:
     *  - given a vertex x of g and a labelisation l, l(x) is the region of l that contains x
     *  - given a region r of l1, s(r, l2) is the region R of l2 that has the largest intersection with r:
     *            s(r, l2) = arg_max(R in l2) |R \cap r|
     * The projection of t onto l1 is a hierarchy given by the saliency map sm on g defined by:
     *     for all {x,y} in edges(g), sm({x,y}) = a(lca_t(s(l1(x), l2), s(l1(y), l2)))
     *
     * See the following helper functions for instanciation
     *  - make_hierarchy_aligner_from_graph_cut
     *  - make_hierarchy_aligner_from_labelisation
     *  - make_hierarchy_aligner_from_hierarchy
     */
    class hierarchy_aligner {
    public:

        hierarchy_aligner(region_adjacency_graph &&rag) : m_fine_rag(std::forward<region_adjacency_graph>(rag)) {

        }

        template<typename T>
        auto align_hierarchy(const hg::tree &tree, const xt::xexpression<T> &xaltitudes) const {
            HG_TRACE();
            auto &altitudes = xaltitudes.derived_cast();
            hg_assert_node_weights(tree, altitudes);
            hg_assert_1d_array(altitudes);
            hg_assert(num_leaves(tree) == m_fine_rag.vertex_map.size(),
                      "Cannot align given hierarchy: incompatible sizes!");
            auto sv_hierarchy = supervertices_hierarchy(tree, altitudes);
            auto altitudes_sv_hierarchy = xt::index_view(altitudes, sv_hierarchy.node_map);
            auto coarse_sm_on_fine_rag =
                    alignment_internal::project_hierarchy(m_fine_rag,
                                                           sv_hierarchy.supervertex_labelisation,
                                                           sv_hierarchy.tree,
                                                           altitudes_sv_hierarchy);
            return rag_back_project_weights(m_fine_rag.edge_map, coarse_sm_on_fine_rag);
        }

        template<typename graph_t, typename T>
        auto align_hierarchy(const graph_t &graph, const xt::xexpression<T> &xsaliency_map) const {
            HG_TRACE();
            auto &saliency_map = xsaliency_map.derived_cast();
            hg_assert_edge_weights(graph, saliency_map);
            hg_assert_1d_array(saliency_map);
            hg_assert(num_vertices(graph) == m_fine_rag.vertex_map.size(),
                      "Cannot align given hierarchy: incompatible sizes!");
            auto coarse_rag = make_region_adjacency_graph_from_graph_cut(graph, saliency_map);
            auto coarse_rag_edge_weights = rag_accumulate(coarse_rag.edge_map, saliency_map, accumulator_first());
            auto bpt_coarse_rag = bpt_canonical(coarse_rag.rag, coarse_rag_edge_weights);

            auto coarse_sm_on_fine_rag =
                    alignment_internal::project_hierarchy(m_fine_rag,
                                                           coarse_rag.vertex_map,
                                                           bpt_coarse_rag.tree,
                                                           bpt_coarse_rag.altitudes);

            return rag_back_project_weights(m_fine_rag.edge_map, coarse_sm_on_fine_rag);
        }

        template<typename T1, typename T2>
        auto align_hierarchy(const xt::xexpression<T1> &xcoarse_supervertices,
                             const hg::tree &tree,
                             const xt::xexpression<T2> &xaltitudes) const {
            HG_TRACE();
            auto &coarse_supervertices = xcoarse_supervertices.derived_cast();
            auto &altitudes = xaltitudes.derived_cast();
            hg_assert_node_weights(tree, altitudes);
            hg_assert_1d_array(altitudes);
            hg_assert_1d_array(coarse_supervertices);
            hg_assert_integral_value_type(coarse_supervertices);
            hg_assert(coarse_supervertices.size() == m_fine_rag.vertex_map.size(),
                      "Cannot align given hierarchy: incompatible sizes!");

            auto coarse_sm_on_fine_rag =
                    alignment_internal::project_hierarchy(m_fine_rag,
                                                           coarse_supervertices,
                                                           tree,
                                                           altitudes);

            return rag_back_project_weights(m_fine_rag.edge_map, coarse_sm_on_fine_rag);
        }

    private:
        region_adjacency_graph m_fine_rag;
    };

    template<typename graph_t, typename T>
    auto make_hierarchy_aligner_from_graph_cut(const graph_t &graph, const xt::xexpression<T> &saliency_map) {
        return hierarchy_aligner(make_region_adjacency_graph_from_graph_cut(graph, saliency_map));
    }

    template<typename graph_t, typename T>
    auto make_hierarchy_aligner_from_labelisation(const graph_t &graph, const xt::xexpression<T> &vertex_labels) {
        return hierarchy_aligner(make_region_adjacency_graph_from_labelisation(graph, vertex_labels));
    }

    template<typename graph_t, typename tree_t, typename T>
    auto make_hierarchy_aligner_from_hierarchy(const graph_t &graph, const tree_t &tree,
                                               const xt::xexpression<T> &altitudes) {
        return hierarchy_aligner(
                make_region_adjacency_graph_from_labelisation(graph, labelisation_hierarchy_supervertices(tree,
                                                                                                          altitudes)));
    }

}


<|start_filename|>test/cpp/algo/test_tree_fusion.cpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#include "../test_utils.hpp"
#include "higra/algo/tree_fusion.hpp"
#include "higra/hierarchy/component_tree.hpp"
#include "higra/image/graph_image.hpp"

using namespace hg;

namespace test_tree_fusion {

    TEST_CASE("tree_fusion_depth_map 1", "[tree_fusion]") {
        array_1d<int> p1{5, 5, 6, 6, 6, 7, 7, 7};
        array_1d<int> p2{7, 7, 6, 5, 5, 6, 7, 7};

        tree t1(p1);
        tree t2(p2);

        auto res = tree_fusion_depth_map(std::vector<tree *>{&t1, &t2});

        array_1d<int> expected{2, 2, 2, 3, 3};

        auto diff = xt::eval(expected - res);
        REQUIRE(xt::sum(diff - diff(0))() == 0);
    }

    TEST_CASE("tree_fusion_depth_map 2", "[tree_fusion]") {

        array_1d<int> p1{4, 4, 6, 5, 6, 6, 6};

        array_1d<int> p2{4, 5, 5, 5, 5, 5};

        auto g = get_4_adjacency_implicit_graph({1, 5});

        tree t1(p1);
        tree t2(p2);

        auto res = tree_fusion_depth_map(std::vector<tree *>{&t1, &t2});

        array_1d<int> expected{3, 2, 1, 2};

        auto diff = xt::eval(expected - res);
        REQUIRE(xt::sum(diff - diff(0))() == 0);
    }

    TEST_CASE("tree_fusion_depth_map 3", "[tree_fusion]") {

        array_1d<int> im1{0, 0, 0, 0, 0, 0, 0,
                          3, 3, 3, 2, 1, 1, 1,
                          3, 3, 3, 2, 1, 1, 1,
                          3, 3, 3, 2, 1, 1, 1,
                          2, 2, 2, 2, 1, 1, 1,
                          1, 1, 1, 1, 1, 0, 0};

        array_1d<int> im2{0, 0, 0, 0, 0, 0, 0,
                          0, 0, 0, 0, 0, 0, 0,
                          0, 2, 1, 1, 1, 2, 0,
                          0, 1, 1, 1, 1, 2, 0,
                          0, 0, 0, 0, 0, 0, 0,
                          0, 0, 0, 0, 0, 0, 0};

        auto g = get_4_adjacency_implicit_graph({6, 7});

        auto t1 = component_tree_max_tree(g, im1);
        auto t2 = component_tree_max_tree(g, im2);

        auto res = tree_fusion_depth_map(std::vector<tree *>{&t1.tree, &t2.tree});

        array_1d<int> expected{0, 0, 0, 0, 0, 0, 0,
                               3, 3, 3, 2, 1, 1, 1,
                               3, 4, 3, 2, 2, 3, 1,
                               3, 3, 3, 2, 2, 3, 1,
                               2, 2, 2, 2, 1, 1, 1,
                               1, 1, 1, 1, 1, 0, 0};

        auto diff = xt::eval(expected - res);
        REQUIRE(xt::sum(diff - diff(0))() == 0);
    }

    TEST_CASE("tree_fusion_depth_map 4", "[tree_fusion]") {
        array_1d<int> p1{8, 6, 6, 6, 7, 7, 7, 8, 8};
        array_1d<int> p2{9, 7, 8, 8, 8, 6, 8, 9, 9, 9};

        tree t1(p1);
        tree t2(p2);

        auto res = tree_fusion_depth_map(std::vector<tree *>{&t1, &t2});
        array_1d<int> expected{0, 3, 2, 2, 2, 3};

        auto diff = xt::eval(expected - res);
        REQUIRE(xt::sum(diff - diff(0))() == 0);
    }

}

<|start_filename|>include/higra/assessment/partition.hpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#pragma once

#include "../structure/array.hpp"
#include <vector>
#include <xtensor/xview.hpp>

namespace hg {

    enum class partition_measure {
        BCE,
        DHamming,
        DCovering
    };

    template<typename value_type=index_t, typename T1, typename T2>
    auto card_intersections(const xt::xexpression<T1> &xcandidate,
                            const xt::xexpression<T2> &xground_truths) {
        auto &candidate = xcandidate.derived_cast();
        auto &ground_truths = xground_truths.derived_cast();

        hg_assert_integral_value_type(candidate);
        hg_assert_integral_value_type(ground_truths);

        std::vector<array_2d<value_type>> result;
        auto num_regions_candidate = xt::amax(candidate)() + 1;

        auto compute = [&candidate, &result, num_regions_candidate](const auto &ground_truth) {
            hg_assert_same_shape(candidate, ground_truth);
            auto num_regions_ground_truth = xt::amax(ground_truth)() + 1;
            typename array_2d<value_type>::shape_type shape_result{(size_t) num_regions_candidate,
                                                                   (size_t) num_regions_ground_truth};
            result.emplace_back(shape_result, 0);
            auto &r = result.back();

            const auto &cf = xt::flatten(candidate);
            const auto &gtf = xt::flatten(ground_truth);
            for (index_t i = 0; i < (index_t)cf.size(); i++) {
                r(cf(i), gtf(i))++;
            }
        };

        if (xt::same_shape(candidate.shape(), ground_truths.shape())) {
            compute(ground_truths);
        } else {
            for (index_t i = 0; i < (index_t)ground_truths.shape()[0]; i++) {
                compute(xt::view(ground_truths, i));
            }
        }

        return result;
    }

    struct scorer_partition_BCE {
        template<typename T>
        static
        auto score(const xt::xexpression<T> &xcard_intersection) {
            auto card_intersection = xcard_intersection.derived_cast();
            auto candidate_regions_area = xt::sum(card_intersection, {1});

            double score = xt::sum(
                    card_intersection *
                    xt::minimum(card_intersection / xt::sum(card_intersection, {0}),
                                card_intersection / xt::view(candidate_regions_area, xt::all(), xt::newaxis())))();

            return score / xt::sum(candidate_regions_area)();
        }
    };

    struct scorer_partition_DHamming {
        template<typename T>
        static
        auto score(const xt::xexpression<T> &xcard_intersection) {
            auto card_intersection = xcard_intersection.derived_cast();

            return (xt::sum(xt::amax(card_intersection, {1}))() / xt::sum(card_intersection)());
        }
    };

    struct scorer_partition_DCovering {
        template<typename T>
        static
        auto score(const xt::xexpression<T> &xcard_intersection) {
            auto card_intersection = xcard_intersection.derived_cast();
            auto candidate_regions_area = xt::sum(card_intersection, {1});

            auto card_union = -card_intersection + xt::sum(card_intersection, {0}) +
                              xt::view(candidate_regions_area, xt::all(), xt::newaxis());

            double score = xt::eval(xt::sum(xt::amax(card_intersection / card_union, {1}) * candidate_regions_area))();

            return score / xt::sum(candidate_regions_area)();
        }
    };

    template<typename T, typename scorer_t>
    auto assess_partition(const std::vector<T> &card_intersections, const scorer_t &scorer) {
        double score = 0;
        for (const auto &card_intersection: card_intersections) {
            score += scorer.score(card_intersection);
        }
        return score / card_intersections.size();
    }

    template<typename T1, typename T2, typename scorer_t>
    auto assess_partition(const xt::xexpression<T1> &xcandidate,
                          const xt::xexpression<T2> &xground_truths,
                          const scorer_t &scorer) {
        auto card_intersections = hg::card_intersections<double>(xcandidate, xground_truths);
        return assess_partition(card_intersections, scorer);
    }

}


<|start_filename|>doc/source/_static/css/custom.css<|end_filename|>
/* override table width restrictions */
@media screen and (min-width: 767px) {

   .wy-table-responsive table td {
      /* !important prevents the common CSS stylesheets from overriding
         this as on RTD they are loaded after this stylesheet */
      white-space: normal !important;
   }

   .wy-table-responsive {
      overflow: visible !important;
   }
}

.wy-nav-content {
   max-width: 1200px !important;
}

.admonition-syntaxe {
   background-color: #EEEEEE;
}

/*
.activity {
	border: 1px;
	width: 80%;
	background-color: #EEEEEE;
	margin-left: auto;
	margin-right: auto;

}*/

.wy-alert-title, .rst-content .admonition-title {
   margin: 0px;
}

div.rst-content h1 {
   background-color: transparent;
   /* border-bottom: 1px solid #000a15;*/
   line-height: 1.5;
   margin: 50px 0px 20px -20px;
   text-shadow: 0px 1px 0 #b3b3b3;
   font-size: 300%;
}

div.rst-content h2 {
   background-color: transparent;
   border-bottom: 1px solid #000a15;
   line-height: 1.5;
   margin: 50px 0px 20px -20px;
   text-shadow: 0px 1px 0 #b3b3b3;
   font-size: 250%;
}

div.rst-content h3,
div.rst-content h4,
div.rst-content h5,
div.rst-content h6 {
   background-color: transparent;
   margin: 10px 0px 10px 0px;
   text-shadow: 0px 1px 0 #b3b3b3;
}

div.rst-content h3 {
   font-size: 150%;
   margin: 40px 0px 12px -10px;
}

p {
   margin-bottom: 12px;
}

.parhead {
   margin: 0 20px 0 0;
   font-size: 110%;
   font-weight: 700;
}

.wy-nav-side {
   position: fixed;
}

.rst-content pre.literal-block, .rst-content div[class^='highlight'] pre, .rst-content .linenodiv pre {
   font-family: Consolas, "Liberation Mono", "Courier New", Courier, monospace;
   font-size: 14px;
   line-height: 1.1;
}

<|start_filename|>include/higra/image/graph_image.hpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#pragma once

#include "../graph.hpp"
#include <stack>

namespace hg {

    /**
     * Create a 4 adjacency implicit regular graph for the given embedding
     * @param embedding
     * @return
     */
    inline
    auto get_4_adjacency_implicit_graph(const embedding_grid_2d &embedding) {
        std::vector<point_2d_i> neighbours{{{-1, 0}},
                                           {{0,  -1}},
                                           {{0,  1}},
                                           {{1,  0}}}; // 4 adjacency

        return regular_grid_graph_2d(embedding, std::move(neighbours));
    }

    /**
     * Create of 4 adjacency implicit regular graph for the given embedding
     * @param embedding
     * @return
     */
    inline
    auto get_8_adjacency_implicit_graph(const embedding_grid_2d &embedding) {
        std::vector<point_2d_i> neighbours{{{-1, -1}},
                                           {{-1, 0}},
                                           {{-1, 1}},
                                           {{0,  -1}},
                                           {{0,  1}},
                                           {{1,  -1}},
                                           {{1,  0}},
                                           {{1,  1}}}; // 8 adjacency

        return regular_grid_graph_2d(embedding, std::move(neighbours));
    }

    /**
     * Create of 4 adjacency explicit regular graph for the given embedding
     * @param embedding
     * @return
     */
    inline
    auto get_4_adjacency_graph(const embedding_grid_2d &embedding) {
        return hg::copy_graph<ugraph>(get_4_adjacency_implicit_graph(embedding));
    }

    /**
     * Create of 8 adjacency explicit regular graph for the given embedding
     * @param embedding
     * @return
     */
    inline
    auto get_8_adjacency_graph(const embedding_grid_2d &embedding) {
        return hg::copy_graph<ugraph>(get_8_adjacency_implicit_graph(embedding));
    }


    /**
     * Represents a 4 adjacency edge weighted regular graph in 2d Khalimsky space
     * @param embedding
     * @return
     */
    template<typename graph_t, typename T, typename result_type = typename T::value_type>
    auto
    graph_4_adjacency_2_khalimsky(const graph_t &graph, const embedding_grid_2d &embedding,
                          const xt::xexpression<T> &xedge_weights,
                          bool add_extra_border = false,
                          result_type extra_border_value = 0) {
        HG_TRACE();
        const auto &weight = xedge_weights.derived_cast();
        hg_assert_edge_weights(graph, weight);
        hg_assert_1d_array(weight);
        hg_assert(num_vertices(graph) == embedding.size(),
                  "Graph number of vertices does not match the size of the embedding.");
        auto &shape = embedding.shape();

        index_t border = (add_extra_border) ? 1 : -1;

        std::array<index_t, 2> res_shape{(index_t)shape[0] * 2 + border, (index_t)shape[1] * 2 + border};

        array_2d <result_type> res = xt::zeros<result_type>(res_shape);

        point_2d_i one{{1, 1}};
        for (auto e: edge_iterator(graph)) {
            auto s = source(e, graph);
            auto t = target(e, graph);
            if (t > s) {
                auto ti = embedding.lin2grid(t);
                auto si = embedding.lin2grid(s);
                if (add_extra_border)
                    res[ti + si + one] = weight(e);
                else
                    res[ti + si] = weight(e);
            }
        }

        embedding_grid_2d res_embedding(res_shape);
        auto adj4 = get_4_adjacency_implicit_graph(res_embedding);
        auto h = res_embedding.shape()[0];
        auto w = res_embedding.shape()[1];
        auto flat_res = xt::flatten(res);

        if (add_extra_border && extra_border_value != 0) {
            for (index_t x = 1; x < w; x += 2) {
                res(0, x) = extra_border_value;
                res(h - 1, x) = extra_border_value;
            }
            for (index_t y = 1; y < h; y += 2) {
                res(y, 0) = extra_border_value;
                res(y, w - 1) = extra_border_value;
            }
        }

        index_t ymin = (add_extra_border) ? 0 : 1;
        index_t ymax = (add_extra_border) ? h : h - 1;
        index_t xmin = (add_extra_border) ? 0 : 1;
        index_t xmax = (add_extra_border) ? w : w - 1;

        for (index_t y = ymin; y < ymax; y += 2) {
            for (index_t x = xmin; x < xmax; x += 2) {
                auto v = res_embedding.grid2lin({y, x});
                result_type max_v = std::numeric_limits<result_type>::lowest();
                for (auto av: adjacent_vertex_iterator(v, adj4)) {
                    max_v = (std::max)(max_v, flat_res(av));
                }
                res(y, x) = max_v;
            }
        }

        return res;
    };

    /**
     * Transforms a contour map represented in 2d Khalimsky space into a weighted 4 adjacency edge weighted regular graph
     * (0-face and 2-face of the Khalimsky space are ignored).
     * @param embedding
     * @return
     */
    template<typename T, typename result_type = typename T::value_type>
    auto
    khalimsky_2_graph_4_adjacency(const xt::xexpression<T> &xkhalimsky, bool extra_border = false) {
        HG_TRACE();
        const auto &khalimsky = xkhalimsky.derived_cast();
        hg_assert(khalimsky.dimension() == 2, "Only 2d khalimsky grids are supported!");

        auto &shape = khalimsky.shape();

        index_t border = (extra_border) ? 0 : 1;

        std::array<index_t, 2> res_shape{(index_t) shape[0] / 2 + border, (index_t) shape[1] / 2 + border};
        embedding_grid_2d res_embedding(res_shape);

        auto g = get_4_adjacency_graph(res_embedding);
        array_1d <result_type> weights = xt::zeros<result_type>({num_edges(g)});

        point_2d_i one{{1, 1}};
        for (auto e : edge_iterator(g)) {
            auto s = res_embedding.lin2grid(source(e, g));
            auto t = res_embedding.lin2grid(target(e, g));
            if (extra_border) {
                weights(e) = khalimsky[s + t + one];
            } else {
                weights(e) = khalimsky[s + t];
            }
        }

        return std::make_tuple(std::move(g), std::move(res_embedding), std::move(weights));
    };

}

<|start_filename|>include/higra/structure/details/light_axis_view.hpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#pragma once
#include "../../utils.hpp"

namespace hg {

    namespace details {


        /**
         * An efficient and simple view over the first axis of an xtensor container
         * (does not support overloaded operators, broadcasting and such fancy things).
         *
         * Mostly provide range functions (begin, end) and an assignement operator.
         *
         * The view must know at compile time if the underlying container has more than one dimension
         * (i.e. if the view contain a scalar or more elements) in order to perform compile time optimization.
         *
         * @tparam vectorial indicates if the underlying container has more than 1 dimension
         * @tparam T Underlying xtensor container
         */
        template<bool vectorial, typename T>
        struct light_axis_view {

            using value_type = typename T::value_type;
            using self_type = light_axis_view<vectorial, T>;

            static const bool is_vectorial = vectorial;

            template<typename T1 = self_type>
            light_axis_view(T &data, index_t position = 0, typename std::enable_if_t<T1::is_vectorial> * = 0) :
                    m_data(data),
                    m_stride(data.size() / data.shape()[0]),
                    m_position(position) {

            }

            template<typename T1 = self_type>
            light_axis_view(T &data, index_t position = 0, typename std::enable_if_t<!T1::is_vectorial> * = 0) :
                    m_data(data),
                    m_stride(1),
                    m_position(position) {
            }

            void set_position(index_t i) {
                m_position = i;
            }

            template<typename T1=self_type>
            auto begin(typename std::enable_if_t<T1::is_vectorial> * = 0) {
                return m_data.begin() + m_position * m_stride;
            }

            template<typename T1=self_type>
            auto begin(typename std::enable_if_t<!T1::is_vectorial> * = 0) {
                return m_data.begin() + m_position;
            }

            template<typename T1=self_type>
            auto end(typename std::enable_if_t<T1::is_vectorial> * = 0) {
                return m_data.begin() + (m_position + 1) * m_stride;
            }

            template<typename T1=self_type>
            auto end(typename std::enable_if_t<!T1::is_vectorial> * = 0) {
                return m_data.begin() + (m_position + 1);
            }

            template<typename T1=self_type>
            auto begin(typename std::enable_if_t<T1::is_vectorial> * = 0) const {
                return m_data.begin() + m_position * m_stride;
            }

            template<typename T1=self_type>
            auto begin(typename std::enable_if_t<!T1::is_vectorial> * = 0) const {
                return m_data.begin() + m_position;
            }

            template<typename T1=self_type>
            auto end(typename std::enable_if_t<T1::is_vectorial> * = 0) const {
                return m_data.begin() + (m_position + 1) * m_stride;
            }

            template<typename T1=self_type>
            auto end(typename std::enable_if_t<!T1::is_vectorial> * = 0) const {
                return m_data.storage_begin() + (m_position + 1);
            }

            template<bool vectorial2, typename T2>
            self_type &operator=(const light_axis_view<vectorial2, T2> &rhs) {
                static_assert(vectorial == vectorial2, "Mixing vectorial and non vectorial soft axis view!");
                assign(*this, rhs);
                return (*this);
            }

            self_type &operator=(const self_type &rhs) {
                assign(*this, rhs);
                return (*this);
            }

            template<bool vectorial2, typename T2, typename combinator_t, typename T1=self_type>
            std::enable_if_t<T1::is_vectorial> combine(const light_axis_view<vectorial2, T2> &rhs, combinator_t fun) {
                static_assert(T1::is_vectorial == vectorial2, "Mixing vectorial and non vectorial soft axis view!");
                auto r = rhs.begin();
                auto v = begin();
                for (; v != end(); v++, r++) {
                    *v = fun(*v, *r);
                }
            }

            template<bool vectorial2, typename T2, typename combinator_t, typename T1=self_type>
            std::enable_if_t<!T1::is_vectorial> combine(const light_axis_view<vectorial2, T2> &rhs, combinator_t fun) {
                static_assert(T1::is_vectorial == vectorial2, "Mixing vectorial and non vectorial soft axis view!");
                *begin() = fun(*begin(), *(rhs.begin()));
            }


        private:

            template<typename T1, typename T2>
            std::enable_if_t<T1::is_vectorial> assign(T1 &lhs, const T2 &rhs) {
                static_assert(T1::is_vectorial == T2::is_vectorial,
                              "Mixing vectorial and non vectorial soft axis view!");
                auto r = rhs.begin();
                auto v = lhs.begin();
                for (; v != end(); v++, r++) {
                    *v = *r;
                }
            }

            template<typename T1, typename T2>
            std::enable_if_t<!T1::is_vectorial> assign(T1 &lhs, const T2 &rhs) {
                static_assert(T1::is_vectorial == T2::is_vectorial,
                              "Mixing vectorial and non vectorial soft axis view!");
                *lhs.begin() = *rhs.begin();
            }

            T &m_data;
            size_t m_stride;
            index_t m_position;
        };
    }

    template<bool vectorial = true, typename T>
    auto make_light_axis_view(T &e, index_t position = 0) {
        return details::light_axis_view<vectorial, T>(e, position);
    }

}

<|start_filename|>higra/py_common.hpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#pragma once
#include "xtl/xmeta_utils.hpp"
#include "pybind11/stl.h"
#include "pybind11/functional.h"
#include "higra/utils.hpp"
#include <functional>


template <typename F, typename T, typename module_t, typename... Args>
void add_type_overloads(module_t & m, const char * doc, Args&&... args){
    F::template def<T>(m, doc, std::forward<Args>(args)...);
};

template <typename F, typename T1, typename... Ts, typename module_t, typename... Args>
typename std::enable_if<sizeof...(Ts) != 0>::type
add_type_overloads(module_t & m, const char * doc, Args&&... args){
    F::template def<T1>(m, doc, std::forward<Args>(args)...);
    add_type_overloads<F, Ts...>(m, "", std::forward<Args>(args)...);
};



<|start_filename|>higra/algo/py_horizontal_cuts.cpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#include "py_horizontal_cuts.hpp"
#include "../py_common.hpp"
#include "higra/algo/horizontal_cuts.hpp"
#include "xtensor-python/pyarray.hpp"
#include "xtensor-python/pytensor.hpp"

using namespace hg;
namespace py = pybind11;


template<typename c_t, typename tree_t>
struct def_reconstruct_leaf_data {
    template<typename type, typename C>
    static
    void def(C &c, const char *doc) {
        c.def("_reconstruct_leaf_data", [](const c_t &c, const tree_t &tree, const xt::pyarray<type> &data) {
                  return c.reconstruct_leaf_data(tree, data);
              },
              doc,
              py::arg("tree"),
              py::arg("altitudes"));
    }
};

template<typename tree_t>
void def_horizontal_cut_nodes(pybind11::module &m) {
    using class_t = hg::horizontal_cut_nodes<double>;
    auto c = py::class_<class_t>(m, "HorizontalCutNodes",
                                 R"""(Represents an horizontal cut in a hierarchy as a set of nodes.)""");
    c.def("nodes",
          [](const class_t &c) -> const array_1d<index_t> & { return c.nodes; },
          "Array containing the indices of the nodes of the cut.");
    c.def("altitude",
          [](const class_t &c) { return c.altitude; },
          "Altitude of the cut.");
    c.def("_labelisation_leaves",
          [](const class_t &c, const tree_t &tree) { return c.labelisation_leaves(tree); },
          "Labelize tree leaves according to the horizontal cut. \n"
          "Two leaves are in the same region (ie. have the same label) if "
          "their lowest common ancestor is a subset or equal to one the node of the cut.",
          py::arg("tree"));
    c.def("_graph_cut",
          [](const class_t &c, const tree_t &tree, const hg::ugraph &leaf_graph) {
              return c.graph_cut(tree, leaf_graph);
          },
          "",
          py::arg("tree"),
          py::arg("leaf_graph"));
    add_type_overloads<def_reconstruct_leaf_data<class_t, tree_t>, HG_TEMPLATE_NUMERIC_TYPES>
            (c,
             "Each leaf of the tree takes the altitude of the node of the cut that contains it."
            );
}

template<typename c_t>
struct def_horizontal_cut_explorer_ctr {
    template<typename type, typename C>
    static
    void def(C &c, const char *doc) {
        c.def_static("_make_HorizontalCutExplorer",
                [](const typename c_t::tree_type &tree, const xt::pyarray<type> &altitudes) {
                    return c_t(tree, altitudes);
                },
              doc,
              py::arg("tree"),
              py::arg("altitudes"));
    }
};

template<typename tree_t>
void def_horizontal_cut_explorer(pybind11::module &m) {
    using class_t = hg::horizontal_cut_explorer<tree_t, double>;
    auto c = py::class_<class_t>(
            m,
            "HorizontalCutExplorer",
            R"""(This class helps to explore and to browse the horizontal cuts of a valued hierarchy.
Construction of the HorizontalCutExplorer if performed in linear time :math:`\mathcal{O}(n)` w.r.t. the number of nodes in the tree.
Each cut of the hierarchy can be accessed through:

  - its index (the first single region cut has index 0). This operations runs in :math:`\mathcal{O}(k)`, with :math:`k` the number of regions in the retrieved cut ;
  - the number of regions in the cut (the smallest partition having at least the given number of regions if found). This operations runs in :math:`\mathcal{O}(k*\log(n))`, with :math:`k` the number of regions in the retrieved cut;
  - the altitude of the cut. This operations runs in :math:`\mathcal{O}(k*\log(n))`, with :math:`k` the number of regions in the retrieved cut.)"""
    );
    add_type_overloads<def_horizontal_cut_explorer_ctr<class_t>, HG_TEMPLATE_NUMERIC_TYPES>
            (c,"");
    c.def("num_cuts", &class_t::num_cuts, "Number of horizontal cuts in the hierarchy.");
    c.def("num_regions_cut",
          &class_t::num_regions_cut,
          "Number of regions in the i-th cut of the hierarchy (cut numbering start at 0 with the cut with a single region).",
          py::arg("i"));
    c.def("num_regions_cuts",
          &class_t::num_regions_cuts,
          "Number of regions in each cut of the hierarchy.");
    c.def("altitude_cut",
          [](const class_t &c, index_t i) {
              hg_assert(i >= 0, "Cut index cannot be negative.");
              hg_assert(i < (index_t)c.num_cuts(), "Cut index out of bounds.");
              return c.altitude_cut(i);
          },
          "Altitude of the i-th cut of the hierarchy (cut numbering start at 0 with the cut with a single region).",
          py::arg("cut_index"));
    c.def("altitude_cuts",
          &class_t::altitude_cuts,
          "Altitude of each cut of the hierarchy.");
    c.def("horizontal_cut_from_index",
          [](const class_t &c, index_t i) {
              hg_assert(i >= 0, "Cut index cannot be negative.");
              hg_assert(i < (index_t)c.num_cuts(), "Cut index out of bounds.");
              return c.horizontal_cut_from_index(i);
          },
          "Retrieve the i-th horizontal cut of tree (cut numbering start at 0 with the cut with a single region).",
          py::arg("i"));
    c.def("horizontal_cut_from_altitude",
          &class_t::horizontal_cut_from_altitude,
          "Retrieve the horizontal cut for given threshold level.",
          py::arg("threshold"));
    c.def("horizontal_cut_from_num_regions",
          &class_t::horizontal_cut_from_num_regions,
          R"""(Horizontal cut with a given number of regions.

If :attr:`at_least` is ``True`` (default), the the smallest horizontal cut having at least the given number of
regions is returned.
If :attr:`at_least` is ``False``, the the largest horizontal cut having at most the given number of
regions is returned.)""",
          py::arg("num_regions"),
          py::arg("at_least") = true);
}

void py_init_horizontal_cuts(pybind11::module &m) {
    xt::import_numpy();

    def_horizontal_cut_nodes<hg::tree>(m);
    def_horizontal_cut_explorer<hg::tree>(m);
}



<|start_filename|>tools/higraTools.cmake<|end_filename|>
function(REGISTER_PYTHON_MODULE_FILES PYTHON_FILES)
    foreach (FILE ${PYTHON_FILES})
        configure_file(${FILE} ${CMAKE_CURRENT_BINARY_DIR}/${FILE} COPYONLY)
    endforeach ()
endfunction(REGISTER_PYTHON_MODULE_FILES)


<|start_filename|>include/higra/image/tree_of_shapes.hpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#pragma once

#include "higra/graph.hpp"
#include "higra/image/graph_image.hpp"
#include "higra/hierarchy/component_tree.hpp"
#include "higra/hierarchy/hierarchy_core.hpp"
#include "higra/accumulator/tree_accumulator.hpp"
#include "xtensor/xview.hpp"
#include "xtensor/xnoalias.hpp"
#include "xtensor/xindex_view.hpp"

#include <map>
#include <deque>

namespace hg {

    namespace tree_of_shapes_internal {

        /**
         * A simple multi-level priority queue with fixed number of integer levels in [min_level, nax_level].
         *
         * All operations are done in constant time, except:
         * - constructor, and
         * - find_closest_non_empty_level
         * which both run in O(num_levels = max_level - min_level + 1).
         *
         * @paramt value_t type of sored values
         */
        template<typename level_t, typename value_t>
        struct integer_level_multi_queue {
            using value_type = value_t;
            using level_type = level_t;

            /**
             * Create a queue with the given number of levels
             * @param num_levels queue will have integer levels in [0, num_levels[
             */
            integer_level_multi_queue(level_type min_level, level_type max_level) :
                    m_min_level(min_level),
                    m_max_level(max_level),
                    m_num_levels(max_level - min_level + 1),
                    m_data(m_num_levels) {
            }

            auto min_level() const {
                return m_min_level;
            }

            auto max_level() const {
                return m_max_level;
            }

            /**
             *
             * @return number of levels in the queue
             */
            auto num_levels() const {
                return m_num_levels;
            }

            /**
             *
             * @return number of elements in the queue
             */
            auto size() const {
                return m_size;
            }

            /**
             *
             * @return true if the queue is empty
             */
            auto empty() const {
                return m_size == 0;
            }

            /**
             *
             * @param level in [min_level, max_level]
             * @return true if the given level of the queue is empty
             */
            auto level_empty(level_type level) const {
                return m_data[level - min_level()].size() == 0;
            }

            /**
             * Add a new element to the given level of the queue
             * @param level in [min_level, max_level]
             * @param v new element
             */
            void push(level_type level, value_type v) {
                m_data[level - min_level()].push_back(v);
                m_size++;
            }

            /**
             * Return a reference to the last element of the given queue level
             * @param level in [min_level, max_level]
             * @return a reference to a value_type element
             */
            auto &top(level_type level) {
                return m_data[level - min_level()].front();
            }

            /**
            * Return a const reference to the last element of the given queue level
            * @param level in [min_level, max_level]
            * @return a const reference to a value_type element
            */
            const auto &top(level_type level) const {
                return m_data[level - min_level()].front();
            }

            /**
             * Removes the last element of the given queue level
             * @param level in [min_level, max_level]
             */
            void pop(level_type level) {
                m_data[level - min_level()].pop_front();
                m_size--;
            }

            /**
             * Given a queue level, find the closest non empty level in the queue.
             * In case of equality the smallest level is returned.
             *
             * @param level in [min_level, max_level]
             * @return a queue level or hg::invalid_index if the queue is empty
             */
            auto find_closest_non_empty_level(level_type level) const {
                if (!level_empty(level)) {
                    return level;
                }

                level_type level_low = level;
                level_type level_high = level;
                bool flag_low = true;
                bool flag_high = true;

                while (flag_low || flag_high) {
                    if (flag_low) {
                        if (!level_empty(level_low)) {
                            return level_low;
                        }
                        if (level_low == m_min_level) {
                            flag_low = false;
                        } else {
                            level_low--;
                        }
                    }
                    if (flag_high) {
                        if (!level_empty(level_high)) {
                            return level_high;
                        }
                        if (level_high == m_max_level) {
                            flag_high = false;
                        } else {
                            level_high++;
                        }
                    }
                }
                throw std::runtime_error("Empty queue!");
                return (level_type) 0;
            }

        private:
            level_t m_min_level;
            level_t m_max_level;
            index_t m_num_levels;
            std::vector<std::deque<value_type>> m_data;
            index_t m_size = 0;
        };

        template<typename T, typename value_type=typename T::value_type>
        auto interpolate_plain_map_khalimsky_2d(const xt::xexpression<T> &ximage, const embedding_grid_2d &embedding) {
            auto &image = ximage.derived_cast();
            size_t h = embedding.shape()[0];
            size_t w = embedding.shape()[1];
            size_t h2 = h * 2 - 1;
            size_t w2 = w * 2 - 1;

            array_2d<value_type> plain_map = array_2d<value_type>::from_shape({h2 * w2, 2});
            const auto image2d = xt::reshape_view(image, {h, w});
            auto plain_map2d = xt::reshape_view(plain_map, {h2, w2, (size_t) 2});

            // 2 faces
            xt::noalias(xt::view(plain_map2d, xt::range(0, h2, 2), xt::range(0, w2, 2), 0)) = image2d;
            xt::noalias(xt::view(plain_map2d, xt::range(0, h2, 2), xt::range(0, w2, 2), 1)) = image2d;

            // horizontal 1 face
            xt::noalias(xt::view(plain_map2d, xt::range(0, h2, 2), xt::range(1, w2, 2), 0)) =
                    xt::minimum(xt::view(image2d, xt::all(), xt::range(0, w - 1)),
                                xt::view(image2d, xt::all(), xt::range(1, w)));
            xt::noalias(xt::view(plain_map2d, xt::range(0, h2, 2), xt::range(1, w2, 2), 1)) =
                    xt::maximum(xt::view(image2d, xt::all(), xt::range(0, w - 1)),
                                xt::view(image2d, xt::all(), xt::range(1, w)));

            // vertical 1 face
            xt::noalias(xt::view(plain_map2d, xt::range(1, h2, 2), xt::range(0, w2, 2), 0)) =
                    xt::minimum(xt::view(image2d, xt::range(0, h - 1), xt::all()),
                                xt::view(image2d, xt::range(1, h), xt::all()));
            xt::noalias(xt::view(plain_map2d, xt::range(1, h2, 2), xt::range(0, w2, 2), 1)) =
                    xt::maximum(xt::view(image2d, xt::range(0, h - 1), xt::all()),
                                xt::view(image2d, xt::range(1, h), xt::all()));

            // 0 face
            xt::noalias(xt::view(plain_map2d, xt::range(1, h2, 2), xt::range(1, w2, 2), 0)) =
                    xt::minimum(
                            xt::minimum(xt::view(image2d, xt::range(0, h - 1), xt::range(0, w - 1)),
                                        xt::view(image2d, xt::range(0, h - 1), xt::range(1, w))),
                            xt::minimum(xt::view(image2d, xt::range(1, h), xt::range(0, w - 1)),
                                        xt::view(image2d, xt::range(1, h), xt::range(1, w))));
            xt::noalias(xt::view(plain_map2d, xt::range(1, h2, 2), xt::range(1, w2, 2), 1)) =
                    xt::maximum(
                            xt::maximum(xt::view(image2d, xt::range(0, h - 1), xt::range(0, w - 1)),
                                        xt::view(image2d, xt::range(0, h - 1), xt::range(1, w))),
                            xt::maximum(xt::view(image2d, xt::range(1, h), xt::range(0, w - 1)),
                                        xt::view(image2d, xt::range(1, h), xt::range(1, w))));
            return plain_map;
        }

        template<typename graph_t,
                typename T,
                typename value_type = typename T::value_type,
                typename std::enable_if_t<sizeof(value_type) <= 2 && std::is_integral<value_type>::value, int> = 0>
        auto sort_vertices_tree_of_shapes(const graph_t &graph,
                                          const xt::xexpression<T> &xplain_map, index_t exterior_vertex = 0) {

            auto &plain_map = xplain_map.derived_cast();
            auto num_v = num_vertices(graph);
            array_1d<bool> dejavu({num_v}, false);
            array_1d<index_t> sorted_vertex_indices = array_1d<index_t>::from_shape({num_v});
            array_1d<value_type> enqueued_level = array_1d<value_type>::from_shape({num_v});
            integer_level_multi_queue<value_type, index_t> queue(xt::amin(plain_map)(), xt::amax(plain_map)());

            value_type current_level = (value_type) ((plain_map(exterior_vertex, 0) + plain_map(exterior_vertex, 1)) /
                                                     2.0);
            queue.push(current_level, exterior_vertex);
            dejavu(exterior_vertex) = true;

            index_t i = 0;
            while (!queue.empty()) {
                current_level = queue.find_closest_non_empty_level(current_level);
                auto current_point = queue.top(current_level);
                queue.pop(current_level);
                enqueued_level(current_point) = current_level;
                sorted_vertex_indices(i++) = current_point;
                for (auto n: adjacent_vertex_iterator(current_point, graph)) {
                    if (!dejavu(n)) {
                        auto newLevel = (std::min)(plain_map(n, 1), (std::max)(plain_map(n, 0), current_level));
                        queue.push(newLevel, n);
                        dejavu(n) = true;
                    }
                }

            }
            return std::make_pair(std::move(sorted_vertex_indices), std::move(enqueued_level));
        }

        template<typename graph_t,
                typename T,
                typename value_type = typename T::value_type,
                typename std::enable_if_t<3 <= sizeof(value_type) || !std::is_integral<value_type>::value, int> = 0>
        auto sort_vertices_tree_of_shapes(const graph_t &graph,
                                          const xt::xexpression<T> &xplain_map, index_t exterior_vertex = 0) {

            auto &plain_map = xplain_map.derived_cast();
            hg_assert(plain_map.dimension() == 2, "Invalid plain map");
            hg_assert(plain_map.shape()[1] == 2, "Invalid plain map");
            hg_assert_vertex_weights(graph, plain_map);
            auto num_v = num_vertices(graph);
            array_1d<bool> dejavu({num_v}, false);
            array_1d<index_t> sorted_vertex_indices = array_1d<index_t>::from_shape({num_v});
            array_1d<value_type> enqueued_level = array_1d<value_type>::from_shape({num_v});

            std::multimap<value_type, index_t> queue{};
            auto find_closest_non_empty_level = [&queue](const auto position) {
                auto next = std::next(position);
                if (position == queue.begin()) {
                    return next;
                }
                auto prev = std::prev(position);
                if (next == queue.end()) {
                    return prev;
                }
                if (next->first - position->first < position->first - prev->first) {
                    return next;
                } else {
                    return prev;
                }
            };

            value_type current_level = (value_type) ((plain_map(exterior_vertex, 0) + plain_map(exterior_vertex, 1)) /
                                                     2.0);

            auto position = queue.insert({current_level, exterior_vertex});
            dejavu(exterior_vertex) = true;

            index_t i = 0;
            do {
                current_level = position->first;
                index_t current_point = position->second;

                enqueued_level(current_point) = current_level;
                sorted_vertex_indices(i++) = current_point;
                for (auto n: adjacent_vertex_iterator(current_point, graph)) {
                    if (!dejavu(n)) {
                        auto newLevel = (std::min)(plain_map(n, 1), (std::max)(plain_map(n, 0), current_level));
                        queue.insert({newLevel, n});
                        dejavu(n) = true;
                    }
                }

                auto new_position = find_closest_non_empty_level(position);
                queue.erase(position);
                position = new_position;
            } while (!queue.empty());
            return std::make_pair(std::move(sorted_vertex_indices), std::move(enqueued_level));
        }

    }

    /**
     * Padding mode for the function component_tree_tree_of_shapes
     */
    enum tos_padding {
        none,
        mean,
        zero
    };

    /**
     * Computes the tree of shapes of a 2d image.
     * The Tree of Shapes was described in [1].
     *
     * The algorithm used in this implementation was first described in [2].
     *
     * The tree is computed in the interpolated multivalued Khalimsky space to provide a continuous and autodual representation of
     * input image.
     *
     * If padding is different from tos_padding::none, an extra border of pixels is added to the input image before
     * anything else. This will ensure the existence of a shape encompassing all the shapes inside the input image
     * (if exterior_vertex is inside the extra border): this shape will be the root of the tree.
     * The padding value can be:
     *   - 0 is padding == tos_padding::zero
     *   - the mean value of the boundary pixels of the input image if padding == tos_padding::mean
     *
     * If original_size is true, all the nodes corresponding to pixels not belonging to the input image are removed
     * (except for the root node).
     * If original_size is false, the returned tree is the tree constructed in the interpolated/padded space.
     * In practice if the size of the input image is (h, w), the leaves of the returned tree will correspond to an image of size:
     *   - (h, w) if original_size is true;
     *   - (h * 2 - 1, w * 2 - 1) is original_size is false and padding is tos_padding::none; and
     *   - ((h + 2) * 2 - 1, (w + 2) * 2 - 1) otherwise.
     *
     * :Advanced options:
     * 
     * Use with care the following options may lead to unexpected results:
     * 
     * Immersion defines if the initial image should be first converted as an equivalent continuous representation called a
     * plain map. If the immersion is deactivated the level lines of the shapes of the image may intersect (if the image is not
     * well composed) and the result of the algorithm is undefined. If immersion is deactivated, the factor :math:`*2 - 1`
     * has to be removed in the result sizes given above.
     * 
     * Exterior_vertex defines the linear coordinates of the pixel corresponding to the exterior (interior and exterior
     * of a shape is defined with respect to this point). The coordinate of this point must be given in the
     * padded/interpolated space.
     *
     * [1] <NAME>, and <NAME>, "Fast computation of a contrast-invariant image representation,"
     *     Image Processing, IEEE Transactions on, vol.9, no.5, pp.860-872, May 2000
     *
     * [2] <NAME>, <NAME>, <NAME>, and <NAME>, "A Quasi-linear Algorithm to Compute the Tree
     *     of Shapes of nD Images", ISMM 2013.
     *
     * @tparam T
     * @param ximage Must be a 2d array
     * @param padding Defines if an extra boundary of pixels is added to the original image (see enum tos_padding).
     * @param original_size remove all nodes corresponding to interpolated/padded pixels
     * @param exterior_vertex linear coordinate of the exterior point
     * @return a node weighted tree
     */
    template<typename T>
    auto component_tree_tree_of_shapes_image2d(const xt::xexpression<T> &ximage,
                                               tos_padding padding = tos_padding::mean,
                                               bool original_size = true,
                                               bool immersion = true,
                                               index_t exterior_vertex = 0) {
        HG_TRACE();
        auto &image = ximage.derived_cast();
        hg_assert(image.dimension() == 2, "image must be a 2d array");
        embedding_grid_2d embedding(image.shape());
        auto shape = embedding.shape();
        size_t h = shape[0];
        size_t w = shape[1];
        auto vertex_weights = xt::flatten(image);
        using value_type = typename T::value_type;

        size_t rh;
        size_t rw;

        array_2d<value_type> cooked_vertex_values;

        auto do_padding = [&padding, &h, &w](const auto &image) {
            value_type pad_value;
            switch (padding) {
                case tos_padding::zero:
                    pad_value = 0;
                    break;
                case tos_padding::mean: {
                    auto tmp = xt::sum(xt::view(image, 0, xt::all()))() +
                               xt::sum(xt::view(image, h - 1, xt::all()))();
                    if (h > 2) {
                        tmp += xt::sum(xt::view(image, xt::range(1, h - 1), 0))() +
                               xt::sum(xt::view(image, xt::range(1, h - 1), w - 1))();
                    }
                    pad_value = (value_type) (tmp / ((std::max)(2.0 * (w + h) - 4, 1.0)));
                    break;
                }
                case none:
                default:
                    throw std::runtime_error("Incorrect padding value.");
            }
            array_1d<value_type> padded_vertices = array_1d<value_type>::from_shape({(w + 2) * (h + 2)});
            auto padded_image = xt::reshape_view(padded_vertices, {h + 2, w + 2});

            xt::noalias(xt::view(padded_image, xt::range(1, h + 1), xt::range(1, w + 1))) = image;
            xt::view(padded_image, 0, xt::all()) = pad_value;
            xt::view(padded_image, h + 1, xt::all()) = pad_value;
            xt::view(padded_image, xt::all(), 0) = pad_value;
            xt::view(padded_image, xt::all(), w + 1) = pad_value;
            return padded_vertices;
        };

        auto process_sorted_pixels = [&original_size, &padding, &rh, &rw, &immersion](auto &graph,
                                                                                      auto &sorted_vertex_indices,
                                                                                      auto &enqueued_levels) {
            auto res_tree = component_tree_internal::tree_from_sorted_vertices(graph, enqueued_levels,
                                                                               sorted_vertex_indices);
            auto &tree = res_tree.tree;
            auto &altitudes = res_tree.altitudes;

            if (!original_size || (!immersion && padding == tos_padding::none)) {
                return res_tree;
            }

            array_1d<bool> deleted_vertices({num_leaves(res_tree.tree)}, true);
            auto deleted = xt::reshape_view(deleted_vertices, {rh, rw});
            if (immersion) {
                if (padding != tos_padding::none) {
                    xt::view(deleted, xt::range(2, rh - 2, 2), xt::range(2, rw - 2, 2)) = false;
                } else {
                    xt::view(deleted, xt::range(0, rh, 2), xt::range(0, rw, 2)) = false;
                }
            } else {
                if (padding != tos_padding::none) {
                    xt::view(deleted, xt::range(1, rh - 1), xt::range(1, rw - 1)) = false;
                } // else handled by bypass if on top
            }

            auto all_deleted = accumulate_sequential(tree, deleted_vertices, accumulator_min());

            auto stree = simplify_tree(tree, all_deleted, true);
            array_1d<value_type> saltitudes = xt::index_view(altitudes, stree.node_map);
            return make_node_weighted_tree(std::move(stree.tree), std::move(saltitudes));
        };

        if (immersion) {
            if (padding != tos_padding::none) {


                auto cooked_vertex_values =
                        tree_of_shapes_internal::interpolate_plain_map_khalimsky_2d(
                                do_padding(image),
                                {(index_t) (h + 2), (index_t) (w + 2)});
                rh = (h + 2) * 2 - 1;
                rw = (w + 2) * 2 - 1;
                auto graph = get_4_adjacency_implicit_graph({(index_t) rh, (index_t) rw});
                auto res_sort = tree_of_shapes_internal::sort_vertices_tree_of_shapes(graph, cooked_vertex_values,
                                                                                      exterior_vertex);
                return process_sorted_pixels(graph, res_sort.first, res_sort.second);
            } else {
                auto cooked_vertex_values =
                        tree_of_shapes_internal::interpolate_plain_map_khalimsky_2d(
                                vertex_weights,
                                embedding);
                rh = h * 2 - 1;
                rw = w * 2 - 1;
                auto graph = get_4_adjacency_implicit_graph({(index_t) rh, (index_t) rw});
                auto res_sort = tree_of_shapes_internal::sort_vertices_tree_of_shapes(graph, cooked_vertex_values,
                                                                                      exterior_vertex);
                return process_sorted_pixels(graph, res_sort.first, res_sort.second);
            }
        } else {
            if (padding != tos_padding::none) {
                auto padded_vertices = do_padding(image);

                rh = h + 2;
                rw = w + 2;
                auto graph = get_4_adjacency_implicit_graph({(index_t) rh, (index_t) rw});

                // clearly not optimal
                array_2d<value_type> plain_map = array_2d<value_type>::from_shape({rh * rw, 2});
                xt::noalias(xt::view(plain_map, xt::all(), 0)) = padded_vertices;
                xt::noalias(xt::view(plain_map, xt::all(), 1)) = padded_vertices;
                auto res_sort = tree_of_shapes_internal::sort_vertices_tree_of_shapes(graph, plain_map,
                                                                                      exterior_vertex);
                return process_sorted_pixels(graph, res_sort.first, res_sort.second);
            } else {
                rh = h;
                rw = w;
                auto graph = get_4_adjacency_implicit_graph({(index_t) rh, (index_t) rw});
                array_2d<value_type> plain_map = array_2d<value_type>::from_shape({rh * rw, 2});
                xt::noalias(xt::view(plain_map, xt::all(), 0)) = xt::ravel(image);
                xt::noalias(xt::view(plain_map, xt::all(), 1)) = xt::ravel(image);
                auto res_sort = tree_of_shapes_internal::sort_vertices_tree_of_shapes(graph, plain_map,
                                                                                      exterior_vertex);
                return process_sorted_pixels(graph, res_sort.first, res_sort.second);
            }

        }


    }
};


<|start_filename|>include/higra/algo/rag.hpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#pragma once

#include "xtensor/xsort.hpp"

#include "../graph.hpp"
#include "../accumulator/at_accumulator.hpp"


namespace hg {

    /**
     * Result of the region adjacency graph (rag) construction algorithm
     */
    struct region_adjacency_graph {
        /**
         * The region adjacency graph
         */
        ugraph rag;

        /**
         * An array indicating for each vertex of the original graph, the corresponding vertex of the rag
         */
        array_1d<index_t> vertex_map;

        /**
         * An array indicating for each edge of the original graph, the corresponding edge of the rag.
         * An edge with no corresponding edge in the rag (edge within a region) is indicated with the value invalid_index.
         */
        array_1d<index_t> edge_map;
    };

    /**
     * Construct a region adjacency graph from a vertex labeled graph in linear time.
     * @tparam graph_t
     * @tparam T
     * @param graph
     * @param xvertex_labels
     * @return see struct region_adjacency_graph
     */
    template<typename graph_t, typename T>
    auto
    make_region_adjacency_graph_from_labelisation(const graph_t &graph, const xt::xexpression<T> &xvertex_labels) {
        HG_TRACE();
        auto &vertex_labels = xvertex_labels.derived_cast();
        hg_assert_vertex_weights(graph, vertex_labels);
        hg_assert_1d_array(vertex_labels);
        hg_assert_integral_value_type(vertex_labels);

        ugraph rag;

        array_1d<index_t> vertex_map({num_vertices(graph)}, invalid_index);
        array_1d<index_t> edge_map({num_edges(graph)}, invalid_index);

        index_t num_regions = 0;
        index_t num_edges = 0;

        std::vector<index_t> canonical_edge_indexes;

        stackv<index_t> s;

        auto explore_component =
                [&s, &graph, &vertex_labels, &rag, &vertex_map, &edge_map, &num_regions, &num_edges, &canonical_edge_indexes]
                        (index_t start_vertex) {

                    auto label_region = vertex_labels[start_vertex];
                    s.push(start_vertex);
                    vertex_map[start_vertex] = num_regions;
                    canonical_edge_indexes.push_back(-1);
                    add_vertex(rag);
                    auto lowest_edge = num_edges;
                    while (!s.empty()) {
                        auto v = s.top();
                        s.pop();

                        for (auto e: out_edge_iterator(v, graph)) {
                            auto adjv = target(e, graph);
                            if (vertex_labels[adjv] == label_region) {
                                if (vertex_map[adjv] == invalid_index) {
                                    vertex_map[adjv] = num_regions;
                                    s.push(adjv);
                                }
                            } else {
                                if (vertex_map[adjv] != invalid_index) {
                                    auto num_region_adjacent = vertex_map[adjv];
                                    if (canonical_edge_indexes[num_region_adjacent] < lowest_edge) {
                                        add_edge(num_region_adjacent, num_regions, rag);
                                        edge_map(e) = num_edges;
                                        canonical_edge_indexes[num_region_adjacent] = num_edges;
                                        num_edges++;
                                    } else {
                                        edge_map(e) = canonical_edge_indexes[num_region_adjacent];
                                    }
                                }
                            }
                        }
                    }
                    num_regions++;
                };


        for (auto v: vertex_iterator(graph)) {
            if (vertex_map[v] != invalid_index)
                continue;
            explore_component(v);

        }

        return region_adjacency_graph{std::move(rag), std::move(vertex_map), std::move(edge_map)};
    }

    /**
     * Construct a region adjacency graph from a graph cut in linear time.
     * Any edge with weight different from 0 belongs to the cut.
     *
     * TODO factorize method with  make_region_adjacency_graph_from_labelisation
     *
     * @tparam graph_t
     * @tparam T
     * @param graph
     * @param xedge_weights
     * @return see struct region_adjacency_graph
     */
    template<typename graph_t, typename T>
    auto
    make_region_adjacency_graph_from_graph_cut(const graph_t &graph, const xt::xexpression<T> &xedge_weights) {
        HG_TRACE();
        auto &edge_weights = xedge_weights.derived_cast();
        hg_assert_edge_weights(graph, edge_weights);
        hg_assert_1d_array(edge_weights);

        ugraph rag;

        array_1d<index_t> vertex_map({num_vertices(graph)}, invalid_index);
        array_1d<index_t> edge_map({num_edges(graph)}, invalid_index);

        index_t num_regions = 0;
        index_t num_edges = 0;

        std::vector<index_t> canonical_edge_indexes;

        stackv<index_t> s;

        auto explore_component =
                [&s, &graph, &edge_weights, &rag, &vertex_map, &edge_map, &num_regions, &num_edges, &canonical_edge_indexes]
                        (index_t start_vertex) {
                    s.push(start_vertex);
                    vertex_map[start_vertex] = num_regions;
                    add_vertex(rag);
                    canonical_edge_indexes.push_back(-1);
                    auto lowest_edge = num_edges;
                    while (!s.empty()) {
                        auto v = s.top();
                        s.pop();

                        for (auto e: out_edge_iterator(v, graph)) {
                            auto adjv = target(e, graph);
                            if (edge_weights(index(e, graph)) == 0) {
                                if (vertex_map[adjv] == invalid_index) {
                                    vertex_map[adjv] = num_regions;
                                    s.push(adjv);
                                }
                            } else {
                                if (vertex_map[adjv] != invalid_index) {
                                    auto num_region_adjacent = vertex_map[adjv];
                                    if (canonical_edge_indexes[num_region_adjacent] < lowest_edge) {
                                        add_edge(num_region_adjacent, num_regions, rag);
                                        edge_map(e) = num_edges;
                                        canonical_edge_indexes[num_region_adjacent] = num_edges;
                                        num_edges++;
                                    } else {
                                        edge_map(e) = canonical_edge_indexes[num_region_adjacent];
                                    }
                                }
                            }
                        }
                    }
                    num_regions++;
                };


        for (auto v: vertex_iterator(graph)) {
            if (vertex_map[v] != invalid_index)
                continue;
            explore_component(v);

        }

        return region_adjacency_graph{std::move(rag), std::move(vertex_map), std::move(edge_map)};
    }

    namespace rag_internal {
        template<bool vectorial, typename T>
        auto
        rag_back_project_weights(const array_1d<index_t> &rag_map, const xt::xexpression<T> &xrag_weights) {
            HG_TRACE();
            auto &rag_weights = xrag_weights.derived_cast();

            index_t numv = rag_map.size();
            std::vector<size_t> shape;
            shape.push_back(numv);
            shape.insert(shape.end(), rag_weights.shape().begin() + 1, rag_weights.shape().end());
            array_nd<typename T::value_type> weights = xt::zeros<typename T::value_type>(shape);


            auto input_view = make_light_axis_view<vectorial>(rag_weights);
            auto output_view = make_light_axis_view<vectorial>(weights);

            for (index_t i = 0; i < numv; ++i) {
                if (rag_map.data()[i] != invalid_index) {
                    output_view.set_position(i);
                    input_view.set_position(rag_map.data()[i]);
                    output_view = input_view;
                }
            }

            return weights;
        }

    }

    /**
     * Projects weights on the rag (vertices or edges) onto the original graph.
     * @tparam T
     * @param rag_map rag vertex_map or rag edge_map (see struct region_adjacency_graph)
     * @param xrag_weights node or edge weights of the rag (depending of the provided rag_map)
     * @return original graph (vertices or edges) weights
     */
    template<typename T>
    auto
    rag_back_project_weights(const array_1d<index_t> &rag_map, const xt::xexpression<T> &xrag_weights) {
        if (xrag_weights.derived_cast().dimension() == 1) {
            return rag_internal::rag_back_project_weights<false>(rag_map, xrag_weights);
        } else {
            return rag_internal::rag_back_project_weights<true>(rag_map, xrag_weights);
        }

    }

    /**
     * Accumulate original graph (vertices or edges) weights onto the rag (vertices or edges)
     * @tparam T
     * @tparam accumulator_t
     * @tparam output_t
     * @param rag_map rag vertex_map or rag edge_map (see struct region_adjacency_graph)
     * @param xweights node or edge weights of the original graph (depending of the provided rag_map)
     * @param accumulator
     * @return rag (vertices or edges) weights
     */
    template<typename T, typename accumulator_t, typename output_t = typename T::value_type>
    auto rag_accumulate(const array_1d<index_t> &rag_map,
                        const xt::xexpression<T> &xweights,
                        const accumulator_t &accumulator) {
        return accumulate_at(rag_map, xweights, accumulator);
    }
}


<|start_filename|>higra/algo/py_tree_fusion.cpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#include "py_tree_fusion.hpp"
#include "higra/algo/tree_fusion.hpp"
#include "../py_common.hpp"
#include "xtensor-python/pyarray.hpp"
#include "xtensor-python/pytensor.hpp"

template<typename T>
using pyarray = xt::pyarray<T>;

namespace py = pybind11;
using namespace hg;

void py_init_tree_fusion(pybind11::module &m) {
    xt::import_numpy();

    m.def("_tree_fusion_depth_map", [](const std::vector<tree *> &trees) {
        return tree_fusion_depth_map(trees);
    });

}





<|start_filename|>test/cpp/io/test_tree_io.cpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#include "../test_utils.hpp"
#include "higra/io/tree_io.hpp"

namespace tree_io {

    using namespace hg;
    using namespace std;


    TEST_CASE("read and save tree", "[tree_io]") {
            array_1d<int> parent{ 5, 5, 6, 6, 6, 7, 7, 7 };

            array_1d<double> attr1{ 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0 };
            array_1d<int> attr2{ 8, 7, 6, 5, 4, 3, 2, 1 };
            tree t(parent);
            ostringstream out;
            save_tree(out, t).add_attribute("attr1", attr1).add_attribute("attr2", attr2).finalize();
            string res = out.str();

            istringstream in(res);
            auto tree_attr = read_tree(in);
            auto t2 = tree_attr.first;
            auto attributes = tree_attr.second;

            REQUIRE(xt::allclose(parent, parents(t2)));

            REQUIRE(attributes.count("attr1") == 1);
            REQUIRE(xt::allclose(attributes["attr1"], attr1));

            REQUIRE(attributes.count("attr2") == 1);
            REQUIRE(xt::allclose(attributes["attr2"], attr2));
    }
}

<|start_filename|>test/cpp/accumulator/test_accumulator.cpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/
#include "../test_utils.hpp"
#include "higra/structure/array.hpp"
#include "higra/accumulator/accumulator.hpp"
#include "higra/structure/details/light_axis_view.hpp"
#include <vector>

namespace accumulator {

    template<bool vec, typename acc_t>
    auto applyAcc(const hg::array_nd<double> &values, acc_t accFactory) {
        auto inview = hg::make_light_axis_view<vec>(values);
        std::vector<size_t> data_shape(values.shape().begin() + 1, values.shape().end());

        auto out_shape = acc_t::get_output_shape(data_shape);
        if (out_shape.empty())
            out_shape.push_back(1);
        hg::array_nd<double> storage = hg::array_nd<double>::from_shape(out_shape);
        auto acc = accFactory.template make_accumulator<vec>(storage);
        acc.initialize();

        for (hg::index_t i = 0; i < (hg::index_t) values.shape()[0]; i++) {
            inview.set_position(i);
            acc.accumulate(inview.begin());
        }
        acc.finalize();
        return storage;
    };

    template<typename acc_t>
    auto applyAccG(const hg::array_nd<double> &values, acc_t acc) {
        if (values.dimension() == 1) {
            return applyAcc<false>(values, acc);
        } else {
            return applyAcc<true>(values, acc);
        }
    };

    auto isclose(double a, double b) {
        return std::abs(a - b) < 10e-5;
    }

    TEST_CASE("accumulator scalar", "[accumulator]") {
        hg::array_nd<double> values{-5, 10, -20, 5, 2, -2};
        double r = applyAccG(values, hg::accumulator_max())();
        REQUIRE(r == 10.0);
        REQUIRE((applyAccG(values, hg::accumulator_min()))() == -20);
        REQUIRE((applyAccG(values, hg::accumulator_sum()))() == -10);
        REQUIRE((applyAccG(values, hg::accumulator_counter()))() == 6);
        REQUIRE((applyAccG(values, hg::accumulator_first()))() == -5);
        REQUIRE((applyAccG(values, hg::accumulator_last()))() == -2);
        REQUIRE((applyAccG(values, hg::accumulator_argmin()))() == 2);
        REQUIRE((applyAccG(values, hg::accumulator_argmax()))() == 1);
        REQUIRE((isclose(applyAccG(values, hg::accumulator_mean())(), (-5 + 10 + -20 + 5 + 2 + -2) / 6.0)));
        REQUIRE((isclose(applyAccG(values, hg::accumulator_prod())(), -5 * 10 * -20 * 5 * 2 * -2)));

    }

    TEST_CASE("accumulator vectorial", "[accumulator]") {

        hg::array_nd<double> values{{{0,  1}, {1,  2}},
                                    {{5,  9}, {-1, 4}},
                                    {{-2, 2}, {1,  -1}}};

        auto res1 = applyAccG(values, hg::accumulator_sum());

        hg::array_nd<double> ref1{{3, 12},
                                  {1, 5}};
        REQUIRE(xt::allclose(res1, ref1));

        auto res2 = applyAccG(values, hg::accumulator_mean());
        hg::array_nd<double> ref2{{1,       4},
                                  {1.0 / 3, 5.0 / 3}};
        REQUIRE(xt::allclose(res2, ref2));

        auto res3 = applyAccG(values, hg::accumulator_prod());
        hg::array_nd<double> ref3{{0,  18},
                                  {-1, -8}};
        REQUIRE(xt::allclose(res3, ref3));

        auto res4 = applyAccG(values, hg::accumulator_first());
        hg::array_nd<double> ref4{{0, 1},
                                  {1, 2}};
        REQUIRE(xt::allclose(res4, ref4));

        auto res5 = applyAccG(values, hg::accumulator_last());
        hg::array_nd<double> ref5{{-2, 2},
                                  {1,  -1}};
        REQUIRE(xt::allclose(res5, ref5));

        hg::array_nd<double> values2{{0,  1},
                                     {-1, -2},
                                     {5,  9},
                                     {-1, 4},
                                     {-2, 10},
                                     {1,  -1}};

        auto res6 = applyAccG(values2, hg::accumulator_argmin())();
        REQUIRE(res6 == 1);

        auto res7 = applyAccG(values2, hg::accumulator_argmax())();
        REQUIRE(res7 == 2);

    }
}

<|start_filename|>include/higra/accumulator/at_accumulator.hpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#pragma once

#include "../structure/array.hpp"
#include "accumulator.hpp"
#include "../structure/details/light_axis_view.hpp"

namespace hg {

    namespace at_accumulator_internal {
        template<bool vectorial,
                typename T,
                typename accumulator_t,
                typename output_t = typename T::value_type>
        auto
        at_accumulate(const array_1d<index_t> &indices,
                      const xt::xexpression<T> &xweights,
                      const accumulator_t &accumulator) {
            HG_TRACE();
            auto &weights = xweights.derived_cast();
            hg_assert(weights.shape()[0] == indices.size(), "Weights dimension does not match rag map dimension.");

            index_t size = xt::amax(indices)() + 1;
            auto data_shape = std::vector<size_t>(weights.shape().begin() + 1, weights.shape().end());
            auto output_shape = accumulator_t::get_output_shape(data_shape);
            output_shape.insert(output_shape.begin(), size);
            array_nd<typename T::value_type> res = array_nd<typename T::value_type>::from_shape(output_shape);

            auto input_view = make_light_axis_view<vectorial>(weights);
            auto output_view = make_light_axis_view<vectorial>(res);

            std::vector<decltype(accumulator.template make_accumulator<vectorial>(output_view))> accs;
            accs.reserve(size);


            for (index_t i = 0; i < size; ++i) {
                output_view.set_position(i);
                accs.push_back(accumulator.template make_accumulator<vectorial>(output_view));
                accs[i].initialize();
            }

            index_t map_size = indices.size();
            for (index_t i = 0; i < map_size; ++i) {
                if (indices.data()[i] != invalid_index) {
                    input_view.set_position(i);
                    accs[indices.data()[i]].accumulate(input_view.begin());
                }
            }

            for (auto &acc: accs) {
                acc.finalize();
            }

            return res;
        }
    }

    /**
     * Accumulate the given weights located at given indices.
     *
     * Let :math:`M = max(indices)`. For all :math:`i \in \{0, \ldots, M\}`
     *
     * .. math::
     *
     *      result[i] = accumulator(\{weights[j, :] \mid indices[j] = i  \})
     *
     * @tparam T
     * @tparam accumulator_t
     * @tparam output_t
     * @param indices a 1d array of indices (entry equals to :math:`-1` are ignored)
     * @param xweights a nd-array of shape :math:`(s_1, \ldots, s_n)` such that :math:`s_1=indices.size()`
     * @param accumulator
     * @return a nd-array of size :math:`(M, s_2, \ldots, s_n)`
     */
    template<typename T, typename accumulator_t, typename output_t = typename T::value_type>
    auto accumulate_at(const array_1d<index_t> &indices,
                       const xt::xexpression<T> &xweights,
                       const accumulator_t &accumulator) {
        if (xweights.derived_cast().dimension() == 1) {
            return at_accumulator_internal::at_accumulate<false, T, accumulator_t, output_t>(indices,
                                                                                             xweights,
                                                                                             accumulator);
        } else {
            return at_accumulator_internal::at_accumulate<true, T, accumulator_t, output_t>(indices,
                                                                                            xweights,
                                                                                            accumulator);
        }
    };

}


<|start_filename|>benchmark/benchmark_parallel_sort.cpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#include <benchmark/benchmark.h>

#include "higra/graph.hpp"
#include "xtensor/xview.hpp"
#include "xtensor/xrandom.hpp"
#include <algorithm>
#include "tbb/parallel_sort.h"
#include "tbb-ssort/parallel_stable_sort.h"

using namespace xt;
using namespace hg;

std::size_t min_array_size = 10;
std::size_t max_array_size = 24;




static void BM_stl_sort(benchmark::State &state) {
    for (auto _ : state) {
        state.PauseTiming();

        size_t size = state.range(0);
        array_1d<float> a = xt::random::rand<float>({size});
        state.ResumeTiming();
        std::sort(a.begin(), a.end());
        bool flag;
        benchmark::DoNotOptimize(flag = (a.size() == size));
    }
}

BENCHMARK(BM_stl_sort)->Range(1 << min_array_size, 1 << max_array_size);

static void BM_stl_stable_sort(benchmark::State &state) {
    for (auto _ : state) {
        state.PauseTiming();

        size_t size = state.range(0);
        array_1d<float> a = xt::random::rand<float>({size});
        state.ResumeTiming();
        std::stable_sort(a.begin(), a.end());
        bool flag;
        benchmark::DoNotOptimize(flag = (a.size() == size));
    }
}

BENCHMARK(BM_stl_stable_sort)->Range(1 << min_array_size, 1 << max_array_size);

static void BM_tbb_parallel_sort(benchmark::State &state) {
    for (auto _ : state) {
        state.PauseTiming();

        size_t size = state.range(0);
        array_1d<float> a = xt::random::rand<float>({size});
        state.ResumeTiming();
        tbb::parallel_sort(a.begin(), a.end());
        bool flag;
        benchmark::DoNotOptimize(flag = (a.size() == size));
    }
}

BENCHMARK(BM_tbb_parallel_sort)->Range(1 << min_array_size, 1 << max_array_size);

static void BM_tbb_parallel_stable_sort(benchmark::State &state) {
    for (auto _ : state) {
        state.PauseTiming();

        size_t size = state.range(0);
        array_1d<float> a = xt::random::rand<float>({size});
        state.ResumeTiming();
        pss::parallel_stable_sort(a.begin(), a.end());
        bool flag;
        benchmark::DoNotOptimize(flag = (a.size() == size));
    }
}

BENCHMARK(BM_tbb_parallel_stable_sort)->Range(1 << min_array_size, 1 << max_array_size);

<|start_filename|>higra/io_utils/py_tree_io.cpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#include "py_tree_io.hpp"
#include "higra/io/tree_io.hpp"
#include "../py_common.hpp"
#include "xtensor-python/pyarray.hpp"
#include "xtensor-python/pytensor.hpp"
#include <fstream>

template<typename T>
using pyarray = xt::pyarray<T>;

void py_init_tree_io(pybind11::module &m) {
    xt::import_numpy();

    m.def("_read_tree", [](const std::string &filename) {
              std::ifstream file(filename);
              return hg::read_tree(file);
          },
          "Read tree from mixed ascii/binary format. Return a pair with the tree and a map of attributes (tree, dict[string => 1d array[double] ])",
          pybind11::arg("filename"));

    m.def("save_tree", [](const std::string &filename, const hg::tree &tree,
                          const std::map<std::string, pyarray<double>> &attributes) {
              std::ofstream file(filename);
              auto s = hg::save_tree(file, tree);
              for (auto e: attributes) {
                  s.add_attribute(e.first, e.second);
              }
              s.finalize();
          },
          "Save a tree and scalar attributes to mixed ascii/binary format. "
          "Attributes must be numpy 1d arrays stored in a dictionary with string keys (attribute names).",
          pybind11::arg("filename"),
          pybind11::arg("tree"),
          pybind11::arg("attributes") = std::map<std::string, pyarray<double>>());
}



<|start_filename|>include/higra/structure/details/graph_concepts.hpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#pragma once

#ifdef HG_USE_BOOST_GRAPH
#include <boost/graph/graph_concepts.hpp>
#endif

namespace hg {
    namespace graph {
#ifdef HG_USE_BOOST_GRAPH

        using directed_tag = boost::directed_tag;
        using undirected_tag = boost::undirected_tag;
        using bidirectional_tag = boost::bidirectional_tag;

        using incidence_graph_tag = boost::incidence_graph_tag;
        using adjacency_graph_tag = boost::adjacency_graph_tag;
        using bidirectional_graph_tag = boost::bidirectional_graph_tag;
        using vertex_list_graph_tag = boost::vertex_list_graph_tag;
        using edge_list_graph_tag = boost::edge_list_graph_tag;
        using adjacency_matrix_tag = boost::adjacency_matrix_tag;

        using allow_parallel_edge_tag = boost::allow_parallel_edge_tag;
        using disallow_parallel_edge_tag = boost::disallow_parallel_edge_tag;

        using graph_traits = boost::graph_traits;

#else

        struct directed_tag {
        };
        struct undirected_tag {
        };
        struct bidirectional_tag : virtual public directed_tag {
        };

        struct incidence_graph_tag {
        };
        struct adjacency_graph_tag {
        };
        struct bidirectional_graph_tag : virtual public incidence_graph_tag {
        };
        struct vertex_list_graph_tag {
        };
        struct edge_list_graph_tag {
        };
        struct adjacency_matrix_tag {
        };

        struct allow_parallel_edge_tag {
        };
        struct disallow_parallel_edge_tag {
        };

        template<typename T>
        struct graph_traits;

#endif
    }
    using graph::graph_traits;
}


<|start_filename|>include/higra/image/hierarchy_mean_pb.hpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#pragma once

#include "../graph.hpp"
#include "graph_image.hpp"
#include "contour_2d.hpp"
#include "../algo/watershed.hpp"
#include "../algo/rag.hpp"
#include "../algo/graph_weights.hpp"
#include "../hierarchy/binary_partition_tree.hpp"


namespace hg {

    /**
     * Compute the *oriented watershed* as described in [ArbelaezPAMI2011]_ .
     * Given a 4 adjacency graph with edge boundary probabilities and estimated boundary orientations, the algorithms computes:
     *
     *  - a region adjacency graph of the watershed regions of the edge boundary probabilities
     *  - the boundaries between watershed regions are vectorized and simplified (see contour_2d class)
     *  - the orientation of each boundary element is estimated
     *  - the edge boundary probabilities are reweighted according to the concordance between user provided boundary orientations and estimated orientation of boundary elements
     *  - the weight of the region adjacency graph edges as the mean value of reweighted edge boundary probabilities on the frontier between the 2 regions
     *
     *  The algorithm returns the region adjacency graph of watershed pixels and its edge weights.
     *
     * .. [ArbelaezPAMI2011] <NAME>., <NAME>., <NAME>., & <NAME>..
     *    Contour detection and hierarchical image segmentation.
     *    IEEE transactions on pattern analysis and machine intelligence, 33(5), 898-916.
     *
     * @tparam graph_t
     * @tparam T1
     * @tparam T2
     * @param graph
     * @param embedding
     * @param xedge_weights
     * @param xedge_orientations
     * @return
     */
    template<typename graph_t, typename T1, typename T2>
    auto oriented_watershed(const graph_t &graph,
                            const embedding_grid_2d &embedding,
                            const xt::xexpression<T1> &xedge_weights,
                            const xt::xexpression<T2> &xedge_orientations = array_nd<int>()) {
        HG_TRACE();
        using value_t = typename T1::value_type;
        const auto &edge_weights = xedge_weights.derived_cast();
        const auto &edge_orientations = xedge_orientations.derived_cast();
        hg_assert_edge_weights(graph, edge_weights);
        hg_assert_1d_array(edge_weights);
        hg_assert(num_vertices(graph) == embedding.size(),
                  "Graph number of vertices does not match the size of the embedding.");
        auto watershed_labels = labelisation_watershed(graph, edge_weights);
        auto rag = make_region_adjacency_graph_from_labelisation(graph, watershed_labels);

        array_1d<value_t> final_weights = xt::zeros<value_t>({num_edges(graph)});

        if (edge_orientations.dimension() != 0) {
            // reweighting contours according to contour orientations
            hg_assert_edge_weights(graph, edge_orientations);
            hg_assert_1d_array(edge_orientations);

            auto watershed_cut = weight_graph(graph, watershed_labels, weight_functions::L0);
            auto contour2d = fit_contour_2d(graph, embedding, watershed_cut);
            contour2d.subdivide();

            for (auto &polyline: contour2d) {
                for (auto &segment: polyline) {
                    auto segment_orientation = std::fmod(segment.angle(), xt::numeric_constants<double>::PI);

                    for (auto element: segment) {
                        auto edge_index = element.first;
                        auto edge_weight = edge_weights(edge_index);
                        auto edge_orientation = edge_orientations(edge_index);
                        auto new_weight = edge_weight
                                          * std::abs(
                                std::cos(edge_orientation - xt::numeric_constants<double>::PI_2 - segment_orientation));
                        if (new_weight > final_weights(edge_index)) {
                            final_weights(edge_index) = new_weight;
                        }
                    }
                }
            }
        } else {
            final_weights = edge_weights;
        }


        // compute rag edge weights
        auto rag_edge_weights = rag_accumulate(rag.edge_map, final_weights, accumulator_mean());

        return std::make_pair(std::move(rag), std::move(rag_edge_weights));
    }

    /**
     * Compute the *mean probability boundary hierarchy* as described in [ArbelaezPAMI2011]_ .
     * Given a 4 adjacency graph with edge boundary probabilities and estimated boundary orientations, the algorithms computes:
     *
     *  - the oriented watershed of the given graph
     *  - the average linkage clustering ot the oriented watershed
     *
     *  The algorithm returns the region adjacency graph of watershed pixels and teh valued tree computed on this graph.
     *
     * .. [ArbelaezPAMI2011] <NAME>., <NAME>., <NAME>., & <NAME>..
     *    Contour detection and hierarchical image segmentation.
     *    IEEE transactions on pattern analysis and machine intelligence, 33(5), 898-916.
     *
     * @tparam graph_t
     * @tparam T1
     * @tparam T2
     * @param graph
     * @param embedding
     * @param xedge_weights
     * @param xedge_orientations
     * @return
     */
    template<typename graph_t, typename T1, typename T2>
    auto mean_pb_hierarchy(const graph_t &graph,
                           const embedding_grid_2d &embedding,
                           const xt::xexpression<T1> &xedge_weights,
                           const xt::xexpression<T2> &xedge_orientations = array_nd<int>()) {
        HG_TRACE();

        using value_t = typename T1::value_type;
        const auto &edge_weights = xedge_weights.derived_cast();
        const auto &edge_orientations = xedge_orientations.derived_cast();
        hg_assert_edge_weights(graph, edge_weights);
        hg_assert_1d_array(edge_weights);
        hg_assert(num_vertices(graph) == embedding.size(),
                  "Graph number of vertices does not match the size of the embedding.");

        auto ows = oriented_watershed(graph, embedding, edge_weights, edge_orientations);
        auto &rag = ows.first;
        auto &rag_edge_weights = ows.second;

        auto rag_edge_length = rag_accumulate(rag.edge_map, edge_weights, accumulator_counter());

        auto tree = binary_partition_tree_average_linkage(rag.rag,
                                                          rag_edge_weights,
                                                          rag_edge_length);
        return std::make_pair(std::move(rag), std::move(tree));
    }
}


<|start_filename|>include/higra/detail/log.hpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#pragma once

#include <vector>
#include <functional>
#include <iostream>
#include <string>

namespace hg {

    struct logger {

        static const std::size_t MAX_MSG_SIZE = 8096;
        using callback_list = std::vector<std::function<void(const std::string &)>>;

        static bool &trace_enabled() {
            static bool value{false};
            return value;
        }

        static callback_list &callbacks() {
            static callback_list callbacks{
                    [](const std::string &msg) { std::cout << msg; }}; //not the perfect initialization...
            return callbacks;
        }

        template<typename ...Args>
        static void emit(const char *format, Args &&...args) {
            char message[MAX_MSG_SIZE];
            snprintf(message, MAX_MSG_SIZE, format, std::forward<Args>(args)...);
            std::string sm(message);
            for (auto c: callbacks()) {
                c(sm);
            }

        }
    };

}
#if defined(__clang__)
#define HG_PRETTY_FUNCTION __PRETTY_FUNCTION__
#elif defined(__GNUC__) || defined(__GNUG__)
#define HG_PRETTY_FUNCTION __PRETTY_FUNCTION__
#elif defined(_MSC_VER)
#define HG_PRETTY_FUNCTION __FUNCSIG__
#else
#define HG_PRETTY_FUNCTION __func__
#endif


#define HG_LOG_LEVEL_ERROR (1)
#define HG_LOG_LEVEL_WARNING (2)
#define HG_LOG_LEVEL_INFO (3)
#define HG_LOG_LEVEL_DEBUG (4)
#define HG_LOG_LEVEL_DETAIL (5)

#define HG_LOG_LEVEL_ERROR_NAME "ERROR"
#define HG_LOG_LEVEL_WARNING_NAME "WARNING"
#define HG_LOG_LEVEL_INFO_NAME "INFO"
#define HG_LOG_LEVEL_DEBUG_NAME "DEBUG"
#define HG_LOG_LEVEL_DETAIL_NAME "DETAIL"


#ifndef HG_LOG_LEVEL
#define HG_LOG_LEVEL HG_LOG_LEVEL_WARNING
#endif

//#define HG_LOG_EMIT(LEVEL, M, ...)   fprintf(stdout, "[" LEVEL "] %s (%s:%d) " M "\n", HG_PRETTY_FUNCTION, __FILE__, __LINE__, ##__VA_ARGS__);
#define HG_LOG_EMIT(LEVEL, M, ...)   hg::logger::emit("[" LEVEL "] %s (%s:%d) " M "\n", HG_PRETTY_FUNCTION, __FILE__, __LINE__, ##__VA_ARGS__)

#if HG_LOG_LEVEL <= HG_LOG_LEVEL_ERROR
#define HG_LOG_ERROR(M, ...) HG_LOG_EMIT(HG_LOG_LEVEL_ERROR_NAME, M, ##__VA_ARGS__)
#else
#define HG_LOG_ERROR(...) do{}while(0)
#endif

#if HG_LOG_LEVEL <= HG_LOG_LEVEL_WARNING
#define HG_LOG_WARNING(M, ...) HG_LOG_EMIT(HG_LOG_LEVEL_WARNING_NAME, M, ##__VA_ARGS__)
#else
#define HG_LOG_WARNING(...) do{}while(0)
#endif

#if HG_LOG_LEVEL <= HG_LOG_LEVEL_INFO
#define HG_LOG_INFO(M, ...) HG_LOG_EMIT(HG_LOG_LEVEL_INFO_NAME, M, ##__VA_ARGS__)
#else
#define HG_LOG_INFO(...) do{}while(0)
#endif

#if HG_LOG_LEVEL <= HG_LOG_LEVEL_DEBUG
#define HG_LOG_DEBUG(M, ...) HG_LOG_EMIT(HG_LOG_LEVEL_DEBUG_NAME, M, ##__VA_ARGS__)
#else
#define HG_LOG_DEBUG(...) do{}while(0)
#endif

#if HG_LOG_LEVEL <= HG_LOG_LEVEL_DETAIL
#define HG_LOG_DETAIL(M, ...) HG_LOG_EMIT(HG_LOG_LEVEL_DETAIL_NAME, M, ##__VA_ARGS__)
#else
#define HG_LOG_DETAIL(...) do{}while(0)
#endif

#ifdef HG_ENABLE_TRACE
#define HG_TRACE(M, ...) do{                                            \
if(hg::logger::trace_enabled()){                                        \
    HG_LOG_EMIT("TRACE", "function called " M, ##__VA_ARGS__);          \
}                                                                       \
}while(0)
#else
#define HG_TRACE(...)  do{}while(0)
#endif


<|start_filename|>test/cpp/io/test_pink_graph_io.cpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#include "higra/io/pink_graph_io.hpp"
#include <sstream>
#include <string>
#include "../test_utils.hpp"
#include "xtensor/xgenerator.hpp"

namespace pink_graph_io {
    
    using namespace hg;
    using namespace std;
    
    string s(
            "#rs 5 cs 3\n"
            "15 14\n"
            "val sommets\n"
            "0 1\n"
            "1 2\n"
            "2 3\n"
            "3 4\n"
            "4 5\n"
            "5 6\n"
            "6 7\n"
            "7 8\n"
            "8 9\n"
            "9 10\n"
            "10 11\n"
            "11 12\n"
            "12 13\n"
            "13 14\n"
            "14 15\n"
            "arcs values\n"
            "0 1 3\n"
            "1 2 0\n"
            "2 3 0\n"
            "3 4 1\n"
            "4 5 3\n"
            "5 6 0\n"
            "6 7 1\n"
            "7 8 0\n"
            "8 9 2\n"
            "9 10 0\n"
            "10 11 1\n"
            "11 12 0\n"
            "12 13 3\n"
            "13 14 0\n");

    string s2(
            "15 14\n"
            "val sommets\n"
            "0 1\n"
            "1 2\n"
            "2 3\n"
            "3 4\n"
            "4 5\n"
            "5 6\n"
            "6 7\n"
            "7 8\n"
            "8 9\n"
            "9 10\n"
            "10 11\n"
            "11 12\n"
            "12 13\n"
            "13 14\n"
            "14 15\n"
            "arcs values\n"
            "0 1 3\n"
            "1 2 0\n"
            "2 3 0\n"
            "3 4 1\n"
            "4 5 3\n"
            "5 6 0\n"
            "6 7 1\n"
            "7 8 0\n"
            "8 9 2\n"
            "9 10 0\n"
            "10 11 1\n"
            "11 12 0\n"
            "12 13 3\n"
            "13 14 0\n");

    TEST_CASE("read graph from stream", "[pink_graph_io]") {
            istringstream in(s);

            auto res = read_pink_graph(in);

            std::vector<size_t> shape = { 3, 5 };

            std::vector<ugraph::edge_descriptor > edges;
            for (index_t i = 0; i < 14; ++i)
            edges.emplace_back(i, i + 1, i);

            array_1d<double> vertex_weights = xt::arange<double>(1, 16);
            array_1d<double> edge_weights = { 3, 0, 0, 1, 3, 0, 1, 0, 2, 0, 1, 0, 3, 0 };

            std::vector<ugraph::edge_descriptor> res_edges;
            for (auto e : edge_iterator(res.graph))
            res_edges.push_back(e);

            REQUIRE(vectorEqual(edges, res_edges));
            REQUIRE(vectorEqual(shape, res.shape));
            REQUIRE(xt::allclose(vertex_weights, res.vertex_weights));
            REQUIRE(xt::allclose(edge_weights, res.edge_weights));
    }

    TEST_CASE("read graph from stream no shape", "[pink_graph_io]") {
        istringstream in(s2);

        auto res = read_pink_graph(in);

        std::vector<size_t> shape = {15};

        std::vector<ugraph::edge_descriptor > edges;
        for (index_t i = 0; i < 14; ++i)
            edges.emplace_back(i, i + 1, i);

        array_1d<double> vertex_weights = xt::arange<double>(1, 16);
        array_1d<double> edge_weights = { 3, 0, 0, 1, 3, 0, 1, 0, 2, 0, 1, 0, 3, 0 };

        std::vector<ugraph::edge_descriptor> res_edges;
        for (auto e : edge_iterator(res.graph))
            res_edges.push_back(e);

        REQUIRE(vectorEqual(edges, res_edges));
        REQUIRE(vectorEqual(shape, res.shape));
        REQUIRE(xt::allclose(vertex_weights, res.vertex_weights));
        REQUIRE(xt::allclose(edge_weights, res.edge_weights));
    }

    TEST_CASE("write graph to stream", "[pink_graph_io]") {
            array_1d<double> vertex_weights = xt::arange<double>(1, 16);
            array_1d<double> edge_weights = { 3, 0, 0, 1, 3, 0, 1, 0, 2, 0, 1, 0, 3, 0 };
            std::vector<size_t> shape = { 3, 5 };

            ugraph g(15);

            std::vector<std::pair<index_t, index_t> > edges;
            for (index_t i = 0; i < 14; ++i)
            add_edge(i, i + 1, g);

            ostringstream out;
            save_pink_graph(out, g, vertex_weights, edge_weights, shape);
            string res = out.str();
            REQUIRE(s == res);
    }

    TEST_CASE("write graph to stream no vertex weights no edge weights no shape", "[pink_graph_io]") {

        string s2(
                "15 14\n"
                "val sommets\n"
                "0 1\n"
                "1 1\n"
                "2 1\n"
                "3 1\n"
                "4 1\n"
                "5 1\n"
                "6 1\n"
                "7 1\n"
                "8 1\n"
                "9 1\n"
                "10 1\n"
                "11 1\n"
                "12 1\n"
                "13 1\n"
                "14 1\n"
                "arcs values\n"
                "0 1 1\n"
                "1 2 1\n"
                "2 3 1\n"
                "3 4 1\n"
                "4 5 1\n"
                "5 6 1\n"
                "6 7 1\n"
                "7 8 1\n"
                "8 9 1\n"
                "9 10 1\n"
                "10 11 1\n"
                "11 12 1\n"
                "12 13 1\n"
                "13 14 1\n");

        array_1d<double> vertex_weights{};
        array_1d<double> edge_weights{};
        std::vector<size_t> shape{};

        ugraph g(15);

        std::vector<std::pair<index_t, index_t> > edges;
        for (index_t i = 0; i < 14; ++i)
            add_edge(i, i + 1, g);

        ostringstream out;
        save_pink_graph(out, g, vertex_weights, edge_weights, shape);
        string res = out.str();
        REQUIRE(s2 == res);
    }

    TEST_CASE("write graph to stream no vertex weights no edge weights shape 1d", "[pink_graph_io]") {

        string s2(
                "#rs 15 cs 1\n"
                "15 14\n"
                "val sommets\n"
                "0 1\n"
                "1 1\n"
                "2 1\n"
                "3 1\n"
                "4 1\n"
                "5 1\n"
                "6 1\n"
                "7 1\n"
                "8 1\n"
                "9 1\n"
                "10 1\n"
                "11 1\n"
                "12 1\n"
                "13 1\n"
                "14 1\n"
                "arcs values\n"
                "0 1 1\n"
                "1 2 1\n"
                "2 3 1\n"
                "3 4 1\n"
                "4 5 1\n"
                "5 6 1\n"
                "6 7 1\n"
                "7 8 1\n"
                "8 9 1\n"
                "9 10 1\n"
                "10 11 1\n"
                "11 12 1\n"
                "12 13 1\n"
                "13 14 1\n");

        array_1d<double> vertex_weights{};
        array_1d<double> edge_weights{};
        std::vector<size_t> shape{15};

        ugraph g(15);

        std::vector<std::pair<index_t, index_t> > edges;
        for (index_t i = 0; i < 14; ++i)
            add_edge(i, i + 1, g);

        ostringstream out;
        save_pink_graph(out, g, vertex_weights, edge_weights, shape);
        string res = out.str();
        REQUIRE(s2 == res);
    }

    TEST_CASE("write graph to stream no vertex weights no edge weights invalid shape", "[pink_graph_io]") {

        array_1d<double> vertex_weights{};
        array_1d<double> edge_weights{};
        std::vector<size_t> shape{3, 5, 1};

        ugraph g(15);

        std::vector<std::pair<index_t, index_t> > edges;
        for (index_t i = 0; i < 14; ++i)
            add_edge(i, i + 1, g);

        ostringstream out;
        REQUIRE_THROWS(save_pink_graph(out, g, vertex_weights, edge_weights, shape));
    }
}

<|start_filename|>higra/algo/py_watershed.cpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#include "py_watershed.hpp"
#include "higra/algo/watershed.hpp"
#include "../py_common.hpp"
#include "xtensor-python/pyarray.hpp"
#include "xtensor-python/pytensor.hpp"

template<typename T>
using pyarray = xt::pyarray<T>;

namespace py = pybind11;


template<typename graph_t>
struct def_labelisation_watershed {
    template<typename value_t, typename C>
    static
    void def(C &c, const char *doc) {
        c.def("_labelisation_watershed", [](const graph_t &graph, const pyarray<value_t> &edge_weights) {
                  return hg::labelisation_watershed(graph, edge_weights);
              },
              doc,
              py::arg("graph"),
              py::arg("edge_weights"));
    }
};

template<typename graph_t>
struct def_labelisation_seeded_watershed {
    template<typename value_t, typename C>
    static
    void def(C &c, const char *doc) {
        c.def("_labelisation_seeded_watershed", 
                [](const graph_t &graph, 
                        const pyarray<value_t> &edge_weights,
                        const pyarray<hg::index_t> & vertex_seeds,
                        const hg::index_t background_label) {
                  return hg::labelisation_seeded_watershed(graph, edge_weights, vertex_seeds, background_label);
              },
              doc,
              py::arg("graph"),
              py::arg("edge_weights"),
              py::arg("vertex_seeds"),
              py::arg("background_label"));
    }
};


void py_init_watershed(pybind11::module &m) {
    xt::import_numpy();

    add_type_overloads<def_labelisation_watershed<hg::ugraph>, HG_TEMPLATE_NUMERIC_TYPES>(m,"");
    add_type_overloads<def_labelisation_seeded_watershed<hg::ugraph>, HG_TEMPLATE_NUMERIC_TYPES>(m,"");
}





<|start_filename|>test/cpp/algo/test_rag.cpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#include "../test_utils.hpp"
#include "higra/algo/rag.hpp"
#include "higra/image/graph_image.hpp"

using namespace hg;

namespace test_rag {
    struct fixture {

        region_adjacency_graph data;

        fixture() {
            auto g = hg::get_4_adjacency_graph({4, 4});
            array_1d<int> vertex_labels{1, 1, 5, 5,
                                        1, 1, 5, 5,
                                        1, 1, 3, 3,
                                        1, 1, 10, 10};

            data = make_region_adjacency_graph_from_labelisation(g, vertex_labels);
        }
    };

    TEST_CASE("simple rag", "[rag]") {

        fixture d;
        auto &rag = d.data.rag;
        auto &vertex_map = d.data.vertex_map;
        auto &edge_map = d.data.edge_map;

        REQUIRE(num_vertices(rag) == 4);
        REQUIRE(num_edges(rag) == 5);

        std::vector<ugraph::edge_descriptor> expected_edges = {
                {0, 1, 0},
                {1, 2, 1},
                {0, 2, 2},
                {2, 3, 3},
                {0, 3, 4}
        };
        index_t i = 0;
        for (auto e: edge_iterator(rag)) {
            REQUIRE(e == expected_edges[i++]);
        }

        array_1d<index_t> expected_vertex_map{
                0, 0, 1, 1,
                0, 0, 1, 1,
                0, 0, 2, 2,
                0, 0, 3, 3
        };
        REQUIRE(xt::equal(vertex_map, expected_vertex_map)());

        auto iv = invalid_index;
        array_1d<index_t> expected_edge_map{
                iv, iv, 0, iv, iv, iv, iv,
                iv, iv, 0, iv, iv, 1, 1,
                iv, iv, 2, iv, iv, 3, 3,
                iv, 4, iv
        };

        REQUIRE((edge_map == expected_edge_map));

    }

    TEST_CASE("simple rag 2", "[rag]") {

        auto g = get_4_adjacency_graph({3, 3});
        array_1d<int> labels{0, 1, 2, 3, 4, 2, 3, 4, 2};
        auto rag = make_region_adjacency_graph_from_labelisation(g, labels);

        REQUIRE(num_vertices(rag.rag) == 5);
        REQUIRE(num_edges(rag.rag) == 6);

        array_1d<int> edge_weights{1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1};
        auto rag2 = make_region_adjacency_graph_from_graph_cut(g, edge_weights);

        REQUIRE(num_vertices(rag2.rag) == 5);
        REQUIRE(num_edges(rag2.rag) == 6);

        REQUIRE((rag.vertex_map == rag2.vertex_map));
        REQUIRE((rag.edge_map == rag2.edge_map));

    }


    TEST_CASE("rag from graph cut", "[rag]") {

        auto g = hg::get_4_adjacency_graph({4, 4});
        array_1d<int> edge_weights{0, 0, 1, 0, 0, 0, 0,
                                   0, 0, 1, 0, 0, 1, 1,
                                   0, 0, 1, 0, 0, 1, 1,
                                   0, 1, 0};

        auto data = make_region_adjacency_graph_from_graph_cut(g, edge_weights);
        auto &rag = data.rag;
        auto &vertex_map = data.vertex_map;
        auto &edge_map = data.edge_map;

        REQUIRE(num_vertices(rag) == 4);
        REQUIRE(num_edges(rag) == 5);

        std::vector<ugraph::edge_descriptor> expected_edges = {
                {0, 1, 0},
                {1, 2, 1},
                {0, 2, 2},
                {2, 3, 3},
                {0, 3, 4}
        };
        index_t i = 0;
        for (auto e: edge_iterator(rag)) {
            REQUIRE(e == expected_edges[i++]);
        }

        array_1d<index_t> expected_vertex_map{
                0, 0, 1, 1,
                0, 0, 1, 1,
                0, 0, 2, 2,
                0, 0, 3, 3
        };
        REQUIRE((vertex_map == expected_vertex_map));

        auto iv = invalid_index;
        array_1d<index_t> expected_edge_map{
                iv, iv, 0, iv, iv, iv, iv,
                iv, iv, 0, iv, iv, 1, 1,
                iv, iv, 2, iv, iv, 3, 3,
                iv, 4, iv
        };
        REQUIRE((edge_map == expected_edge_map));

    }

    TEST_CASE("rag back project vertex weights", "[rag]") {

        fixture d;
        auto &vertex_map = d.data.vertex_map;

        array_nd<double> rag_vertex_weights{5, 7, 1, 3};
        auto vertex_weights = rag_back_project_weights(vertex_map, rag_vertex_weights);
        array_nd<double> expected_vertex_weights{
                5, 5, 7, 7,
                5, 5, 7, 7,
                5, 5, 1, 1,
                5, 5, 3, 3
        };
        REQUIRE((vertex_weights == expected_vertex_weights));

        array_nd<double> rag_vertex_weights_vec{
                {5, 2},
                {7, 1},
                {1, 9},
                {3, -2}
        };
        auto vertex_weights_vec = rag_back_project_weights(vertex_map, rag_vertex_weights_vec);
        array_nd<double> expected_vertex_weights_vec{
                {5, 2},
                {5, 2},
                {7, 1},
                {7, 1},
                {5, 2},
                {5, 2},
                {7, 1},
                {7, 1},
                {5, 2},
                {5, 2},
                {1, 9},
                {1, 9},
                {5, 2},
                {5, 2},
                {3, -2},
                {3, -2}
        };
        REQUIRE((vertex_weights_vec == expected_vertex_weights_vec));
    }

    TEST_CASE("rag back project edge weights", "[rag]") {

        fixture d;
        auto &edge_map = d.data.edge_map;

        array_nd<double> rag_edge_weights{5, 7, 1, 3, 2};
        auto edge_weights = rag_back_project_weights(edge_map, rag_edge_weights);
        double iv = 0;
        array_nd<double> expected_edge_weights{
                iv, iv, 5, iv, iv, iv, iv,
                iv, iv, 5, iv, iv, 7, 7,
                iv, iv, 1, iv, iv, 3, 3,
                iv, 2, iv
        };

        REQUIRE((edge_weights == expected_edge_weights));

        array_nd<double> rag_edge_weights_vec{
                {5, 1},
                {7, 1},
                {1, 9},
                {3, -4},
                {2, 8}
        };
        auto edge_weights_vec = rag_back_project_weights(edge_map, rag_edge_weights_vec);
        array_nd<double> expected_edge_weights_vec{
                {0, 0},
                {0, 0},
                {5, 1},
                {0, 0},
                {0, 0},
                {0, 0},
                {0, 0},
                {0, 0},
                {0, 0},
                {5, 1},
                {0, 0},
                {0, 0},
                {7, 1},
                {7, 1},
                {0, 0},
                {0, 0},
                {1, 9},
                {0, 0},
                {0, 0},
                {3, -4},
                {3, -4},
                {0, 0},
                {2, 8},
                {0, 0}
        };
        REQUIRE((edge_weights_vec == expected_edge_weights_vec));
    }

    TEST_CASE("rag accumulate vertex weights", "[rag]") {

        fixture d;

        auto &vertex_map = d.data.vertex_map;

        array_1d<int> vertex_weights({16}, 1);

        auto rag_vertex_weights = rag_accumulate(vertex_map, vertex_weights, accumulator_sum());
        array_nd<double> expected_rag_vertex_weights{8, 4, 2, 2};
        REQUIRE((rag_vertex_weights == expected_rag_vertex_weights));

        array_2d<int> vertex_weights_vec({16, 2}, 1);
        auto rag_vertex_weights_vec = rag_accumulate(vertex_map, vertex_weights_vec, accumulator_sum());
        array_nd<double> expected_rag_vertex_weights_vec{
                {8, 8},
                {4, 4},
                {2, 2},
                {2, 2}
        };
        REQUIRE((rag_vertex_weights_vec == expected_rag_vertex_weights_vec));
    }

    TEST_CASE("rag accumulate edge weights", "[rag]") {

        fixture d;

        auto &edge_map = d.data.edge_map;

        array_1d<int> edge_weights({24}, 1);

        auto rag_edge_weights = rag_accumulate(edge_map, edge_weights, accumulator_sum());
        array_nd<double> expected_rag_edge_weights{2, 2, 1, 2, 1};
        REQUIRE((rag_edge_weights == expected_rag_edge_weights));

        array_2d<int> edge_weights_vec({24, 2}, 1);
        auto rag_edge_weights_vec = rag_accumulate(edge_map, edge_weights_vec, accumulator_sum());
        array_nd<double> expected_rag_edge_weights_vec{
                {2, 2},
                {2, 2},
                {1, 1},
                {2, 2},
                {1, 1}
        };
        REQUIRE((rag_edge_weights_vec == expected_rag_edge_weights_vec));
    }

}

<|start_filename|>test/cpp/accumulator/test_graph_accumulator.cpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#include "../test_utils.hpp"
#include "higra/accumulator/graph_accumulator.hpp"
#include "higra/image/graph_image.hpp"

namespace graph_accumulator {

    using namespace hg;
    using namespace std;

    TEST_CASE("accumulator graph vertices", "[graph_accumulator]") {

        ugraph g = get_4_adjacency_graph({2, 3});

        array_1d<int> vertex_weights{1, 2, 3, 4, 5, 6};
        auto res1 = accumulate_graph_vertices(g, vertex_weights, accumulator_max());
        array_1d<int> ref1{4, 5, 6, 5, 6, 5};
        REQUIRE(xt::allclose(ref1, res1));

        array_2d<int> vertex_weights2{
                {1, 6},
                {2, 5},
                {3, 4},
                {4, 3},
                {5, 2},
                {6, 1}
        };
        auto res2 = accumulate_graph_vertices(g, vertex_weights2, accumulator_sum());
        array_2d<int> ref2{
                {6,  8},
                {9,  12},
                {8,  6},
                {6,  8},
                {12, 9},
                {8,  6}
        };
        REQUIRE(xt::allclose(ref2, res2));
    }

    TEST_CASE("accumulator graph edges", "[graph_accumulator]") {

        ugraph g = get_4_adjacency_graph({2, 3});

        array_1d<int> edge_weights{1, 2, 3, 4, 6, 5, 7};
        auto res1 = accumulate_graph_edges(g, edge_weights, accumulator_max());
        array_1d<int> ref1{2, 4, 6, 5, 7, 7};
        REQUIRE(xt::allclose(ref1, res1));

        array_2d<int> edge_weights2{
                {1, 6},
                {2, 5},
                {3, 4},
                {4, 3},
                {5, 2},
                {6, 1},
                {7, 9}
        };
        auto res2 = accumulate_graph_edges(g, edge_weights2, accumulator_sum());
        array_2d<int> ref2{
                {3,  11},
                {8,  13},
                {8,  6},
                {8,  6},
                {17, 13},
                {12, 11}
        };
        REQUIRE(xt::allclose(ref2, res2));
    }
}

<|start_filename|>lib/lib/cmake/xtl/xtlConfig.cmake<|end_filename|>
############################################################################
# Copyright (c) 2017, <NAME> and <NAME>                     #
#                                                                          #
# Distributed under the terms of the BSD 3-Clause License.                 #
#                                                                          #
# The full license is in the file LICENSE, distributed with this software. #
############################################################################

# xtl cmake module
# This module sets the following variables in your project::
#
#   xtl_FOUND - true if xtl found on the system
#   xtl_INCLUDE_DIRS - the directory containing xtl headers
#   xtl_LIBRARY - empty


####### Expanded from @PACKAGE_INIT@ by configure_package_config_file() #######
####### Any changes to this file will be overwritten by the next CMake run ####
####### The input file was xtlConfig.cmake.in                            ########

get_filename_component(PACKAGE_PREFIX_DIR "${CMAKE_CURRENT_LIST_DIR}/../../../" ABSOLUTE)

macro(set_and_check _var _file)
  set(${_var} "${_file}")
  if(NOT EXISTS "${_file}")
    message(FATAL_ERROR "File or directory ${_file} referenced by variable ${_var} does not exist !")
  endif()
endmacro()

macro(check_required_components _NAME)
  foreach(comp ${${_NAME}_FIND_COMPONENTS})
    if(NOT ${_NAME}_${comp}_FOUND)
      if(${_NAME}_FIND_REQUIRED_${comp})
        set(${_NAME}_FOUND FALSE)
      endif()
    endif()
  endforeach()
endmacro()

####################################################################################

if(NOT TARGET xtl)
    include("${CMAKE_CURRENT_LIST_DIR}/xtlTargets.cmake")
    get_target_property(xtl_INCLUDE_DIRS xtl INTERFACE_INCLUDE_DIRECTORIES)
endif()


<|start_filename|>higra/attribute/py_tree_attributes.cpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2019)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#include "py_tree_attributes.hpp"
#include "../py_common.hpp"
#include "higra/attribute/tree_attribute.hpp"
#include "xtensor-python/pyarray.hpp"
#include "xtensor-python/pytensor.hpp"

template<typename T>
using pyarray = xt::pyarray<T>;

namespace py = pybind11;

struct def_contour_length_component_tree {
    template<typename T>
    static
    void def(pybind11::module &m, const char *doc) {
        m.def("_attribute_contour_length_component_tree",
              [](const hg::tree &tree,
                 const hg::ugraph &graph,
                 const pyarray<T> &vertex_perimeter,
                 const pyarray<T> &edge_length) {
                  return hg::attribute_contour_length_component_tree(
                          tree,
                          graph,
                          vertex_perimeter,
                          edge_length
                  );
              },
              doc,
              py::arg("tree"),
              py::arg("graph"),
              py::arg("vertex_perimeter"),
              py::arg("edge_length"));
    }
};

struct def_attribute_extrema {
    template<typename T>
    static
    void def(pybind11::module &m, const char *doc) {
        m.def("_attribute_extrema",
              [](const hg::tree &tree,
                 const pyarray<T> &altitudes) {
                  return hg::attribute_extrema(
                          tree,
                          altitudes
                  );
              },
              doc,
              py::arg("tree"),
              py::arg("altitudes"));
    }
};

struct def_attribute_height {
    template<typename T>
    static
    void def(pybind11::module &m, const char *doc) {
        m.def("_attribute_height",
              [](const hg::tree &tree,
                 const pyarray<T> &altitudes,
                 bool increasing_altitudes) {
                  return hg::attribute_height(
                          tree,
                          altitudes,
                          increasing_altitudes
                  );
              },
              doc,
              py::arg("tree"),
              py::arg("altitudes"),
              py::arg("increasing_altitudes"));
    }
};

struct def_attribute_extinction_value {
    template<typename T>
    static
    void def(pybind11::module &m, const char *doc) {
        m.def("_attribute_extinction_value",
              [](const hg::tree &tree,
                 const pyarray<T> &altitudes,
                 const pyarray<T> &attribute,
                 bool increasing_altitudes) {
                  return hg::attribute_extinction_value(
                          tree,
                          altitudes,
                          attribute,
                          increasing_altitudes
                  );
              },
              doc,
              py::arg("tree"),
              py::arg("altitudes"),
              py::arg("attribute"),
              py::arg("increasing_altitudes"));
    }
};

struct def_attribute_children_pair_sum_product {
    template<typename T>
    static
    void def(pybind11::module &m, const char *doc) {
        m.def("_attribute_children_pair_sum_product",
              [](const hg::tree &tree,
                 const pyarray<T> &node_weights) {
                  return hg::attribute_children_pair_sum_product(
                          tree,
                          node_weights
                  );
              },
              doc,
              py::arg("tree"),
              py::arg("node_weights"));
    }
};

void py_init_attributes(pybind11::module &m) {
    xt::import_numpy();
    m.def("_attribute_sibling",
          [](const hg::tree &tree, hg::index_t skip) {
              return hg::attribute_sibling(tree, skip);
          },
          "",
          pybind11::arg("tree"),
          pybind11::arg("skip") = 1);

    m.def("_attribute_depth",
          [](const hg::tree &tree) {
              return hg::attribute_depth(tree);
          },
          "",
          pybind11::arg("tree"));

    m.def("_attribute_child_number",
          [](const hg::tree &tree) {
              return hg::attribute_child_number(tree);
          },
          "",
          pybind11::arg("tree"));

    add_type_overloads<def_contour_length_component_tree,
            HG_TEMPLATE_FLOAT_TYPES>(m, "");

    add_type_overloads<def_attribute_extrema,
            HG_TEMPLATE_NUMERIC_TYPES>(m, "");

    add_type_overloads<def_attribute_extinction_value,
            HG_TEMPLATE_FLOAT_TYPES>(m, "");

    add_type_overloads<def_attribute_height,
            HG_TEMPLATE_NUMERIC_TYPES>(m, "");

    add_type_overloads<def_attribute_children_pair_sum_product,
            int32_t, uint32_t, int64_t, uint64_t, float, double>(m, "");
}


<|start_filename|>include/higra/hierarchy/binary_partition_tree.hpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#pragma once

#include "common.hpp"
#include "../graph.hpp"
#include "hierarchy_core.hpp"
#include "../structure/fibonacci_heap.hpp"
#include "xtensor/xview.hpp"
#include "xtensor/xnoalias.hpp"
#include <string>

namespace hg {

    namespace binary_partition_tree_internal {

        template<typename T>
        struct heap_element {
            using self_t = heap_element<T>;
            T value;
            index_t index;

            bool operator==(const self_t &rhs) const { return value == rhs.value; }

            bool operator!=(const self_t &rhs) const { return value != rhs.value; }

            bool operator<(const self_t &rhs) const { return value < rhs.value; }

            bool operator>(const self_t &rhs) const { return value > rhs.value; }

            bool operator<=(const self_t &rhs) const { return value <= rhs.value; }

            bool operator>=(const self_t &rhs) const { return value >= rhs.value; }

        };

        /**
         * This structure is provided by the binary partition algorithm when two nodes are merged in order to
         * compute the edge weight between the newly created node and one of its neighbouring node.
         * @tparam T Value type of edge weights
         */
        template<typename T>
        struct new_neighbour {
            using value_type = T;
        private:
            index_t m_neighbour_vertex;
            index_t m_edge1_index;
            index_t m_edge2_index;
            mutable T m_new_edge_weight;

        public:

            /**
             *
             * @param neighbour_vertex The index of the existing neighbour of the newly created node
             * @param edge1_index The index of the edge linking the first merged node with the neighbouring node
             * @param edge2_index The index of the edge linking the second merged node with the neighbouring node:
             * might be set to invalid_index (default value) if no such edge exists.
             */
            new_neighbour(index_t neighbour_vertex, index_t edge1_index, index_t edge2_index = invalid_index) :
                    m_neighbour_vertex(neighbour_vertex), m_edge1_index(edge1_index), m_edge2_index(edge2_index) {

            }

            /**
             * Number of edges between the merged nodes and the neighbour node.
             * @return
             */
            auto num_edges() const {
                return (m_edge2_index == invalid_index) ? 1 : 2;
            }

            /**
             * The index of the edge linking the first merged node with the neighbouring node.
             * @return
             */
            const auto &first_edge_index() const {
                return m_edge1_index;
            }

            /**
             * The index of the edge linking the first merged node with the neighbouring node.
             * @return
             */
            auto &first_edge_index() {
                return m_edge1_index;
            }

            /**
             * The index of the edge linking the second merged node with the neighbouring node (invalid_index if num_edges() < 2).
             * @return
             */
            const auto &second_edge_index() const {
                return m_edge2_index;
            }

            /**
            * The index of the edge linking the second merged node with the neighbouring node (invalid_index if num_edges() < 2).
            * @return
            */
            auto &second_edge_index() {
                return m_edge2_index;
            }

            /**
             * The index of the neighbour node.
             * @return
             */
            const auto &neighbour_vertex() const {
                return m_neighbour_vertex;
            }

            /**
             * The index of the neighbour node.
             * @return
             */
            auto &neighbour_vertex() {
                return m_neighbour_vertex;
            }

            /**
             * The new value of the edge linking the new node to the new neighbour.
             * @return
             */
            auto &new_edge_weight() const {
                return m_new_edge_weight;
            }

            /**
             * The index of the edge linking the new node to the neighbour node.
             * @return
             */
            auto new_edge_index() const {
                return m_edge1_index;
            }

        };

        /**
     * Weighting function to be used in conjunction to the binary_partition_tree method in order to perform a single linkage clustering.
     *
     * Given a graph G, with initial edge weights W,
     * the distance d(X,Y) between any two regions X, Y is defined as :
     *      d(X,Y) = min {W({x,y}) | x in X, y in Y, {x,y} in G }
     *
     * Warning: this is a demonstration: in practice, the bptCanonical function (hierarchy_core.hpp)
     * can compute the single linkage clustering more efficiently.
     *
     * Consider using the helper factory function make_binary_partition_tree_min_linkage
     *
     * @tparam T
     */
        // no used, better implementation in bpt_canonical
        /*
        template<typename T>
        struct binary_partition_tree_min_linkage {

            T &m_weights;


            binary_partition_tree_min_linkage(T &weights) : m_weights(weights) {
                HG_LOG_INFO("Please consider using bpt_canonical to compute the minimum linkage binary partition tree for improved performances.");
            }

            template<typename graph_t, typename neighbours_t>
            void operator()(const graph_t &g,
                            index_t fusion_edge_index,
                            index_t new_region,
                            index_t merged_region1,
                            index_t merged_region2,
                            neighbours_t &new_neighbours) {

                for (auto &n: new_neighbours) {
                    auto min_value = m_weights[n.first_edge_index()];
                    if (n.num_edges() > 1) {
                        if (m_weights[n.second_edge_index()] < min_value)
                            min_value = m_weights[n.second_edge_index()];
                    }
                    n.new_edge_weight() = min_value;
                    m_weights[n.new_edge_index()] = min_value;
                }
            }
        };*/


        /**
        * Weighting function to be used in conjunction to the binary_partition_tree method in order to perform a complete linkage clustering.
        *
        * Given a graph G, with initial edge weights W,
        * the distance d(X,Y) between any two regions X, Y is defined as :
        *      d(X,Y) = max {W({x,y}) | x in X, y in Y, {x,y} in G }
        *
        *
        * @tparam T
        */
        template<typename T>
        struct binary_partition_tree_complete_linkage_weighting_functor {
            using value_type = typename T::value_type;

            array_1d<value_type> m_weights;

            /**
             * Initialize the clustering with given edge weights
             * @param weights
             */
            binary_partition_tree_complete_linkage_weighting_functor(const xt::xexpression<T> &weights) :
                    m_weights(weights) {
            }

            template<typename graph_t, typename neighbours_t>
            void operator()(const graph_t &g,
                            index_t fusion_edge_index,
                            index_t new_region,
                            index_t merged_region1,
                            index_t merged_region2,
                            neighbours_t &new_neighbours) {

                for (auto &n: new_neighbours) {
                    auto max_value = m_weights[n.first_edge_index()];
                    if (n.num_edges() > 1) {
                        if (max_value < m_weights[n.second_edge_index()])
                            max_value = m_weights[n.second_edge_index()];
                    }
                    n.new_edge_weight() = max_value;
                    m_weights[n.new_edge_index()] = max_value;
                }
            }
        };

        /**
        * Weighting function to be used in conjunction to the binary_partition_tree method in order to perform an average linkage clustering.
        *
        * Given a graph G, with initial edge values V with associated weights W,
        * the distance d(X,Y) between any two regions X, Y is defined as :
        *      d(X,Y) = (1 / Z) + sum_{x in X, y in Y, {x,y} in G} V({x,y}) x W({x,y})
        * with Z = sum_{x in X, y in Y, {x,y} in G} W({x,y})
        *
        * @tparam T
        */
        template<typename T>
        struct binary_partition_tree_average_linkage_weighting_functor {
            using value_type = typename T::value_type;

            array_1d<value_type> m_values;
            array_1d<value_type> m_weights;

            binary_partition_tree_average_linkage_weighting_functor(const xt::xexpression<T> &values,
                                                                    const xt::xexpression<T> &weights)
                    : m_values(values), m_weights(weights) {
                hg_assert_same_shape(m_values, m_weights);
            }

            template<typename graph_t, typename neighbours_t>
            void operator()(const graph_t &g,
                            index_t fusion_edge_index,
                            index_t new_region,
                            index_t merged_region1,
                            index_t merged_region2,
                            neighbours_t &new_neighbours) {

                for (auto &n: new_neighbours) {
                    value_type new_value;
                    value_type new_weight;
                    if (n.num_edges() > 1) {
                        new_weight = m_weights[n.first_edge_index()] + m_weights[n.second_edge_index()];
                        new_value = (m_values[n.first_edge_index()] * m_weights[n.first_edge_index()]
                                     + m_values[n.second_edge_index()] * m_weights[n.second_edge_index()])
                                    / new_weight;
                    } else {
                        new_weight = m_weights[n.first_edge_index()];
                        new_value = m_values[n.first_edge_index()];
                    }
                    n.new_edge_weight() = new_value;
                    m_values[n.new_edge_index()] = new_value;
                    m_weights[n.new_edge_index()] = new_weight;
                }
            }
        };

        /**
       * Weighting function to be used in conjunction to the binary_partition_tree method in order to perform an exponential linkage clustering.
       *
       * Given a graph G, with initial edge values V with associated weights W and a parameter alpha in R,
       * the distance d(X,Y) between any two regions X, Y is defined as :
       *      d(X,Y) = (1 / Z) + sum_{x in X, y in Y, {x,y} in G} W({x,y}) x exp(alpha * V({x,y})) x V({x,y})
       * with Z = sum_{x in X, y in Y, {x,y} in G} W({x,y}) x exp(alpha * V({x,y}))
       *
       * @tparam T
       */
        template<typename T>
        struct binary_partition_tree_exponential_linkage_weighting_functor {
            using value_type = typename T::value_type;

            array_1d<value_type> m_values;
            array_1d<value_type> m_weights;
            value_type m_alpha;

            binary_partition_tree_exponential_linkage_weighting_functor(
                    const xt::xexpression<T> &xvalues,
                    const xt::xexpression<T> &xweights,
                    const value_type &alpha)
                    : m_alpha(alpha) {
                auto &values = xvalues.derived_cast();
                auto &weights = xweights.derived_cast();
                hg_assert_same_shape(values, weights);

                m_weights = weights * xt::exp(alpha * values);
                m_values = m_weights * values;
            }

            template<typename graph_t, typename neighbours_t>
            void operator()(const graph_t &g,
                            index_t fusion_edge_index,
                            index_t new_region,
                            index_t merged_region1,
                            index_t merged_region2,
                            neighbours_t &new_neighbours) {

                for (auto &n: new_neighbours) {
                    value_type new_value;
                    value_type new_weight;
                    if (n.num_edges() > 1) {
                        new_weight = m_weights[n.first_edge_index()] + m_weights[n.second_edge_index()];
                        new_value = (m_values[n.first_edge_index()] + m_values[n.second_edge_index()]);
                    } else {
                        new_weight = m_weights[n.first_edge_index()];
                        new_value = m_values[n.first_edge_index()];
                    }
                    n.new_edge_weight() = new_value / new_weight;
                    m_values[n.new_edge_index()] = new_value;
                    m_weights[n.new_edge_index()] = new_weight;
                }
            }
        };

        /**
       * Weighting function to be used in conjunction to the binary_partition_tree method in order to perform a Ward linkage clustering.
       *
       * @tparam T
       */
        template<typename T1, typename T2>
        struct binary_partition_tree_ward_linkage_weighting_functor {

        private:
            array_1d<double> m_sizes;
            array_2d<double> m_centroids;
            index_t m_dim;

        public:

            binary_partition_tree_ward_linkage_weighting_functor(
                    const xt::xexpression<T1> &xvertex_centroids,
                    const xt::xexpression<T2> &xvertex_sizes) {
                auto &vertex_centroids = xvertex_centroids.derived_cast();
                auto &vertex_sizes = xvertex_sizes.derived_cast();
                hg_assert_1d_array(vertex_sizes);
                hg_assert(vertex_centroids.dimension() == 2,
                          "vertex_centroids must be 2d (each vertex centroid is a 1d vector).");
                hg_assert(vertex_centroids.shape(0) == vertex_sizes.shape(0),
                          "vertex_centroids and vertex_sizes first dimension must be equal.");

                auto num_elem = vertex_sizes.size() * 2 - 1;
                m_dim = vertex_centroids.shape(1);

                m_sizes = xt::empty<double>({num_elem});
                xt::noalias(xt::view(m_sizes, xt::range(0, vertex_sizes.size()))) = vertex_sizes;
                m_centroids = xt::empty<double>({num_elem, vertex_centroids.shape(1)});
                xt::noalias(
                        xt::view(m_centroids, xt::range(0, vertex_centroids.shape(0)), xt::all())) = vertex_centroids;

            }

            template<typename graph_t>
            auto get_weights(const graph_t &graph) {
                array_1d<double> weights = xt::empty<double>({num_edges(graph)});
                for (auto e: edge_iterator(graph)) {
                    weights(e) = cluster_distance(source(e, graph), target(e, graph));
                }
                return weights;
            };

            template<typename graph_t, typename neighbours_t>
            void operator()(const graph_t &g,
                            index_t fusion_edge_index,
                            index_t new_region,
                            index_t merged_region1,
                            index_t merged_region2,
                            neighbours_t &new_neighbours) {

                // merge of c1 and c2, neighbour is c3
                auto n1 = m_sizes(merged_region1);
                auto n2 = m_sizes(merged_region2);
                auto new_size = n1 + n2;
                m_sizes(new_region) = new_size;

                for (index_t k = 0; k < m_dim; k++) {
                    m_centroids(new_region, k) =
                            (n1 * m_centroids(merged_region1, k) +
                             n2 * m_centroids(merged_region2, k)) / new_size;
                }

                for (auto &n: new_neighbours) {
                    double new_weight = cluster_distance(new_region, n.neighbour_vertex());

                    n.new_edge_weight() = new_weight;
                }
            }

        private:
            auto cluster_distance(index_t ci, index_t cj) {
                auto si = m_sizes(ci);
                auto sj = m_sizes(cj);
                return (si * sj) * squared_cluster_euclidean_distance(ci, cj) / (si + sj);
            }

            auto squared_cluster_euclidean_distance(index_t ci, index_t cj) {
                double r = 0;
                for (index_t k = 0; k < m_dim; k++) {
                    double tmp = m_centroids(ci, k) - m_centroids(cj, k);
                    r += tmp * tmp;
                }
                return r;
            }
        };


    }

    /**
     * Compute the binary partition tree of the graph.
     *
     * At each step:
     * 1 - the algorithm finds the edge of smallest weight.
     * 2 - the two vertices linked by this edge are merged: the new vertex is the parent of the two merged vertices
     * 3 - the weight of the edges linking the new vertex to the remaining vertices of the graph are updated according
     *      to the user provided function (weight_function)
     * 4 - repeat until a single edge remain
     *
     * The initial weight of the edges (xedge_weights) and the callback (weight_function) determine the shape of the
     * hierarchy.
     *
     * The weight_function callback can be anything that defining the operator() and should follow the following pattern:
     *
     * struct my_weighter {
     *  ...
     *
     *  template<typename graph_t, typename neighbours_t>
     *  void operator()(const graph_t &g,               // the current state of the graph
     *                  index_t fusion_edge_index,      // the edge between the two vertices being merged
     *                  index_t new_region,             // the new vertex in the graph
     *                  index_t merged_region1,         // the first vertex merged
     *                  index_t merged_region2,         // the second vertex merged
     *                  neighbours_t &new_neighbours){  // list of edges to be weighted (see below)
     *      ...
     *      for (auto &n: new_neighbours) {
     *          ...
     *          n.new_edge_weight() = new_edge_value; // define the weight of this edge
     *      }
     *  }
     *
     * Each element in the parameter new_neighbours represent an edge between the new vertex and another vertex of
     * the graph. For each element of the list, the following methods are available:
     *  - neighbour_vertex(): the other vertex
     *  - num_edges(): returns 2 if both the two merged vertices add an edge linking themselves with neighbour_vertex()
     *      and 1 otherwise
     *  - first_edge_index(): the index of the edge linking one of the merged region to neighbour_vertex()
     *  - second_edge_index(): the index of the edge linking the other merged region to neighbour_vertex() (only if num_edges()==2)
     *  - new_edge_weight(): weight of the new edge (THIS HAS TO BE DEFINED IN THE WEIGHTING FUNCTION)
     *  - new_edge_index(): the index of the new edge: the weighting function will probably have to track new weight values
     *
     * Example of weighting function: binary_partition_tree_min_linkage
     *
     * @tparam graph_t
     * @tparam weighter
     * @tparam T
     * @param graph
     * @param xedge_weights
     * @param weight_function
     * @return a node weighted tree
     */
    template<typename graph_t, typename weighter, typename T>
    auto
    binary_partition_tree(const graph_t &graph, const xt::xexpression<T> &xedge_weights, weighter weight_function) {
        using weight_t = typename T::value_type;
        using heap_t = fibonacci_heap<binary_partition_tree_internal::heap_element<weight_t> >;

        auto &edge_weights = xedge_weights.derived_cast();
        hg_assert_edge_weights(graph, edge_weights);

        auto g = copy_graph<undirected_graph<hash_setS> >(graph); // optimized for removal

        auto num_points = num_vertices(g);
        auto num_nodes_tree = num_points * 2 - 1;

        array_1d<index_t> parents = xt::arange(num_nodes_tree);
        array_1d<weight_t> levels = xt::zeros<weight_t>({num_nodes_tree});

        // optimization to detect already visited neighbours during neighbour search
        array_1d<index_t> new_neighbour_indices({num_nodes_tree}, invalid_index);

        // active edges are in the heap and still present in the graph (removed edges are leazily left in the heap)
        // TODO: check for performance impact
        array_1d<bool> active = xt::zeros<bool>({num_edges(g)});

        // special structure to store the list of neighbours adjacent to the fused regions.
        std::vector<binary_partition_tree_internal::new_neighbour<weight_t> > new_neighbours;
        const decltype(new_neighbours) &const_new_neighbours = new_neighbours;

        // init heap
        heap_t heap;
        array_1d<typename heap_t::value_handle> heap_handles({num_edges(g)}, nullptr);

        for (auto v: vertex_iterator(graph)) {
            for (auto &e: out_edge_iterator(v, g)) {
                if (!active(e)) {
                    heap_handles(e) = heap.push({edge_weights(e), e});
                    active(e) = true;
                }
            }
        }

        // main loop
        size_t current_num_nodes_tree = num_points;
        while (!heap.empty() && current_num_nodes_tree < num_nodes_tree) {

            auto heap_handle = heap.top();
            auto min_element = heap_handle->get_value();

            auto fusion_edge_index = min_element.index;
            auto fusion_edge_weight = min_element.value;

            heap.pop();
            heap_handles[fusion_edge_index] = nullptr;

            if (active[fusion_edge_index]) {
                active[fusion_edge_index] = false;
                // create new region, update tree
                auto new_parent = g.add_vertex();
                auto fusion_edge = edge_from_index(fusion_edge_index, g);
                auto region1 = source(fusion_edge, g);
                auto region2 = target(fusion_edge, g);
                parents[region1] = new_parent;
                parents[region2] = new_parent;
                levels[new_parent] = fusion_edge_weight;
                current_num_nodes_tree++;

                // remove fusion edge
                remove_edge(fusion_edge_index, g);
                active[fusion_edge_index] = false;

                // search for neighbours of region1 and region2 and store them in new_neighbours
                new_neighbours.clear();
                auto explore_region = [&active, &g, &new_neighbours, &new_neighbour_indices](
                        index_t region, index_t other_region) {
                    for (auto e: out_edge_iterator(region, g)) {
                        auto n = other_vertex(e, region, g);
                        if (n != other_region) { // may happen with multiple edges
                            if (new_neighbour_indices[n] != invalid_index) {
                                new_neighbours[new_neighbour_indices[n]].second_edge_index() = e;
                            } else {
                                new_neighbour_indices[n] = new_neighbours.size();
                                new_neighbours.emplace_back(n, e);
                            }
                        } else {
                            active[index(e, g)] = false;
                        }
                    }
                };

                explore_region(region1, region2);
                explore_region(region2, region1);
                for (auto &n: new_neighbours) {
                    new_neighbour_indices[n.neighbour_vertex()] = invalid_index;
                }

                // update edge weights
                if (!new_neighbours.empty()) { // should only happen at last iteration
                    // external callback : compute new edge weights
                    weight_function(g, fusion_edge_index, new_parent, region1, region2, const_new_neighbours);

                    // process new weights, update heap and things
                    for (auto &nn: new_neighbours) {
                        if (nn.num_edges() > 1) {
                            active[nn.second_edge_index()] = false;
                            // not removed from heap: maybe not necessary
                            remove_edge(nn.second_edge_index(), g);
                        }
                        set_edge(nn.first_edge_index(), nn.neighbour_vertex(), new_parent, g);
                        heap.update(heap_handles[nn.first_edge_index()], {nn.new_edge_weight(), nn.first_edge_index()});
                        active[nn.first_edge_index()] = true;
                    }
                }
            }
        }
        return make_node_weighted_tree(tree(parents), std::move(levels));
    }


    /**
     * Binary partition tree, i.e. the agglomerative clustering, with the  minimum/single linkage rule.
     *
     * Given a graph :math:`G=(V, E)`, with initial edge weights :math:`w`,
     * the distance :math:`d(X,Y)` between any two clusters :math:`X` and :math:`Y` is
     *
     * .. math::
     *
     *      d(X,Y) = \min \{w(\{x,y\}) | x \in X, y \in Y, \{x,y\} \in E \}
     *
     * Regions are then iteratively merged following the above distance (closest first) until a single region remains.
     *
     * @tparam graph_t
     * @tparam T
     * @param graph
     * @param xedge_weights
     * @return a node weighted tree
     */
    template<typename graph_t, typename T>
    auto binary_partition_tree_min_linkage(const graph_t &graph, const xt::xexpression<T> &xedge_weights) {
        auto res = bpt_canonical(graph, xedge_weights);
        return make_node_weighted_tree(std::move(res.tree), std::move(res.altitudes));
    }

    /**
     * Binary partition tree, i.e. the agglomerative clustering, with the  maximum/complete linkage rule.
     *
     * Given a graph :math:`G=(V, E)`, with initial edge weights :math:`w`,
     * the distance :math:`d(X,Y)` between any two clusters :math:`X` and :math:`Y` is
     *
     * .. math::
     *
     *      d(X,Y) = \max \{w(\{x,y\}) | x \in X, y \in Y, \{x,y\} \in E \}
     *
     * Regions are then iteratively merged following the above distance (closest first) until a single region remains
     *
     * @tparam graph_t
     * @tparam T
     * @param graph
     * @param xedge_weights
     * @return a node weighted tree
     */
    template<typename graph_t, typename T>
    auto binary_partition_tree_complete_linkage(const graph_t &graph, const xt::xexpression<T> &xedge_weights) {
        return binary_partition_tree(
                graph,
                xedge_weights,
                binary_partition_tree_internal::binary_partition_tree_complete_linkage_weighting_functor<T>(
                        xedge_weights));
    }

    /**
     * Binary partition tree, i.e. the agglomerative clustering, with the  average linkage rule.
     *
     * Given a graph :math:`G=(V, E)`, with initial edge weights :math:`w` with associated weights :math:`w'`,
     * the distance :math:`d(X,Y)` between any two clusters :math:`X` and :math:`Y` is
     *
     * .. math::
     *
     *      d(X,Y) = \\frac{1}{Z} sum_{x \in X, y \in Y, \{x,y\} in E} w(\{x,y\}) \\times w'(\{x,y\})
     *
     * with :math:`Z = \sum_{x \in X, y \in Y, \{x,y\} \in E} w'({x,y})`.
     *
     * Regions are then iteratively merged following the above distance (closest first) until a single region remains
     *
     * @tparam graph_t
     * @tparam T
     * @param graph
     * @param xedge_weights
     * @param xedge_weight_weights
     * @return a node weighted tree
     */
    template<typename graph_t, typename T>
    auto binary_partition_tree_average_linkage(const graph_t &graph,
                                               const xt::xexpression<T> &xedge_weights,
                                               const xt::xexpression<T> &xedge_weight_weights) {
        return binary_partition_tree(
                graph,
                xedge_weights,
                binary_partition_tree_internal::binary_partition_tree_average_linkage_weighting_functor<T>(
                        xedge_weights,
                        xedge_weight_weights));
    }

    /**
     * Binary partition tree, i.e. the agglomerative clustering, with the exponential linkage rule.
     *
     * Given a graph :math:`G=(V, E)`, with initial edge weights :math:`w` with associated weights :math:`w'` and
     * real parameter :math:`\\aplha`, the distance :math:`d(X,Y)` between any two clusters :math:`X` and :math:`Y` is
     *
     * .. math::
     *
     *      d(X,Y) = \\frac{1}{Z} \sum_{x \in X, y \in Y, \{x,y\} in E} w'(\{x,y\}) \\times \exp(\\alpha * w(\{x,y\})) \\times w({x,y})
     *
     * with :math:`Z = \sum_{x \in X, y \in Y, \{x,y\} \in E} w'(\{x,y\}) \\times \exp(\\alpha * w(\{x,y\}))`.
     *
     * Regions are then iteratively merged following the above distance (closest first) until a single region remains
     *
     * Note that:
     *
     *   - :math:`\\apha=0` is equivalent to average linkage clustering
     *   - :math:`\\alpha=-\infty` is equivalent to single linkage clustering
     *   - :math:`\\alpha=+\infty` is equivalent to complete linkage clustering
     *
     * See:
     *
     *      <NAME>, <NAME>, <NAME>, <NAME> ;
     *      Supervised Hierarchical Clustering with Exponential Linkage
     *      Proceedings of the 36th International Conference on Machine Learning, PMLR 97:6973-6983, 2019.
     *
     * @tparam graph_t
     * @tparam T
     * @param graph
     * @param xedge_weights
     * @param alpha
     * @param xedge_weight_weights
     * @return a node weighted tree
     */
    template<typename graph_t, typename T>
    auto binary_partition_tree_exponential_linkage(const graph_t &graph,
                                               const xt::xexpression<T> &xedge_weights,
                                               const typename T::value_type &alpha,
                                               const xt::xexpression<T> &xedge_weight_weights) {
        return binary_partition_tree(
                graph,
                xedge_weights,
                binary_partition_tree_internal::binary_partition_tree_exponential_linkage_weighting_functor<T>(
                        xedge_weights,
                        xedge_weight_weights,
                        alpha));
    }

    /**
     * Binary partition tree, i.e. the agglomerative clustering, with the Ward linkage rule.
     *
     * Given a graph :math:`G=(V, E)`, with initial edge weights :math:`w` with associated weights :math:`w'`,
     * the distance :math:`d(X,Y)` between any two clusters :math:`X` and :math:`Y` is
     *
     * .. math::
     *
     *      d(X,Y) = \\frac{| X |\\times| Y |}{| X |+| Y |} \| \\vec{X} - \\vec{Y} \|^2
     *
     * where :math:`\\vec{X}` and :math:`\\vec{Y}` are the centroids of  :math:`X` and  :math:`Y`.
     *
     * Regions are then iteratively merged following the above distance (closest first) until a single region remains
     *
     * Note that the Ward distance is not necessarily strictly increasing when processing a non complete graph.
     * This can be corrected afterward with an altitude correction strategy. Valid values for ``altitude correction`` are:
     *
     *      - ``"none"``: nothing is done and the altitude of a node is equal to the Ward distance between its 2 children;
     *          this may not be non-decreasing
     *      - ``"max"``: the altitude of a node :math:`n` is defined as the maximum of the the Ward distance associated
     *          to each node in the subtree rooted in :math:`n`.
     *
     * @tparam graph_t
     * @tparam T1
     * @tparam T2
     * @param graph
     * @param xvertex_centroids Centroids of the graph vertices (must be a 2d array)
     * @param xvertex_sizes Size (number of elements) of the graph vertices
     * @param altitude_correction can be ``"none"`` or ``"max"`` (default)
     * @return a node weighted tree
     */
    template<typename graph_t, typename T1, typename T2>
    auto binary_partition_tree_ward_linkage(const graph_t &graph,
                                            const xt::xexpression<T1> &xvertex_centroids,
                                            const xt::xexpression<T2> &xvertex_sizes,
                                            const std::string &altitude_correction = "max") {

        auto f = binary_partition_tree_internal::binary_partition_tree_ward_linkage_weighting_functor<T1, T2>
                (xvertex_centroids, xvertex_sizes);

        auto res = binary_partition_tree(
                graph,
                f.get_weights(graph),
                f);

        auto &tree = res.tree;
        auto &altitudes = res.altitudes;
        if (altitude_correction.compare("max") == 0) {
            for (auto i: leaves_to_root_iterator(tree, leaves_it::include, root_it::exclude)) {
                altitudes(parent(i, tree)) = (std::max)(altitudes(i), altitudes(parent(i, tree)));
            }
        } else if (altitude_correction.compare("none") == 0) {

        } else {
            throw std::runtime_error("Invalid altitude_correction mode.");
        }
        return res;
    }

}


<|start_filename|>test/cpp/structure/test_fibonacci_heap.cpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#include "higra/structure/fibonacci_heap.hpp"
#include "../test_utils.hpp"
#include <random>
#include <map>
#include <algorithm>

namespace test_fibonacci_heap {

    using namespace hg;
    using namespace std;

    template<typename T>
    struct node {

        auto get_value() {
            return m_value;
        }

        node() {

        }

        node(T value, index_t index) : m_value(value), m_index(index) {

        }

        T m_value;
        index_t m_index;
    };

    template<typename T>
    struct trivial_heap {
        using value_handle = node<T>;

        bool empty() const {
            return elements.empty();
        }

        value_handle push(T value) {
            elements.emplace_back(value, m_counter++);
            return elements[elements.size() - 1];
        }

        void merge(trivial_heap &other) {
            for (auto n: other.elements)
                elements.push_back(n);
            other.elements.clear();
        }

        value_handle top() {
            return elements[imin()];
        }

        void pop() {
            if (elements.size() > 0)
                elements.erase(elements.begin() + imin());
        }

        void erase(value_handle &node) {
            auto i = find(node.m_index);
            elements.erase(elements.begin() + i);
        }

        void increase(value_handle &node, const T &value) {
            update(node, value);
        }

        void decrease(value_handle &node, const T &value) {
            update(node, value);
        }

        void update(value_handle &node, const T &value) {
            auto i = find(node.m_index);
            node.m_value = value;
            elements[i].m_value = value;
        }

        void increase(value_handle node) {
            update(node);
        }

        void decrease(value_handle node) {
            update(node);
        }

        void update(value_handle node) {
            auto i = find(node.m_index);
            elements[i].m_value = node.m_value;
        }

        void clear() {
            elements.clear();
        }

        auto size() const {
            return elements.size();
        }

    private:
        vector<node<T>> elements;

        index_t imin() {
            T minv = elements[0].get_value();
            index_t imini = 0;
            for (index_t i = 1; i < (index_t) elements.size(); i++) {
                if (elements[i].get_value() < minv) {
                    imini = i;
                    minv = elements[i].get_value();
                }
            }
            return imini;
        }

        index_t find(index_t index) {
            for (index_t i = 0; i < (index_t) elements.size(); i++)
                if (elements[i].m_index == index)
                    return i;
            throw std::runtime_error("cannot find element");
        }

        index_t m_counter = 0;
    };

    TEST_CASE("memory pool 1 block", "[fibonacci_heap]") {
        fibonacci_heap_internal::object_pool<hg::index_t> pool;
        hg::index_t *i1 = pool.allocate();

        hg::index_t *i2 = pool.allocate();
        REQUIRE((i2 - i1) == 1);
        hg::index_t *i3 = pool.allocate();
        REQUIRE((i3 - i1) == 2);
        hg::index_t *i4 = pool.allocate();
        REQUIRE((i4 - i1) == 3);

        pool.free(i3);

        hg::index_t *i5 = pool.allocate();
        REQUIRE((i5 - i1) == 2);
        hg::index_t *i6 = pool.allocate();
        REQUIRE((i6 - i1) == 4);

        pool.free(i5);
        pool.free(i4);

        hg::index_t *i7 = pool.allocate();
        REQUIRE((i7 - i1) == 3);
        hg::index_t *i8 = pool.allocate();
        REQUIRE((i8 - i1) == 2);
        hg::index_t *i9 = pool.allocate();
        REQUIRE(i9 - i1 == 5);
        hg::index_t *i10 = pool.allocate();
        REQUIRE((i10 - i1) == 6);
    }

    TEST_CASE("memory pool several blocks", "[fibonacci_heap]") {
        fibonacci_heap_internal::object_pool<hg::index_t> pool(3);
        hg::index_t *i1 = pool.allocate();
        hg::index_t *i2 = pool.allocate();
        REQUIRE(i2 - i1 == 1);
        hg::index_t *i3 = pool.allocate();
        REQUIRE(i3 - i1 == 2);

        hg::index_t *i4 = pool.allocate();
        hg::index_t *i5 = pool.allocate();
        REQUIRE(i5 - i4 == 1);
        hg::index_t *i6 = pool.allocate();
        REQUIRE(i6 - i4 == 2);

        hg::index_t *i7 = pool.allocate();
        hg::index_t *i8 = pool.allocate();
        REQUIRE(i8 - i7 == 1);

        pool.free(i6);
        pool.free(i2);
        pool.free(i4);

        hg::index_t *i9 = pool.allocate();
        REQUIRE(i9 - i4 == 0);
        hg::index_t *i10 = pool.allocate();
        REQUIRE(i10 - i1 == 1);
        hg::index_t *i11 = pool.allocate();
        REQUIRE(i11 - i4 == 2);

        hg::index_t *i12 = pool.allocate();
        REQUIRE(i12 - i7 == 2);

        hg::index_t *i13 = pool.allocate();
        hg::index_t *i14 = pool.allocate();
        REQUIRE(i14 - i13 == 1);
    }

    TEST_CASE("fibonacci heap push-top-size-empty", "[fibonacci_heap]") {
        fibonacci_heap<hg::index_t> heap;
        heap.push(10);
        REQUIRE(heap.size() == 1);
        REQUIRE(!heap.empty());
        REQUIRE(heap.top()->get_value() == 10);
        heap.push(15);
        REQUIRE(heap.size() == 2);
        REQUIRE(heap.top()->get_value() == 10);
        heap.push(8);
        REQUIRE(heap.size() == 3);
        REQUIRE(heap.top()->get_value() == 8);

        heap.clear();
        REQUIRE(heap.size() == 0);
        REQUIRE(heap.empty());
    }

    TEST_CASE("fibonacci heap push-top-size-empty 2", "[fibonacci_heap]") {
        fibonacci_heap<hg::index_t> heap;
        heap.push(10);
        heap.pop();
        REQUIRE(heap.size() == 0);
        REQUIRE(heap.empty());

        heap.push(10);
        heap.push(15);
        heap.push(8);
        heap.push(22);
        heap.push(17);

        REQUIRE(heap.top()->get_value() == 8);
        heap.pop();

        heap.push(5);
        heap.push(19);
        heap.push(2);


        REQUIRE(heap.top()->get_value() == 2);
        heap.pop();
        REQUIRE(heap.top()->get_value() == 5);
        heap.pop();
        REQUIRE(heap.top()->get_value() == 10);
        heap.pop();
        REQUIRE(heap.top()->get_value() == 15);
        heap.pop();
        REQUIRE(heap.top()->get_value() == 17);
        heap.pop();
        REQUIRE(heap.top()->get_value() == 19);
        heap.pop();
        REQUIRE(heap.top()->get_value() == 22);
        heap.pop();
        REQUIRE(heap.size() == 0);
    }


    TEST_CASE("fibonacci heap push-top-size-empty 3", "[fibonacci_heap]") {
        fibonacci_heap<hg::index_t> heap;
        heap.push(10);
        heap.push(15);
        heap.push(8);

        fibonacci_heap<hg::index_t> heap2;
        heap.push(9);
        heap.push(7);

        heap.merge(heap2);
        REQUIRE(heap.top()->get_value() == 7);
        heap.pop();
        REQUIRE(heap.top()->get_value() == 8);
        heap.pop();
        REQUIRE(heap.top()->get_value() == 9);
        heap.pop();
        REQUIRE(heap.top()->get_value() == 10);
        heap.pop();
        REQUIRE(heap.top()->get_value() == 15);
        heap.pop();
        REQUIRE(heap.size() == 0);

    }

    TEST_CASE("fibonacci heap decrease key", "[fibonacci_heap]") {
        fibonacci_heap<hg::index_t> heap;
        heap.push(10);
        heap.pop();
        REQUIRE(heap.size() == 0);
        REQUIRE(heap.empty());

        heap.push(10);
        heap.push(15);
        heap.push(8);
        auto e1 = heap.push(22);
        auto e2 = heap.push(17);

        REQUIRE(heap.top()->get_value() == 8);
        heap.pop();

        heap.push(5);
        heap.push(19);
        heap.push(2);

        heap.decrease(e2, 12);
        heap.decrease(e1, 3);

        REQUIRE(heap.top()->get_value() == 2);
        heap.pop();
        REQUIRE(heap.top()->get_value() == 3);
        heap.pop();
        REQUIRE(heap.top()->get_value() == 5);
        heap.pop();
        REQUIRE(heap.top()->get_value() == 10);
        heap.pop();
        REQUIRE(heap.top()->get_value() == 12);
        heap.pop();
        REQUIRE(heap.top()->get_value() == 15);
        heap.pop();
        REQUIRE(heap.top()->get_value() == 19);
        heap.pop();
    }

    TEST_CASE("fibonacci heap erase key", "[fibonacci_heap]") {
        fibonacci_heap<hg::index_t> heap;
        heap.push(10);
        heap.pop();
        REQUIRE(heap.size() == 0);
        REQUIRE(heap.empty());

        heap.push(10);
        heap.push(15);
        heap.push(8);
        auto e1 = heap.push(22);
        auto e2 = heap.push(17);

        REQUIRE(heap.top()->get_value() == 8);
        heap.pop();

        heap.push(5);
        heap.push(19);
        heap.push(2);

        heap.erase(e2);
        heap.erase(e1);

        REQUIRE(heap.top()->get_value() == 2);
        heap.pop();
        REQUIRE(heap.top()->get_value() == 5);
        heap.pop();
        REQUIRE(heap.top()->get_value() == 10);
        heap.pop();
        REQUIRE(heap.top()->get_value() == 15);
        heap.pop();
        REQUIRE(heap.top()->get_value() == 19);
        heap.pop();
    }

    TEST_CASE("fibonacci heap increase key", "[fibonacci_heap]") {
        fibonacci_heap<hg::index_t> heap;
        heap.push(10);
        heap.pop();
        REQUIRE(heap.size() == 0);
        REQUIRE(heap.empty());

        heap.push(10);
        heap.push(15);
        heap.push(8);
        auto e1 = heap.push(22);
        auto e2 = heap.push(17);

        REQUIRE(heap.top()->get_value() == 8);
        heap.pop();

        heap.push(5);
        heap.push(19);
        heap.push(2);

        heap.increase(e2, 25);
        heap.increase(e1, 23);

        REQUIRE(heap.top()->get_value() == 2);
        heap.pop();
        REQUIRE(heap.top()->get_value() == 5);
        heap.pop();
        REQUIRE(heap.top()->get_value() == 10);
        heap.pop();
        REQUIRE(heap.top()->get_value() == 15);
        heap.pop();
        REQUIRE(heap.top()->get_value() == 19);
        heap.pop();
        REQUIRE(heap.top()->get_value() == 23);
        heap.pop();
        REQUIRE(heap.top()->get_value() == 25);
        heap.pop();
    }

    struct rnd {
        static auto &get_rng() {
            static std::mt19937 rng(150000); //std::random_device()()
            return rng;
        }

    };

    auto random_heaps(int nbop) {
        std::uniform_int_distribution<std::mt19937::result_type> dist100(1, 100);
        std::uniform_int_distribution<std::mt19937::result_type> weights(1, 100000);

        auto &rng = rnd::get_rng();


        fibonacci_heap<hg::index_t> heap;
        trivial_heap<hg::index_t> theap;

        for (int i = 0; i < nbop; i++) {
            int op = dist100(rng);
            if (op < 80) {
                auto w = weights(rng);
                heap.push(w);
                theap.push(w);
            } else {
                if (theap.size() > 0) {
                    REQUIRE(heap.top()->get_value() == theap.top().get_value());
                    REQUIRE(heap.size() == theap.size());
                    heap.pop();
                    theap.pop();
                    if (theap.size() > 0) {
                        REQUIRE(heap.top()->get_value() == theap.top().get_value());
                    }
                    REQUIRE(heap.size() == theap.size());
                }
            }

        }
        return std::make_pair(std::move(heap), std::move(theap));
    }


    TEST_CASE("fibonacci heap randomized stress test push-pop", "[fibonacci_heap]") {
        random_heaps(1000);
        random_heaps(1000);
        random_heaps(1000);
    }

    TEST_CASE("fibonacci heap randomized stress test push-pop-merge", "[fibonacci_heap]") {

        std::uniform_int_distribution<std::mt19937::result_type> dist100(1, 100);
        std::uniform_int_distribution<std::mt19937::result_type> weights(1, 100000);

        auto &rng = rnd::get_rng();
        int nbop = 500;

        fibonacci_heap<hg::index_t> heap;
        trivial_heap<hg::index_t> theap;

        for (int i = 0; i < nbop; i++) {
            int op = dist100(rng);
            if (op < 60) {
                auto w = weights(rng);
                heap.push(w);
                theap.push(w);
            } else if (op < 80) {
                if (theap.size() > 0) {
                    auto fht = heap.top();
                    auto tht = theap.top();

                    REQUIRE(fht->get_value() == tht.get_value());
                    REQUIRE(heap.size() == theap.size());
                    heap.pop();
                    theap.pop();
                    if (theap.size() > 0) {
                        REQUIRE(heap.top()->get_value() == theap.top().get_value());
                    }
                    REQUIRE(heap.size() == theap.size());
                }
            } else {
                auto hh = random_heaps(100);
                heap.merge(hh.first);
                theap.merge(hh.second);
            }
        }
    }

    TEST_CASE("fibonacci heap randomized stress test push-pop-update-erase", "[fibonacci_heap]") {
        std::uniform_int_distribution<std::mt19937::result_type> dist100(1, 100);
        std::uniform_int_distribution<std::mt19937::result_type> weights(1, 100000);

        auto &rng = rnd::get_rng();
        int nbop = 20000;

        fibonacci_heap<hg::index_t> heap;
        trivial_heap<hg::index_t> theap;

        map<fibonacci_heap<hg::index_t>::value_handle, trivial_heap<hg::index_t>::value_handle> heapset;

        for (int i = 0; i < nbop; i++) {
            int op = dist100(rng);
            if (op < 60) {
                auto w = weights(rng);
                heapset[heap.push(w)] = theap.push(w);
            } else if (op < 80) {
                if (theap.size() > 0) {
                    auto fht = heap.top();
                    auto tht = theap.top();

                    REQUIRE(fht->get_value() == tht.get_value());
                    REQUIRE(heap.size() == theap.size());
                    heap.pop();
                    theap.pop();
                    heapset.erase(fht);
                    if (theap.size() > 0) {
                        REQUIRE(heap.top()->get_value() == theap.top().get_value());
                    }
                    REQUIRE(heap.size() == theap.size());
                }

            } else if (op < 90) {
                if (heap.size() > 0) {
                    std::uniform_int_distribution<std::mt19937::result_type> dist(0, (index_t)heapset.size() - 1);
                    auto nb = dist(rng);

                    auto it = heapset.begin();
                    std::advance(it, nb);
                    auto e = *it;

                    auto fhe = e.first;
                    auto the = e.second;

                    heap.erase(fhe);
                    theap.erase(the);
                    heapset.erase(fhe);
                }
            } else {
                if (heap.size() > 0) {


                    std::uniform_int_distribution<std::mt19937::result_type> dist(0, heapset.size() - 1);
                    auto nb = dist(rng);

                    auto it = heapset.begin();
                    std::advance(it, nb);
                    auto e = *it;

                    auto fhe = e.first;
                    auto the = e.second;

                    if (fhe->get_value() != 1) {
                        auto w = weights(rng);
                        heap.update(fhe, w);
                        theap.update(the, w);
                    }

                }
            }
        }
    }
}

<|start_filename|>benchmark/benchmark_array_accessor.cpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#include <benchmark/benchmark.h>

#include "higra/graph.hpp"
#include "xtensor/xview.hpp"
#include "xtensor/xrandom.hpp"

using namespace xt;
using namespace hg;

std::size_t min_array_size = 10;
std::size_t max_array_size = 16;
std::size_t max_array2d_size = 12;


template<typename T>
int sum1d_fori(const xt::xexpression<T> &xa) {
    auto a = xa.derived_cast();
    auto s = a.shape();
    int sum = 0;
    for (index_t i = 0; i < (index_t)s[0]; ++i) {
        sum += a(i);
    }
    return sum;
}

template<typename T>
int sum2d_fori(const xt::xexpression<T> &xa) {
    auto a = xa.derived_cast();
    auto s = a.shape();
    int sum = 0;
    for (index_t i = 0; i < (index_t)s[0]; ++i) {
        for (index_t j = 0; j < (index_t)s[1]; ++j)
            sum += a(i, j);
    }
    return sum;
}

template<typename T>
int sum_it(const T &a) {
    int sum = 0;
    for (auto v = a.begin(); v != a.end(); v++)
        sum += *v;
    return sum;
}

static void BM_1Darray_access_cstyle_fori(benchmark::State &state) {
    for (auto _ : state) {
        state.PauseTiming();

        int size = state.range(0);
        std::vector<int> array;
        array.resize(size, 1);
        state.ResumeTiming();
        int sum = 0;
        for (long i = 0; i < size; ++i)
            sum += array[i];
        bool flag;
        benchmark::DoNotOptimize(flag = (sum == size));
    }
}

BENCHMARK(BM_1Darray_access_cstyle_fori)->Range(1 << min_array_size, 1 << max_array_size);

static void BM_1Darray_access_cstyle_iter(benchmark::State &state) {
    for (auto _ : state) {
        state.PauseTiming();

        int size = state.range(0);
        std::vector<int> array;
        array.resize(size, 1);
        state.ResumeTiming();
        int sum = sum_it(array);
        bool flag;
        benchmark::DoNotOptimize(flag = (sum == size));
    }
}

BENCHMARK(BM_1Darray_access_cstyle_iter)->Range(1 << min_array_size, 1 << max_array_size);

static void BM_1Darray_access_xtensor_fori(benchmark::State &state) {
    for (auto _ : state) {
        state.PauseTiming();

        int size = state.range(0);
        xt::xtensor<int, 1> array({(ulong) size}, 1);
        state.ResumeTiming();
        int sum = sum1d_fori(array);
        bool flag;
        benchmark::DoNotOptimize(flag = (sum == size));
    }
}

BENCHMARK(BM_1Darray_access_xtensor_fori)->Range(1 << min_array_size, 1 << max_array_size);

static void BM_1Darray_access_xtensor_iter(benchmark::State &state) {
    for (auto _ : state) {
        state.PauseTiming();

        int size = state.range(0);
        xt::xtensor<int, 1> array({(ulong) size}, 1);
        state.ResumeTiming();
        int sum = sum_it(array);
        bool flag;
        benchmark::DoNotOptimize(flag = (sum == size));
    }
}

BENCHMARK(BM_1Darray_access_xtensor_iter)->Range(1 << min_array_size, 1 << max_array_size);

static void BM_1Darray_access_xarray_fori(benchmark::State &state) {
    for (auto _ : state) {
        state.PauseTiming();

        int size = state.range(0);
        xt::xarray<int> array({(ulong) size}, 1);
        state.ResumeTiming();
        int sum = sum1d_fori(array);
        bool flag;
        benchmark::DoNotOptimize(flag = (sum == size));
    }
}

BENCHMARK(BM_1Darray_access_xarray_fori)->Range(1 << min_array_size, 1 << max_array_size);

static void BM_1Darray_access_xarray_iter(benchmark::State &state) {
    for (auto _ : state) {
        state.PauseTiming();

        int size = state.range(0);
        xt::xarray<int> array({(ulong) size}, 1);
        state.ResumeTiming();
        int sum = sum_it(array);
        bool flag;
        benchmark::DoNotOptimize(flag = (sum == size));
    }
}

BENCHMARK(BM_1Darray_access_xarray_iter)->Range(1 << min_array_size, 1 << max_array_size);


static void BM_2Darray_access_cstyle_fori(benchmark::State &state) {
    for (auto _ : state) {
        state.PauseTiming();

        int size = state.range(0);
        std::vector<int> array;
        array.resize(size * size, 1);
        state.ResumeTiming();
        int sum = 0;
        for (long i = 0; i < size; ++i) {
            for (long j = 0; j < size; ++j)
                sum += array[i * size + j];
        }
        bool flag;
        benchmark::DoNotOptimize(flag = (sum == size));
    }
}

BENCHMARK(BM_2Darray_access_cstyle_fori)->Range(1 << min_array_size, 1 << max_array2d_size);

static void BM_2Darray_access_cstyle_iter(benchmark::State &state) {
    for (auto _ : state) {
        state.PauseTiming();

        int size = state.range(0);
        std::vector<int> array;
        array.resize(size * size, 1);
        state.ResumeTiming();
        int sum = sum_it(array);
        bool flag;
        benchmark::DoNotOptimize(flag = (sum == size));
    }
}

BENCHMARK(BM_2Darray_access_cstyle_iter)->Range(1 << min_array_size, 1 << max_array2d_size);

static void BM_2Darray_access_xtensor_fori(benchmark::State &state) {
    for (auto _ : state) {
        state.PauseTiming();

        int size = state.range(0);
        xt::xtensor<int, 2> array({(ulong) size, (ulong) size}, 1);
        state.ResumeTiming();
        int sum = sum2d_fori(array);
        bool flag;
        benchmark::DoNotOptimize(flag = (sum == size));
    }
}

BENCHMARK(BM_2Darray_access_xtensor_fori)->Range(1 << min_array_size, 1 << max_array2d_size);

static void BM_2Darray_access_xtensor_iter(benchmark::State &state) {
    for (auto _ : state) {
        state.PauseTiming();

        int size = state.range(0);
        xt::xtensor<int, 2> array({(ulong) size, (ulong) size}, 1);
        state.ResumeTiming();
        int sum = sum_it(array);
        bool flag;
        benchmark::DoNotOptimize(flag = (sum == size));
    }
}

BENCHMARK(BM_2Darray_access_xtensor_iter)->Range(1 << min_array_size, 1 << max_array2d_size);

static void BM_2Darray_access_xarray_fori(benchmark::State &state) {
    for (auto _ : state) {
        state.PauseTiming();

        int size = state.range(0);
        xt::xarray<int> array({(ulong) size, (ulong) size}, 1);
        state.ResumeTiming();
        int sum = sum2d_fori(array);
        bool flag;
        benchmark::DoNotOptimize(flag = (sum == size));
    }
}

BENCHMARK(BM_2Darray_access_xarray_fori)->Range(1 << min_array_size, 1 << max_array2d_size);

static void BM_2Darray_access_xarray_iter(benchmark::State &state) {
    for (auto _ : state) {
        state.PauseTiming();

        int size = state.range(0);
        xt::xarray<int> array({(ulong) size, (ulong) size}, 1);
        state.ResumeTiming();
        int sum = sum_it(array);
        bool flag;
        benchmark::DoNotOptimize(flag = (sum == size));
    }
}

BENCHMARK(BM_2Darray_access_xarray_iter)->Range(1 << min_array_size, 1 << max_array2d_size);

<|start_filename|>include/higra/accumulator/tree_contour_accumulator.hpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#pragma once

#include "../graph.hpp"
#include "accumulator.hpp"
#include "../structure/details/light_axis_view.hpp"

namespace hg {

    namespace tree_contour_accumulator_detail {

        template<bool vectorial,
                typename graph_t,
                typename tree_t,
                typename T,
                typename T2,
                typename accumulator_t,
                typename output_t = typename T::value_type>
        auto accumulate_on_contours_impl(const graph_t &graph,
                                         const tree_t &tree,
                                         const xt::xexpression<T> &xinput,
                                         const xt::xexpression<T2> &xdetph,
                                         const accumulator_t accumulator) {
            HG_TRACE();
            auto &input = xinput.derived_cast();
            hg_assert_node_weights(tree, input);
            auto &depth = xdetph.derived_cast();
            hg_assert_node_weights(tree, depth);
            hg_assert_1d_array(depth);
            hg_assert_integral_value_type(depth);

            auto data_shape = std::vector<size_t>(input.shape().begin() + 1, input.shape().end());
            auto output_shape = accumulator_t::get_output_shape(data_shape);
            output_shape.insert(output_shape.begin(), num_edges(graph));

            array_nd<output_t> output = array_nd<output_t>::from_shape(output_shape);

            auto input_view = make_light_axis_view<vectorial>(input);
            auto output_view = make_light_axis_view<vectorial>(output);
            auto acc = accumulator.template make_accumulator<vectorial>(output_view);

            for (auto e: edge_iterator(graph)) {
                auto n1 = source(e, graph);
                auto n2 = target(e, graph);
                auto i = index(e, graph);

                output_view.set_position(i);
                acc.set_storage(output_view);
                acc.initialize();

                while (n1 != n2) {
                    auto dn1 = depth(n1);
                    auto dn2 = depth(n2);
                    auto new_n1 = n1;
                    auto new_n2 = n2;
                    if (dn1 >= dn2) {
                        input_view.set_position(n1);
                        acc.accumulate(input_view.begin());
                        new_n1 = parent(n1, tree);
                    }
                    if (dn2 >= dn1) {
                        input_view.set_position(n2);
                        acc.accumulate(input_view.begin());
                        new_n2 = parent(n2, tree);
                    }
                    n1 = new_n1;
                    n2 = new_n2;
                }
                acc.finalize();
            }

            return output;
        };

    }

    template<typename graph_t, typename tree_t, typename T, typename T1, typename accumulator_t, typename output_t = typename T::value_type>
    auto accumulate_on_contours(const graph_t &graph,
                                const tree_t &tree,
                                const xt::xexpression<T> &xinput,
                                const xt::xexpression<T1> &xdepth,
                                const accumulator_t &accumulator) {
        auto &input = xinput.derived_cast();
        if (input.dimension() == 1) {
            return tree_contour_accumulator_detail::accumulate_on_contours_impl<false>(graph,
                                                                                       tree,
                                                                                       xinput,
                                                                                       xdepth,
                                                                                       accumulator);
        } else {
            return tree_contour_accumulator_detail::accumulate_on_contours_impl<true>(graph,
                                                                                      tree,
                                                                                      xinput,
                                                                                      xdepth,
                                                                                      accumulator);
        }
    };

}

<|start_filename|>include/higra/assessment/py_dendrogram_purity.hpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#pragma once

#include "../graph.hpp"
#include "../attribute/tree_attribute.hpp"
#include "../accumulator/tree_accumulator.hpp"

namespace hg {

    using namespace xt;
    using namespace xt::placeholders;

    /**
     * Weighted average of the purity of each node of the tree with respect to a ground truth
     * labelization of the tree leaves.
     *
     * Let :maht:`T` be a tree with leaves :maht:`V=\{1, \ldots, n\}`.
     * Let :maht:`C=\{C_1, \ldots, C_K\}` be a partition of :maht:`V` into :maht:`k` (label) sets.
     *
     * The purity of a subset :maht:`X` of :maht:`V` with respect to class :maht:`C_\ell\in C` is the fraction of
     * elements of :maht:`X` that belongs to class :maht:`C_\ell`:
     *
     * .. math::
     *
     *      pur(X, C_\ell) = \\frac{| X \cap C_\ell |}{| X |}.
     *
     * The purity of :maht:`T` is the defined as:
     *
     * .. math::
     *
     *      pur(T) = \\frac{1}{Z}\sum_{k=1}^{K}\sum_{x,y\in C_k, x\neq y} pur(lca_T(x,y), C_k)
     *
     * with :math:`Z=| \{\{x,y\} \subseteq V \mid x\neq y, \exists k, \{x,y\}\subseteq C_k\} |`.
     *
     * :See:
     *
     *      Heller, <NAME>., and <NAME>. "Bayesian hierarchical clustering."
     *      Proc. ICML. ACM, 2005.
     *
     * :Complexity:
     *
     * The dendrogram purity is computed in :math:`\mathcal{O}(N\times K \times C^2)` with :math:`N` the number of nodes
     * in the tree, :math:`K` the number of classes, and :math:`C` the maximal number of children of a node in the tree.
     *
     * @tparam tree_t
     * @tparam T
     * @param tree input tree
     * @param xleaf_labels must be a 1d array with values in [0, max_label]
     * @return a score between 0 and 1 (higher is better)
     */
    template<typename tree_t, typename T>
    auto dendrogram_purity(const tree_t &tree, const xt::xexpression<T> &xleaf_labels) {
        auto &leaf_labels = xleaf_labels.derived_cast();
        hg_assert_1d_array(leaf_labels);
        hg_assert_leaf_weights(tree, leaf_labels);
        hg_assert_integral_value_type(leaf_labels);

        auto num_l = num_leaves(tree);
        auto area = attribute_area(tree);

        auto max_label = xt::amax(leaf_labels)();
        size_t num_labels = max_label + 1;
        array_2d<double> label_histo_leaves = zeros<double>({num_l, num_labels});
        for (index_t i = 0; i < (index_t) num_l; i++) {
            label_histo_leaves(i, leaf_labels(i)) = 1;
        }

        auto label_histo = accumulate_sequential(tree, label_histo_leaves, accumulator_sum());
        auto class_purity = label_histo / view(area, all(), newaxis());

        array_2d<double> weights = zeros<double>({num_vertices(tree), num_labels});
        double Z = 0;
        for (index_t i = num_l; i < (index_t) num_vertices(tree); i++) {
            for (index_t c1 = 0; c1 < (index_t)num_children(i, tree); c1++) {
                for (index_t c2 = c1 + 1; c2 < (index_t)num_children(i, tree); c2++) {
                    for (index_t l = 0; l < (index_t) num_labels; l++) {
                        double v = label_histo(child(c1, i, tree), l) * label_histo(child(c2, i, tree), l);
                        weights(i, l) += v;
                        Z += v;
                    }
                }
            }
        }

        double total = sum(view(class_purity, range(num_l, _), all()) *
                           view(weights, range(num_l, _), all()))();

        return total / Z;
    }
};


<|start_filename|>higra/structure/all.hpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#pragma once

#include "py_embedding.hpp"
#include "py_lca_fast.hpp"
#include "py_regular_graph.hpp"
#include "py_tree_graph.hpp"
#include "py_undirected_graph.hpp"


<|start_filename|>higra/algo/py_alignement.cpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#include "py_alignement.hpp"
#include "../py_common.hpp"
#include "higra/algo/alignment.hpp"
#include "xtensor-python/pyarray.hpp"
#include "xtensor-python/pytensor.hpp"

template<typename T>
using pyarray = xt::pyarray<T>;

namespace py = pybind11;

struct def_project_fine_to_coarse_labelisation {
    template<typename value_t, typename C>
    static
    void def(C &c, const char *doc) {
        c.def("_project_fine_to_coarse_labelisation",
              [](const pyarray<value_t> &labelisation_fine,
                 const pyarray<value_t> &labelisation_coarse,
                 size_t num_regions_fine,
                 size_t num_regions_coarse) {
                  return hg::project_fine_to_coarse_labelisation(
                          labelisation_fine,
                          labelisation_coarse,
                          num_regions_fine,
                          num_regions_coarse);
              },
              doc,
              py::arg("labelisation_fine"),
              py::arg("labelisation_coarse"),
              py::arg("num_regions_fine") = 0,
              py::arg("num_regions_coarse") = 0);
    }
};

struct def_from_cut {
    template<typename value_t, typename C>
    static
    void def(C &c, const char *doc) {
        c.def_static("from_graph_cut", [](const hg::ugraph &g,
                                          const pyarray<value_t> &edge_weights) {
                         return hg::make_hierarchy_aligner_from_graph_cut(g, edge_weights);
                     },
                     doc,
                     py::arg("graph"),
                     py::arg("edge_weights"));
    }
};

struct def_from_labelisation {
    template<typename value_t, typename C>
    static
    void def(C &c, const char *doc) {
        c.def_static("from_labelisation", [](const hg::ugraph &g,
                                             const pyarray<value_t> &vertex_labels) {
                         return hg::make_hierarchy_aligner_from_labelisation(g, vertex_labels);
                     },
                     doc,
                     py::arg("graph"),
                     py::arg("vertex_labels"));
    }
};

struct def_from_hierarchy {
    template<typename value_t, typename C>
    static
    void def(C &c, const char *doc) {
        c.def_static("from_hierarchy", [](const hg::ugraph &g,
                                          const hg::tree &t,
                                          const pyarray<value_t> &altitudes) {
                         return hg::make_hierarchy_aligner_from_hierarchy(g, t, altitudes);
                     },
                     doc,
                     py::arg("graph"),
                     py::arg("tree"),
                     py::arg("altitudes"));
    }
};

struct def_align_hierarchy {
    template<typename value_t, typename C>
    static
    void def(C &c, const char *doc) {
        c.def("align_hierarchy", [](
                      const hg::hierarchy_aligner &a,
                      const hg::tree &t,
                      const pyarray<value_t> &altitudes) {
                  return a.align_hierarchy(t, altitudes);
              },
              doc,
              py::arg("tree"),
              py::arg("altitudes"));
        c.def("align_hierarchy", [](
                      const hg::hierarchy_aligner &a,
                      const hg::ugraph &graph,
                      const pyarray<value_t> &saliency_map) {
                  return a.align_hierarchy(graph, saliency_map);
              },
              doc,
              py::arg("graph"),
              py::arg("saliency_map"));
        c.def("align_hierarchy", [](
                      const hg::hierarchy_aligner &a,
                      const pyarray<hg::index_t> &super_vertices,
                      const hg::tree &t,
                      const pyarray<value_t> &altitudes) {
                  return a.align_hierarchy(super_vertices, t, altitudes);
              },
              doc,
              py::arg("super_vertices"),
              py::arg("tree"),
              py::arg("altitudes"));
    }
};

void py_init_alignement(pybind11::module &m) {
    xt::import_numpy();

    add_type_overloads<def_project_fine_to_coarse_labelisation, HG_TEMPLATE_INTEGRAL_TYPES>
            (m, "");


    auto c = py::class_<hg::hierarchy_aligner>(m, "HierarchyAligner");

    add_type_overloads<def_from_cut, HG_TEMPLATE_NUMERIC_TYPES>
            (c, "Create a hierarchy aligner based on a graph cut.");

    add_type_overloads<def_from_labelisation, HG_TEMPLATE_INTEGRAL_TYPES>
            (c, "Create a hierarchy aligner based on a labelisation of graph vertices.");

    add_type_overloads<def_from_hierarchy, HG_TEMPLATE_NUMERIC_TYPES>
            (c, "Create a hierarchy aligner based on the supervertices of a hierarchy.");

    add_type_overloads<def_align_hierarchy, HG_TEMPLATE_NUMERIC_TYPES>
            (c, "Align the given hierarchy given either as a tree or as a saliency map.");

}

<|start_filename|>tools/build_wheel_msvc.bat<|end_filename|>
REM Simple script to build pypi wheels on windows
REM Very dirty: don't use it
REM Really i'm serious don't use it

@echo off
REM set pyversions=3.4 3.5 3.6 3.7
set pyversions=3.5 3.6 3.7

set wheellocation=.\wheelhouse
set tmpfolder=.\tmp-build-wheel

if exist %wheellocation% RMDIR "%wheellocation%" /s /q
MKDIR "%wheellocation%"
CALL :NORMALIZEPATH "%wheellocation%"
SET wheellocation=%RETVAL%

if exist "%tmpfolder%" RMDIR "%tmpfolder%" /s /q
MKDIR "%tmpfolder%"
CALL :NORMALIZEPATH "%tmpfolder%"
SET tmpfolder=%RETVAL%
set TEMP=.

cd %tmpfolder%

FOR %%p IN (%pyversions%) DO (
	CALL :PROCESS_VERSION %%p
)

cd ..
RMDIR "%tmpfolder%" /s /q
echo Finished, wheels are in %wheellocation%
echo To upload to pypi: twine upload %wheellocation%\*.whl

:: ========== FUNCTIONS ==========
EXIT /B

:PROCESS_VERSION
	set pyversion=%~1
	set env_name=build-wheel-py%~1
	echo Preparing environment %env_name%
	call conda remove --name %env_name% --all --yes || exit /b
	call conda create -n %env_name% python=%pyversion% pip  --yes || exit /b
	call conda activate %env_name%  || exit /b
	call conda install numpy cmake boost -c conda-forge --yes || exit /b
	call python -m pip install --upgrade pip==18.1 || exit /b
	
	echo Building project
	call git clone https://github.com/PerretB/Higra.git || exit /b
	cd Higra
	call pip wheel . || exit /b
	
	echo Testing project
	for  %%s in (higra*whl) do (
		call pip install %%s || exit /b
	)
	cd ..
	call python -c "import unittest;result=unittest.TextTestRunner().run(unittest.defaultTestLoader.discover('Higra\\test\\python\\'));exit(0 if result.wasSuccessful() else 1)" || exit /b

	echo Copying wheel
	xcopy .\Higra\higra*whl %wheellocation%
	
	echo Cleaning environment
	RMDIR Higra /s /q
	call conda deactivate || exit /b
	call conda remove --name %env_name% --all --yes || exit /b
	
	echo done
	EXIT /B

:NORMALIZEPATH
  SET RETVAL=%~dpfn1
  EXIT /B


<|start_filename|>test/cpp/structure/details/test_iterator.cpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#include "higra/structure/details/iterators.hpp"
#include "../../test_utils.hpp"


using namespace hg;

TEST_CASE("counting iterator", "[iterator]") {
    counting_iterator<> c1(0, 2);
    counting_iterator<> c2(10, 2);

    REQUIRE((*c1 == 0));
    REQUIRE((*c2 == 10));
    REQUIRE((c1[0] == 0));
    REQUIRE((c1[1] == 2));
    REQUIRE((c1[3] == 6));
    REQUIRE((c2[-2] == 6));
    REQUIRE((c1 <= c2));
    REQUIRE((c1 < c2));
    REQUIRE((c2 > c1));
    REQUIRE((c2 >= c1));
    REQUIRE((c1 + 5 == c2));
    auto c3 = ++c1;
    REQUIRE((*c3 == 2));
    REQUIRE((*c1 == 2));
    REQUIRE((*(c1+3) == 8));
    REQUIRE(((c2-c1) == 4));
    c3 = --c2;
    REQUIRE((*c3 == 8));
    REQUIRE((c2[-1] == 6));
    c3 = c1--;
    REQUIRE((*c1 == 0));
    REQUIRE((*c3 == 2));
    c3 = c1++;
    REQUIRE((*c1 == 2));
    REQUIRE((*c3 == 0));
}

<|start_filename|>test/cpp/structure/test_embedding.cpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#include "../test_utils.hpp"
#include "higra/structure/embedding.hpp"
#include "xtensor/xview.hpp"
#include "xtensor/xgenerator.hpp"
#include "xtensor/xinfo.hpp"
#include "xtensor/xeval.hpp"

namespace embedding {


    TEST_CASE("create embedding grid 1d", "[embedding]") {
        hg::embedding_grid_1d e1{10};
        REQUIRE(e1.size() == 10);
        REQUIRE(e1.dimension() == 1);

        REQUIRE(e1.contains({5}));
        REQUIRE(!e1.contains({-2}));
        REQUIRE(!e1.contains({12}));

        auto p1 = e1.lin2grid(2);
        std::vector<long> p2{2};

        REQUIRE(std::equal(p1.begin(), p1.end(), p2.begin()));
        hg::point_1d_i p3{15};

        REQUIRE((e1.contains(p1)));
        REQUIRE(!e1.contains(p3));
    }

    TEST_CASE("create embedding grid 2d", "[embedding]") {
        hg::embedding_grid_2d e1{10, 5};
        REQUIRE(e1.size() == 50);
        REQUIRE(e1.dimension() == 2);

        hg::point_2d_i p1 = {{0, 3}};
        auto p1t = e1.lin2grid(3);
        REQUIRE(std::equal(p1.begin(), p1.end(), p1t.begin()));
        REQUIRE((e1.grid2lin(p1t)) == 3);

        hg::point_2d_i p2{{2, 4}};
        auto p2t = e1.lin2grid(14);
        REQUIRE(std::equal(p2.begin(), p2.end(), p2t.begin()));
        REQUIRE(e1.grid2lin(p2) == 14);

        REQUIRE(e1.contains(p1t));
        REQUIRE(e1.contains(p2t));

        hg::point_2d_i p3{{-1l, 2l}};
        hg::point_2d_i p4{{6l, -1l}};
        hg::point_2d_i p5{{10l, 2l}};
        hg::point_2d_i p6{{6l, 5l}};
        REQUIRE(!e1.contains(p3));
        REQUIRE(!e1.contains(p4));
        REQUIRE(!e1.contains(p5));
        REQUIRE(!e1.contains(p6));
    }

    TEST_CASE("create embedding grid 3d", "[embedding]") {
        hg::embedding_grid_3d e1{10, 5, 2};
        REQUIRE(e1.size() == 100);
        REQUIRE(e1.dimension() == 3);

        hg::point_3d_i p1{{3, 2, 1}};
        auto p1t = e1.lin2grid(35);
        REQUIRE(std::equal(p1.begin(), p1.end(), p1t.begin()));
        REQUIRE(e1.grid2lin(p1) == 35);
    }

    TEST_CASE("create embedding grid from xtensor shape", "[embedding]") {
        xt::xarray<int> a = xt::zeros<int>({10, 5, 2});
        hg::embedding_grid_3d e1(a.shape());
        REQUIRE(e1.size() == 100);
        REQUIRE(e1.dimension() == 3);

        hg::point_3d_i p1{{3, 2, 1}};
        auto p1t = e1.lin2grid(35);
        REQUIRE(std::equal(p1.begin(), p1.end(), p1t.begin()));
        REQUIRE(e1.grid2lin(p1) == 35);
    }

    TEST_CASE("create embedding grid from xtensor", "[embedding]") {
        xt::xarray<hg::index_t> shape = {10, 5, 2};
        hg::embedding_grid_3d e1(shape);
        REQUIRE(e1.size() == 100);
        REQUIRE(e1.dimension() == 3);

#ifndef _MSC_VER // vs 2017 ICE
        hg::point_3d_i p1 = {{3, 2, 1}};
        auto p1t = e1.lin2grid(35);
        REQUIRE(std::equal(p1.begin(), p1.end(), p1t.begin()));
        REQUIRE(e1.grid2lin(p1) == 35);
#endif // !_MSC_VER
    }

    TEST_CASE("grid to linear coordinates", "[embedding]") {
        xt::xarray<hg::index_t> shape = {10, 5, 2};
        hg::embedding_grid_3d e1(shape);

        xt::xarray<hg::index_t> coord = {{0, 0, 0},
                                         {0, 0, 1},
                                         {0, 0, 2},
                                         {3, 2, 1}};
        auto linCoord = e1.grid2lin(coord);
        REQUIRE(linCoord.shape().size() == 1);
        REQUIRE(linCoord.shape()[0] == 4);

        REQUIRE(linCoord(0) == 0);
        REQUIRE(linCoord(1) == 1);
        REQUIRE(linCoord(2) == 2);
        REQUIRE(linCoord(3) == 35);
    }

    TEST_CASE("linear coordinates to grid", "[embedding]") {
        xt::xarray<hg::index_t> shape = {5, 10};
        hg::embedding_grid_2d e1(shape);

        xt::xarray<hg::index_t> coordLin = {{0,  1,  2,  3},
                                            {22, 42, 43, 44}};
        xt::xarray<hg::index_t> coords = {{{0, 0}, {0, 1}, {0, 2}, {0, 3}},
                                          {{2, 2}, {4, 2}, {4, 3}, {4, 4}}};

        auto res = e1.lin2grid(coordLin);
        REQUIRE((res == coords));
    }

    TEST_CASE("contains", "[embedding]") {
        xt::xarray<hg::index_t> shape = {5, 10};
        hg::embedding_grid_2d e1(shape);

        xt::xarray<hg::index_t> coords{{{0, 0}, {3, 8}, {-1, 2}},
                                       {{2, 4}, {5, 5}, {43, 44}}};

        xt::xarray<bool> ref{{true, true,  false},
                             {true, false, false}};

        auto res = e1.contains(coords);
        REQUIRE((res == ref));
    }
}


<|start_filename|>higra/assessment/py_fragmentation_curve.cpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#include "py_fragmentation_curve.hpp"
#include "../py_common.hpp"
#include "higra/assessment/fragmentation_curve.hpp"
#include "higra/assessment/partition.hpp"
#include "xtensor-python/pyarray.hpp"
#include "xtensor-python/pytensor.hpp"

using namespace hg;
namespace py = pybind11;

struct def_assesser_optimal_cut_ctr {
    template<typename value_type, typename C>
    static
    void def(C &c, const char *doc) {
        c.def(py::init<const hg::tree &,
                      const xt::pyarray<value_type> &,
                      optimal_cut_measure,
                      const xt::pytensor<index_t, 1> &,
                      hg::size_t>(),
              doc,
              py::arg("tree"),
              py::arg("ground_truth"),
              py::arg("optimal_cut_measure") = hg::optimal_cut_measure::BCE,
              py::arg("vertex_map") = xt::pytensor<index_t, 1>{},
              py::arg("max_regions") = 200);
    }
};

template<typename tree_t>
struct def_assesse_horizontal_cut {
    template<typename value_type, typename C>
    static
    void def(C &c, const char *doc) {
        c.def("_assess_fragmentation_horizontal_cut",
              [](const tree_t &tree,
                 const xt::pyarray<value_type> &altitudes,
                 const xt::pyarray<index_t> &ground_truth,
                 hg::partition_measure measure,
                 const xt::pytensor<index_t, 1> &vertex_map,
                 hg::size_t max_regions
              ) {
                  switch (measure) {
                      case partition_measure::DHamming:
                          return hg::assess_fragmentation_horizontal_cut(tree, altitudes, ground_truth,
                                                                         hg::scorer_partition_DHamming(), vertex_map,
                                                                         max_regions);
                      case partition_measure::DCovering:
                          return hg::assess_fragmentation_horizontal_cut(tree, altitudes, ground_truth,
                                                                         hg::scorer_partition_DCovering(), vertex_map,
                                                                         max_regions);
                      case partition_measure::BCE:
                          return hg::assess_fragmentation_horizontal_cut(tree, altitudes, ground_truth,
                                                                         hg::scorer_partition_BCE(), vertex_map,
                                                                         max_regions);
                      default:
                          throw std::runtime_error(
                                  "Partition measure is not known, see enumeration PartitionMeasure for legal values.");

                  }
              },
              doc,
              py::arg("tree"),
              py::arg("altitudes"),
              py::arg("ground_truth"),
              py::arg("optimal_cut_measure"),
              py::arg("vertex_map") = xt::pytensor<index_t, 1>{},
              py::arg("max_regions") = 200);
    }
};

void py_init_fragmentation_curve(pybind11::module &m) {
    xt::import_numpy();

    using fg_t = fragmentation_curve<double>;
    py::class_<fg_t>(m,
                     "FragmentationCurve",
                     "This class represents a fragmentation curve, ie the evolution of the "
                     "scores of the partitions of a hierarchy with respect to the number of regions "
                     "in those partitions.\n\n"
                     ""
                     "Example:\n\n"
                     "\tplt.plot(x=fg.num_regions(), y=fg.scores())")
            .def("num_regions", &fg_t::num_regions,
                 "Array of number of regions in the different cuts")
            .def("num_regions_normalized", &fg_t::num_regions_normalized,
                 "Array of number of regions in the different cuts divided by the number of regions in the ground-truth")
            .def("num_regions_ground_truth", &fg_t::num_regions_ground_truth,
                 "Array of number of regions in the different cuts")
            .def("scores", &fg_t::scores,
                 "Array of scores of the different cuts");


    py::enum_<optimal_cut_measure>(m, "OptimalCutMeasure",
            "Quality measures usable with optimal cut assessment")
            .value("BCE", optimal_cut_measure::BCE)
            .value("DHamming", optimal_cut_measure::DHamming)
            .value("DCovering", optimal_cut_measure::DCovering);

    auto c = py::class_<assesser_fragmentation_optimal_cut>(m, "AssesserFragmentationOptimalCut");
    add_type_overloads<def_assesser_optimal_cut_ctr, HG_TEMPLATE_INTEGRAL_TYPES>
            (c,
             "Create an assesser for hierarchy optimal cuts w.r.t. a given ground-truth partition of hierarchy"
             "leaves and the given optimal cut measure (see OptimalCutMeasure). The algorithms will explore optimal cuts containing at most "
             "max_regions regions.");

    c.def("fragmentation_curve",
          &assesser_fragmentation_optimal_cut::fragmentation_curve,
          "Fragmentation curve, i.e. for each number of region k between 1 and max_regions, "
          "the score of the optimal cut with k regions.");

    c.def("optimal_number_of_regions",
          &assesser_fragmentation_optimal_cut::optimal_number_of_regions,
          "Number of regions in the optimal cut.");

    c.def("optimal_score",
          &assesser_fragmentation_optimal_cut::optimal_score,
          "Score of the optimal cut.");

    c.def("optimal_partition",
          &assesser_fragmentation_optimal_cut::optimal_partition,
          "Labelisation of the tree vertices that corresponds to the optimal cut with"
          "the given number of regions. If the number of regions is equal to 0 (default), the "
          "global optimal cut it returned (it will contain get_optimal_number_of_regions regions).",
          py::arg("num_regions") = 0);

    add_type_overloads<def_assesse_horizontal_cut<hg::tree>, HG_TEMPLATE_NUMERIC_TYPES>
            (m,
             "Compute the fragmentation curve of the horizontal cuts in a hierarchy w.r.t. a given measure.");
}


<|start_filename|>include/higra/structure/unionfind.hpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#pragma once

#include <vector>
#include "../utils.hpp"

namespace hg {

    namespace union_find_internal {

        template<typename idx_t=index_t>
        struct union_find {

        public:

            union_find(size_t size = 0) : parent(size), rank(size) {
                for (index_t i = 0; i < (index_t)parent.size(); ++i) {
                    parent[i] = i;
                }
            }

            idx_t make_set() {
                idx_t i = parent.size();
                parent.push_back(i);
                rank.push_back(0);
                return i;
            }


            idx_t find(idx_t element) {
                idx_t i = element;
                // find canonical node i
                while (parent[i] != i)
                    i = parent[i];
                // path compression
                while (parent[element] != i) {
                    idx_t tmp = element;
                    element = parent[element];
                    parent[tmp] = i;
                }
                return i;
            }

            /**
             * Union by rank
             * @param i index of canonical node
             * @param j index of canonical node
             * @return index of the canonical node representing the union of i and j (either i or j)
             */
            idx_t link(idx_t i, idx_t j) {
                if (rank[i] > rank[j])
                    std::swap(i, j);
                else if (rank[i] == rank[j]) {
                    rank[j] += 1;
                }
                parent[i] = j;
                return j;
            }

        private:
            using container_t = std::vector<idx_t>;

            container_t parent;
            container_t rank;
        };
    }

    using union_find = union_find_internal::union_find<>;

}


<|start_filename|>test/cpp/test_utils.hpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#pragma once



#include "higra/structure/array.hpp"
#include "xtensor/xgenerator.hpp"
#include "xtensor/xio.hpp"
#include "xtensor/xstrided_view.hpp"
#include "xtensor/xview.hpp"
#include "xtensor/xmath.hpp"
#include "higra/utils.hpp"

#include <vector>
#include <algorithm>
#include <iostream>
#include <string>
#include <map>
#include <iterator>

#include "catch2/catch.hpp"

/**
 * Test if two vectors are equal
 * @tparam T
 * @param v1
 * @param v2
 * @return
 */
template <typename T>
bool vectorEqual(std::vector<T> v1, std::vector<T> v2){
    if (v1.size() != v2.size())
        return false;
    return std::equal(v1.begin(), v1.end(), v2.begin());
}

/**
 * Test if two ranges are equal
 * @tparam T1
 * @tparam T2
 * @param r1
 * @param r2
 * @return
 */
template <typename T1, typename T2>
bool rangeEqual(const T1 & r1, const T2 & r2){
    auto b1 = r1.begin();
    auto b2 = r2.begin();
    auto e1 = r1.end();
    auto e2 = r2.end();
    for(;b1 != e1 && b2 != e2; b1++, b2++){
        if(*b1 != *b2){
            return false;
        }
    }
    if(b1 != e1 || b2 != e2){
        return false;
    }
    return true;
}

/**
 * Test if two containers contain the same elements
 * @tparam T
 * @param v1
 * @param v2
 * @return
 */
template<typename T1, typename T2>
bool vectorSame(const T1 & v1, const T2 & v2) {
    if (v1.size() != v2.size())
        return false;
    return std::is_permutation(v1.begin(), v1.end(), v2.begin());
}

/**
 * Test if there exists a bijective function f such that a[i] = f(b[i]) for all i.
 *
 * Returns false if a.size() != b.size(). Does not test if the shapes of a and b are the same.
 * @tparam T1
 * @tparam T2
 * @param a
 * @param b
 * @return
 */
template <typename T1, typename T2>
bool is_in_bijection(const xt::xexpression<T1> & a, const xt::xexpression<T2> & b){
    using vt1 = typename T1::value_type;
    using vt2 = typename T2::value_type;
    auto aa = xt::flatten(a.derived_cast());
    auto bb = xt::flatten(b.derived_cast());

    if(aa.size() != bb.size())
        return false;

    std::map<vt1, vt2> equiv1;
    std::map<vt2, vt1> equiv2;

    for(long i = 0; i < (long)aa.size(); i++){
        auto v1 = aa[i];
        auto v2 = bb[i];
        if (equiv1.count(v1) > 0){
            if(! (v2 == equiv1[v1]))
                return false;
        } else{
            equiv1[v1] = v2;
        }

        if (equiv2.count(v2) > 0){
            if(! (v1 == equiv2[v2]))
                return false;
        } else{
            equiv2[v2] = v1;
        }
    }
    return true;
};

/**
 * Show a message with type name at runtime
 * @tparam T
 * @param msg
 */
 /*
template<typename T>
void showTypeName(std::string msg = "") {
    std::cout << msg << boost::typeindex::type_id<T>().pretty_name() << std::endl;
}
*/

/**
 * Do not use except if you want a compile error showing the type of the provided template parameter !
 * @tparam T
 */
template<typename T>
struct COMPILE_ERROR;

template <typename T>
bool almost_equal(const T & a, const T& b, T epsilon){
    return std::abs(a - b) < epsilon;
}

inline
bool almost_equal(const double & a, const double& b){
    return almost_equal(a, b, 1e-9);
}

inline
bool almost_equal(const float & a, const float& b){
    return almost_equal(a, b, 1e-4f);
}

template <typename T>
void show_list(const T & l) {
    std::cout << "{";
    std::copy(l.cbegin(), l.cend(), std::ostream_iterator<typename T::value_type>(std::cout, ", "));
    std::cout << "}" << std::endl;
}

<|start_filename|>test/cpp/accumulator/test_tree_contour_accumulator.cpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#include "../test_utils.hpp"
#include "higra/accumulator/tree_contour_accumulator.hpp"
#include "higra/image/graph_image.hpp"
#include "higra/attribute/tree_attribute.hpp"


using namespace hg;
using namespace std;

namespace tree_contour_accumulator {


    TEST_CASE("contour accumulator partition tree", "[tree_contour_accumulator]") {

        auto graph = get_4_adjacency_graph({3, 3});
        array_1d<index_t> parents{9, 9, 10, 11, 11, 13, 12, 12, 13, 10, 14, 14, 15, 14, 15, 15};
        auto tree = hg::tree(parents);

        auto depth = attribute_depth(tree);

        array_1d<double> node_saliency{0, 0, 0, 0, 0, 0, 20, 0, 0,
                                       5, 2, 7, 3, 8, 1, 50};

        auto result = accumulate_on_contours(graph, tree, node_saliency, depth, hg::accumulator_max());
        array_1d<double> expected{0, 7, 5, 7, 8, 0, 20, 8, 7, 0, 20, 8};
        REQUIRE(xt::allclose(result, expected));

        array_2d<double> node_saliency2{{0,  0},
                                        {1,  0},
                                        {2,  0},
                                        {3,  0},
                                        {4,  0},
                                        {5,  0},
                                        {6,  20},
                                        {7,  0},
                                        {8,  0},
                                        {9,  5},
                                        {10, 2},
                                        {11, 7},
                                        {12, 3},
                                        {13, 8},
                                        {14, 1},
                                        {15, 50}};

        auto result2 = accumulate_on_contours(graph, tree, node_saliency2, depth, hg::accumulator_sum());
        array_2d<double> expected2{{1,  0},
                                   {33,  14},
                                   {12,  5},
                                   {35,  14},
                                   {30,  10},
                                   {7,  0},
                                   {46,  31},
                                   {33,  15},
                                   {48,  11},
                                   {13, 0},
                                   {13, 20},
                                   {54, 12}};

        REQUIRE(xt::allclose(result2, expected2));
    }

    TEST_CASE("contour accumulator component tree", "[tree_contour_accumulator]") {

            auto graph = get_4_adjacency_graph({3, 3});
            array_1d<index_t> parents{9, 10, 10, 11, 12, 17, 14, 16, 15, 10, 17, 13, 13, 17, 16, 16, 17, 17};
            auto tree = hg::tree(parents);

            auto depth = attribute_depth(tree);

            array_1d<double> node_saliency{0, 0, 0, 0, 0, 0, 0, 0, 0,
                                           5, 2, 8, 1, 3, 9, 2, 8, 20};

            auto result = accumulate_on_contours(graph, tree, node_saliency, depth, hg::accumulator_max());
            array_1d<double> expected{5, 8, 0, 3, 2, 8, 9, 3, 8, 8, 9, 2};
            REQUIRE(xt::allclose(result, expected));

    }
}

<|start_filename|>include/higra/image/contour_2d.hpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#pragma once

#include "../graph.hpp"
#include "graph_image.hpp"
#include "../structure/details/iterators.hpp"
#include "../algo/graph_weights.hpp"
#include <stack>
#include <higra/algo/rag.hpp>

namespace hg {

    namespace contour_2d_internal {
        // forward declaration
        template<typename point_type>
        class polyline_contour_2d;

        template<typename point_type>
        class contour_segment_2d_iterator;

        template<typename point_type=point_2d_f>
        class contour_segment_2d {
            const polyline_contour_2d<point_type> &m_polyline;
            const index_t m_first_control_point;
            const index_t m_second_control_point;
            const index_t m_size;

        public:
            using value_type = std::pair<index_t, point_2d_f>;

            contour_segment_2d(const polyline_contour_2d<point_type> &polyline,
                               index_t first_control_point,
                               index_t second_control_point) :
                    m_polyline(polyline),
                    m_first_control_point(first_control_point),
                    m_second_control_point(second_control_point),
                    m_size(m_second_control_point - m_first_control_point + 1) {
            }


            const auto begin() const {
                return contour_segment_2d_iterator<point_type>(*this, 0);
            }

            const auto end() const {
                return contour_segment_2d_iterator<point_type>(*this, m_size);
            }

            decltype(auto) operator[](index_t i) const {
                return std::make_pair(m_polyline.m_contour_elements[i + m_first_control_point],
                                      m_polyline.m_contour_points[i + m_first_control_point]);
            }

            /**
             * First element of the contour segment
             * @return
             */
            decltype(auto) first() const {
                return (*this)[0];
            }

            /**
             * Last element of the contour segment
             * @return
             */
            decltype(auto) last() const {
                return (*this)[m_size - 1];
            }

            /**
             * Number of elements in the contour segment
             * @return
             */
            auto size() const {
                return m_size;
            }

            auto norm() const {
                auto v = first().second;
                auto w = last().second;
                return std::sqrt((v[0] - w[0]) * (v[0] - w[0]) + (v[1] - w[1]) * (v[1] - w[1]));
            };

            auto distance_to_point(const point_type &p) {
                auto v = first().second;
                auto w = last().second;
                auto l2 = std::sqrt((v[0] - w[0]) * (v[0] - w[0]) + (v[1] - w[1]) * (v[1] - w[1]));
                if (l2 == 0.0)
                    return std::sqrt((v[0] - p[0]) * (v[0] - p[0]) + (v[1] - p[1]) * (v[1] - p[1]));;   // v == w case
                return std::abs((w[0] - v[0]) * p[1] - (w[1] - v[1]) * p[0] + w[1] * v[0] - w[0] * v[1]) / l2;
            };

            auto angle() const {
                auto v = first().second;
                auto w = last().second;
                return std::atan2(v[0] - w[0], v[1] - w[1]);
            }

        };

        template<typename point_type=point_2d_f>
        class contour_segment_2d_iterator :
                public forward_iterator_facade<contour_segment_2d_iterator<point_type>,
                        typename contour_segment_2d<point_type>::value_type,
                        typename contour_segment_2d<point_type>::value_type> {
        public:

            contour_segment_2d_iterator(const contour_segment_2d<point_type> &segment, index_t position = 0) :
                    m_segment(segment),
                    m_position(position) {}

            void increment() {
                m_position++;
            }

            bool equal(contour_segment_2d_iterator<point_type> const &other) const {
                return this->m_position == other.m_position;
            }

            decltype(auto) dereference() const {
                return m_segment[m_position];
            }

        private:
            const contour_segment_2d<point_type> &m_segment;
            index_t m_position;

        };


        // forward declaration
        template<typename point_type>
        class polyline_contour_2d_iterator;

        /**
         * A polyline contour is a set of contour segments that represent a connected frontier between two regions.
         */
        template<typename point_type=point_2d_f>
        class polyline_contour_2d {
            std::vector<index_t> m_contour_elements;
            std::vector<point_type> m_contour_points;
            std::vector<index_t> m_control_points;

            friend class contour_segment_2d<point_type>;

        public:
            using value_type = contour_segment_2d<point_type>;

            polyline_contour_2d() {

            }

            void add_contour_element(index_t element, point_type coordinates) {
                m_contour_elements.push_back(element);
                m_contour_points.push_back(coordinates);
                if (m_contour_points.size() == 1) {
                    m_control_points.push_back(0);
                    m_control_points.push_back(0);
                } else {
                    m_control_points.back() = m_contour_points.size() - 1;
                }
            }

            auto operator[](index_t i) const {
                return contour_segment_2d<point_type>(*this, m_control_points[i], m_control_points[i + 1]);
            }

            auto size() const {
                return m_control_points.size() - 1;
            }

            const auto begin() const {
                return polyline_contour_2d_iterator<point_type>(*this, 0);
            }

            const auto end() const {
                return polyline_contour_2d_iterator<point_type>(*this, size());
            }

            auto number_of_contour_elements() const {
                return m_contour_elements.size();
            }

            /**
             * Subdivide the line such that the distance between the line
             * joining the extremities of the contour segment and each of its elements is lower than the threshold (
             * Ramer–Douglas–Peucker algorithm) aor smaller than the minimal specified size.
             *
             * The threshold is equal to
             *  - epsilon if relative_epsilon is false
             *  - epsilon times the distance between the segment extremities if relative_epsilon is true
             *
             * @param epsilon
             * @param relative_epsilon
             * @param minSize
             */
            void subdivide(double epsilon = 0.1,
                           bool relative_epsilon = true,
                           int min_size = 2) {

                // stack elements are the portions of the segment that have to be checked for subdivision
                stackv<std::pair<index_t, index_t>> stack;

                // if i-th element true the polyline has to be subdivided at this element
                std::vector<bool> is_subdivision_element(m_contour_elements.size(), false);

                for (index_t segment_index = 0; segment_index < (index_t) size(); segment_index++) {
                    stack.push({m_control_points[segment_index], m_control_points[segment_index + 1]});

                    // current segment points are preserved
                    is_subdivision_element[m_control_points[segment_index]] = true;
                    is_subdivision_element[m_control_points[segment_index + 1]] = true;


                    // recursive identification of subdivision elements
                    while (!stack.empty()) {
                        auto element_indexes = stack.top();
                        stack.pop();
                        auto first_element = element_indexes.first;
                        auto last_element = element_indexes.second;

                        // nothing to be done
                        if (last_element - first_element < 2)
                            continue;

                        contour_segment_2d<point_type> segment(*this, first_element, last_element);

                        auto norm_segment = segment.norm();

                        double distance_threshold;
                        if (relative_epsilon) {
                            distance_threshold = epsilon * norm_segment;
                        } else {
                            distance_threshold = epsilon;
                        }

                        auto max_distance = distance_threshold;
                        auto max_distance_element = invalid_index;

                        for (index_t i = first_element + 1; i < last_element; i++) {
                            auto &coordinate_element = m_contour_points[i];
                            auto d = segment.distance_to_point(coordinate_element);
                            if (d >= max_distance && d > min_size) {
                                max_distance = d;
                                max_distance_element = i;
                            }
                        }

                        if (max_distance_element != invalid_index) {
                            is_subdivision_element[max_distance_element] = true;
                            stack.push({first_element, max_distance_element});
                            stack.push({max_distance_element, last_element});
                        }
                    }

                    // final subdivision
                    m_control_points.clear();

                    for (index_t i = 0; i < (index_t) m_contour_elements.size(); i++) {
                        if (is_subdivision_element[i]) {
                            m_control_points.push_back(i);
                        }
                    }
                    if (m_control_points.size() == 1)
                        m_control_points.push_back(0);
                }

            }
        };

        template<typename point_type=point_2d_f>
        class polyline_contour_2d_iterator :
                public forward_iterator_facade<polyline_contour_2d_iterator<point_type>,
                        typename polyline_contour_2d<point_type>::value_type,
                        typename polyline_contour_2d<point_type>::value_type> {
        public:

            polyline_contour_2d_iterator(const polyline_contour_2d<point_type> &polyline, index_t position = 0) :
                    m_polyline(polyline),
                    m_position(position) {}

            void increment() {
                m_position++;
            }

            bool equal(polyline_contour_2d_iterator<point_type> const &other) const {
                return this->m_position == other.m_position;
            }

            decltype(auto) dereference() const {
                return m_polyline[m_position];
            }

        private:
            const polyline_contour_2d<point_type> &m_polyline;
            index_t m_position;

        };


        /**
         * A contour is a set of polyline contours that represent the frontiers separating regions.
         */
        template<typename point_type=point_2d_f>
        class contour_2d {
            std::vector<polyline_contour_2d<point_type>> m_polyline_contours;

        public:
            auto &new_polyline_contour_2d() {
                m_polyline_contours.emplace_back();
                return m_polyline_contours[m_polyline_contours.size() - 1];
            }

            auto size() {
                return m_polyline_contours.size();
            }

            auto begin() {
                return m_polyline_contours.begin();
            }

            auto end() {
                return m_polyline_contours.end();
            }

            const auto begin() const {
                return m_polyline_contours.begin();
            }

            const auto end() const {
                return m_polyline_contours.end();
            }

            auto operator[](index_t i) {
                return m_polyline_contours[i];
            }

            /**
             * Subdivide each polyline of the given contours such that the distance between the line
             * joining the extremities of the contour segment and each of its elements is lower than the threshold (
             * Ramer–Douglas–Peucker algorithm) or smaller than the minimal specified size
             *
             * The threshold is equal to
             *  - epsilon if relative_epsilon is false
             *  - epsilon times the distance between the segment extremities if relative_epsilon is true
             *
             * Implementation note: simply call subdivide on each polyline of the contour.
             *
             * @param epsilon
             * @param relative_epsilon
             * @param min_size
             */
            void subdivide(
                    double epsilon = 0.1,
                    bool relative_epsilon = true,
                    int min_size = 2) {
                for (auto &polyline: m_polyline_contours) {
                    polyline.subdivide(epsilon, relative_epsilon, min_size);
                }
            };

        };

    }

    using contour_2d = contour_2d_internal::contour_2d<point_2d_f>;

    using polyline_contour_2d = contour_2d_internal::polyline_contour_2d<point_2d_f>;

    using contour_segment_2d = contour_2d_internal::contour_segment_2d<point_2d_f>;

    /**
     * Construct a contour_2d object from a graph cut of a 2d image with a 4 adjacency (non zero edges are part of the cut).
     * @tparam graph_t
     * @tparam T
     * @param graph
     * @param embedding
     * @param xedge_weights
     * @return
     */
    template<typename graph_t, typename T>
    auto
    fit_contour_2d(const graph_t &graph,
                   const embedding_grid_2d &embedding,
                   const xt::xexpression<T> &xedge_weights) {
        HG_TRACE();
        using point_type = point_2d_f;
        const auto &edge_weights = xedge_weights.derived_cast();
        hg_assert_edge_weights(graph, edge_weights);
        hg_assert_1d_array(edge_weights);
        hg_assert(num_vertices(graph) == embedding.size(),
                  "Graph number of vertices does not match the size of the embedding.");

        contour_2d result;

        array_1d<index_t> positive_edge_index = xt::empty<index_t>({num_edges(graph)});
        for (index_t i = 0; i < (index_t) positive_edge_index.size(); i++) {
            if (edge_weights[i] > 0)
                positive_edge_index[i] = i;
            else positive_edge_index[i] = invalid_index;
        }

        auto contours_khalimsky = graph_4_adjacency_2_khalimsky(graph, embedding, positive_edge_index, true,
                                                                invalid_index);

        auto edge_coordinates = [&embedding, &graph](index_t edge_index) {
            auto &e = edge_from_index(edge_index, graph);
            auto s = source(e, graph);
            auto t = target(e, graph);
            point_type coordinates = embedding.lin2grid(s);
            if (s + 1 == t) { // horizontal edge
                coordinates[1] += 0.5;
            } else { // vertical edge
                coordinates[0] += 0.5;
            }
            return coordinates;
        };

        array_2d<bool> processed = xt::zeros<bool>(contours_khalimsky.shape());

        index_t height = contours_khalimsky.shape()[0];
        index_t width = contours_khalimsky.shape()[1];

        auto is_intersection = [&contours_khalimsky, &height, &width](
                index_t y,
                index_t x) {
            if (x == 0 || y == 0 || x == width - 1 || y == height - 1)
                return true;
            int count = 0;
            if (contours_khalimsky(y, x - 1) != invalid_index)
                count++;
            if (contours_khalimsky(y, x + 1) != invalid_index)
                count++;
            if (contours_khalimsky(y - 1, x) != invalid_index)
                count++;
            if (contours_khalimsky(y + 1, x) != invalid_index)
                count++;
            return count > 2;
        };

        enum direction {
            NORTH, EAST, SOUTH, WEST
        };

        // store the result of explore_contour_part (below)
        std::vector<index_t> contour_part;

        auto explore_contour_part = [&contours_khalimsky, &processed, &is_intersection, &contour_part](
                index_t y,
                index_t x,
                direction dir) {
            //auto &polyline = result.new_polyline_contour_2d();
            contour_part.clear();
            direction previous = dir;
            bool flag;

            do {
                processed(y, x) = true;
                index_t edge_index = contours_khalimsky(y, x);
                contour_part.push_back(edge_index);
                //polyline.add_contour_element(edge_index, edge_coordinates(edge_index));
                if (x % 2 == 0) // horizontal edge
                {
                    if (previous == NORTH) {
                        y++;
                    } else {
                        y--;
                    }
                } else { // vertical edge
                    if (previous == WEST) {
                        x++;
                    } else {
                        x--;
                    }
                }

                flag = processed(y, x) || is_intersection(y, x);
                if (!flag) {
                    processed(y, x) = true;
                    if (previous != NORTH &&
                        contours_khalimsky(y - 1, x) != invalid_index) {
                        previous = SOUTH;
                        y--;
                    } else if (previous != EAST &&
                               contours_khalimsky(y, x + 1) != invalid_index) {
                        previous = WEST;
                        x++;
                    } else if (previous != SOUTH &&
                               contours_khalimsky(y + 1, x) != invalid_index) {
                        previous = NORTH;
                        y++;
                    } else if (previous != WEST &&
                               contours_khalimsky(y, x - 1) != invalid_index) {
                        previous = EAST;
                        x--;
                    }
                }
            } while (!flag);

        };

        auto add_contour_parts_to_polyline = [&contour_part, &edge_coordinates](polyline_contour_2d &polyline,
                                                                                bool reverse = false) {
            if (reverse) {
                for (auto edge_index = contour_part.rbegin(); edge_index != contour_part.rend(); edge_index++) {
                    polyline.add_contour_element(*edge_index, edge_coordinates(*edge_index));
                }
            } else {
                for (auto edge_index = contour_part.begin(); edge_index != contour_part.end(); edge_index++) {
                    polyline.add_contour_element(*edge_index, edge_coordinates(*edge_index));
                }
            }

        };

        for (index_t y = 0; y < height; y += 2) {
            for (index_t x = 0; x < width; x += 2) {
                auto edge_index = contours_khalimsky(y, x);
                if (edge_index != invalid_index && // is there a non zero edge around this 0 face
                    !processed(y, x)) { // if so did we already processed it ?
                    processed(y, x) = true;
                    if (is_intersection(y, x)) { // explore each polyline starting from this point
                        if (x != 0 && contours_khalimsky(y, x - 1) != invalid_index && !processed(y, x - 1)) {
                            explore_contour_part(y, x - 1, EAST);
                            auto &polyline = result.new_polyline_contour_2d();
                            add_contour_parts_to_polyline(polyline);
                        }
                        if (x != width - 1 && contours_khalimsky(y, x + 1) != invalid_index && !processed(y, x + 1)) {
                            explore_contour_part(y, x + 1, WEST);
                            auto &polyline = result.new_polyline_contour_2d();
                            add_contour_parts_to_polyline(polyline);
                        }
                        if (y != 0 && contours_khalimsky(y - 1, x) != invalid_index && !processed(y - 1, x)) {
                            explore_contour_part(y - 1, x, SOUTH);
                            auto &polyline = result.new_polyline_contour_2d();
                            add_contour_parts_to_polyline(polyline);
                        }
                        if (y != height - 1 && contours_khalimsky(y + 1, x) != invalid_index && !processed(y + 1, x)) {
                            explore_contour_part(y + 1, x, NORTH);
                            auto &polyline = result.new_polyline_contour_2d();
                            add_contour_parts_to_polyline(polyline);
                        }
                    } else { // explore the two ends of the polyline passing by this point and join them
                        auto &polyline = result.new_polyline_contour_2d();
                        bool first = true;
                        if (x != 0 && contours_khalimsky(y, x - 1) != invalid_index && !processed(y, x - 1)) {
                            explore_contour_part(y, x - 1, EAST);
                            //if(first){ // impossible at first case
                            add_contour_parts_to_polyline(polyline, true);
                            first = false;
                            //}else{
                            //    add_contour_parts_to_polyline(polyline);
                            //}

                        }
                        if (x != width - 1 && contours_khalimsky(y, x + 1) != invalid_index && !processed(y, x + 1)) {
                            explore_contour_part(y, x + 1, WEST);
                            if (first) {
                                add_contour_parts_to_polyline(polyline, true);
                                first = false;
                            } else {
                                add_contour_parts_to_polyline(polyline);
                            }
                        }
                        if (y != 0 && contours_khalimsky(y - 1, x) != invalid_index && !processed(y - 1, x)) {
                            explore_contour_part(y - 1, x, SOUTH);
                            if (first) {
                                add_contour_parts_to_polyline(polyline, true);
                                first = false;
                            } else {
                                add_contour_parts_to_polyline(polyline);
                            }
                        }
                        if (y != height - 1 && contours_khalimsky(y + 1, x) != invalid_index && !processed(y + 1, x)) {
                            explore_contour_part(y + 1, x, NORTH);
                            if (first) {
                                add_contour_parts_to_polyline(polyline, true);
                                first = false;
                            } else {
                                add_contour_parts_to_polyline(polyline);
                            }
                        }
                    }


                }
            }
        }

        return result;
    }

    /**
     * Estimate the vertex perimeter and the length of the frontier associated to the edges of a
     * region adjacency graph constructed on a 2d 4 adjacency graph.
     *
     * The region boundaries are simplified with Ramer–Douglas–Peucker algorithm and is controlled
     * by the parameters epsilon, relative_epsilon, min_size. See function subdivide of the class contour_2d for more information.
     *
     * @tparam graph_t
     * @param rag_graph Region Adjacency Graph
     * @param xvertex_map Vertex map of the rag_graph
     * @param xedge_map Edge map of the rag_graph
     * @param embedding 2d shape of the input graph
     * @param graph input graph on which the region adjacency graph has been build: must be a 4 adjacency graph whose shape correspond to the given embedding
     * @param epsilon larger epsilon values will provide stronger contour shapes simplification
     * @param relative_epsilon Is epsilon given in relative or absolute units
     * @param min_size Boundaries elements smaller than min_size will be deleted
     * @return a pair composed of two 1d arrays: vertex_perimeter and edge_length.
     */
    template<typename rag_graph_t, typename graph_t, typename T>
    auto rag_2d_vertex_perimeter_and_edge_length(
            const rag_graph_t &rag_graph,
            const xt::xexpression<T> &xvertex_map,
            const xt::xexpression<T> &xedge_map,
            const embedding_grid_2d &embedding,
            const graph_t &graph,
            double epsilon = 0.1,
            bool relative_epsilon = true,
            int min_size = 2) {

        const auto &vertex_map = xvertex_map.derived_cast();
        const auto &edge_map = xedge_map.derived_cast();
        hg_assert_edge_weights(graph, edge_map);
        hg_assert_1d_array(edge_map);
        hg_assert_integral_value_type(edge_map);
        hg_assert_vertex_weights(graph, vertex_map);
        hg_assert_1d_array(vertex_map);
        hg_assert_integral_value_type(vertex_map);

        auto cut = weight_graph(graph, vertex_map, weight_functions::L0);
        auto contour2d = fit_contour_2d(graph, embedding, cut);

        contour2d.subdivide(epsilon, relative_epsilon, min_size);

        array_1d<double> vertex_perimeter = xt::zeros<double>({num_vertices(rag_graph)});
        array_1d<double> edge_length = xt::zeros<double>({num_edges(rag_graph)});

        for (auto &polyline: contour2d) {

            for (auto &segment: polyline) {
                auto segment_length = segment.norm() + 1;
                auto rag_edge_index = edge_map(segment.first().first);
                auto rag_edge = edge_from_index(rag_edge_index, rag_graph);
                edge_length(rag_edge_index) += segment_length;
                vertex_perimeter(source(rag_edge, rag_graph)) += segment_length;
                vertex_perimeter(target(rag_edge, rag_graph)) += segment_length;
            }
        }

        index_t height = embedding.shape()[0];
        index_t width = embedding.shape()[1];

        for (index_t x = 0; x < width; x++) {
            vertex_perimeter(vertex_map(embedding.grid2lin({0, x})))++;
            vertex_perimeter(vertex_map(embedding.grid2lin({height - 1, x})))++;
        }
        for (index_t y = 0; y < height; y++) {
            vertex_perimeter(vertex_map(embedding.grid2lin({y, 0})))++;
            vertex_perimeter(vertex_map(embedding.grid2lin({y, width - 1})))++;
        }

        return std::make_pair(std::move(vertex_perimeter), std::move(edge_length));
    }

    /**
     * Estimate the vertex perimeter and the length of the frontier associated to the edges of a
     * region adjacency graph constructed on a 2d 4 adjacency graph.
     *
     * The region boundaries are simplified with Ramer–Douglas–Peucker algorithm and is controlled
     * by the parameters epsilon, relative_epsilon, min_size. See function subdivide of the class contour_2d for more information.
     *
     * @tparam graph_t
     * @param rag Region Adjacency Graph
     * @param embedding 2d shape of the input graph
     * @param graph input graph on which the region adjacency graph has been build: must be a 4 adjacency graph whose shape correspond to the given embedding
     * @param epsilon larger epsilon values will provide stronger contour shapes simplification
     * @param relative_epsilon Is epsilon given in relative or absolute units
     * @param min_size Boundaries elements smaller than min_size will be deleted
     * @return a pair composed of two 1d arrays: vertex_perimeter and edge_length.
     */
    template<typename graph_t>
    auto rag_2d_vertex_perimeter_and_edge_length(
            const region_adjacency_graph &rag,
            const embedding_grid_2d &embedding,
            const graph_t &graph,
            double epsilon = 0.1,
            bool relative_epsilon = true,
            int min_size = 2) {
        return rag_2d_vertex_perimeter_and_edge_length(
                rag.rag,
                rag.vertex_map,
                rag.edge_map,
                embedding,
                graph,
                epsilon,
                relative_epsilon,
                min_size
        );
    }
}

<|start_filename|>test/cpp/image/test_tree_of_shapes.cpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#include "higra/image/tree_of_shapes.hpp"
#include "higra/image/graph_image.hpp"
#include "higra/algo/tree.hpp"
#include "xtensor/xstrided_view.hpp"
#include "xtensor/xrandom.hpp"
#include "../test_utils.hpp"
#include <set>

namespace tree_of_shapes {

    using namespace hg;
    using namespace std;

    TEST_CASE("test integer_level_multi_queue", "[tree_of_shapes]") {
        using qt = hg::tree_of_shapes_internal::integer_level_multi_queue<int, int>;

        qt q(-2, 7);

        SECTION("empty queue") {
            REQUIRE(q.size() == 0);
            REQUIRE(q.empty());
            REQUIRE(q.num_levels() == 10);
            REQUIRE(q.min_level() == -2);
            REQUIRE(q.max_level() == 7);
            for (int i = -2; i < 8; i++) {
                REQUIRE(q.level_empty(i));
            }
        }SECTION("push top pop") {
            q.push(1, 10);
            REQUIRE(!q.level_empty(1));
            REQUIRE(q.size() == 1);
            q.push(1, 7);
            REQUIRE(q.size() == 2);
            REQUIRE(q.top(1) == 10);
            q.pop(1);
            REQUIRE(q.size() == 1);
            REQUIRE(q.top(1) == 7);
            q.pop(1);
            REQUIRE(q.size() == 0);
            REQUIRE(q.level_empty(1));
        }SECTION("closest non empty") {
            q.push(0, 4);
            q.push(5, 7);
            std::vector<int> res{0, 0, 0, 0, 0, 5, 5, 5, 5, 5};
            for (int i = -2; i < 8; i++) {
                REQUIRE(q.find_closest_non_empty_level(i) == res[i + 2]);
            }
        }
    }

    TEST_CASE("test interpolate_plain_map_khalimsky2d", "[tree_of_shapes]") {
        array_1d<int> image{1, 1, 1, 1, 1, 1,
                            1, 0, 0, 3, 3, 1,
                            1, 0, 1, 1, 3, 1,
                            1, 0, 0, 3, 3, 1,
                            1, 1, 1, 1, 1, 1};

        auto result = hg::tree_of_shapes_internal::interpolate_plain_map_khalimsky_2d(image, {5, 6});

        array_3d<int> expected_result
                {{{1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1}},
                 {{1, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 3}, {1, 3}, {1, 3}, {1, 3}, {1, 3}, {1, 1}},
                 {{1, 1}, {0, 1}, {0, 0}, {0, 0}, {0, 0}, {0, 3}, {3, 3}, {3, 3}, {3, 3}, {1, 3}, {1, 1}},
                 {{1, 1}, {0, 1}, {0, 0}, {0, 1}, {0, 1}, {0, 3}, {1, 3}, {1, 3}, {3, 3}, {1, 3}, {1, 1}},
                 {{1, 1}, {0, 1}, {0, 0}, {0, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 3}, {3, 3}, {1, 3}, {1, 1}},
                 {{1, 1}, {0, 1}, {0, 0}, {0, 1}, {0, 1}, {0, 3}, {1, 3}, {1, 3}, {3, 3}, {1, 3}, {1, 1}},
                 {{1, 1}, {0, 1}, {0, 0}, {0, 0}, {0, 0}, {0, 3}, {3, 3}, {3, 3}, {3, 3}, {1, 3}, {1, 1}},
                 {{1, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 3}, {1, 3}, {1, 3}, {1, 3}, {1, 3}, {1, 1}},
                 {{1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1}}};

        REQUIRE((result == xt::reshape_view(expected_result, {result.shape()[0], result.shape()[1]})));
    }

    TEST_CASE("test sort_vertices_tree_of_shapes small integers", "[tree_of_shapes]") {
        array_nd<char> plain_map =
                {{{1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1}},
                 {{1, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 3}, {1, 3}, {1, 3}, {1, 3}, {1, 3}, {1, 1}},
                 {{1, 1}, {0, 1}, {0, 0}, {0, 0}, {0, 0}, {0, 3}, {3, 3}, {3, 3}, {3, 3}, {1, 3}, {1, 1}},
                 {{1, 1}, {0, 1}, {0, 0}, {0, 1}, {0, 1}, {0, 3}, {1, 3}, {1, 3}, {3, 3}, {1, 3}, {1, 1}},
                 {{1, 1}, {0, 1}, {0, 0}, {0, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 3}, {3, 3}, {1, 3}, {1, 1}},
                 {{1, 1}, {0, 1}, {0, 0}, {0, 1}, {0, 1}, {0, 3}, {1, 3}, {1, 3}, {3, 3}, {1, 3}, {1, 1}},
                 {{1, 1}, {0, 1}, {0, 0}, {0, 0}, {0, 0}, {0, 3}, {3, 3}, {3, 3}, {3, 3}, {1, 3}, {1, 1}},
                 {{1, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 3}, {1, 3}, {1, 3}, {1, 3}, {1, 3}, {1, 1}},
                 {{1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1}}};
        plain_map.reshape({11 * 9, 2});

        auto g = get_4_adjacency_implicit_graph({9, 11});
        auto result = hg::tree_of_shapes_internal::sort_vertices_tree_of_shapes(g, plain_map, 1);
        auto &sorted_vertex_indices = result.first;
        auto &enqueued_level = result.second;

        set<index_t> zero_vertices{24, 25, 35, 26, 46, 57, 68, 69, 70};
        set<index_t> three_vertices{28, 29, 30, 41, 52, 72, 63, 73, 74};
        index_t num_ones = 9 * 11 - 2 * 9;
        set<index_t> zero_vertices_found{sorted_vertex_indices.begin() + num_ones,
                                         sorted_vertex_indices.begin() + num_ones + 9};
        REQUIRE(zero_vertices == zero_vertices_found);
        set<index_t> three_vertices_found{sorted_vertex_indices.begin() + num_ones + 9,
                                          sorted_vertex_indices.end()};
        REQUIRE(three_vertices == three_vertices_found);

        array_nd<char> expected_enqueued_level{{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
                                               {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
                                               {1, 1, 0, 0, 0, 1, 3, 3, 3, 1, 1},
                                               {1, 1, 0, 1, 1, 1, 1, 1, 3, 1, 1},
                                               {1, 1, 0, 1, 1, 1, 1, 1, 3, 1, 1},
                                               {1, 1, 0, 1, 1, 1, 1, 1, 3, 1, 1},
                                               {1, 1, 0, 0, 0, 1, 3, 3, 3, 1, 1},
                                               {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
                                               {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}};
        expected_enqueued_level.reshape({11 * 9});
        REQUIRE((enqueued_level == expected_enqueued_level));
    }

    TEST_CASE("test sort_vertices_tree_of_shapes float", "[tree_of_shapes]") {
        array_nd<float> plain_map =
                {{{1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1}},
                 {{1, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 3}, {1, 3}, {1, 3}, {1, 3}, {1, 3}, {1, 1}},
                 {{1, 1}, {0, 1}, {0, 0}, {0, 0}, {0, 0}, {0, 3}, {3, 3}, {3, 3}, {3, 3}, {1, 3}, {1, 1}},
                 {{1, 1}, {0, 1}, {0, 0}, {0, 1}, {0, 1}, {0, 3}, {1, 3}, {1, 3}, {3, 3}, {1, 3}, {1, 1}},
                 {{1, 1}, {0, 1}, {0, 0}, {0, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 3}, {3, 3}, {1, 3}, {1, 1}},
                 {{1, 1}, {0, 1}, {0, 0}, {0, 1}, {0, 1}, {0, 3}, {1, 3}, {1, 3}, {3, 3}, {1, 3}, {1, 1}},
                 {{1, 1}, {0, 1}, {0, 0}, {0, 0}, {0, 0}, {0, 3}, {3, 3}, {3, 3}, {3, 3}, {1, 3}, {1, 1}},
                 {{1, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 3}, {1, 3}, {1, 3}, {1, 3}, {1, 3}, {1, 1}},
                 {{1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1}}};
        plain_map.reshape({11 * 9, 2});

        auto g = get_4_adjacency_implicit_graph({9, 11});
        auto result = hg::tree_of_shapes_internal::sort_vertices_tree_of_shapes(g, plain_map, 1);
        auto &sorted_vertex_indices = result.first;
        auto &enqueued_level = result.second;

        set<index_t> zero_vertices{24, 25, 35, 26, 46, 57, 68, 69, 70};
        set<index_t> three_vertices{28, 29, 30, 41, 52, 72, 63, 73, 74};
        index_t num_ones = 9 * 11 - 2 * 9;
        set<index_t> zero_vertices_found{sorted_vertex_indices.begin() + num_ones,
                                         sorted_vertex_indices.begin() + num_ones + 9};
        REQUIRE(zero_vertices == zero_vertices_found);
        set<index_t> three_vertices_found{sorted_vertex_indices.begin() + num_ones + 9,
                                          sorted_vertex_indices.end()};
        REQUIRE(three_vertices == three_vertices_found);

        array_nd<float> expected_enqueued_level{{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
                                                {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
                                                {1, 1, 0, 0, 0, 1, 3, 3, 3, 1, 1},
                                                {1, 1, 0, 1, 1, 1, 1, 1, 3, 1, 1},
                                                {1, 1, 0, 1, 1, 1, 1, 1, 3, 1, 1},
                                                {1, 1, 0, 1, 1, 1, 1, 1, 3, 1, 1},
                                                {1, 1, 0, 0, 0, 1, 3, 3, 3, 1, 1},
                                                {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
                                                {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}};
        expected_enqueued_level.reshape({11 * 9});
        REQUIRE((enqueued_level == expected_enqueued_level));
    }

TEMPLATE_TEST_CASE("test tree of shapes no padding", "[tree_of_shapes]", char, float) {
    array_2d <TestType> image{{1, 1, 1, 1, 1, 1},
                              {1, 0, 0, 3, 3, 1},
                              {1, 0, 1, 1, 3, 1},
                              {1, 0, 0, 3, 3, 1},
                              {1, 1, 1, 1, 1, 1}};

    auto result = component_tree_tree_of_shapes_image2d(image, tos_padding::none, false);
    auto &tree = result.tree;
    auto &altitudes = result.altitudes;
    array_1d <index_t>
            ref_parents{101, 101, 101, 101, 101, 101, 101, 101, 101, 101, 101,
                        101, 101, 101, 101, 101, 101, 101, 101, 101, 101, 101,
                        101, 101, 100, 100, 100, 101, 99, 99, 99, 101, 101,
                        101, 101, 100, 101, 101, 101, 101, 101, 99, 101, 101,
                        101, 101, 100, 101, 101, 101, 101, 101, 99, 101, 101,
                        101, 101, 100, 101, 101, 101, 101, 101, 99, 101, 101,
                        101, 101, 100, 100, 100, 101, 99, 99, 99, 101, 101,
                        101, 101, 101, 101, 101, 101, 101, 101, 101, 101, 101,
                        101, 101, 101, 101, 101, 101, 101, 101, 101, 101, 101,
                        101, 101, 101};
    array_1d <TestType>
            ref_altitudes{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
                          1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
                          1, 1, 0, 0, 0, 1, 3, 3, 3, 1, 1,
                          1, 1, 0, 1, 1, 1, 1, 1, 3, 1, 1,
                          1, 1, 0, 1, 1, 1, 1, 1, 3, 1, 1,
                          1, 1, 0, 1, 1, 1, 1, 1, 3, 1, 1,
                          1, 1, 0, 0, 0, 1, 3, 3, 3, 1, 1,
                          1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
                          1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
                          3, 0, 1};
    REQUIRE((tree.parents() == ref_parents));
    REQUIRE((altitudes == ref_altitudes));
}

TEMPLATE_TEST_CASE("test tree of shapes no padding original space", "[tree_of_shapes]", char, float) {
    array_2d <TestType> image{{1, 1, 1, 1, 1, 1},
                              {1, 0, 0, 3, 3, 1},
                              {1, 0, 1, 1, 3, 1},
                              {1, 0, 0, 3, 3, 1},
                              {1, 1, 1, 1, 1, 1}};

    auto result = component_tree_tree_of_shapes_image2d(image, tos_padding::none, true);
    auto &tree = result.tree;
    auto &altitudes = result.altitudes;

    array_1d <index_t>
            ref_parents{32, 32, 32, 32, 32, 32,
                        32, 30, 30, 31, 31, 32,
                        32, 30, 32, 32, 31, 32,
                        32, 30, 30, 31, 31, 32,
                        32, 32, 32, 32, 32, 32,
                        32, 32, 32};
    array_1d <TestType>
            ref_altitudes{1, 1, 1, 1, 1, 1,
                          1, 0, 0, 3, 3, 1,
                          1, 0, 1, 1, 3, 1,
                          1, 0, 0, 3, 3, 1,
                          1, 1, 1, 1, 1, 1,
                          0, 3, 1};
    REQUIRE((tree.parents() == ref_parents));
    REQUIRE((altitudes == ref_altitudes));
}

TEST_CASE("test tree of shapes padding 0", "[tree_of_shapes]") {
    array_2d<float> image{{1, 1,  1},
                          {1, -2, 3}};

    auto result = component_tree_tree_of_shapes_image2d(image, tos_padding::zero, false);
    auto &tree = result.tree;
    auto &altitudes = result.altitudes;

    array_1d <index_t>
            ref_parents{66, 66, 66, 66, 66, 66, 66, 66, 66,
                        66, 66, 66, 66, 66, 66, 66, 66, 66,
                        66, 66, 65, 65, 65, 65, 65, 66, 66,
                        66, 66, 65, 66, 66, 66, 65, 66, 66,
                        66, 66, 65, 66, 63, 66, 64, 66, 66,
                        66, 66, 66, 66, 66, 66, 66, 66, 66,
                        66, 66, 66, 66, 66, 66, 66, 66, 66,
                        66, 65, 66, 66};
    array_1d<float>
            ref_altitudes{0, 0, 0, 0, 0, 0, 0, 0, 0,
                          0, 0, 0, 0, 0, 0, 0, 0, 0,
                          0, 0, 1, 1, 1, 1, 1, 0, 0,
                          0, 0, 1, 0, 0, 0, 1, 0, 0,
                          0, 0, 1, 0, -2, 0, 3, 0, 0,
                          0, 0, 0, 0, 0, 0, 0, 0, 0,
                          0, 0, 0, 0, 0, 0, 0, 0, 0,
                          -2, 3, 1, 0};
    REQUIRE((tree.parents() == ref_parents));
    REQUIRE((altitudes == ref_altitudes));
}

TEST_CASE("test tree of shapes padding 0 original space", "[tree_of_shapes]") {
    array_2d<float> image{{1, 1,  1},
                          {1, -2, 3}};

    auto result = component_tree_tree_of_shapes_image2d(image, tos_padding::zero, true);
    auto &tree = result.tree;
    auto &altitudes = result.altitudes;

    array_1d <index_t>
            ref_parents{7, 7, 7,
                        7, 8, 6,
                        7, 9, 9, 9};
    array_1d<char>
            ref_altitudes{1, 1, 1,
                          1, -2, 3,
                          3, 1, -2, 0};
    REQUIRE((tree.parents() == ref_parents));
    REQUIRE((altitudes == ref_altitudes));
}

TEST_CASE("test tree of shapes padding mean original space", "[tree_of_shapes]") {
    array_2d<float> image{{1, 1},
                          {1, -2},
                          {1, 7}};

    auto result = component_tree_tree_of_shapes_image2d(image, tos_padding::mean, true);
    auto &tree = result.tree;
    auto &altitudes = result.altitudes;

    array_1d <index_t>
            ref_parents{7, 7,
                        7, 6,
                        7, 8,
                        7, 9, 9, 9};
    array_1d<float>
            ref_altitudes{1., 1.,
                          1., -2.,
                          1., 7.,
                          -2., 1., 7., 1.5};
    REQUIRE((tree.parents() == ref_parents));
    REQUIRE((altitudes == ref_altitudes));
}

TEST_CASE("test tree of shapes no immersion no padding original space", "[tree_of_shapes]") {
    array_nd<float> image{{1, 1, 1, 1, 1},
                          {1, 0, 1, 2, 1},
                          {1, 1, 1, 1, 1}};

    auto result = component_tree_tree_of_shapes_image2d(image, tos_padding::none, /*original*/true, /*immersion*/false);
    auto &tree = result.tree;
    auto &altitudes = result.altitudes;

    array_1d <index_t>
            ref_parents{17, 17, 17, 17, 17,
                        17, 16, 17, 15, 17,
                        17, 17, 17, 17, 17, 17, 17, 17};
    array_1d<float> ref_altitudes{1, 1, 1, 1, 1,
                                  1, 0, 1, 2, 1,
                                  1, 1, 1, 1, 1, 2, 0, 1};

    REQUIRE((tree.parents() == ref_parents));
    REQUIRE((altitudes == ref_altitudes));
}

TEST_CASE("test tree of shapes no immersion padding zero original space", "[tree_of_shapes]") {
    array_nd<float> image{{1, 1, 1, 1, 1},
                          {1, 0, 1, 2, 1},
                          {1, 1, 1, 1, 1}};

    auto result = component_tree_tree_of_shapes_image2d(image, tos_padding::zero, /*original*/true, /*immersion*/false);
    auto &tree = result.tree;
    auto &altitudes = result.altitudes;

    array_1d <index_t>
            ref_parents{17, 17, 17, 17, 17,
                        17, 15, 17, 16, 17,
                        17, 17, 17, 17, 17, 17, 17, 18, 18};
    array_1d<float> ref_altitudes{1, 1, 1, 1, 1,
                                  1, 0, 1, 2, 1,
                                  1, 1, 1, 1, 1, 0, 2, 1, 0};

    REQUIRE((tree.parents() == ref_parents));
    REQUIRE((altitudes == ref_altitudes));
}

TEST_CASE("test tree of shapes no immersion no padding no original space", "[tree_of_shapes]") {
    array_nd<float> image{{1, 1, 1, 1, 1},
                          {1, 0, 1, 2, 1},
                          {1, 1, 1, 1, 1}};

    auto result = component_tree_tree_of_shapes_image2d(image, tos_padding::none, /*original*/false, /*immersion*/
                                                        false);
    auto &tree = result.tree;
    auto &altitudes = result.altitudes;

    array_1d <index_t>
            ref_parents{17, 17, 17, 17, 17,
                        17, 16, 17, 15, 17,
                        17, 17, 17, 17, 17, 17, 17, 17};
    array_1d<float> ref_altitudes{1, 1, 1, 1, 1,
                                  1, 0, 1, 2, 1,
                                  1, 1, 1, 1, 1, 2, 0, 1};
    REQUIRE((tree.parents() == ref_parents));
    REQUIRE((altitudes == ref_altitudes));
}

TEST_CASE("test tree of shapes no immersion padding zero no original space", "[tree_of_shapes]") {
    array_nd<float> image{{1, 1, 1, 1, 1},
                          {1, 0, 1, 2, 1},
                          {1, 1, 1, 1, 1}};

    auto result = component_tree_tree_of_shapes_image2d(image, tos_padding::zero, /*original*/false, /*immersion*/
                                                        false);
    auto &tree = result.tree;
    auto &altitudes = result.altitudes;

    array_1d <index_t>
            ref_parents{38, 38, 38, 38, 38, 38, 38,
                        38, 37, 37, 37, 37, 37, 38,
                        38, 37, 36, 37, 35, 37, 38,
                        38, 37, 37, 37, 37, 37, 38,
                        38, 38, 38, 38, 38, 38, 38, 37, 37, 38, 38};
    array_1d<float> ref_altitudes{0, 0, 0, 0, 0, 0, 0,
                                  0, 1, 1, 1, 1, 1, 0,
                                  0, 1, 0, 1, 2, 1, 0,
                                  0, 1, 1, 1, 1, 1, 0,
                                  0, 0, 0, 0, 0, 0, 0, 2, 0, 1, 0};

    REQUIRE((tree.parents() == ref_parents));
    REQUIRE((altitudes == ref_altitudes));
}


TEST_CASE("test tree of shapes self duality", "[tree_of_shapes]") {
    xt::random::seed(42);
    array_2d<double> image = xt::random::rand<double>({25, 38});
    auto res1 = component_tree_tree_of_shapes_image2d(image);
    auto res2 = component_tree_tree_of_shapes_image2d(-image);
    REQUIRE(test_tree_isomorphism(res1.tree, res2.tree));
}

}


<|start_filename|>higra/accumulator/all.hpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#pragma once

#include "py_accumulators.hpp"
#include "py_at_accumulator.hpp"
#include "py_graph_accumulator.hpp"
#include "py_tree_accumulators.hpp"
#include "py_tree_contour_accumulator.hpp"


<|start_filename|>include/higra/accumulator/graph_accumulator.hpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#pragma once

#include "../graph.hpp"
#include "accumulator.hpp"
#include "../structure/details/light_axis_view.hpp"

namespace hg {

    namespace graph_accumulator_detail {


        template<bool vectorial,
                typename graph_t,
                typename T,
                typename accumulator_t,
                typename output_t = typename T::value_type>
        auto accumulate_graph_edges_impl(const graph_t &graph,
                                         const xt::xexpression<T> &xinput,
                                         const accumulator_t accumulator) {
            HG_TRACE();
            auto &input = xinput.derived_cast();
            hg_assert_edge_weights(graph, input);

            auto data_shape = std::vector<size_t>(input.shape().begin() + 1, input.shape().end());
            auto output_shape = accumulator_t::get_output_shape(data_shape);
            output_shape.insert(output_shape.begin(), num_vertices(graph));

            array_nd<output_t> output = array_nd<output_t>::from_shape(output_shape);

            auto input_view = make_light_axis_view<vectorial>(input);
            auto output_view = make_light_axis_view<vectorial>(output);
            auto acc = accumulator.template make_accumulator<vectorial>(output_view);

            for (auto i: vertex_iterator(graph)) {
                output_view.set_position(i);
                acc.set_storage(output_view);
                acc.initialize();
                for (auto e: out_edge_iterator(i, graph)) {
                    input_view.set_position(e);
                    acc.accumulate(input_view.begin());
                }
            }

            return output;
        };

        template<bool vectorial,
                typename graph_t,
                typename T,
                typename accumulator_t,
                typename output_t = typename T::value_type>
        auto accumulate_graph_vertices_impl(const graph_t &graph,
                                            const xt::xexpression<T> &xinput,
                                            const accumulator_t accumulator) {
            HG_TRACE();
            auto &input = xinput.derived_cast();
            hg_assert_vertex_weights(graph, input);

            auto data_shape = std::vector<size_t>(input.shape().begin() + 1, input.shape().end());
            auto output_shape = accumulator_t::get_output_shape(data_shape);
            output_shape.insert(output_shape.begin(), num_vertices(graph));

            array_nd<output_t> output = array_nd<output_t>::from_shape(output_shape);

            auto input_view = make_light_axis_view<vectorial>(input);
            auto output_view = make_light_axis_view<vectorial>(output);
            auto acc = accumulator.template make_accumulator<vectorial>(output_view);

            for (auto i: vertex_iterator(graph)) {
                output_view.set_position(i);
                acc.set_storage(output_view);
                acc.initialize();
                for (auto v: adjacent_vertex_iterator(i, graph)) {
                    input_view.set_position(v);
                    acc.accumulate(input_view.begin());
                }
            }

            return output;
        };


    }

    template<typename graph_t, typename T, typename accumulator_t, typename output_t = typename T::value_type>
    auto accumulate_graph_edges(const graph_t &graph,
                                const xt::xexpression<T> &xedge_weights,
                                const accumulator_t &accumulator) {
        auto &edge_weights = xedge_weights.derived_cast();
        if (edge_weights.dimension() == 1) {
            return graph_accumulator_detail::accumulate_graph_edges_impl<false>(graph, xedge_weights, accumulator);
        } else {
            return graph_accumulator_detail::accumulate_graph_edges_impl<true>(graph, xedge_weights, accumulator);
        }
    };

    template<typename graph_t, typename T, typename accumulator_t, typename output_t = typename T::value_type>
    auto accumulate_graph_vertices(const graph_t &graph,
                                   const xt::xexpression<T> &xvertex_weights,
                                   const accumulator_t &accumulator) {
        auto &vertex_weights = xvertex_weights.derived_cast();
        if (vertex_weights.dimension() == 1) {
            return graph_accumulator_detail::accumulate_graph_vertices_impl<false>(graph, xvertex_weights, accumulator);
        } else {
            return graph_accumulator_detail::accumulate_graph_vertices_impl<true>(graph, xvertex_weights, accumulator);
        }
    };


}

<|start_filename|>test/python_cpp/main.cpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#define CATCH_CONFIG_MAIN

//#include "xtl/xmeta_utils.hpp"
#define FORCE_IMPORT_ARRAY

#include "catch2/catch.hpp"
#include <xtensor/xtensor.hpp>
#include <xtensor/xarray.hpp>
#include <xtensor-python/pyarray.hpp>
#include <xtensor-python/pytensor.hpp>
#include <pybind11/embed.h> // everything needed for embedding

namespace py = pybind11;
using namespace py::literals;
//using namespace xt;
using namespace std;

PYBIND11_EMBEDDED_MODULE(dummy, m
) {
    xt::import_numpy();

    m.def("foo", [](
            xt::pytensor<float, 1> array
    ) {
        return 42;
    });
    m.def("foo", [](
            xt::pyarray<float> array
    ) {
        return 43;
    });
}


TEST_CASE("basic python embedded interpreter", "[embedded]") {
    py::initialize_interpreter();
    SECTION("example import") {
        py::module sys = py::module::import("sys");

        /*    py::scoped_interpreter guard{};
            auto locals = py::dict("name"_a = "World", "number"_a = 42);
            py::exec(R"(
            result = 42
        )", py::globals(), locals);
            BOOST_CHECK(locals["result"].cast<int>() == 42);*/
    }
    SECTION("failed overload") {
        py::module sys = py::module::import("sys");
        /*sys.attr("path") = py::make_tuple("", "/home/perretb/Higra/cmake-build-debug/python",
                "/home/perretb/anaconda3/lib/python36.zip",
                "/home/perretb/anaconda3/lib/python3.6",
                "/home/perretb/anaconda3/lib/python3.6/lib-dynload",
                "/home/perretb/.local/lib/python3.6/site-packages",
                "/home/perretb/anaconda3/lib/python3.6/site-packages");

        auto dummy_m = py::module::import("dummy");
        BOOST_CHECK_THROW(dummy_m.attr("foo")("bar").cast<int>(), py::error_already_set);
        */
    }
    SECTION("overload tensor array") {
        /* py::module sys = py::module::import("sys");
        sys.attr("path") = py::make_tuple("", "/home/perretb/Higra/cmake-build-debug/python",
                "/home/perretb/anaconda3/lib/python36.zip",
                "/home/perretb/anaconda3/lib/python3.6",
                "/home/perretb/anaconda3/lib/python3.6/lib-dynload",
                "/home/perretb/.local/lib/python3.6/site-packages",
                "/home/perretb/anaconda3/lib/python3.6/site-packages");

        auto dummy_m = py::module::import("dummy");

        xt::xtensor<float, 2> a{{1}};
        dummy_m.attr("foo")(a);*/
    }
    SECTION("higra sand box") {
        /*
        py::module sys = py::module::import("sys");
        sys.attr("path") = py::make_tuple("", "/home/perretb/Higra/cmake-build-debug/python",
                                          "/home/perretb/anaconda3/lib/python36.zip",
                                          "/home/perretb/anaconda3/lib/python3.6",
                                          "/home/perretb/anaconda3/lib/python3.6/lib-dynload",
                                          "/home/perretb/.local/lib/python3.6/site-packages",
                                          "/home/perretb/anaconda3/lib/python3.6/site-packages");

        auto higra_m = py::module::import("higra");
        */
        //auto res = higra_m.attr("read_graph_pink")("/home/perretb/2008_000009.graph");
    }
    py::finalize_interpreter();
}


<|start_filename|>higra/io_utils/py_pink_graph_io.cpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#include "py_pink_graph_io.hpp"
#include "../py_common.hpp"
#include "higra/io/pink_graph_io.hpp"
#include "xtensor-python/pyarray.hpp"
#include "xtensor-python/pytensor.hpp"

template<typename T>
using pyarray = xt::pyarray<T>;

namespace py = pybind11;

template<typename graph_t>
void def_save(pybind11::module &m) {
    m.def("_save_graph_pink", [](const std::string &filename,
                                const graph_t &graph,
                                const pyarray<double> &vertex_values = {0},
                                const pyarray<double> &edge_values = {0},
                                const std::vector<size_t> &shape = {}) {
              hg::save_pink_graph(filename, graph, vertex_values, edge_values, shape);
          },
          py::arg("filename"),
          py::arg("graph"),
          py::arg("vertex_weights") = pyarray<double>(),
          py::arg("edge_weights") = pyarray<double>(),
          py::arg("shape") = std::vector<size_t>());
}

void py_init_pink_io(pybind11::module &m) {
    xt::import_numpy();

    def_save<hg::ugraph>(m);

    m.def("_read_graph_pink", [](const std::string &filename) {
              auto res = hg::read_pink_graph(filename);
              return std::make_tuple(std::move(res.graph), std::move(res.vertex_weights), std::move(res.edge_weights),
                                     std::move(res.shape));
          },
          py::arg("filename"));
}

<|start_filename|>higra/assessment/py_partition.cpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#include "py_partition.hpp"
#include "../py_common.hpp"
#include "higra/assessment/partition.hpp"
#include "xtensor-python/pyarray.hpp"
#include "xtensor-python/pytensor.hpp"

using namespace hg;
namespace py = pybind11;

struct def_assess_partition {
    template<typename value_type, typename C>
    static
    void def(C &c, const char *doc) {
        c.def("assess_partition",
              [](const xt::pyarray <value_type> &candidate,
                 const xt::pyarray <value_type> &ground_truth,
                 partition_measure measure) {
                  switch (measure) {
                      case partition_measure::DHamming:
                          return assess_partition(candidate, ground_truth, scorer_partition_DHamming());
                      case partition_measure::DCovering:
                          return assess_partition(candidate, ground_truth, scorer_partition_DCovering());
                      case partition_measure::BCE:
                          return assess_partition(candidate, ground_truth, scorer_partition_BCE());
                      default:
                          throw std::runtime_error("Partition measure is not known, see enumeration PartitionMeasure for legal values.");
                  }
              },
              doc,
              py::arg("candidate"),
              py::arg("ground_truth"),
              py::arg("partition_measure"));
    }
};

void py_init_assessment_partition(pybind11::module &m) {
    xt::import_numpy();

    py::enum_<partition_measure>(m, "PartitionMeasure",
                                 "Quality measures usable with partition assessment")
            .value("BCE", partition_measure::BCE)
            .value("DHamming", partition_measure::DHamming)
            .value("DCovering", partition_measure::DCovering);

    add_type_overloads<def_assess_partition, HG_TEMPLATE_INTEGRAL_TYPES>
            (m,
             "Assess a given candidate partition with a ground-truth segmentation and an evaluation measure "
             "(see enumeration PartitionMeasure). The candidate and ground-truth partitions must be given as "
             "labelisations with integers values between 0 (included) and the number of regions in the partition "
             "(excluded).");
}


<|start_filename|>higra/accumulator/py_accumulators.cpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#include "py_accumulators.hpp"
#include "../py_common.hpp"
#include "higra/accumulator/accumulator.hpp"

namespace py = pybind11;

void py_init_accumulators(pybind11::module &m) {

    // only exposes enumeration
    py::enum_<hg::accumulators>(m, "Accumulators",
            "The Accumulators class enumerates the various way of combining a list of values into a single value "
            "that are used in various algorithms.")
            .value("min", hg::accumulators::min)
            .value("max", hg::accumulators::max)
            .value("mean", hg::accumulators::mean)
            .value("counter", hg::accumulators::counter)
            .value("sum", hg::accumulators::sum)
            .value("prod", hg::accumulators::prod)
            .value("first", hg::accumulators::first)
            .value("last", hg::accumulators::last)
            .value("argmin", hg::accumulators::argmin)
            .value("argmax", hg::accumulators::argmax);
}

<|start_filename|>test/cpp/algo/test_horizontal_cuts.cpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#include "../test_utils.hpp"
#include "higra/graph.hpp"
#include "higra/image/graph_image.hpp"
#include "higra/algo/horizontal_cuts.hpp"

using namespace hg;
namespace test_horizontal_cuts {
    TEST_CASE("horizontal cut explorer constructor asserts", "[horizontal_cuts]") {

        hg::tree tree{
                array_1d<index_t>{4, 4, 5, 5, 6, 6, 6}
        };
        array_1d<int> altitudes{1, 0, 0, 0, 2, 3, 4};
        REQUIRE_THROWS_AS(make_horizontal_cut_explorer(tree, altitudes), std::runtime_error);

        array_1d<int> altitudes2{0, 0, 0, 0, 2, 3, -1};
        REQUIRE_THROWS_AS(make_horizontal_cut_explorer(tree, altitudes2), std::runtime_error);
    }

    TEST_CASE("horizontal cut explorer indexed accessor", "[horizontal_cuts]") {

        hg::tree tree{
                array_1d<index_t>{11, 11, 11, 12, 12, 16, 13, 13, 13, 14, 14, 17, 16, 15, 15, 18, 17, 18, 18}
        };
        array_1d<int> altitudes{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 3, 1, 2, 3};
        auto hch = make_horizontal_cut_explorer(tree, altitudes);

        REQUIRE(hch.num_cuts() == 4);

        std::vector<array_1d<index_t>> cut_nodes{
                {18},
                {17, 13, 14},
                {11, 16, 13, 14},
                {0,  1,  2,  3, 4, 5, 13, 9, 10}
        };

        std::vector<int> alt_cuts{3, 2, 1, 0};

        for (index_t i = 0; i < (index_t) hch.num_cuts(); i++) {
            auto c = hch.horizontal_cut_from_index(i);
            REQUIRE(vectorSame(c.nodes, cut_nodes[i]));
            REQUIRE(c.altitude == alt_cuts[i]);
        }
    }

    TEST_CASE("horizontal cut explorer indexed accessor on sorted tree", "[horizontal_cuts]") {

        hg::tree tree{
                array_1d<index_t>{5, 5, 5, 6, 6, 7, 7, 7}
        };
        array_1d<int> altitudes{0, 0, 0, 0, 0, 1, 2, 3};

        auto hch = make_horizontal_cut_explorer(tree, altitudes);

        REQUIRE(hch.num_cuts() == 4);

        std::vector<array_1d<index_t>> cut_nodes{
                {7},
                {5, 6},
                {5, 3, 4},
                {0, 1, 2, 3, 4}
        };

        std::vector<int> alt_cuts{3, 2, 1, 0};

        for (index_t i = 0; i < (index_t) hch.num_cuts(); i++) {
            auto c = hch.horizontal_cut_from_index(i);
            REQUIRE(vectorSame(c.nodes, cut_nodes[i]));
            REQUIRE(c.altitude == alt_cuts[i]);
        }
    }

    TEST_CASE("horizontal cut explorer altitudes accessor", "[horizontal_cuts]") {

        hg::tree tree{
                array_1d<index_t>{11, 11, 11, 12, 12, 16, 13, 13, 13, 14, 14, 17, 16, 15, 15, 18, 17, 18, 18}
        };
        array_1d<int> altitudes{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 3, 1, 2, 3};
        auto hch = make_horizontal_cut_explorer(tree, altitudes);

        REQUIRE(hch.num_cuts() == 4);

        std::vector<array_1d<index_t>> cut_nodes{
                {18},
                {17, 13, 14},
                {11, 16, 13, 14},
                {0,  1,  2,  3, 4, 5, 13, 9, 10}
        };

        std::vector<int> alt_cuts{3, 2, 1, 0};

        for (index_t i = 0; i < (index_t) hch.num_cuts(); i++) {
            auto c = hch.horizontal_cut_from_altitude(alt_cuts[i]);
            REQUIRE(vectorSame(c.nodes, cut_nodes[i]));
            REQUIRE(c.altitude == alt_cuts[i]);
        }
    }

    TEST_CASE("horizontal cut explorer altitudes accessor sorted tree", "[horizontal_cuts]") {

        hg::tree tree{
                array_1d<index_t>{5, 5, 5, 6, 6, 7, 7, 7}
        };
        array_1d<int> altitudes{0, 0, 0, 0, 0, 1, 2, 3};
        auto hch = make_horizontal_cut_explorer(tree, altitudes);

        REQUIRE(hch.num_cuts() == 4);

        std::vector<array_1d<index_t>> cut_nodes{
                {7},
                {5, 6},
                {5, 3, 4},
                {0, 1, 2, 3, 4}
        };

        std::vector<int> alt_cuts{3, 2, 1, 0};

        for (index_t i = 0; i < (index_t) hch.num_cuts(); i++) {
            auto c = hch.horizontal_cut_from_altitude(alt_cuts[i]);
            REQUIRE(vectorSame(c.nodes, cut_nodes[i]));
            REQUIRE(c.altitude == alt_cuts[i]);
        }
    }

    TEST_CASE("horizontal cut explorer number of regions accessor", "[horizontal_cuts]") {

        hg::tree tree{
                array_1d<index_t>{11, 11, 11, 12, 12, 16, 13, 13, 13, 14, 14, 17, 16, 15, 15, 18, 17, 18, 18}
        };
        array_1d<int> altitudes{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 3, 1, 2, 3};
        auto hch = make_horizontal_cut_explorer(tree, altitudes);

        REQUIRE(hch.num_cuts() == 4);

        std::vector<array_1d<index_t>> cut_nodes{
                {18},
                {17, 13, 14},
                {11, 16, 13, 14},
                {0,  1,  2,  3, 4, 5, 13, 9, 10}
        };

        std::vector<int> k_cuts{1, 3, 4, 9};

        for (index_t i = 0; i < (index_t) hch.num_cuts(); i++) {
            auto c = hch.horizontal_cut_from_num_regions(k_cuts[i]);
            REQUIRE(vectorSame(c.nodes, cut_nodes[i]));
        }

        std::vector<int> k_cuts2{1, 2, 4, 5};

        for (index_t i = 0; i < (index_t) hch.num_cuts(); i++) {
            auto c = hch.horizontal_cut_from_num_regions(k_cuts2[i]);
            REQUIRE(vectorSame(c.nodes, cut_nodes[i]));
        }

        std::vector<int> k_cuts3{2, 3, 8, 20};

        for (index_t i = 0; i < (index_t) hch.num_cuts(); i++) {
            auto c = hch.horizontal_cut_from_num_regions(k_cuts3[i], false);
            REQUIRE(vectorSame(c.nodes, cut_nodes[i]));
        }
    }

    TEST_CASE("horizontal cut explorer number of regions accessor sorted tree", "[horizontal_cuts]") {

        hg::tree tree{
                array_1d<index_t>{5, 5, 5, 6, 6, 7, 7, 7}
        };
        array_1d<int> altitudes{0, 0, 0, 0, 0, 1, 2, 3};
        auto hch = make_horizontal_cut_explorer(tree, altitudes);

        REQUIRE(hch.num_cuts() == 4);

        std::vector<array_1d<index_t>> cut_nodes{
                {7},
                {5, 6},
                {5, 3, 4},
                {0, 1, 2, 3, 4}
        };

        std::vector<int> k_cuts{1, 2, 3, 4};

        for (index_t i = 0; i < (index_t) hch.num_cuts(); i++) {
            auto c = hch.horizontal_cut_from_num_regions(k_cuts[i]);
            REQUIRE(vectorSame(c.nodes, cut_nodes[i]));
        }

        std::vector<int> k_cuts2{1, 2, 3, 5};

        for (index_t i = 0; i < (index_t) hch.num_cuts(); i++) {
            auto c = hch.horizontal_cut_from_num_regions(k_cuts2[i]);
            REQUIRE(vectorSame(c.nodes, cut_nodes[i]));
        }
    }

    TEST_CASE("horizontal cut explorer consistency", "[horizontal_cuts]") {

        hg::tree tree{
                array_1d<index_t>{11, 11, 11, 12, 12, 16, 13, 13, 13, 14, 14, 17, 16, 15, 15, 18, 17, 18, 18}
        };
        array_1d<int> altitudes{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 3, 1, 2, 3};
        auto hch = make_horizontal_cut_explorer(tree, altitudes);

        for (index_t i = 0; i <= 3; i++) {
            auto c = hch.horizontal_cut_from_altitude((int)i);
            auto r1 = c.labelisation_leaves(tree);
            auto r2 = labelisation_horizontal_cut_from_threshold(tree, altitudes, i);
            REQUIRE((r1 == r2));
        }

    }

    TEST_CASE("horizontal cut nodes functions", "[horizontal_cuts]") {

        auto g = get_4_adjacency_graph({1, 11});
        hg::tree tree{
                array_1d<index_t>{11, 11, 11, 12, 12, 16, 13, 13, 13, 14, 14, 17, 16, 15, 15, 18, 17, 18, 18}
        };
        array_1d<int> altitudes{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 3, 1, 2, 3};
        auto hch = make_horizontal_cut_explorer(tree, altitudes);

        auto c = hch.horizontal_cut_from_num_regions(3);

        auto lbls = c.labelisation_leaves(tree);
        array_1d<index_t> ref_lbls{17, 17, 17, 17, 17, 17, 13, 13, 13, 14, 14};
        REQUIRE((lbls == ref_lbls));

        auto rec = c.reconstruct_leaf_data(tree, altitudes);
        array_1d<int> ref_rec{2, 2, 2, 2, 2, 2, 0, 0, 0, 1, 1};
        REQUIRE((rec == ref_rec));

        auto cut = c.graph_cut(tree, g);
        array_1d<int> ref_cut{0, 0, 0, 0, 0, 1, 0, 0, 1, 0};
        REQUIRE((cut == ref_cut));
    }
}



<|start_filename|>include/higra/io/pink_graph_io.hpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#pragma once

#include "../graph.hpp"
#include <istream>
#include <ostream>
#include <fstream>
#include <string>
#include <vector>
#include "higra/structure/array.hpp"
#include "xtensor/xscalar.hpp"


namespace hg {

    template<typename A=array_1d<double>,
            typename B=A>
    struct pink_graph {
        ugraph graph;
        std::vector<std::size_t> shape;
        A vertex_weights;
        B edge_weights;
    };

    inline
    auto read_pink_graph(std::istream &in) {
        HG_TRACE();
        std::string discard;

        std::vector<std::size_t> shape;

        // maybe shape
        if (in.peek() == '#') {
            std::size_t rs;
            std::size_t cs;
            in >> discard >> rs >> discard >> cs;
            shape.push_back(cs);
            shape.push_back(rs);
        }

        index_t num_points;
        index_t num_edges;
        in >> num_points >> num_edges;

        hg_assert(num_points > 0, "The number of vertices cannot be negative.");
        hg_assert(num_edges > 0, "The number of edges cannot be negative.");

        if (shape.empty()) // construct valid shape
        {
            shape.push_back(num_points);
        }

        //useless line to announce vertex list
        in >> discard >> discard;

        // vertex list
        ugraph g(num_points);
        auto vertex_weight = array_1d<double>::from_shape({(size_t)num_points});

        for (index_t l = 0; l < num_points; ++l) {
            index_t i;
            double d;
            in >> i >> d;
            hg_assert(0 <= i && i < num_points, "Invalid graph file: vertex index out of range.");
            vertex_weight(i) = d;
        }

        //useless line to announce edge list
        in >> discard >> discard;

        auto edge_weight = array_1d<double>::from_shape({(size_t)num_edges});

        for (index_t l = 0; l < num_edges; ++l) {
            index_t i, j;
            double d;
            in >> i >> j >> d;
            hg_assert(0 <= i && 0 <= j && i < num_points && j < num_points,
                      "Invalid graph file: vertex index out of range in edge definition.");
            g.add_edge(i, j);
            edge_weight(l) = d;
        }

        return pink_graph<>{std::move(g), std::move(shape), std::move(vertex_weight), std::move(edge_weight)};
    }

    inline
    auto read_pink_graph(const std::string &filename) {
        std::ifstream file(filename);
        return read_pink_graph(file);
    };


    template<typename graph_t,
            typename T1,
            typename T2,
            typename S>
    auto save_pink_graph(std::ostream &out,
                         const graph_t &graph,
                         const xt::xexpression<T1> &xvertex_values = xt::xscalar<char>(0),
                         const xt::xexpression<T2> &xedge_values = xt::xscalar<char>(0),
                         S &shape = std::vector<std::size_t>()
    ) {
        HG_TRACE();
        auto &vertex_values = xvertex_values.derived_cast();
        auto &edge_values = xedge_values.derived_cast();

        hg_assert(vertex_values.dimension() <= 1, "Too many dimensions for vertex values!");
        hg_assert(edge_values.dimension() <= 1, "Too many dimensions for edge values!");

        switch (shape.size()) {
            case 0:
                break;
            case 1:
                out << "#rs " << shape[0] << " cs 1\n";
                break;
            case 2:
                out << "#rs " << shape[1] << " cs " << shape[0] << "\n";
                break;
            default:
                throw std::runtime_error("Too many dimensions !");
        }

        out << num_vertices(graph) << " " << num_edges(graph) << "\n";

        out << "val sommets\n";

        if (vertex_values.size() == 0) {
            for (std::size_t i = 0; i < num_vertices(graph); ++i) {
                out << i << " 1\n";
            }
        } else {
            hg_assert_vertex_weights(graph, vertex_values);
            for (std::size_t i = 0; i < num_vertices(graph); ++i) {
                out << i << " " << vertex_values(i) << "\n";
            }
        }

        out << "arcs values\n";

        if (edge_values.size() == 0) {
            for (auto e: edge_iterator(graph)) {
                out << source(e, graph) << " " << target(e, graph) << " 1\n";
            }
        } else {
            hg_assert_edge_weights(graph, edge_values);
            for (auto &e: edge_iterator(graph)) {
                out << source(e, graph) << " " << target(e, graph) << " " << edge_values(e) << "\n";
            }
        }

    }

    template<typename graph_t,
            typename T1,
            typename T2,
            typename S>
    void save_pink_graph(const std::string &filename,
                         const graph_t &graph,
                         const xt::xexpression<T1> &xvertex_values = xt::xscalar<char>(0),
                         const xt::xexpression<T2> &xedge_values = xt::xscalar<char>(0),
                         S &shape = std::vector<std::size_t>()
    ) {
        std::ofstream file(filename);
        save_pink_graph(file, graph, xvertex_values, xedge_values, shape);
    };

}


<|start_filename|>test/cpp/algo/test_tree_energy_optimization.cpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#include "../test_utils.hpp"
#include <cmath>
#include <sstream>
#include "higra/algo/tree_energy_optimization.hpp"
#include "higra/graph.hpp"
#include "higra/image/graph_image.hpp"

using namespace hg;
using namespace std;
using namespace hg::tree_energy_optimization_internal;

using lep_t = piecewise_linear_energy_function_piece<double>;
using lef_t = piecewise_linear_energy_function<double>;

std::ostream &operator<<(std::ostream &os, const lep_t &rhs) {
    os << "((" << rhs.origin_x() << ", " << rhs.origin_y() << "), " << rhs.slope() << ")";
    return os;
}

std::ostream &operator<<(std::ostream &os, const lef_t &rhs) {
    os << "{";
    for (const auto &lp: rhs) {
        os << lp << ", ";
    }
    os << "}";
    return os;
}

namespace Catch {

    template<>
    struct StringMaker<lep_t> {
        static std::string convert(lep_t const &rhs) {
            std::ostringstream strs;
            strs << "((" << rhs.origin_x() << ", " << rhs.origin_y() << "), " << rhs.slope() << ")";
            return strs.str();
        }
    };

}

namespace test_linear_energy_function_optimization {

    TEST_CASE("test piecewise_linear_energy_function_piece", "[linear_energy_function_optimization]") {

        lep_t p1(1, 2, 3);
        lep_t p2(2, 1, 3);
        lep_t p3(1, 2, 3);

        REQUIRE(p1 != p2);
        REQUIRE(p1 == p3);

        REQUIRE(p1(1) == 2);
        REQUIRE(p1(0) == -1);
    }

    TEST_CASE("test piecewise_linear_energy_function sum", "[linear_energy_function_optimization]") {

        SECTION("edge case empty") {
            lep_t p1(1, 2, 3);
            lef_t f1(p1);
            lef_t empty;

            REQUIRE(empty.sum(empty) == empty);
            REQUIRE(f1.sum(empty) == f1);
            REQUIRE(empty.sum(f1) == f1);
        }
        SECTION("simple + simple") {
            lef_t f1({0, 0, 1});
            lef_t f2({0, 1, 1});

            lef_t r({0, 1, 2});
            REQUIRE(f1.sum(f2) == r);
            REQUIRE(f2.sum(f1) == r);
        }
        SECTION("compound + compound") {
            lef_t f1({{0, 0, 2},
                      {1, 2, 1}});
            lef_t f2({{0,   0,   1},
                      {0.5, 0.5, 0.5},
                      {2.5, 1.5, 0.1}});

            lef_t r({{0,   0,    3},
                     {0.5, 1.5,  2.5},
                     {1,   2.75, 1.5},
                     {2.5, 5,    1.1}});
            REQUIRE(f1.sum(f2) == r);
            REQUIRE(f2.sum(f1) == r);
        }

        SECTION("compound + compound with max pieces") {
            lef_t f1({{0, 0, 2},
                      {1, 2, 1}});
            lef_t f2({{0,   0,   1},
                      {0.5, 0.5, 0.5},
                      {2.5, 1.5, 0.1}});

            lef_t r({{0.,  0.25, 2.5},
                     {1,   2.75, 1.5},
                     {2.5, 5,    1.1}});
            REQUIRE(f1.sum(f2, 3) == r);
            REQUIRE(f2.sum(f1, 3) == r);
        }
    }

    TEST_CASE("test piecewise_linear_energy_function infimum", "[linear_energy_function_optimization]") {

        SECTION("simple no intersection") {
            lef_t f({0, 0, 1});
            lep_t p(0, 1, 1);

            lef_t r(f);
            REQUIRE(f.infimum(p) >= std::numeric_limits<double>::max());
            REQUIRE(f == r);
        }
        SECTION("simple intersection") {
            lef_t f({0, 1, 1});
            lep_t p(0, 2, 0.5);

            lef_t r({{0, 1, 1},
                     {2, 3, 0.5}});
            REQUIRE(f.infimum(p) == 2);
            REQUIRE(f == r);
        }
        SECTION("compound intersection") {
            lef_t f({{0, 0,  5},
                     {1, 5,  3},
                     {3, 11, 2}});
            lep_t p(0, 6, 1);

            lef_t r({{0, 0, 5},
                     {1, 5, 3},
                     {2, 8, 1}});
            REQUIRE(f.infimum(p) == 2);
            REQUIRE(f == r);
        }
        SECTION("parallel edge case 1") {
            lef_t f({{0, 0,  5},
                     {1, 5,  3},
                     {3, 11, 2}});
            lep_t p(0, 20, 2);

            lef_t r({{0, 0,  5},
                     {1, 5,  3},
                     {3, 11, 2}});
            REQUIRE(f.infimum(p) >= std::numeric_limits<double>::max());
            REQUIRE(f == r);
        }
        SECTION("parallel edge case 2") {
            lef_t f({{0, 0,  5},
                     {1, 5,  3},
                     {3, 11, 2}});
            lep_t p(0, 5, 2);

            lef_t r({{0, 0,  5},
                     {1, 5,  3},
                     {3, 11, 2}});
            REQUIRE(f.infimum(p) == 3);
            REQUIRE(f == r);
        }
        SECTION("parallel edge case 3") {
            lef_t f({{0, 0,  5},
                     {1, 5,  3},
                     {3, 11, 2}});
            lep_t p(0, 1, 2);

            lef_t r({{0,      0,      5},
                     {1. / 3, 5. / 3, 2}});
            REQUIRE(f.infimum(p) == Approx(1.0 / 3));
            REQUIRE(f == r);
        }
    }

    TEST_CASE("test labelisation_optimal_cut_from_energy", "[optimal_cut_tree]") {
        tree t(array_1d<index_t>{8, 8, 9, 7, 7, 11, 11, 9, 10, 10, 12, 12, 12});
        array_1d<double> energy_attribute{2, 1, 3, 2, 1, 1, 1, 2, 2, 4, 10, 5, 20};

        auto res = labelisation_optimal_cut_from_energy(t, energy_attribute);

        array_1d<index_t> ref{0, 0, 1, 1, 1, 2, 3};
        REQUIRE(is_in_bijection(res, ref));
    }

    TEST_CASE("test hierarchy_to_optimal_energy_cut_hierarchy", "[optimal_cut_tree]") {
        tree t(array_1d<index_t>{8, 8, 9, 7, 7, 11, 11, 9, 10, 10, 12, 12, 12});
        array_1d<double> data_fidelity_attribute{1, 1, 1, 1, 1, 1, 1, 4, 5, 10, 15, 25, 45};
        array_1d<double> regularization_attribute{4, 4, 4, 4, 4, 4, 4, 4, 4, 6, 10, 4, 12};

        auto res = hierarchy_to_optimal_energy_cut_hierarchy(t, data_fidelity_attribute, regularization_attribute);
        auto &tree = res.tree;
        auto &altitudes = res.altitudes;

        array_1d<index_t> ref_parents{8, 8, 9, 7, 7, 10, 10, 9, 9, 10, 10};
        array_1d<double> ref_altitudes{0., 0., 0., 0., 0., 0., 0., 0.5, 0.75, 2.5, 14.0 / 3};
        REQUIRE(tree.parents() == ref_parents);
        REQUIRE(xt::allclose(altitudes, ref_altitudes));
    }

    TEST_CASE("test binary_partition_tree_MumfordShah_energy scalar", "[optimal_cut_tree]") {
        auto g = hg::get_4_adjacency_graph({3, 3});
        array_1d<double> edge_length = xt::ones<double>({num_edges(g)});
        array_1d<double> vertex_perimeter({9}, 4);
        array_1d<double> vertex_values{1, 1, 20, 6, 1, 20, 10, 10, 10};
        array_1d<double> squared_vertex_values = vertex_values * vertex_values;
        array_1d<double> vertex_area = xt::ones<double>({num_vertices(g)});

        auto res = binary_partition_tree_MumfordShah_energy(
                g,
                vertex_perimeter,
                vertex_area,
                vertex_values,
                squared_vertex_values,
                edge_length);
        auto &tree = res.tree;
        auto &altitudes = res.altitudes;
        array_1d<index_t> ref_parents{10, 10, 11, 14, 13, 11, 12, 9, 9, 12, 13, 16, 15, 14, 15, 16, 16};
        array_1d<double> ref_altitudes{0., 0., 0.,
                                       0., 0., 0.,
                                       0., 0., 0.,
                                       0., 0., 0.,
                                       0., 0., 4.6875, 25.741071, 53.973545};
        REQUIRE(tree.parents() == ref_parents);
        REQUIRE(xt::allclose(altitudes, ref_altitudes));
    }

    TEST_CASE("test binary_partition_tree_MumfordShah_energy vectorial", "[optimal_cut_tree]") {
        auto g = hg::get_4_adjacency_graph({3, 3});
        array_1d<double> edge_length = xt::ones<double>({num_edges(g)});
        array_1d<double> vertex_perimeter({9}, 4);
        array_2d<double> vertex_values{{1, 2}, {1, 2}, {20, 30}, {6, 7}, {1, 2}, {20, 30}, {10, 12}, {10, 12},
                                       {10, 12}};
        array_2d<double> squared_vertex_values = vertex_values * vertex_values;
        array_1d<double> vertex_area = xt::ones<double>({num_vertices(g)});

        auto res = binary_partition_tree_MumfordShah_energy(
                g,
                vertex_perimeter,
                vertex_area,
                vertex_values,
                squared_vertex_values,
                edge_length);
        auto &tree = res.tree;
        auto &altitudes = res.altitudes;

        array_1d<index_t> ref_parents{10, 10, 11, 14, 13, 11, 12, 9, 9, 12, 13, 16, 15, 14, 15, 16, 16};
        array_1d<double> ref_altitudes{0., 0., 0.,
                                       0., 0., 0.,
                                       0., 0., 0.,
                                       0., 0., 0.,
                                       0., 0., 9.375, 58.553571, 191.121693};
        REQUIRE(tree.parents() == ref_parents);
        REQUIRE(xt::allclose(altitudes, ref_altitudes));
    }
}

<|start_filename|>benchmark/benchmark_views.cpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#include <benchmark/benchmark.h>

#include "higra/graph.hpp"
#include "higra/accumulator/tree_accumulator.hpp"
#include "xtensor/xarray.hpp"
#include "xtensor/xtensor.hpp"
#include "xtensor/xview.hpp"
#include "xtensor/xgenerator.hpp"
#include "xtensor/xrandom.hpp"
#include "xtensor/xindex_view.hpp"
#include "xtensor/xeval.hpp"

using namespace xt;
using namespace hg;

std::size_t min_tree_size = 10;
std::size_t max_tree_size = 16;

bool is_power_of_two(std::size_t x) {
    return (x != 0) && ((x & (x - 1)) == 0);
}

auto get_complete_binary_tree(std::size_t num_leaves) {
    assert(is_power_of_two(num_leaves));
    array_1d<std::size_t> parent = array_1d<std::size_t>::from_shape({num_leaves * 2 - 1});
    for (std::size_t i = 0, j = num_leaves; i < parent.size() - 1; j++) {
        parent(i++) = j;
        parent(i++) = j;
    }
    parent(parent.size() - 1) = parent.size() - 1;
    return tree(parent);
}

static void BM_tree_propagate_parallel(benchmark::State &state) {
    for (auto _ : state) {
        state.PauseTiming();

        std::size_t size = state.range(0);
        xt::random::seed(42);
        auto t = get_complete_binary_tree(size);
        auto input = eval(random::randn<double>({t.num_vertices()}));
        state.ResumeTiming();
        array_nd<double> output = array_nd<double>::from_shape({t.num_vertices()});
        auto &parents = t.parents();
        for (auto i: leaves_to_root_iterator(t, leaves_it::exclude)) {
            output.unchecked(i) = input.unchecked(parents.unchecked(i));
        }

        benchmark::DoNotOptimize(output[t.root()]);
    }
}

BENCHMARK(BM_tree_propagate_parallel)->Range(1 << min_tree_size, 1 << max_tree_size);

static void BM_view_propagate_parallel(benchmark::State &state) {
    for (auto _ : state) {
        state.PauseTiming();

        std::size_t size = state.range(0);
        xt::random::seed(42);
        auto t = get_complete_binary_tree(size);
        auto input = eval(random::randn<double>({t.num_vertices()}));
        state.ResumeTiming();
        array_nd<double> output = xt::eval(xt::index_view(input, t.parents()));

        benchmark::DoNotOptimize(output[t.root()]);
    }
}

BENCHMARK(BM_view_propagate_parallel)->Range(1 << min_tree_size, 1 << max_tree_size);

<|start_filename|>test/cpp/structure/details/test_light_axis_view.cpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#include "higra/structure/details/light_axis_view.hpp"
#include "higra/structure/array.hpp"
#include "xtensor/xview.hpp"
#include "xtensor/xstrided_view.hpp"
#include "../../test_utils.hpp"

namespace light_axis_view {

    using namespace hg;
    using namespace std;
    using namespace xt;

    template<typename T>
    void print_it(T &&it) {
        std::cout << "{";
        for (auto v = it.begin(); v != it.end(); v++) {
            std::cout << *v << " ";
        }
        std::cout << "}" << std::endl;
    }


    template<typename T1, typename T2>
    bool compare(T1 &&v1, T2 &&v2) {
        auto b1 = v1.begin();
        auto b2 = v2.begin();
        auto e1 = v1.end();
        auto e2 = v2.end();

        for (; b1 != e1 && b2 != e2; b1++, b2++) {
            if (*b1 != *b2)
                return false;
        }

        if (b1 != e1 || b2 != e2)
            return false;
        return true;
    };

    TEST_CASE("view on array", "[light_axis_view]") {
        array_nd<int> a{
                {
                        {1, 2, 3},
                        {4,  5,  6}
                },
                {
                        {7, 8, 9},
                        {10, 11, 12}
                }
        };
        auto v = make_light_axis_view<true>(a);
        auto v1 = view(a, 0);
        REQUIRE(compare(v, v1));

        v.set_position(1);
        auto v2 = view(a, 1);
        REQUIRE(compare(v, v2));
    }


    /* BOOST_AUTO_TEST_CASE(view_on_view_simple) {
 
         array_nd<int> a{{{1, 2, 3},
                                 {4,  5,  6}},
                         {{7, 8, 9},
                                 {10, 11, 12}}};
         auto va = view(a, 1);
         auto v = make_light_axis_view<true>(va);
         std::array<int, 3> v1{7, 8, 9};
         REQUIRE(compare(v, v1));
         std::cout << va << std::endl;
         print_it(v);
         v.set_position(1);
         print_it(v);
         std::array<int, 3> v2{10, 11, 12};
         REQUIRE(compare(v, v2));
     }*/

    TEST_CASE("view on xtensor view", "[light_axis_view]") {
        array_nd<int> a{
                {
                        {1, 2, 3},
                        {4,  5,  6}
                },
                {
                        {7, 8, 9},
                        {10, 11, 12}
                }
        };
        auto va = view(a, xt::all(), xt::range(1, 2), xt::range(0, 3, 2));
        auto v = make_light_axis_view<true>(va);
        std::array<int, 2> v1{4, 6};
        REQUIRE(compare(v, v1));

        v.set_position(1);
        std::array<int, 2> v2{10, 12};
        REQUIRE(compare(v, v2));
    }

    TEST_CASE("view on xtensor strided view", "[light_axis_view]") {
        array_nd<int> a{
                {
                        {1, 2, 3},
                        {4,  5,  6}
                },
                {
                        {7, 8, 9},
                        {10, 11, 12}
                }
        };
        auto va = strided_view(a, {1});
        auto v = make_light_axis_view<true>(va);
        std::array<int, 3> v1{7, 8, 9};
        REQUIRE(compare(v, v1));

        v.set_position(1);
        std::array<int, 3> v2{10, 11, 12};
        REQUIRE(compare(v, v2));
    }

    TEST_CASE("view on xtensor strided view complex", "[light_axis_view]") {
        array_nd<int> a{
                {
                        {1, 2, 3},
                        {4,  5,  6}
                },
                {
                        {7, 8, 9},
                        {10, 11, 12}
                }
        };
        auto va = strided_view(a, {xt::all(), xt::range(1, 2), xt::range(0, 3, 2)});
        auto v = make_light_axis_view<true>(va);
        std::array<int, 2> v1{4, 6};
        REQUIRE(compare(v, v1));

        v.set_position(1);
        std::array<int, 2> v2{10, 12};
        REQUIRE(compare(v, v2));
    }

}

<|start_filename|>include/higra/io/tree_io.hpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#pragma once

#include "../graph.hpp"
#include "xtensor/xexpression.hpp"
#include <istream>
#include <ostream>
#include <map>

namespace hg {

#define HG_TREE_IO_VERSION "1"

#define HG_TREE_IO_VERSION_KEY "VERSION"
#define HG_TREE_IO_NBNODES_KEY "NBNODES"
#define HG_TREE_IO_NBATTRIBUTES_KEY "NBATTR"
#define HG_TREE_IO_HEADEREND_KEY "END"
#define HG_TREE_IO_NAME_KEY "NAME"

    //bool saveBPT(char * path, int nbnodes, int * parents, int numAttr, double ** attrs, char ** attrNames);
    //bool readBPT(char * path, int * nbnodes, int ** parents, int * numAttr, double *** attrs, char *** attrNames);


    namespace tree_io_internal {


        struct tree_saver_helper {

            using out_type = std::ostream &;

            tree_saver_helper(out_type out, const tree &t) : m_tree(t), m_out(out) {
                init();
            }

            ~tree_saver_helper() {
                finalize();
            }

            template<typename T>
            tree_saver_helper &add_attribute(const std::string &name, const xt::xexpression<T> &xarray) {
                auto &array = xarray.derived_cast();
                hg_assert(array.dimension() == 1, "Only scalar attributes are supported.");
                hg_assert(array.size() == m_tree.num_vertices(), "Attribute size does not match the size of the tree.");

                // force conversion to double
                // TODO avoid copy when not needed
                xt::xarray<double> a = array;

                m_num_attr++;

                m_out << HG_TREE_IO_NAME_KEY << "=" << name << std::endl;
                m_out << HG_TREE_IO_HEADEREND_KEY << std::endl;

                m_out.write(reinterpret_cast<const char *>(a.data()), std::streamsize(array.size() * sizeof(double)));

                return *this;
            }

            void finalize() {
                if (!finalized) {
                    m_out.seekp(m_nb_attr_position);
                    m_out << m_num_attr;
                    finalized = true;
                }
            }


        private:
            void init() {
                m_out << HG_TREE_IO_VERSION_KEY << "=" << HG_TREE_IO_VERSION << std::endl;
                m_out << HG_TREE_IO_NBNODES_KEY << "=" << m_tree.num_vertices() << std::endl;
                m_out << HG_TREE_IO_NBATTRIBUTES_KEY << "=";
                m_nb_attr_position = m_out.tellp();
                // this will be filled later ....
                m_out << "                             " << std::endl;
                m_out << HG_TREE_IO_HEADEREND_KEY << std::endl;

                std::vector<int> p;
                p.reserve(num_vertices(m_tree));
                for (auto v: parents(m_tree))
                    p.push_back(v);

                m_out.write(reinterpret_cast<const char *>(&p[0]), std::streamsize(p.size() * sizeof(int)));
            }


            const tree &m_tree;
            out_type m_out;
            index_t m_nb_attr_position = -1;
            index_t m_num_attr = 0;
            bool finalized = false;
        };

    }

    inline
    auto
    save_tree(std::ostream &out, const tree &t) {
        return tree_io_internal::tree_saver_helper(out, t);
    }


    inline
    auto
    read_tree(std::istream &in) {
        std::string key = "";
        char dummy;
        std::string tmp;

        int num_vertices = -1;
        int num_attributes = -1;

        while (key != HG_TREE_IO_HEADEREND_KEY) {
            int value;
            in >> tmp;
            std::size_t pos = tmp.find('=');
            if (pos != std::string::npos) {
                key = tmp.substr(0, pos);
                value = std::stoi(tmp.substr(pos + 1));
            } else {
                key = tmp;
            }

            if (key == HG_TREE_IO_VERSION_KEY) {

            } else if (key == HG_TREE_IO_NBNODES_KEY) {
                num_vertices = value;
            } else if (key == HG_TREE_IO_NBATTRIBUTES_KEY) {
                num_attributes = value;
            } else if (key == HG_TREE_IO_HEADEREND_KEY) {

            } else {
                HG_LOG_WARNING("Key '%s' is unknown and will be ignored.", key.c_str());
            }

        }

        hg_assert(num_vertices > 0, "Incorrect or missing key "
                HG_TREE_IO_NBNODES_KEY);
        hg_assert(num_attributes >= 0, "Incorrect or missing key "
                HG_TREE_IO_NBATTRIBUTES_KEY);

        in.read(&dummy, 1); // consumme the last \n left by cin...

        array_1d<int> parents;
        parents.resize({std::size_t(num_vertices)});
        in.read(reinterpret_cast<char *>(parents.data()), std::streamsize(num_vertices * sizeof(int)));

        std::map<std::string, array_1d<double>> attributes;

        for (int i = 0; i < num_attributes; i++) {
            std::string name = "";
            key = "";
            while (key != HG_TREE_IO_HEADEREND_KEY) {
                in >> tmp;
                std::size_t pos = tmp.find('=');
                std::string value;
                if (pos != std::string::npos) {
                    key = tmp.substr(0, pos);
                    value = tmp.substr(pos + 1);
                } else {
                    key = tmp;
                }

                if (key == HG_TREE_IO_NAME_KEY) {
                    name = value;
                } else if (key == HG_TREE_IO_HEADEREND_KEY) {

                } else {
                    HG_LOG_WARNING("Key '%s' is unknown and will be ignored.", key.c_str());
                }
            }
            hg_assert(name != "", "Incorrect or missing key for attribute " + std::to_string(i) + " " +
                                  HG_TREE_IO_NAME_KEY);
            in.read(&dummy, 1); // consumme the last \n left by cin...

            attributes.emplace(name, array_1d<double>::from_shape({std::size_t(num_vertices)}));
            in.read(reinterpret_cast<char *>(attributes[name].data()), std::streamsize(num_vertices * sizeof(double)));
        }

        return std::make_pair(tree(parents), std::move(attributes));
    }

}




<|start_filename|>lib/include/xtensor-python/pyarray.hpp<|end_filename|>
/***************************************************************************
* Copyright (c) <NAME>, <NAME> and <NAME>          *
* Copyright (c) QuantStack                                                 *
*                                                                          *
* Distributed under the terms of the BSD 3-Clause License.                 *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#ifndef PY_ARRAY_HPP
#define PY_ARRAY_HPP

#include <algorithm>
#include <cstddef>
#include <vector>

#include "xtensor/xbuffer_adaptor.hpp"
#include "xtensor/xiterator.hpp"
#include "xtensor/xsemantic.hpp"

#include "pyarray_backstrides.hpp"
#include "pycontainer.hpp"
#include "pystrides_adaptor.hpp"
#include "xtensor_type_caster_base.hpp"

namespace xt
{
    template <class T, layout_type L = layout_type::dynamic>
    class pyarray;
}

namespace pybind11
{
    namespace detail
    {
#ifdef PYBIND11_DESCR // The macro is removed from pybind11 since 2.3
        template <class T, xt::layout_type L>
        struct handle_type_name<xt::pyarray<T, L>>
        {
            static PYBIND11_DESCR name()
            {
                return _("numpy.ndarray[") + npy_format_descriptor<T>::name() + _("]");
            }
        };
#endif

        template <typename T, xt::layout_type L>
        struct pyobject_caster<xt::pyarray<T, L>>
        {
            using type = xt::pyarray<T, L>;

            bool load(handle src, bool convert)
            {
                if (!convert)
                {
                    if (!xt::detail::check_array<T>(src))
                    {
                        return false;
                    }
                }
                value = type::ensure(src);
                return static_cast<bool>(value);
            }

            static handle cast(const handle& src, return_value_policy, handle)
            {
                return src.inc_ref();
            }

#ifdef PYBIND11_DESCR // The macro is removed from pybind11 since 2.3
            PYBIND11_TYPE_CASTER(type, handle_type_name<type>::name());
#else
            PYBIND11_TYPE_CASTER(type, _("numpy.ndarray[") + npy_format_descriptor<T>::name + _("]"));
#endif
        };

        // Type caster for casting ndarray to xexpression<pyarray>
        template <typename T, xt::layout_type L>
        struct type_caster<xt::xexpression<xt::pyarray<T, L>>> : pyobject_caster<xt::pyarray<T, L>>
        {
            using Type = xt::xexpression<xt::pyarray<T, L>>;

            operator Type&()
            {
                return this->value;
            }

            operator const Type&()
            {
                return this->value;
            }
        };

        // Type caster for casting xarray to ndarray
        template <class T, xt::layout_type L>
        struct type_caster<xt::xarray<T, L>> : xtensor_type_caster_base<xt::xarray<T, L>>
        {
        };
    }
}

namespace xt
{
    template <class T, layout_type L>
    struct xiterable_inner_types<pyarray<T, L>>
        : xcontainer_iterable_types<pyarray<T, L>>
    {
    };

    template <class T, layout_type L>
    struct xcontainer_inner_types<pyarray<T, L>>
    {
        using storage_type = xbuffer_adaptor<T*>;
        using reference = typename storage_type::reference;
        using const_reference = typename storage_type::const_reference;
        using size_type = typename storage_type::size_type;
        using shape_type = std::vector<typename storage_type::size_type>;
        using strides_type = std::vector<typename storage_type::difference_type>;
        using backstrides_type = pyarray_backstrides<pyarray<T, L>>;
        using inner_shape_type = xbuffer_adaptor<std::size_t*>;
        using inner_strides_type = pystrides_adaptor<sizeof(T)>;
        using inner_backstrides_type = backstrides_type;
        using temporary_type = pyarray<T, L>;
        static constexpr layout_type layout = L;
    };

    /**
     * @class pyarray
     * @brief Multidimensional container providing the xtensor container semantics to a numpy array.
     *
     * pyarray is similar to the xarray container in that it has a dynamic dimensionality.
     * Reshapes of a pyarray container are reflected in the underlying numpy array.
     *
     * @tparam T The type of the element stored in the pyarray.
     * @tparam L Static layout of the pyarray
     *
     * @sa pytensor
     */
    template <class T, layout_type L>
    class pyarray : public pycontainer<pyarray<T, L>>,
                    public xcontainer_semantic<pyarray<T, L>>
    {
    public:

        using self_type = pyarray<T, L>;
        using semantic_base = xcontainer_semantic<self_type>;
        using base_type = pycontainer<self_type>;
        using storage_type = typename base_type::storage_type;
        using value_type = typename base_type::value_type;
        using reference = typename base_type::reference;
        using const_reference = typename base_type::const_reference;
        using pointer = typename base_type::pointer;
        using size_type = typename base_type::size_type;
        using difference_type = typename base_type::difference_type;
        using shape_type = typename base_type::shape_type;
        using strides_type = typename base_type::strides_type;
        using backstrides_type = typename base_type::backstrides_type;
        using inner_shape_type = typename base_type::inner_shape_type;
        using inner_strides_type = typename base_type::inner_strides_type;
        using inner_backstrides_type = typename base_type::inner_backstrides_type;

        pyarray();
        pyarray(const value_type& t);
        pyarray(nested_initializer_list_t<T, 1> t);
        pyarray(nested_initializer_list_t<T, 2> t);
        pyarray(nested_initializer_list_t<T, 3> t);
        pyarray(nested_initializer_list_t<T, 4> t);
        pyarray(nested_initializer_list_t<T, 5> t);

        pyarray(pybind11::handle h, pybind11::object::borrowed_t);
        pyarray(pybind11::handle h, pybind11::object::stolen_t);
        pyarray(const pybind11::object& o);

        explicit pyarray(const shape_type& shape, layout_type l = layout_type::row_major);
        explicit pyarray(const shape_type& shape, const_reference value, layout_type l = layout_type::row_major);
        explicit pyarray(const shape_type& shape, const strides_type& strides, const_reference value);
        explicit pyarray(const shape_type& shape, const strides_type& strides);

        template <class S = shape_type>
        static pyarray from_shape(S&& s);

        pyarray(const self_type& rhs);
        self_type& operator=(const self_type& rhs);

        pyarray(self_type&&) = default;
        self_type& operator=(self_type&& e) = default;

        template <class E>
        pyarray(const xexpression<E>& e);

        template <class E>
        self_type& operator=(const xexpression<E>& e);

        using base_type::begin;
        using base_type::end;

        static self_type ensure(pybind11::handle h);
        static bool check_(pybind11::handle h);

#if PYBIND11_VERSION_MAJOR == 2 && PYBIND11_VERSION_MINOR >= 3
        // Prevent ambiguous overload resolution for operators defined for
        // both xt::xcontainer_semantic and pybind11::object.
        using semantic_base::operator+=;
        using semantic_base::operator-=;
        using semantic_base::operator*=;
        using semantic_base::operator/=;
        using semantic_base::operator|=;
        using semantic_base::operator&=;
        using semantic_base::operator^=;
        // using semantic_base::operator<<=;
        // using semantic_base::operator>>=;
#endif

    private:

        inner_shape_type m_shape;
        inner_strides_type m_strides;
        mutable inner_backstrides_type m_backstrides;
        storage_type m_storage;

        void init_array(const shape_type& shape, const strides_type& strides);
        void init_from_python();

        const inner_shape_type& shape_impl() const noexcept;
        const inner_strides_type& strides_impl() const noexcept;
        const inner_backstrides_type& backstrides_impl() const noexcept;

        storage_type& storage_impl() noexcept;
        const storage_type& storage_impl() const noexcept;

        friend class xcontainer<pyarray<T, L>>;
        friend class pycontainer<pyarray<T, L>>;
    };

    /**************************
     * pyarray implementation *
     **************************/

    /**
     * @name Constructors
     */
    //@{
    template <class T, layout_type L>
    inline pyarray<T, L>::pyarray()
        : base_type()
    {
        // TODO: avoid allocation
        shape_type shape = xtl::make_sequence<shape_type>(0, size_type(1));
        strides_type strides = xtl::make_sequence<strides_type>(0, size_type(0));
        init_array(shape, strides);
        detail::default_initialize(m_storage);
    }

    /**
     * Allocates a pyarray with nested initializer lists.
     */
    template <class T, layout_type L>
    inline pyarray<T, L>::pyarray(const value_type& t)
        : base_type()
    {
        base_type::resize(xt::shape<shape_type>(t), layout_type::row_major);
        nested_copy(m_storage.begin(), t);
    }

    template <class T, layout_type L>
    inline pyarray<T, L>::pyarray(nested_initializer_list_t<T, 1> t)
        : base_type()
    {
        base_type::resize(xt::shape<shape_type>(t), layout_type::row_major);
        nested_copy(m_storage.begin(), t);
    }

    template <class T, layout_type L>
    inline pyarray<T, L>::pyarray(nested_initializer_list_t<T, 2> t)
        : base_type()
    {
        base_type::resize(xt::shape<shape_type>(t), layout_type::row_major);
        nested_copy(m_storage.begin(), t);
    }

    template <class T, layout_type L>
    inline pyarray<T, L>::pyarray(nested_initializer_list_t<T, 3> t)
        : base_type()
    {
        base_type::resize(xt::shape<shape_type>(t), layout_type::row_major);
        nested_copy(m_storage.begin(), t);
    }

    template <class T, layout_type L>
    inline pyarray<T, L>::pyarray(nested_initializer_list_t<T, 4> t)
        : base_type()
    {
        base_type::resize(xt::shape<shape_type>(t), layout_type::row_major);
        nested_copy(m_storage.begin(), t);
    }

    template <class T, layout_type L>
    inline pyarray<T, L>::pyarray(nested_initializer_list_t<T, 5> t)
        : base_type()
    {
        base_type::resize(xt::shape<shape_type>(t), layout_type::row_major);
        nested_copy(m_storage.begin(), t);
    }

    template <class T, layout_type L>
    inline pyarray<T, L>::pyarray(pybind11::handle h, pybind11::object::borrowed_t b)
        : base_type(h, b)
    {
        init_from_python();
    }

    template <class T, layout_type L>
    inline pyarray<T, L>::pyarray(pybind11::handle h, pybind11::object::stolen_t s)
        : base_type(h, s)
    {
        init_from_python();
    }

    template <class T, layout_type L>
    inline pyarray<T, L>::pyarray(const pybind11::object& o)
        : base_type(o)
    {
        init_from_python();
    }

    /**
     * Allocates an uninitialized pyarray with the specified shape and
     * layout.
     * @param shape the shape of the pyarray
     * @param l the layout of the pyarray
     */
    template <class T, layout_type L>
    inline pyarray<T, L>::pyarray(const shape_type& shape, layout_type l)
        : base_type()
    {
        strides_type strides(shape.size());
        compute_strides(shape, l, strides);
        init_array(shape, strides);
    }

    /**
     * Allocates a pyarray with the specified shape and layout. Elements
     * are initialized to the specified value.
     * @param shape the shape of the pyarray
     * @param value the value of the elements
     * @param l the layout of the pyarray
     */
    template <class T, layout_type L>
    inline pyarray<T, L>::pyarray(const shape_type& shape, const_reference value, layout_type l)
        : base_type()
    {
        strides_type strides(shape.size());
        compute_strides(shape, l, strides);
        init_array(shape, strides);
        std::fill(m_storage.begin(), m_storage.end(), value);
    }

    /**
     * Allocates an uninitialized pyarray with the specified shape and strides.
     * Elements are initialized to the specified value.
     * @param shape the shape of the pyarray
     * @param strides the strides of the pyarray
     * @param value the value of the elements
     */
    template <class T, layout_type L>
    inline pyarray<T, L>::pyarray(const shape_type& shape, const strides_type& strides, const_reference value)
        : base_type()
    {
        init_array(shape, strides);
        std::fill(m_storage.begin(), m_storage.end(), value);
    }

    /**
     * Allocates an uninitialized pyarray with the specified shape and strides.
     * @param shape the shape of the pyarray
     * @param strides the strides of the pyarray
     */
    template <class T, layout_type L>
    inline pyarray<T, L>::pyarray(const shape_type& shape, const strides_type& strides)
        : base_type()
    {
        init_array(shape, strides);
    }

    /**
     * Allocates and returns an pyarray with the specified shape.
     * @param shape the shape of the pyarray
     */
    template <class T, layout_type L>
    template <class S>
    inline pyarray<T, L> pyarray<T, L>::from_shape(S&& shape)
    {
        auto shp = xtl::forward_sequence<shape_type, S>(shape);
        return self_type(shp);
    }
    //@}

    /**
     * @name Copy semantic
     */
    //@{
    /**
     * The copy constructor.
     */
    template <class T, layout_type L>
    inline pyarray<T, L>::pyarray(const self_type& rhs)
        : base_type(), semantic_base(rhs)
    {
        auto tmp = pybind11::reinterpret_steal<pybind11::object>(
            PyArray_NewLikeArray(rhs.python_array(), NPY_KEEPORDER, nullptr, 1));

        if (!tmp)
        {
            throw std::runtime_error("NumPy: unable to create ndarray");
        }

        this->m_ptr = tmp.release().ptr();
        init_from_python();
        std::copy(rhs.storage().cbegin(), rhs.storage().cend(), this->storage().begin());
    }

    /**
     * The assignment operator.
     */
    template <class T, layout_type L>
    inline auto pyarray<T, L>::operator=(const self_type& rhs) -> self_type&
    {
        self_type tmp(rhs);
        *this = std::move(tmp);
        return *this;
    }

    //@}

    /**
     * @name Extended copy semantic
     */
    //@{
    /**
     * The extended copy constructor.
     */
    template <class T, layout_type L>
    template <class E>
    inline pyarray<T, L>::pyarray(const xexpression<E>& e)
        : base_type()
    {
        // TODO: prevent intermediary shape allocation
        shape_type shape = xtl::forward_sequence<shape_type, decltype(e.derived_cast().shape())>(e.derived_cast().shape());
        strides_type strides = xtl::make_sequence<strides_type>(shape.size(), size_type(0));
        compute_strides(shape, layout_type::row_major, strides);
        init_array(shape, strides);
        semantic_base::assign(e);
    }

    /**
     * The extended assignment operator.
     */
    template <class T, layout_type L>
    template <class E>
    inline auto pyarray<T, L>::operator=(const xexpression<E>& e) -> self_type&
    {
        return semantic_base::operator=(e);
    }
    //@}

    template <class T, layout_type L>
    inline auto pyarray<T, L>::ensure(pybind11::handle h) -> self_type
    {
        return base_type::ensure(h);
    }

    template <class T, layout_type L>
    inline bool pyarray<T, L>::check_(pybind11::handle h)
    {
        return base_type::check_(h);
    }

    template <class T, layout_type L>
    inline void pyarray<T, L>::init_array(const shape_type& shape, const strides_type& strides)
    {
        strides_type adapted_strides(strides);

        std::transform(strides.begin(), strides.end(), adapted_strides.begin(),
                       [](auto v) { return sizeof(T) * v; });

        int flags = NPY_ARRAY_ALIGNED;
        if (!std::is_const<T>::value)
        {
            flags |= NPY_ARRAY_WRITEABLE;
        }

        auto dtype = pybind11::detail::npy_format_descriptor<T>::dtype();

        npy_intp* shape_data = reinterpret_cast<npy_intp*>(const_cast<size_type*>(shape.data()));
        npy_intp* strides_data = reinterpret_cast<npy_intp*>(adapted_strides.data());

        auto tmp = pybind11::reinterpret_steal<pybind11::object>(
            PyArray_NewFromDescr(&PyArray_Type, (PyArray_Descr*) dtype.release().ptr(), static_cast<int>(shape.size()), shape_data, strides_data,
                        nullptr, flags, nullptr));

        if (!tmp)
        {
            throw std::runtime_error("NumPy: unable to create ndarray");
        }

        this->m_ptr = tmp.release().ptr();
        init_from_python();
    }

    template <class T, layout_type L>
    inline void pyarray<T, L>::init_from_python()
    {
        if (!static_cast<bool>(*this))
        {
            return;
        }
        
        m_shape = inner_shape_type(reinterpret_cast<size_type*>(PyArray_SHAPE(this->python_array())),
                                   static_cast<size_type>(PyArray_NDIM(this->python_array())));
        m_strides = inner_strides_type(reinterpret_cast<difference_type*>(PyArray_STRIDES(this->python_array())),
                                       static_cast<size_type>(PyArray_NDIM(this->python_array())));

        if (L != layout_type::dynamic && !do_strides_match(m_shape, m_strides, L, 1))
        {
            throw std::runtime_error("NumPy: passing container with bad strides for layout (is it a view?).");
        }

        m_backstrides = backstrides_type(*this);
        m_storage = storage_type(reinterpret_cast<pointer>(PyArray_DATA(this->python_array())),
                                 this->get_buffer_size());
    }

    template <class T, layout_type L>
    inline auto pyarray<T, L>::shape_impl() const noexcept -> const inner_shape_type&
    {
        return m_shape;
    }

    template <class T, layout_type L>
    inline auto pyarray<T, L>::strides_impl() const noexcept -> const inner_strides_type&
    {
        return m_strides;
    }

    template <class T, layout_type L>
    inline auto pyarray<T, L>::backstrides_impl() const noexcept -> const inner_backstrides_type&
    {
        // m_backstrides wraps the numpy array backstrides, which is a raw pointer.
        // The address of the raw pointer stored in the wrapper would be invalidated when the pyarray is copied.
        // Hence, we build a new backstrides object (cheap wrapper around the underlying pointer) upon access.
        m_backstrides = backstrides_type(*this);
        return m_backstrides;
    }

    template <class T, layout_type L>
    inline auto pyarray<T, L>::storage_impl() noexcept -> storage_type&
    {
        return m_storage;
    }

    template <class T, layout_type L>
    inline auto pyarray<T, L>::storage_impl() const noexcept -> const storage_type&
    {
        return m_storage;
    }
}

#endif


<|start_filename|>test/cpp/assessment/test_dendrogram_purity.cpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#include "higra/assessment/dendrogram_purity.hpp"
#include "../test_utils.hpp"

using namespace hg;

namespace assessment_dendrogram_purity {

    TEST_CASE("dendrogram purity", "[dendrogram purity]") {
        SECTION("binary"){
            tree t(array_1d <index_t>{5,5,6,7,7,6,8,8,8});
            array_1d<int> labels{1,1,0,1,0};

            auto p = dendrogram_purity(t, labels);

            REQUIRE(almost_equal(p, 0.65));
        }
        SECTION("non binary"){
            tree t(array_1d <index_t>{5,5,5,6,6,7,7,7});
            array_1d<int> labels{1,1,0,1,0};

            auto p = dendrogram_purity(t, labels);

            REQUIRE(almost_equal(p, 0.5666666666666667));
        }
    }
}


<|start_filename|>test/cpp/algo/test_graph_weights.cpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#include "higra/image/graph_image.hpp"
#include "higra/algo/graph_weights.hpp"
#include "../test_utils.hpp"


using namespace hg;
using namespace std;

namespace test_graph_weights {
    TEST_CASE("graph edge weighting scalar", "[graph_weights]") {

        auto g = get_4_adjacency_graph({2, 2});

        array_1d<double> data{0, 1, 2, 3};

        array_1d<double> ref1{0.5, 1, 2, 2.5};
        auto r1 = weight_graph(g, data, hg::weight_functions::mean);
        REQUIRE(xt::allclose(ref1, r1));

        array_1d<double> ref2{0, 0, 1, 2};
        auto r2 = weight_graph(g, data, hg::weight_functions::min);
        REQUIRE(xt::allclose(ref2, r2));

        array_1d<double> ref3{1, 2, 3, 3};
        auto r3 = weight_graph(g, data, hg::weight_functions::max);
        REQUIRE(xt::allclose(ref3, r3));

        array_1d<double> ref4{1, 2, 2, 1};
        auto r4 = weight_graph(g, data, hg::weight_functions::L1);
        REQUIRE(xt::allclose(ref4, r4));

        array_1d<double> ref5{std::sqrt(1), 2, 2, std::sqrt(1)};
        auto r5 = weight_graph(g, data, hg::weight_functions::L2);
        REQUIRE(xt::allclose(ref5, r5));

        array_1d<double> ref6{1, 2, 2, 1};
        auto r6 = weight_graph(g, data, hg::weight_functions::L_infinity);
        REQUIRE(xt::allclose(ref6, r6));

        array_1d<double> ref7{1, 4, 4, 1};
        auto r7 = weight_graph(g, data, hg::weight_functions::L2_squared);
        REQUIRE(xt::allclose(ref7, r7));

        array_1d<double> data2{0, 0, 2, 0};
        array_1d<double> ref8{0, 1, 0, 1};
        auto r8 = weight_graph(g, data2, hg::weight_functions::L0);
        REQUIRE(xt::allclose(ref8, r8));

        array_1d<double> ref9{0, 0, 1, 2};
        auto r9 = weight_graph(g, data, hg::weight_functions::source);
        REQUIRE(xt::allclose(ref9, r9));

        array_1d<double> ref10{1, 2, 3, 3};
        auto r10 = weight_graph(g, data, hg::weight_functions::target);
        REQUIRE(xt::allclose(ref10, r10));
    }

    TEST_CASE("graph edge weighting vectorial", "[graph_weights]") {

        auto g = get_4_adjacency_graph({2, 2});

        array_2d<double> data{{0, 1},
                              {2, 3},
                              {4, 5},
                              {6, 7}};

        array_1d<double> ref4{4, 8, 8, 4};
        auto r4 = weight_graph(g, data, hg::weight_functions::L1);
        REQUIRE(xt::allclose(ref4, r4));

        array_1d<double> ref5{std::sqrt(8), std::sqrt(32), std::sqrt(32), std::sqrt(8)};
        auto r5 = weight_graph(g, data, hg::weight_functions::L2);
        REQUIRE(xt::allclose(ref5, r5));

        array_1d<double> ref6{2, 4, 4, 2};
        auto r6 = weight_graph(g, data, hg::weight_functions::L_infinity);
        REQUIRE(xt::allclose(ref6, r6));

        array_1d<double> ref7{8, 32, 32, 8};
        auto r7 = weight_graph(g, data, hg::weight_functions::L2_squared);
        REQUIRE(xt::allclose(ref7, r7));

        array_2d<double> data2{{0, 1},
                              {2, 3},
                              {0, 1},
                              {6, 7}};

        array_1d<double> ref8{1, 0, 1, 1};
        auto r8 = weight_graph(g, data2, hg::weight_functions::L0);
        REQUIRE(xt::allclose(ref8, r8));
    }
}


<|start_filename|>include/higra/structure/details/indexed_edge.hpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#pragma once
#include "graph_concepts.hpp"

namespace hg {

    /**
     * An edge with a source vertex, a target vertex, and an index.
     * @tparam vertex_descriptor
     * @tparam edge_index_t
     */
    template<typename vertex_descriptor,
            typename edge_index_t>
    class indexed_edge {

    public:
        union{vertex_descriptor source; vertex_descriptor first;};
        union{vertex_descriptor target; vertex_descriptor second;};
        edge_index_t index;

        indexed_edge(vertex_descriptor _source,
                     vertex_descriptor _target,
                     edge_index_t _index) :
                source(_source),
                target(_target),
                index(_index) {
        }

        indexed_edge(const std::pair<vertex_descriptor, vertex_descriptor> & edge,
                     edge_index_t _index) :
                source(edge.first),
                target(edge.second),
                index(_index) {
        }

        operator edge_index_t() const { return index; }

        operator std::pair<vertex_descriptor, vertex_descriptor>() const {
            return std::make_pair(source, target);
        }
    };

    /**
     * Source vertex of an edge
     * @tparam graph_t
     * @param e
     * @return
     */
    template<typename graph_t>
    auto & source(
            const indexed_edge<typename graph::graph_traits<graph_t>::vertex_descriptor,
                    typename graph::graph_traits<graph_t>::edge_index> &e,
            const graph_t &) {
        return e.source;
    }

    /**
     * Target vertex of an edge
     * @tparam graph_t
     * @param e
     * @return
     */
    template<typename graph_t>
    auto & target(
            const indexed_edge<typename graph::graph_traits<graph_t>::vertex_descriptor,
                    typename graph::graph_traits<graph_t>::edge_index> &e,
            const graph_t &) {
        return e.target;
    }

    /**
    * Index of an edge
    * @tparam graph_t
    * @param e
    * @return
    */
    template<typename graph_t>
    auto & index(
            const indexed_edge<typename graph::graph_traits<graph_t>::vertex_descriptor,
                    typename graph::graph_traits<graph_t>::edge_index> &e,
            const graph_t &) {
        return e.index;
    }
}


<|start_filename|>higra/algo/py_rag.cpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#include "py_rag.hpp"
#include "higra/algo/rag.hpp"
#include "higra/algo/alignment.hpp"
#include "../py_common.hpp"
#include "xtensor-python/pyarray.hpp"
#include "xtensor-python/pytensor.hpp"

// @TODO Remove layout_type when xtensor solves the issue with iterators
template<typename T>
using pyarray = xt::pyarray<T, xt::layout_type::row_major>;

namespace py = pybind11;

template<typename graph_t>
struct def_make_rag {
    template<typename value_t, typename C>
    static
    void def(C &c, const char *doc) {
        c.def("_make_region_adjacency_graph_from_labelisation",
              [](const graph_t &graph, const pyarray<value_t> &input) {
                  auto res = hg::make_region_adjacency_graph_from_labelisation(graph, input);
                  return py::make_tuple(std::move(res.rag), std::move(res.vertex_map), std::move(res.edge_map));
              },
              doc,
              py::arg("graph"),
              py::arg("vertex_labels"));
    }
};

template<typename graph_t>
struct def_make_rag_cut {
    template<typename value_t, typename C>
    static
    void def(C &c, const char *doc) {
        c.def("_make_region_adjacency_graph_from_graph_cut", [](const graph_t &graph, const pyarray<value_t> &input) {
                  auto res = hg::make_region_adjacency_graph_from_graph_cut(graph, input);
                  return py::make_tuple(std::move(res.rag), std::move(res.vertex_map), std::move(res.edge_map));
              },
              doc,
              py::arg("graph"),
              py::arg("edge_weights"));
    }
};

struct def_rag_back_project_weights {
    template<typename value_t, typename C>
    static
    void def(C &c, const char *doc) {
        c.def("_rag_back_project_weights",
              [](const xt::pyarray<hg::index_t> &rag_map, const pyarray<value_t> &rag_weights) {
                  return hg::rag_back_project_weights(rag_map, rag_weights);
              },
              doc,
              py::arg("rag_map"),
              py::arg("rag_weights"));
    }
};


void py_init_rag(pybind11::module &m) {
    xt::import_numpy();

    add_type_overloads<def_make_rag<hg::ugraph>, HG_TEMPLATE_INTEGRAL_TYPES>
            (m,
             "Create a region adjacency graph of the input graph with regions identified by the provided vertex labels.");

    add_type_overloads<def_make_rag_cut<hg::ugraph>, HG_TEMPLATE_NUMERIC_TYPES>
            (m,
             "Create a region adjacency graph of the input graph with regions identified by the provided graph cut.");

    add_type_overloads<def_rag_back_project_weights, HG_TEMPLATE_NUMERIC_TYPES>
            (m,
             "Projects vertex or edge weights defined on a region adjacency graph back to the original graph space.");
}




<|start_filename|>test/cpp/algo/test_alignment.cpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#include "higra/algo/alignment.hpp"
#include "higra/image/graph_image.hpp"
#include "../test_utils.hpp"
#include "higra/algo/tree.hpp"

using namespace hg;

namespace test_alignment {

    TEST_CASE("project fine to coarse labelisation", "[alignment]") {

        array_1d<index_t> fine_labels{0, 1, 2, 3, 4, 2, 3, 4, 2};
        array_1d<index_t> coarse_labels{0, 1, 1, 0, 2, 2, 0, 2, 2};

        auto map = project_fine_to_coarse_labelisation(fine_labels, coarse_labels);

        array_1d<index_t> ref_map{0, 1, 2, 0, 2};
        REQUIRE((ref_map == map));
    }

    TEST_CASE("hierarchy alignement", "[alignment]") {

        auto g = get_4_adjacency_graph({3, 3});
        array_1d<int> fine_labels{0, 1, 2, 3, 4, 2, 3, 4, 2};

        auto aligner = make_hierarchy_aligner_from_labelisation(g, fine_labels);

        hg::tree t(array_1d<index_t>{9, 10, 10, 9, 11, 11, 9, 11, 11, 13, 12, 12, 13, 13});
        array_1d<int> altitudes{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2};
        auto sm = aligner.align_hierarchy(t, altitudes);
        auto sm_k = graph_4_adjacency_2_khalimsky(g, {3, 3}, sm);

        array_1d<int> sm_coarse{2, 0, 0, 1, 1, 2, 0, 0, 0, 0, 2, 0};
        auto sm2 = aligner.align_hierarchy(g, sm_coarse);
        auto sm2_k = graph_4_adjacency_2_khalimsky(g, {3, 3}, sm2);

        array_2d<int> sm_k_ref = {{0, 2, 0, 1, 0},
                                  {0, 2, 1, 1, 0},
                                  {0, 2, 0, 0, 0},
                                  {0, 2, 0, 0, 0},
                                  {0, 2, 0, 0, 0}};

        REQUIRE((sm_k == sm_k_ref));
        REQUIRE((sm2_k == sm_k_ref));
    }

    TEST_CASE("hierarchy alignment 2", "[alignment]") {

        auto g = get_4_adjacency_graph({3, 3});
        array_1d<int> edge_weights{1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1};

        auto aligner = make_hierarchy_aligner_from_graph_cut(g, edge_weights);

        hg::tree t(array_1d<index_t>{9, 10, 10, 9, 11, 11, 9, 11, 11, 13, 12, 12, 13, 13});
        array_1d<int> altitudes{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2};
        auto sm = aligner.align_hierarchy(t, altitudes);
        auto sm_k = graph_4_adjacency_2_khalimsky(g, {3, 3}, sm);

        array_1d<int> sm_coarse{2, 0, 0, 1, 1, 2, 0, 0, 0, 0, 2, 0};
        auto sm2 = aligner.align_hierarchy(g, sm_coarse);
        auto sm2_k = graph_4_adjacency_2_khalimsky(g, {3, 3}, sm2);

        array_2d<int> sm_k_ref = {{0, 2, 0, 1, 0},
                                  {0, 2, 1, 1, 0},
                                  {0, 2, 0, 0, 0},
                                  {0, 2, 0, 0, 0},
                                  {0, 2, 0, 0, 0}};

        REQUIRE((sm_k == sm_k_ref));
        REQUIRE((sm2_k == sm_k_ref));
    }

    TEST_CASE("hierarchy alignment with rag", "[alignment]") {

        auto g = get_4_adjacency_graph({3, 3});
        array_1d<int> edge_weights{1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1};

        auto aligner = make_hierarchy_aligner_from_graph_cut(g, edge_weights);


        hg::tree t(array_1d<index_t>{9, 10, 10, 9, 11, 11, 9, 11, 11, 13, 12, 12, 13, 13});
        array_1d<int> altitudes{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2};

        auto res = supervertices_hierarchy(t, altitudes);
        auto &tree_res = res.tree;
        auto &supervertex_labelisation_res = res.supervertex_labelisation;
        auto &node_map_res = res.node_map;

        altitudes = xt::index_view(altitudes, node_map_res);

        auto sm = aligner.align_hierarchy(supervertex_labelisation_res, tree_res, altitudes);
        auto sm_k = graph_4_adjacency_2_khalimsky(g, {3, 3}, sm);

        array_2d<int> sm_k_ref = {{0, 2, 0, 1, 0},
                                  {0, 2, 1, 1, 0},
                                  {0, 2, 0, 0, 0},
                                  {0, 2, 0, 0, 0},
                                  {0, 2, 0, 0, 0}};

        REQUIRE((sm_k == sm_k_ref));
    }

}

<|start_filename|>higra/algo/py_graph_weights.cpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#include "py_graph_weights.hpp"
#include "../py_common.hpp"
#include "higra/algo/graph_weights.hpp"
#include "xtensor-python/pyarray.hpp"
#include "xtensor-python/pytensor.hpp"

// @TODO Remove layout_type when xtensor solves the issue with iterators
template<typename T>
using pyarray = xt::pyarray<T, xt::layout_type::row_major>;

#include "pybind11/functional.h"


namespace py = pybind11;

template<typename graph_t>
struct def_weight_graph {
    template<typename type, typename C>
    static
    void def(C &m, const char *doc) {
        m.def("_weight_graph", [](const graph_t &graph, const pyarray<type> &data, hg::weight_functions weight_f) {
                  return hg::weight_graph(graph, data, weight_f);
              },
              doc,
              py::arg("explicit_graph"),
              py::arg("vertex_weights"),
              py::arg("weigh_function"));
    }
};

void py_init_graph_weights(pybind11::module &m) {
    xt::import_numpy();

    py::enum_<hg::weight_functions>(m, "WeightFunction")
            .value("mean", hg::weight_functions::mean)
            .value("min", hg::weight_functions::min)
            .value("max", hg::weight_functions::max)
            .value("L0", hg::weight_functions::L0)
            .value("L1", hg::weight_functions::L1)
            .value("L2", hg::weight_functions::L2)
            .value("L_infinity", hg::weight_functions::L_infinity)
            .value("L2_squared", hg::weight_functions::L2_squared)
            .value("source", hg::weight_functions::source)
            .value("target", hg::weight_functions::target);


    add_type_overloads<def_weight_graph<hg::ugraph>, HG_TEMPLATE_NUMERIC_TYPES>
            (m,
             "Compute the edge weights of a graph using source and target vertices values"
             " and specified weighting function (see WeightFunction enumeration)."
            );

    add_type_overloads<def_weight_graph<hg::tree>, HG_TEMPLATE_NUMERIC_TYPES>
            (m,
             "Compute the edge weights of a graph using source and target vertices values"
             " and specified weighting function (see WeightFunction enumeration)."
            );

}



<|start_filename|>test/cpp/hierarchy/test_component_tree.cpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#include "../test_utils.hpp"
#include "higra/hierarchy/component_tree.hpp"
#include "higra/attribute/tree_attribute.hpp"
#include "higra/image/graph_image.hpp"
#include "higra/algo/tree.hpp"
#include "xtensor/xadapt.hpp"

using namespace hg;
using namespace std;

namespace binary_partition_tree {

    TEST_CASE("test pre_tree_construction", "[component_tree]") {
        auto graph = get_4_adjacency_implicit_graph({4, 4});
        array_1d<double> vertex_weights({0, 1, 4, 4,
                                         7, 5, 6, 8,
                                         2, 3, 4, 1,
                                         9, 8, 6, 7});
        array_1d<index_t> sorted_vertex_indices = xt::arange(num_vertices(graph));
        std::stable_sort(sorted_vertex_indices.begin(), sorted_vertex_indices.end(),
                         [&vertex_weights](index_t i, index_t j) { return vertex_weights[i] < vertex_weights[j]; });
        auto parents = component_tree_internal::pre_tree_construction(graph, sorted_vertex_indices);

        array_1d<index_t> expected_parents({0, 0, 9, 2,
                                            5, 10, 5, 6,
                                            11, 8, 3, 1,
                                            13, 14, 10, 14});
        REQUIRE((expected_parents == parents));
    }

    TEST_CASE("test canonize_tree", "[component_tree]") {
        auto graph = get_4_adjacency_implicit_graph({4, 4});
        array_1d<double> vertex_weights({0, 1, 4, 4,
                                         7, 5, 6, 8,
                                         2, 3, 4, 1,
                                         9, 8, 6, 7});
        array_1d<index_t> sorted_vertex_indices = xt::arange(num_vertices(graph));
        std::stable_sort(sorted_vertex_indices.begin(), sorted_vertex_indices.end(),
                         [&vertex_weights](index_t i, index_t j) { return vertex_weights[i] < vertex_weights[j]; });

        array_1d<index_t> parents({0, 0, 9, 2,
                                   5, 10, 5, 6,
                                   11, 8, 3, 1,
                                   13, 14, 10, 14});

        component_tree_internal::canonize_tree(parents, vertex_weights, sorted_vertex_indices);

        array_1d<index_t> expected_parents({0, 0, 9, 2,
                                            5, 2, 5, 6,
                                            1, 8, 2, 1,
                                            13, 14, 2, 14});
        REQUIRE((expected_parents == parents));
    }

    TEST_CASE("test expand_canonized_parent_relation", "[component_tree]") {
        auto graph = get_4_adjacency_implicit_graph({4, 4});
        array_1d<double> vertex_weights({0, 1, 4, 4,
                                         7, 5, 6, 8,
                                         2, 3, 4, 1,
                                         9, 8, 6, 7});
        array_1d<index_t> sorted_vertex_indices = xt::arange(num_vertices(graph));
        std::stable_sort(sorted_vertex_indices.begin(), sorted_vertex_indices.end(),
                         [&vertex_weights](index_t i, index_t j) { return vertex_weights[i] < vertex_weights[j]; });

        array_1d<index_t> parents({0, 0, 9, 2,
                                   5, 2, 5, 6,
                                   1, 8, 2, 1,
                                   13, 14, 2, 14});

        auto res = component_tree_internal::expand_canonized_parent_relation(parents, vertex_weights,
                                                                             sorted_vertex_indices);
        auto new_parents = xt::adapt(res.first, {res.first.size()});
        auto new_altitudes = xt::adapt(res.second, {res.second.size()});

        array_1d<index_t> expected_parents({28, 27, 24, 24,
                                            20, 23, 22, 18,
                                            26, 25, 24, 27,
                                            16, 17, 21, 19,
                                            17, 21, 22, 21, 23, 24, 23, 24, 25, 26, 27, 28, 28});
        REQUIRE((expected_parents == new_parents));

        array_1d<double> expected_altitudes({0., 1., 4., 4.,
                                             7., 5., 6., 8.,
                                             2., 3., 4., 1.,
                                             9., 8., 6., 7., 9.,
                                             8., 8., 7., 7., 6.,
                                             6., 5., 4., 3., 2.,
                                             1., 0.});
        REQUIRE((expected_altitudes == new_altitudes));
    }

    TEST_CASE("test max tree", "[component_tree]") {
        auto graph = get_4_adjacency_implicit_graph({4, 4});
        array_1d<double> vertex_weights({0, 1, 4, 4,
                                         7, 5, 6, 8,
                                         2, 3, 4, 1,
                                         9, 8, 6, 7});

        auto res = component_tree_max_tree(graph, vertex_weights);
        auto &tree = res.tree;
        auto &altitudes = res.altitudes;

        REQUIRE(category(tree) == tree_category::component_tree);
        array_1d<index_t> expected_parents({28, 27, 24, 24,
                                            20, 23, 22, 18,
                                            26, 25, 24, 27,
                                            16, 17, 21, 19,
                                            17, 21, 22, 21, 23, 24, 23, 24, 25, 26, 27, 28, 28});
        REQUIRE((expected_parents == tree.parents()));

        array_1d<double> expected_altitudes({0., 1., 4., 4.,
                                             7., 5., 6., 8.,
                                             2., 3., 4., 1.,
                                             9., 8., 6., 7., 9.,
                                             8., 8., 7., 7., 6.,
                                             6., 5., 4., 3., 2.,
                                             1., 0.});
        REQUIRE((expected_altitudes == altitudes));
    }

    TEST_CASE("test min tree", "[component_tree]") {
        auto graph = get_4_adjacency_implicit_graph({4, 4});
        array_1d<double> vertex_weights({0, 1, 4, 4,
                                         7, 5, 6, 8,
                                         2, 3, 4, 1,
                                         9, 8, 6, 7});

        vertex_weights *= -1.;

        auto res = component_tree_min_tree(graph, vertex_weights);
        auto &tree = res.tree;
        auto &altitudes = res.altitudes;

        REQUIRE(category(tree) == tree_category::component_tree);
        array_1d<index_t> expected_parents({28, 27, 24, 24,
                                            20, 23, 22, 18,
                                            26, 25, 24, 27,
                                            16, 17, 21, 19,
                                            17, 21, 22, 21, 23, 24, 23, 24, 25, 26, 27, 28, 28});
        REQUIRE((expected_parents == tree.parents()));

        array_1d<double> expected_altitudes({0., 1., 4., 4.,
                                             7., 5., 6., 8.,
                                             2., 3., 4., 1.,
                                             9., 8., 6., 7., 9.,
                                             8., 8., 7., 7., 6.,
                                             6., 5., 4., 3., 2.,
                                             1., 0.});
        expected_altitudes *= -1.;
        REQUIRE((expected_altitudes == altitudes));
    }

    TEST_CASE("test max tree area filter", "[component_tree]") {
        auto graph = get_4_adjacency_implicit_graph({5, 5});
        array_1d<double> vertex_weights({-5, 2, 2, 5, 5,
                                         -4, 2, 2, 6, 5,
                                         3, 3, 3, 3, 3,
                                         -2, -2, -2, 9, 7,
                                         -1, 0, -2, 8, 9});

        auto res = component_tree_max_tree(graph, vertex_weights);
        auto &tree = res.tree;
        auto &altitudes = res.altitudes;

        auto area = attribute_area(tree);
        auto filtered_weights = reconstruct_leaf_data(tree, altitudes, area <= 4);

        array_1d<double> expected_filtered_weights
                ({-5, 2, 2, 3, 3,
                  -4, 2, 2, 3, 3,
                  3, 3, 3, 3, 3,
                  -2, -2, -2, 3, 3,
                  -2, -2, -2, 3, 3});

        REQUIRE((expected_filtered_weights == filtered_weights));
    }
}

<|start_filename|>include/higra/algo/tree_fusion.hpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#pragma once

#include "../graph.hpp"
#include "../attribute/tree_attribute.hpp"
#include <xtensor/xnoalias.hpp>
#include <vector>
#include <stack>

namespace hg {

    namespace tree_fusion_internal {

        using namespace std;

        template<typename tree_iterator>
        auto tree_fusion_depth_map(const tree_iterator first, const tree_iterator last) {

            index_t i, j;
            tree_iterator ti, tj;
            auto ntrees = last - first;
            hg_assert(ntrees > 1, "Fusion requires at least two trees");
            auto nleaves = num_leaves(**first);
            for (tree_iterator t = first; t != last; t++) {
                hg_assert(num_leaves(**t), "All trees must have the same number of leaves.");
            }

            // cheap digraph!
            vector<vector<index_t>> adj_lists;

            // precompute areas and smallest enclosing shapes
            vector<array_1d<index_t>> areas;
            array_2d<array_1d<index_t>> ses = xt::empty<array_1d<index_t>>({ntrees, ntrees});
            for (ti = first, i = 0; ti != last; ti++, i++) {
                areas.push_back(attribute_area(**ti));
                for (tj = first, j = 0; tj != last; tj++, j++) {
                    if (j != i) {
                        ses(i, j) = attribute_smallest_enclosing_shape(**ti, **tj);
                    }
                }
            }

            /* ***************
             * Add nodes to the graph of shapes (GOS)
             */

            // associate each node of each tree to a node of the GOS
            vector<array_1d<index_t>> node_maps;

            // add leaves
            adj_lists.resize(nleaves);

            // add internal nodes (except root) and avoid duplication
            for (ti = first, i = 0; ti != last; ti++, i++) {
                node_maps.emplace_back(array_1d<index_t>::from_shape({num_vertices(**ti)}));
                // create leaves association
                xt::noalias(xt::view(node_maps[i], xt::range(0, nleaves))) = xt::arange<index_t>(nleaves);

                for (index_t n: leaves_to_root_iterator(**ti, leaves_it::exclude, root_it::exclude)) {
                    bool keep = true;
                    for (index_t j = 0; j < i && keep; j++) {
                        auto ses_ij_n = ses(i, j)(n);
                        if (areas[j](ses_ij_n) == areas[i](n)) {
                            keep = false;
                            node_maps[i](n) = node_maps[j](ses_ij_n);
                        }
                    }
                    if (keep) {
                        node_maps[i](n) = adj_lists.size();
                        adj_lists.emplace_back();
                    }
                }
            }

            // add root
            auto rootn = adj_lists.size();
            adj_lists.emplace_back();
            for (ti = first, i = 0; ti != last; ti++, i++) {
                node_maps[i](root(**ti)) = rootn;
            }


            /* ***************
             * Add edges to the graph of shapes (GOS)
             */
            for (ti = first, i = 0; ti != last; ti++, i++) {
                for (index_t n: leaves_to_root_iterator(**ti, leaves_it::include, root_it::exclude)) {
                    auto represent_n = node_maps[i](n);
                    adj_lists[node_maps[i](parent(n, **ti))].push_back(represent_n);
                    for (j = 0; j < (index_t) ntrees; j++) {
                        if (i != j) {
                            auto ses_ij_n = ses(i, j)(n);
                            if (areas[j](ses_ij_n) != areas[i](n)) {
                                adj_lists[node_maps[j](ses_ij_n)].push_back(represent_n);
                            }
                        }

                    }
                }
            }

            /* ***************
            * Transitive reduction of the GOS
            */

            // perhaps one day, doesn't bring anything in terms of complexity

            /* ***************
            * Topological sort of the GOS
            */
            auto nnodes = adj_lists.size();
            array_1d<index_t> sorted_nodes = xt::empty<index_t>({nnodes});
            // marks: 0 = never seen, 1 = being visited (not finalized and sucessors on the stack), 2 = sorted
            array_1d<char> marks = xt::zeros<char>({nnodes});
            stack<index_t> s;

            index_t count = 0;
            s.push(rootn);
            while (!s.empty()) {
                auto n = s.top();
                if (marks(n) > 0) {
                    s.pop();
                    if (marks(n) == 1) {
                        sorted_nodes(count++) = n;
                        marks(n) = 2;
                    }
                } else {
                    marks(n) = 1;
                    for (auto o: adj_lists[n]) {
                        if (marks(o) != 2) {
                            s.push(o);
                        }
                    }
                }
            }

            /* ***************
            * Depth of the nodes of the GOS
            */
            array_1d<index_t> depth = xt::zeros<index_t>({nnodes});
            for (index_t i = nnodes - 1; i >= 0; i--) {
                index_t n = sorted_nodes[i];
                for (auto o: adj_lists[n]) {
                    depth(o) = (std::max)(depth(o), depth(n) + 1);
                }
            }

            /* ***************
            * Depth of the nodes of the GOS
            */
            return xt::eval(xt::view(depth, xt::range(0, nleaves)));
        }

        template<typename range_tree_t>
        auto tree_fusion_depth_map(const range_tree_t &range) {
            return tree_fusion_depth_map(range.begin(), range.end());
        }

    }

    /**
     * Depth map associated to the fusion of the given list of trees.
     *
     * The method is described in:
     *
     * > <NAME>. A Tree of shapes for multivariate images. PhD Thesis, Université Paris-Est, 2015.
     *
     * All trees must be defined over the same domain, i.e. have the same number of leaves.
     *
     * Given a set of trees (T_1, T_2, T_3,... T_n) composed of the nodes (N_1, N_2, N_3, ... N_n).
     * We define the fusion graph as the graph induced the inclusion relation on the union of all the tree nodes
     * \bigcup\{N_1, N_2, N_3, ... N_n\}.
     * The result is a directed acyclic graph with a single root (corresponding to the roots of the input trees).
     * The depth of a node in this graph is defined as the length of the longest path from the root this node.
     *
     * This function returns the depth of the leaves of this graph (which are the same as the leaves of the input trees).
     *
     * @tparam tree_iterator Iterator on tree pointers
     * @param first
     * @param last
     * @return an 1d array of size num_leaves(**first)
     */
    template<typename tree_iterator>
    auto tree_fusion_depth_map(const tree_iterator first, const tree_iterator last) {
        static_assert(std::is_same<typename tree_iterator::value_type, tree *>::value,
                      "Iterator value type must 'tree *'.");
        return tree_fusion_internal::tree_fusion_depth_map(first, last);
    }

    template<typename range_tree_t>
    auto tree_fusion_depth_map(const range_tree_t &range) {
        return tree_fusion_internal::tree_fusion_depth_map(range.begin(), range.end());
    }


}

<|start_filename|>test/cpp/accumulator/test_at_accumulator.cpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/
#include "../test_utils.hpp"
#include "higra/accumulator/at_accumulator.hpp"

using namespace hg;

namespace accumulator {

    TEST_CASE("test at_accumulator", "at_accumulator") {

        array_1d<index_t> indices{1, 1, -1, 2, 0};
        array_1d<int> weights{1, 2, 3, 4, 5};

        auto res = accumulate_at(indices, weights, accumulator_sum());
        array_nd<double> expected_res{5, 3, 4};
        REQUIRE((res == expected_res));

        array_2d<int> weights_vec{{1, 6},
                                  {2, 7},
                                  {3, 8},
                                  {4, 9},
                                  {5, 10}};
        auto res_vec = accumulate_at(indices, weights_vec, accumulator_sum());
        array_nd<double> expected_res_vec{
                {5, 10},
                {3, 13},
                {4, 9}};
        REQUIRE((res_vec == expected_res_vec));
    }
}

<|start_filename|>test/cpp/image/test_contour2d.cpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#include "../test_utils.hpp"
#include "higra/image/contour_2d.hpp"

namespace test_contour_2d {

    using namespace hg;
    using namespace std;

    auto contour_2_khalimsky(const hg::ugraph &graph, const std::array<index_t, 2> &shape, const contour_2d &contour) {
        std::array<index_t, 2> res_shape{shape[0] * 2 - 1, shape[1] * 2 - 1};
        array_2d<index_t> result = xt::zeros<index_t>(res_shape);
        embedding_grid_2d embedding(shape);
        index_t count = 0;

        auto edge_to_k = [&graph, &embedding](index_t edge_index) {
            auto &e = edge_from_index(edge_index, graph);
            auto s = source(e, graph);
            auto t = target(e, graph);

            auto ti = embedding.lin2grid(t);
            auto si = embedding.lin2grid(s);
            return xt::eval(ti + si);
        };

        for (const auto &polyline: contour) {

            for (const auto &segment: polyline) {
                count++;
                for (const auto &e: segment) {
                    result[edge_to_k(e.first)] = count;
                }
                result[edge_to_k(segment.first().first)] = invalid_index * count;
                result[edge_to_k(segment.last().first)] = invalid_index * count;
            }
        }
        /*
        if(interp){
            embedding_grid_2d res_embedding(res_shape);
            auto adj4 = get_4_adjacency_implicit_graph(res_embedding);
            auto flat_res = xt::flatten(result);

            for (long y = 1; y < shape[0] * 2 - 2; y += 2) {
                for (long x = 1; x < shape[1] * 2 - 2; x += 2) {
                    auto v = res_embedding.grid2lin({y, x});
                    index_t max_v = std::numeric_limits<index_t>::lowest();
                    for (auto av: adjacent_vertex_iterator(v, adj4)) {
                        max_v = std::max(max_v, flat_res(av));
                    }
                    result(y, x) = max_v;
                }
            }
        }
        */
        return result;
    }

    TEST_CASE("contour 2d empty", "[contour_2d]") {

        auto g = get_4_adjacency_graph({4, 5});

        xt::xarray<int> data{
                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
        };

        auto r = fit_contour_2d(g, {4, 5}, data);

        REQUIRE(r.size() == 0);
    }

    TEST_CASE("contour 2d simple", "[contour_2d]") {

        std::array<index_t, 2> shape{4, 5};
        auto g = get_4_adjacency_graph(shape);

        xt::xarray<int> data{
                0, 0, 1, 0, 2, 0, 3, 0, 0, 0, 0, 1, 0, 2, 0, 3, 0, 0, 0, 1, 1, 1, 2, 0, 3, 0, 0, 0, 0, 2,
                3
        };


        xt::xarray<int> ref{
                {0, 0, 0, 9, 0, 7, 0, 8, 0},
                {0, 0, 0, 0, 0, 0, 0, 0, 0},
                {0, 0, 0, 1, 0, 2, 0, 3, 0},
                {0, 0, 0, 0, 0, 0, 0, 0, 0},
                {0, 0, 0, 1, 0, 2, 0, 3, 0},
                {9, 0, 1, 0, 0, 0, 0, 0, 0},
                {0, 0, 0, 0, 0, 7, 0, 8, 0}
        };

        auto contours = fit_contour_2d(g, shape, data);
        auto contours_khalimsky = contour_2_khalimsky(g, shape, contours);

        REQUIRE(is_in_bijection(ref, contours_khalimsky));
    }

    TEST_CASE("contour 2d no intersection", "[contour_2d]") {

        std::array<index_t, 2> shape{5, 5};
        auto g = get_4_adjacency_graph(shape);

        array_1d<index_t> data({40}, 0);
        data(14) = 1;
        data(20) = 1;
        data(22) = 1;
        data(23) = 1;

        array_2d<index_t> ref{
                {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, 1, 0, 0, 0, 0},
                {0, 0, 0, 1, 0, 2, 0, 0, 0},
                {0, 0, 0, 0, 2, 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}
        };

        auto contours = fit_contour_2d(g, shape, data);
        auto contours_khalimsky = contour_2_khalimsky(g, shape, contours);

        REQUIRE(is_in_bijection(ref, contours_khalimsky));
    }

    TEST_CASE("contour 2d more complex", "[contour_2d]") {

        std::array<index_t, 2> shape{4, 5};
        auto g = get_4_adjacency_graph(shape);

        xt::xarray<int> data{
                0, 0, 1, 0, 2, 0, 3, 0, 0, 0, 0, 1, 0, 2, 4, 3, 0, 0, 0, 1, 1, 1, 2, 0, 3, 0, 0, 0, 1, 2,
                3
        };

        xt::xarray<int> ref{
                {0, 0, 0, 1, 0, 6, 0, 8, 0},
                {0, 0, 0, 0, 0, 0, 0, 0, 0},
                {0, 0, 0, 1, 0, 6, 0, 9, 0},
                {0, 0, 0, 0, 5, 0, 0, 0, 0},
                {0, 0, 0, 2, 0, 7, 0, 9, 0},
                {3, 0, 3, 0, 0, 0, 0, 0, 0},
                {0, 0, 0, 4, 0, 7, 0, 8, 0}
        };

        auto contours = fit_contour_2d(g, shape, data);
        auto contours_khalimsky = contour_2_khalimsky(g, shape, contours);

        REQUIRE(is_in_bijection(ref, contours_khalimsky));
    }

    TEST_CASE("contour 2d subdivide nothing", "[contour_2d]") {

        std::array<index_t, 2> shape{4, 5};
        auto g = get_4_adjacency_graph(shape);

        xt::xarray<int> data{
                0, 0, 1, 0, 2, 0, 3, 0, 0, 0, 0, 1, 0, 2, 4, 3, 0, 0, 0, 1, 1, 1, 2, 0, 3, 0, 0, 0, 1, 2,
                3
        };


        xt::xarray<int> ref{
                {0, 0, 0, 1, 0, 6, 0, 8, 0},
                {0, 0, 0, 0, 0, 0, 0, 0, 0},
                {0, 0, 0, 1, 0, 6, 0, 9, 0},
                {0, 0, 0, 0, 5, 0, 0, 0, 0},
                {0, 0, 0, 2, 0, 7, 0, 9, 0},
                {3, 0, 3, 0, 0, 0, 0, 0, 0},
                {0, 0, 0, 4, 0, 7, 0, 8, 0}
        };

        auto contours = fit_contour_2d(g, shape, data);
        contours.subdivide();
        auto contours_khalimsky = contour_2_khalimsky(g, shape, contours);

        REQUIRE(is_in_bijection(ref, contours_khalimsky));
    }

    TEST_CASE("contour 2d subdivide simple", "[contour_2d]") {

        std::array<index_t, 2> shape{4, 5};
        auto g = get_4_adjacency_graph(shape);

        xt::xarray<int> data{
                0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
                0
        };

        xt::xarray<int> ref{
                {0, 0, 0, 2, 0, 0, 0, 0, 0},
                {0, 0, 0, 0, 0, 0, 0, 0, 0},
                {0, 0, 0, 1, 0, 0, 0, 0, 0},
                {0, 0, 0, 0, 0, 0, 0, 0, 0},
                {0, 0, 0, 3, 0, 0, 0, 0, 0},
                {4, 0, 4, 0, 0, 0, 0, 0, 0},
                {0, 0, 0, 0, 0, 0, 0, 0, 0}
        };

        auto contours = fit_contour_2d(g, shape, data);
        contours.subdivide(0.000001, false, 0);
        auto contours_khalimsky = contour_2_khalimsky(g, shape, contours);

        REQUIRE(is_in_bijection(ref, contours_khalimsky));
    }

    TEST_CASE("contour 2d subdivide simple 2", "[contour_2d]") {

        std::array<index_t, 2> shape{4, 5};
        auto g = get_4_adjacency_graph(shape);

        xt::xarray<int> data{
                0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                0
        };

        xt::xarray<int> ref{
                {0, 0, 0, 0, 0, 0, 0, 0, 0},
                {1, 0, 0, 0, 5, 0, 0, 0, 7},
                {0, 2, 0, 4, 0, 6, 0, 8, 0},
                {0, 0, 3, 0, 0, 0, 7, 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}
        };

        auto contours = fit_contour_2d(g, shape, data);
        contours.subdivide(0.000001, false, 0);
        auto contours_khalimsky = contour_2_khalimsky(g, shape, contours);

        REQUIRE(is_in_bijection(ref, contours_khalimsky));
    }

    TEST_CASE("test rag_2d_vertex_perimeter_and_edge_length simple", "[contour_2d]") {

        std::array<index_t, 2> shape{3, 2};
        auto g = get_4_adjacency_graph(shape);

        xt::xarray<int> vertex_labels{
                1, 2,
                3, 3,
                4, 4
        };

        auto rag = make_region_adjacency_graph_from_labelisation(g, vertex_labels);

        auto res = rag_2d_vertex_perimeter_and_edge_length(rag, shape, g);
        auto &vertex_perimeter = res.first;
        auto &edge_length = res.second;

        array_1d<double> ref_perimeter{4, 4, 6, 6};
        array_1d<double> ref_length{1, 1, 1, 2};

        REQUIRE((vertex_perimeter == ref_perimeter));
        REQUIRE((edge_length == ref_length));
    }
}

<|start_filename|>lib/include/xtensor-python/xtensor_type_caster_base.hpp<|end_filename|>
/***************************************************************************
* Copyright (c) <NAME>, <NAME> and <NAME>          *
* Copyright (c) QuantStack                                                 *
*                                                                          *
* Distributed under the terms of the BSD 3-Clause License.                 *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#ifndef XTENSOR_TYPE_CASTER_HPP
#define XTENSOR_TYPE_CASTER_HPP

#include <cstddef>
#include <algorithm>
#include <vector>

#include "xtensor/xtensor.hpp"

#include <pybind11/numpy.h>
#include <pybind11/pybind11.h>

namespace pybind11
{
    namespace detail
    {
        // Casts an xtensor (or xarray) type to numpy array.If given a base,
        // the numpy array references the src data, otherwise it'll make a copy.
        // The writeable attributes lets you specify writeable flag for the array.
        template <typename Type>
        handle xtensor_array_cast(const Type& src, handle base = handle(), bool writeable = true)
        {
            // TODO: make use of xt::pyarray instead of array.
            std::vector<std::size_t> python_strides(src.strides().size());
            std::transform(src.strides().begin(), src.strides().end(),
                           python_strides.begin(), [](auto v) {
                return sizeof(typename Type::value_type) * v;
            });

            std::vector<std::size_t> python_shape(src.shape().size());
            std::copy(src.shape().begin(), src.shape().end(), python_shape.begin());

            array a(python_shape, python_strides, src.begin(), base);

            if (!writeable)
            {
                array_proxy(a.ptr())->flags &= ~detail::npy_api::NPY_ARRAY_WRITEABLE_;
            }

            return a.release();
        }

        // Takes an lvalue ref to some xtensor (or xarray) type and a (python) base object, creating a numpy array that
        // reference the xtensor object's data with `base` as the python-registered base class (if omitted,
        // the base will be set to None, and lifetime management is up to the caller).  The numpy array is
        // non-writeable if the given type is const.
        template <typename Type, typename CType>
        handle xtensor_ref_array(CType& src, handle parent = none())
        {
            return xtensor_array_cast<Type>(src, parent, !std::is_const<CType>::value);
        }

        // Takes a pointer to xtensor (or xarray), builds a capsule around it, then returns a numpy
        // array that references the encapsulated data with a python-side reference to the capsule to tie
        // its destruction to that of any dependent python objects.  Const-ness is determined by whether or
        // not the CType of the pointer given is const.
        template <typename Type, typename CType>
        handle xtensor_encapsulate(CType* src)
        {
            capsule base(src, [](void* o) { delete static_cast<CType*>(o); });
            return xtensor_ref_array<Type>(*src, base);
        }

        // Base class of type_caster for xtensor and xarray
        template <class Type>
        struct xtensor_type_caster_base
        {
            bool load(handle src, bool)
            {
                return false;
            }

        private:

            // Cast implementation
            template <typename CType>
            static handle cast_impl(CType* src, return_value_policy policy, handle parent)
            {
                switch (policy)
                {
                case return_value_policy::take_ownership:
                case return_value_policy::automatic:
                    return xtensor_encapsulate<Type>(src);
                case return_value_policy::move:
                    return xtensor_encapsulate<Type>(new CType(std::move(*src)));
                case return_value_policy::copy:
                    return xtensor_array_cast<Type>(*src);
                case return_value_policy::reference:
                case return_value_policy::automatic_reference:
                    return xtensor_ref_array<Type>(*src);
                case return_value_policy::reference_internal:
                    return xtensor_ref_array<Type>(*src, parent);
                default:
                    throw cast_error("unhandled return_value_policy: should not happen!");
                };
            }

        public:

            // Normal returned non-reference, non-const value:
            static handle cast(Type&& src, return_value_policy /* policy */, handle parent)
            {
                return cast_impl(&src, return_value_policy::move, parent);
            }

            // If you return a non-reference const, we mark the numpy array readonly:
            static handle cast(const Type&& src, return_value_policy /* policy */, handle parent)
            {
                return cast_impl(&src, return_value_policy::move, parent);
            }

            // lvalue reference return; default (automatic) becomes copy
            static handle cast(Type& src, return_value_policy policy, handle parent)
            {
                if (policy == return_value_policy::automatic || policy == return_value_policy::automatic_reference)
                {
                    policy = return_value_policy::copy;
                }

                return cast_impl(&src, policy, parent);
            }

            // const lvalue reference return; default (automatic) becomes copy
            static handle cast(const Type& src, return_value_policy policy, handle parent)
            {
                if (policy == return_value_policy::automatic || policy == return_value_policy::automatic_reference)
                {
                    policy = return_value_policy::copy;
                }

                return cast(&src, policy, parent);
            }

            // non-const pointer return
            static handle cast(Type* src, return_value_policy policy, handle parent)
            {
                return cast_impl(src, policy, parent);
            }

            // const pointer return
            static handle cast(const Type* src, return_value_policy policy, handle parent)
            {
                return cast_impl(src, policy, parent);
            }

#ifdef PYBIND11_DESCR // The macro is removed from pybind11 since 2.3
            static PYBIND11_DESCR name()
            {
                return _("xt::xtensor");
            }
#else
            static constexpr auto name = _("xt::xtensor");
#endif

            template <typename T>
            using cast_op_type = cast_op_type<T>;
        };
    }
}

#endif


<|start_filename|>include/higra/structure/array.hpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#pragma once

#include "xtensor/xarray.hpp"
#include "xtensor/xtensor.hpp"
#include "xtensor/xgenerator.hpp"
#include "xtensor/xeval.hpp"

#include "point.hpp"

namespace hg {

    template<typename value_t>
    using array_1d = xt::xtensor<value_t, 1>;

    template<typename value_t>
    using array_2d = xt::xtensor<value_t, 2>;

    template<typename value_t>
    using array_3d = xt::xtensor<value_t, 3>;

    template<typename value_t>
    using array_3d = xt::xtensor<value_t, 3>;

    template<typename value_t>
    using array_4d = xt::xtensor<value_t, 4>;

    template<typename value_t>
    using array_nd = xt::xarray<value_t>;
}


<|start_filename|>lib/include/tbb-ssort/parallel_stable_sort.h<|end_filename|>
/*
  Copyright (C) 2014 Intel Corporation
  All rights reserved.

  Redistribution and use in source and binary forms, with or without
  modification, are permitted provided that the following conditions
  are met:

  * Redistributions of source code must retain the above copyright
    notice, this list of conditions and the following disclaimer.
  * Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions and the following disclaimer in
    the documentation and/or other materials provided with the
    distribution.
  * Neither the name of Intel Corporation nor the names of its
    contributors may be used to endorse or promote products derived
    from this software without specific prior written permission.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
  AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
  WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  POSSIBILITY OF SUCH DAMAGE.
*/
#include <iterator>
#include <algorithm>
#include <tbb/task.h>

#include "pss_common.h"

namespace pss {

    namespace internal {

        template<typename RandomAccessIterator1, typename RandomAccessIterator2, typename RandomAccessIterator3, typename Compare>
        class merge_task : public tbb::task {
            tbb::task *execute();

            RandomAccessIterator1 xs, xe;
            RandomAccessIterator2 ys, ye;
            RandomAccessIterator3 zs;
            Compare comp;
            bool destroy;
        public:
            merge_task(RandomAccessIterator1 xs_, RandomAccessIterator1 xe_, RandomAccessIterator2 ys_,
                       RandomAccessIterator2 ye_, RandomAccessIterator3 zs_, bool destroy_, Compare comp_) :
                    xs(xs_), xe(xe_), ys(ys_), ye(ye_), zs(zs_), comp(comp_), destroy(destroy_) {}
        };

        template<typename RandomAccessIterator1, typename RandomAccessIterator2, typename RandomAccessIterator3, typename Compare>
        tbb::task *merge_task<RandomAccessIterator1, RandomAccessIterator2, RandomAccessIterator3, Compare>::execute() {
            const size_t MERGE_CUT_OFF = 2000;
            auto n = (xe - xs) + (ye - ys);
            if ((size_t) n <= MERGE_CUT_OFF) {
                serial_move_merge(xs, xe, ys, ye, zs, comp);
                if (destroy) {
                    serial_destroy(xs, xe);
                    serial_destroy(ys, ye);
                }
                return NULL;
            } else {
                RandomAccessIterator1 xm;
                RandomAccessIterator2 ym;
                if (xe - xs < ye - ys) {
                    ym = ys + (ye - ys) / 2;
                    xm = std::upper_bound(xs, xe, *ym, comp);
                } else {
                    xm = xs + (xe - xs) / 2;
                    ym = std::lower_bound(ys, ye, *xm, comp);
                }
                RandomAccessIterator3 zm = zs + ((xm - xs) + (ym - ys));
                tbb::task *right = new(allocate_additional_child_of(*parent())) merge_task(xm, xe, ym, ye, zm, destroy,
                                                                                           comp);
                spawn(*right);
                recycle_as_continuation();
                xe = xm;
                ye = ym;
                return this;
            }
        }

        template<typename RandomAccessIterator1, typename RandomAccessIterator2, typename Compare>
        class stable_sort_task : public tbb::task {
            tbb::task *execute();

            RandomAccessIterator1 xs, xe;
            RandomAccessIterator2 zs;
            Compare comp;
            signed char inplace;
        public:
            stable_sort_task(RandomAccessIterator1 xs_, RandomAccessIterator1 xe_, RandomAccessIterator2 zs_,
                             int inplace_, Compare comp_) :
                    xs(xs_), xe(xe_), zs(zs_), comp(comp_), inplace(inplace_) {}
        };

        template<typename RandomAccessIterator1, typename RandomAccessIterator2, typename Compare>
        tbb::task *stable_sort_task<RandomAccessIterator1, RandomAccessIterator2, Compare>::execute() {
            const size_t SORT_CUT_OFF = 500;
            if ((size_t) (xe - xs) <= SORT_CUT_OFF) {
                stable_sort_base_case(xs, xe, zs, inplace, comp);
                return NULL;
            } else {
                RandomAccessIterator1 xm = xs + (xe - xs) / 2;
                RandomAccessIterator2 zm = zs + (xm - xs);
                RandomAccessIterator2 ze = zs + (xe - xs);
                task *m;
                if (inplace)
                    m = new(allocate_continuation()) merge_task<RandomAccessIterator2, RandomAccessIterator2, RandomAccessIterator1, Compare>(
                            zs, zm, zm, ze, xs, inplace == 2, comp);
                else
                    m = new(allocate_continuation()) merge_task<RandomAccessIterator1, RandomAccessIterator1, RandomAccessIterator2, Compare>(
                            xs, xm, xm, xe, zs, false, comp);
                m->set_ref_count(2);
                task *right = new(m->allocate_child()) stable_sort_task(xm, xe, zm, !inplace, comp);
                spawn(*right);
                recycle_as_child_of(*m);
                xe = xm;
                inplace = !inplace;
                return this;
            }
        }

    } // namespace internal

    template<typename RandomAccessIterator, typename Compare>
    void parallel_stable_sort(RandomAccessIterator xs, RandomAccessIterator xe, Compare comp) {
        typedef typename std::iterator_traits<RandomAccessIterator>::value_type T;
        if (internal::raw_buffer z = internal::raw_buffer(sizeof(T) * (xe - xs))) {
            using tbb::task;
            typedef typename std::iterator_traits<RandomAccessIterator>::value_type T;
            internal::raw_buffer buf(sizeof(T) * (xe - xs));
            task::spawn_root_and_wait(
                    *new(task::allocate_root()) internal::stable_sort_task<RandomAccessIterator, T *, Compare>(
                            xs,
                            xe,
                            (T *) buf.get(),
                            2,
                            comp));
        } else
            // Not enough memory available - fall back on serial sort
            std::stable_sort(xs, xe, comp);
    }

    template<typename RandomAccessIterator>
    void parallel_stable_sort(RandomAccessIterator xs, RandomAccessIterator xe) {
        typedef typename std::iterator_traits<RandomAccessIterator>::value_type T;
        parallel_stable_sort(xs, xe, std::less<T>());
    }

} // namespace pss


<|start_filename|>include/higra/algo/graph_weights.hpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#pragma once

#include "../graph.hpp"
#include "xtensor/xexpression.hpp"
#include "../structure/details/light_axis_view.hpp"

namespace hg {

    /**
     * Predefined edge-weighting functions (see weight_graph function)
     */
    enum class weight_functions {
        mean,
        min,
        max,
        L0,
        L1,
        L2,
        L_infinity,
        L2_squared,
        source,
        target
    };

    /**
     * Compute edge-weights of a graph based on a weighting function.
     *
     * A weighting function is a function that associates a weights to a pair of vertices.
     *
     * @tparam graph_t
     * @tparam result_value_t
     * @param graph
     * @param fun
     * @return an array of weights
     */
    template<typename result_value_t=double, typename graph_t>
    auto weight_graph(const graph_t &graph, const std::function<result_value_t(
            typename graph_t::vertex_descriptor,
            typename graph_t::vertex_descriptor)> &fun) {
        auto result = array_1d<result_value_t>::from_shape({num_edges(graph)});

        parfor(0, num_edges(graph), [&graph, &fun, &result](index_t i) {
            auto e = edge_from_index(i, graph);
            result(e) = fun(source(e, graph), target(e, graph));
        });
        return result;
    };

    /**
     * Compute edge-weights of a graph based from the vertex-weights and a predefined weighting function (see weight_functions enum).
     *
     * Each edge is weighted with a combination of its extremities weights.
     *
     * @tparam result_value_t The value type of the result
     * @tparam promoted_type The value type used for internal computation
     * @tparam graph_t
     * @tparam T
     * @param graph
     * @param xvertex_weights
     * @param weight
     * @return
     */
    template<typename result_value_t = double,
            typename promoted_type = double,
            typename graph_t,
            typename T>
    auto weight_graph(const graph_t &graph, const xt::xexpression<T> &xvertex_weights, weight_functions weight) {
        HG_TRACE();
        using vertex_t = typename graph_t::vertex_descriptor;
        const auto &vertex_weights = xvertex_weights.derived_cast();
        hg_assert_vertex_weights(graph, vertex_weights);

        auto num_v = num_vertices(graph);
        switch (weight) {
            case weight_functions::mean: {
                hg_assert_1d_array(vertex_weights);
                std::function<result_value_t(vertex_t, vertex_t)> fun = [&vertex_weights](vertex_t i,
                                                                                          vertex_t j)
                        -> result_value_t {
                    return static_cast<result_value_t>(
                            (static_cast<promoted_type>(vertex_weights(i)) +
                             static_cast<promoted_type>(vertex_weights(j))) /
                            static_cast<promoted_type>(2.0));
                };
                return weight_graph(graph, fun);
            }
            case weight_functions::min: {
                hg_assert_1d_array(vertex_weights);
                std::function<result_value_t(vertex_t, vertex_t)> fun = [&vertex_weights](
                        vertex_t i, vertex_t j) -> result_value_t {
                    return static_cast<result_value_t>((std::min)(vertex_weights(i), vertex_weights(j)));
                };
                return weight_graph(graph, fun);
            }
            case weight_functions::max: {
                hg_assert_1d_array(vertex_weights);
                std::function<result_value_t(vertex_t, vertex_t)> fun = [&vertex_weights](vertex_t i,
                                                                                          vertex_t j) -> result_value_t {
                    return static_cast<result_value_t>((std::max)(vertex_weights(i), vertex_weights(j)));
                };
                return weight_graph(graph, fun);
            }
            case weight_functions::L0: {
                if (vertex_weights.dimension() > 1) {
                    index_t dim = vertex_weights.size() / num_v;
                    auto view = xt::reshape_view(vertex_weights, {num_v, (size_t)dim});
                    std::function<result_value_t(vertex_t, vertex_t)> fun = [&view, dim](
                            vertex_t i,
                            vertex_t j) -> result_value_t {
                        for (index_t k = 0; k < dim; k++) {
                            if (view(i, k) != view(j, k))
                                return 1;
                        }
                        return 0;
                    };
                    return weight_graph(graph, fun);
                } else {
                    std::function<result_value_t(vertex_t, vertex_t)> fun = [&vertex_weights](vertex_t i,
                                                                                              vertex_t j) -> result_value_t {
                        return (vertex_weights(i) == vertex_weights(j)) ? 0 : 1;
                    };
                    return weight_graph(graph, fun);
                }
            }
            case weight_functions::L1: {
                if (vertex_weights.dimension() > 1) {
                    index_t dim = vertex_weights.size() / num_v;
                    auto view = xt::reshape_view(vertex_weights, {num_v, (size_t)dim});
                    std::function<result_value_t(vertex_t, vertex_t)> fun = [&view, dim](
                            vertex_t i,
                            vertex_t j) -> result_value_t {
                        promoted_type res = 0;
                        for (index_t k = 0; k < dim; k++) {
                            res += std::abs(static_cast<promoted_type>(view(i, k)) - static_cast<promoted_type>(view(j,k)));
                        }
                        return static_cast<result_value_t>(res);
                    };
                    return weight_graph(graph, fun);
                } else {
                    std::function<result_value_t(vertex_t, vertex_t)> fun = [&vertex_weights](vertex_t i,
                                                                                              vertex_t j) -> result_value_t {
                        return static_cast<result_value_t>(std::abs(static_cast<promoted_type>(vertex_weights(i)) -
                                                                    static_cast<promoted_type>(vertex_weights(j))));
                    };
                    return weight_graph(graph, fun);
                }
            }
            case weight_functions::L2: {
                if (vertex_weights.dimension() > 1) {
                    index_t dim = vertex_weights.size() / num_v;
                    auto view = xt::reshape_view(vertex_weights, {num_v, (size_t)dim});
                    std::function<result_value_t(vertex_t, vertex_t)> fun = [&view, dim](
                            vertex_t i,
                            vertex_t j) -> result_value_t {
                        promoted_type res = 0;
                        for (index_t k = 0; k < dim; k++) {
                            auto tmp = static_cast<promoted_type>(view(i, k)) - static_cast<promoted_type>(view(j, k));
                            res += tmp * tmp;
                        }
                        return static_cast<result_value_t>(std::sqrt(res));
                    };
                    return weight_graph(graph, fun);
                } else {
                    std::function<result_value_t(vertex_t, vertex_t)> fun = [&vertex_weights](vertex_t i,
                                                                                              vertex_t j) -> result_value_t {
                        auto v1 = vertex_weights(i);
                        auto v2 = vertex_weights(j);
                        auto tmp = static_cast<promoted_type>(v1) - static_cast<promoted_type>(v2);
                        return static_cast<result_value_t>(std::sqrt(tmp * tmp));
                    };
                    return weight_graph(graph, fun);
                }
            }
            case weight_functions::L_infinity: {
                if (vertex_weights.dimension() > 1) {
                    index_t dim = vertex_weights.size() / num_v;
                    auto view = xt::reshape_view(vertex_weights, {num_v, (size_t)dim});
                    std::function<result_value_t(vertex_t, vertex_t)> fun = [&view, dim](
                            vertex_t i,
                            vertex_t j) -> result_value_t {
                        promoted_type res = -1;
                        for (index_t k = 0; k < dim; k++) {
                            res = (std::max)(res, std::abs(
                                    static_cast<promoted_type>(view(i, k)) - static_cast<promoted_type>(view(j, k))));
                        }
                        return static_cast<result_value_t>(res);
                    };
                    return weight_graph(graph, fun);
                } else {
                    std::function<result_value_t(vertex_t, vertex_t)> fun = [&vertex_weights](vertex_t i,
                                                                                              vertex_t j) -> result_value_t {
                        return static_cast<result_value_t>(std::abs(static_cast<promoted_type>(vertex_weights(i)) -
                                                                    static_cast<promoted_type>(vertex_weights(j))));
                    };
                    return weight_graph(graph, fun);
                }
            }
            case weight_functions::L2_squared: {
                if (vertex_weights.dimension() > 1) {
                    index_t dim = vertex_weights.size() / num_v;
                    auto view = xt::reshape_view(vertex_weights, {num_v, (size_t)dim});
                    std::function<result_value_t(vertex_t, vertex_t)> fun = [&view, dim](
                            vertex_t i,
                            vertex_t j) -> result_value_t {
                        promoted_type res = 0;
                        for (index_t k = 0; k < dim; k++) {
                            auto tmp = static_cast<promoted_type>(view(i, k)) - static_cast<promoted_type>(view(j, k));
                            res += tmp * tmp;
                        }
                        return static_cast<result_value_t>(res);
                    };
                    return weight_graph(graph, fun);
                } else {
                    std::function<result_value_t(vertex_t, vertex_t)> fun = [&vertex_weights](
                            vertex_t i,
                            vertex_t j) -> result_value_t {
                        auto v1 = vertex_weights(i);
                        auto v2 = vertex_weights(j);
                        auto tmp = static_cast<promoted_type>(v1) - static_cast<promoted_type>(v2);
                        return static_cast<result_value_t>(tmp * tmp);
                    };
                    return weight_graph(graph, fun);
                }
            }
            case weight_functions::source: {
                hg_assert_1d_array(vertex_weights);
                std::function<result_value_t(vertex_t, vertex_t)> fun = [&vertex_weights](vertex_t i,
                                                                                          vertex_t j) -> result_value_t {
                    return static_cast<result_value_t>(vertex_weights(i));
                };
                return weight_graph(graph, fun);
            }
            case weight_functions::target: {
                hg_assert_1d_array(vertex_weights);
                std::function<result_value_t(vertex_t, vertex_t)> fun = [&vertex_weights](vertex_t i,
                                                                                          vertex_t j) -> result_value_t {
                    return static_cast<result_value_t>(vertex_weights(j));
                };
                return weight_graph(graph, fun);
            }
        }
        throw std::runtime_error("Unknown weight function.");
    };
}

<|start_filename|>include/higra/hierarchy/common.hpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#pragma once


namespace hg {

    /**
     * A simple structure to hold the result of hierarchy construction algorithms.
     *
     * See make_node_weighted_tree for construction
     *
     * @tparam tree_t
     * @tparam altitude_t
     */
    template <typename tree_t, typename altitude_t>
    struct node_weighted_tree{
        tree_t tree;
        altitude_t altitudes;
    };

    template <typename tree_t, typename altitude_t>
    decltype(auto) make_node_weighted_tree(tree_t && tree, altitude_t && node_altitude){
        return node_weighted_tree<tree_t, altitude_t>{std::forward<tree_t>(tree), std::forward<altitude_t>(node_altitude)};
    }

    /**
     * A simple structure to hold the result of a remapping operation on the nodes of a tree.
     * When algorithm transforms a tree into a new tree by removing or duplicating or reordering some of its nodes,
     * it is useful to know the relation between the nodes of the new tree and the nodes of the original one.
     *
     * For each node i of the new tree, node_map[i] gives the corresponding node in the original tree.
     *
     * @tparam tree_t
     * @tparam node_map_t
     */
    template<typename tree_t, typename node_map_t>
    struct remapped_tree {
        tree_t tree;
        node_map_t node_map;
    };

    template <typename tree_t, typename node_map_t>
    decltype(auto) make_remapped_tree(tree_t && tree, node_map_t && node_map){
        return remapped_tree<tree_t, node_map_t>{std::forward<tree_t>(tree), std::forward<node_map_t>(node_map)};
    }
}


<|start_filename|>test/cpp/hierarchy/test_binary_partition_tree.cpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#include "../test_utils.hpp"
#include "higra/hierarchy/binary_partition_tree.hpp"
#include "higra/image/graph_image.hpp"
#include "xtensor/xrandom.hpp"
#include "higra/hierarchy/hierarchy_core.hpp"
#include "higra/algo/tree.hpp"


using namespace hg;
using namespace std;

namespace binary_partition_tree {

    TEST_CASE("single linkage clustering simple", "[binary_partition_tree]") {
        auto graph = get_4_adjacency_graph({3, 3});
        array_1d<double> edge_weights({1, 9, 6, 7, 5, 8, 12, 4, 10, 11, 2, 3});
        auto res = binary_partition_tree_min_linkage(graph, edge_weights);
        auto &tree = res.tree;
        auto &levels = res.altitudes;

        array_1d<index_t> expected_parents({9, 9, 13, 15, 12, 12, 10, 10, 11, 14, 11, 16, 13, 14, 15, 16, 16});
        array_1d<double> expected_levels({0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 8, 10});

        REQUIRE((expected_parents == tree.parents()));
        REQUIRE((expected_levels == levels));
    }

    TEST_CASE("single linkage clustering", "[binary_partition_tree]") {
        long size = 100;
        auto graph = get_4_adjacency_graph({size, size});
        array_1d<double> edge_weights = xt::random::rand<double>({num_edges(graph)});
        auto res = binary_partition_tree_min_linkage(graph, edge_weights);
        auto &tree = res.tree;

        auto res2 = hg::bpt_canonical(graph, edge_weights);
        auto &tree2 = res2.tree;

        REQUIRE(hg::test_tree_isomorphism(tree, tree2));
    }

    TEST_CASE("complete linkage clustering multiple edges", "[binary_partition_tree]") {
        auto graph = get_4_adjacency_graph({3, 3});
        index_t ne = (index_t)num_edges(graph);
        for (index_t i = 0; i < ne; i++) {
            auto e = edge_from_index(i, graph);
            add_edge(source(e, graph), target(e, graph), graph);
        }
        array_1d<double> edge_weights({1, 8, 2, 10, 15, 3, 11, 4, 12, 13, 5, 6,
                                       1, 8, 2, 10, 15, 3, 11, 4, 12, 13, 5, 6});


        auto res = binary_partition_tree_complete_linkage(graph, edge_weights);
        auto &tree = res.tree;
        auto &levels = res.altitudes;

        array_1d<index_t> expected_parents({9, 9, 10, 11, 11, 12, 13, 13, 14, 10, 16, 12, 15, 14, 15, 16, 16});
        array_1d<double> expected_levels({0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 13, 15});

        REQUIRE((expected_parents == tree.parents()));
        REQUIRE((expected_levels == levels));
    }

    TEST_CASE("complete linkage clustering simple", "[binary_partition_tree]") {
        auto graph = get_4_adjacency_graph({3, 3});
        array_1d<double> edge_weights({1, 8, 2, 10, 15, 3, 11, 4, 12, 13, 5, 6});
        auto res = binary_partition_tree_complete_linkage(graph, edge_weights);
        auto &tree = res.tree;
        auto &levels = res.altitudes;

        array_1d<index_t> expected_parents({9, 9, 10, 11, 11, 12, 13, 13, 14, 10, 16, 12, 15, 14, 15, 16, 16});
        array_1d<double> expected_levels({0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 13, 15});

        REQUIRE((expected_parents == tree.parents()));
        REQUIRE((expected_levels == levels));
    }

    TEST_CASE("average linkage clustering simple", "[binary_partition_tree]") {
        auto graph = get_4_adjacency_graph({3, 3});
        array_1d<double> edge_weights({1, 7, 2, 10, 16, 3, 11, 4, 12, 14, 5, 6});
        array_1d<double> edge_weight_weights({7, 1, 7, 3, 2, 8, 2, 2, 2, 1, 5, 9});
        auto res = binary_partition_tree_average_linkage(graph, edge_weights, edge_weight_weights);
        auto &tree = res.tree;
        auto &levels = res.altitudes;

        array_1d<index_t> expected_parents({9, 9, 10, 11, 11, 12, 13, 13, 14, 10, 15, 12, 15, 14, 16, 16, 16});
        array_1d<double> expected_levels({0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 11.5, 12});

        REQUIRE((expected_parents == tree.parents()));
        REQUIRE((expected_levels == levels));
    }

    TEST_CASE("ward linkage clustering", "[binary_partition_tree]") {
        ugraph graph(5);

        array_1d<index_t> sources{0, 0, 0, 1, 2, 2, 3};
        array_1d<index_t> targets{1, 2, 3, 2, 3, 4, 4};
        add_edges(sources, targets, graph);
        array_2d<double> vertex_centroids{
                {0,  0},
                {1,  1},
                {1,  3},
                {-3, 4},
                {-1, 5}};

        array_1d<double> vertex_sizes{1,
                                      1,
                                      1,
                                      2,
                                      1};

        auto res = binary_partition_tree_ward_linkage(graph, vertex_centroids, vertex_sizes);

        auto &tree = res.tree;
        auto &altitudes = res.altitudes;

        array_1d<index_t> expected_parents{5, 5, 7, 6, 6, 7, 8, 8, 8};
        array_1d<double> expected_altitudes{0., 0., 0., 0., 0.,
                                            1., 3.333333, 4.333333, 27.};
        REQUIRE((expected_parents == parents(tree)));
        REQUIRE(xt::allclose(expected_altitudes, altitudes));
    }

    TEST_CASE("ward linkage non increasing", "[binary_partition_tree]") {
        ugraph graph(3);

        array_1d<index_t> sources{0, 1};
        array_1d<index_t> targets{2, 2};
        add_edges(sources, targets, graph);
        array_2d<double> vertex_centroids{{0},
                                          {1},
                                          {5}};

        array_1d<double> vertex_sizes{1, 1, 1};

        auto res = binary_partition_tree_ward_linkage(graph, vertex_centroids, vertex_sizes);

        auto &tree = res.tree;
        auto &altitudes = res.altitudes;

        array_1d<index_t> expected_parents{4, 3, 3, 4, 4};
        array_1d<double> expected_altitudes{0., 0., 0., 8, 8};
        REQUIRE((expected_parents == parents(tree)));
        REQUIRE(xt::allclose(expected_altitudes, altitudes));

        auto res2 = binary_partition_tree_ward_linkage(graph, vertex_centroids, vertex_sizes, "none");

        auto &tree2 = res2.tree;
        auto &altitudes2 = res2.altitudes;

        array_1d<index_t> expected_parents2{4, 3, 3, 4, 4};
        array_1d<double> expected_altitudes2{0., 0., 0., 8, 6};
        REQUIRE((expected_parents2 == parents(tree2)));
        REQUIRE(xt::allclose(expected_altitudes2, altitudes2));
    }

    TEST_CASE("average linkage clustering", "[binary_partition_tree]") {
        ugraph graph(10);
        array_1d<index_t> sources{0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 5, 5, 7, 7};
        array_1d<index_t> targets{3, 6, 4, 2, 5, 3, 6, 9, 7, 3, 8, 5, 9, 4, 6, 9, 7, 8, 6, 9, 8};
        add_edges(sources, targets, graph);
        array_1d<double> edge_weights{
                0.87580029, 0.60123697, 0.79924759, 0.74221387, 0.75418382, 0.66159356,
                1.31856839, 0.76080612, 1.08881471, 0.98557615, 0.61454158, 0.50913424,
                0.63556478, 0.64684775, 1.14865302, 0.81741018, 2.1591071, 0.60563004,
                2.06636665, 1.35617725, 0.83085949
        };
        array_1d<double> edge_weight_weights = xt::ones_like(edge_weights);
        auto res = binary_partition_tree_average_linkage(graph, edge_weights, edge_weight_weights);

        auto &tree = res.tree;
        auto &altitudes = res.altitudes;

        array_1d<index_t> expected_parents{11, 14, 10, 13, 15, 10, 11, 18, 12, 13, 12, 17, 16, 14, 15, 16, 17, 18, 18};
        array_1d<double> expected_altitudes{
                0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.509134, 0.601237, 0.610086,
                0.635565, 0.661594, 0.732129, 0.810695, 1.241727, 1.35874
        };
        REQUIRE((expected_parents == parents(tree)));
        REQUIRE(xt::allclose(expected_altitudes, altitudes));
    }

    TEST_CASE("exponential linkage clustering", "[binary_partition_tree]") {
        ugraph graph(5);
        array_1d<index_t> sources{0, 0, 1, 2, 2, 3};
        array_1d<index_t> targets{1, 2, 4, 3, 4, 4};
        add_edges(sources, targets, graph);

        array_1d<double> edge_weights{1, 3, 5, 2, 4, 6};
        array_1d<double> edge_weight_weights{2, 2, 1, 3, 3, 1};

        auto r = binary_partition_tree_exponential_linkage(graph, edge_weights, -1, edge_weight_weights);
        auto &tree = r.tree;
        auto &altitudes = r.altitudes;

        array_1d<index_t> ref_parents{5, 5, 6, 6, 8, 7, 7, 8, 8};
        array_1d<double> ref_altitudes{0., 0., 0., 0., 0., 1.,
                                       2., 3., 4.182275};
        
        REQUIRE(tree.parents() == ref_parents);
        // large tolerance due to numerical instabilities, especially with fastmath
        REQUIRE(xt::allclose(altitudes, ref_altitudes, 1e-5, 1e-1));
    }

    TEST_CASE("exponential linkage clustering equiv", "[binary_partition_tree]") {
        xt::random::seed(10);
        auto g = get_4_adjacency_graph({5, 5});
        array_1d<double> edge_weights = xt::random::rand<double>({num_edges(g)});
        array_1d<double> edge_weight_weights = xt::random::randint<int>({num_edges(g)}, 1, 10);

        auto r1 = binary_partition_tree_exponential_linkage(g, edge_weights, 0, edge_weight_weights);
        auto r1_ref = binary_partition_tree_average_linkage(g, edge_weights, edge_weight_weights);

        REQUIRE(r1.tree.parents() == r1_ref.tree.parents());
        REQUIRE(xt::allclose(r1.altitudes, r1_ref.altitudes));

        auto r2 = binary_partition_tree_exponential_linkage(g, edge_weights, 200, edge_weight_weights);
        auto r2_ref = binary_partition_tree_complete_linkage(g, edge_weights);

        REQUIRE(r2.tree.parents() == r2_ref.tree.parents());

        auto r3 = binary_partition_tree_exponential_linkage(g, edge_weights, -600, edge_weight_weights);
        auto r3_ref = binary_partition_tree_min_linkage(g, edge_weights);

        REQUIRE(r3.tree.parents() == r3_ref.tree.parents());
    }

}


<|start_filename|>higra/image/py_contour_2d.cpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#include "py_contour_2d.hpp"

#include "../py_common.hpp"
#include "xtensor-python/pyarray.hpp"
#include "xtensor-python/pytensor.hpp"
#include "higra/image/contour_2d.hpp"

template<typename T>
using pyarray = xt::pyarray<T>;

using namespace hg;
namespace py = pybind11;


void py_init_contour_segment_2d(pybind11::module &m) {

    using class_t = contour_segment_2d;
    auto c = py::class_<class_t>(m, "ContourSegment2d",
                                 "A contour segment is an ordered list of contour elements that "
                                 "are considered to form a straight line segment."
                                 "A contour element is a pair (edge_index, coordinates) where coordinates is a 2d point.");
    c.def("__iter__", [](class_t &c) {

              auto transform = [](const std::pair<index_t, point_2d_f> &e) {
                  return std::make_pair(e.first, std::make_pair(e.second[0], e.second[1]));
              };
              // ::TODO:: remove this stupid transorm iterator when xtensor python supports it
              using transform_it_t = transform_forward_iterator<
                      decltype(transform),
                      contour_2d_internal::contour_segment_2d_iterator<point_2d_f>,
                      std::pair<index_t, std::pair<double, double> > >;
              return py::make_iterator(transform_it_t(c.begin(), transform), transform_it_t(c.end(), transform));
              //return py::make_iterator(c.begin(), c.end());
          },
          "Iterator on the contour elements of the contour segment. ");
    c.def("__len__", &class_t::size, "Number of elements in the contour segment.");
    c.def("__getitem__", [](class_t &c, index_t i) {
        // ::TODO:: remove this stupid thing when xtensor python supports it
        const auto &e = c[i];
        return std::make_pair(e.first, std::make_pair(e.second[0], e.second[1]));
    });
    // ::TODO:: uncomment when xtensor python supports it
    //c.def("first", &class_t::first, "First element of the contour segment.");
    //c.def("last", &class_t::last, "First element of the contour segment.");
    c.def("norm", &class_t::norm, "Distance between first and last elements of the contour segment.");
    c.def("angle", &class_t::angle, "Angle of the vector (last()-first()) in [-pi; pi].");
    c.def("distance_to_point", &class_t::distance_to_point,
          "Distance between the given point and the line "
          "defined by the fist and last element of the contour segment.",
          py::arg("point"));
}

void py_init_polyline_contour_2d(pybind11::module &m) {

    using class_t = polyline_contour_2d;
    auto c = py::class_<class_t>(m, "PolylineContour2d",
                                 "A polyline contour is an ordered list of contour segment "
                                 "that form a continuous piece of frontier between 2 regions.\n"
                                 "Note that consecutive segments share their extremities: given two consecutive "
                                 "contour segments s1 and s2, "
                                 "the last element of s1 is equal to the first element of s2.");
    c.def("__iter__", [](class_t &c) {
              return py::make_iterator(c.begin(), c.end());
          },
          "Iterator on the contour segments of the polyline contour. ");
    c.def("__len__", &class_t::size, "Number of segments in the polyline contour.");
    c.def("__getitem__", [](class_t &c, index_t i) { return c[i]; });
    c.def("subdivide", &class_t::subdivide,
          "Subdivide the line such that the distance between the line\n"
          "joining the extremities of the contour segment and each of its elements is lower than the threshold (\n"
          "Ramer–Douglas–Peucker algorithm) or smaller than the minimal specified size\n"
          "\n"
          "The threshold is equal to\n"
          "- epsilon if relative_epsilon is false\n"
          "- epsilon times the distance between the segment extremities if relative_epsilon is true\n",
          py::arg("epsilon") = 0.1,
          py::arg("relative_epsilon") = true,
          py::arg("min_size") = 2
    );
}

void py_init_contour_2d_impl(pybind11::module &m) {

    using class_t = contour_2d;
    auto c = py::class_<class_t>(m, "Contour2d", "A contour2d is a set of polyline contours. "
                                                 "Each polyline contour represent a continuous piece of contour between two regions.");
    c.def("__iter__", [](class_t &c) {
              return py::make_iterator(c.begin(), c.end());
          },
          "Iterator on the polyline contours. ");
    c.def("__len__", &class_t::size, "Number of polyline contours.");
    c.def("__getitem__", [](class_t &c, index_t i) { return c[i]; });
    c.def("subdivide", &class_t::subdivide,
          "Subdivide each segment of the contour such that: "
          "For each segment, the distance between the line joining the extremities "
          "of the segment and each of its elements is lower than the given threshold ("
          "Ramer–Douglas–Peucker algorithm) or smaller than the minimal specified size"
          "\n"
          "The threshold is equal to\n"
          "- epsilon if relative_epsilon is false\n"
          "- epsilon times the distance between the segment extremities if relative_epsilon is true\n"
          "\n"
          "Implementation note: simply call subdivide on each polyline of the contour.",
          py::arg("epsilon") = 0.1,
          py::arg("relative_epsilon") = true,
          py::arg("min_size") = 2
    );
}


struct def_fit_contour_2d {
    template<typename T>
    static
    void def(pybind11::module &m, const char *doc) {
        m.def("fit_contour_2d", [](const hg::ugraph &graph,
                                   const std::vector<size_t> &shape,
                                   const pyarray<T> &weights) {
                  hg::embedding_grid_2d embedding(shape);
                  return hg::fit_contour_2d(graph, embedding, weights);
              },
              doc,
              py::arg("graph"),
              py::arg("shape"),
              py::arg("edgeWeights"));
    }
};

void py_init_contour_2d(pybind11::module &m) {
    xt::import_numpy();
    py_init_contour_segment_2d(m);
    py_init_polyline_contour_2d(m);
    py_init_contour_2d_impl(m);

    add_type_overloads<def_fit_contour_2d, HG_TEMPLATE_NUMERIC_TYPES>
            (m,
             "Construct a contour_2d object from a graph cut of a 2d image with a 4 adjacency (non zero edges are part of the cut).");

    m.def("_rag_2d_vertex_perimeter_and_edge_length",
          [](const ugraph &rag,
             const pyarray<index_t> &vertex_map,
             const pyarray<index_t> &edge_map,
             const std::vector<size_t> &shape,
             const ugraph &graph,
             double epsilon = 0.1,
             bool relative_epsilon = true,
             int min_size = 2) {
              hg::embedding_grid_2d embedding(shape);
              auto res = rag_2d_vertex_perimeter_and_edge_length(
                      rag,
                      vertex_map,
                      edge_map,
                      embedding,
                      graph,
                      epsilon,
                      relative_epsilon,
                      min_size
              );
              return pybind11::make_tuple(std::move(res.first), std::move(res.second));
          },
          "",
          py::arg("rag"),
          py::arg("vertex_map"),
          py::arg("edge_map"),
          py::arg("shape"),
          py::arg("graph"),
          py::arg("epsilon") = 0.1,
          py::arg("relative_epsilon") = true,
          py::arg("min_size") = 2
    );
}


<|start_filename|>include/higra/structure/point.hpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#pragma once

#include "../utils.hpp"
#include "xtensor/xfixed.hpp"

namespace hg {
    template<typename value_t, unsigned int dim>
    using point = xt::xtensor_fixed <value_t, xt::xshape<dim>>;

    using point_1d_f = point<double, 1>;
    using point_1d_i = point<index_t, 1>;

    using point_2d_f = point<double, 2>;
    using point_2d_i = point<index_t, 2>;

    using point_3d_f = point<double, 3>;
    using point_3d_i = point<index_t, 3>;

    using point_4d_f = point<double, 4>;
    using point_4d_i = point<index_t, 4>;
}

<|start_filename|>include/higra/io/pnm_io.hpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

/**
 * TODO: support max value > 255 (muti-byte components)
 * TODO: support P3 format (raw binary data)
 */
#pragma once

#include "../utils.hpp"
#include "../structure/array.hpp"
#include <iostream>
#include <sstream>
#include <string>
#include <fstream>

namespace hg {

    namespace pnm_io_internal {

        class tokenizer {

        public:
            inline
            tokenizer(std::istream &in) : m_in(in), m_stream("") {

            }

            inline
            std::string next_token() {
                if (m_stream.eof()) {
                    do {
                        if (m_in.eof())
                            return "";
                        std::getline(m_in, m_current_line);
                    } while (m_current_line.size() == 0 || m_current_line[0] == '#');
                    m_stream.clear();
                    m_stream.str(m_current_line);
                }
                std::string token;
                m_stream >> token;
                if (token == "") { // empty stream does not indicate eof on first read even if empty
                    return next_token();
                }
                return token;
            }

        private:
            std::istream &m_in;
            std::string m_current_line;
            std::stringstream m_stream;
        };

        inline
        array_nd<unsigned char> read_image_pnm(std::istream &in) {
            std::string tmp;
            bool ascii;
            bool binary;
            int max_value = 1;
            size_t bands;
            tokenizer tok(in);
            tmp = tok.next_token();
            hg_assert(tmp.size() == 2 && tmp[0] == 'P',
                      "Incorrect file format (magic number).");

            /*
             * Type	Magic number	Extension	Colors
             *               ASCII	Binary
             * Portable BitMap[1]	P1	P4	.pbm	0–1 (white & black)
             * Portable GrayMap[2]	P2	P5	.pgm	0–255 (gray scale)
             * Portable PixMap[3]	P3	P6	.ppm	0–255 (RGB)
             */
            switch (tmp[1]) {
                case '1':
                    ascii = true;
                    binary = true;
                    bands = 1;
                    break;
                case '2':
                    ascii = true;
                    binary = false;
                    bands = 1;
                    break;
                case '3':
                    ascii = true;
                    binary = false;
                    bands = 3;
                    break;
                case '4':
                    ascii = false;
                    binary = true;
                    bands = 1;
                    break;
                case '5':
                    ascii = false;
                    binary = false;
                    bands = 1;
                    break;
                case '6':
                    ascii = false;
                    binary = false;
                    bands = 3;
                    break;
                default:
                    hg_assert(false, "Unknown file format (magic number): " + tmp);
            }
            tmp = tok.next_token();
            hg_assert(tmp != "", "End of header reached too soon.");
            size_t width = std::stoi(tmp);

            tmp = tok.next_token();
            hg_assert(tmp != "", "End of header reached too soon.");
            size_t height = std::stoi(tmp);

            if (!binary) {
                tmp = tok.next_token();
                hg_assert(tmp != "", "End of header reached too soon.");
                max_value = std::stoi(tmp);
            }

            hg_assert(width > 0 && height > 0, "Incorrect dimensions.");

            hg_assert(max_value <= 255, "Multi-byte values not supported.");

            index_t num_values = width * height * bands;
            std::vector<size_t> shape{height, width};
            if (bands != 1) {
                shape.push_back(bands);
            }
            array_nd<unsigned char> result = xt::empty<unsigned char>(shape);
            auto r = xt::flatten(result);
            if (ascii) {
                for (index_t i = 0; i < num_values; i++) {
                    tmp = tok.next_token();
                    hg_assert(tmp != "", "End of data reached too soon.");
                    r(i) = std::stoi(tmp);
                }
            } else {
                //consume remaining comments
                while(in.peek() == '#'){
                    std::string tmp;
                    std::getline(in, tmp);
                }
                if (binary) {
                    hg_assert(false, "Binary raw data not supported.");
                } else {
                    in.read((char*)result.data(), num_values);
                }
            }
            return result;

        }

        template<typename T>
        void save_image_pnm(std::ostream &out, const xt::xexpression<T> &ximage) {
            static_assert(std::is_same<typename T::value_type, unsigned char>::value, "Can only save unsigned char values");
            auto &image = ximage.derived_cast();
            hg_assert(image.dimension() == 2 || image.dimension() == 3, "Array must have 2 or 3 dimensions.");
            hg_assert(image.dimension() == 2 || image.shape()[2] == 3,
                      "The size of the 3rd dimension must be 3 (RGB value).");

            // magic number
            if (image.dimension() == 3)
                out << "P6" << std::endl;
            else out << "P5" << std::endl;

            // size
            out << image.shape()[1] << " " << image.shape()[0] << std::endl;

            // max value
            out << "255" << std::endl;

            out.write((char*)image.data(), image.size());
        }

    }

    /**
     * Read the given pnm image (pbm, pgm or ppm formats).
     * Current the following pnm specification are supported
     * P1 binary ascii: supported
     * P2 byte ascii: supported (max value <= 255)
     * P3 RGB ascii: supported (max value <= 255)
     * P4 binary raw: NOT supported
     * P5 byte raw: supported (max value <= 255)
     * P6 RGB rax: supported (max value <= 255)
     *
     * @param filename Path to the file to read
     * @return an array with pixel data
     */
    inline
    array_nd<unsigned char> read_image_pnm(const char *filename) {
        std::ifstream s(filename);
        return pnm_io_internal::read_image_pnm(s);
    }

    /**
     * Save an array as a pnm file (pgm or ppm).
     * The array value_type MUST be unsigned char.
     * If the array has 2 dimensions it is saved as a pgm raw file (format P5).
     * If the array has 3 dimensions, the size of the third dimension must be 3
     * and it is saved as a ppm raw file (format P6).
     *
     * If the provided filename already exists, it will be overwritten !
     *
     * @tparam T xexpression derived type for ximage
     * @param filename Path to the file to write.
     * @param ximage The array containing the data to save.
     */
    template<typename T>
    void save_image_pnm(const char *filename, const xt::xexpression<T> &ximage) {
        std::ofstream s(filename);
        return pnm_io_internal::save_image_pnm(s, ximage);
    }
}

<|start_filename|>test/cpp/io/test_pnm_io.cpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#include "../test_utils.hpp"
#include "higra/io/pnm_io.hpp"

namespace pnm_io {

    using namespace hg;
    using namespace std;

    TEST_CASE("tokenizer", "[pnm_io]") {
        string test = "#comment\n"
                      "2 abc\n"
                      "####\n"
                      "3\n"
                      "jkl\n"
                      "\n";
        std::stringstream in(test);
        pnm_io_internal::tokenizer tok(in);

        string ref[]{"2", "abc", "3", "jkl"};
        string s;
        int i = 0;
        while ((s = tok.next_token()) != "") {
            REQUIRE(ref[i++] == s);
        }
        REQUIRE(i == 4);
    }
    /*
    * Type	Magic number	Extension	Colors
    *               ASCII	Binary
    * Portable BitMap[1]	P1	P4	.pbm	0–1 (white & black)
    * Portable GrayMap[2]	P2	P5	.pgm	0–255 (gray scale)
    * Portable PixMap[3]	P3	P6	.ppm	0–255 (RGB)
    */
    TEST_CASE("read PNM type P1", "[pnm_io]") {
        string test = "P1\n"
                      "# w h\n"
                      "5 3\n"
                      "# data\n"
                      "0 1 1 0 1\n"
                      "1 1 0 1 1\n"
                      "1 0 1 1 1";
        std::stringstream in(test);
        auto res = pnm_io_internal::read_image_pnm(in);

        array_nd<unsigned char> ref = {
                {0, 1, 1, 0, 1},
                {1, 1, 0, 1, 1},
                {1, 0, 1, 1, 1}
        };
        REQUIRE((res == ref));
    }

    TEST_CASE("read PNM type P2", "[pnm_io]") {
        string test = "P2\n"
                      "# w h\n"
                      "5 3 255\n"
                      "# data\n"
                      "0 10 1 0 1\n"
                      "1 1 0 255 1\n"
                      "1 0 1 1 1";
        std::stringstream in(test);
        auto res = pnm_io_internal::read_image_pnm(in);

        array_nd<unsigned char> ref = {
                {0, 10, 1, 0,   1},
                {1, 1,  0, 255, 1},
                {1, 0,  1, 1,   1}
        };
        REQUIRE((res == ref));
    }

    TEST_CASE("read PNM type P3", "[pnm_io]") {
        string test = "P3\n"
                      "# w h\n"
                      "2 3 255\n"
                      "# data\n"
                      "0 10 1      0 1 25\n"
                      "1  1 0    255 1 12\n"
                      "1  0 1     34 1  1";
        std::stringstream in(test);
        auto res = pnm_io_internal::read_image_pnm(in);

        array_nd<unsigned char> ref = {
                {{0, 10, 1}, {0,   1, 25}},
                {{1, 1,  0}, {255, 1, 12}},
                {{1, 0,  1}, {34,  1, 1}}
        };
        REQUIRE((res == ref));
    }

    TEST_CASE("read PNM type P5", "[pnm_io]") {
        string test = "P5\n"
                      "# w h\n"
                      "5 3 255\n"
                      "# data\n"
                      "aaaaabbbbbccccc";
        std::stringstream in(test);
        auto res = pnm_io_internal::read_image_pnm(in);
        union {
            char c;
            unsigned char uc;
        } converter;

        converter.c = 'a';
        unsigned char a = converter.uc;
        converter.c = 'b';
        unsigned char b = converter.uc;
        converter.c = 'c';
        unsigned char c = converter.uc;
        array_nd<unsigned char> ref = {
                {a, a, a, a, a},
                {b, b, b, b, b},
                {c, c, c, c, c}
        };
        REQUIRE((res == ref));
    }

    TEST_CASE("read PNM type P6", "[pnm_io]") {
        string test = "P6\n"
                      "# w h\n"
                      "2 3 255\n"
                      "# data\n"
                      "abccbaaabbcabbccac";

        std::stringstream in(test);
        auto res = pnm_io_internal::read_image_pnm(in);

        union {
            char c;
            unsigned char uc;
        } converter;
        converter.c = 'a';
        unsigned char a = converter.uc;
        converter.c = 'b';
        unsigned char b = converter.uc;
        converter.c = 'c';
        unsigned char c = converter.uc;
        array_nd<unsigned char> ref = {
                {{a, b, c}, {c, b, a}},
                {{a, a, b}, {b, c, a}},
                {{b, b, c}, {c, a, c}}
        };
        REQUIRE((res == ref));
    }

    TEST_CASE("save PNM 1 band", "[pnm_io]") {
        array_nd<unsigned char> ref = {
                {0, 10, 1, 0,   1},
                {1, 1,  0, 255, 1},
                {1, 0,  1, 1,   1}
        };
        ostringstream out;
        pnm_io_internal::save_image_pnm(out, ref);
        string res = out.str();

        istringstream in(res);
        auto res2 = pnm_io_internal::read_image_pnm(in);
        REQUIRE((res2 == ref));
    }

    TEST_CASE("save PNM 3 bands", "[pnm_io]") {
        array_nd<unsigned char> ref = {
                {{0, 10, 1}, {0,   1, 25}},
                {{1, 1,  0}, {255, 1, 12}},
                {{1, 0,  1}, {34,  1, 1}}
        };
        ostringstream out;
        pnm_io_internal::save_image_pnm(out, ref);
        string res = out.str();

        istringstream in(res);
        auto res2 = pnm_io_internal::read_image_pnm(in);
        REQUIRE((res2 == ref));
    }
}

<|start_filename|>include/higra/structure/fibonacci_heap.hpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#pragma once

#include <vector>
#include <stack>
#include <array>
#include "../utils.hpp"
#include <cstring>

namespace hg {

    namespace fibonacci_heap_internal {

        /**
         * A simple object pool, only support allocating objects one at a time.
         *
         * Warning : not thread safe
         *
         * @tparam T Type of objects in the pool
         */
        template<typename T>
        struct object_pool {


            union object {
                T t;
                object *next;

                object() {
                    next = nullptr;
                }

                ~object() {

                }
            };


            void free(T *element) {
                //memset(element, 0, sizeof(T));
                object *new_first = (object *) element;
                new_first->next = first_free;
                first_free = new_first;
            }

            T *allocate() {
                if (first_free == nullptr) {
                    pool.emplace_back(m_blocksize);
                    auto &block = pool[pool.size() - 1];
                    first_free = &block[0];
                    for (index_t i = 0; i < (index_t) block.size() - 1; i++)
                        block[i].next = first_free + i + 1;
                    block[block.size() - 1].next = nullptr;
                }

                object *tmp = first_free;
                first_free = first_free->next;
                return (T *) tmp;
            }

            object_pool(size_t blocksize = 4096) : m_blocksize(blocksize) {

            }

        private:
            std::vector<std::vector<object>> pool;
            size_t m_blocksize;
            object *first_free = nullptr;
        };


        template<typename T>
        struct fibonacci_heap;

        template<typename T>
        struct node {
            using self_t = node<T>;
            using pointer_t = self_t *;
        private:
            pointer_t m_previous;
            pointer_t m_next;
            pointer_t m_child;
            pointer_t m_parent;

            T m_value;

            int m_degree;
            bool m_marked;

            node() {}

            void init(T value) {
                m_value = value;
                m_previous = this;
                m_next = this;
                m_child = nullptr;
                m_parent = nullptr;
                m_degree = 0;
                m_marked = false;
            }

        public:
            friend struct fibonacci_heap<T>;

            T get_value() { return m_value; }
        };


        /**
         * Fibonacci Heap
         *
         * Warning different heaps will share the same object pool which is NOT thread safe !
         * The following method are not thread safe: push, pop, erase, clear, destructor
         *
         * @tparam T Value type, must implement operator < (ie. with a and b two values of type T, a < b must be a well formed expression)
         */
        template<typename T>
        struct fibonacci_heap final {
            using node_t = node<T>;
            using value_handle = node_t *;

        private:

            static object_pool<node_t> &s_pool() {
                static object_pool<node_t> pool{};
                return pool;
            }

            node_t *m_heap = nullptr;
            size_t m_size = 0;

        public:

            /**
             * Creates an empty min-heap
             */
            fibonacci_heap() {

            }

            /**
             * Deletes current heap
             */
            ~fibonacci_heap() {
                clear();
            }

            fibonacci_heap(const fibonacci_heap &) = delete;

            fibonacci_heap &operator=(const fibonacci_heap &) = delete;

            fibonacci_heap(fibonacci_heap &&other) {
                m_heap = other.m_heap;
                m_size = other.m_size;
                other.m_heap = nullptr;
                other.m_size = 0;
            }

            fibonacci_heap &operator=(fibonacci_heap &&other) {
                m_heap = other.m_heap;
                m_size = other.m_size;
                other.m_heap = nullptr;
                other.m_size = 0;
            }

            /**
             * Test if heap is empty
             * @return
             */
            bool empty() const {
                return m_heap == nullptr;
            }

            /**
             * Insert new value in  the heap
             *
             * Complexity O(1)
             *
             * @param value
             * @return Handle on the new value (used for increase/decrease/update/erase operations)
             */
            value_handle push(T value) {
                m_size++;
                node_t *new_node = s_pool().allocate();
                new_node->init(value);
                m_heap = m_merge(m_heap, new_node);
                return new_node;
            }

            /**
             * Merge the provided heap into the current heap.
             * The provided heap is empty after the operation.
             *
             * Complexity O(1)
             *
             * @param other
             */
            void merge(fibonacci_heap &other) {
                m_heap = m_merge(m_heap, other.m_heap);
                m_size += other.size();
                other.m_size = 0;
                other.m_heap = nullptr;
            }

            /**
             * Returns an handle on the min element of the heap
             *
             * Complexity O(1)
             *
             * @return Handle on the value (used for increase/decrease/update/erase operations)
             */
            value_handle top() {
                return m_heap;
            }

            /**
             * Removes the min element from the heap (this invalidates any previously obtained handles on this element)
             */
            void pop() {
                auto old_heap = m_heap;
                m_extract_min();
                s_pool().free(old_heap);
            }

            /**
             * Removes the given element from the heap.
             *
             * Complexity amortized O(log(n))
             *
             * @param node
             */
            void erase(value_handle node) {
                m_delete_key(node);
                s_pool().free(node);
            }


            /**
             * Decreases the value of the given element to the given value.
             *
             * Complexity amortized O(1)
             *
             * @param node
             * @param value
             */
            void decrease(value_handle node, const T &value) {
                node->m_value = value;
                decrease(node);
            }

            /**
             * Updates the heap after the value of the given element has been decreased
             *
             * Complexity amortized O(1)
             *
             * @param node
             */
            void decrease(value_handle node) {
                m_decrease_key(node);
            }

            /**
             * Increases the value of the given element to the given value.
             *
             * Complexity amortized O(log(n))
             *
             * @param node
             * @param value
             */
            void increase(value_handle node, const T &value) {
                node->m_value = value;
                increase(node);
            }

            /**
             * Updates the heap after the value of the given element has been increased
             *
             * Complexity amortized O(log(n))
             *
             * @param node
             */
            void increase(value_handle node) {
                m_increase_key(node);
            }

            /**
             * Changes the value of the given element to the given value.
             *
             * Complexity amortized O(log(n))
             *
             * @param node
             * @param value
             */
            void update(value_handle node, const T &value) {
                if (value < node->m_value)
                    decrease(node, value);
                else if (node->m_value < value) {
                    increase(node, value);
                }
            }

            /**
             * Empties the heap
             *
             * Complexity O(n)
             */
            void clear() {
                if (m_heap == nullptr)
                    return;

                std::stack<node_t *> s;
                s.push(m_heap);
                for (node_t *tmp = m_heap->m_next; tmp != m_heap; tmp = tmp->m_next) {
                    s.push(tmp);
                }
                while (!s.empty()) {
                    node_t *n = s.top();
                    s.pop();
                    node_t *c = n->m_child;
                    if (c != nullptr) {
                        s.push(c);
                        for (node_t *tmp = c->m_next; tmp != c; tmp = tmp->m_next) {
                            s.push(tmp);
                        }
                    }
                    s_pool().free(n);
                }
                m_heap = nullptr;
                m_size = 0;
            }

            auto size() const {
                return m_size;
            }

        private:
            auto m_merge(node_t *root1, node_t *root2) {
                if (root1 == nullptr)
                    return root2;
                if (root2 == nullptr)
                    return root1;

                if (root1->m_value < root2->m_value) {
                    std::swap(root1, root2);
                }

                node_t *root1_previous = root1->m_previous;
                node_t *root2_previous = root2->m_previous;

                root2->m_previous = root1_previous;
                root1_previous->m_next = root2;

                root2_previous->m_next = root1;
                root1->m_previous = root2_previous;

                return root2;
            }

            void m_link_heap(node_t *y, node_t *x) {
                y->m_previous->m_next = y->m_next;
                y->m_next->m_previous = y->m_previous;

                y->m_parent = x;
                auto *child = x->m_child;
                if (child != nullptr) {
                    y->m_next = child->m_next;
                    y->m_previous = child;
                    child->m_next->m_previous = y;
                    child->m_next = y;
                } else {
                    y->m_next = y;
                    y->m_previous = y;
                    x->m_child = y;
                }
                x->m_degree++;
                y->m_marked = false;
            }

            void m_consolidate() {
                if (m_heap == m_heap->m_next)
                    return;

                std::array<node_t *, 64> A;
                std::fill(A.begin(), A.end(), nullptr);
                auto *last = m_heap->m_previous;
                auto *x = m_heap;
                while (true) {
                    auto d = x->m_degree;
                    auto *cur = x;
                    auto *next = x->m_next;
                    while (A[d] != nullptr) {
                        auto *y = A[d];
                        if (y->m_value < x->m_value) {
                            std::swap(x, y);
                        }
                        m_link_heap(y, x);
                        A[d] = nullptr;
                        d++;
                    }
                    A[d] = x;

                    if (cur == last)
                        break;
                    x = next;
                };

                node_t *minh = nullptr;
                int i = 0;
                while (minh == nullptr && i < (index_t) A.size()) {
                    if (A[i] != nullptr) {
                        minh = A[i];
                    }
                    i++;
                }

                auto *start = minh;
                auto *pos = minh->m_next;

                while (pos != start) {
                    if (pos->m_value < minh->m_value)
                        minh = pos;
                    pos = pos->m_next;
                }
                m_heap = minh;
            }

            void m_extract_min() {
                if (m_heap != nullptr) {
                    auto *child = m_heap->m_child;
                    if (child != nullptr) {
                        do {
                            child->m_parent = nullptr;
                            child = child->m_next;
                        } while (child != m_heap->m_child);
                        m_merge(m_heap, m_heap->m_child);
                        m_heap->m_child = nullptr;
                    }

                    auto *old_heap = m_heap;
                    if (old_heap->m_next == old_heap) {
                        m_heap = nullptr;
                    } else {
                        m_heap->m_previous->m_next = m_heap->m_next;
                        m_heap->m_next->m_previous = m_heap->m_previous;
                        m_heap = old_heap->m_next;
                        m_consolidate();
                    }

                    m_size--;
                }
            }

            void m_decrease_key(node_t *node) {
                auto parent = node->m_parent;
                if (parent != nullptr && node->m_value < parent->m_value) {
                    m_cut(node, parent);
                    m_cascading_cut(parent);
                }
                if (node->m_value < m_heap->m_value)
                    m_heap = node;
            }

            void m_cut(node_t *node, node_t *parent) {
                node->m_parent = nullptr;
                if (node->m_next == node) {
                    parent->m_child = nullptr;
                } else {
                    parent->m_child = node->m_next;
                    node->m_next->m_previous = node->m_previous;
                    node->m_previous->m_next = node->m_next;
                }
                parent->m_degree--;

                node->m_next = m_heap->m_next;
                m_heap->m_next->m_previous = node;

                m_heap->m_next = node;
                node->m_previous = m_heap;

                node->m_marked = false;
            }

            void m_cascading_cut(node_t *node) {
                auto parent = node->m_parent;
                if (parent != nullptr) {
                    if (!node->m_marked) {
                        node->m_marked = true;
                    } else {
                        m_cut(node, parent);
                        m_cascading_cut(parent);
                    }
                }
            }

            void m_delete_key(node_t *node) {
                auto parent = node->m_parent;
                if (parent != nullptr) {
                    m_cut(node, parent);
                    m_cascading_cut(parent);
                }

                auto *child = node->m_child;
                if (child != nullptr) {
                    do {
                        child->m_parent = nullptr;
                        child = child->m_next;
                    } while (child != node->m_child);
                    m_merge(m_heap, node->m_child);
                    node->m_child = nullptr;
                }

                if (node->m_next == node) {
                    m_heap = nullptr;
                } else {
                    node->m_next->m_previous = node->m_previous;
                    node->m_previous->m_next = node->m_next;
                    m_heap = node->m_next;
                    m_consolidate();
                }


                m_size--;
            }

            void m_increase_key(node_t *node) {
                m_delete_key(node);
                m_size++;
                node->init(node->m_value);
                m_heap = m_merge(m_heap, node);
            }

            /*
            int m_check_integrity(node_t * node, node_t * parent){
                if(node== nullptr)
                    return 0;
                int size = 0;
                node_t * start = node;

                node_t * position = node;
                do{
                    size++;
                    if(position->m_next->m_previous != position)
                    {
                        std::cout << "error";
                    }
                    if(position->m_previous->m_next != position)
                    {
                        std::cout << "error";
                    }
                    if(position->m_parent != parent)
                    {
                        std::cout << "error";
                    }
                    if(position->m_child != nullptr){
                        size+=m_check_integrity(position->m_child, position);
                    }
                    position = position->m_next;
                }while (position!=start);
                return size;
            };*/

        };
    }

    template<typename value_type>
    using fibonacci_heap = fibonacci_heap_internal::fibonacci_heap<value_type>;

}


<|start_filename|>lib/lib/cmake/xtensor-python/xtensor-pythonConfig.cmake<|end_filename|>
############################################################################
# Copyright (c) <NAME>, <NAME> and <NAME>          #
# Copyright (c) QuantStack                                                 #
#                                                                          #
# Distributed under the terms of the BSD 3-Clause License.                 #
#                                                                          #
# The full license is in the file LICENSE, distributed with this software. #
############################################################################

# xtensor-python cmake module
# This module sets the following variables in your project::
#
#   xtensor-python_FOUND - true if xtensor-python found on the system
#   xtensor-python_INCLUDE_DIRS - the directory containing xtensor-python headers
#   xtensor-python_LIBRARY - empty


####### Expanded from @PACKAGE_INIT@ by configure_package_config_file() #######
####### Any changes to this file will be overwritten by the next CMake run ####
####### The input file was xtensor-pythonConfig.cmake.in                            ########

get_filename_component(PACKAGE_PREFIX_DIR "${CMAKE_CURRENT_LIST_DIR}/../../../" ABSOLUTE)

macro(set_and_check _var _file)
  set(${_var} "${_file}")
  if(NOT EXISTS "${_file}")
    message(FATAL_ERROR "File or directory ${_file} referenced by variable ${_var} does not exist !")
  endif()
endmacro()

macro(check_required_components _NAME)
  foreach(comp ${${_NAME}_FIND_COMPONENTS})
    if(NOT ${_NAME}_${comp}_FOUND)
      if(${_NAME}_FIND_REQUIRED_${comp})
        set(${_NAME}_FOUND FALSE)
      endif()
    endif()
  endforeach()
endmacro()

####################################################################################

if(NOT TARGET xtensor-python)
  include("${CMAKE_CURRENT_LIST_DIR}/xtensor-pythonTargets.cmake")
  get_target_property(xtensor-python_INCLUDE_DIRS xtensor-python INTERFACE_INCLUDE_DIRECTORIES)
endif()


<|start_filename|>higra/image/py_hierarchy_mean_pb.cpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#include "py_hierarchy_mean_pb.hpp"
#include "../py_common.hpp"
#include "higra/hierarchy/hierarchy_core.hpp"
#include "higra/image/hierarchy_mean_pb.hpp"
#include "xtensor-python/pyarray.hpp"
#include "xtensor-python/pytensor.hpp"
#include "pybind11/functional.h"

template<typename T>
using pyarray = xt::pyarray<T>;

namespace py = pybind11;

template<typename graph_t>
struct def_oriented_watershed {
    template<typename value_t, typename C>
    static
    void def(C &m, const char *doc) {
        m.def("_oriented_watershed", [](const graph_t &graph,
                                       const std::vector<size_t> &shape,
                                       const pyarray<value_t> &edge_weights,
                                       const pyarray<value_t> &edge_orientations) {
                  auto res = hg::oriented_watershed(graph,
                                                   hg::embedding_grid_2d(shape),
                                                   edge_weights,
                                                   edge_orientations);
                  return py::make_tuple(std::move(res.first.rag),
                                        std::move(res.first.vertex_map),
                                        std::move(res.first.edge_map),
                                        std::move(res.second));
              },
              doc,
              py::arg("graph"),
              py::arg("shape"),
              py::arg("edge_weights"),
              py::arg("edge_orientations") = pyarray<value_t>()
        );
    }
};

template<typename graph_t>
struct def_hierarchy_mean_pb {
    template<typename value_t, typename C>
    static
    void def(C &m, const char *doc) {
        m.def("_mean_pb_hierarchy", [](const graph_t &graph,
                                       const std::vector<size_t> &shape,
                                       const pyarray<value_t> &edge_weights,
                                       const pyarray<value_t> &edge_orientations) {
                  auto res = hg::mean_pb_hierarchy(graph,
                                                   hg::embedding_grid_2d(shape),
                                                   edge_weights,
                                                   edge_orientations);
                  return py::make_tuple(std::move(res.first.rag),
                                        std::move(res.first.vertex_map),
                                        std::move(res.first.edge_map),
                                        std::move(res.second.tree),
                                        std::move(res.second.altitudes)
                  );
              },
              doc,
              py::arg("graph"),
              py::arg("shape"),
              py::arg("edge_weights"),
              py::arg("edge_orientations") = pyarray<value_t>()
        );
    }
};


void py_init_hierarchy_mean_pb(pybind11::module &m) {
    xt::import_numpy();

    add_type_overloads<def_oriented_watershed<hg::ugraph>, HG_TEMPLATE_FLOAT_TYPES>
            (m,
             "Compute the oriented watershed as described in \n\n"
             "<NAME>, <NAME>, <NAME> and <NAME>, \"Contour Detection and Hierarchical Image Segmentation,\" "
             "in IEEE Transactions on Pattern Analysis and Machine Intelligence, vol. 33, no. 5, pp. 898-916, May 2011.\n"
             "doi: 10.1109/TPAMI.2010.161\n"
             "\n"
             "This does not include gradient estimation."
            );

    add_type_overloads<def_hierarchy_mean_pb<hg::ugraph>, HG_TEMPLATE_FLOAT_TYPES>
            (m,
             "Compute the mean pb hierarchy as described in \n\n"
             "<NAME>, <NAME>, <NAME> and <NAME>, \"Contour Detection and Hierarchical Image Segmentation,\" "
             "in IEEE Transactions on Pattern Analysis and Machine Intelligence, vol. 33, no. 5, pp. 898-916, May 2011.\n"
             "doi: 10.1109/TPAMI.2010.161\n"
             "\n"
             "This does not include gradient estimation."
            );


}

<|start_filename|>include/higra/structure/details/iterators.hpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/
#include "../../utils.hpp"

#pragma once

namespace hg {

    /**
     * Facade to ease the declaration of new forward iterators (quick reimplementation of boost facade iterator).
     *
     * The iterator must inherit of the facade and declare the following 3 functions:
     *  - deference: obtain the element at the current position of the iterator
     *  - increment: move the iterator to next element
     *  - equal: test if two iterators are equal
     *
     * @tparam derived_type
     * @tparam value_t
     * @tparam reference_t
     */
    template<typename derived_type, typename value_t, typename reference_t = value_t>
    struct forward_iterator_facade {
        using self_type = forward_iterator_facade<derived_type, value_t, reference_t>;
        using value_type =  value_t;
        using difference_type = std::ptrdiff_t;
        using reference = reference_t;
        using pointer = value_t *;
        using iterator_category = std::forward_iterator_tag;

        friend reference operator*(const derived_type &lhs) {
            return lhs.dereference();
        }

        friend const reference operator*(derived_type &lhs) {
            return lhs.dereference();
        }

        friend derived_type operator++(derived_type &lhs) {
            lhs.increment();
            return lhs;
        }

        friend derived_type operator++(derived_type &lhs, int) {
            derived_type tmp(lhs);
            lhs.increment();
            return tmp;
        }

        friend bool operator==(const derived_type &lhs, const derived_type &rhs) {
            return lhs.equal(rhs);
        }

        friend bool operator!=(const derived_type &lhs, const derived_type &rhs) {
            return !lhs.equal(rhs);
        }
    };

    /**
     * Facade to ease the declaration of new random iterators (quick reimplementation of boost facade iterator).
     *
     * The iterator must inherit of the facade and declare the following 3 functions:
     *  - deference: obtain the element at the current position of the iterator
     *  - increment: move the iterator to next element
     *  - decrement: move the iterator to previous element
     *  - advance(n):	Advance by n positions
     *  - distance_to(j)	Measure the distance to j
     *  - equal: test if two iterators are equal
     *
     * @tparam derived_type
     * @tparam value_t
     * @tparam reference_t
     */
    template<typename derived_type, typename value_t, typename reference_t = value_t>
    struct random_iterator_facade {
        using self_type = random_iterator_facade<derived_type, value_t, reference_t>;
        using value_type =  value_t;
        using difference_type = std::ptrdiff_t;
        using reference = reference_t;
        using pointer = value_t *;
        using iterator_category = std::random_access_iterator_tag;

        friend reference operator*(const derived_type &lhs) {
            return lhs.dereference();
        }

        friend const reference operator*(derived_type &lhs) {
            return lhs.dereference();
        }

        friend derived_type operator++(derived_type &lhs) {
            lhs.increment();
            return lhs;
        }

        friend derived_type operator++(derived_type &lhs, int) {
            derived_type tmp(lhs);
            lhs.increment();
            return tmp;
        }

        friend derived_type operator--(derived_type &lhs) {
            lhs.decrement();
            return lhs;
        }

        friend derived_type operator--(derived_type &lhs, int) {
            derived_type tmp(lhs);
            lhs.decrement();
            return tmp;
        }

        friend bool operator==(const derived_type &lhs, const derived_type &rhs) {
            return lhs.equal(rhs);
        }

        friend bool operator!=(const derived_type &lhs, const derived_type &rhs) {
            return !lhs.equal(rhs);
        }

        friend bool operator<=(const derived_type &lhs, const derived_type &rhs) {
            return rhs.distance_to(lhs) <= 0;
        }

        friend bool operator<(const derived_type &lhs, const derived_type &rhs) {
            return rhs.distance_to(lhs) < 0;
        }

        friend bool operator>=(const derived_type &lhs, const derived_type &rhs) {
            return rhs.distance_to(lhs) >= 0;
        }

        friend bool operator>(const derived_type &lhs, const derived_type &rhs) {
            return rhs.distance_to(lhs) > 0;
        }

        friend auto operator-(const derived_type &lhs, const derived_type &rhs) {
            return rhs.distance_to(lhs);
        }

        friend derived_type operator+(const derived_type &lhs, const difference_type &n) {
            derived_type tmp(lhs);
            tmp.advance(n);
            return tmp;
        }

        friend derived_type operator+(const difference_type &n, const derived_type &rhs) {
            derived_type tmp(rhs);
            tmp.advance(n);
            return tmp;
        }

        auto operator[](const difference_type &n) {
            derived_type tmp(*(derived_type*)(this));
            tmp.advance(n);
            return *tmp;
        }
    };


    /**
     * Facade to ease the declaration of new forward iterators as a transformation of an existing forward iterator
     * (quick reimplementation of boost transform iterator).
     *
     * @tparam transform_fun_type
     * @tparam base_iterator_t
     * @tparam value_t
     * @tparam reference_t
     */
    template<typename transform_fun_type, typename base_iterator_t, typename value_t, typename reference_t = value_t>
    struct transform_forward_iterator :
            public forward_iterator_facade<transform_forward_iterator<transform_fun_type, base_iterator_t, value_t, reference_t>, value_t, reference_t> {

        using self_type = transform_forward_iterator<transform_fun_type, base_iterator_t, value_t, reference_t>;

        void increment() {
            m_base++;
        };

        bool equal(const self_type &other) const {
            return m_base == other.m_base;
        }

        value_t dereference() const {
            return m_fun(*m_base);
        }

        transform_forward_iterator(base_iterator_t &&base, transform_fun_type &&fun) :
                m_base(std::forward<base_iterator_t>(base)),
                m_fun(std::forward<transform_fun_type>(fun)) {}

        transform_forward_iterator(const base_iterator_t &base, const transform_fun_type &fun) :
                m_base(base),
                m_fun(fun) {}

        transform_forward_iterator() : m_base(), m_fun() {}

    private:
        base_iterator_t m_base;
        transform_fun_type m_fun;
    };


    /**
     * Quick implementation of a counting iterator.
     *
     * Counting iterator has an integer value and a step size.
     * Incrementing the iterator simply add the step size to the current value of the iterator.
     *
     * @tparam value_type
     */
    template<typename value_type = index_t>
    struct counting_iterator :
            public random_iterator_facade<counting_iterator<value_type>,
                    value_type> {
        using self_type = counting_iterator<value_type>;
        counting_iterator() : m_position(0), m_step(1) {}

        counting_iterator(value_type position, value_type step = 1) :
                m_position(position),
                m_step(step) {}

        void increment() {
            m_position += m_step;
        }

        void decrement() {
            m_position -= m_step;
        }

        void advance(size_t n) {
            m_position += n * m_step;
        }

        auto distance_to(const self_type& rhs) const {
            return (rhs.m_position - m_position) / m_step;
        }

        bool equal(const counting_iterator<value_type> &other) const {
            return m_position == other.m_position;
        }

        value_type dereference() const {
            return m_position;
        }

    private:
        value_type m_position;
        value_type m_step;
    };


    /**
     * A range of integer value with a start value, a step size, and a maximum value (excluded).
     *
     * irange(start, stop, step) is the equivalent to
     * - python range(start, stop, step)
     * - matlab start:step:stop
     *
     * @tparam value_type
     */
    template<typename value_type = index_t>
    struct irange {

        irange() : m_start(0), m_stop(0), m_step(1) {}

        irange(value_type start, value_type stop, value_type step = 1) :
                m_start(start),
                m_step(step) {
            auto num_steps = std::abs(m_start - stop) / std::abs(m_step);
            m_stop = m_start + num_steps * m_step;
        }

        auto begin() const {
            return counting_iterator<value_type>(m_start, m_step);
        }

        auto end() const {
            return counting_iterator<value_type>(m_stop, m_step);
        }

    private:

        value_type m_start;
        value_type m_step;
        value_type m_stop;

    };
}




<|start_filename|>higra/assessment/py_partition.hpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#pragma once

#include "pybind11/pybind11.h"

void py_init_assessment_partition(pybind11::module &m);



<|start_filename|>benchmark/main.cpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/


#include <iostream>

#include <benchmark/benchmark.h>
#include "xtensor/xarray.hpp"
#include "xtensor/xtensor.hpp"

#ifdef XTENSOR_USE_XSIMD
#ifdef __GNUC__

template<class T>
void print_type(T && /*t*/) {
    std::cout << __PRETTY_FUNCTION__ << std::endl;
}

#endif

void print_stats() {
    std::cout << "USING XSIMD\nSIMD SIZE: " << xsimd::simd_traits<double>::size << "\n\n";
#ifdef __GNUC__
    print_type(xt::xarray<double>());
    print_type(xt::xtensor<double, 2>());
#endif
}

#else

void print_stats() {
    std::cout << "NOT USING XSIMD\n\n";
};
#endif


// Custom main function to print SIMD config
int main(int argc, char **argv) {
    print_stats();
    benchmark::Initialize(&argc, argv);
    if (benchmark::ReportUnrecognizedArguments(argc, argv)) return 1;
    benchmark::RunSpecifiedBenchmarks();
}

<|start_filename|>test/cpp/detail/test_log.cpp<|end_filename|>
/***************************************************************************
* Copyright ESIEE Paris (2018)                                             *
*                                                                          *
* Contributor(s) : <NAME>                                         *
*                                                                          *
* Distributed under the terms of the CECILL-B License.                     *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#include "../test_utils.hpp"
#include "higra/detail/log.hpp"
#include <string>

namespace test_log {

    using namespace hg;
    using namespace std;

    TEST_CASE("test logger", "[logger]") {
        string ref_msg("This is a test");
        auto & loggers = logger::callbacks();
        auto save = loggers.front();
        loggers.clear();
        SECTION( "do test" ) {
            loggers.push_back([&ref_msg](const std::string &msg) {
                REQUIRE(msg.find(ref_msg) != std::string::npos);
            });

            HG_LOG_INFO("%s", ref_msg.c_str());
        }
        loggers.clear();
        loggers.push_back(save);
    }
}