From c928ee794c1f8f5f1aaea8addafbd48ea084f6bf Mon Sep 17 00:00:00 2001 From: colorxixi Date: Sun, 2 Feb 2025 06:03:48 -0700 Subject: [PATCH] Major upgrade of dependencies and running time MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Removed the Boost dependency by adopting a newer cut-pursuit version from the authors (resulting in a 5–15× speed improvement). 2. Fixed an issue with the nearest neighbor search caused by an incorrect 2D array order input to the knn_cpp query function. 3. Enabled customization of the number of threads for knn-cpp, yielding a slight speed improvement. 4. Optimized array calculations through enhanced use of STL algorithms. --- CMakeLists.txt | 13 - include/API.h | 407 --------- include/CMakeLists.txt | 15 +- include/Common.h | 147 ---- include/CutPursuit.h | 708 --------------- include/CutPursuit_KL.h | 553 ------------ include/CutPursuit_L2.h | 492 ----------- include/CutPursuit_Linear.h | 301 ------- include/CutPursuit_SPG.h | 499 ----------- include/Graph.h | 112 --- include/TreeIso.h | 10 +- include/TreeIsoHelper.h | 79 -- include/TreeIsoHelper.hpp | 399 +++++++++ include/block.hpp | 291 +++++++ include/ccTreeIsoDlg.h | 8 +- include/cp_d0_dist.hpp | 195 +++++ include/cut_pursuit.hpp | 427 +++++++++ include/cut_pursuit_d0.hpp | 133 +++ include/maxflow.hpp | 157 ++++ include/qTreeIso.h | 4 +- include/qTreeIsoCommands.h | 6 +- src/CMakeLists.txt | 4 + src/TreeIso.cpp | 1033 ++++++---------------- src/cp_d0_dist.cpp | 294 +++++++ src/cut_pursuit.cpp | 1638 +++++++++++++++++++++++++++++++++++ src/cut_pursuit_d0.cpp | 334 +++++++ src/maxflow.cpp | 446 ++++++++++ src/qTreeIso.cpp | 10 +- ui/TreeIsoDlg.ui | 70 +- 29 files changed, 4641 insertions(+), 4144 deletions(-) delete mode 100644 include/API.h delete mode 100644 include/Common.h delete mode 100644 include/CutPursuit.h delete mode 100644 include/CutPursuit_KL.h delete mode 100644 include/CutPursuit_L2.h delete mode 100644 include/CutPursuit_Linear.h delete mode 100644 include/CutPursuit_SPG.h delete mode 100644 include/Graph.h delete mode 100644 include/TreeIsoHelper.h create mode 100644 include/TreeIsoHelper.hpp create mode 100644 include/block.hpp create mode 100644 include/cp_d0_dist.hpp create mode 100644 include/cut_pursuit.hpp create mode 100644 include/cut_pursuit_d0.hpp create mode 100644 include/maxflow.hpp create mode 100644 src/cp_d0_dist.cpp create mode 100644 src/cut_pursuit.cpp create mode 100644 src/cut_pursuit_d0.cpp create mode 100644 src/maxflow.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 39f5377..800c05c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,19 +8,6 @@ if( PLUGIN_STANDARD_QTREEISO ) AddPlugin( NAME ${PROJECT_NAME} ) - find_package(Boost 1.67 REQUIRED COMPONENTS graph) - if(NOT Boost_FOUND) - set( BOOST_ROOT_DIR "" CACHE PATH "Boost root (contains the Boost directory)" ) - if ( NOT BOOST_ROOT_DIR ) - message( SEND_ERROR "No Boost directory specified (BOOST_ROOT_DIR)" ) - endif() - include_directories(${BOOST_ROOT_DIR}) - else() - target_link_libraries(${PROJECT_NAME} Boost::graph) - endif() - - target_compile_definitions( ${PROJECT_NAME} PRIVATE BOOST_ALLOW_DEPRECATED_HEADERS ) - find_package(Eigen3 QUIET) if(NOT Eigen3_FOUND) set( EIGEN_ROOT_DIR "" CACHE PATH "Eigen root (contains the Eigen directory)" ) diff --git a/include/API.h b/include/API.h deleted file mode 100644 index eb071ff..0000000 --- a/include/API.h +++ /dev/null @@ -1,407 +0,0 @@ -#pragma once -#include -#include "CutPursuit_L2.h" -#include "CutPursuit_Linear.h" -#include "CutPursuit_KL.h" -#include "CutPursuit_SPG.h" -//********************************************************************************** -//*******************************L0-CUT PURSUIT************************************* -//********************************************************************************** -//Greedy graph cut based algorithm to solve the generalized minimal -//partition problem -// -//Cut Pursuit: fast algorithms to learn piecewise constant functions on -//general weighted graphs, Loic Landrieu and Guillaume Obozinski,2016. -// -//Produce a piecewise constant approximation of signal $y$ structured -//by the graph G=(V,e,mu,w) with mu the node weight and w the edgeweight: -//argmin \sum_{i \IN V}{mu_i * phi(x_I, y_I)} -//+ \sum_{(i,j) \IN E}{w_{i,j} 1(x_I != x_J)} -// -//phi(X,Y) the fidelity function (3 are implemented) -//(x != y) the function equal to 1 if x!=y and 0 else -// -// LOIC LANDRIEU 2017 -// -//=======================SYNTAX=================================================== -//---------------REGULARIZATION--------------------------------------------------- -//C style inputs -//void cut_pursuit(const uint32_t n_nodes, const uint32_t n_edges, const uint32_t nObs -// ,const T * observation, const uint32_t * Eu, const uint32_t * Ev -// ,const T * edgeWeight, const T * nodeWeight -// ,T * solution, const T lambda, const uint32_t cutoff, const T mode, const T speed, const T weight_decay -// , const float verbose) -//C++ style input -//void cut_pursuit(const uint32_t n_nodes, const uint32_t n_edges, const uint32_t nObs -// , std::vector< std::vector > & observation -// , const std::vector & Eu, const std::vector & Ev -// ,const std::vector & edgeWeight, const std::vector & nodeWeight -// ,std::vector< std::vector > & solution, const T lambda, const uint32_t cutoff, const T mode, const T speed, const T weight_decay -// , const float verbose) -// when D = 1 -//void cut_pursuit(const uint32_t n_nodes, const uint32_t n_edges, const uint32_t nObs -// , std::vector & observation -// , const std::vector & Eu, const std::vector & Ev -// ,const std::vector & edgeWeight, const std::vector & nodeWeight -// ,std::vector & solution, const T lambda, const uint32_t cutoff, const T mode, const T speed -// , const float verbose) - -//-----INPUT----- -// 1x1 uint32_t n_nodes = number of nodes -// 1x1 uint32_t n_edges = number of edges -// 1x1 uint32_t nObs = dimension of data on each node -// NxD float observation : the observed signal -// Ex1 uint32_t Eu, Ev: the origin and destination of each node -// Ex1 float edgeWeight: the edge weight -// Nx1 float nodeWeight: the node weight -// 1x1 float lambda : the regularization strength -// 1x1 uint32_t cutoff : minimal component size -// 1x1 float mode : the fidelity function -// 0 : linear (for simplex bound data) -// 1 : quadratic (default) -// 0 > & observation -// , const std::vector & Eu, const std::vector & Ev -// ,const std::vector & edgeWeight, const std::vector & nodeWeight -// ,std::vector< std::vector > & solution, -// , const std::vector & in_component -// , std::vector< std::vector > & components -// , uint32_t & n_nodes_red, uint32_t & n_edges_red -// , std::vector & Eu_red, std::vector & Ev_red -// , std::vector & edgeWeight_red, std::vector & nodeWeight_red -// , const T lambda, const T mode, const T speed, const T weight_decay -// , const float verbose) -//-----EXTRA INPUT----- -// Nx1 uint32_t inComponent: for each node, in which component it belongs -// 1x1 n_node_red : number of components -// 1x1 uint32_t n_edges_red : number of edges in reduced graph -// n_node_redx1 cell components : for each component, list of the nodes -// n_edges_redx1 uint32_t Eu_red, Ev_red : source and target of reduced edges -// n_edges_redx1 float edgeWeight_red: weights of reduced edges -// n_node_redx1 float nodeWeight_red: weights of reduced nodes - - -namespace CP { -//=========================================================================== -//===================== CREATE_CP =================================== -//=========================================================================== - -template -CP::CutPursuit * create_CP(const T mode, const float verbose) -{ - CP::CutPursuit * cp = NULL; - fidelityType fidelity = L2; - if (mode == 0) - { - if (verbose > 0) - { - std::cout << " WITH LINEAR FIDELITY" << std::endl; - } - fidelity = linear; - cp = new CP::CutPursuit_Linear(); - } - else if (mode == 1) - { - if (verbose > 0) - { - std::cout << " WITH L2 FIDELITY" << std::endl; - } - fidelity = L2; - cp = new CP::CutPursuit_L2(); - } - else if (mode > 0 && mode < 1) - { - if (verbose > 0) - { - std::cout << " WITH KULLBACK-LEIBLER FIDELITY SMOOTHING : " - << mode << std::endl; - } - fidelity = KL; - cp = new CP::CutPursuit_KL(); - cp->parameter.smoothing = mode; - } - else if (mode == -1) - { - if (verbose > 0) - { - std::cout << " WITH ALTERNATE L2 NORM : " - << mode << std::endl; - } - fidelity = SPG; - cp = new CP::CutPursuit_KL(); - cp->parameter.smoothing = mode; - } - else if (mode == 2) - { - if (verbose > 0) - { - std::cout << " PARTITION MODE WITH SPATIAL INFORMATION : " - << mode << std::endl; - } - fidelity = SPG; - cp = new CP::CutPursuit_SPG(); - cp->parameter.smoothing = mode; - } - else - { - std::cout << " UNKNOWN MODE, SWICTHING TO L2 FIDELITY" - << std::endl; - fidelity = L2; - cp = new CP::CutPursuit_L2(); - } - cp->parameter.fidelity = fidelity; - cp->parameter.verbose = verbose; - return cp; -} - - -//=========================================================================== -//===================== cut_pursuit C++-style ============================ -//=========================================================================== -template -void cut_pursuit(const uint32_t n_nodes, const uint32_t n_edges, const uint32_t nObs - , std::vector< std::vector > & observation - , const std::vector & Eu, const std::vector & Ev - , const std::vector & edgeWeight, const std::vector & nodeWeight - , std::vector< std::vector > & solution, const T lambda, const uint32_t cutoff, const T mode, const T speed, const T weight_decay - , const float verbose) -{ //C-style ++ interface - std::srand (1); - if (verbose > 0) - { - std::cout << "L0-CUT PURSUIT"; - } - //--------parameterization--------------------------------------------- - CP::CutPursuit * cp = create_CP(mode, verbose); - set_speed(cp, speed, weight_decay, verbose); - set_up_CP(cp, n_nodes, n_edges, nObs, observation, Eu, Ev - ,edgeWeight, nodeWeight); - cp->parameter.reg_strenth = lambda; - cp->parameter.cutoff = cutoff; - //-------run the optimization------------------------------------------ - cp->run(); - //------------write the solution----------------------------- - VertexAttributeMap vertex_attribute_map = boost::get( - boost::vertex_bundle, cp->main_graph); - VertexIterator ite_nod = boost::vertices(cp->main_graph).first; - for(uint32_t ind_nod = 0; ind_nod < n_nodes; ind_nod++ ) - { - for(uint32_t ind_dim=0; ind_dim < nObs; ind_dim++) - { - solution[ind_nod][ind_dim] = vertex_attribute_map[*ite_nod].value[ind_dim]; - } - ite_nod++; - } - delete cp; -} - - -//=========================================================================== -//===================== cut_pursuit segmentation light C++-style ================ -//=========================================================================== -template -void cut_pursuit(const uint32_t n_nodes, const uint32_t n_edges, const uint32_t nObs - , std::vector< std::vector > & observation - , const std::vector & Eu, const std::vector & Ev - , const std::vector & edgeWeight, const std::vector & nodeWeight - , std::vector< std::vector > & solution - , std::vector & in_component, std::vector< std::vector > & components - , const T lambda, const uint32_t cutoff, const T mode, const T speed, const T weight_decay - , const float verbose) -{ //C-style ++ interface - std::srand (1); - - if (verbose > 0) - { - std::cout << "L0-CUT PURSUIT"; - } - //--------parameterization--------------------------------------------- - CP::CutPursuit * cp = create_CP(mode, verbose); - - set_speed(cp, speed, weight_decay, verbose); - set_up_CP(cp, n_nodes, n_edges, nObs, observation, Eu, Ev, edgeWeight, nodeWeight); - cp->parameter.reg_strenth = lambda; - cp->parameter.cutoff = cutoff; - //-------run the optimization------------------------------------------ - cp->run(); - cp->compute_reduced_graph(); - //------------resize the vectors----------------------------- - uint32_t n_nodes_red = static_cast(boost::num_vertices(cp->reduced_graph)); - in_component.resize(n_nodes); - components.resize(n_nodes_red); - //------------write the solution----------------------------- - VertexAttributeMap vertex_attribute_map = boost::get(boost::vertex_bundle, cp->main_graph); - VertexIterator ite_nod = boost::vertices(cp->main_graph).first; - for(uint32_t ind_nod = 0; ind_nod < n_nodes; ind_nod++ ) - { - for(uint32_t ind_dim=0; ind_dim < nObs; ind_dim++) - { - solution[ind_nod][ind_dim] = vertex_attribute_map[*ite_nod].value[ind_dim]; - } - ite_nod++; - } - //------------fill the components----------------------------- - VertexIndexMap vertex_index_map = get(boost::vertex_index, cp->main_graph); - for(uint32_t ind_nod_red = 0; ind_nod_red < n_nodes_red; ind_nod_red++ ) - { - size_t component_size = cp->components[ind_nod_red].size(); - components[ind_nod_red] = std::vector(component_size, 0); - for(size_t ind_nod = 0; ind_nod < component_size; ind_nod++ ) - { - components[ind_nod_red][ind_nod] = static_cast(vertex_index_map(cp->components[ind_nod_red][ind_nod])); - } - } - ite_nod = boost::vertices(cp->main_graph).first; - for(uint32_t ind_nod = 0; ind_nod < n_nodes; ind_nod++ ) - { - in_component[ind_nod] = vertex_attribute_map[*ite_nod].in_component; - ite_nod++; - } - delete cp; -} - -//=========================================================================== -//===================== SET_UP_CP C++ style ============================ -//=========================================================================== -template -void set_up_CP(CP::CutPursuit * cp, const uint32_t n_nodes, const uint32_t n_edges, const uint32_t nObs - ,const std::vector< std::vector> observation, const std::vector Eu, const std::vector Ev - ,const std::vector edgeWeight, const std::vector nodeWeight) -{ - cp->main_graph = Graph(n_nodes); - cp->dim = nObs; - //--------fill the vertices-------------------------------------------- - VertexAttributeMap vertex_attribute_map = boost::get( - boost::vertex_bundle, cp->main_graph); - VertexIterator ite_nod = boost::vertices(cp->main_graph).first; - //the node attributes used to fill each node - for(uint32_t ind_nod = 0; ind_nod < n_nodes; ind_nod++ ) - { - VertexAttribute v_attribute (nObs); - for(uint32_t i_dim=0; i_dim < nObs; i_dim++) - { //fill the observation of v_attribute - v_attribute.observation[i_dim] = observation[ind_nod][i_dim]; - }//and its weight - v_attribute.weight = nodeWeight[ind_nod]; - //set the attributes of the current node - vertex_attribute_map[*ite_nod++] = v_attribute; - } - //--------build the edges----------------------------------------------- - EdgeAttributeMap edge_attribute_map = boost::get(boost::edge_bundle - , cp->main_graph); - uint32_t true_ind_edg = 0; //this index count the number of edges ACTUALLY added - for( uint32_t ind_edg = 0; ind_edg < n_edges; ind_edg++ ) - { //add edges in each direction - if (addDoubledge(cp->main_graph, boost::vertex(Eu[ind_edg] - , cp->main_graph), boost::vertex(Ev[ind_edg] - , cp->main_graph), edgeWeight[ind_edg], true_ind_edg - , edge_attribute_map)) - { - true_ind_edg += 2; - } - - } -} - -//=========================================================================== -//===================== SET SPEED =================================== -//=========================================================================== -template -void set_speed(CP::CutPursuit * cp, const T speed, const T weight_decay, const float verbose) -{ - if (speed == 4) - { - if (verbose > 0) - { - std::cout << "PARAMETERIZATION = SPECIAL SUPERPOINTGRAPH" << std::endl; - } - cp->parameter.flow_steps = 3; - cp->parameter.weight_decay = weight_decay; - cp->parameter.kmeans_ite = 5; - cp->parameter.kmeans_resampling = 10; - cp->parameter.max_ite_main = 15; - cp->parameter.backward_step = true; - cp->parameter.stopping_ratio = 0.05; - } - if (speed == 3) - { - if (verbose > 0) - { - std::cout << "PARAMETERIZATION = LUDICROUS SPEED" << std::endl; - } - cp->parameter.flow_steps = 1; - cp->parameter.weight_decay = weight_decay; - cp->parameter.kmeans_ite = 3; - cp->parameter.kmeans_resampling = 1; - cp->parameter.max_ite_main = 5; - cp->parameter.backward_step = false; - cp->parameter.stopping_ratio = 0.1; - } - if (speed == 2) - { - if (verbose > 0) - { - std::cout << "PARAMETERIZATION = FAST" << std::endl; - } - cp->parameter.flow_steps = 2; - cp->parameter.weight_decay = weight_decay; - cp->parameter.kmeans_ite = 5; - cp->parameter.kmeans_resampling = 2; - cp->parameter.max_ite_main = 5; - cp->parameter.backward_step = true; - cp->parameter.stopping_ratio = 0.05; - } - else if (speed == 0) - { - if (verbose > 0) - { - std::cout << "PARAMETERIZATION = SLOW" << std::endl; - } - cp->parameter.flow_steps = 4; - cp->parameter.weight_decay = weight_decay; - cp->parameter.kmeans_ite = 8; - cp->parameter.kmeans_resampling = 5; - cp->parameter.max_ite_main = 20; - cp->parameter.backward_step = true; - cp->parameter.stopping_ratio = 0.001; - } - else if (speed == 1) - { - if (verbose > 0) - { - std::cout << "PARAMETERIZATION = STANDARD" << std::endl; - } - cp->parameter.flow_steps = 3; - cp->parameter.weight_decay = weight_decay; - cp->parameter.kmeans_ite = 5; - cp->parameter.kmeans_resampling = 2; - cp->parameter.max_ite_main = 10; - cp->parameter.backward_step = true; - cp->parameter.stopping_ratio = 0.01; - } -} -} diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt index cae8806..bf2bd94 100644 --- a/include/CMakeLists.txt +++ b/include/CMakeLists.txt @@ -1,20 +1,17 @@ target_sources( ${PROJECT_NAME} PRIVATE - ${CMAKE_CURRENT_LIST_DIR}/API.h ${CMAKE_CURRENT_LIST_DIR}/ccTreeIsoDlg.h - ${CMAKE_CURRENT_LIST_DIR}/Common.h - ${CMAKE_CURRENT_LIST_DIR}/CutPursuit.h - ${CMAKE_CURRENT_LIST_DIR}/CutPursuit_KL.h - ${CMAKE_CURRENT_LIST_DIR}/CutPursuit_L2.h - ${CMAKE_CURRENT_LIST_DIR}/CutPursuit_Linear.h - ${CMAKE_CURRENT_LIST_DIR}/CutPursuit_SPG.h - ${CMAKE_CURRENT_LIST_DIR}/Graph.h + ${CMAKE_CURRENT_LIST_DIR}/block.hpp + ${CMAKE_CURRENT_LIST_DIR}/maxflow.hpp + ${CMAKE_CURRENT_LIST_DIR}/cp_d0_dist.hpp + ${CMAKE_CURRENT_LIST_DIR}/cut_pursuit_d0.hpp + ${CMAKE_CURRENT_LIST_DIR}/cut_pursuit.hpp ${CMAKE_CURRENT_LIST_DIR}/knncpp.h ${CMAKE_CURRENT_LIST_DIR}/qTreeIso.h ${CMAKE_CURRENT_LIST_DIR}/qTreeIsoCommands.h ${CMAKE_CURRENT_LIST_DIR}/TreeIso.h - ${CMAKE_CURRENT_LIST_DIR}/TreeIsoHelper.h + ${CMAKE_CURRENT_LIST_DIR}/TreeIsoHelper.hpp ) target_include_directories( ${PROJECT_NAME} diff --git a/include/Common.h b/include/Common.h deleted file mode 100644 index 2454a88..0000000 --- a/include/Common.h +++ /dev/null @@ -1,147 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -namespace patch -{ - template < typename T > std::string to_string(const T& n) - { - std::ostringstream stm; - stm << n; - return stm.str(); - } -} - -enum fidelityType { L2, linear, KL, SPG }; - -struct GenericParameter -{ - GenericParameter(std::string inName = "in_name", double reg_strength = 0, double fidelity = 0) - { - this->in_name = inName; - char* buffer = new char[inName.size() + 10]; - //char buffer [inName.size() + 10]; - std::string extension = inName.substr(inName.find_last_of(".") + 1); - this->extension = extension; - std::string baseName = inName.substr(0, inName.size() - extension.size() - 1); - this->base_name = baseName; - sprintf(buffer, "%s_out_%1.0f_%.0f.%s", baseName.c_str(), fidelity, reg_strength * 1000, extension.c_str()); - this->out_name = std::string(buffer); - this->natureOfData = 0; - this->fidelity = L2; - } - - std::string in_name, out_name, base_name, extension; - int natureOfData; - fidelityType fidelity; -}; - -class TimeStack -{ -public: - TimeStack() - : lastTime{} - {} - - void tic() - { - lastTime = clock(); - } - - std::string toc() const - { - std::ostringstream stm; - stm << static_cast(clock() - lastTime) / CLOCKS_PER_SEC; - return stm.str(); - } - - double tocDouble() const - { - double x = static_cast(clock() - lastTime) / CLOCKS_PER_SEC; - return x; - } - -protected: - clock_t lastTime; - -}; - -template -struct ComponentsFusion -{//this class encode a potential fusion between two cadjacent component - //and is ordered wrt the merge_gain - ComponentsFusion(std::size_t c1, std::size_t c2, std::size_t ind = 0, T gain = 0.) - { - this->comp1 = c1; - this->comp2 = c2; - this->border_index = ind; - this->merge_gain = gain; - } - - std::size_t comp1, comp2; //index of the components - std::size_t border_index; //index of the border-edge - T merge_gain; //gain obtained by mergeing the components - std::vector merged_value; //value of the new components when they are merged -}; - -template -struct lessComponentsFusion: public std::binary_function, ComponentsFusion, bool> -{ - bool operator()(const ComponentsFusion lhs, const ComponentsFusion rhs) const - { - return lhs.merge_gain < rhs.merge_gain; - } -}; - - -template -class VectorOfCentroids -{ - //VectorOfCentroids is a vector of size k x 2 x d where k is the number of components and - // d the dimension of the observation -public: - std::vector< std::vector< std::vector > > centroids; - VectorOfCentroids(std::size_t nb_comp, std::size_t dim) - { - this->centroids = std::vector< std::vector< std::vector > >(nb_comp, - std::vector< std::vector >(2, std::vector(dim, 0.0))); - } -}; -template -class Point3D -{ -public: - T x,y,z; - Point3D(T x = 0., T y = 0., T z = 0.) - { - this->x = x; - this->y = y; - this->z = z; - } -}; - -template -struct lessPoint3D: public std::binary_function, Point3D, bool> -{ - bool operator()(const Point3D lhs, const Point3D rhs) const - { - if (lhs.x != rhs.x) - { - return lhs.x < rhs.x; - } - if (lhs.y != rhs.y) - { - return lhs.y < rhs.y; - } - if (lhs.z > rhs.z) - { - return lhs.z < rhs.z; - } - return true; - } -}; diff --git a/include/CutPursuit.h b/include/CutPursuit.h deleted file mode 100644 index ef60cdb..0000000 --- a/include/CutPursuit.h +++ /dev/null @@ -1,708 +0,0 @@ -#pragma once - -//Local -#include "Graph.h" - -//System -#include -#include -#include -#include - -//Boost -#include - -namespace CP -{ - template - struct CPparameter - { - T reg_strenth; //regularization strength, multiply the edge weight - uint32_t cutoff; //minimal component size - uint32_t flow_steps; //number of steps in the optimal binary cut computation - uint32_t kmeans_ite; //number of iteration in the kmeans sampling - uint32_t kmeans_resampling; //number of kmeans re-intilialization - uint32_t verbose; //verbosity - uint32_t max_ite_main; //max number of iterations in the main loop - bool backward_step; //indicates if a backward step should be performed - double stopping_ratio; //when (E(t-1) - E(t) / (E(0) - E(t)) is too small, the algorithm stops - fidelityType fidelity; //the fidelity function - double smoothing; //smoothing term (for Kl divergence only) - bool parallel; //enable/disable parrallelism - T weight_decay; //for continued optimization of the flow steps - }; - - template - struct CutPursuit - { - Graph main_graph; //the Graph structure containing the main structure - Graph reduced_graph; //the reduced graph whose vertices are the connected component - std::vector>> components; //contains the list of the vertices in each component - std::vector> root_vertex; //the root vertex for each connected components - std::vector saturated_components; //is the component saturated (uncuttable) - std::vector> borders; //the list of edges forming the borders between the connected components - VertexDescriptor source; //source vertex for graph cut - VertexDescriptor sink; //sink vertex - uint32_t dim; // dimension of the data - uint32_t nVertex; // number of data point - uint32_t nEdge; // number of edges between vertices (not counting the edge to source/sink) - CP::VertexIterator lastIterator; //iterator pointing to the last vertex which is neither sink nor source - CPparameter parameter; - - CutPursuit(uint32_t nbVertex = 1) - { - this->main_graph = Graph(nbVertex); - this->reduced_graph = Graph(1); - this->components = std::vector>>(1); - this->root_vertex = std::vector>(1, 0); - this->saturated_components = std::vector(1, false); - this->source = VertexDescriptor(); - this->sink = VertexDescriptor(); - this->dim = 1; - this->nVertex = 1; - this->nEdge = 0; - this->parameter.reg_strenth = 0; - this->parameter.cutoff = 0; - this->parameter.flow_steps = 3; - this->parameter.kmeans_ite = 5; - this->parameter.kmeans_resampling = 3; - this->parameter.verbose = 2; - this->parameter.max_ite_main = 6; - this->parameter.backward_step = true; - this->parameter.stopping_ratio = 0.0001; - this->parameter.fidelity = L2; - this->parameter.smoothing = 0.1; - this->parameter.parallel = true; - this->parameter.weight_decay = static_cast(0.7); - } - - //============================================================================================= - std::pair, std::vector> run() - { - //first initilialize the structure - this->initialize(); - if (this->parameter.verbose > 0) - { - std::cout << "Graph " << boost::num_vertices(this->main_graph) << " vertices and " - << boost::num_edges(this->main_graph) << " edges and observation of dimension " - << this->dim << '\n'; - } - T energy_zero = this->compute_energy().first; //energy with 1 component - T old_energy = energy_zero; //energy at the previous iteration - //vector with time and energy, useful for benchmarking - std::vector energy_out(this->parameter.max_ite_main), time_out(this->parameter.max_ite_main); - TimeStack ts; ts.tic(); - //the main loop - for (uint32_t ite_main = 1; ite_main <= this->parameter.max_ite_main; ite_main++) - { - //--------those two lines are the whole iteration------------------------- - size_t saturation = this->split(); //compute optimal binary partition - this->reduce(); //compute the new reduced graph - //-------end of the iteration - rest is stopping check and display------ - std::pair energy = this->compute_energy(); - energy_out.push_back((energy.first + energy.second)); - time_out.push_back(ts.tocDouble()); - if (this->parameter.verbose > 1) - { - printf("Iteration %3i - %4i components - ", ite_main, static_cast(this->components.size())); - printf("Saturation %5.1f %% - ", (100.0 * saturation) / this->nVertex); - switch (this->parameter.fidelity) - { - case L2: - { - printf("Quadratic Energy %4.3f %% - ", 100 * (energy.first + energy.second) / energy_zero); - break; - } - case linear: - { - printf("Linear Energy %10.1f - ", energy.first + energy.second); - break; - } - case KL: - { - printf("KL Energy %4.3f %% - ", 100 * (energy.first + energy.second) / energy_zero); - break; - } - case SPG: - { - printf("Quadratic Energy %4.3f %% - ", 100 * (energy.first + energy.second) / energy_zero); - break; - } - } - std::cout << "Timer " << ts.toc() << std::endl; - } - //----stopping checks----- - if (saturation == this->nVertex) - { //all components are saturated - if (this->parameter.verbose > 1) - { - std::cout << "All components are saturated" << std::endl; - } - break; - } - if ((old_energy - energy.first - energy.second) / (old_energy) - < this->parameter.stopping_ratio) - { //relative energy progress stopping criterion - if (this->parameter.verbose > 1) - { - std::cout << "Stopping criterion reached" << std::endl; - } - break; - } - if (ite_main >= this->parameter.max_ite_main) - { //max number of iteration - if (this->parameter.verbose > 1) - { - std::cout << "Max number of iteration reached" << std::endl; - } - break; - } - old_energy = energy.first + energy.second; - } - if (this->parameter.cutoff > 0) - { - this->cutoff(); - } - return std::pair, std::vector>(energy_out, time_out); - } - - //============================================================================================= - //=========== VIRTUAL METHODS DEPENDING ON THE CHOICE OF FIDELITY FUNCTION ===================== - //============================================================================================= - // - //============================================================================================= - //============================= SPLIT =========================================== - //============================================================================================= - virtual size_t split() - { - //compute the optimal binary partition - return 0; - } - - //============================================================================================= - //================================ compute_energy_L2 ==================================== - //============================================================================================= - virtual std::pair compute_energy() - { - //compute the current energy - return std::pair(0, 0); - } - - //============================================================================================= - //================================= COMPUTE_VALUE ========================================= - //============================================================================================= - virtual std::pair, T> compute_value(const uint32_t & ind_com) - { - //compute the optimal the values associated with the current partition - return std::pair, T>(std::vector(0), 0); - } - - //============================================================================================= - //================================= COMPUTE_MERGE_GAIN ========================================= - //============================================================================================= - virtual std::pair, T> compute_merge_gain(const VertexDescriptor & comp1 - , const VertexDescriptor & comp2) - { - //compute the gain of mergeing two connected components - return std::pair, T>(std::vector(0), 0); - } - - //============================================================================================= - //========================== END OF VIRTUAL METHODS =========================================== - //============================================================================================= - - //============================================================================================= - //============================= INITIALIZE =========================================== - //============================================================================================= - void initialize() - { - //build the reduced graph with one component, fill the first vector of components - //and add the sink and source nodes - VertexIterator ite_ver, ite_ver_end; - EdgeAttributeMap edge_attribute_map = boost::get(boost::edge_bundle, this->main_graph); - this->components[0] = std::vector>(0);//(this->nVertex); - this->root_vertex[0] = *boost::vertices(this->main_graph).first; - this->nVertex = static_cast(boost::num_vertices(this->main_graph)); - this->nEdge = static_cast(boost::num_edges(this->main_graph)); - //--------compute the first reduced graph---------------------------------------------------------- - for (boost::tie(ite_ver, ite_ver_end) = boost::vertices(this->main_graph); - ite_ver != ite_ver_end; ++ite_ver) - { - this->components[0].push_back(*ite_ver); - } - this->lastIterator = ite_ver; - this->compute_value(0); - //--------build the link to source and sink-------------------------------------------------------- - this->source = boost::add_vertex(this->main_graph); - this->sink = boost::add_vertex(this->main_graph); - uint32_t eIndex = static_cast(boost::num_edges(this->main_graph)); - ite_ver = boost::vertices(this->main_graph).first; - for (uint32_t ind_ver = 0; ind_ver < this->nVertex; ind_ver++) - { - // note that source and edge will have many nieghbors, and hence boost::edge should never be called to get - // the in_edge. use the out_edge and then reverse_Edge - addDoubledge(this->main_graph, this->source, boost::vertex(ind_ver, this->main_graph), 0., - eIndex, edge_attribute_map, false); - eIndex += 2; - addDoubledge(this->main_graph, boost::vertex(ind_ver, this->main_graph), this->sink, 0., - eIndex, edge_attribute_map, false); - eIndex += 2; - ++ite_ver; - } - - } - - //============================================================================================= - //================================ COMPUTE_REDUCE_VALUE ==================================== - //============================================================================================= - void compute_reduced_value() - { - for (uint32_t ind_com = 0; ind_com < this->components.size(); ++ind_com) - { //compute the reduced value of each component - compute_value(ind_com); - } - } - - //============================================================================================= - //============================= ACTIVATE_EDGES ========================================== - //============================================================================================= - size_t activate_edges(bool allows_saturation = true) - { //this function analyzes the optimal binary partition to detect: - //- saturated components (i.e. uncuttable) - //- new activated edges - VertexAttributeMap vertex_attribute_map = boost::get(boost::vertex_bundle, this->main_graph); - EdgeAttributeMap edge_attribute_map = boost::get(boost::edge_bundle, this->main_graph); - //saturation is the proportion of nodes in saturated components - size_t saturation = 0; - uint32_t nb_comp = static_cast(this->components.size()); - //---- first check if the component are saturated------------------------- - //#pragma omp parallel for if (this->parameter.parallel) schedule(dynamic) - for (uint32_t ind_com = 0; ind_com < nb_comp; ind_com++) - { - if (this->saturated_components[ind_com]) - { //ind_com is saturated, we increement saturation by ind_com size - saturation += this->components[ind_com].size(); - continue; - } - std::vector totalWeight(2, 0); - for (uint32_t ind_ver = 0; ind_ver < this->components[ind_com].size(); ind_ver++) - { - bool isSink - = (vertex_attribute_map(this->components[ind_com][ind_ver]).color - == vertex_attribute_map(this->sink).color); - if (isSink) - { - totalWeight[0] += vertex_attribute_map(this->components[ind_com][ind_ver]).weight; - } - else - { - totalWeight[1] += vertex_attribute_map(this->components[ind_com][ind_ver]).weight; - } - } - if (allows_saturation && ((totalWeight[0] == 0) || (totalWeight[1] == 0))) - { - //the component is saturated - this->saturateComponent(ind_com); - saturation += this->components[ind_com].size(); - } - } - //----check which edges have been activated---- - EdgeIterator ite_edg, ite_edg_end; - uint32_t color_v1, color_v2, color_combination; - for (boost::tie(ite_edg, ite_edg_end) = boost::edges(this->main_graph); - ite_edg != ite_edg_end; ++ite_edg) - { - if (!edge_attribute_map(*ite_edg).realEdge) - { - continue; - } - color_v1 = vertex_attribute_map(boost::source(*ite_edg, this->main_graph)).color; - color_v2 = vertex_attribute_map(boost::target(*ite_edg, this->main_graph)).color; - //color_source = 0, color_sink = 4, uncolored = 1 - //we want an edge when a an interface source/sink - //this corresponds to a sum of 4 - //for the case of uncolored nodes we arbitrarily chose source-uncolored - color_combination = color_v1 + color_v2; - if ((color_combination == 0) || (color_combination == 2) || (color_combination == 2) - || (color_combination == 8)) - { //edge between two vertices of the same color - continue; - } - //the edge is active! - edge_attribute_map(*ite_edg).isActive = true; - edge_attribute_map(*ite_edg).capacity = 0; - vertex_attribute_map(boost::source(*ite_edg, this->main_graph)).isBorder = true; - vertex_attribute_map(boost::target(*ite_edg, this->main_graph)).isBorder = true; - } - return saturation; - } - - //============================================================================================= - //============================= REDUCE =========================================== - //============================================================================================= - void reduce() - { //compute the reduced graph, and if need be performed a backward check - this->compute_connected_components(); - if (this->parameter.backward_step) - { //compute the structure of the reduced graph - this->compute_reduced_graph(); - //check for beneficial merges - this->merge(false); - } - else - { //compute only the value associated to each connected components - this->compute_reduced_value(); - } - } - - //============================================================================================= - //============================== compute_connected_components========================================= - //============================================================================================= - void compute_connected_components() - { //this function compute the connected components of the graph with active edges removed - //the boolean vector indicating wether or not the edges and vertices have been seen already - //the root is the first vertex of a component - //this function is written such that the new components are appended at the end of components - //this allows not to recompute saturated component - VertexAttributeMap vertex_attribute_map = boost::get(boost::vertex_bundle, this->main_graph); - VertexIndexMap vertex_index_map = get(boost::vertex_index, this->main_graph); - //indicate which edges and nodes have been seen already by the dpsearch - std::vector edges_seen(this->nEdge, false); - std::vector vertices_seen(this->nVertex + 2, false); - vertices_seen[vertex_index_map(this->source)] = true; - vertices_seen[vertex_index_map(this->sink)] = true; - //-------- start with the known roots------------------------------------------------------ - //#pragma omp parallel for if (this->parameter.parallel) schedule(dynamic) - for (uint32_t ind_com = 0; ind_com < this->root_vertex.size(); ind_com++) - { - VertexDescriptor root = this->root_vertex[ind_com]; //the first vertex of the component - if (this->saturated_components[ind_com]) - { //this component is saturated, we don't need to recompute it - for (uint32_t ind_ver = 0; ind_ver < this->components[ind_com].size(); ++ind_ver) - { - vertices_seen[vertex_index_map(this->components[ind_com][ind_ver])] = true; - } - } - else - { //compute the new content of this component - this->components.at(ind_com) = connected_comp_from_root(root, this->components.at(ind_com).size(), vertices_seen, edges_seen); - } - } - //----now look for components that did not already exists---- - VertexIterator ite_ver; - for (ite_ver = boost::vertices(this->main_graph).first; - ite_ver != this->lastIterator; ++ite_ver) - { - if (vertices_seen[vertex_index_map(*ite_ver)]) - { - continue; - } //this vertex is not currently in a connected component - VertexDescriptor root = *ite_ver; //we define it as the root of a new component - size_t current_component_size = this->components[vertex_attribute_map(root).in_component].size(); - this->components.push_back(connected_comp_from_root(root, current_component_size, vertices_seen, edges_seen)); - this->root_vertex.push_back(root); - this->saturated_components.push_back(false); - } - this->components.shrink_to_fit(); - } - - //============================================================================================= - //============================== CONNECTED_COMP_FROM_ROOT========================================= - //============================================================================================= - inline std::vector> connected_comp_from_root(const VertexDescriptor& root - , const size_t& size_comp, std::vector& vertices_seen, std::vector& edges_seen) - { - //this function compute the connected component of the graph with active edges removed - // associated with the root ROOT by performing a depth search first - EdgeAttributeMap edge_attribute_map = boost::get(boost::edge_bundle, this->main_graph); - VertexIndexMap vertex_index_map = get(boost::vertex_index, this->main_graph); - EdgeIndexMap edge_index_map = get(&EdgeAttribute::index, this->main_graph); - std::vector> vertices_added; //the vertices in the current connected component - // vertices_added contains the vertices that have been added to the current coomponent - vertices_added.reserve(size_comp); - //heap_explore contains the vertices to be added to the current component - std::vector> vertices_to_add; - vertices_to_add.reserve(size_comp); - VertexDescriptor vertex_current; //the node being consideed - EdgeDescriptor edge_current, edge_reverse; //the edge being considered - //fill the heap with the root node - vertices_to_add.push_back(root); - while (vertices_to_add.size() > 0) - { //as long as there are vertices left to add - vertex_current = vertices_to_add.back(); //the current node is the last node to add - vertices_to_add.pop_back(); //remove the current node from the vertices to add - if (vertices_seen[vertex_index_map(vertex_current)]) - { //this vertex has already been treated - continue; - } - vertices_added.push_back(vertex_current); //we add the current node - vertices_seen[vertex_index_map(vertex_current)] = true; //and flag it as seen - //----we now explore the neighbors of current_node - typename boost::graph_traits>::out_edge_iterator ite_edg, ite_edg_end; - for (boost::tie(ite_edg, ite_edg_end) = boost::out_edges(vertex_current, this->main_graph); - ite_edg != ite_edg_end; ++ite_edg) - { //explore edges leaving current_node - edge_current = *ite_edg; - if (edge_attribute_map(*ite_edg).isActive || (edges_seen[edge_index_map(edge_current)])) - { //edge is either active or treated, we skip it - continue; - } - //the target of this edge is a node to add - edge_reverse = edge_attribute_map(edge_current).edge_reverse; - edges_seen[edge_index_map(edge_current)] = true; - edges_seen[edge_index_map(edge_reverse)] = true; - vertices_to_add.push_back(boost::target(edge_current, this->main_graph)); - } - } - vertices_added.shrink_to_fit(); - return vertices_added; - } - - //============================================================================================= - //================================ COMPUTE_REDUCE_GRAPH ==================================== - //============================================================================================= - void compute_reduced_graph() - { //compute the adjacency structure between components as well as weight and value of each component - //this is stored in the reduced graph structure - EdgeAttributeMap edge_attribute_map = boost::get(boost::edge_bundle, this->main_graph); - VertexAttributeMap vertex_attribute_map = boost::get(boost::vertex_bundle, this->main_graph); - this->reduced_graph = Graph(this->components.size()); - VertexAttributeMap component_attribute_map = boost::get(boost::vertex_bundle, this->reduced_graph); - //----fill the value sof the reduced graph---- -#ifdef OPENMP -#pragma omp parallel for schedule(dynamic) -#endif - for (uint32_t ind_com = 0; ind_com < this->components.size(); ind_com++) - { - std::pair, T> component_values_and_weight = this->compute_value(ind_com); - //----fill the value and weight field of the reduced graph----------------------------- - VertexDescriptor reduced_vertex = boost::vertex(ind_com, this->reduced_graph); - component_attribute_map[reduced_vertex] = VertexAttribute(this->dim); - component_attribute_map(reduced_vertex).weight - = component_values_and_weight.second; - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - component_attribute_map(reduced_vertex).value[i_dim] = component_values_and_weight.first[i_dim]; - } - } - //------compute the edges of the reduced graph - EdgeAttributeMap border_edge_attribute_map = boost::get(boost::edge_bundle, this->reduced_graph); - this->borders.clear(); - EdgeDescriptor edge_current, border_edge_current; - uint32_t ind_border_edge = 0, comp1, comp2, component_source, component_target; - VertexDescriptor source_component, target_component; - bool reducedEdgeExists; - typename boost::graph_traits>::edge_iterator ite_edg, ite_edg_end; - for (boost::tie(ite_edg, ite_edg_end) = boost::edges(this->main_graph); ite_edg != ite_edg_end; ++ite_edg) - { - if (!edge_attribute_map(*ite_edg).realEdge) - { //edges linking the source or edge node do not take part - continue; - } - edge_current = *ite_edg; - //compute the connected components of the source and target of current_edge - comp1 = vertex_attribute_map(boost::source(edge_current, this->main_graph)).in_component; - comp2 = vertex_attribute_map(boost::target(edge_current, this->main_graph)).in_component; - if (comp1 == comp2) - { //this edge links two nodes in the same connected component - continue; - } - //by convention we note component_source the smallest index and - //component_target the largest - component_source = std::min(comp1, comp2); - component_target = std::max(comp1, comp2); - //retrieve the corresponding vertex in the reduced graph - source_component = boost::vertex(component_source, this->reduced_graph); - target_component = boost::vertex(component_target, this->reduced_graph); - //try to add the border-edge linking those components in the reduced graph - boost::tie(border_edge_current, reducedEdgeExists) - = boost::edge(source_component, target_component, this->reduced_graph); - if (!reducedEdgeExists) - { //this border-edge did not already existed in the reduced graph - //border_edge_current = boost::add_edge(source_component, target_component, this->reduced_graph).first; - border_edge_current = boost::add_edge(source_component, target_component, this->reduced_graph).first; - border_edge_attribute_map(border_edge_current).index = ind_border_edge; - border_edge_attribute_map(border_edge_current).weight = 0; - ind_border_edge++; - //create a new entry for the borders list containing this border - this->borders.push_back(std::vector(0)); - } - //add the weight of the current edge to the weight of the border-edge - border_edge_attribute_map(border_edge_current).weight += 0.5*edge_attribute_map(edge_current).weight; - this->borders[border_edge_attribute_map(border_edge_current).index].push_back(edge_current); - } - } - - //============================================================================================= - //================================ MERGE ==================================== - //============================================================================================= - uint32_t merge(bool is_cutoff) - { - // TODO: right now we only do one loop through the heap of potential mergeing, and only - //authorize one mergeing per component. We could update the gain and merge until it is no longer - //beneficial - //check wether the energy can be decreased by removing edges from the reduced graph - //----load graph structure--- - VertexAttributeMap vertex_attribute_map = boost::get(boost::vertex_bundle, this->main_graph); - VertexAttributeMap component_attribute_map = boost::get(boost::vertex_bundle, this->reduced_graph); - EdgeAttributeMap border_edge_attribute_map = boost::get(boost::edge_bundle, this->reduced_graph); - EdgeAttributeMap edge_attribute_map = boost::get(boost::edge_bundle, this->main_graph); - VertexIndexMap component_index_map = boost::get(boost::vertex_index, this->reduced_graph); - //----------------------------------- - EdgeDescriptor border_edge_current; - typename boost::graph_traits>::edge_iterator ite_border, ite_border_end; - typename std::vector::iterator ite_border_edge; - VertexDescriptor source_component, target_component; - uint32_t ind_source_component, ind_target_component, border_edge_currentIndex; - //gain_current is the vector of gains associated with each mergeing move - //std::vector gain_current(boost::num_edges(this->reduced_graph)); - //we store in merge_queue the potential mergeing with a priority on the potential gain - std::priority_queue, std::vector>, lessComponentsFusion> merge_queue; - T gain; // the gain obtained by removing the border corresponding to the edge in the reduced graph - for (boost::tie(ite_border, ite_border_end) = boost::edges(this->reduced_graph); ite_border != ite_border_end; ++ite_border) - { - //a first pass go through all the edges in the reduced graph and compute the gain obtained by - //mergeing the corresponding vertices - border_edge_current = *ite_border; - border_edge_currentIndex = border_edge_attribute_map(border_edge_current).index; - //retrieve the two components corresponding to this border - source_component = boost::source(border_edge_current, this->reduced_graph); - target_component = boost::target(border_edge_current, this->reduced_graph); - if (is_cutoff && component_attribute_map(source_component).weight >= this->parameter.cutoff - &&component_attribute_map(target_component).weight >= this->parameter.cutoff) - { - continue; - } - ind_source_component = static_cast(component_index_map(source_component)); - ind_target_component = static_cast(component_index_map(target_component)); - //----now compute the gain of mergeing those two components----- - // compute the fidelity lost by mergeing the two connected components - std::pair, T> merge_gain = compute_merge_gain(source_component, target_component); - // the second part is due to the removing of the border - gain = merge_gain.second - + border_edge_attribute_map(border_edge_current).weight * this->parameter.reg_strenth; - //mergeing_information store the indexes of the components as well as the edge index and the gain - //in a structure ordered by the gain - ComponentsFusion mergeing_information(ind_source_component, ind_target_component, border_edge_currentIndex, gain); - mergeing_information.merged_value = merge_gain.first; - if (is_cutoff || gain > 0) - { //it is beneficial to merge those two components - //we add them to the merge_queue - merge_queue.push(mergeing_information); - //gain_current.at(border_edge_currentIndex) = gain; - } - } - uint32_t n_merged = 0; - //----go through the priority queue of merges and perform them as long as it is beneficial--- - //is_merged indicate which components no longer exists because they have been merged with a neighboring component - std::vector is_merged(this->components.size(), false); - //to_destroy indicates the components that are needed to be removed - std::vector to_destroy(this->components.size(), false); - while (merge_queue.size() > 0) - { //loop through the potential mergeing and accept the ones that decrease the energy - ComponentsFusion mergeing_information = merge_queue.top(); - if (!is_cutoff && mergeing_information.merge_gain <= 0) - { //no more mergeing provide a gain in energy - break; - } - merge_queue.pop(); - if (is_merged.at(mergeing_information.comp1) || (is_merged.at(mergeing_information.comp2))) - { - //at least one of the components have already been merged - continue; - } - n_merged++; - //---proceed with the fusion of comp1 and comp2---- - //add the vertices of comp2 to comp1 - this->components[mergeing_information.comp1].insert(this->components[mergeing_information.comp1].end() - , components[mergeing_information.comp2].begin(), this->components[mergeing_information.comp2].end()); - //if comp1 was saturated it might not be anymore - this->saturated_components[mergeing_information.comp1] = false; - //the new weight is the sum of both weights - component_attribute_map(mergeing_information.comp1).weight - += component_attribute_map(mergeing_information.comp2).weight; - //the new value is already computed in mergeing_information - component_attribute_map(mergeing_information.comp1).value = mergeing_information.merged_value; - //we deactivate the border between comp1 and comp2 - for (ite_border_edge = this->borders.at(mergeing_information.border_index).begin(); - ite_border_edge != this->borders.at(mergeing_information.border_index).end(); ++ite_border_edge) - { - edge_attribute_map(*ite_border_edge).isActive = false; - } - is_merged.at(mergeing_information.comp1) = true; - is_merged.at(mergeing_information.comp2) = true; - to_destroy.at(mergeing_information.comp2) = true; - } - //we now rebuild the vectors components, rootComponents and saturated_components - std::vector>> new_components; - std::vector> new_root_vertex; - std::vector new_saturated_components; - uint32_t ind_new_component = 0; - for (uint32_t ind_com = 0; ind_com < this->components.size(); ind_com++) - { - if (to_destroy.at(ind_com)) - { //this component has been removed - continue; - }//this components is kept - new_components.push_back(this->components.at(ind_com)); - new_root_vertex.push_back(this->root_vertex.at(ind_com)); - new_saturated_components.push_back(saturated_components.at(ind_com)); - //if (is_merged.at(ind_com)) - //{ //we need to update the value of the vertex in this component - for (uint32_t ind_ver = 0; ind_ver < this->components[ind_com].size(); ++ind_ver) - { - vertex_attribute_map(this->components[ind_com][ind_ver]).value - = component_attribute_map(boost::vertex(ind_com, this->reduced_graph)).value; - vertex_attribute_map(this->components[ind_com][ind_ver]).in_component - = ind_new_component;//ind_com; - } - //} - ind_new_component++; - } - this->components = new_components; - this->root_vertex = new_root_vertex; - this->saturated_components = new_saturated_components; - return n_merged; - } - - //============================================================================================= - //================================ CUTOFF ==================================== - //============================================================================================= - void cutoff() - { - int i = 0; - uint32_t n_merged; - while (true) - { - //this->compute_connected_components(); - this->compute_reduced_graph(); - n_merged = merge(true); - i++; - if (n_merged == 0 || i > 50) - { - break; - } - } - } - - //=============================================================================================== - //========================= saturateComponent =================================================== - //=============================================================================================== - inline void saturateComponent(const uint32_t & ind_com) - { //this component is uncuttable and needs to be removed from further graph-cuts - EdgeAttributeMap edge_attribute_map = boost::get(boost::edge_bundle, this->main_graph); - this->saturated_components[ind_com] = true; - for (uint32_t i_ver = 0; i_ver < this->components[ind_com].size(); i_ver++) - { - VertexDescriptor desc_v = this->components[ind_com][i_ver]; - // because of the adjacency structure NEVER access edge (source,v) directly! - EdgeDescriptor edg_ver2source = boost::edge(desc_v, this->source, this->main_graph).first; - EdgeDescriptor edg_source2ver = edge_attribute_map(edg_ver2source).edge_reverse; //use edge_reverse instead - EdgeDescriptor edg_sink2ver = boost::edge(desc_v, this->sink, this->main_graph).first; - // we set the capacities of edges to source and sink to zero - edge_attribute_map(edg_source2ver).capacity = 0.; - edge_attribute_map(edg_sink2ver).capacity = 0.; - } - } - }; -} diff --git a/include/CutPursuit_KL.h b/include/CutPursuit_KL.h deleted file mode 100644 index 0540e69..0000000 --- a/include/CutPursuit_KL.h +++ /dev/null @@ -1,553 +0,0 @@ -#pragma once -#include "Common.h" -#include "CutPursuit.h" - -namespace CP -{ - template - struct CutPursuit_KL : public CutPursuit - { - std::pair compute_energy() override - { - VertexAttributeMap vertex_attribute_map - = boost::get(boost::vertex_bundle, this->main_graph); - EdgeAttributeMap edge_attribute_map - = boost::get(boost::edge_bundle, this->main_graph); - std::pair pair_energy; - T energy = 0, smoothedObservation, smoothedValue; - //#pragma omp parallel if (this->parameter.parallel) - for (VertexIterator i_ver = boost::vertices(this->main_graph).first; - i_ver != this->lastIterator; ++i_ver) - { - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { //smoothing as a linear combination with the uniform probability - smoothedObservation = - this->parameter.smoothing / this->dim - + (1 - this->parameter.smoothing) - * vertex_attribute_map(*i_ver).observation[i_dim]; - smoothedValue = - this->parameter.smoothing / this->dim - + (1 - this->parameter.smoothing) - * vertex_attribute_map(*i_ver).value[i_dim]; - energy += smoothedObservation - * (log(smoothedObservation) - log(smoothedValue)) - * vertex_attribute_map(*i_ver).weight; - } - } - pair_energy.first = energy; - energy = 0; - EdgeIterator i_edg_end = boost::edges(this->main_graph).second; - for (EdgeIterator i_edg = boost::edges(this->main_graph).first; - i_edg != i_edg_end; ++i_edg) - { - if (!edge_attribute_map(*i_edg).realEdge) - { - continue; - } - energy += .5 * edge_attribute_map(*i_edg).isActive * this->parameter.reg_strenth - * edge_attribute_map(*i_edg).weight; - } - pair_energy.second = energy; - return pair_energy; - } - - //============================================================================================= - //============================= SPLIT =========================================== - //============================================================================================= - size_t split() override - { // split the graph by trying to find the best binary partition - // each components is split into B and notB - // for each components we associate the value h_1 and h_2 to vertices in B or notB - // the affectation as well as h_1 and h_2 are computed alternatively - //tic(); - //--------loading structures--------------------------------------------------------------- - TimeStack ts; ts.tic(); - uint32_t nb_comp = static_cast(this->components.size()); - VertexAttributeMap vertex_attribute_map - = boost::get(boost::vertex_bundle, this->main_graph); - VertexIndexMap vertex_index_map = boost::get(boost::vertex_index, this->main_graph); - //initialize h_1 and h_2 with kmeans - //stores wether each vertex is B or notB - std::vector binary_label(this->nVertex); - this->init_labels(binary_label); - VectorOfCentroids centers(nb_comp, this->dim); - //-----main loop---------------------------------------------------------------- - // the optimal flow is iteratively approximated - for (uint32_t i_step = 1; i_step <= this->parameter.flow_steps; i_step++) - { - //compute h_1 and h_2 - centers = VectorOfCentroids(nb_comp, this->dim); - this->compute_centers(centers, binary_label); - // update the capacities of the flow graph - this->set_capacities(centers); - //compute flow - boost::boykov_kolmogorov_max_flow( - this->main_graph, - get(&EdgeAttribute::capacity, this->main_graph), - get(&EdgeAttribute::residualCapacity, this->main_graph), - get(&EdgeAttribute::edge_reverse, this->main_graph), - get(&VertexAttribute::color, this->main_graph), - get(boost::vertex_index, this->main_graph), - this->source, - this->sink); - - for (uint32_t i_com = 0; i_com < nb_comp; i_com++) - { - if (this->saturated_components[i_com]) - { - continue; - } - for (uint32_t i_ver = 0; i_ver < this->components[i_com].size(); i_ver++) - { - binary_label[vertex_index_map(this->components[i_com][i_ver])] - = (vertex_attribute_map(this->components[i_com][i_ver]).color - == vertex_attribute_map(this->sink).color); - } - } - } - size_t saturation = this->activate_edges(); - return saturation; - } - - //============================================================================================= - //============================= INIT_KL =================================================== - //============================================================================================= - inline void init_labels(std::vector & binary_label) - { //-----initialize the labelling for each components with kmeans------------------------------ - VertexAttributeMap vertex_attribute_map - = boost::get(boost::vertex_bundle, this->main_graph); - VertexIndexMap vertex_index_map = boost::get(boost::vertex_index, this->main_graph); - std::vector< std::vector > kernels(2, std::vector(this->dim)); - std::vector< std::vector > smooth_kernels(2, std::vector(this->dim)); - T total_weight[2]; - uint32_t nb_comp = static_cast(this->components.size()); - T best_energy, current_energy; - //#pragma omp parallel for private(kernels, total_weight, best_energy, current_energy) if (this->parameter.parallel && nb_comp>8) schedule(dynamic) - for (uint32_t i_com = 0; i_com < nb_comp; i_com++) - { - uint32_t comp_size = static_cast(this->components[i_com].size()); - std::vector potential_label(comp_size); - std::vector energy_array(comp_size); - std::vector constant_part(comp_size); - std::vector< std::vector > smooth_obs(comp_size, std::vector(2, 0)); - if (this->saturated_components[i_com] || comp_size <= 1) - { - continue; - } - //KL fidelity has a part that depends - //purely on the observation that can be precomputed - //#pragma omp parallel for if (this->parameter.parallel && nb_comp<=8) schedule(dynamic) - for (uint32_t i_ver = 0; i_ver < comp_size; i_ver++) - { - constant_part[i_ver] = 0; - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - smooth_obs[i_ver][i_dim] = 0; - } - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - smooth_obs[i_ver][i_dim] = - this->parameter.smoothing / this->dim - + (1 - this->parameter.smoothing) - * vertex_attribute_map(this->components[i_com][i_ver]).observation[i_dim]; - constant_part[i_ver] += smooth_obs[i_ver][i_dim] - * log(smooth_obs[i_ver][i_dim]) - * vertex_attribute_map(this->components[i_com][i_ver]).weight; - } - } - for (uint32_t init_kmeans = 0; init_kmeans < this->parameter.kmeans_resampling; init_kmeans++) - { - //----- initialization with KM++ ------------------ - // first kernel chosen randomly - uint32_t first_kernel = std::rand() % comp_size, second_kernel = 0; - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { //fill the first kernel - kernels[0][i_dim] = vertex_attribute_map(this->components[i_com][first_kernel]).observation[i_dim]; - smooth_kernels[0][i_dim] = this->parameter.smoothing - / this->dim + (1 - this->parameter.smoothing) - * kernels[0][i_dim]; - } - //now compute the square distance of each pouint32_t to this kernel - best_energy = 0; //energy total - //#pragma omp parallel for if (this->parameter.parallel && nb_comp<=8) schedule(dynamic) - for (uint32_t i_ver = 0; i_ver < comp_size; i_ver++) - { - energy_array[i_ver] = constant_part[i_ver]; - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - energy_array[i_ver] -= - smooth_obs[i_ver][i_dim] - * log(smooth_kernels[0][i_dim]) - * vertex_attribute_map(this->components[i_com][i_ver]).weight; - } - energy_array[i_ver] = pow(energy_array[i_ver], 2); - best_energy += energy_array[i_ver]; - } // we now generate a random number to determinate which node will be the second kernel - if (best_energy == 0) - { //all the points in this components are identical - for (uint32_t i_ver = 0; i_ver < comp_size; i_ver++) - { - binary_label[vertex_index_map(this->components[i_com][i_ver])] = false; - } - break; - } - //we now choose the second kernel with a probability - //proportional to the square distance - T random_sample = ((T)(rand())) / ((T)(RAND_MAX)); - current_energy = best_energy * random_sample; - for (uint32_t i_ver = 0; i_ver < comp_size; i_ver++) - { - current_energy -= energy_array[i_ver]; - if (current_energy < 0) - { //we have selected the second kernel - second_kernel = i_ver; - break; - } - } - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { // now fill the second kernel - kernels[1][i_dim] = vertex_attribute_map(this->components[i_com][second_kernel]).observation[i_dim]; - smooth_kernels[1][i_dim] = this->parameter.smoothing - / this->dim + (1 - this->parameter.smoothing) - * kernels[1][i_dim]; - } - //----main kmeans loop----- - for (uint32_t ite_kmeans = 0; ite_kmeans < this->parameter.kmeans_ite; ite_kmeans++) - { - //--affectation step: associate each node with its closest kernel------------------- - //#pragma omp parallel for if (this->parameter.parallel && nb_comp<=8) schedule(dynamic) - for (uint32_t i_ver = 0; i_ver < comp_size; i_ver++) - { - //the distance to each kernel - std::vector distance_kernels(2, constant_part[i_ver]); - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - distance_kernels[0] -= smooth_obs[i_ver][i_dim] - * log(smooth_kernels[0][i_dim]) - * vertex_attribute_map(this->components[i_com][i_ver]).weight; - distance_kernels[1] -= smooth_obs[i_ver][i_dim] - * log(smooth_kernels[1][i_dim]) - * vertex_attribute_map(this->components[i_com][i_ver]).weight; - } - potential_label[i_ver] = distance_kernels[0] > distance_kernels[1]; - } - //-----computation of the new kernels---------------------------- - total_weight[0] = 0.; - total_weight[1] = 0.; - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - kernels[0][i_dim] = 0; - kernels[1][i_dim] = 0; - } - for (uint32_t i_ver = 0; i_ver < comp_size; i_ver++) - { - if (vertex_attribute_map(this->components[i_com][i_ver]).weight == 0) - { - continue; - } - if (potential_label[i_ver]) - { - total_weight[0] += vertex_attribute_map(this->components[i_com][i_ver]).weight; - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - kernels[0][i_dim] += - vertex_attribute_map(this->components[i_com][i_ver]).observation[i_dim] - * vertex_attribute_map(this->components[i_com][i_ver]).weight; - } - } - else - { - total_weight[1] += vertex_attribute_map(this->components[i_com][i_ver]).weight; - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - kernels[1][i_dim] += - vertex_attribute_map(this->components[i_com][i_ver]).observation[i_dim] - * vertex_attribute_map(this->components[i_com][i_ver]).weight; - } - } - } - if ((total_weight[0] == 0) || (total_weight[1] == 0)) - { - std::cout << "kmeans error" << std::endl; - } - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - kernels[0][i_dim] = kernels[0][i_dim] / total_weight[0]; - kernels[1][i_dim] = kernels[1][i_dim] / total_weight[1]; - smooth_kernels[0][i_dim] = this->parameter.smoothing - / this->dim + (1 - this->parameter.smoothing) - * kernels[0][i_dim]; - smooth_kernels[1][i_dim] = this->parameter.smoothing - / this->dim + (1 - this->parameter.smoothing) - * kernels[1][i_dim]; - } - } - //----compute the associated energy ------ - current_energy = 0; - for (uint32_t i_ver = 0; i_ver < comp_size; i_ver++) - { - current_energy += constant_part[i_ver]; - if (potential_label[i_ver]) - { - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - current_energy -= smooth_obs[i_ver][i_dim] - * log(smooth_kernels[0][i_dim]) - * vertex_attribute_map(this->components[i_com][i_ver]).weight; - } - } - else - { - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - current_energy -= smooth_obs[i_ver][i_dim] - * log(smooth_kernels[1][i_dim]) - * vertex_attribute_map(this->components[i_com][i_ver]).weight; - } - } - } - if (current_energy < best_energy) - { - best_energy = current_energy; - for (uint32_t i_ver = 0; i_ver < comp_size; i_ver++) - { - binary_label[vertex_index_map(this->components[i_com][i_ver])] = potential_label[i_ver]; - } - } - } - } - } - - //============================================================================================= - //============================= COMPUTE_CENTERS_KL ========================================== - //============================================================================================= - inline void compute_centers(VectorOfCentroids & centers, const std::vector & binary_label) - { - //compute for each component the values of h_1 and h_2 - VertexAttributeMap vertex_attribute_map = boost::get(boost::vertex_bundle, this->main_graph); - VertexIndexMap vertex_index_map = boost::get(boost::vertex_index, this->main_graph); - uint32_t nb_comp = static_cast(this->components.size()); - //#pragma omp parallel for if (this->parameter.parallel) schedule(dynamic) - for (uint32_t i_com = 0; i_com < nb_comp; i_com++) - { - if (this->saturated_components[i_com]) - { - continue; - } - T total_weight[2]; - total_weight[0] = 0.; - total_weight[1] = 0.; - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - centers.centroids[i_com][0][i_dim] = 0.; - centers.centroids[i_com][1][i_dim] = 0.; - } - for (uint32_t i_ver = 0; i_ver < this->components[i_com].size(); i_ver++) - { - if (vertex_attribute_map(this->components[i_com][i_ver]).weight == 0) - { - continue; - } - if (binary_label[vertex_index_map(this->components[i_com][i_ver])]) - { - total_weight[0] += vertex_attribute_map(this->components[i_com][i_ver]).weight; - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - centers.centroids[i_com][0][i_dim] += vertex_attribute_map(this->components[i_com][i_ver]).observation[i_dim] - * vertex_attribute_map(this->components[i_com][i_ver]).weight; - } - } - else - { - total_weight[1] += vertex_attribute_map(this->components[i_com][i_ver]).weight; - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - centers.centroids[i_com][1][i_dim] += vertex_attribute_map(this->components[i_com][i_ver]).observation[i_dim] - * vertex_attribute_map(this->components[i_com][i_ver]).weight; - } - } - } - if ((total_weight[0] == 0) || (total_weight[1] == 0)) - { - //the component is saturated - this->saturateComponent(i_com); - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - centers.centroids[i_com][0][i_dim] = vertex_attribute_map(this->components[i_com].back()).value[i_dim]; - centers.centroids[i_com][1][i_dim] = vertex_attribute_map(this->components[i_com].back()).value[i_dim]; - } - } - else - { - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - centers.centroids[i_com][0][i_dim] = centers.centroids[i_com][0][i_dim] / total_weight[0]; - centers.centroids[i_com][1][i_dim] = centers.centroids[i_com][1][i_dim] / total_weight[1]; - } - } - } - } - - //============================================================================================= - //============================= SET_CAPACITIES ========================================== - //============================================================================================= - inline void set_capacities(const VectorOfCentroids & centers) - { - VertexAttributeMap vertex_attribute_map = boost::get(boost::vertex_bundle, this->main_graph); - EdgeAttributeMap edge_attribute_map = boost::get(boost::edge_bundle, this->main_graph); - VertexDescriptor desc_v; - EdgeDescriptor desc_source2v, desc_v2sink, desc_v2source; - uint32_t nb_comp = static_cast(this->components.size()); - T cost_B, cost_notB, smoothedValueB, smoothedValueNotB, smoothedObservation; //the cost of being in B or not B, local for each component - //----first compute the capacity in sink/node edges------------------------------------ - //#pragma omp parallel for if (this->parameter.parallel) schedule(dynamic) - for (uint32_t i_com = 0; i_com < nb_comp; i_com++) - { - if (this->saturated_components[i_com]) - { - continue; - } - for (uint32_t i_ver = 0; i_ver < this->components[i_com].size(); i_ver++) - { - desc_v = this->components[i_com][i_ver]; - // because of the adjacency structure NEVER access edge (source,v) directly! - desc_v2source = boost::edge(desc_v, this->source, this->main_graph).first; - desc_source2v = edge_attribute_map(desc_v2source).edge_reverse; //use edge_reverse instead - desc_v2sink = boost::edge(desc_v, this->sink, this->main_graph).first; - cost_B = 0; - cost_notB = 0; - if (vertex_attribute_map(desc_v).weight == 0) - { - edge_attribute_map(desc_source2v).capacity = 0; - edge_attribute_map(desc_v2sink).capacity = 0; - continue; - } - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - smoothedObservation = - this->parameter.smoothing / this->dim - + (1 - this->parameter.smoothing) - * vertex_attribute_map(desc_v).observation[i_dim]; - smoothedValueB = - this->parameter.smoothing / this->dim - + (1 - this->parameter.smoothing) - * centers.centroids[i_com][0][i_dim]; - smoothedValueNotB = - this->parameter.smoothing / this->dim - + (1 - this->parameter.smoothing) - * centers.centroids[i_com][1][i_dim]; - cost_B += smoothedObservation - * (log(smoothedObservation) - - log(smoothedValueB)); - cost_notB += smoothedObservation - * (log(smoothedObservation) - - log(smoothedValueNotB)); - } - if (cost_B > cost_notB) - { - edge_attribute_map(desc_source2v).capacity = cost_B - cost_notB; - edge_attribute_map(desc_v2sink).capacity = 0.; - } - else - { - edge_attribute_map(desc_source2v).capacity = 0.; - edge_attribute_map(desc_v2sink).capacity = cost_notB - cost_B; - } - } - } - //----then set the vertex to vertex edges --------------------------------------------- - EdgeIterator i_edg, i_edg_end; - for (boost::tie(i_edg, i_edg_end) = boost::edges(this->main_graph); - i_edg != i_edg_end; ++i_edg) - { - if (!edge_attribute_map(*i_edg).realEdge) - { - continue; - } - if (!edge_attribute_map(*i_edg).isActive) - { - edge_attribute_map(*i_edg).capacity - = edge_attribute_map(*i_edg).weight * this->parameter.reg_strenth; - } - else - { - edge_attribute_map(*i_edg).capacity = 0; - } - } - } - - //============================================================================================= - //================================= COMPUTE_VALUE ========================================= - //============================================================================================= - std::pair, T> compute_value(const uint32_t & i_com) override - { - VertexAttributeMap vertex_attribute_map - = boost::get(boost::vertex_bundle, this->main_graph); - T total_weight = 0; - std::vector compValue(this->dim); - std::fill((compValue.begin()), (compValue.end()), 0); - for (uint32_t ind_ver = 0; ind_ver < this->components[i_com].size(); ++ind_ver) - { - total_weight += vertex_attribute_map(this->components[i_com][ind_ver]).weight; - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - compValue[i_dim] += vertex_attribute_map(this->components[i_com][ind_ver]).observation[i_dim] - * vertex_attribute_map(this->components[i_com][ind_ver]).weight; - } - vertex_attribute_map(this->components[i_com][ind_ver]).in_component = i_com; - } - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - compValue[i_dim] = compValue[i_dim] / total_weight; - } - for (uint32_t ind_ver = 0; ind_ver < this->components[i_com].size(); ++ind_ver) - { - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - vertex_attribute_map(this->components[i_com][ind_ver]).value[i_dim] = compValue[i_dim]; - } - } - return std::pair, T>(compValue, total_weight); - } - - //============================================================================================= - //================================= COMPUTE_MERGE_GAIN ========================================= - //============================================================================================= - std::pair, T> compute_merge_gain(const VertexDescriptor & comp1, const VertexDescriptor & comp2) override - { - VertexAttributeMap reduced_vertex_attribute_map = boost::get(boost::vertex_bundle, this->reduced_graph); - std::vector merge_value(this->dim); - T gain = 0, smoothedValue1, smoothedValue2, smoothedValueMerged; - // compute the value obtained by mergeing the two connected components - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - merge_value[i_dim] = - (reduced_vertex_attribute_map(comp1).weight * - reduced_vertex_attribute_map(comp1).value[i_dim] - + reduced_vertex_attribute_map(comp2).weight * - reduced_vertex_attribute_map(comp2).value[i_dim]) - / (reduced_vertex_attribute_map(comp1).weight - + reduced_vertex_attribute_map(comp2).weight); - smoothedValue1 = - this->parameter.smoothing / this->dim - + (1 - this->parameter.smoothing) - * reduced_vertex_attribute_map(comp1).value[i_dim]; - smoothedValue2 = - this->parameter.smoothing / this->dim - + (1 - this->parameter.smoothing) - * reduced_vertex_attribute_map(comp2).value[i_dim]; - smoothedValueMerged = - this->parameter.smoothing / this->dim - + (1 - this->parameter.smoothing) - * merge_value[i_dim]; - gain -= reduced_vertex_attribute_map(comp1).weight - * smoothedValue1 * (log(smoothedValue1) - - log(smoothedValueMerged)) - + reduced_vertex_attribute_map(comp2).weight - * smoothedValue2 * (log(smoothedValue2) - - log(smoothedValueMerged)); - } - return std::pair, T>(merge_value, gain); - } - }; -} diff --git a/include/CutPursuit_L2.h b/include/CutPursuit_L2.h deleted file mode 100644 index 8ce2815..0000000 --- a/include/CutPursuit_L2.h +++ /dev/null @@ -1,492 +0,0 @@ -#pragma once - -#include "CutPursuit.h" - -namespace CP -{ - template - struct CutPursuit_L2 : public CutPursuit - { - //============================================================================================= - //============================= COMPUTE ENERGY =========================================== - //============================================================================================= - std::pair compute_energy() override - { - VertexAttributeMap vertex_attribute_map = boost::get(boost::vertex_bundle, this->main_graph); - EdgeAttributeMap edge_attribute_map = boost::get(boost::edge_bundle, this->main_graph); - //the first element pair_energy of is the fidelity and the second the penalty - std::pair pair_energy; - T energy = 0; - //#pragma omp parallel for private(i_dim) if (this->parameter.parallel) schedule(static) reduction(+:energy,i) - for (uint32_t ind_ver = 0; ind_ver < this->nVertex; ind_ver++) - { - VertexDescriptor i_ver = boost::vertex(ind_ver, this->main_graph); - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - energy += .5*vertex_attribute_map(i_ver).weight - * pow(vertex_attribute_map(i_ver).observation[i_dim] - - vertex_attribute_map(i_ver).value[i_dim], 2); - } - } - pair_energy.first = energy; - energy = 0; - EdgeIterator i_edg, i_edg_end = boost::edges(this->main_graph).second; - for (i_edg = boost::edges(this->main_graph).first; i_edg != i_edg_end; ++i_edg) - { - if (!edge_attribute_map(*i_edg).realEdge) - { - continue; - } - energy += .5 * edge_attribute_map(*i_edg).isActive * this->parameter.reg_strenth - * edge_attribute_map(*i_edg).weight; - } - pair_energy.second = energy; - return pair_energy; - } - - //============================================================================================= - //============================= SPLIT =========================================== - //============================================================================================= - size_t split() override - { // split the graph by trying to find the best binary partition - // each components is split into B and notB - // for each components we associate the value h_1 and h_2 to vertices in B or notB - // the affectation as well as h_1 and h_2 are computed alternatively - //tic(); - //--------loading structures--------------------------------------------------------------- - uint32_t nb_comp = static_cast(this->components.size()); - VertexAttributeMap vertex_attribute_map = boost::get(boost::vertex_bundle, this->main_graph); - VertexIndexMap vertex_index_map = boost::get(boost::vertex_index, this->main_graph); - //stores wether each vertex is B or not - std::vector binary_label(this->nVertex); - //initialize the binary partition with kmeans - this->init_labels(binary_label); - //centers is the value of each binary component in the optimal partition - VectorOfCentroids centers(nb_comp, this->dim); - //-----main loop---------------------------------------------------------------- - // the optimal flow is iteratively approximated - for (uint32_t i_step = 1; i_step <= this->parameter.flow_steps; i_step++) - { - //the regularization strength at this step - //compute h_1 and h_2 - centers = VectorOfCentroids(nb_comp, this->dim); - this->compute_centers(centers, nb_comp, binary_label); - this->set_capacities(centers); - - // update the capacities of the flow graph - boost::boykov_kolmogorov_max_flow( - this->main_graph, - get(&EdgeAttribute::capacity, this->main_graph), - get(&EdgeAttribute::residualCapacity, this->main_graph), - get(&EdgeAttribute::edge_reverse, this->main_graph), - get(&VertexAttribute::color, this->main_graph), - get(boost::vertex_index, this->main_graph), - this->source, - this->sink); - - for (uint32_t ind_com = 0; ind_com < nb_comp; ind_com++) - { - if (this->saturated_components[ind_com]) - { - continue; - } - for (uint32_t i_ver = 0; i_ver < this->components[ind_com].size(); i_ver++) - { - binary_label[vertex_index_map(this->components[ind_com][i_ver])] - = (vertex_attribute_map(this->components[ind_com][i_ver]).color - == vertex_attribute_map(this->sink).color); - } - } - } - - size_t saturation = this->activate_edges(); - return saturation; - } - - //============================================================================================= - //============================= INIT_L2 ====== =========================================== - //============================================================================================= - inline void init_labels(std::vector & binary_label) - { //-----initialize the labelling for each components with kmeans------------------------------ - VertexAttributeMap vertex_attribute_map = boost::get(boost::vertex_bundle, this->main_graph); - VertexIndexMap vertex_index_map = boost::get(boost::vertex_index, this->main_graph); - uint32_t nb_comp = static_cast(this->components.size()); - - //#pragma omp parallel for private(ind_com) //if (nb_comp>=8) schedule(dynamic) -#ifdef OPENMP -#pragma omp parallel for if (nb_comp >= omp_get_num_threads()) schedule(dynamic) -#endif - for (uint32_t ind_com = 0; ind_com < nb_comp; ind_com++) - { - std::vector< std::vector > kernels(2, std::vector(this->dim)); - T total_weight[2]; - T best_energy; - T current_energy; - uint32_t comp_size = static_cast(this->components[ind_com].size()); - std::vector potential_label(comp_size); - std::vector energy_array(comp_size); - - if (this->saturated_components[ind_com] || comp_size <= 1) - { - continue; - } - for (uint32_t init_kmeans = 0; init_kmeans < this->parameter.kmeans_resampling; init_kmeans++) - {//proceed to several initilialisation of kmeans and pick up the best one - //----- initialization with KM++ ------------------ - uint32_t first_kernel = std::rand() % comp_size, second_kernel = 0; // first kernel attributed - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - kernels[0][i_dim] = vertex_attribute_map(this->components[ind_com][first_kernel]).observation[i_dim]; - } - best_energy = 0; //now compute the square distance of each pouint32_tto this kernel -#ifdef OPENMP -#pragma omp parallel for if (nb_comp < omp_get_num_threads()) shared(best_energy) schedule(static) -#endif - for (uint32_t i_ver = 0; i_ver < comp_size; i_ver++) - { - energy_array[i_ver] = 0; - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - energy_array[i_ver] += pow(vertex_attribute_map(this->components[ind_com][i_ver]).observation[i_dim] - - kernels[0][i_dim], 2) * vertex_attribute_map(this->components[ind_com][i_ver]).weight; - } - best_energy += energy_array[i_ver]; - } // we now generate a random number to determinate which node will be the second kernel - T random_sample = ((T)(rand())) / ((T)(RAND_MAX)); - current_energy = best_energy * random_sample; - for (uint32_t i_ver = 0; i_ver < comp_size; i_ver++) - { - current_energy -= energy_array[i_ver]; - if (current_energy < 0) - { //we have selected the second kernel - second_kernel = i_ver; - break; - } - } - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { // now fill the second kernel - kernels[1][i_dim] = vertex_attribute_map(this->components[ind_com][second_kernel]).observation[i_dim]; - } - //----main kmeans loop----- - for (uint32_t ite_kmeans = 0; ite_kmeans < this->parameter.kmeans_ite; ite_kmeans++) - { - //--affectation step: associate each node with its closest kernel------------------- -#ifdef OPENMP -#pragma omp parallel for if (nb_comp < omp_get_num_threads()) shared(potential_label) schedule(static) -#endif - for (uint32_t i_ver = 0; i_ver < comp_size; i_ver++) - { - std::vector distance_kernels(2); - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - distance_kernels[0] += pow(vertex_attribute_map(this->components[ind_com][i_ver]).observation[i_dim] - - kernels[0][i_dim], 2); - distance_kernels[1] += pow(vertex_attribute_map(this->components[ind_com][i_ver]).observation[i_dim] - - kernels[1][i_dim], 2); - } - potential_label[i_ver] = distance_kernels[0] > distance_kernels[1]; - } - //-----computation of the new kernels---------------------------- - total_weight[0] = 0.; - total_weight[1] = 0.; - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - kernels[0][i_dim] = 0; - kernels[1][i_dim] = 0; - } -#ifdef OPENMP -#pragma omp parallel for if (nb_comp < omp_get_num_threads()) shared(potential_label) schedule(static) -#endif - for (uint32_t i_ver = 0; i_ver < comp_size; i_ver++) - { - if (vertex_attribute_map(this->components[ind_com][i_ver]).weight == 0) - { - continue; - } - if (potential_label[i_ver]) - { - total_weight[0] += vertex_attribute_map(this->components[ind_com][i_ver]).weight; - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - kernels[0][i_dim] += vertex_attribute_map(this->components[ind_com][i_ver]).observation[i_dim] - * vertex_attribute_map(this->components[ind_com][i_ver]).weight; - } - } - else - { - total_weight[1] += vertex_attribute_map(this->components[ind_com][i_ver]).weight; - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - kernels[1][i_dim] += vertex_attribute_map(this->components[ind_com][i_ver]).observation[i_dim] - * vertex_attribute_map(this->components[ind_com][i_ver]).weight; - } - } - } - if ((total_weight[0] == 0) || (total_weight[1] == 0)) - { - //std::cout << "kmeans error : " << comp_size << std::endl; - break; - } - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - kernels[0][i_dim] = kernels[0][i_dim] / total_weight[0]; - kernels[1][i_dim] = kernels[1][i_dim] / total_weight[1]; - } - } - //----compute the associated energy ------ - current_energy = 0; -#ifdef OPENMP -#pragma omp parallel for if (nb_comp < omp_get_num_threads()) shared(potential_label) schedule(static) -#endif - for (uint32_t i_ver = 0; i_ver < comp_size; i_ver++) - { - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - if (potential_label[i_ver]) - { - current_energy += pow(vertex_attribute_map(this->components[ind_com][i_ver]).observation[i_dim] - - kernels[0][i_dim], 2) * vertex_attribute_map(this->components[ind_com][i_ver]).weight; - } - else - { - current_energy += pow(vertex_attribute_map(this->components[ind_com][i_ver]).observation[i_dim] - - kernels[1][i_dim], 2) * vertex_attribute_map(this->components[ind_com][i_ver]).weight; - } - } - } - if (current_energy < best_energy) - { - best_energy = current_energy; - for (uint32_t i_ver = 0; i_ver < comp_size; i_ver++) - { - binary_label[vertex_index_map(this->components[ind_com][i_ver])] = potential_label[i_ver]; - } - } - } - } - } - - //============================================================================================= - //============================= COMPUTE_CENTERS_L2 ========================================== - //============================================================================================= - inline void compute_centers(VectorOfCentroids & centers, const uint32_t & nb_comp - , const std::vector & binary_label) - { - //compute for each component the values of h_1 and h_2 -#ifdef OPENMP -#pragma omp parallel for if (nb_comp >= omp_get_num_threads()) schedule(dynamic) -#endif - for (uint32_t ind_com = 0; ind_com < nb_comp; ind_com++) - { - if (this->saturated_components[ind_com]) - { - continue; - } - compute_center(centers.centroids[ind_com], ind_com, binary_label); - } - } - - //============================================================================================= - //============================= COMPUTE_CENTERS_L2 ========================================== - //============================================================================================= - inline void compute_center(std::vector< std::vector > & center, const uint32_t & ind_com - , const std::vector & binary_label) - { - //compute for each component the values of the centroids corresponding to the optimal binary partition - VertexAttributeMap vertex_attribute_map - = boost::get(boost::vertex_bundle, this->main_graph); - VertexIndexMap vertex_index_map = boost::get(boost::vertex_index, this->main_graph); - T total_weight[2]; - total_weight[0] = 0.; - total_weight[1] = 0.; - //#pragma omp parallel for if (this->parameter.parallel) - for (uint32_t i_ver = 0; i_ver < this->components[ind_com].size(); i_ver++) - { - if (vertex_attribute_map(this->components[ind_com][i_ver]).weight == 0) - { - continue; - } - if (binary_label[vertex_index_map(this->components[ind_com][i_ver])]) - { - total_weight[0] += vertex_attribute_map(this->components[ind_com][i_ver]).weight; - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - center[0][i_dim] += vertex_attribute_map(this->components[ind_com][i_ver]).observation[i_dim] - * vertex_attribute_map(this->components[ind_com][i_ver]).weight; - } - } - else - { - total_weight[1] += vertex_attribute_map(this->components[ind_com][i_ver]).weight; - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - center[1][i_dim] += vertex_attribute_map(this->components[ind_com][i_ver]).observation[i_dim] - * vertex_attribute_map(this->components[ind_com][i_ver]).weight; - } - } - } - if ((total_weight[0] == 0) || (total_weight[1] == 0)) - { - //the component is saturated - this->saturateComponent(ind_com); - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - center[0][i_dim] = vertex_attribute_map(this->components[ind_com][0]).value[i_dim]; - center[1][i_dim] = vertex_attribute_map(this->components[ind_com][0]).value[i_dim]; - } - } - else - { - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - center[0][i_dim] = center[0][i_dim] / total_weight[0]; - center[1][i_dim] = center[1][i_dim] / total_weight[1]; - } - } - } - - //============================================================================================= - //============================= SET_CAPACITIES ========================================== - //============================================================================================= - inline void set_capacities(const VectorOfCentroids & centers) - { - VertexAttributeMap vertex_attribute_map = boost::get(boost::vertex_bundle, this->main_graph); - EdgeAttributeMap edge_attribute_map = boost::get(boost::edge_bundle, this->main_graph); - //----first compute the capacity in sink/node edges------------------------------------ - //#pragma omp parallel for if (this->parameter.parallel) schedule(dynamic) - uint32_t nb_comp = static_cast(this->components.size()); -#ifdef OPENMP -#pragma omp parallel for if (nb_comp >= omp_get_num_threads()) schedule(dynamic) -#endif - for (uint32_t ind_com = 0; ind_com < nb_comp; ind_com++) - { - VertexDescriptor desc_v; - EdgeDescriptor desc_source2v, desc_v2sink, desc_v2source; - T cost_B, cost_notB; //the cost of being in B or not B, local for each component - if (this->saturated_components[ind_com]) - { - continue; - } - for (uint32_t i_ver = 0; i_ver < this->components[ind_com].size(); i_ver++) - { - desc_v = this->components[ind_com][i_ver]; - // because of the adjacency structure NEVER access edge (source,v) directly! - desc_v2source = boost::edge(desc_v, this->source, this->main_graph).first; - desc_source2v = edge_attribute_map(desc_v2source).edge_reverse; //use edge_reverse instead - desc_v2sink = boost::edge(desc_v, this->sink, this->main_graph).first; - cost_B = 0; - cost_notB = 0; - if (vertex_attribute_map(desc_v).weight == 0) - { //no observation - no cut - edge_attribute_map(desc_source2v).capacity = 0; - edge_attribute_map(desc_v2sink).capacity = 0; - continue; - } - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - cost_B += 0.5*vertex_attribute_map(desc_v).weight - * (pow(centers.centroids[ind_com][0][i_dim], 2) - 2 * (centers.centroids[ind_com][0][i_dim] - * vertex_attribute_map(desc_v).observation[i_dim])); - cost_notB += 0.5*vertex_attribute_map(desc_v).weight - * (pow(centers.centroids[ind_com][1][i_dim], 2) - 2 * (centers.centroids[ind_com][1][i_dim] - * vertex_attribute_map(desc_v).observation[i_dim])); - } - if (cost_B > cost_notB) - { - edge_attribute_map(desc_source2v).capacity = cost_B - cost_notB; - edge_attribute_map(desc_v2sink).capacity = 0.; - } - else - { - edge_attribute_map(desc_source2v).capacity = 0.; - edge_attribute_map(desc_v2sink).capacity = cost_notB - cost_B; - } - } - } - //----then set the vertex to vertex edges --------------------------------------------- - EdgeIterator i_edg, i_edg_end; - for (boost::tie(i_edg, i_edg_end) = boost::edges(this->main_graph); - i_edg != i_edg_end; ++i_edg) - { - if (!edge_attribute_map(*i_edg).realEdge) - { - continue; - } - if (!edge_attribute_map(*i_edg).isActive) - { - edge_attribute_map(*i_edg).capacity - = edge_attribute_map(*i_edg).weight * this->parameter.reg_strenth; - } - else - { - edge_attribute_map(*i_edg).capacity = 0; - } - } - } - - //============================================================================================= - //================================= COMPUTE_VALUE ========================================= - //============================================================================================= - std::pair, T> compute_value(const uint32_t & ind_com) override - { - VertexAttributeMap vertex_attribute_map = boost::get(boost::vertex_bundle, this->main_graph); - T total_weight = 0; - std::vector compValue(this->dim); - std::fill((compValue.begin()), (compValue.end()), 0); -#ifdef OPENMP -#pragma omp parallel for if (this->parameter.parallel) schedule(static) -#endif - for (uint32_t ind_ver = 0; ind_ver < this->components[ind_com].size(); ++ind_ver) - { - total_weight += vertex_attribute_map(this->components[ind_com][ind_ver]).weight; - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - compValue[i_dim] += vertex_attribute_map(this->components[ind_com][ind_ver]).observation[i_dim] - * vertex_attribute_map(this->components[ind_com][ind_ver]).weight; - } - vertex_attribute_map(this->components[ind_com][ind_ver]).in_component = ind_com; - } - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - compValue[i_dim] = compValue[i_dim] / total_weight; - } - for (uint32_t ind_ver = 0; ind_ver < this->components[ind_com].size(); ++ind_ver) - { - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - vertex_attribute_map(this->components[ind_com][ind_ver]).value[i_dim] = compValue[i_dim]; - } - } - return std::pair, T>(compValue, total_weight); - } - - //============================================================================================= - //================================= COMPUTE_MERGE_GAIN ========================================= - //============================================================================================= - std::pair, T> compute_merge_gain(const VertexDescriptor & comp1, const VertexDescriptor & comp2) override - { - VertexAttributeMap reduced_vertex_attribute_map = boost::get(boost::vertex_bundle, this->reduced_graph); - std::vector merge_value(this->dim); - T gain = 0; - // compute the value obtained by mergeing the two connected components - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - merge_value[i_dim] = - (reduced_vertex_attribute_map(comp1).weight * - reduced_vertex_attribute_map(comp1).value[i_dim] - + reduced_vertex_attribute_map(comp2).weight * - reduced_vertex_attribute_map(comp2).value[i_dim]) - / (reduced_vertex_attribute_map(comp1).weight - + reduced_vertex_attribute_map(comp2).weight); - gain += 0.5 * (pow(merge_value[i_dim], 2) - * (reduced_vertex_attribute_map(comp1).weight - + reduced_vertex_attribute_map(comp2).weight) - - pow(reduced_vertex_attribute_map(comp1).value[i_dim], 2) - * reduced_vertex_attribute_map(comp1).weight - - pow(reduced_vertex_attribute_map(comp2).value[i_dim], 2) - * reduced_vertex_attribute_map(comp2).weight); - } - return std::pair, T>(merge_value, gain); - } - }; -} diff --git a/include/CutPursuit_Linear.h b/include/CutPursuit_Linear.h deleted file mode 100644 index e87b018..0000000 --- a/include/CutPursuit_Linear.h +++ /dev/null @@ -1,301 +0,0 @@ -#pragma once - -#include "CutPursuit.h" - -namespace CP -{ - template - struct CutPursuit_Linear : public CutPursuit - { - std::vector> componentVector; - - // only used with backward step - the sum of all observation in the component - CutPursuit_Linear(uint32_t nbVertex = 1) : CutPursuit(nbVertex) - { - this->componentVector = std::vector>(1); - } - - std::pair compute_energy() override - { - VertexAttributeMap vertex_attribute_map = boost::get(boost::vertex_bundle, this->main_graph); - EdgeAttributeMap edge_attribute_map = boost::get(boost::edge_bundle, this->main_graph); - std::pair pair_energy; - T energy = 0; - VertexIterator i_ver; - //#pragma omp parallel for private(i_ver) if (this->parameter.parallel) - for (i_ver = boost::vertices(this->main_graph).first; - i_ver != this->lastIterator; ++i_ver) - { - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - energy -= vertex_attribute_map(*i_ver).weight - * vertex_attribute_map(*i_ver).observation[i_dim] - * vertex_attribute_map(*i_ver).value[i_dim]; - } - } - pair_energy.first = energy; - energy = 0; - EdgeIterator i_edg, i_edg_end = boost::edges(this->main_graph).second; - for (i_edg = boost::edges(this->main_graph).first; - i_edg != i_edg_end; ++i_edg) - { - if (!edge_attribute_map(*i_edg).realEdge) - { - continue; - } - energy += .5 * edge_attribute_map(*i_edg).isActive * this->parameter.reg_strenth - * edge_attribute_map(*i_edg).weight; - } - pair_energy.second = energy; - return pair_energy; - } - - //============================================================================================= - //============================= SPLIT =========================================== - //============================================================================================= - size_t split() override - { // split the graph by trying to find the best binary partition - // each components is split into B and notB - //initialize h_1 and h_2 with kmeans - //--------initilializing labels------------------------------------------------------------ - //corner contains the two most likely class for each component - std::vector< std::vector< uint32_t > > corners = - std::vector< std::vector< uint32_t > >(this->components.size(), - std::vector< uint32_t >(2, 0)); - this->compute_corners(corners); - this->set_capacities(corners); - //compute flow - boost::boykov_kolmogorov_max_flow( - this->main_graph, - get(&EdgeAttribute::capacity, this->main_graph), - get(&EdgeAttribute::residualCapacity, this->main_graph), - get(&EdgeAttribute::edge_reverse, this->main_graph), - get(&VertexAttribute::color, this->main_graph), - get(boost::vertex_index, this->main_graph), - this->source, - this->sink); - size_t saturation = this->activate_edges(); - return saturation; - } - - //============================================================================================= - //============================= COMPUTE CORNERS =================================== - //============================================================================================= - inline void compute_corners(std::vector< std::vector< uint32_t > > & corners) - { //-----compute the 2 most populous labels------------------------------ - //#pragma omp parallel for if (this->parameter.parallel) schedule(dynamic) - for (uint32_t i_com = 0; i_com < this->components.size(); i_com++) - { - if (this->saturated_components[i_com]) - { - continue; - } - std::pair corners_pair = find_corner(i_com); - corners[i_com][0] = corners_pair.first; - corners[i_com][1] = corners_pair.second; - } - } - - //============================================================================================= - //============================= find_corner ======================================= - //============================================================================================= - std::pair find_corner(const uint32_t & i_com) - { - // given a component will output the pairs of the two most likely labels - VertexAttributeMap vertex_attribute_map - = boost::get(boost::vertex_bundle, this->main_graph); - std::vector average_vector(this->dim, 0); - for (uint32_t i_ver = 0; i_ver < this->components[i_com].size(); i_ver++) - { - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - average_vector.at(i_dim) += vertex_attribute_map[this->components[i_com][i_ver]].observation[i_dim] - * vertex_attribute_map[this->components[i_com][i_ver]].weight; - } - } - uint32_t indexOfMax = 0; - for (uint32_t i_dim = 1; i_dim < this->dim; i_dim++) - { - if (average_vector.at(indexOfMax) < average_vector.at(i_dim)) - { - indexOfMax = i_dim; - } - } - average_vector[indexOfMax] = -1; - uint32_t indexOfSndMax = 0; - for (uint32_t i_dim = 1; i_dim < this->dim; i_dim++) - { - if (average_vector[indexOfSndMax] < average_vector[i_dim]) - { - indexOfSndMax = i_dim; - } - } - return std::pair(indexOfMax, indexOfSndMax); - } - - //============================================================================================= - //============================= SET_CAPACITIES ======================================= - //============================================================================================= - inline void set_capacities(const std::vector< std::vector< uint32_t > > & corners) - { - VertexDescriptor desc_v; - EdgeDescriptor desc_source2v, desc_v2sink, desc_v2source; - VertexAttributeMap vertex_attribute_map = boost::get(boost::vertex_bundle, this->main_graph); - EdgeAttributeMap edge_attribute_map = boost::get(boost::edge_bundle, this->main_graph); - T cost_B, cost_notB; //the cost of being in B or not B, local for each component - //----first compute the capacity in sink/node edges------------------------------------ - //#pragma omp parallel for if (this->parameter.parallel) schedule(dynamic) - for (uint32_t i_com = 0; i_com < this->components.size(); i_com++) - { - if (this->saturated_components[i_com]) - { - continue; - } - for (uint32_t i_ver = 0; i_ver < this->components[i_com].size(); i_ver++) - { - desc_v = this->components[i_com][i_ver]; - // because of the adjacency structure NEVER access edge (source,v) directly! - desc_v2source = boost::edge(desc_v, this->source, this->main_graph).first; - desc_source2v = edge_attribute_map(desc_v2source).edge_reverse; //use edge_reverse instead - desc_v2sink = boost::edge(desc_v, this->sink, this->main_graph).first; - cost_B = 0; - cost_notB = 0; - if (vertex_attribute_map(desc_v).weight == 0) - { - edge_attribute_map(desc_source2v).capacity = 0; - edge_attribute_map(desc_v2sink).capacity = 0; - continue; - } - cost_B += vertex_attribute_map(desc_v).observation[corners[i_com][0]]; - cost_notB += vertex_attribute_map(desc_v).observation[corners[i_com][1]]; - if (cost_B > cost_notB) - { - edge_attribute_map(desc_source2v).capacity = cost_B - cost_notB; - edge_attribute_map(desc_v2sink).capacity = 0.; - } - else - { - edge_attribute_map(desc_source2v).capacity = 0.; - edge_attribute_map(desc_v2sink).capacity = cost_notB - cost_B; - } - } - } - //----then set the vertex to vertex edges --------------------------------------------- - EdgeIterator i_edg, i_edg_end; - for (boost::tie(i_edg, i_edg_end) = boost::edges(this->main_graph); - i_edg != i_edg_end; ++i_edg) - { - if (!edge_attribute_map(*i_edg).realEdge) - { - continue; - } - if (!edge_attribute_map(*i_edg).isActive) - { - edge_attribute_map(*i_edg).capacity - = edge_attribute_map(*i_edg).weight * this->parameter.reg_strenth; - } - else - { - edge_attribute_map(*i_edg).capacity = 0; - } - } - } - - //============================================================================================= - //================================= COMPUTE_VALUE ========================================= - //============================================================================================= - std::pair, T> compute_value(const uint32_t & i_com) override - { - VertexAttributeMap vertex_attribute_map = boost::get(boost::vertex_bundle, this->main_graph); - if (i_com == 0) - { // we allocate the space necessary for the component vector at the first read of the component - this->componentVector = std::vector>(this->components.size()); - } - std::vector average_vector(this->dim), component_value(this->dim); - T total_weight = 0; - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - average_vector[i_dim] = 0; - } - for (uint32_t ind_ver = 0; ind_ver < this->components[i_com].size(); ++ind_ver) - { - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - average_vector[i_dim] += vertex_attribute_map[this->components[i_com][ind_ver]].observation[i_dim] - * vertex_attribute_map[this->components[i_com][ind_ver]].weight; - } - total_weight += vertex_attribute_map[this->components[i_com][ind_ver]].weight; - vertex_attribute_map(this->components[i_com][ind_ver]).in_component = i_com; - } - this->componentVector[i_com] = average_vector; - uint32_t indexOfMax = 0; - for (uint32_t i_dim = 1; i_dim < this->dim; i_dim++) - { - if (average_vector[indexOfMax] < average_vector[i_dim]) - { - indexOfMax = i_dim; - } - } - for (uint32_t ind_ver = 0; ind_ver < this->components[i_com].size(); ++ind_ver) - { - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - if (i_dim == indexOfMax) - { - component_value[i_dim] = 1; - vertex_attribute_map(this->components[i_com][ind_ver]).value[i_dim] = 1; - } - else - { - component_value[i_dim] = 0; - vertex_attribute_map(this->components[i_com][ind_ver]).value[i_dim] = 0; - } - } - } - return std::pair, T>(component_value, total_weight); - } - - //============================================================================================= - //================================= COMPUTE_MERGE_GAIN ========================================= - //============================================================================================= - std::pair, T> compute_merge_gain(const VertexDescriptor & comp1, const VertexDescriptor & comp2) override - { - VertexAttributeMap reduced_vertex_attribute_map = boost::get(boost::vertex_bundle, this->reduced_graph); - VertexIndexMap reduced_vertex_vertex_index_map = get(boost::vertex_index, this->reduced_graph); - std::vector merge_value(this->dim), mergedVector(this->dim); - T gain = 0; - // compute the value obtained by mergeing the two connected components - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - mergedVector[i_dim] = this->componentVector[reduced_vertex_vertex_index_map(comp1)][i_dim] - + this->componentVector[reduced_vertex_vertex_index_map(comp2)][i_dim]; - } - uint32_t indexOfMax = 0; - for (uint32_t i_dim = 1; i_dim < this->dim; i_dim++) - { - if (mergedVector[indexOfMax] < mergedVector[i_dim]) - { - indexOfMax = i_dim; - } - } - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - if (i_dim == indexOfMax) - { - merge_value[i_dim] = 1; - } - else - { - merge_value[i_dim] = 0; - } - gain += mergedVector[i_dim] * merge_value[i_dim] - - this->componentVector[reduced_vertex_vertex_index_map(comp1)][i_dim] - * reduced_vertex_attribute_map(comp1).value[i_dim] - - this->componentVector[reduced_vertex_vertex_index_map(comp2)][i_dim] - * reduced_vertex_attribute_map(comp2).value[i_dim]; - } - - return std::pair, T>(merge_value, gain); - } - }; -} diff --git a/include/CutPursuit_SPG.h b/include/CutPursuit_SPG.h deleted file mode 100644 index 4d3d9f0..0000000 --- a/include/CutPursuit_SPG.h +++ /dev/null @@ -1,499 +0,0 @@ -#pragma once - -#include "CutPursuit.h" - -namespace CP -{ - template - struct CutPursuit_SPG : public CutPursuit - { - //============================================================================================= - //============================= COMPUTE ENERGY =========================================== - //============================================================================================= - std::pair compute_energy() override - { - VertexAttributeMap vertex_attribute_map= boost::get(boost::vertex_bundle, this->main_graph); - EdgeAttributeMap edge_attribute_map = boost::get(boost::edge_bundle, this->main_graph); - //the first element pair_energy of is the fidelity and the second the penalty - std::pair pair_energy; - T energy = 0; - //#pragma omp parallel for private(i_dim) if (this->parameter.parallel) schedule(static) reduction(+:energy,i) - for (uint32_t ind_ver = 0; ind_ver < this->nVertex; ind_ver++) - { - VertexDescriptor i_ver = boost::vertex(ind_ver, this->main_graph); - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - energy += .5*vertex_attribute_map(i_ver).weight - * pow(vertex_attribute_map(i_ver).observation[i_dim] - - vertex_attribute_map(i_ver).value[i_dim], 2); - } - } - pair_energy.first = energy; - energy = 0; - EdgeIterator i_edg, i_edg_end = boost::edges(this->main_graph).second; - for (i_edg = boost::edges(this->main_graph).first; i_edg != i_edg_end; ++i_edg) - { - if (!edge_attribute_map(*i_edg).realEdge) - { - continue; - } - energy += .5 * edge_attribute_map(*i_edg).isActive * this->parameter.reg_strenth - * edge_attribute_map(*i_edg).weight; - } - pair_energy.second = energy; - return pair_energy; - } - - //============================================================================================= - //============================= SPLIT =========================================== - //============================================================================================= - size_t split() override - { // split the graph by trying to find the best binary partition - // each components is split into B and notB - // for each components we associate the value h_1 and h_2 to vertices in B or notB - // the affectation as well as h_1 and h_2 are computed alternatively - //tic(); - //--------loading structures--------------------------------------------------------------- - uint32_t nb_comp = static_cast(this->components.size()); - VertexAttributeMap vertex_attribute_map = boost::get(boost::vertex_bundle, this->main_graph); - VertexIndexMap vertex_index_map = boost::get(boost::vertex_index, this->main_graph); - //stores wether each vertex is B or not - std::vector binary_label(this->nVertex); - //initialize the binary partition with kmeans - this->init_labels(binary_label, true); - - //centers is the value of each binary component in the optimal partition - VectorOfCentroids centers(nb_comp, this->dim); - //-----main loop---------------------------------------------------------------- - - // the optimal flow is iteratively approximated - T unary_weight = pow(this->parameter.weight_decay, -float(this->parameter.flow_steps)); - for (uint32_t i_step = 0; i_step < this->parameter.flow_steps; i_step++) - { - unary_weight = unary_weight * this->parameter.weight_decay; - - //the regularization strength at this step - //compute h_1 and h_2 - centers = VectorOfCentroids(nb_comp, this->dim); - this->compute_centers(centers, nb_comp, binary_label); - this->set_capacities(centers, unary_weight); - - // update the capacities of the flow graph - boost::boykov_kolmogorov_max_flow( - this->main_graph, - get(&EdgeAttribute::capacity, this->main_graph), - get(&EdgeAttribute::residualCapacity, this->main_graph), - get(&EdgeAttribute::edge_reverse, this->main_graph), - get(&VertexAttribute::color, this->main_graph), - get(boost::vertex_index, this->main_graph), - this->source, - this->sink); - - for (uint32_t ind_com = 0; ind_com < nb_comp; ind_com++) - { - if (this->saturated_components[ind_com]) - { - continue; - } - - for (uint32_t i_ver = 0; i_ver < this->components[ind_com].size(); i_ver++) - { - binary_label[vertex_index_map(this->components[ind_com][i_ver])] - = (vertex_attribute_map(this->components[ind_com][i_ver]).color - == vertex_attribute_map(this->sink).color); - - } - } - } - size_t saturation = this->activate_edges(false); - return saturation; - } - - //============================================================================================= - //============================= INIT_L2 ====== =========================================== - //============================================================================================= - inline void init_labels(std::vector & binary_label, bool spatial_part) - { //-----initialize the labelling for each components with kmeans------------------------------ - VertexAttributeMap vertex_attribute_map - = boost::get(boost::vertex_bundle, this->main_graph); - VertexIndexMap vertex_index_map = boost::get(boost::vertex_index, this->main_graph); - uint32_t nb_comp = static_cast(this->components.size()); - // ind_com; - //#pragma omp parallel for private(ind_com) //if (nb_comp>=8) schedule(dynamic) - uint32_t dim_spat = spatial_part ? this->dim - 0 : this->dim; - -#ifdef OPENMP -#pragma omp parallel for if (nb_comp >= omp_get_num_threads()) schedule(dynamic) -#endif - for (uint32_t ind_com = 0; ind_com < nb_comp; ind_com++) - { - - std::vector< std::vector > kernels(2, std::vector(this->dim)); - T total_weight[2]; - T best_energy; - T current_energy; - uint32_t comp_size = static_cast(this->components[ind_com].size()); - std::vector potential_label(comp_size); - std::vector energy_array(comp_size); - if (this->saturated_components[ind_com] || comp_size <= 1) - { - continue; - } - for (uint32_t init_kmeans = 0; init_kmeans < this->parameter.kmeans_resampling; init_kmeans++) - {//proceed to several initilialisation of kmeans and pick up the best one - //----- initialization with KM++ ------------------ - uint32_t first_kernel = std::rand() % comp_size, second_kernel = 0; // first kernel attributed - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - kernels[0][i_dim] = vertex_attribute_map(this->components[ind_com][first_kernel]).observation[i_dim]; - } - best_energy = 0; //now compute the square distance of each vertex to this kernel -#ifdef OPENMP -#pragma omp parallel for if (nb_comp < omp_get_num_threads()) shared(best_energy) schedule(static) -#endif - for (uint32_t i_ver = 0; i_ver < comp_size; i_ver++) - { - energy_array[i_ver] = 0; - for (uint32_t i_dim = 0; i_dim < dim_spat; i_dim++) - { - energy_array[i_ver] += pow(vertex_attribute_map(this->components[ind_com][i_ver]).observation[i_dim] - kernels[0][i_dim], 2) * vertex_attribute_map(this->components[ind_com][i_ver]).weight; - } - best_energy += energy_array[i_ver]; - } // we now generate a random number to determinate which node will be the second kernel - T random_sample = ((T)(rand())) / ((T)(RAND_MAX)); - current_energy = best_energy * random_sample; - for (uint32_t i_ver = 0; i_ver < comp_size; i_ver++) - { - current_energy -= energy_array[i_ver]; - if (current_energy < 0) - { //we have selected the second kernel - second_kernel = i_ver; - break; - } - } - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { // now fill the second kernel - kernels[1][i_dim] = vertex_attribute_map(this->components[ind_com][second_kernel]).observation[i_dim]; - } - //----main kmeans loop----- - for (uint32_t ite_kmeans = 0; ite_kmeans < this->parameter.kmeans_ite; ite_kmeans++) - { - //--affectation step: associate each node with its closest kernel------------------- -#ifdef OPENMP -#pragma omp parallel for if (nb_comp < omp_get_num_threads()) shared(potential_label) schedule(static) -#endif - for (uint32_t i_ver = 0; i_ver < comp_size; i_ver++) - { - std::vector distance_kernels(2); - for (uint32_t i_dim = 0; i_dim < dim_spat; i_dim++) - { - distance_kernels[0] += pow(vertex_attribute_map(this->components[ind_com][i_ver]).observation[i_dim] - kernels[0][i_dim], 2); - distance_kernels[1] += pow(vertex_attribute_map(this->components[ind_com][i_ver]).observation[i_dim] - kernels[1][i_dim], 2); - } - potential_label[i_ver] = distance_kernels[0] > distance_kernels[1]; - } - //-----computation of the new kernels---------------------------- - total_weight[0] = 0.; - total_weight[1] = 0.; - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - kernels[0][i_dim] = 0; - kernels[1][i_dim] = 0; - } -#ifdef OPENMP -#pragma omp parallel for if (nb_comp < omp_get_num_threads()) shared(potential_label) schedule(static) -#endif - for (uint32_t i_ver = 0; i_ver < comp_size; i_ver++) - { - if (vertex_attribute_map(this->components[ind_com][i_ver]).weight == 0) - { - continue; - } - if (potential_label[i_ver]) - { - total_weight[0] += vertex_attribute_map(this->components[ind_com][i_ver]).weight; - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - kernels[0][i_dim] += vertex_attribute_map(this->components[ind_com][i_ver]).observation[i_dim] - * vertex_attribute_map(this->components[ind_com][i_ver]).weight; - } - } - else - { - total_weight[1] += vertex_attribute_map(this->components[ind_com][i_ver]).weight; - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - kernels[1][i_dim] += vertex_attribute_map(this->components[ind_com][i_ver]).observation[i_dim] - * vertex_attribute_map(this->components[ind_com][i_ver]).weight; - } - } - } - - if ((total_weight[0] == 0) || (total_weight[1] == 0)) - { - break; - } - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - kernels[0][i_dim] = kernels[0][i_dim] / total_weight[0]; - kernels[1][i_dim] = kernels[1][i_dim] / total_weight[1]; - } - } - //----compute the associated energy ------ - current_energy = 0; -#ifdef OPENMP -#pragma omp parallel for if (nb_comp < omp_get_num_threads()) shared(potential_label) schedule(static) -#endif - for (uint32_t i_ver = 0; i_ver < comp_size; i_ver++) - { - for (uint32_t i_dim = 0; i_dim < dim_spat; i_dim++) - { - if (potential_label[i_ver]) - { - current_energy += pow(vertex_attribute_map(this->components[ind_com][i_ver]).observation[i_dim] - - kernels[0][i_dim], 2) * vertex_attribute_map(this->components[ind_com][i_ver]).weight; - } - else - { - current_energy += pow(vertex_attribute_map(this->components[ind_com][i_ver]).observation[i_dim] - - kernels[1][i_dim], 2) * vertex_attribute_map(this->components[ind_com][i_ver]).weight; - } - } - } - if (current_energy < best_energy) - { - best_energy = current_energy; - for (uint32_t i_ver = 0; i_ver < comp_size; i_ver++) - { - binary_label[vertex_index_map(this->components[ind_com][i_ver])] = potential_label[i_ver]; - } - } - } - } - } - - //============================================================================================= - //============================= COMPUTE_CENTERS_L2 ========================================== - //============================================================================================= - inline void compute_centers(VectorOfCentroids & centers, const uint32_t & nb_comp, const std::vector & binary_label) - { - //compute for each component the values of h_1 and h_2 -#ifdef OPENMP -#pragma omp parallel for if (nb_comp >= omp_get_num_threads()) schedule(dynamic) -#endif - for (uint32_t ind_com = 0; ind_com < nb_comp; ind_com++) - { - if (this->saturated_components[ind_com]) - { - continue; - } - compute_center(centers.centroids[ind_com], ind_com, binary_label); - } - } - - //============================================================================================= - //============================= COMPUTE_CENTER_L2 ========================================== - //============================================================================================= - inline void compute_center(std::vector< std::vector > & center, const uint32_t & ind_com, const std::vector & binary_label) - { - //compute for each component the values of the centroids corresponding to the optimal binary partition - VertexAttributeMap vertex_attribute_map - = boost::get(boost::vertex_bundle, this->main_graph); - VertexIndexMap vertex_index_map = boost::get(boost::vertex_index, this->main_graph); - T total_weight[2]; - total_weight[0] = 0.; - total_weight[1] = 0.; - - //#pragma omp parallel for if (this->parameter.parallel) - for (uint32_t i_ver = 0; i_ver < this->components[ind_com].size(); i_ver++) - { - if (vertex_attribute_map(this->components[ind_com][i_ver]).weight == 0) - { - continue; - } - if (binary_label[vertex_index_map(this->components[ind_com][i_ver])]) - { - total_weight[0] += vertex_attribute_map(this->components[ind_com][i_ver]).weight; - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - center[0][i_dim] += vertex_attribute_map(this->components[ind_com][i_ver]).observation[i_dim] - * vertex_attribute_map(this->components[ind_com][i_ver]).weight; - } - } - else - { - total_weight[1] += vertex_attribute_map(this->components[ind_com][i_ver]).weight; - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - center[1][i_dim] += vertex_attribute_map(this->components[ind_com][i_ver]).observation[i_dim] - * vertex_attribute_map(this->components[ind_com][i_ver]).weight; - } - } - } - if ((total_weight[0] == 0) || (total_weight[1] == 0)) - { - //the component is saturated - //this->saturateComponent(ind_com, false); - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - center[0][i_dim] = vertex_attribute_map(this->components[ind_com][0]).value[i_dim]; - center[1][i_dim] = vertex_attribute_map(this->components[ind_com][0]).value[i_dim]; - } - } - else - { - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - center[0][i_dim] = center[0][i_dim] / total_weight[0]; - center[1][i_dim] = center[1][i_dim] / total_weight[1]; - } - } - } - - //============================================================================================= - //============================= SET_CAPACITIES ========================================== - //============================================================================================= - inline void set_capacities(const VectorOfCentroids & centers, T unary_weight) - { - VertexAttributeMap vertex_attribute_map = boost::get(boost::vertex_bundle, this->main_graph); - EdgeAttributeMap edge_attribute_map = boost::get(boost::edge_bundle, this->main_graph); - //----first compute the capacity in sink/node edges------------------------------------ - //#pragma omp parallel for if (this->parameter.parallel) schedule(dynamic) - uint32_t nb_comp = static_cast(this->components.size()); -#ifdef OPENMP -#pragma omp parallel for if (nb_comp >= omp_get_num_threads()) schedule(dynamic) -#endif - for (uint32_t ind_com = 0; ind_com < nb_comp; ind_com++) - { - VertexDescriptor desc_v; - EdgeDescriptor desc_source2v, desc_v2sink, desc_v2source; - T cost_B, cost_notB; //the cost of being in B or not B, local for each component - if (this->saturated_components[ind_com]) - { - continue; - } - for (uint32_t i_ver = 0; i_ver < this->components[ind_com].size(); i_ver++) - { - desc_v = this->components[ind_com][i_ver]; - // because of the adjacency structure NEVER access edge (source,v) directly! - desc_v2source = boost::edge(desc_v, this->source, this->main_graph).first; - desc_source2v = edge_attribute_map(desc_v2source).edge_reverse; //use edge_reverse instead - desc_v2sink = boost::edge(desc_v, this->sink, this->main_graph).first; - cost_B = 0; - cost_notB = 0; - if (vertex_attribute_map(desc_v).weight == 0) - { //no observation - no cut - edge_attribute_map(desc_source2v).capacity = 0; - edge_attribute_map(desc_v2sink).capacity = 0; - continue; - } - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - cost_B += 0.5*vertex_attribute_map(desc_v).weight - * (pow(centers.centroids[ind_com][0][i_dim], 2) - 2 * (centers.centroids[ind_com][0][i_dim] - * vertex_attribute_map(desc_v).observation[i_dim])); - cost_notB += 0.5*vertex_attribute_map(desc_v).weight - * (pow(centers.centroids[ind_com][1][i_dim], 2) - 2 * (centers.centroids[ind_com][1][i_dim] - * vertex_attribute_map(desc_v).observation[i_dim])); - - } - if (cost_B > cost_notB) - { - edge_attribute_map(desc_source2v).capacity = (cost_B - cost_notB); - edge_attribute_map(desc_v2sink).capacity = 0.; - } - else - { - edge_attribute_map(desc_source2v).capacity = 0.; - edge_attribute_map(desc_v2sink).capacity = (cost_notB - cost_B); - } - } - } - //----then set the vertex to vertex edges --------------------------------------------- - EdgeIterator i_edg, i_edg_end; - for (boost::tie(i_edg, i_edg_end) = boost::edges(this->main_graph); i_edg != i_edg_end; ++i_edg) - { - if (!edge_attribute_map(*i_edg).realEdge) - { - continue; - } - if (!edge_attribute_map(*i_edg).isActive) - { - edge_attribute_map(*i_edg).capacity - = edge_attribute_map(*i_edg).weight * this->parameter.reg_strenth / unary_weight; - } - else - { - edge_attribute_map(*i_edg).capacity = 0; - } - } - } - - //============================================================================================= - //================================= COMPUTE_VALUE ========================================= - //============================================================================================= - std::pair, T> compute_value(const uint32_t & ind_com) override - { - VertexAttributeMap vertex_attribute_map - = boost::get(boost::vertex_bundle, this->main_graph); - T total_weight = 0; - std::vector compValue(this->dim); - std::fill((compValue.begin()), (compValue.end()), 0); -#ifdef OPENMP -#pragma omp parallel for if (this->parameter.parallel) schedule(static) -#endif - for (uint32_t ind_ver = 0; ind_ver < this->components[ind_com].size(); ++ind_ver) - { - total_weight += vertex_attribute_map(this->components[ind_com][ind_ver]).weight; - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - compValue[i_dim] += vertex_attribute_map(this->components[ind_com][ind_ver]).observation[i_dim] - * vertex_attribute_map(this->components[ind_com][ind_ver]).weight; - } - vertex_attribute_map(this->components[ind_com][ind_ver]).in_component = ind_com; - } - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - compValue[i_dim] = compValue[i_dim] / total_weight; - } - for (uint32_t ind_ver = 0; ind_ver < this->components[ind_com].size(); ++ind_ver) - { - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - vertex_attribute_map(this->components[ind_com][ind_ver]).value[i_dim] = compValue[i_dim]; - } - } - return std::pair, T>(compValue, total_weight); - } - - //============================================================================================= - //================================= COMPUTE_MERGE_GAIN ========================================= - //============================================================================================= - std::pair, T> compute_merge_gain(const VertexDescriptor & comp1, const VertexDescriptor & comp2) override - { - VertexAttributeMap reduced_vertex_attribute_map - = boost::get(boost::vertex_bundle, this->reduced_graph); - std::vector merge_value(this->dim); - T gain = 0; - // compute the value obtained by mergeing the two connected components - for (uint32_t i_dim = 0; i_dim < this->dim; i_dim++) - { - merge_value[i_dim] = - (reduced_vertex_attribute_map(comp1).weight * - reduced_vertex_attribute_map(comp1).value[i_dim] - + reduced_vertex_attribute_map(comp2).weight * - reduced_vertex_attribute_map(comp2).value[i_dim]) - / (reduced_vertex_attribute_map(comp1).weight - + reduced_vertex_attribute_map(comp2).weight); - gain += 0.5 * (pow(merge_value[i_dim], 2) - * (reduced_vertex_attribute_map(comp1).weight - + reduced_vertex_attribute_map(comp2).weight) - - pow(reduced_vertex_attribute_map(comp1).value[i_dim], 2) - * reduced_vertex_attribute_map(comp1).weight - - pow(reduced_vertex_attribute_map(comp2).value[i_dim], 2) - * reduced_vertex_attribute_map(comp2).weight); - } - return std::pair, T>(merge_value, gain); - } - }; -} diff --git a/include/Graph.h b/include/Graph.h deleted file mode 100644 index d44df76..0000000 --- a/include/Graph.h +++ /dev/null @@ -1,112 +0,0 @@ -#pragma once - -//Local -#include "Common.h" - -//Boost -#include -#include -#include -#include - -namespace CP -{ - typedef boost::graph_traits >::edge_descriptor EdgeDescriptor; - - template struct VertexAttribute - { - typedef T calc_type; - - VertexAttribute(uint32_t dim = 1, T weight = 1.) - : weight(weight) - , observation(dim, 0.) - , value(dim, 0.) - , color(-1) - , isBorder(false) - , in_component(0) - {} - - T weight; //weight of the observation - std::vector observation; //observed value - std::vector value; //current value - uint32_t color; //field use for the graph cut - bool isBorder; //is the node part of an activated edge - uint32_t in_component; //index of the component in which the node belong - }; - - template struct EdgeAttribute - { - typedef T calc_type; - - EdgeAttribute(T weight = 1., uint32_t eIndex = 0, bool real = true) - : index(eIndex) - , weight(weight) - , capacity(weight) - , residualCapacity(0) - , isActive(!real) - , realEdge(real) - {} - - uint32_t index; //index of the edge (necessary for graph cuts) - EdgeDescriptor edge_reverse; //pointer to the reverse edge, also necessary for graph cuts - T weight; //weight of the edge - T capacity; //capacity in the flow graph - T residualCapacity; //necessary for graph cuts - bool isActive; //is the edge in the support of the values - bool realEdge; //is the edge between real nodes or link to source/sink - }; - - template< typename T> - using Graph = typename boost::adjacency_list, EdgeAttribute >; - - template< typename T> - using VertexDescriptor = typename boost::graph_traits>::vertex_descriptor; - template< typename T> - using VertexIndex = typename boost::graph_traits>::vertices_size_type; - template< typename T> - using EdgeIndex = typename boost::graph_traits>::edges_size_type; - template< typename T> - using VertexIterator = typename boost::graph_traits>::vertex_iterator; - template< typename T> - using EdgeIterator = typename boost::graph_traits>::edge_iterator; - - template - using VertexAttributeMap = boost::vec_adj_list_vertex_property_map, Graph* - , VertexAttribute, VertexAttribute &, boost::vertex_bundle_t >; - template - using EdgeAttributeMap = boost::adj_list_edge_property_map< - boost::directed_tag, EdgeAttribute, EdgeAttribute & - , uint64_t, CP::EdgeAttribute, boost::edge_bundle_t>; - template - using VertexIndexMap = typename boost::property_map, boost::vertex_index_t>::type; - template - using EdgeIndexMap = typename boost::property_map, uint32_t EdgeAttribute::*>::type; - - template - bool addDoubledge(Graph & g, const VertexDescriptor & source, const VertexDescriptor & target - , const T weight, uint32_t eIndex, EdgeAttributeMap & edge_attribute_map, bool real = true) - { - // Add edges between two vertices. We have to create the edge and the reverse edge, - // then add the edge_reverse as the corresponding reverse edge to 'edge', and then add 'edge' - // as the corresponding reverse edge to 'edge_reverse' - - EdgeDescriptor edge, edge_reverse; - std::pair edge_added = boost::add_edge(source, target, g); - if (edge_added.second) - { - edge = edge_added.first; - edge_reverse = boost::add_edge(target, source, g).first; - EdgeAttribute attrib_edge(weight, eIndex, real); - EdgeAttribute attrib_edge_reverse(weight, eIndex + 1, real); - attrib_edge.edge_reverse = edge_reverse; - attrib_edge_reverse.edge_reverse = edge; - edge_attribute_map(edge) = attrib_edge; - edge_attribute_map(edge_reverse) = attrib_edge_reverse; - return true; - } - else - { - return false; - } - } -} diff --git a/include/TreeIso.h b/include/TreeIso.h index e159560..9e9005e 100644 --- a/include/TreeIso.h +++ b/include/TreeIso.h @@ -34,7 +34,7 @@ //# # //####################################################################################### -// A Matlab version shared via: +// Matlab and python versions shared via: // https://github.com/truebelief/artemis_treeiso class ccMainAppInterface; @@ -45,11 +45,11 @@ class TreeIso { public: - static bool Init_seg(const unsigned min_nn1, const float regStrength1, const float PR_DECIMATE_RES1, ccMainAppInterface* app, QProgressDialog* progressDlg); - static bool Intermediate_seg(const unsigned PR_MIN_NN2, const float PR_REG_STRENGTH2, const float PR_DECIMATE_RES2, const float PR_MAX_GAP, ccMainAppInterface* app, QProgressDialog* progressDlg); + static bool Init_seg(const unsigned PR_MIN_NN1,const float PR_REG_STRENGTH1,const float PR_DECIMATE_RES1,const unsigned PR_THREADS1,ccMainAppInterface* app,QProgressDialog* progressDlg); + static bool Intermediate_seg(const unsigned PR_MIN_NN2, const float PR_REG_STRENGTH2, const float PR_DECIMATE_RES2, const float PR_MAX_GAP, const unsigned PR_THREADS2, ccMainAppInterface* app, QProgressDialog* progressDlg); static bool Final_seg(const unsigned PR_MIN_NN3, const float PR_REL_HEIGHT_LENGTH_RATIO, const float PR_VERTICAL_WEIGHT, ccMainAppInterface* app, QProgressDialog* progressDlg); - static bool Init_seg_pcd(ccPointCloud* pc, const unsigned min_nn1, const float regStrength1, const float PR_DECIMATE_RES1, QProgressDialog* progressDlg = nullptr); - static bool Intermediate_seg_pcd(ccPointCloud* pc, const unsigned PR_MIN_NN2, const float PR_REG_STRENGTH2, const float PR_DECIMATE_RES2, const float PR_MAX_GAP, QProgressDialog* progressDlg = nullptr); + static bool Init_seg_pcd(ccPointCloud* pc, const unsigned PR_MIN_NN1, const float PR_REG_STRENGTH1, const float PR_DECIMATE_RES1, const unsigned PR_THREADS1, QProgressDialog* progressDlg = nullptr); + static bool Intermediate_seg_pcd(ccPointCloud* pc, const unsigned PR_MIN_NN2, const float PR_REG_STRENGTH2, const float PR_DECIMATE_RES2, const float PR_MAX_GAP, const unsigned PR_THREADS2, QProgressDialog* progressDlg = nullptr); static bool Final_seg_pcd(ccPointCloud* pc, const unsigned PR_MIN_NN3, const float PR_REL_HEIGHT_LENGTH_RATIO, const float PR_VERTICAL_WEIGHT, QProgressDialog* progressDlg = nullptr); }; diff --git a/include/TreeIsoHelper.h b/include/TreeIsoHelper.h deleted file mode 100644 index 7d83835..0000000 --- a/include/TreeIsoHelper.h +++ /dev/null @@ -1,79 +0,0 @@ -#pragma once - -//####################################################################################### -//# # -//# CLOUDCOMPARE PLUGIN: qTreeIso # -//# # -//# This program is free software; you can redistribute it and/or modify # -//# it under the terms of the GNU General Public License as published by # -//# the Free Software Foundation; version 2 or later of the License. # -//# # -//# This program is distributed in the hope that it will be useful, # -//# but WITHOUT ANY WARRANTY; without even the implied warranty of # -//# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # -//# GNU General Public License for more details. # -//# # -//# Please cite the following paper if you find this tool helpful # -//# # -//# Xi, Z.; Hopkinson, C. 3D Graph-Based Individual-Tree Isolation (Treeiso) # -//# from Terrestrial Laser Scanning Point Clouds. Remote Sens. 2022, 14, 6116. # -//# https://doi.org/10.3390/rs14236116 # -//# # -//# Our work relies on the cut-pursuit algorithm, please also consider citing: # -//# Landrieu, L.; Obozinski, G. Cut Pursuit: Fast Algorithms to Learn Piecewise # -//# Constant Functions on General Weighted Graphs. SIAM J. Imaging Sci. # -//# 2017, 10, 1724–1766. # -//# # -//# Copyright © # -//# Artemis Lab, Department of Geography & Environment # -//# University of Lethbridge, Canada # -//# # -//# # -//# Zhouxin Xi and Chris Hopkinson; # -//# truebelief2010@gmail.com; c.hopkinson@uleth.ca # -//# # -//####################################################################################### - -//Local -#include "API.h" -#include "knncpp.h" - -//Eigen -#include - -//STL -#include - -class ccPointCloud; - -bool perform_cut_pursuit(const uint32_t K, const float regStrength, const std::vector>& pc_vec, std::vector& edgeWeight, std::vector& Eu, std::vector& Ev, std::vector& in_component, std::vector>& components); -void perform_cut_pursuit2d(const uint32_t K, const float regStrength, const std::vector>& pc_vec, std::vector& edgeWeight, std::vector& Eu, std::vector& Ev, std::vector&); - -template void toTranslatedVector(const ccPointCloud* pc, std::vector>& y); -template size_t arg_min_col(std::vector& arr); -template size_t arg_max_col(std::vector& arr); -template void min_col(std::vector>& arr, std::vector&); -template T min_col(std::vector& arr); -template void max_col(std::vector>& arr, std::vector&); -template void mean_col(std::vector>& arr, std::vector&); -template T mean_col(std::vector& arr); -template T median_col(std::vector& arr); -template T mode_col(std::vector& arr); -template void decimate_vec(std::vector>& arr, T res, std::vector>& vec_dec); - -template void unique_group(std::vector& arr, std::vector>& u_group, std::vector& arr_unq, std::vector& ui); -template void unique_group(std::vector& idx, std::vector>&); -template void unique_group(std::vector& arr, std::vector>& u_group, std::vector& arr_unq); -template void unique_index_by_rows(std::vector>& arr, std::vector& ia, std::vector& ic); -template void sort_indexes_by_row(std::vector>& v, std::vector& idx, std::vector>&); -template void sort_indexes(std::vector& v, std::vector& idx, std::vector&); - -template void get_subset(const std::vector>& arr, const std::vector& indices, std::vector>&); -template void get_subset(const std::vector>& arr, const std::vector& indices, Eigen::MatrixXf&); -template bool get_subset(ccPointCloud* pcd, std::vector& indices, std::vector>& arr_sub); -template void get_subset(std::vector& arr, std::vector& indices, std::vector& arr_sub); - -void knn_cpp_nearest_neighbors(const std::vector>& dataset, size_t k, std::vector >& res_idx, std::vector >& res_dists, unsigned n_thread); -void knn_cpp_build(knncpp::KDTreeMinkowskiX>& kdtree, unsigned n_thread = 0); -void knn_cpp_query(knncpp::KDTreeMinkowskiX>& kdtree, Eigen::MatrixXf& query_points, size_t k, std::vector >& res_idx, std::vector >& res_dists); -float knn_cpp_query_min_d(knncpp::KDTreeMinkowskiX>& kdtree, Eigen::MatrixXf& query_points, size_t k); diff --git a/include/TreeIsoHelper.hpp b/include/TreeIsoHelper.hpp new file mode 100644 index 0000000..ccaddb7 --- /dev/null +++ b/include/TreeIsoHelper.hpp @@ -0,0 +1,399 @@ +#pragma once + +//####################################################################################### +//# # +//# CLOUDCOMPARE PLUGIN: qTreeIso # +//# # +//# This program is free software; you can redistribute it and/or modify # +//# it under the terms of the GNU General Public License as published by # +//# the Free Software Foundation; version 2 or later of the License. # +//# # +//# This program is distributed in the hope that it will be useful, # +//# but WITHOUT ANY WARRANTY; without even the implied warranty of # +//# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +//# GNU General Public License for more details. # +//# # +//# Please cite the following paper if you find this tool helpful # +//# # +//# Xi, Z.; Hopkinson, C. 3D Graph-Based Individual-Tree Isolation (Treeiso) # +//# from Terrestrial Laser Scanning Point Clouds. Remote Sens. 2022, 14, 6116. # +//# https://doi.org/10.3390/rs14236116 # +//# # +//# Our work relies on the cut-pursuit algorithm, please also consider citing: # +//# Landrieu, L.; Obozinski, G. Cut Pursuit: Fast Algorithms to Learn Piecewise # +//# Constant Functions on General Weighted Graphs. SIAM J. Imaging Sci. # +//# 2017, 10, 1724–1766. # +//# # +//# Copyright © # +//# Artemis Lab, Department of Geography & Environment # +//# University of Lethbridge, Canada # +//# # +//# # +//# Zhouxin Xi and Chris Hopkinson; # +//# truebelief2010@gmail.com; c.hopkinson@uleth.ca # +//# # +//####################################################################################### + +// Matlab and python versions shared via: +// https://github.com/truebelief/artemis_treeiso + +//Local +#include "knncpp.h" +#include "cp_d0_dist.hpp" + +//Eigen +#include + +//STL +#include + +class ccPointCloud; +typedef std::vector Vec3d; + +typedef uint32_t index_t; // For vertex and edge indices +typedef uint16_t comp_t; // For component indices + + +void knn_cpp_build(knncpp::KDTreeMinkowskiX>& kdtree, unsigned n_thread = 0); +void knn_cpp_query(knncpp::KDTreeMinkowskiX>& kdtree, Eigen::MatrixXf& query_points, size_t k, std::vector >& res_idx, std::vector >& res_dists); +float knn_cpp_query_min_d(knncpp::KDTreeMinkowskiX>& kdtree, Eigen::MatrixXf& query_points, size_t k); +void build_knn_graph(const std::vector& points, size_t k, std::vector& first_edge, std::vector& adj_vertices, std::vector& edge_weights, float regStrength1 = 1.0, unsigned n_thread = 8); +void knn_cpp_nearest_neighbors(const std::vector& dataset, size_t k, std::vector>& res_idx, std::vector& res_dists, unsigned n_thread); + + +void load_initseg_points(const std::string& filename, std::vector& points, std::vector& in_component); +bool perform_cut_pursuit(const unsigned K, size_t D, const float regStrength, const std::vector& pc_vec, std::vector& edge_weights, std::vector& Eu, std::vector& Ev, std::vector& in_component, const unsigned threads); + +template +size_t arg_min_col(const std::vector& arr) { + return std::distance(arr.begin(), std::min_element(arr.begin(), arr.end())); +} + +template +size_t arg_max_col(const std::vector& arr) { + return std::distance(arr.begin(), std::max_element(arr.begin(), arr.end())); +} + +template +void min_col(const std::vector>& arr, std::vector& min_vals) { + if (arr.empty()) { + min_vals.clear(); + return; + } + + min_vals = arr[0]; + for (const auto& row : arr) { + std::transform(min_vals.begin(), min_vals.end(), row.begin(), + min_vals.begin(), [](const T& a, const T& b) { return std::min(a, b); }); + } +} + +template +T min_col(const std::vector& arr) { + return arr.empty() ? std::numeric_limits::quiet_NaN() + : *std::min_element(arr.begin(), arr.end()); +} + +template +T mean_col(const std::vector& arr) { + if (arr.empty()) return std::numeric_limits::quiet_NaN(); + return static_cast(std::accumulate(arr.begin(), arr.end(), 0.0) / arr.size()); +} + +template +T median_col(std::vector& arr) { + if (arr.empty()) return std::numeric_limits::quiet_NaN(); + + const size_t n = arr.size(); + const size_t mid = n / 2; + std::nth_element(arr.begin(), arr.begin() + mid, arr.end()); + + if (n % 2 == 0) { + const T right = arr[mid]; + std::nth_element(arr.begin(), arr.begin() + mid - 1, arr.end()); + return (arr[mid - 1] + right) / 2; + } + return arr[mid]; +} + +template +T mode_col(const std::vector& arr) { + if (arr.empty()) return std::numeric_limits::quiet_NaN(); + + std::unordered_map freq; + for (const auto& val : arr) ++freq[val]; + return std::max_element(freq.begin(), freq.end(), + [](const auto& a, const auto& b) { return a.second < b.second; })->first; +} + +template +void max_col(const std::vector>& arr, std::vector& max_vals) { + if (arr.empty()) { + max_vals.clear(); + return; + } + + max_vals = arr[0]; + for (const auto& row : arr) { + std::transform(max_vals.begin(), max_vals.end(), row.begin(), + max_vals.begin(), [](const T& a, const T& b) { return std::max(a, b); }); + } +} + +template +void mean_col(const std::vector>& arr, std::vector& mean_vals) { + if (arr.empty()) { + mean_vals.clear(); + return; + } + + const size_t cols = arr[0].size(); + mean_vals.resize(cols); + std::fill(mean_vals.begin(), mean_vals.end(), T{}); + + for (const auto& row : arr) { + std::transform(mean_vals.begin(), mean_vals.end(), row.begin(), + mean_vals.begin(), std::plus()); + } + + const T size = static_cast(arr.size()); + std::transform(mean_vals.begin(), mean_vals.end(), mean_vals.begin(), + [size](T val) { return val / size; }); +} + +template +void decimate_vec(const std::vector>& arr, T res, std::vector>& vec_dec) { + if (arr.empty() || res <= T{}) { + vec_dec.clear(); + return; + } + + std::vector arr_min; + min_col(arr, arr_min); + + vec_dec.resize(arr.size(), std::vector(arr[0].size())); + for (size_t i = 0; i < arr.size(); ++i) { + std::transform(arr[i].begin(), arr[i].end(), arr_min.begin(), + vec_dec[i].begin(), + [res](T val, T min) { return std::floor((val - min) / res) + T{ 1 }; }); + } +} + + +template +void sort_indexes_by_row(const std::vector>& v, std::vector& idx, + std::vector>& v_sorted) { + if (v.empty()) { + idx.clear(); + v_sorted.clear(); + return; + } + + const size_t rows = v.size(); + const size_t cols = v[0].size(); + + idx.resize(rows); + std::iota(idx.begin(), idx.end(), 0); + + std::stable_sort(idx.begin(), idx.end(), [&v](size_t i1, size_t i2) { + return std::lexicographical_compare(v[i1].begin(), v[i1].end(), + v[i2].begin(), v[i2].end()); + }); + + v_sorted.resize(rows); + for (size_t i = 0; i < rows; ++i) { + v_sorted[i] = v[idx[i]]; + } +} + +template +void sort_indexes(const std::vector& v, std::vector& idx, + std::vector& v_sorted) { + if (v.empty()) { + idx.clear(); + v_sorted.clear(); + return; + } + + idx.resize(v.size()); + std::iota(idx.begin(), idx.end(), 0); + + std::stable_sort(idx.begin(), idx.end(), + [&v](IndexType i1, IndexType i2) { return v[i1] < v[i2]; }); + + v_sorted.resize(v.size()); + std::transform(idx.begin(), idx.end(), v_sorted.begin(), + [&v](IndexType i) { return v[i]; }); +} + +template +void unique_index_by_rows(const std::vector>& arr, + std::vector& ia, std::vector& ic) { + if (arr.empty()) { + ia.clear(); + ic.clear(); + return; + } + + std::vector> arr_sorted; + std::vector sort_idx; + sort_indexes_by_row(arr, sort_idx, arr_sorted); + + const size_t rows = arr_sorted.size(); + ic.resize(rows); + ia.clear(); + ia.push_back(sort_idx[0]); + ic[sort_idx[0]] = 0; + + size_t counter = 0; + for (size_t i = 1; i < rows; ++i) { + if (!std::equal(arr_sorted[i].begin(), arr_sorted[i].end(), + arr_sorted[i - 1].begin())) { + ia.push_back(sort_idx[i]); + ++counter; + } + ic[sort_idx[i]] = counter; + } +} + +template +void to_translated_vector(const ccPointCloud* pc, std::vector>& y) { + if (!pc || pc->size() == 0) { + y.clear(); + return; + } + + const size_t pointCount = pc->size(); + y.resize(pointCount, std::vector(3)); + + std::vector y_mean(3, 0); + for (size_t i = 0; i < pointCount; ++i) { + const CCVector3* pv = pc->getPoint(i); + y[i] = { static_cast(pv->x), static_cast(pv->y), static_cast(pv->z) }; + std::transform(y_mean.begin(), y_mean.end(), y[i].begin(), y_mean.begin(), std::plus()); + } + + std::transform(y_mean.begin(), y_mean.end(), y_mean.begin(), + [pointCount](T val) { return val / pointCount; }); + + for (auto& point : y) { + std::transform(point.begin(), point.end(), y_mean.begin(), point.begin(), std::minus()); + } +} + + +template +void unique_group(const std::vector& arr, std::vector>& u_group, + std::vector& arr_unq, std::vector& ui) { + if (arr.empty()) { + arr_unq.clear(); + ui.clear(); + u_group.clear(); + return; + } + + std::vector arr_sorted_idx; + std::vector arr_sorted; + sort_indexes(arr, arr_sorted_idx, arr_sorted); + + arr_unq.clear(); + ui.clear(); + u_group.clear(); + + ui.push_back(arr_sorted_idx[0]); + std::vector current_group = { arr_sorted_idx[0] }; + + for (size_t i = 1; i < arr.size(); ++i) { + if (arr_sorted[i] != arr_sorted[i - 1]) { + ui.push_back(arr_sorted_idx[i]); + arr_unq.push_back(arr_sorted[i - 1]); + u_group.push_back(std::move(current_group)); + current_group = { arr_sorted_idx[i] }; + } + else { + current_group.push_back(arr_sorted_idx[i]); + } + } + + arr_unq.push_back(arr_sorted.back()); + u_group.push_back(std::move(current_group)); +} + +// Overloaded versions with fewer return parameters +template +void unique_group(const std::vector& arr, std::vector>& u_group, + std::vector& arr_unq) { + std::vector ui; + unique_group(arr, u_group, arr_unq, ui); +} + +template +void unique_group(const std::vector& arr, std::vector>& u_group) { + std::vector arr_unq, ui; + unique_group(arr, u_group, arr_unq, ui); +} + + +template +void get_subset(std::vector& arr, std::vector& indices, std::vector& arr_sub) +{ + arr_sub.clear(); + for (const auto& idx : indices) + { + arr_sub.push_back(arr[idx]); + } +} + +template +void get_subset(const std::vector>& arr, const std::vector& indices, Eigen::MatrixXf& arr_sub) +{ + arr_sub.setZero(); + + if (arr.empty()) + { + assert(false); + return; + } + + arr_sub.resize(arr[0].size(), indices.size()); + + for (size_t i = 0; i < indices.size(); ++i) + { + for (size_t j = 0; j < arr[0].size(); ++j) + { + arr_sub(j, i) = arr[indices[i]][j]; + } + } +} + +template +void get_subset(const std::vector>& arr, const std::vector& indices, std::vector>& arr_sub) +{ + arr_sub.clear(); + + if (arr.empty() || indices.empty()) + { + return; + } + + arr_sub.resize(indices.size()); + for (size_t i = 0; i < indices.size(); ++i) + { + arr_sub[i] = arr[indices[i]]; + } +} + +template +bool get_subset(ccPointCloud* pcd, std::vector& indices, std::vector>& arr_sub) +{ + arr_sub.clear(); + arr_sub.resize(indices.size(), std::vector(3)); + for (size_t i = 0; i < indices.size(); ++i) + { + const CCVector3* vec = pcd->getPoint(indices[i]); + arr_sub[i][0] = vec->x; + arr_sub[i][1] = vec->y; + arr_sub[i][2] = vec->z; + } + return true; +} diff --git a/include/block.hpp b/include/block.hpp new file mode 100644 index 0000000..791c08c --- /dev/null +++ b/include/block.hpp @@ -0,0 +1,291 @@ +/* block.hpp */ +/* Vladimir Kolmogorov vnk@ist.ac.at */ +/* Version slightly modified by Hugo Raguet 2016 (different error handling: no + * error function, message handed to standard error) */ +/* + Template classes Block and DBlock + Implement adding and deleting items of the same type in blocks. + + If there there are many items then using Block or DBlock + is more efficient than using 'new' and 'delete' both in terms + of memory and time since + (1) On some systems there is some minimum amount of memory + that 'new' can allocate (e.g., 64), so if items are + small that a lot of memory is wasted. + (2) 'new' and 'delete' are designed for items of varying size. + If all items has the same size, then an algorithm for + adding and deleting can be made more efficient. + (3) All Block and DBlock functions are inline, so there are + no extra function calls. + + Differences between Block and DBlock: + (1) DBlock allows both adding and deleting items, + whereas Block allows only adding items. + (2) Block has an additional operation of scanning + items added so far (in the order in which they were added). + (3) Block allows to allocate several consecutive + items at a time, whereas DBlock can add only a single item. + + Note that no constructors or destructors are called for items. + + Example usage for items of type 'MyType': + + /////////////////////////////////////////////////// + #include "block.h" + #define BLOCK_SIZE 1024 + typedef struct { int a, b; } MyType; + MyType *ptr, *array[10000]; + + ... + + Block *block = new Block(BLOCK_SIZE); + + // adding items + for (int i=0; i New(); + ptr -> a = ptr -> b = rand(); + } + + // reading items + for (ptr=block->ScanFirst(); ptr; ptr=block->ScanNext()) + { + printf("%d %d\n", ptr->a, ptr->b); + } + + delete block; + + ... + + DBlock *dblock = new DBlock(BLOCK_SIZE); + + // adding items + for (int i=0; i New(); + } + + // deleting items + for (int i=0; i Delete(array[i]); + } + + // adding items + for (int i=0; i New(); + } + + delete dblock; + + /////////////////////////////////////////////////// + + Note that DBlock deletes items by marking them as + empty (i.e., by adding them to the list of free items), + so that this memory could be used for subsequently + added items. Thus, at each moment the memory allocated + is determined by the maximum number of items allocated + simultaneously at earlier moments. All memory is + deallocated only when the destructor is called. +*/ + +#pragma once +#include + +/***********************************************************************/ +/***********************************************************************/ +/***********************************************************************/ + +template class Block +{ +public: + /* Constructor. Arguments are the block size */ + Block(int size) { first = last = nullptr; block_size = size; } + + /* Destructor. Deallocates all items added so far */ + ~Block() { while (first) { block *next = first -> next; delete[] ((char*)first); first = next; } } + + /* Allocates 'num' consecutive items; returns pointer + to the first item. 'num' cannot be greater than the + block size since items must fit in one block */ + Type *New(int num = 1) + { + Type *t; + + if (!last || last->current + num > last->last) + { + if (last && last->next) last = last -> next; + else + { + block *next = (block *) new char [sizeof(block) + (block_size-1)*sizeof(Type)]; + if (!next) { + std::cerr << "Block: not enough memory." << std::endl; + exit(EXIT_FAILURE); + } + if (last) last -> next = next; + else first = next; + last = next; + last -> current = & ( last -> data[0] ); + last -> last = last -> current + block_size; + last -> next = nullptr; + } + } + + t = last -> current; + last -> current += num; + return t; + } + + /* Returns the first item (or nullptr, if no items were added) */ + Type *ScanFirst() + { + for (scan_current_block=first; scan_current_block; scan_current_block = scan_current_block->next) + { + scan_current_data = & ( scan_current_block -> data[0] ); + if (scan_current_data < scan_current_block -> current) return scan_current_data ++; + } + return nullptr; + } + + /* Returns the next item (or nullptr, if all items have been read) + Can be called only if previous ScanFirst() or ScanNext() + call returned not nullptr. */ + Type *ScanNext() + { + while (scan_current_data >= scan_current_block -> current) + { + scan_current_block = scan_current_block -> next; + if (!scan_current_block) return nullptr; + scan_current_data = & ( scan_current_block -> data[0] ); + } + return scan_current_data ++; + } + + struct iterator; // for overlapping scans + Type *ScanFirst(iterator& i) + { + for (i.scan_current_block=first; i.scan_current_block; i.scan_current_block = i.scan_current_block->next) + { + i.scan_current_data = & ( i.scan_current_block -> data[0] ); + if (i.scan_current_data < i.scan_current_block -> current) return i.scan_current_data ++; + } + return nullptr; + } + Type *ScanNext(iterator& i) + { + while (i.scan_current_data >= i.scan_current_block -> current) + { + i.scan_current_block = i.scan_current_block -> next; + if (!i.scan_current_block) return nullptr; + i.scan_current_data = & ( i.scan_current_block -> data[0] ); + } + return i.scan_current_data ++; + } + + /* Marks all elements as empty */ + void Reset() + { + block *b; + if (!first) return; + for (b=first; ; b=b->next) + { + b -> current = & ( b -> data[0] ); + if (b == last) break; + } + last = first; + } + +/***********************************************************************/ + +private: + + typedef struct block_st + { + Type *current, *last; + struct block_st *next; + Type data[1]; + } block; + + int block_size; + block *first; + block *last; +public: + struct iterator + { + block *scan_current_block; + Type *scan_current_data; + }; +private: + block *scan_current_block; + Type *scan_current_data; +}; + +/***********************************************************************/ +/***********************************************************************/ +/***********************************************************************/ + +template class DBlock +{ +public: + /* Constructor. Arguments are the block size and + (optionally) the pointer to the function which + will be called if allocation failed; the message + passed to this function is "Not enough memory!" */ + DBlock(int size){ first = nullptr; first_free = nullptr; block_size = size; } + + /* Destructor. Deallocates all items added so far */ + ~DBlock() { while (first) { block *next = first -> next; delete[] ((char*)first); first = next; } } + + /* Allocates one item */ + Type *New() + { + block_item *item; + + if (!first_free) + { + block *next = first; + first = (block *) new char [sizeof(block) + (block_size-1)*sizeof(block_item)]; + if (!first) { + std::cerr << "Block: not enough memory." << std::endl; + exit(EXIT_FAILURE); + } + first_free = & (first -> data[0] ); + for (item=first_free; item next_free = item + 1; + item -> next_free = nullptr; + first -> next = next; + } + + item = first_free; + first_free = item -> next_free; + return (Type *) item; + } + + /* Deletes an item allocated previously */ + void Delete(Type *t) + { + ((block_item *) t) -> next_free = first_free; + first_free = (block_item *) t; + } + +/***********************************************************************/ + +private: + + typedef union block_item_st + { + Type t; + block_item_st *next_free; + } block_item; + + typedef struct block_st + { + struct block_st *next; + block_item data[1]; + } block; + + int block_size; + block *first; + block_item *first_free; +}; diff --git a/include/ccTreeIsoDlg.h b/include/ccTreeIsoDlg.h index 9a67771..27fccf8 100644 --- a/include/ccTreeIsoDlg.h +++ b/include/ccTreeIsoDlg.h @@ -1,4 +1,6 @@ -//####################################################################################### +#pragma once + +//####################################################################################### //# # //# CLOUDCOMPARE PLUGIN: qTreeIso # //# # @@ -32,9 +34,7 @@ //# # //####################################################################################### - -#pragma once -// A Matlab version shared via: +// Matlab and python versions shared via: // https://github.com/truebelief/artemis_treeiso #ifndef CC_TREEISO_DLG_HEADER diff --git a/include/cp_d0_dist.hpp b/include/cp_d0_dist.hpp new file mode 100644 index 0000000..2694c52 --- /dev/null +++ b/include/cp_d0_dist.hpp @@ -0,0 +1,195 @@ +/*============================================================================= + * Derived class for cut-pursuit algorithm with d0 (weighted contour length) + * penalization, with a loss akin to a distance: + * + * minimize functional over a graph G = (V, E) + * + * F(x) = sum_v loss(y_v, x_v) + ||x||_d0 + * + * where for each vertex, y_v and x_v are D-dimensional vectors, the loss is + * a mix of the sum of square differences and a Kullback-Leibler divergence + * (equivalent to cross-entropy in this formulation); see the 'loss' attribute, + * and ||x||_d0 = sum_{uv in E : xu != xv} w_d0_uv , + * + * using greedy cut-pursuit approach with splitting initialized with k-means++. + * + * Parallel implementation with OpenMP API. + * + * L. Landrieu and G. Obozinski, Cut Pursuit: fast algorithms to learn + * piecewise constant functions on general weighted graphs, SIAM Journal on + * Imaging Science, 10(4):1724-1766, 2017 + * + * L. Landrieu et al., A structured regularization framework for spatially + * smoothing semantic labelings of 3D point clouds, ISPRS Journal of + * Photogrammetry and Remote Sensing, 132:102-118, 2017 + * + * Hugo Raguet 2019, 2022, 2023 + *===========================================================================*/ +#pragma once +#include +#include "cut_pursuit_d0.hpp" + +/* real_t is the real numeric type, used for the base field and for the + * objective functional computation; + * index_t must be able to represent the number of vertices and of (undirected) + * edges in the main graph; + * comp_t must be able to represent the number of constant connected components + * in the reduced graph */ +template +class Cp_d0_dist : public Cp_d0 +{ +public: + /** constructor, destructor **/ + + /* only creates BK graph structure and assign Y, D */ + Cp_d0_dist(index_t V, index_t E, const index_t* first_edge, + const index_t* adj_vertices, const real_t* Y, size_t D = 1); + + /* the destructor does not free pointers which are supposed to be provided + * by the user (forward-star graph structure given at construction, + * monitoring arrays, observation arrays); IT DOES FREE THE REST + * (components assignment and reduced problem elements, etc.), but this can + * be prevented by getting the corresponding pointer member and setting it + * to null beforehand */ + ~Cp_d0_dist(); + + /** methods for manipulating parameters **/ + + /* parameters of d0 penalization (w_d0_uv) can be set using base class Cp + * method set_edge_weights() */ + + /* specific loss */ + real_t quadratic_loss() const { return D; } + + /* Y is changed only if the corresponding argument is not null */ + void set_loss(real_t loss, const real_t* Y = nullptr, + const real_t* vert_weights = nullptr, + const real_t* coor_weights = nullptr); + + /* overload for changing only loss weights */ + void set_loss(const real_t* vert_weights = nullptr, + const real_t* coor_weights = nullptr) + { set_loss(loss, nullptr, vert_weights, coor_weights); } + + /* overload base method for higher init and iter num */ + void set_split_param(index_t max_split_size, comp_t K = 2, + int split_iter_num = 1, real_t split_damp_ratio = 1.0, + int split_values_init_num = 3, int split_values_iter_num = 3); + + void set_min_comp_weight(real_t min_comp_weight = 1.0); + +private: + /** separable loss term: weighted square l2 or smoothed KL **/ + const real_t* Y; // observations, D-by-V array, column major format + + /* D (or public method quadratic_loss()) for quadratic + * f(x) = 1/2 ||y - x||_{l2,W}^2 , + * where W is a diagonal metric (separable product along ℝ^V and ℝ^D), + * that is ||y - x||_{l2,W}^2 = sum_{v in V} w_v ||x_v - y_v||_{l2,M}^2 + * = sum_{v in V} w_v sum_d m_d (x_vd - y_vd)^2. + * + * 0 < loss < 1 for smoothed Kullback-Leibler divergence (equivalent to + * cross-entropy) on the probability simplex + * f(x) = sum_v w_v KLs_m(x_v, y_v), + * with KLs(y_v, x_v) = KL(s u + (1 - s) y_v , s u + (1 - s) x_v), where + * KL is the regular Kullback-Leibler divergence, + * u is the uniform discrete distribution over {1,...,D}, and + * s = loss is the smoothing parameter + * it yields + * KLs(y_v, x_v) = - H(s u + (1 - s) y_v) + * - sum_d (s/D + (1 - s) y_{v,d}) log(s/D + (1 - s) x_{v,d}) , + * where H_m is the entropy, that is H(s u + (1 - s) y_v) + * = - sum_d (s/D + (1 - s) y_{v,d}) log(s/D + (1 - s) y_{v,d}) ; + * note that the choosen order of the arguments in the Kullback-Leibler + * does not favor the entropy of x (H(s u + (1 - s) y_v) is a constant), + * hence this loss is actually equivalent to cross-entropy; + * + * 1 <= loss < D for both: quadratic on coordinates from 1 to loss, and + * Kullback-Leibler divergence on coordinates from loss + 1 to D; + * + * the weights w_v are set in vert_weights and m_d are set in coor_weights; + * set corresponding pointer to null for no weight; note that coordinate + * weights makes no sense for Kullback-Leibler divergence alone, but should + * be used for weighting quadratic and KL when mixing both, in which case + * coor_weights should be of length loss + 1 */ + real_t loss; + const real_t *vert_weights, *coor_weights; + + /* minimum weight allowed for a component */ + real_t min_comp_weight; + + /* compute the functional f at a single vertex */ + /* NOTA: not actually a metric, in spite of its name */ + real_t distance(const real_t* Xv, const real_t* Yv) const; + real_t fv(index_t v, const real_t* Xv) const override; + /* override for storing values (used for iterate evolution) */ + real_t compute_f() const override; + real_t fXY; // dist(X, Y), reinitialized when freeing rX + real_t fYY; // dist(Y, Y), reinitialized when modifying the loss + + /** reduced problem **/ + real_t* comp_weights; + + /* allocate and compute reduced values; + * do nothing if the array of reduced values is not null */ + void solve_reduced_problem() override; + + /** greedy splitting **/ + + /* override for setting observation Yv */ + using typename Cp::Split_info; + void set_split_value(Split_info& split_info, comp_t k, index_t v) const + override; + /* override for average of observations Y */ + void update_split_info(Split_info& split_info) const override; + + /** merging components **/ + + /* compute merge information of the given reduced edge; + * populate member arrays merge_gains and merge_values; allocate value with + * malloc; negative gain values might still get accepted, inacceptable + * merge candidate must be deleted */ + void compute_merge_candidate(index_t re) override; + + /* override for transfering component weights to root component */ + comp_t accept_merge_candidate(index_t re) override; + + /* rough estimate of the number of operations for computing merge info of a + * reduced edge; useful for estimating the number of parallel threads */ + size_t merge_info_complexity() const override; + + index_t merge() override; // override for freeing comp_weights + + /** monitoring evolution **/ + + /* iterate evolution in terms of distance relative to distance to Y */ + real_t compute_evolution() const override; + + /** type resolution for base template class members + * https://isocpp.org/wiki/faq/templates#nondependent-name-lookup-members + **/ + using Cp_d0::delete_merge_candidate; + using Cp_d0::merge_gains; + using Cp_d0::merge_values; + using Cp::set_split_param; + using Cp::saturated_vert; + using Cp::last_comp_assign; + using Cp::eps; + using Cp::D; + using Cp::V; + using Cp::rV; + using Cp::rE; + using Cp::rX; + using Cp::last_rX; + using Cp::monitor_evolution; + using Cp::comp_assign; + using Cp::label_assign; + using Cp::comp_list; + using Cp::first_vertex; + using Cp::reduced_edges_u; + using Cp::reduced_edges_v; + using Cp::reduced_edge_weights; + using Cp::is_saturated; + using Cp::malloc_check; + using Cp::real_inf; +}; diff --git a/include/cut_pursuit.hpp b/include/cut_pursuit.hpp new file mode 100644 index 0000000..10c0f35 --- /dev/null +++ b/include/cut_pursuit.hpp @@ -0,0 +1,427 @@ +/*============================================================================= + * Base class for cut-pursuit algorithm + * + * L. Landrieu and G. Obozinski, Cut Pursuit: Fast Algorithms to Learn + * Piecewise Constant Functions on General Weighted Graphs, SIAM Journal on + * Imaging Sciences, 2017, 10, 1724-1766 + * + * Hugo Raguet 2018, 2020, 2022 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *===========================================================================*/ +#pragma once +#include // for uintmax_t, requires C++11 +#include // for size_t, malloc, exit +#include +#include +#include +#include "maxflow.hpp" + +/* real_t is the real numeric type, used for objective functional computation + * and thus for edge weights and flow graph capacities; + * index_t is an integer type able to hold the number of vertices and of edges + * in the main graph; + * comp_t is an integer type able to hold the maximum number of constant + * connected components in the reduced graph; + * value_t is the type associated to the space to which the values belong, it + * is usually real_t, and if multidimensional, this must be specified in the + * parameter D (e.g. for R^3, specify value_t = real_t and D = 3) */ +template +class Cp +{ +public: + /** constructor, destructor **/ + + Cp(index_t V, index_t E, const index_t* first_edge, + const index_t* adj_vertices, size_t D = 1); + + /* the destructor does not free pointers which are supposed to be provided + * by the user (forward-star graph structure given at construction, + * monitoring arrays, etc.); IT DOES FREE THE REST (components assignment + * and reduced problem elements, etc.), but this can be prevented by + * getting the corresponding pointer member and setting it to null + * beforehand */ + virtual ~Cp(); + + /** methods for manipulating parameters **/ + + void reset_edges(); // bind all edges + + /* if 'edge_weights' is null, homogeneously equal to 'homo_edge_weight' */ + void set_edge_weights(const real_t* edge_weights = nullptr, + real_t homo_edge_weight = 1.0); + + void set_monitoring_arrays(real_t* objective_values = nullptr, + double* elapsed_time = nullptr, real_t* iterate_evolution = nullptr); + + /* if rV is zero or unity, comp_assign will be automatically initialized; + * if rV is zero, arbitrary components will be assigned at initialization, + * in an attempt to optimize parallelization along components; + * if rV is greater than one, comp_assign must be given and initialized; + * comp_assign is free()'d by destructor, unless set to null beforehand */ + void set_components(comp_t rV = 0, comp_t* comp_assign = nullptr); + + void set_cp_param(real_t dif_tol, int it_max, int verbose, real_t eps); + /* overload for default eps parameter */ + void set_cp_param(real_t dif_tol = 0.0, int it_max = 10, + int verbose = 1000) + { + set_cp_param(dif_tol, it_max, verbose, + std::numeric_limits::epsilon()); + } + + /* tune split parameters; set max_split_size to V for no max */ + void set_split_param(index_t max_split_size, comp_t K = 2, + int split_iter_num = 1, real_t split_damp_ratio = 1.0, + int split_values_init_num = 1, int split_values_iter_num = 1); + + //void set_parallel_param(int max_num_threads, + // bool balance_parallel_split = true); + ///* overload for default max_num_threads parameter */ + //void set_parallel_param(bool balance_parallel_split) + //{ + // set_parallel_param(omp_get_max_threads(), balance_parallel_split); + //} + + /* the 'get' methods takes pointers to pointers as arguments; a null means + * that the user is not interested by the corresponding pointer; NOTA: + * 1) if not explicitely set by the user, memory pointed by these members + * is allocated using malloc(), and thus should be deleted with free() + * 2) they are free()'d by destructor, unless set to null beforehand */ + + comp_t get_components(const comp_t** comp_assign = nullptr, + const index_t** first_vertex = nullptr, + const index_t** comp_list = nullptr) const; + + /* return the number of reduced edges */ + index_t get_reduced_graph(const comp_t** reduced_edges = nullptr, + const real_t** reduced_edge_weights = nullptr); + + /* retrieve the reduced iterate (values of the components); + * WARNING: reduced values are free()'d by destructor */ + const value_t* get_reduced_values() const; + + /* set the reduced iterate (values of the components); + * WARNING: if not set to null before deletion of the main cp object, + * this will be deleted by free() so the given pointer must have been + * allocated with malloc() and the likes */ + void set_reduced_values(value_t* rX); + + /* solve the main problem */ + int cut_pursuit(bool init = true); + +protected: + /** main graph **/ + + const index_t V, E; // number of vertices, of edges + + /** forward-star graph representation **/ + /* - edges are numeroted so that all edges originating from a same vertex + * are consecutive; + * - for each vertex, 'first_edge' indicates the first edge starting + * from the vertex (or, if there are none, starting from the next vertex); + * array of length V + 1, the first value is always zero and the last + * value is always the total number of edges E + * - for each edge, 'adj_vertices' indicates its ending vertex */ + const index_t *first_edge, *adj_vertices; + + const real_t *edge_weights; // array of length E, weights of edges + real_t homo_edge_weight; // homogeneous weights, set edge_weights to null + + /* dimension of the data; total size signal is V*D */ + const size_t D; + + /** reduced graph **/ + + /* last_* are used to identify saturated components and to compute + * iterate evolution */ + comp_t rV, last_rV; // number of components (reduced vertices) + value_t *rX, *last_rX; // reduced iterate (values of the components) + index_t rE; // number of reduced edges + /* assignment of each vertex to a component */ + comp_t* comp_assign, *last_comp_assign; + /* list the vertices of each components: + * - vertices are gathered in 'comp_list' so that all vertices belonging + * to a same components are consecutive + * - for each component, 'first_vertex' indicates the index of its first + * vertex in 'comp_list' */ + index_t *comp_list, *first_vertex; + /* reverse mapping of comp list: index of a given vertex within its + * components (useful for working within components in parallel) */ + index_t *index_in_comp; + /* components saturation */ + bool* is_saturated; + comp_t saturated_comp; // number of saturated components + index_t saturated_vert; // number of vertices within saturated components + + /* reduced connectivity + * reduced edges represented with edges list (array of size twice the + * number of reduced edges, consecutive indices are linked components) + * guarantees: + * 1) starting component identifiers are smaller than ending components + * 2) increasing order of starting and ending components identifiers + * (this eases some routines, like conversion to forward-star) + * 3) each edge appears only once + * 4) isolated components (not linked to any other component) are linked + * to themselves with epsilon reduced weight */ + comp_t* reduced_edges; + + /* easy accessors for reduced_edges */ + const comp_t& reduced_edges_u(index_t re) const + { return reduced_edges[((size_t) 2)*re]; } + const comp_t& reduced_edges_v(index_t re) const + { return reduced_edges[((size_t) 2)*re + 1]; } + comp_t& reduced_edges_u(index_t re) + { return reduced_edges[((size_t) 2)*re]; } + comp_t& reduced_edges_v(index_t re) + { return reduced_edges[((size_t) 2)*re + 1]; } + + real_t* reduced_edge_weights; + + /** parameters **/ + + real_t dif_tol, eps; // eps gives a characteristic precision + /* with nonzero verbose information on the process will be printed; + * for convex methods, this will be passed on to the reduced problem + * subroutine, controlling the number of subiterations between prints */ + int verbose; + + /** split components with graph cuts **/ + struct Split_info { + comp_t rv; // component to split + comp_t K; // number of alternative values in the component's split + /* first alternative to compete, useful to avoid competing with a value + * already assigned to all vertices, or for single cut with K = 2 */ + comp_t first_k; + value_t* sX; // D-by-K array with alternative values in the split + Split_info(comp_t rv); + ~Split_info(); + }; + comp_t K; // maximum number of alternative values in any component's split + int split_iter_num; // number of partition-and-update iterations + real_t split_damp_ratio; // split damping along iterations + /* number of repetitions in case of stochastic split values computation */ + int split_values_init_num; + int split_values_iter_num; + + virtual index_t split(); + + virtual void split_component(comp_t rv, Maxflow* maxflow); + + /* initialize candidate split values, schedule and assignments; + * base class version implements a kmeans++, with distances replaced by + * split costs, and centroids assignments and updates made purely virtual + * for specializing data and computations */ + virtual Split_info initialize_split_info(comp_t rv); + /* make split value k that would optimaly fit vertex v */ + virtual void set_split_value(Split_info& split_info, comp_t k, index_t v) + const = 0; + /* usually some kind of averaging; must remove alternative values which are + * no longer interesting (e.g. associated to no vertex) */ + virtual void update_split_info(Split_info& split_info) const = 0; + /* rough estimate of the number of operations for initializing the split + * values and all subsequent updates */ + virtual uintmax_t split_values_complexity() const; + /* compute unary cost of split value k at vertex v in component rv; + * can be +infinity, not -infinity */ + virtual real_t vert_split_cost(const Split_info& split_info, index_t v, + comp_t k) const = 0; + /* overload for possibly saving computations for the difference when + * choosing alternative k against alternative l */ + virtual real_t vert_split_cost(const Split_info& split_info, index_t v, + comp_t k, comp_t l) const; + /* compute binary cost of choosing alternatives lu and lv at edge e */ + virtual real_t edge_split_cost(const Split_info& split_info, index_t e, + comp_t lu, comp_t lv) const = 0; + + /* methods for setting and checking edge status */ + bool is_cut(index_t e) const // check if edge e is cut (active) + { return edge_status[e] == CUT; } + bool is_bind(index_t e) const // check if edge e is binding (inactive) + { return edge_status[e] == BIND; } + bool is_separation(index_t e) const // check if edge is a separation + { return edge_status[e] == SEPARATION; } + void cut(index_t e) // flag a cut (active) edge + { edge_status[e] = CUT; } + void bind(index_t e) // flag a binding (inactive) edge + { edge_status[e] = BIND; } + void separate(index_t e) // flag a balancing separation edge + { edge_status[e] = SEPARATION; } + + /* split large components for balancing split, either for parallelism or + * for preventing bad maxflow performance on huge components; + * new components are computed by breadth-first search, restarting when a + * maximum size is reached; + * reorder comp_list and populate first vertex accordingly; + * rV_new is the number of components resulting from such split; + * rV_big is the number of large original components split this way; + * first_vertex_big holds the first vertices of components split this way; + * returns the number of useful parallel threads */ + int balance_split(comp_t& rV_new, comp_t& rV_big, + index_t*& first_vertex_big); + + /* after splitting, separation edges must be removed or activated; + * when called, first_vertex contains additional components due to large + * components being split by balance_split(); + * NOTA: currently, separation edges must be either removed or activated + * at this step; this cannot wait for a future split step, because + * components list of vertex must be kept consecutive for parallel + * treatment of the resulting connected components, and removing parallel + * separation edges in a later step might connect components whose list of + * vertices are not consecutive */ + virtual index_t remove_balance_separations(comp_t rV_new); + + /* revert the above process; + * no change to comp_list, only suppress elements from first_vertex */ + void revert_balance_split(comp_t rV_new, comp_t rV_big, + index_t* first_vertex_big); + + /* rough estimate of the number of operations for split step; + * useful for estimating the number of parallel threads */ + uintmax_t maxflow_complexity() const + { return (uintmax_t) 2*E + V; } // just for a graph cut; heuristic + virtual uintmax_t split_complexity() const; + + /* prefered alternative value for each vertex */ + comp_t*& label_assign = comp_assign; // reuse the same storage + + /** compute reduced values **/ + + virtual void solve_reduced_problem() = 0; + + /** merging components when deemed useful **/ + + /* during the merging step, merged components are stored as chains, + * represented by arrays of length rV 'merge_chains_root', '_next' and + * '_leaf'; merge chain involving component rv follows the scheme + * root[rv] -> ... -> rv -> next[rv] -> ... -> leaf[rv] ; + * NOTA: CHAIN_END is a special values, and: + * - only next[rv] is always up-to-date; + * - root[rv] is always a strictly preceding component in its chain, or + * CHAIN_END if rv is a root; + * - leaf[rv] is up-to-date if rv is a root; + * - rv is the leaf of its chain if, and only if next[rv] == CHAIN_END; + * an additional requirement is that the root of each chain should be the + * component in the chain with lowest index */ + comp_t get_merge_chain_root(comp_t rv) const; + + /* merge the merge chains of the two given roots; + * the root of the resulting chain will be the component in the chains + * with lowest index, which is returned by the function */ + comp_t merge_components(comp_t ru, comp_t rv); + + /* compute the merge chains and return the number of effective merges */ + virtual comp_t compute_merge_chains() = 0; + + /* main routine using the above to perform the merge step; + * NOTA: reduced edges must guarantee 1-4), see member declaration */ + virtual index_t merge(); + + /** monitoring evolution **/ + + /* test if computation of evolution is required */ + virtual bool monitor_evolution() const + { return dif_tol > (real_t) 0.0 || iterate_evolution; } + + /* compute relative iterate evolution */ + virtual real_t compute_evolution() const = 0; + + /* compute objective functional, often on the reduced problem objects */ + virtual real_t compute_objective() const = 0; + + /* allocate memory and fail with error message if not successful */ + static void* malloc_check(size_t size) + { + void *ptr = malloc(size); + if (!ptr){ + std::cerr << "Cut-pursuit: not enough memory." << std::endl; + exit(EXIT_FAILURE); + } + return ptr; + } + + /* simply free if size is zero */ + static void* realloc_check(void* ptr, size_t size) + { + if (!size){ + free(ptr); + return nullptr; + } + ptr = realloc(ptr, size); + if (!ptr){ + std::cerr << "Cut-pursuit: not enough memory." << std::endl; + exit(EXIT_FAILURE); + } + return ptr; + } + + ///** control parallelization **/ + //int max_num_threads; // maximum number of parallel threads + ///* take into account max_num_threads attribute */ + //int compute_num_threads(uintmax_t num_ops, uintmax_t max_threads) const + //{ + // int num_threads = ::compute_num_threads(num_ops, max_threads); + // return num_threads < max_num_threads ? num_threads : max_num_threads; + //} + ///* overload for max_threads defaulting to num_ops */ + //int compute_num_threads(uintmax_t num_ops) const + //{ return compute_num_threads(num_ops, num_ops); } + + /* representing infinite values (has_infinity checked by constructor) */ + static real_t real_inf(){ return std::numeric_limits::infinity(); } + +private: + enum Edge_status : char // requires C++11 to ensure 1 byte + {BIND, CUT, SEPARATION}; + Edge_status* edge_status; // edge activation + + /* parameters */ + int it_max; // maximum number of cut-pursuit iterations + //bool balance_parallel_split; // switch parallel split balancing + index_t max_split_size; // ensure maxflow not working on components too big + + /* monitoring */ + real_t* objective_values; + double* elapsed_time; + real_t* iterate_evolution; + + /* during the merging step, merged components are stored as chains */ + comp_t *merge_chains_root, *merge_chains_next, *merge_chains_leaf; + + double monitor_time(std::chrono::steady_clock::time_point start) const; + + void print_progress(int it, real_t dif, double t) const; + + /* set components assignment and values (and allocate them if needed); + * assumes that no edge of the graph are cut when it is called */ + void initialize(); + + /* initialize with components specified in 'comp_assign' */ + void assign_connected_components(); + + /* initialize with only one component and reduced graph accordingly */ + void single_connected_component(); + + /* compute binding reverse edge forward star graph structure */ + void get_bind_reverse_edges(comp_t rv, index_t*& first_edge_r, + index_t*& adj_vertices_r); + + /* update connected components and count saturated ones */ + void compute_connected_components(); + + /* allocate and compute reduced graph structure; + * NOTA: reduced edges must guarantee 1-4), see member declaration */ + void compute_reduced_graph(); +}; diff --git a/include/cut_pursuit_d0.hpp b/include/cut_pursuit_d0.hpp new file mode 100644 index 0000000..674d5a0 --- /dev/null +++ b/include/cut_pursuit_d0.hpp @@ -0,0 +1,133 @@ +/*============================================================================= + * Derived class for cut-pursuit algorithm with d0 (weighted contour length) + * penalization, with a separable loss term over a given space: + * + * minimize functional over a graph G = (V, E) + * + * F(x) = f(x) + ||x||_d0 + * + * where for each vertex, x_v belongs in a possibly multidimensional space Ω, + * f(x) = sum_{v in V} f_v(x_v) is separable along V with f_v : Ω → ℝ + * and ||x||_d0 = sum_{uv in E : xu != xv} w_d0_uv , + * + * using greedy cut-pursuit approach. + * + * Parallel implementation with OpenMP API. + * + * References: + * + * L. Landrieu and G. Obozinski, Cut Pursuit: fast algorithms to learn + * piecewise constant functions on general weighted graphs, SIAM Journal on + * Imaging Science, 10(4):1724-1766, 2017 + * + * L. Landrieu et al., A structured regularization framework for spatially + * smoothing semantic labelings of 3D point clouds, ISPRS Journal of + * Photogrammetry and Remote Sensing, 132:102-118, 2017 + * + * Hugo Raguet 2019, 2020 + *===========================================================================*/ +#pragma once +#include "cut_pursuit.hpp" + +/* real_t is the real numeric type, used for objective functional computation; + * index_t must be able to represent the number of vertices and of (undirected) + * edges in the main graph; + * comp_t must be able to represent the number of constant connected components + * in the reduced graph; + * value_t is the type associated to the space to which the values belong, it + * is usually real_t, and if multidimensional, this must be specified in the + * parameter D (e.g. for R^3, specify value_t = real_t and D = 3) */ +template +class Cp_d0 : public Cp +{ +public: + Cp_d0(index_t V, index_t E, const index_t* first_edge, + const index_t* adj_vertices, size_t D = 1); + +protected: + /* compute the functional f at a single vertex */ + virtual real_t fv(index_t v, const value_t* Xv) const = 0; + + /* compute graph contour length; use reduced edges and reduced weights */ + real_t compute_graph_d0() const; + + /* compute objective functional */ + virtual real_t compute_f() const; + real_t compute_objective() const override; + + /** greedy splitting **/ + + /* compute unary cost of split value k at vertex v in component rv */ + using typename Cp::Split_info; + real_t vert_split_cost(const Split_info& split_info, index_t v, + comp_t k) const override; + /* compute binary cost of choosing alternatives lu and lv at edge e */ + real_t edge_split_cost(const Split_info& split_info, index_t e, + comp_t lu, comp_t lv) const override; + + /** merging components **/ + + /* the strategy is to compute the gain on the functional for the merge of + * each reduced edge, and accept greedily the candidates with greatest + * gain; merge gains and values of neighboring components might be impacted + * by the merge of two components, see implementation for details; override + * the following virtual merge methods for taking additional information + * into account + * NOTA: during the merging step, merged components are stored as chains, + * see header `cut_pursuit.hpp` for details */ + + /* arrays are indexed by reduced edges */ + real_t* merge_gains; // gain on the objective if components are merged + value_t** merge_values; // the value of the components if they are merged + + /* compute merge information of the given reduced edge; + * populate member arrays merge_gains and merge_values; allocate value with + * malloc; negative gain values might still get accepted, inacceptable + * merge candidate must be deleted */ + virtual void compute_merge_candidate(index_t re) = 0; + + /* accept and delete the merge candidate, and return the component root of + * the resulting merge chain; can be overriden to take into account other + * merge effects */ + virtual comp_t accept_merge_candidate(index_t re); + + /* frees the merge value and flag it to null pointer */ + void delete_merge_candidate(index_t re); + + /* rough estimate of the number of operations for computing merge info of a + * reduced edge; useful for estimating the number of parallel threads */ + virtual size_t merge_info_complexity() const = 0; + + + + /** type resolution for base template class members + * https://isocpp.org/wiki/faq/templates#nondependent-name-lookup-members + **/ + using Cp::get_merge_chain_root; + using Cp::K; + using Cp::split_iter_num; + using Cp::split_damp_ratio; + using Cp::split_values_init_num; + using Cp::split_values_iter_num; + using Cp::V; + using Cp::E; + using Cp::D; + using Cp::rV; + using Cp::rE; + using Cp::rX; + using Cp::edge_weights; + using Cp::homo_edge_weight; + using Cp::comp_list; + using Cp::first_vertex; + using Cp::reduced_edges_u; + using Cp::reduced_edges_v; + using Cp::reduced_edge_weights; + using Cp::merge_components; + using Cp::realloc_check; + using Cp::malloc_check; + +private: + /* compute the merge chains and return the number of effective merges */ + comp_t compute_merge_chains() override; +}; diff --git a/include/maxflow.hpp b/include/maxflow.hpp new file mode 100644 index 0000000..948ae43 --- /dev/null +++ b/include/maxflow.hpp @@ -0,0 +1,157 @@ +/* maxflow.hpp */ +/* modified from graph.h by Hugo Raguet 2020, for use with cut-pursuit + * algorithms */ +/* + Copyright Vladimir Kolmogorov and Yuri Boykov + + This file is part of MAXFLOW. + + MAXFLOW is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + MAXFLOW is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with MAXFLOW. If not, see . +=============================================================================*/ + +#pragma once +#include "block.hpp" + +/* index_t is an integer type able to hold the number of nodes and of edges; + * flow_t is a numeric type for the flow (capacities) */ +template class Maxflow +{ +public: + Maxflow(index_t node_num, index_t edge_num); + + ~Maxflow(); + + void add_edge(index_t i, index_t j); + + flow_t& terminal_capacity(index_t i); + + void set_edge_capacities(index_t e, flow_t cap, flow_t rev_cap); + + /* retrieve (signed) flow passing through a given edge + second parameter is the initial capacity */ + flow_t get_edge_flow(index_t e, flow_t cap); + + void maxflow(); + + bool is_sink(index_t i, bool default_side = false); + +private: + struct node; + struct arc; + + // internal variables and functions + + struct node + { + arc *first; // first outcoming arc + arc *parent; // node's parent + node *next; // pointer to the next active node + index_t TS; // timestamp showing when DIST was computed + index_t DIST; // distance to the terminal + bool is_sink : 1; // indicate source or sink tree, if parent not null + /* positive if connected to source, negative if connected to sink */ + flow_t term_res_cap; + }; + + struct arc + { + node* head; // node the arc points to + arc* next; // next arc with the same originating node + arc* sister; // reverse arc + flow_t res_cap; // residual capacity + }; + + struct nodeptr + { + node *ptr; + nodeptr *next; + }; + static const int NODEPTR_BLOCK_SIZE = 128; + + node *nodes, *node_last; + arc *arcs, *arc_last; + + /* special constants for parent arcs */ + arc reserved_terminal_arc; // the parent is an arc to terminal + arc* const terminal; + arc reserved_orphan_arc; // no parent + arc* const orphan; + + DBlock *nodeptr_block; + + node *queue_first[2], *queue_last[2]; // list of active nodes + nodeptr *orphan_first, *orphan_last; // list of pointers to orphans + index_t TIME; // monotonically increasing global counter + + // functions for processing active list + void set_active(node *i); + node *next_active(); + + // functions for processing orphans list + void set_orphan_front(node* i); // add to the beginning of the list + void set_orphan_rear(node* i); // add to the end of the list + + void maxflow_init(); // called if reuse_trees == false + void augment(arc *middle_arc); + void process_source_orphan(node *i); + void process_sink_orphan(node *i); +}; + +#define TPL template +#define MXFL Maxflow + +TPL inline void MXFL::add_edge(index_t _i, index_t _j) +{ + arc *a = arc_last++; + arc *a_rev = arc_last++; + + node* i = nodes + _i; + node* j = nodes + _j; + + a->sister = a_rev; + a_rev->sister = a; + a->next = i->first; + i->first = a; + a_rev->next = j->first; + j->first = a_rev; + a->head = j; + a_rev->head = i; +} + +TPL inline flow_t& MXFL::terminal_capacity(index_t i) +{ + return nodes[i].term_res_cap; +} + +TPL inline void MXFL::set_edge_capacities(index_t e, flow_t cap, + flow_t rev_cap) +{ + arc* a = arcs + (size_t) 2*e; + a->res_cap = cap; + (a + 1)->res_cap = rev_cap; +} + +TPL inline flow_t MXFL::get_edge_flow(index_t e, flow_t cap) +{ + arc* a = arcs + (size_t) 2*e; + return cap - a->res_cap; +} + +TPL inline bool MXFL::is_sink(index_t i, bool default_side) +{ + return nodes[i].parent ? nodes[i].is_sink : default_side; +} + +#undef TPL +#undef MXFL diff --git a/include/qTreeIso.h b/include/qTreeIso.h index 3f7aabd..142041d 100644 --- a/include/qTreeIso.h +++ b/include/qTreeIso.h @@ -34,7 +34,7 @@ //# # //####################################################################################### -// A Matlab version shared via: +// Matlab and python versions shared via: // https://github.com/truebelief/artemis_treeiso #include "ccStdPluginInterface.h" @@ -67,11 +67,13 @@ public: float reg_strength1 = 1.0f; //lambda1 int min_nn1 = 5; //K1:key parameter float decimate_res1 = 0.05f; + int threads1 = 1; int reg_strength2 = 20; //lambda2:key parameter int min_nn2 = 20; //K2:key parameter float decimate_res2 = 0.1f; float max_gap = 2.0f; + int threads2 = 1; float rel_height_length_ratio = 0.5f; //rho float vertical_weight = 0.5; //w:key parameter diff --git a/include/qTreeIsoCommands.h b/include/qTreeIsoCommands.h index 9fce289..d0f9005 100644 --- a/include/qTreeIsoCommands.h +++ b/include/qTreeIsoCommands.h @@ -34,7 +34,7 @@ //# # //####################################################################################### -// A Matlab version shared via: +// Matlab and python versions shared via: // https://github.com/truebelief/artemis_treeiso //CloudCompare @@ -221,14 +221,14 @@ struct CommandTreeIso : public ccCommandLineInterface::Command if (try_init_seg) { - if (!TreeIso::Init_seg_pcd(desc.pc, parameters.min_nn1, parameters.reg_strength1, parameters.decimate_res1)) + if (!TreeIso::Init_seg_pcd(desc.pc, parameters.min_nn1, parameters.reg_strength1, parameters.decimate_res1, parameters.threads1)) { return cmd.error("Failed to finish initial segmentation due to unknown reasons."); } } if (try_intermediate_seg) { - if (!TreeIso::Intermediate_seg_pcd(desc.pc, parameters.min_nn2, parameters.reg_strength2, parameters.decimate_res2, parameters.max_gap)) + if (!TreeIso::Intermediate_seg_pcd(desc.pc, parameters.min_nn2, parameters.reg_strength2, parameters.decimate_res2, parameters.max_gap, parameters.threads2)) { return cmd.error("Failed to finish intermediate segmentation due to unknown reasons."); } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c2429b5..b347916 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -2,6 +2,10 @@ target_sources( ${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_LIST_DIR}/ccTreeIsoDlg.cpp + ${CMAKE_CURRENT_LIST_DIR}/cp_d0_dist.cpp + ${CMAKE_CURRENT_LIST_DIR}/cut_pursuit.cpp + ${CMAKE_CURRENT_LIST_DIR}/cut_pursuit_d0.cpp + ${CMAKE_CURRENT_LIST_DIR}/maxflow.cpp ${CMAKE_CURRENT_LIST_DIR}/TreeIso.cpp ${CMAKE_CURRENT_LIST_DIR}/qTreeIso.cpp ) diff --git a/src/TreeIso.cpp b/src/TreeIso.cpp index c95be3a..b3b67c7 100644 --- a/src/TreeIso.cpp +++ b/src/TreeIso.cpp @@ -32,12 +32,12 @@ //# # //####################################################################################### -// A Matlab version shared via: +// Matlab and python versions shared via: // https://github.com/truebelief/artemis_treeiso //TreeIso #include "TreeIso.h" -#include "TreeIsoHelper.h" +#include "TreeIsoHelper.hpp" //CC #include @@ -57,21 +57,8 @@ #include #include #include - -//Boost -#include -#include -#include - -typedef boost::geometry::model::d2::point_xy point_xy; -typedef boost::geometry::model::polygon polygon; -typedef boost::geometry::model::multi_point multi_point; -typedef boost::geometry::model::multi_polygon multi_polygon; - -typedef std::vector Vec3d; - -//custom -using namespace CP; +#include +#include //scalar field names static const char InitSegsSFName[] = "init_segs"; @@ -86,7 +73,44 @@ static auto Since(std::chrono::time_point const& start) return std::chrono::duration_cast(clock_t::now() - start); } -bool TreeIso::Init_seg_pcd(ccPointCloud* pc, const unsigned min_nn1, const float regStrength1, const float PR_DECIMATE_RES1, QProgressDialog* progressDlg/*=nullptr*/) + +struct BBox { + float minX, minY, maxX, maxY; + + float area() const { + return (maxX - minX) * (maxY - minY); + } + + static BBox from_points(const std::vector& points) { + BBox bbox = { + std::numeric_limits::max(), + std::numeric_limits::max(), + -std::numeric_limits::max(), + -std::numeric_limits::max() + }; + + for (const auto& p : points) { + bbox.minX = std::min(bbox.minX, p[0]); + bbox.minY = std::min(bbox.minY, p[1]); + bbox.maxX = std::max(bbox.maxX, p[0]); + bbox.maxY = std::max(bbox.maxY, p[1]); + } + return bbox; + } + + static float overlap_ratio(const BBox& a, const BBox& b) { + float intersectX = std::max(0.0f, + std::min(a.maxX, b.maxX) - std::max(a.minX, b.minX)); + float intersectY = std::max(0.0f, + std::min(a.maxY, b.maxY) - std::max(a.minY, b.minY)); + float intersectArea = intersectX * intersectY; + float minArea = std::min(a.area(), b.area()); + return intersectArea / minArea; + } +}; + + +bool TreeIso::Init_seg_pcd(ccPointCloud* pc, const unsigned PR_MIN_NN1, const float PR_REG_STRENGTH1, const float PR_DECIMATE_RES1, const unsigned PR_THREAD1, QProgressDialog* progressDlg/*=nullptr*/) { if (!pc) { @@ -104,7 +128,7 @@ bool TreeIso::Init_seg_pcd(ccPointCloud* pc, const unsigned min_nn1, const float const unsigned pointCount = pc->size(); std::vector pc_vec; - toTranslatedVector(pc, pc_vec); + to_translated_vector(pc, pc_vec); if (progressDlg) { @@ -124,14 +148,14 @@ bool TreeIso::Init_seg_pcd(ccPointCloud* pc, const unsigned min_nn1, const float QCoreApplication::processEvents(); } - const unsigned K = (min_nn1 - 1); + const unsigned K = (PR_MIN_NN1 - 1); Vec3d edgeWeight; std::vector Eu; std::vector Ev; std::vector in_component; std::vector> components; - perform_cut_pursuit(K, regStrength1, pc_sub, edgeWeight, Eu, Ev, in_component, components); + perform_cut_pursuit(K, 3, PR_REG_STRENGTH1, pc_sub, edgeWeight, Eu, Ev, in_component, PR_THREAD1); if (progressDlg) { @@ -156,8 +180,7 @@ bool TreeIso::Init_seg_pcd(ccPointCloud* pc, const unsigned min_nn1, const float std::vector clusterIdx(pointCount); for (unsigned i = 0; i < pointCount; ++i) { - clusterIdx[i] = in_component[ic[i]]; - outSF->setValue(i, in_component[ic[i]]); + outSF->setValue(i, (int)in_component[ic[i]]); } outSF->computeMinAndMax(); pc->colorsHaveChanged(); @@ -176,7 +199,8 @@ bool TreeIso::Init_seg_pcd(ccPointCloud* pc, const unsigned min_nn1, const float return true; } -bool TreeIso::Intermediate_seg_pcd(ccPointCloud* pc, const unsigned PR_MIN_NN2, const float PR_REG_STRENGTH2, const float PR_DECIMATE_RES2, const float PR_MAX_GAP, QProgressDialog* progressDlg/*=nullptr*/) + +bool TreeIso::Intermediate_seg_pcd(ccPointCloud* pc, const unsigned PR_MIN_NN2, const float PR_REG_STRENGTH2, const float PR_DECIMATE_RES2, const float PR_MAX_GAP, const unsigned PR_THREADS2, QProgressDialog* progressDlg/*=nullptr*/) { if (!pc) { @@ -201,7 +225,8 @@ bool TreeIso::Intermediate_seg_pcd(ccPointCloud* pc, const unsigned PR_MIN_NN2, } CCCoreLib::ScalarField* initSF = pc->getScalarField(initSFIndex); - std::vector in_component; + std::vector in_component; + try { in_component.resize(pointCount); @@ -215,11 +240,16 @@ bool TreeIso::Intermediate_seg_pcd(ccPointCloud* pc, const unsigned PR_MIN_NN2, { in_component[i] = initSF->getValue(i); } - std::vector> clusterVGroup; - unique_group(in_component, clusterVGroup); - std::vector pc_vec; - toTranslatedVector(pc, pc_vec); + to_translated_vector(pc, pc_vec); + + //std::vector pc_vec; + //load_initseg_points("F:\\treeiso\\data\\JP10_plot_2cm_test2_treeiso_dec_res.txt", pc_vec, in_component); + //std::cout << "Loaded " << pc_vec.size() << " points" << std::endl; + //unsigned pointCount = pc_vec.size(); + + std::vector> clusterVGroup; + unique_group(in_component, clusterVGroup); size_t n_clusters = clusterVGroup.size(); @@ -262,7 +292,7 @@ bool TreeIso::Intermediate_seg_pcd(ccPointCloud* pc, const unsigned PR_MIN_NN2, std::vector> minIdxsC; std::vector minIdxsD; - knn_cpp_nearest_neighbors(clusterCentroids, PR_MIN_NN2, minIdxsC, minIdxsD, 8); + knn_cpp_nearest_neighbors(clusterCentroids, PR_MIN_NN2, minIdxsC, minIdxsD, PR_THREADS2); if (progressDlg) { @@ -301,7 +331,7 @@ bool TreeIso::Intermediate_seg_pcd(ccPointCloud* pc, const unsigned PR_MIN_NN2, for (size_t i = 0; i < n_centroids; ++i) { knncpp::KDTreeMinkowskiX> knn_kdtree(currentClusterDecMats[minIdxsC[i][0]]); - knn_cpp_build(knn_kdtree); + knn_cpp_build(knn_kdtree, PR_THREADS2); for (size_t j = 1; j < n_K; ++j) { if (minIdxsD[i][j] > 0) @@ -349,21 +379,29 @@ bool TreeIso::Intermediate_seg_pcd(ccPointCloud* pc, const unsigned PR_MIN_NN2, QCoreApplication::processEvents(); } - size_t nNodes = currentClusterDecsFlat.size(); - unsigned nKs = PR_MIN_NN2; std::vector> minIdxs; std::vector Ds; - knn_cpp_nearest_neighbors(currentClusterDecsFlat, PR_MIN_NN2, minIdxs, Ds, 8); + knn_cpp_nearest_neighbors(currentClusterDecsFlat, PR_MIN_NN2, minIdxs, Ds, PR_THREADS2); - Vec3d edgeWeight; - std::vector Eu; - std::vector Ev; + std::vector edgeWeight; + std::vector Eu; + std::vector Ev; - for (size_t i = 0; i < minIdxs.size(); i++) + Eu.resize(minIdxs.size() + 1); + Eu[0] = 0; + + Ev.clear(); + edgeWeight.clear(); + Ev.reserve(minIdxs.size() * minIdxs[0].size()); + edgeWeight.reserve(minIdxs.size() * minIdxs[0].size()); + + for (size_t i = 0; i < minIdxs.size(); ++i) { size_t currentNode = currentClusterDecsFlatIndex[i]; Vec3d currentDists = nnDists[currentNode]; + + size_t edges_for_point = 0; for (size_t j = 1; j < minIdxs[0].size(); ++j) { size_t nnNode = currentClusterDecsFlatIndex[minIdxs[i][j]]; @@ -374,12 +412,14 @@ bool TreeIso::Intermediate_seg_pcd(ccPointCloud* pc, const unsigned PR_MIN_NN2, float nnDist = currentDists[it - nnCand.begin()]; if (nnDist < PR_MAX_GAP) { - Eu.push_back(static_cast(i)); - Ev.push_back(minIdxs[i][j]); - edgeWeight.push_back(10 / ((nnDist + 0.1) / 0.01)); + //Eu.push_back(static_cast(i)); + Ev.push_back(static_cast(minIdxs[i][j])); + edgeWeight.push_back(10 / ((nnDist + 0.001) / 0.01) * PR_REG_STRENGTH2);//when there is edge weight other than 1, this PR_REG_STRENGTH2 will be ignored; so multiply PR_REG_STRENGTH2 here + edges_for_point++; } } } + Eu[i + 1] = Eu[i] + static_cast(edges_for_point); } if (progressDlg) @@ -388,8 +428,10 @@ bool TreeIso::Intermediate_seg_pcd(ccPointCloud* pc, const unsigned PR_MIN_NN2, QCoreApplication::processEvents(); } - std::vector in_component2d; - perform_cut_pursuit2d(PR_MIN_NN2, PR_REG_STRENGTH2, currentClusterDecsFlat, edgeWeight, Eu, Ev, in_component2d); + std::vector in_component2d; + perform_cut_pursuit(PR_MIN_NN2, 2, PR_REG_STRENGTH2, currentClusterDecsFlat, edgeWeight, Eu, Ev, in_component2d, 0); + + if (progressDlg) { @@ -456,7 +498,6 @@ bool TreeIso::Intermediate_seg_pcd(ccPointCloud* pc, const unsigned PR_MIN_NN2, return true; } - bool TreeIso::Final_seg_pcd(ccPointCloud* pc, const unsigned PR_MIN_NN3, const float PR_REL_HEIGHT_LENGTH_RATIO, const float PR_VERTICAL_WEIGHT, QProgressDialog* progressDlg/*=nullptr*/) { if (!pc) @@ -474,7 +515,7 @@ bool TreeIso::Final_seg_pcd(ccPointCloud* pc, const unsigned PR_MIN_NN3, const f unsigned pointCount = pc->size(); std::vector pc_vec; - toTranslatedVector(pc, pc_vec); + to_translated_vector(pc, pc_vec); int initIdx = pc->getScalarFieldIndexByName(InitSegsSFName); if (initIdx < 0) @@ -512,7 +553,7 @@ bool TreeIso::Final_seg_pcd(ccPointCloud* pc, const unsigned PR_MIN_NN3, const f { segs_group_ids[i] = groupSF->getValue(i); } - + std::vector> initVGroup; std::vector initU; std::vector initUI; @@ -528,7 +569,7 @@ bool TreeIso::Final_seg_pcd(ccPointCloud* pc, const unsigned PR_MIN_NN3, const f Vec3d clusterCentroid; mean_col(clusterPts, clusterCentroid); clusterCentroids[i] = clusterCentroid; - + std::vector segs_group_id; get_subset(segs_group_ids, initVGroup[i], segs_group_id); float seg_group_mode = mode_col(segs_group_id); @@ -577,9 +618,8 @@ bool TreeIso::Final_seg_pcd(ccPointCloud* pc, const unsigned PR_MIN_NN3, const f std::vector lenFeatures; lenFeatures.resize(nGroups); - std::vector groupHulls; - for (size_t i = 0; i < nGroups; ++i) - { + std::vector groupBBoxes; + for (size_t i = 0; i < nGroups; ++i) { std::vector groupPts; get_subset(pc_vec, groupVGroup[i], groupPts); Vec3d groupCentroids; @@ -594,21 +634,14 @@ bool TreeIso::Final_seg_pcd(ccPointCloud* pc, const unsigned PR_MIN_NN3, const f max_col(groupPts, maxPts); lenFeatures[i] = maxPts[2] - minPts[2]; - polygon hull; - multi_point conv_points; - for (const auto& p : groupPts) - { - conv_points.push_back(point_xy(p[0], p[1])); - } - - boost::geometry::convex_hull(conv_points, hull); - groupHulls.push_back(hull); + groupBBoxes.push_back(BBox::from_points(groupPts)); } size_t knncpp_nn = (PR_MIN_NN3 < n_clusters ? PR_MIN_NN3 : n_clusters); std::vector> groupNNIdxC; std::vector groupNNCDs; - knn_cpp_nearest_neighbors(centroid2DFeatures, knncpp_nn, groupNNIdxC, groupNNCDs, 8); + knn_cpp_nearest_neighbors(centroid2DFeatures, knncpp_nn, groupNNIdxC, groupNNCDs, 1);//threads=0 means optimal + Vec3d mds; mean_col(groupNNCDs, mds); float sigmaD = mds[1]; @@ -669,9 +702,8 @@ bool TreeIso::Final_seg_pcd(ccPointCloud* pc, const unsigned PR_MIN_NN3, const f get_subset(centroid2DFeatures, remainIds, groupCentroidsRemain); size_t knncpp_nn2 = (PR_MIN_NN3 < remainIds.size() ? PR_MIN_NN3 : remainIds.size()); - knncpp::KDTreeMinkowskiX> knn_kdtree(groupCentroidsRemain); - knn_cpp_build(knn_kdtree); + knn_cpp_build(knn_kdtree, 1);//threads=0 means optimal std::vector> groupNNIdx; std::vector groupNNIdxDists; @@ -683,48 +715,36 @@ bool TreeIso::Final_seg_pcd(ccPointCloud* pc, const unsigned PR_MIN_NN3, const f { size_t toMergeId = toMergeIds[i]; + + Eigen::MatrixXf currentClusterCentroids; get_subset(clusterCentroids, clusterVGroup[toMergeId], currentClusterCentroids); - size_t nNNs = groupNNIdx.size(); std::vector scores; std::vector filteredRemainIds; std::vector min3DSpacings; - for (size_t j = 0; j < nNNs; ++j) + for (size_t j = 0; j < knncpp_nn2; ++j) { - size_t remainId = remainIds[groupNNIdx[j][i]]; - - float lineSegs2 = zFeatures[toMergeId] + lenFeatures[toMergeId] - zFeatures[remainId]; + size_t remainId = remainIds[groupNNIdx[i][j]]; float lineSegs1 = zFeatures[remainId] + lenFeatures[remainId] - zFeatures[toMergeId]; - + float lineSegs2 = zFeatures[toMergeId] + lenFeatures[toMergeId] - zFeatures[remainId]; float verticalOverlapRatio = (lineSegs2 > lineSegs1 ? lineSegs1 : lineSegs2) / (lineSegs1 > lineSegs2 ? lineSegs1 : lineSegs2); - float horizontalOverlapRatio; - if ((boost::geometry::num_points(groupHulls[toMergeId]) > 3) & (boost::geometry::num_points(groupHulls[remainId]) > 3)) - { - multi_polygon intersection; - boost::geometry::intersection(groupHulls[toMergeId], groupHulls[remainId], intersection); - float intersect_area = boost::geometry::area(intersection); - float area1 = boost::geometry::area(groupHulls[toMergeId]); - float area2 = boost::geometry::area(groupHulls[remainId]); - horizontalOverlapRatio = intersect_area / (area1 < area2 ? area1 : area2); - } - else - { - horizontalOverlapRatio = 0.0; - } + float horizontalOverlapRatio = BBox::overlap_ratio(groupBBoxes[toMergeId], groupBBoxes[remainId]); Eigen::MatrixXf nnClusterCentroids; get_subset(clusterCentroids, clusterVGroup[remainId], nnClusterCentroids); knncpp::KDTreeMinkowskiX> knn_kdtree2(nnClusterCentroids); - knn_cpp_build(knn_kdtree2); + knn_cpp_build(knn_kdtree2, 1);//threads=0 means optimal std::vector> min3D_idx; std::vector min3D_dists; knn_cpp_query(knn_kdtree2, currentClusterCentroids, 1, min3D_idx, min3D_dists); - float min3DSpacing = min_col(min3D_dists[0]); + float min3DSpacing = std::min_element(min3D_dists.begin(), min3D_dists.end(), + [](const Vec3d& a, const Vec3d& b) { return a[0] < b[0]; })->operator[](0); + min3DSpacings.push_back(min3DSpacing); Eigen::MatrixXf nnClusterCentroids2D = nnClusterCentroids.block(0, 0, 2, nnClusterCentroids.cols()); @@ -811,51 +831,53 @@ bool TreeIso::Final_seg_pcd(ccPointCloud* pc, const unsigned PR_MIN_NN3, const f { segs_group_ids[currentVGroup[k]] = j + 1; } - } - - //export segments as a new scalar field - int outSFIndex = pc->getScalarFieldIndexByName(FinalSegsSFName); + } + } + //export segments as a new scalar field + int outSFIndex = pc->getScalarFieldIndexByName(FinalSegsSFName); + if (outSFIndex < 0) + { + outSFIndex = pc->addScalarField(FinalSegsSFName); if (outSFIndex < 0) { - outSFIndex = pc->addScalarField(FinalSegsSFName); - if (outSFIndex < 0) - { - ccLog::Error("[TreeIso] Not enough memory!"); - return false; - } + ccLog::Error("[TreeIso] Not enough memory!"); + return false; } - CCCoreLib::ScalarField* outSF = pc->getScalarField(outSFIndex); - outSF->fill(CCCoreLib::NAN_VALUE); + } + CCCoreLib::ScalarField* outSF = pc->getScalarField(outSFIndex); + outSF->fill(CCCoreLib::NAN_VALUE); - std::vector groupIdx(pointCount); - for (unsigned i = 0; i < pointCount; ++i) - { - outSF->setValue(i, segs_group_ids[i]); - } - outSF->computeMinAndMax(); - pc->colorsHaveChanged(); - pc->setCurrentDisplayedScalarField(outSFIndex); - pc->showSF(true); - if (progressDlg) - { - progressDlg->setValue(100); - QCoreApplication::processEvents(); - } - - auto elapsed = Since(start).count() / 1000; - ccLog::Print(QString("[TreeIso] Final segs took: %1 seconds !!!").arg(elapsed)); + for (unsigned i = 0; i < pointCount; ++i) + { + outSF->setValue(i, segs_group_ids[i]); } + + + outSF->computeMinAndMax(); + pc->colorsHaveChanged(); + pc->setCurrentDisplayedScalarField(outSFIndex); + pc->showSF(true); + + if (progressDlg) + { + progressDlg->setValue(100); + QCoreApplication::processEvents(); + } + + auto elapsed = Since(start).count() / 1000; + ccLog::Print(QString("[TreeIso] Final segs took: %1 seconds !!!").arg(elapsed)); return true; } //1. initial 3D segmentation -bool TreeIso::Init_seg( const unsigned min_nn1, - const float regStrength1, - const float PR_DECIMATE_RES1, - ccMainAppInterface* app, - QProgressDialog* progressDlg) +bool TreeIso::Init_seg(const unsigned PR_MIN_NN1, + const float PR_REG_STRENGTH1, + const float PR_DECIMATE_RES1, + const unsigned PR_THREADS1, + ccMainAppInterface* app, + QProgressDialog* progressDlg) { if (!app) { @@ -879,7 +901,7 @@ bool TreeIso::Init_seg( const unsigned min_nn1, } ccPointCloud* pointCloud = static_cast(ent); - if (Init_seg_pcd(pointCloud, min_nn1, regStrength1, PR_DECIMATE_RES1, progressDlg)) + if (Init_seg_pcd(pointCloud, PR_MIN_NN1, PR_REG_STRENGTH1, PR_DECIMATE_RES1, PR_THREADS1, progressDlg)) { ent->redrawDisplay(); return true; @@ -895,6 +917,7 @@ bool TreeIso::Intermediate_seg( const unsigned PR_MIN_NN2, const float PR_REG_STRENGTH2, const float PR_DECIMATE_RES2, const float PR_MAX_GAP, + const unsigned PR_THREADS2, ccMainAppInterface* app, QProgressDialog* progressDlg) { @@ -923,7 +946,7 @@ bool TreeIso::Intermediate_seg( const unsigned PR_MIN_NN2, ccPointCloud* pointCloud = static_cast(ent); - if (Intermediate_seg_pcd(pointCloud, PR_MIN_NN2, PR_REG_STRENGTH2, PR_DECIMATE_RES2, PR_MAX_GAP, progressDlg)) + if (Intermediate_seg_pcd(pointCloud, PR_MIN_NN2, PR_REG_STRENGTH2, PR_DECIMATE_RES2, PR_MAX_GAP, PR_THREADS2, progressDlg)) { ent->redrawDisplay(); return true; @@ -979,28 +1002,29 @@ void knn_cpp_build(knncpp::KDTreeMinkowskiX 0) kdtree.setThreads(n_thread); kdtree.build(); } -void knn_cpp_query(knncpp::KDTreeMinkowskiX>& kdtree, Eigen::MatrixXf& query_points, size_t k, std::vector >& res_idx, std::vector& res_dists) +void knn_cpp_query(knncpp::KDTreeMinkowskiX>& kdtree, Eigen::MatrixXf& query_points, size_t k, std::vector>& res_idx, std::vector& res_dists) { res_idx.clear(); res_dists.clear(); - knncpp::Matrixi indices; Eigen::MatrixXf distances; - kdtree.query(query_points, k, indices, distances); - res_idx.resize(indices.rows(), std::vector(indices.cols())); - for (Eigen::Index i = 0; i < indices.rows(); i++) + + // Change to column-wise access + res_idx.resize(indices.cols(), std::vector(indices.rows())); + for (Eigen::Index i = 0; i < indices.cols(); i++) { - res_idx[i] = std::vector(indices.row(i).data(), indices.row(i).data() + indices.cols()); + res_idx[i] = std::vector(indices.col(i).data(), indices.col(i).data() + indices.rows()); } - res_dists.resize(distances.rows(), Vec3d(distances.cols())); - for (Eigen::Index i = 0; i < distances.rows(); i++) + + res_dists.resize(distances.cols(), Vec3d(distances.rows())); + for (Eigen::Index i = 0; i < distances.cols(); i++) { - res_dists[i] = Vec3d(distances.row(i).data(), distances.row(i).data() + distances.cols()); + res_dists[i] = Vec3d(distances.col(i).data(), distances.col(i).data() + distances.rows()); } } @@ -1013,28 +1037,25 @@ float knn_cpp_query_min_d(knncpp::KDTreeMinkowskiX& dataset, - size_t k, - std::vector>& res_idx, - std::vector& res_dists, - unsigned n_thread) -{ - if (dataset.empty()) - { - assert(false); +void knn_cpp_nearest_neighbors(const std::vector& dataset, + size_t k, + std::vector>& res_idx, + std::vector& res_dists, + unsigned n_thread) { + + if (dataset.empty()) { return; } Eigen::MatrixXf mat(dataset[0].size(), dataset.size()); - for (size_t i = 0; i < dataset.size(); i++) - { + for (size_t i = 0; i < dataset.size(); i++) { mat.col(i) = Eigen::VectorXf::Map(&dataset[i][0], dataset[i].size()); } knncpp::KDTreeMinkowskiX> kdtree(mat); kdtree.setBucketSize(16); kdtree.setSorted(true); - kdtree.setThreads(n_thread); + if (n_thread > 0) kdtree.setThreads(n_thread); kdtree.build(); knncpp::Matrixi indices; Eigen::MatrixXf distances; @@ -1042,686 +1063,126 @@ void knn_cpp_nearest_neighbors( const std::vector& dataset, kdtree.query(mat, k, indices, distances); res_idx.resize(indices.cols(), std::vector(indices.rows())); - for (Eigen::Index i = 0; i < indices.cols(); i++) - { + for (Eigen::Index i = 0; i < indices.cols(); i++) { res_idx[i] = std::vector(indices.col(i).data(), indices.col(i).data() + indices.rows()); } res_dists.resize(distances.cols(), Vec3d(distances.rows())); - for (Eigen::Index i = 0; i < distances.cols(); i++) - { + for (Eigen::Index i = 0; i < distances.cols(); i++) { res_dists[i] = Vec3d(distances.col(i).data(), distances.col(i).data() + distances.rows()); } } -template -void unique_group(std::vector& arr, std::vector>& u_group, std::vector& arr_unq, std::vector& ui) -{ - arr_unq.clear(); - ui.clear(); - u_group.clear(); +void build_knn_graph(const std::vector& points, size_t k, + std::vector& first_edge, + std::vector& adj_vertices, + std::vector& edge_weights, float regStrength1, unsigned n_thread) { - if (arr.empty()) - { - assert(false); - return; - } + const size_t n_points = points.size(); + std::vector> nn_idx; + std::vector nn_D; - std::vector arr_sorted_idx; - std::vector arr_sorted; - sort_indexes(arr, arr_sorted_idx, arr_sorted); + // Get k+1 nearest neighbors (first one will be the point itself) + knn_cpp_nearest_neighbors(points, k + 1, nn_idx, nn_D, n_thread); - const size_t n = arr.size(); + // Initialize edge arrays + first_edge.resize(n_points + 1); + first_edge[0] = 0; - ui.push_back(arr_sorted_idx[0]); - std::size_t counter = 0; + adj_vertices.clear(); + edge_weights.clear(); + adj_vertices.reserve(n_points * k); + edge_weights.reserve(n_points * k); - std::vector ut; - ut.push_back(arr_sorted_idx[0]); - - //detect the location (before sorted) where a row in the sorted array is different from the previous row (as ia), and add one for the reverse index as ic - for (std::size_t i = 1; i < n; ++i) - { - if (arr_sorted[i] != arr_sorted[i - 1]) - { - ui.push_back(arr_sorted_idx[i]); - arr_unq.push_back(arr_sorted[i]); - - u_group.push_back(ut); - ut.clear(); - ut.push_back(arr_sorted_idx[i]); - counter++; + // Build graph structure + for (size_t i = 0; i < n_points; i++) { + size_t edges_for_point = 0; + // Skip first neighbor (self) + for (size_t j = 1; j < k + 1; j++) { + adj_vertices.push_back(nn_idx[i][j]); + // Convert distance to weight - for d0, we want high weights for close points + float dist = std::sqrt(nn_D[i][j]) + 1e-6f; + edge_weights.push_back(std::exp(-dist * dist) * regStrength1); // Gaussian weight + edges_for_point++; } - else - { - ut.push_back(arr_sorted_idx[i]); - } - } - u_group.push_back(ut); -} - -template -void unique_group(std::vector& arr, std::vector>& u_group, std::vector& arr_unq) -{ - arr_unq.clear(); - u_group.clear(); - - if (arr.empty()) - { - assert(false); - return; - } - - std::vector arr_sorted_idx; - std::vector arr_sorted; - sort_indexes(arr, arr_sorted_idx, arr_sorted); - - const size_t n = arr.size(); - arr_unq.push_back(arr_sorted[0]); - std::size_t counter = 0; - - std::vector ut; - ut.push_back(arr_sorted_idx[0]); - - //detect the location (before sorted) where a row in the sorted array is different from the previous row (as ia), and add one for the reverse index as ic - for (std::size_t i = 1; i < n; ++i) - { - - if (arr_sorted[i] != arr_sorted[i - 1]) - { - arr_unq.push_back(arr_sorted[i]); - - u_group.push_back(ut); - ut.clear(); - ut.push_back(arr_sorted_idx[i]); - counter++; - } - else - { - ut.push_back(arr_sorted_idx[i]); - } - } - u_group.push_back(ut); -} - -template -void unique_group(std::vector& arr, std::vector>& u_group) -{ - u_group.clear(); - - if (arr.empty()) - { - assert(false); - return; - } - - std::vector arr_sorted_idx; - std::vector arr_sorted; - sort_indexes(arr, arr_sorted_idx, arr_sorted); - - const size_t n = arr.size(); - std::size_t counter = 0; - - std::vector ut; - ut.push_back(arr_sorted_idx[0]); - - //detect the location (before sorted) where a row in the sorted array is different from the previous row (as ia), and add one for the reverse index as ic - for (std::size_t i = 1; i < n; ++i) - { - if (arr_sorted[i] != arr_sorted[i - 1]) - { - u_group.push_back(ut); - ut.clear(); - ut.push_back(arr_sorted_idx[i]); - counter++; - } - else - { - ut.push_back(arr_sorted_idx[i]); - } - } - u_group.push_back(ut); -} - -template -void get_subset(std::vector& arr, std::vector& indices, std::vector& arr_sub) -{ - arr_sub.clear(); - for (const auto& idx : indices) - { - arr_sub.push_back(arr[idx]); + first_edge[i + 1] = first_edge[i] + static_cast(edges_for_point); } } -template -void get_subset(const std::vector>& arr, const std::vector& indices, Eigen::MatrixXf& arr_sub) -{ - arr_sub.setZero(); +bool perform_cut_pursuit(const unsigned K, + size_t D, + const float regStrength, + const std::vector& pc_vec, + std::vector& edge_weights, + std::vector& Eu, + std::vector& Ev, + std::vector& in_component, + const unsigned threads +) { - if (arr.empty()) - { - assert(false); - return; - } + using CP = Cp_d0_dist; - arr_sub.resize(arr[0].size(), indices.size()); - - for (size_t i = 0; i < indices.size(); ++i) - { - for (size_t j = 0; j < arr[0].size(); ++j) - { - arr_sub(j,i) = arr[indices[i]][j]; - } - } -} - -template -void get_subset(const std::vector>& arr, const std::vector& indices, std::vector>& arr_sub) -{ - arr_sub.clear(); - - if (arr.empty() || indices.empty()) - { - return; - } - - arr_sub.resize(indices.size()); - for (size_t i = 0; i < indices.size(); ++i) - { - arr_sub[i] = arr[indices[i]]; - } -} - -template -bool get_subset(ccPointCloud* pcd, std::vector& indices, std::vector>& arr_sub) -{ - arr_sub.clear(); - arr_sub.resize(indices.size(), std::vector(3)); - for (size_t i = 0; i < indices.size(); ++i) - { - const CCVector3* vec = pcd->getPoint(indices[i]); - arr_sub[i][0] = vec->x; - arr_sub[i][1] = vec->y; - arr_sub[i][2] = vec->z; - } - return true; -} - -//convert ccPointCloud to a vector of points (centering the points about their gravity center) -template -void toTranslatedVector(const ccPointCloud* pc, std::vector>& y) -{ - y.clear(); - - const unsigned pointCount = pc->size(); - if (pointCount == 0) - { - assert(false); - return; - } - y.resize(pointCount, std::vector(3)); - - for (unsigned i = 0; i < pointCount; ++i) - { - const CCVector3* pv = pc->getPoint(i); - y[i] = { static_cast(pv->x), - static_cast(pv->y), - static_cast(pv->z) }; - } - - std::vector y_mean; - mean_col(y, y_mean); - - for (unsigned i = 0; i < pointCount; ++i) - { - y[i][0] -= y_mean[0]; - y[i][1] -= y_mean[1]; - y[i][2] -= y_mean[2]; - } -} - -bool perform_cut_pursuit( const uint32_t K, - const float regStrength, - const std::vector& pc, - std::vector& edgeWeight, - std::vector& Eu, - std::vector& Ev, - std::vector& in_component, - std::vector>& components ) -{ - const uint32_t pointCount = static_cast(pc.size()); - if (pointCount == 0) - { + const index_t pointCount = static_cast(pc_vec.size()); + if (pointCount == 0) { return false; } - std::vector> nn_idx; - std::vector nn_D; - knn_cpp_nearest_neighbors(pc, K + 1, nn_idx, nn_D, 8); - - const uint32_t nNod = pointCount; - const uint32_t nObs = 3; - const uint32_t nEdg = pointCount * K; - const uint32_t cutoff = 0; - const float mode = 1.0f; - const float speed = 0; - const float weight_decay = 0; - const float verbose = 0; - - if (edgeWeight.size() == 0) - { - edgeWeight.resize(nEdg); - std::fill(edgeWeight.begin(), edgeWeight.end(), 1.0); + if (Eu.size() == 0) { + // Build graph structure using efficient kNN + build_knn_graph(pc_vec, K, Eu, Ev, edge_weights, regStrength, threads); } - //minus average - std::vector y_avg(nObs, 0.0); + const index_t E = static_cast(Ev.size()); - if (Eu.size() == 0) - { - Eu.resize(nEdg); - Ev.resize(nEdg); - for (unsigned i = 0; i < pointCount; ++i) - { - y_avg[0] += pc[i][0]; - y_avg[1] += pc[i][1]; - y_avg[2] += pc[i][2]; - - for (unsigned j = 0; j < K; ++j) - { - Eu[i * K + j] = i; - Ev[i * K + j] = nn_idx[i][j + 1]; - } - } - } - else - { - for (unsigned i = 0; i < pointCount; ++i) - { - y_avg[0] += pc[i][0]; - y_avg[1] += pc[i][1]; - y_avg[2] += pc[i][2]; + // Convert point cloud to flat array for observations + std::vector Y(pointCount * D); + for (size_t i = 0; i < pointCount; i++) { + for (size_t d = 0; d < D; d++) { + Y[i * D + d] = pc_vec[i][d]; } } - y_avg[0] /= pointCount; - y_avg[1] /= pointCount; - y_avg[2] /= pointCount; + // Create cut pursuit instance + CP* cp = new CP(pointCount, E, Eu.data(), Ev.data(), Y.data(), D); - std::vector> y = pc; - for (unsigned i = 0; i < pointCount; ++i) - { - y[i][0] -= static_cast(y_avg[0]); - y[i][1] -= static_cast(y_avg[1]); - y[i][2] -= static_cast(y_avg[2]); + // Rest of the implementation stays the same + cp->set_edge_weights(edge_weights.data(), regStrength); + cp->set_loss(cp->quadratic_loss()); + cp->set_cp_param(1e-4, 20, 1000); + //cp->set_min_comp_weight(10.0);//optional + + comp_t rV = 1; + cp->set_components(rV, nullptr); + cp->cut_pursuit(); + + // Get components assignment + const comp_t* comp_assign; + const index_t* first_vertex; + const index_t* comp_list; + rV = cp->get_components(&comp_assign, &first_vertex, &comp_list); + + // Copy results, converting back to unsigned types for output + in_component.resize(pointCount); + for (uint32_t i = 0; i < pointCount; i++) { + in_component[i] = static_cast(comp_assign[i]); } - std::vector nodeWeight(pointCount, 1.0); - std::vector solution(pointCount, std::vector(K)); - CP::cut_pursuit(nNod, nEdg, nObs, y, Eu, Ev, edgeWeight, nodeWeight, solution, in_component, components, regStrength, cutoff, mode, speed, weight_decay, verbose); + delete cp; return true; } -void perform_cut_pursuit2d( const uint32_t K, - const float regStrength, - const std::vector& pc_vec, - Vec3d& edgeWeight, - std::vector& Eu, - std::vector& Ev, - std::vector& in_component ) -{ - //build graph for cut-pursuit - const uint32_t pointCount = static_cast(pc_vec.size()); - - const uint32_t nNod = pointCount; - const uint32_t nObs = 2; - const uint32_t nEdgMax = pointCount * K; - const uint32_t cutoff = 0; - const float mode = 1; - const float speed = 0; - const float weight_decay = 0; - const float verbose = 0; - - if (edgeWeight.size() == 0) - { - edgeWeight.resize(nEdgMax); - std::fill(edgeWeight.begin(), edgeWeight.end(), 1.0); - } - const uint32_t nEdg = static_cast(edgeWeight.size()); - - //minus average - Vec3d y_avg(nObs, 0.0); - - if (Eu.size() == 0) - { - std::vector> nn_idx; - std::vector nn_D; - knn_cpp_nearest_neighbors(pc_vec, K + 1, nn_idx, nn_D, 8); - Eu.resize(nEdg); - Ev.resize(nEdg); - for (uint32_t i = 0; i < pointCount; ++i) - { - y_avg[0] += pc_vec[i][0]; - y_avg[1] += pc_vec[i][1]; - for (unsigned j = 0; j < K; ++j) - { - Eu[i * K + j] = i; - Ev[i * K + j] = nn_idx[i][j + 1]; - } - } - } - else - { - for (unsigned i = 0; i < pointCount; ++i) - { - y_avg[0] += pc_vec[i][0]; - y_avg[1] += pc_vec[i][1]; - } - } - - y_avg[0] /= pointCount; - y_avg[1] /= pointCount; - - std::vector y = pc_vec; - for (unsigned i = 0; i < pointCount; ++i) - { - y[i][0] -= static_cast(y_avg[0]); - y[i][1] -= static_cast(y_avg[1]); - } - - std::vector nodeWeight(pointCount, 1.0); - std::vector > components; - std::vector> solution(pointCount, std::vector(K)); - CP::cut_pursuit(nNod, nEdg, nObs, y, Eu, Ev, edgeWeight, nodeWeight, solution, in_component, components, regStrength, cutoff, mode, speed, weight_decay, verbose); -} - -template -size_t arg_min_col(std::vector& arr) -{ - auto min_element_it = std::min_element(arr.begin(), arr.end()); - std::size_t min_index = std::distance(arr.begin(), min_element_it); - return min_index; -} - -template -size_t arg_max_col(std::vector& arr) -{ - auto max_element_it = std::max_element(arr.begin(), arr.end()); - std::size_t max_index = std::distance(arr.begin(), max_element_it); - return max_index; -} - -template -void min_col(std::vector>& arr, std::vector& min_vals) -{ - min_vals.clear(); - if (arr.empty()) - { +// Load points from file: an example +void load_initseg_points(const std::string& filename, std::vector& points, std::vector& in_component) { + std::ifstream file(filename); + if (!file.is_open()) { + std::cerr << "Error opening file: " << filename << std::endl; return; } - min_vals = arr[0]; - for (size_t j = 1; j < arr.size(); j++) - { - const auto& row = arr[j]; - for (size_t i = 0; i < row.size(); i++) - { - min_vals[i] = std::min(min_vals[i], row[i]); - } + float x, y, z, s; + while (file >> x >> y >> z >> s) { + Vec3d point = { x, y, z }; + points.push_back(point); + in_component.push_back(s); } } -template -T min_col(std::vector& arr) -{ - auto result_it = std::min_element(arr.begin(), arr.end()); - T result=*result_it; - return result; -} - -template -T mean_col(std::vector& arr) -{ - if (arr.empty()) - { - return std::numeric_limits::quite_NaN(); - } - - double sum = 0.0; - for (const T& value : arr) - { - sum += value; - } - - return static_cast(sum /= arr.size()); -} - -template -T median_col(std::vector& arr) -{ - size_t n = arr.size(); - if (n == 0) - { - return std::numeric_limits::quiet_NaN(); - } - // Sort the vector - std::sort(arr.begin(), arr.end()); - // Calculate the median - if (n % 2 == 0) - { - return (arr[n / 2 - 1] + arr[n / 2]) / 2; - } - else - { - return arr[n / 2]; - } -} - -template -T mode_col(std::vector& arr) { - std::unordered_map freq; - for (const auto& val : arr) freq[val]++; - return std::max_element(freq.begin(), freq.end(), [](const auto& a, const auto& b) { return a.second < b.second; })->first; -} - -template -void max_col(std::vector>& arr, std::vector& max_vals) -{ - max_vals.clear(); - if (arr.empty()) - { - return; - } - - max_vals = arr[0]; - for (size_t j = 1; j < arr.size(); j++) - { - const auto& row = arr[j]; - for (size_t i = 0; i < row.size(); i++) - { - max_vals[i] = std::max(max_vals[i], row[i]); - } - } -} - -template -void mean_col(std::vector>& arr, std::vector& mean_vals) -{ - mean_vals.clear(); - if (arr.empty()) - { - return; - } - - std::vector sums(arr[0].size(), 0.0); - - for (const std::vector& element : arr) - { - for (size_t i = 0; i < arr[0].size(); i++) - { - sums[i] += element[i]; - } - } - - mean_vals = std::vector(arr[0].size()); - for (size_t i = 0; i < arr[0].size(); i++) - { - mean_vals[i] = static_cast(sums[i] / arr.size()); - } -} - -template -void decimate_vec(std::vector>& arr, T res, std::vector>& vec_dec) -{ - vec_dec.clear(); - - if (res == 0) - { - assert(false); - return; - } - - size_t num_rows = arr.size(); - if (num_rows == 0) - { - assert(false); - return; - } - size_t num_cols = arr[0].size(); - - std::vector arr_min; - min_col(arr, arr_min); - vec_dec.resize(num_rows, std::vector(num_cols)); - - for (unsigned i = 0; i < num_rows; ++i) - { - for (unsigned j = 0; j < num_cols; ++j) - { - vec_dec[i][j] = std::floor((arr[i][j] - arr_min[j]) / res) + 1; - } - } -} - -//return index -template -void sort_indexes_by_row(std::vector>& v, std::vector& idx, std::vector>& v_sorted) -{ - idx.clear(); - v_sorted.clear(); - - // initialize original index locations - size_t m = v.size(); - if (m == 0) - { - return; - } - size_t n = v[0].size(); - - idx.resize(m); - std::iota(idx.begin(), idx.end(), 0); - - //std::vector> v_sorted; - v_sorted.resize(m, std::vector(n)); - - // sort indexes based on comparing values in v - // using std::stable_sort instead of std::sort - // to avoid unnecessary index re-orderings - // when v contains elements of equal values - std::stable_sort(idx.begin(), idx.end(), - [&v](size_t i1, size_t i2) - { - //bool vt = true; - for (size_t k = 0; k < v[0].size(); ++k) - { - if (v[i1][k] == v[i2][k]) - { - continue; - } - else - { - return v[i1][k] < v[i2][k]; - } - } - return false; - }); - - for (size_t i = 0; i < idx.size(); ++i) - { - v_sorted[i].resize(n); - for (size_t k = 0; k < n; ++k) - { - v_sorted[i][k] = v[idx[i]][k]; - } - } -} - -//return index -template -void sort_indexes(std::vector& v, std::vector& idx, std::vector& v_sorted) -{ - idx.clear(); - v_sorted.clear(); - - size_t m = v.size(); - if (m == 0) - { - return; - } - idx.resize(m); - std::iota(idx.begin(), idx.end(), 0); - - v_sorted.resize(m); - - std::stable_sort(idx.begin(), idx.end(), - [&v](IndexType i1, IndexType i2) - { - return v[i1] < v[i2]; - }); - - for (size_t i = 0; i < idx.size(); ++i) - { - v_sorted[i] = v[idx[i]]; - } -} - -template -void unique_index_by_rows(std::vector>& arr, std::vector& ia, std::vector& ic) -{ - ia.clear(); - ic.clear(); - - std::vector> arr_sorted; - arr_sorted.resize(arr.size()); - std::vector sort_idx; - - //sort array first and get indices - sort_indexes_by_row(arr, sort_idx, arr_sorted); - - const size_t num_rows = arr_sorted.size(); - const size_t num_cols = arr_sorted[0].size(); - - ic.resize(num_rows); - ia.push_back(sort_idx[0]); - std::size_t counter = 0; - ic[sort_idx[0]] = counter; - - //detect the location (before sorted) where a row in the sorted array is different from the previous row (as ia), and add one for the reverse index as ic - for (std::size_t i = 1; i < num_rows; ++i) - { - bool diff = false; - for (std::size_t k = 0; k < num_cols; ++k) - { - if (arr_sorted[i][k] != arr_sorted[i - 1][k]) - { - diff = true; - break; - } - } - - if (diff) - { - ia.push_back(sort_idx[i]); - counter++; - } - ic[sort_idx[i]] = counter; - } -} diff --git a/src/cp_d0_dist.cpp b/src/cp_d0_dist.cpp new file mode 100644 index 0000000..c52ca46 --- /dev/null +++ b/src/cp_d0_dist.cpp @@ -0,0 +1,294 @@ +/*============================================================================= + * Hugo Raguet 2018, 2022, 2023 + *===========================================================================*/ +#include "cp_d0_dist.hpp" + +#define VERT_WEIGHTS_(v) (vert_weights ? vert_weights[(v)] : (real_t) 1.0) +#define COOR_WEIGHTS_(d) (coor_weights ? coor_weights[(d)] : (real_t) 1.0) + +#define TPL template +#define CP_D0_DIST Cp_d0_dist + +using namespace std; + +TPL CP_D0_DIST::Cp_d0_dist(index_t V, index_t E, const index_t* first_edge, + const index_t* adj_vertices, const real_t* Y, size_t D) + : Cp_d0(V, E, first_edge, adj_vertices, D), Y(Y) +{ + vert_weights = coor_weights = nullptr; + comp_weights = nullptr; + + loss = quadratic_loss(); + fYY = 0.0; + fXY = real_inf(); + + min_comp_weight = 0.0; +} + +TPL CP_D0_DIST::~Cp_d0_dist(){ free(comp_weights); } + +TPL real_t CP_D0_DIST::distance(const real_t* Yv, const real_t* Xv) const +{ + real_t dist = 0.0; + size_t Q = loss; // number of coordinates for quadratic part + if (Q != 0){ /* quadratic part */ + for (size_t d = 0; d < Q; d++){ + dist += COOR_WEIGHTS_(d)*(Yv[d] - Xv[d])*(Yv[d] - Xv[d]); + } + } + if (Q != D){ /* smoothed Kullback-Leibler; + just compute cross-entropy here */ + real_t distKL = 0.0; + const real_t s = loss < 1.0 ? loss : eps; + const real_t c = 1.0 - s; + const real_t u = s/(D - Q); + for (size_t d = Q; d < D; d++){ + distKL -= (u + c*Yv[d])*log(u + c*Xv[d]); + } + dist += COOR_WEIGHTS_(Q)*distKL; + } + return dist; +} + +TPL void CP_D0_DIST::set_loss(real_t loss, const real_t* Y, + const real_t* vert_weights, const real_t* coor_weights) +{ + if (loss < 0.0 || (loss > 1.0 && ((size_t) loss) != loss) || loss > D){ + cerr << "Cut-pursuit d0 distance: loss parameter should be positive," + "either in (0,1) or an integer that do not exceed the dimension " + "(" << loss << " given)." << endl; + exit(EXIT_FAILURE); + } + if (loss == 0.0){ loss = eps; } // avoid singularities + this->loss = loss; + if (Y){ this->Y = Y; } + this->vert_weights = vert_weights; + if (0.0 < loss && loss < 1.0 && coor_weights){ + cerr << "Cut-pursuit d0 distance: no sense in weighting coordinates of" + " the probability space in Kullback-Leibler divergence." << endl; + exit(EXIT_FAILURE); + } + this->coor_weights = coor_weights; + if (loss == quadratic_loss()){ fYY = 0.0; return; } + /* recompute the constant dist(Y, Y) for Kullback-Leibler */ + const size_t Q = loss; // number of coordinates for quadratic part + const real_t s = loss < 1.0 ? loss : eps; + const real_t c = 1.0 - s; + const real_t u = s/(D - Q); + real_t fYY_par = 0.0; // auxiliary variable for parallel region + for (index_t v = 0; v < V; v++){ + const real_t* Yv = Y + D*v; + real_t H_Yv = 0.0; + for (size_t d = Q; d < D; d++){ + H_Yv -= (u + c*Yv[d])*log(u + c*Yv[d]); + } + fYY_par += VERT_WEIGHTS_(v)*H_Yv; + } + fYY = fYY_par; +} + +TPL void CP_D0_DIST::set_split_param(index_t max_split_size, comp_t K, + int split_iter_num, real_t split_damp_ratio, int split_values_init_num, + int split_values_iter_num) +{ + Cp::set_split_param(max_split_size, K, + split_iter_num, split_damp_ratio, split_values_init_num, + split_values_iter_num); +} + +TPL void CP_D0_DIST::set_min_comp_weight(real_t min_comp_weight) +{ + if (min_comp_weight < 0.0){ + cerr << "Cut-pursuit d0 distance: min component weight parameter " + "should be positive (" << min_comp_weight << " given)." << endl; + exit(EXIT_FAILURE); + } + this->min_comp_weight = min_comp_weight; +} + +TPL real_t CP_D0_DIST::fv(index_t v, const real_t* Xv) const +{ return VERT_WEIGHTS_(v)*distance(Y + D*v, Xv); } + +TPL real_t CP_D0_DIST::compute_f() const +{ + return fXY == real_inf() ? + Cp_d0::compute_f() - fYY : fXY - fYY; +} + +TPL void CP_D0_DIST::solve_reduced_problem() +{ + free(comp_weights); + comp_weights = (real_t*) malloc_check(sizeof(real_t)*rV); + + for (comp_t rv = 0; rv < rV; rv++){ + real_t* rXv = rX + D*rv; + comp_weights[rv] = 0.0; + for (size_t d = 0; d < D; d++){ rXv[d] = 0.0; } + for (index_t i = first_vertex[rv]; i < first_vertex[rv + 1]; i++){ + index_t v = comp_list[i]; + comp_weights[rv] += VERT_WEIGHTS_(v); + const real_t* Yv = Y + D*v; + for (size_t d = 0; d < D; d++){ rXv[d] += VERT_WEIGHTS_(v)*Yv[d]; } + } + if (comp_weights[rv] <= 0.0){ + cerr << "Cut-pursuit d0 distance: nonpositive total component " + "weight; something went wrong." << endl; + exit(EXIT_FAILURE); + } + for (size_t d = 0; d < D; d++){ rXv[d] /= comp_weights[rv]; } + } +} + +TPL void CP_D0_DIST::set_split_value(Split_info& split_info, comp_t k, + index_t v) const +{ + const real_t* Yv = Y + D*v; + real_t* sXk = split_info.sX + D*k; + for (size_t d = 0; d < D; d++){ sXk[d] = Yv[d]; } +} + +TPL void CP_D0_DIST::update_split_info(Split_info& split_info) const +{ + comp_t rv = split_info.rv; + real_t* sX = split_info.sX; + real_t* total_weights = (real_t*) + malloc_check(sizeof(real_t)*split_info.K); + for (comp_t k = 0; k < split_info.K; k++){ + total_weights[k] = 0.0; + real_t* sXk = sX + D*k; + for (size_t d = 0; d < D; d++){ sXk[d] = 0.0; } + } + for (index_t i = first_vertex[rv]; i < first_vertex[rv + 1]; i++){ + index_t v = comp_list[i]; + comp_t k = label_assign[v]; + total_weights[k] += VERT_WEIGHTS_(v); + const real_t* Yv = Y + D*v; + real_t* sXk = sX + D*k; + for (size_t d = 0; d < D; d++){ sXk[d] += VERT_WEIGHTS_(v)*Yv[d]; } + } + comp_t kk = 0; // actual number of alternatives kept + for (comp_t k = 0; k < split_info.K; k++){ + const real_t* sXk = sX + D*k; + real_t* sXkk = sX + D*kk; + if (total_weights[k]){ + for (size_t d = 0; d < D; d++){ + sXkk[d] = sXk[d]/total_weights[k]; + } + kk++; + } // else no vertex assigned to k, discard this alternative + } + split_info.K = kk; + free(total_weights); +} + +TPL void CP_D0_DIST::compute_merge_candidate(index_t re) +{ + comp_t ru = reduced_edges_u(re); + comp_t rv = reduced_edges_v(re); + real_t edge_weight = reduced_edge_weights[re]; + + real_t* rXu = rX + D*ru; + real_t* rXv = rX + D*rv; + real_t wru = comp_weights[ru]/(comp_weights[ru] + comp_weights[rv]); + real_t wrv = comp_weights[rv]/(comp_weights[ru] + comp_weights[rv]); + + real_t gain = edge_weight; + size_t Q = loss; // number of coordinates for quadratic part + + if (Q != 0){ + /* quadratic gain */ + real_t gainQ = 0.0; + for (size_t d = 0; d < Q; d++){ + gainQ -= COOR_WEIGHTS_(d)*(rXu[d] - rXv[d])*(rXu[d] - rXv[d]); + } + gain += comp_weights[ru]*wrv*gainQ; + } + + if (gain > 0.0 || comp_weights[ru] < min_comp_weight + || comp_weights[rv] < min_comp_weight){ + if (!merge_values[re]){ + merge_values[re] = (real_t*) malloc_check(sizeof(real_t)*D); + } + real_t* value = merge_values[re]; + for (size_t d = 0; d < D; d++){ value[d] = wru*rXu[d] + wrv*rXv[d]; } + + if (Q != D){ + /* smoothed Kullback-Leibler gain */ + real_t gainKLu = 0.0, gainKLv = 0.0; + const real_t s = loss < 1.0 ? loss : eps; + const real_t c = 1.0 - s; + const real_t u = s/(D - Q); + for (size_t d = Q; d < D; d++){ + real_t u_value_d = u + c*value[d]; + real_t u_rXu_d = u + c*rXu[d]; + real_t u_rXv_d = u + c*rXv[d]; + gainKLu -= (u_rXu_d)*log(u_rXu_d/u_value_d); + gainKLv -= (u_rXv_d)*log(u_rXv_d/u_value_d); + } + gain += COOR_WEIGHTS_(Q)* + (comp_weights[ru]*gainKLu + comp_weights[rv]*gainKLv); + } + } + + merge_gains[re] = gain; + if (gain <= 0.0 && comp_weights[ru] >= min_comp_weight + && comp_weights[rv] >= min_comp_weight){ + delete_merge_candidate(re); + } +} + +TPL size_t CP_D0_DIST::merge_info_complexity() const +{ return 2*D; } + +TPL comp_t CP_D0_DIST::accept_merge_candidate(index_t re) +{ + comp_t ro = Cp_d0::accept_merge_candidate(re); + comp_t ru = reduced_edges_u(re); + comp_t rv = reduced_edges_v(re); + if (ro != ru){ rv = ru; ru = ro; } + comp_weights[ru] += comp_weights[rv]; + return ru; +} + +TPL index_t CP_D0_DIST::merge() +{ + index_t deactivation = Cp_d0::merge(); + free(comp_weights); comp_weights = nullptr; + /* fXY can be updated now to avoid computing it twice later */ + if (monitor_evolution()){ + fXY = Cp_d0::compute_f(); + } + return deactivation; +} + +TPL real_t CP_D0_DIST::compute_evolution() const +{ + real_t dif = 0.0; + for (comp_t rv = 0; rv < rV; rv++){ + if (is_saturated[rv]){ continue; } + const real_t* rXv = rX + D*rv; + real_t distXX = 0.0; + if (loss != quadratic_loss()){ + const size_t Q = loss; // number of coordinates for quadratic part + const real_t s = loss < 1.0 ? loss : eps; + const real_t c = 1.0 - s; + const real_t u = s/(D - Q); + for (size_t d = Q; d < D; d++){ + distXX -= (u + c*rXv[d])*log(u + c*rXv[d]); + } + distXX *= COOR_WEIGHTS_(Q); + } + for (index_t i = first_vertex[rv]; i < first_vertex[rv + 1]; i++){ + index_t v = comp_list[i]; + const real_t* lrXv = last_rX + D*last_comp_assign[v]; + dif += VERT_WEIGHTS_(v)*(distance(rXv, lrXv) - distXX); + } + } + real_t amp = compute_f(); + return amp > eps ? dif/amp : dif/eps; +} + +template class Cp_d0_dist; +template class Cp_d0_dist; +template class Cp_d0_dist; +template class Cp_d0_dist; diff --git a/src/cut_pursuit.cpp b/src/cut_pursuit.cpp new file mode 100644 index 0000000..8d6ca5a --- /dev/null +++ b/src/cut_pursuit.cpp @@ -0,0 +1,1638 @@ +/*============================================================================= + * Hugo Raguet 2018 + *===========================================================================*/ +#include +#include +#include "cut_pursuit.hpp" + +#define ADD1(i) (((size_t) i) + (size_t) 1) // avoid overflows +#define EDGE_WEIGHTS_(e) (edge_weights ? edge_weights[(e)] : homo_edge_weight) + +/** specific flags **/ +/* enusre number of components do not exceed integer representation */ +#define MAX_NUM_COMP (std::numeric_limits::max()) +/* use maximum number of components; no component can have this identifier */ +#define NOT_ASSIGNED (std::numeric_limits::max()) +#define CHAIN_END (std::numeric_limits::max()) +#define NO_COMP (std::numeric_limits::max()) +#define ASSIGNED ((comp_t) 0) +#define ASSIGNED_ROOT ((comp_t) 1) // must differ from ASSIGNED +#define ASSIGNED_ROOT_SAT ((comp_t) 2) // must differ from ASSIGNED_ROOT +#define NOT_SATURATED ((comp_t) 1) // must differ from ASSIGNED +/* use maximum number of edges; no edge can have this identifier */ +#define NO_EDGE (std::numeric_limits::max()) +#define NOT_ISOLATED (std::numeric_limits::max()) +#define ISOLATED ((index_t) 0) + +#define TPL template +#define CP Cp + +using namespace std; + +TPL CP::Cp(index_t V, index_t E, const index_t* first_edge, + const index_t* adj_vertices, size_t D) + : V(V), E(E), first_edge(first_edge), adj_vertices(adj_vertices), D(D) +{ + /* real type with infinity is handy */ + static_assert(numeric_limits::has_infinity, + "Cut-pursuit: real_t must be able to represent infinity."); + + /* edge activation */ + edge_status = (Edge_status*) malloc_check(sizeof(Edge_status)*E); + for (index_t e = 0; e < E; e++){ bind(e); } + + /* reduced graph **/ + rV = 1; rE = 0; + last_rV = 0; + saturated_comp = 0; + saturated_vert = 0; + edge_weights = nullptr; + homo_edge_weight = 1.0; + comp_assign = last_comp_assign = nullptr; + comp_list = first_vertex = index_in_comp = nullptr; + is_saturated = nullptr; + reduced_edge_weights = nullptr; + reduced_edges = nullptr; + elapsed_time = nullptr; + objective_values = iterate_evolution = nullptr; + rX = last_rX = nullptr; + + /* some algorithmic parameters */ + it_max = 10; verbose = 1000; + dif_tol = 0.0; + eps = numeric_limits::epsilon(); + K = 2; + split_iter_num = 1; + split_damp_ratio = 1.0; + split_values_init_num = 1; + split_values_iter_num = 1; + + //max_num_threads = omp_get_max_threads(); + //balance_parallel_split = max_num_threads > 1 && + // compute_num_threads(maxflow_complexity()) > 1; + max_split_size = V; +} + +TPL CP::~Cp() +{ + free(edge_status); + free(comp_assign); free(last_comp_assign); + free(first_vertex); + free(comp_list); + free(index_in_comp); + free(is_saturated); + free(reduced_edges); free(reduced_edge_weights); + free(rX); free(last_rX); +} + +TPL void CP::reset_edges() +{ for (index_t e = 0; e < E; e++){ bind(e); } } + +TPL void CP::set_edge_weights(const real_t* edge_weights, + real_t homo_edge_weight) +{ + this->edge_weights = edge_weights; + this->homo_edge_weight = homo_edge_weight; +} + +TPL void CP::set_monitoring_arrays(real_t* objective_values, + double* elapsed_time, real_t* iterate_evolution) +{ + this->objective_values = objective_values; + this->elapsed_time = elapsed_time; + this->iterate_evolution = iterate_evolution; +} + +TPL void CP::set_components(comp_t rV, comp_t* comp_assign) +{ + if (rV > 1 && !comp_assign){ + cerr << "Cut-pursuit: if an initial number of components greater than " + "one is given, components assignment must be provided." << endl; + exit(EXIT_FAILURE); + } + this->rV = rV; + this->comp_assign = comp_assign; +} + +TPL void CP::set_cp_param(real_t dif_tol, int it_max, int verbose, real_t eps) +{ + this->dif_tol = dif_tol; + this->it_max = it_max; + this->verbose = verbose; + this->eps = 0.0 < dif_tol && dif_tol < eps ? dif_tol : eps; +} + +TPL void CP::set_split_param(index_t max_split_size, comp_t K, + int split_iter_num, real_t split_damp_ratio, int split_values_init_num, + int split_values_iter_num) +{ + if (K < 2){ + cerr << "Cut-pursuit: there must be at least two alternative values" + "in the split (" << K << " specified)." << endl; + exit(EXIT_FAILURE); + } + if (split_iter_num < 1){ + cerr << "Cut-pursuit: there must be at least one iteration in the " + "split (" << split_iter_num << " specified)." << endl; + exit(EXIT_FAILURE); + } + if (split_damp_ratio <= 0 || split_damp_ratio > 1.0){ + cerr << "Cut-pursuit: split damping ratio must be between zero " + "excluded and one included (" << split_damp_ratio << " specified)." + << endl; + exit(EXIT_FAILURE); + } + if (split_values_init_num < 1){ + cerr << "Cut-pursuit: split values must be computed at least once per" + "split (" << split_values_init_num << " specified)." << endl; + exit(EXIT_FAILURE); + } + if (split_values_iter_num < 1){ + cerr << "Cut-pursuit: split values must be updated at least once per" + "split (" << split_values_iter_num << " specified)." << endl; + exit(EXIT_FAILURE); + } + this->max_split_size = max_split_size; + this->K = K; + this->split_iter_num = split_iter_num; + this->split_damp_ratio = split_damp_ratio; + this->split_values_init_num = split_values_init_num; + this->split_values_iter_num = split_values_iter_num; +} + +//TPL void CP::set_parallel_param(int max_num_threads, +// bool balance_parallel_split) +//{ +// if (max_num_threads <= 0){ max_num_threads = omp_get_max_threads(); } +// this->max_num_threads = max_num_threads; +// this->balance_parallel_split = balance_parallel_split +// && max_num_threads > 1 +// && compute_num_threads(split_complexity()) > 1; +//} + +TPL comp_t CP::get_components(const comp_t** comp_assign, + const index_t** first_vertex, const index_t** comp_list) const +{ + if (comp_assign){ *comp_assign = this->comp_assign; } + if (first_vertex){ *first_vertex = this->first_vertex; } + if (comp_list){ *comp_list = this->comp_list; } + return this->rV; +} + +TPL index_t CP::get_reduced_graph(const comp_t** reduced_edges, + const real_t** reduced_edge_weights) +{ + + if (reduced_edges){ + if (!this->reduced_edges){ compute_reduced_graph(); } + *reduced_edges = this->reduced_edges; + } + if (reduced_edge_weights){ + *reduced_edge_weights = this->reduced_edge_weights; + } + return this->rE; +} + +TPL const value_t* CP::get_reduced_values() const { return rX; } + +TPL void CP::set_reduced_values(value_t* rX){ this->rX = rX; } + +TPL int CP::cut_pursuit(bool init) +{ + + int it = 0; + double timer = 0.0; + real_t dif = real_inf(); + + chrono::steady_clock::time_point start; + if (elapsed_time){ start = chrono::steady_clock::now(); } + if (init){ + if (verbose){ cout << "Cut-pursuit initialization:" << endl; } + initialize(); + if (objective_values){ objective_values[0] = compute_objective(); } + } + + while (true){ + if (elapsed_time){ elapsed_time[it] = timer = monitor_time(start); } + if (verbose){ print_progress(it, dif, timer); } + if (it == it_max || dif <= dif_tol){ break; } + + if (verbose){ + cout << "Cut-pursuit iteration " << it + 1 << " (max. " << it_max + << "): " << endl; + } + + if (verbose){ cout << "\tSplit... " << flush; } + index_t activation = split(); + if (verbose){ + cout << activation << " new activated edge(s)." << endl; + } + + if (!activation){ /* do not recompute reduced problem */ + saturated_comp = rV; + saturated_vert = V; + + if (monitor_evolution()){ + dif = 0.0; + if (iterate_evolution){ iterate_evolution[it] = dif; } + } + + it++; + + if (objective_values){ + objective_values[it] = objective_values[it - 1]; + } + + continue; + } + + /* store previous component assignment */ + last_comp_assign = (comp_t*) malloc_check(sizeof(comp_t)*V); + for (index_t v = 0; v < V; v++){ + last_comp_assign[v] = comp_assign[v]; + } + last_rV = rV; + if (monitor_evolution()){ /* store also last iterate values */ + last_rX = (value_t*) malloc_check(sizeof(value_t)*D*rV); + for (size_t i = 0; i < D*rV; i++){ last_rX[i] = rX[i]; } + } + /* reduced graph and components will be updated */ + free(rX); rX = nullptr; + + if (verbose){ cout << "\tCompute connected components... " << flush; } + compute_connected_components(); + if (verbose){ + cout << rV << " connected component(s), " << saturated_comp << + " saturated." << endl; + } + + if (verbose){ cout << "\tCompute reduced graph... " << flush; } + compute_reduced_graph(); + if (verbose){ cout << rE << " reduced edge(s)." << endl; } + + if (verbose){ cout << "\tSolve reduced problem: " << endl; } + rX = (value_t*) malloc_check(sizeof(value_t)*D*rV); + solve_reduced_problem(); + + if (verbose){ cout << "\tMerge... " << flush; } + index_t deactivation = merge(); + if (verbose){ + cout << deactivation << " deactivated edge(s)." << endl; + } + + if (dif_tol > 0.0 || iterate_evolution){ + dif = compute_evolution(); + if (iterate_evolution){ iterate_evolution[it] = dif; } + free(last_rX); last_rX = nullptr; + } + + free(last_comp_assign); last_comp_assign = nullptr; + + it++; + + if (objective_values){ objective_values[it] = compute_objective(); } + + free(reduced_edges); reduced_edges = nullptr; + free(reduced_edge_weights); reduced_edge_weights = nullptr; + + } /* endwhile true */ + + return it; +} + +TPL double CP::monitor_time(chrono::steady_clock::time_point start) const +{ + using namespace chrono; + steady_clock::time_point current = steady_clock::now(); + return ((current - start).count()) * steady_clock::period::num + / static_cast(steady_clock::period::den); +} + +TPL void CP::print_progress(int it, real_t dif, double timer) const +{ + if (it && monitor_evolution()){ + cout.precision(2); + cout << scientific << "\trelative iterate evolution " << dif + << " (tol. " << dif_tol << ")\n"; + } + cout << "\t" << rV << " connected component(s), " << saturated_comp << + " saturated, and " << rE << " reduced edge(s).\n"; + if (timer > 0.0){ + cout.precision(1); + cout << fixed << "\telapsed time " << timer << " s.\n"; + } + cout << endl; +} + +TPL void CP::single_connected_component() +{ + free(first_vertex); + first_vertex = (index_t*) malloc_check(sizeof(index_t)*2); + first_vertex[0] = 0; first_vertex[1] = V; + rV = 1; + for (index_t v = 0; v < V; v++){ comp_assign[v] = 0; } + for (index_t v = 0; v < V; v++){ comp_list[v] = v; } +} + +TPL void CP::assign_connected_components() +{ + /* activate edges between components */ + for (index_t v = 0; v < V; v++){ + comp_t rv = comp_assign[v]; + for (index_t e = first_edge[v]; e < first_edge[v + 1]; e++){ + if (rv != comp_assign[adj_vertices[e]]){ cut(e); } + } + } + + /* translate 'comp_assign' into dual representation 'comp_list' */ + free(first_vertex); + first_vertex = (index_t*) malloc_check(sizeof(index_t)*ADD1(rV)); + for (comp_t rv = 0; rv < ADD1(rV); rv++){ first_vertex[rv] = 0; } + for (index_t v = 0; v < V; v++){ first_vertex[comp_assign[v] + 1]++; } + for (comp_t rv = 1; rv < rV - 1; rv++){ + first_vertex[rv + 1] += first_vertex[rv]; + } + for (index_t v = 0; v < V; v++){ + comp_list[first_vertex[comp_assign[v]]++] = v; + } + for (comp_t rv = rV; rv > 0; rv--){ + first_vertex[rv] = first_vertex[rv - 1]; + } + first_vertex[0] = 0; +} + +TPL void CP::get_bind_reverse_edges(comp_t rv, index_t*& first_edge_r, + index_t*& adj_vertices_r) +{ + const index_t* comp_list_rv = comp_list + first_vertex[rv]; + index_t comp_size = first_vertex[rv + 1] - first_vertex[rv]; + first_edge_r = (index_t*) malloc_check(sizeof(index_t)*ADD1(comp_size)); + /* set index of each vertex in the component */ + for (index_t i = 0; i < comp_size; i++){ + index_in_comp[comp_list_rv[i]] = i; + } + /* count reverse edges for each vertex (shift by one index) */ + for (index_t i = 0; i < ADD1(comp_size); i++){ first_edge_r[i] = 0; } + for (index_t i = 0; i < comp_size; i++){ + index_t v = comp_list_rv[i]; + for (index_t e = first_edge[v]; e < first_edge[v + 1]; e++){ + if (is_bind(e)){ /* keep only binding edges */ + first_edge_r[index_in_comp[adj_vertices[e]] + 1]++; + } + } + } + /* cumulative sum for actual first binding edge id for each vertex */ + first_edge_r[0] = 0; + for (index_t i = 2; i < ADD1(comp_size); i++){ + first_edge_r[i] += first_edge_r[i - 1]; + } + /* store adjacent vertices, using previous sum as starting indices */ + adj_vertices_r = (index_t*) + malloc_check(sizeof(index_t)*first_edge_r[comp_size]); + for (index_t i = 0; i < comp_size; i++){ + index_t v = comp_list_rv[i]; + for (index_t e = first_edge[v]; e < first_edge[v + 1]; e++){ + if (is_bind(e)){ + index_t j = index_in_comp[adj_vertices[e]]; + index_t e_r = first_edge_r[j]++; + adj_vertices_r[e_r] = v; + } + } + } + /* first reverse edges have been shifted in the process, shift back */ + for (index_t i = comp_size; i > 0; i--){ + first_edge_r[i] = first_edge_r[i - 1]; + } + first_edge_r[0] = 0; +} + +TPL void CP::compute_connected_components() +{ + /** new connected components hierarchically derives from previous ones, + ** we can thus compute them in parallel along previous components **/ + + /* auxiliary variables for parallel region */ + comp_t saturated_comp_par = 0; + index_t saturated_vert_par = 0; + index_t tmp_rV = 0; // identify and count components, prevent overflow + + /** there is need to scan all edges involving a given vertex without + * running through all edges of the graph, so we create the list of + * 'reverse edges' within each component; to facilitate this, we keep the + * index of each vertex within its component **/ + index_in_comp = (index_t*) malloc_check(sizeof(index_t)*V); + + for (comp_t rv = 0; rv < rV; rv++){ + index_t comp_size = first_vertex[rv + 1] - first_vertex[rv]; + + if (is_saturated[rv]){ /* component stays the same */ + index_t i = first_vertex[rv]; + comp_assign[comp_list[i]] = ASSIGNED_ROOT_SAT; // flag the root + for (i++; i < first_vertex[rv + 1]; i++){ + comp_assign[comp_list[i]] = ASSIGNED; + } + saturated_comp_par++; + saturated_vert_par += comp_size; + tmp_rV++; + continue; + } /* else component has been split */ + + /* cleanup assigned components */ + for (index_t i = first_vertex[rv]; i < first_vertex[rv + 1]; i++){ + comp_assign[comp_list[i]] = NOT_ASSIGNED; + } + + /* get reverse binding edges for breadth-first search */ + index_t *first_edge_r, *adj_vertices_r; + get_bind_reverse_edges(rv, first_edge_r, adj_vertices_r); + + /* auxiliary component list for reordering vertices */ + index_t* tmp_comp_list_rv = (index_t*) + malloc_check(sizeof(index_t)*comp_size); + + /** compute the connected components **/ + index_t i = 0, j = 0; + for (index_t k = first_vertex[rv]; k < first_vertex[rv + 1]; k++){ + index_t u = comp_list[k]; + if (comp_assign[u] != NOT_ASSIGNED){ continue; } + comp_assign[u] = ASSIGNED_ROOT; // flag a component's root + /* put in connected components list */ + tmp_comp_list_rv[j++] = u; + while (i < j){ /* breadth-first search */ + index_t v = tmp_comp_list_rv[i++]; + /* add neighbors to the connected component list */ + index_t e = first_edge[v]; + index_t l = index_in_comp[v]; + const index_t* adj_vert = adj_vertices; + while (adj_vert == adj_vertices || e < first_edge_r[l + 1]){ + if (adj_vert == adj_vertices){ + if (e == first_edge[v + 1]){ + e = first_edge_r[l]; + adj_vert = adj_vertices_r; + continue; + }else if (!is_bind(e)){ + e++; continue; + } + } + index_t w = adj_vert[e]; + if (comp_assign[w] == NOT_ASSIGNED){ + comp_assign[w] = ASSIGNED; + tmp_comp_list_rv[j++] = w; + } + e++; + } + } /* the current connected component is complete */ + tmp_rV++; + } + free(first_edge_r); free(adj_vertices_r); + + index_t* comp_list_rv = comp_list + first_vertex[rv]; + for (index_t i = 0; i < comp_size; i++){ + comp_list_rv[i] = tmp_comp_list_rv[i]; + } + + free(tmp_comp_list_rv); + } + + free(index_in_comp); index_in_comp = nullptr; + + saturated_comp = saturated_comp_par; + saturated_vert = saturated_vert_par; + + if (tmp_rV > MAX_NUM_COMP){ + cerr << "Cut-pursuit: number of components (" << tmp_rV << ") greater " + "than can be represented by comp_t (" << MAX_NUM_COMP << ")" + << endl; + exit(EXIT_FAILURE); + } + + /** update components lists, assignments and saturation **/ + rV = tmp_rV; + free(first_vertex); + first_vertex = (index_t*) malloc_check(sizeof(index_t)*ADD1(rV)); + free(is_saturated); + is_saturated = (bool*) malloc_check(sizeof(index_t)*rV); + + comp_t rv = (comp_t) -1; + for (index_t i = 0; i < V; i++){ + index_t v = comp_list[i]; + if (comp_assign[v] == ASSIGNED_ROOT || + comp_assign[v] == ASSIGNED_ROOT_SAT){ + first_vertex[++rv] = i; + is_saturated[rv] = comp_assign[v] == ASSIGNED_ROOT_SAT; + } + comp_assign[v] = rv; + } + first_vertex[rV] = V; +} + +TPL void CP::compute_reduced_graph() +/* this could actually be parallelized, but is it worth the pain? */ +{ + free(reduced_edges); + free(reduced_edge_weights); + + if (rV == 1){ /* reduced graph only edge from the component to itself + * this is only useful for solving reduced problems with + * certain implementations where isolated vertices must be + * linked to themselves */ + rE = 1; + reduced_edges = (comp_t*) malloc_check(sizeof(comp_t)*2); + reduced_edges_u(0) = reduced_edges_v(0) = 0; + reduced_edge_weights = (real_t*) malloc_check(sizeof(real_t)*1); + reduced_edge_weights[0] = eps; + return; + } + + /* to avoid allocating rV*(rV - 1)/2, we work component by component; + * when dealing with component ru, reduced_edge_to[rv] is the identifier of + * the reduced edge ru -> rv, or NO_EDGE if the edge is not created yet */ + index_t* reduced_edge_to = (index_t*) malloc_check(sizeof(index_t)*rV); + /* same storage can also be used to indicate isolated vertices */ + index_t* is_isolated = reduced_edge_to; + for (comp_t rv = 0; rv < rV; rv++){ is_isolated[rv] = ISOLATED; } + + /** get all active (cut) edges linking a component to another + ** forward-star representation (first_active_edge, adj_components) **/ + index_t* first_active_edge = (index_t*) + malloc_check(sizeof(index_t)*ADD1(rV)); + /* count the number of such edges for each component (ind shift by one) */ + for (comp_t rv = 0; rv < ADD1(rV); rv++){ first_active_edge[rv] = 0; } + for (index_t v = 0; v < V; v++){ + comp_t ru = comp_assign[v]; + for (index_t e = first_edge[v]; e < first_edge[v + 1]; e++){ + if (!is_bind(e) && EDGE_WEIGHTS_(e) > 0.0){ + comp_t rv = comp_assign[adj_vertices[e]]; + if (ru != rv){ + /* a nonzero edge involving ru and rv exists */ + is_isolated[ru] = is_isolated[rv] = NOT_ISOLATED; + if (ru < rv){ // count only undirected edges + first_active_edge[ru + 1]++; + }else{ + first_active_edge[rv + 1]++; + } + } + } + } + } + /* cumulative sum, giving first active edge id for each vertex */ + for (comp_t rv = 2; rv < ADD1(rV); rv++){ + first_active_edge[rv] += first_active_edge[rv - 1]; + } + /* store adjacent components and edge weights using previous sum as + * starting indices */ + comp_t* adj_components = (comp_t*) + malloc_check(sizeof(comp_t)*first_active_edge[rV]); + real_t* active_edge_weights = edge_weights ? + (real_t*) malloc_check(sizeof(real_t)*first_active_edge[rV]) : nullptr; + for (index_t v = 0; v < V; v++){ + comp_t ru = comp_assign[v]; + for (index_t e = first_edge[v]; e < first_edge[v + 1]; e++){ + if (!is_bind(e) && EDGE_WEIGHTS_(e) > 0.0){ + comp_t rv = comp_assign[adj_vertices[e]]; + index_t ae = NO_EDGE; + if (ru < rv){ // count only undirected edges + ae = first_active_edge[ru]++; + adj_components[ae] = rv; + }else if (rv < ru){ + ae = first_active_edge[rv]++; + adj_components[ae] = ru; + } + if (edge_weights && ae != NO_EDGE){ + active_edge_weights[ae] = edge_weights[e]; + } + } + } + } + /* first active edges have been shifted in the process, shift back */ + for (comp_t rv = rV; rv > 0; rv--){ + first_active_edge[rv] = first_active_edge[rv - 1]; + } + first_active_edge[0] = 0; + + /* temporary buffer size */ + size_t bufsize = rE > rV * (double) E/V ? rE : rV * (double) E/V; + + reduced_edges = (comp_t*) malloc_check(sizeof(comp_t)*2*bufsize); + reduced_edge_weights = (real_t*) malloc_check(sizeof(real_t)*bufsize); + + /** convert to edge list representation with weights **/ + + rE = 0; // current number of reduced edges + index_t last_rE = 0; // keep track of number of processed edges + for (comp_t ru = 0; ru < rV; ru++){ /* iterate over the components */ + + if (is_isolated[ru] == ISOLATED){ /* this is only useful for solving + * reduced problems with certain implementations where isolated + * vertices must be linked to themselves */ + if (rE == bufsize){ // reach buffer size + bufsize += bufsize/2 + 1; + reduced_edges = (comp_t*) realloc_check(reduced_edges, + sizeof(comp_t)*2*bufsize); + reduced_edge_weights = (real_t*) realloc_check( + reduced_edge_weights, sizeof(real_t)*bufsize); + } + reduced_edges_u(rE) = reduced_edges_v(rE) = ru; + reduced_edge_weights[rE++] = eps; + continue; + } + + for (index_t ae = first_active_edge[ru]; + ae < first_active_edge[ru + 1]; ae++){ + real_t edge_weight = edge_weights ? active_edge_weights[ae] + : homo_edge_weight; + comp_t rv = adj_components[ae]; + index_t re = reduced_edge_to[rv]; + if (re == NO_EDGE){ // a new edge must be created + if (rE == bufsize){ // reach buffer size + bufsize += bufsize/2 + 1; + reduced_edges = (comp_t*) realloc_check(reduced_edges, + sizeof(comp_t)*2*bufsize); + reduced_edge_weights = (real_t*) realloc_check( + reduced_edge_weights, sizeof(real_t)*bufsize); + } + reduced_edges_u(rE) = ru; + reduced_edges_v(rE) = rv; + reduced_edge_weights[rE] = edge_weight; + reduced_edge_to[rv] = rE++; + }else{ /* edge already exists */ + reduced_edge_weights[re] += edge_weight; + } + } + + /* reset reduced_edge_to */ + for (; last_rE < rE; last_rE++){ + reduced_edge_to[reduced_edges_v(last_rE)] = NO_EDGE; + } + + } + + free(adj_components); + free(active_edge_weights); + free(first_active_edge); + free(reduced_edge_to); + + if (bufsize > rE){ + reduced_edges = (comp_t*) realloc_check(reduced_edges, + sizeof(comp_t)*2*rE); + reduced_edge_weights = (real_t*) realloc_check(reduced_edge_weights, + sizeof(real_t)*rE); + } +} + +TPL void CP::initialize() +{ + free(rX); + if (!comp_assign){ + comp_assign = (comp_t*) malloc_check(sizeof(comp_t)*V); + } + if (!comp_list){ + comp_list = (index_t*) malloc_check(sizeof(index_t)*V); + } + + last_rV = 0; + + reset_edges(); + + if (rV > 1){ assign_connected_components(); } + else{ single_connected_component(); } + + /* start with no saturated component */ + free(is_saturated); + is_saturated = (bool*) malloc_check(sizeof(bool)*rV); + for (comp_t rv = 0; rv < rV; rv++){ is_saturated[rv] = false; } + + compute_reduced_graph(); + rX = (value_t*) malloc_check(sizeof(value_t)*D*rV); + solve_reduced_problem(); + merge(); +} + +TPL int CP::balance_split(comp_t& rV_big, comp_t& rV_new, + index_t*& first_vertex_big) +/* rV_big will be the number of big components to be split + rV_new will be the number of resulting new components + first_vertex_big will store info on list of vertices of big components */ +{ + + /** sort components by decreasing size + * even if no balancing is required, sorting is useful for dynamic + * scheduling of parallel split */ + if (max_split_size < V){ + /* get component sizes */ + index_t* comp_sizes = (index_t*) malloc_check(sizeof(index_t)*rV); + for (comp_t rv = 0; rv < rV; rv++){ + /* saturated components need no processing */ + comp_sizes[rv] = is_saturated[rv] ? 0 : + first_vertex[rv + 1] - first_vertex[rv]; + } + /* get sorting permutation indices */ + comp_t* sort_comp = (comp_t*) malloc_check(sizeof(comp_t)*rV); + for (comp_t rv = 0; rv < rV; rv++){ sort_comp[rv] = rv; } + + sort(sort_comp, sort_comp + rV, + [comp_sizes] (comp_t ru, comp_t rv) -> bool + { return comp_sizes[ru] > comp_sizes[rv]; }); // decreasing order + + /* reorder saturation */ + for (comp_t rv = 0; rv < rV; rv++){ + is_saturated[rv] = !comp_sizes[sort_comp[rv]]; + } + /* reorder components list */ + index_t* tmp_comp_list = (index_t*) malloc_check(sizeof(index_t)*V); + index_t* tmp_first_vertex = comp_sizes; /* reuse storage */ + index_t i = 0; + for (comp_t rv = 0; rv < rV; rv++){ + comp_t sort_rv = sort_comp[rv]; + tmp_first_vertex[rv] = i; + for (index_t j = first_vertex[sort_rv]; + j < first_vertex[sort_rv + 1]; j++){ + tmp_comp_list[i++] = comp_list[j]; + } + } + for (index_t v = 0; v < V; v++){ comp_list[v] = tmp_comp_list[v]; } + for (comp_t rv = 0; rv < rV; rv++){ + first_vertex[rv] = tmp_first_vertex[rv]; + } + free(tmp_comp_list); + free(comp_sizes); /* also storage of tmp_first_vertex */ + /* reorder component values */ + value_t* tmp_rX = (value_t*) malloc_check(sizeof(value_t)*D*rV); + for (comp_t rv = 0; rv < rV; rv++){ + value_t* tmp_rXv = tmp_rX + D*rv; + value_t* rXv = rX + D*sort_comp[rv]; + for (size_t d = 0; d < D; d++){ tmp_rXv[d] = rXv[d]; } + } + free(rX); + rX = tmp_rX; + + free(sort_comp); + } + if (max_split_size >= first_vertex[1] - first_vertex[0]) { + rV_new = 0; rV_big = 0; + return (comp_t) rV; + } + + /* maximum component size for parallelism or maxflow performance */ + index_t max_comp_size = (V - 1) + 1; + if (max_comp_size > max_split_size){ max_comp_size = max_split_size; } + + /** get number of components to split **/ + rV_big = 0; // the number of components to split + while (rV_big < rV && !is_saturated[rV_big] && + first_vertex[rV_big + 1] - first_vertex[rV_big] > max_comp_size){ + rV_big++; + } + + if (!rV_big){ + rV_new = 0; + return (comp_t) rV; + } + + /** split big components and create balanced component list **/ + /* the number of resulting new components */ + comp_t rV_new_par = 0; // auxiliary variable for parallel region + + /* there is need to scan all edges involving a given vertex without + * running through all edges of the graph, so we create the list of + * 'reverse edges' within each component; to facilitate this, we keep the + * index of each vertex within its component */ + index_in_comp = (index_t*) malloc_check(sizeof(index_t)*V); + + for (comp_t rv = 0; rv < rV_big; rv++){ + index_t comp_size = first_vertex[rv + 1] - first_vertex[rv]; + + /* cleanup assigned components */ + for (index_t i = first_vertex[rv]; i < first_vertex[rv + 1]; i++){ + comp_assign[comp_list[i]] = NOT_ASSIGNED; + } + + /* get reverse binding edges for breadth-first search */ + index_t *first_edge_r, *adj_vertices_r; + get_bind_reverse_edges(rv, first_edge_r, adj_vertices_r); + + /* auxiliary component list for reordering vertices */ + index_t* tmp_comp_list_rv = (index_t*) + malloc_check(sizeof(index_t)*comp_size); + + /** compute the new components **/ + index_t residual_comp_size = comp_size; + index_t i = 0, j = 0; + for (index_t k = first_vertex[rv]; k < first_vertex[rv + 1]; k++){ + index_t u = comp_list[k]; + if (comp_assign[u] != NOT_ASSIGNED){ continue; } + /* start a new component with u as root */ + comp_assign[u] = ASSIGNED_ROOT; + /* adjust maximum component size */ + index_t n = (residual_comp_size - 1)/max_comp_size + 1; + index_t max_comp_size_u = (residual_comp_size - 1)/n + 1; + /* put u in the new component list */ + tmp_comp_list_rv[j++] = u; + index_t size = 1; + while (i < j){ /* breadth-first search up to max component size */ + index_t v = tmp_comp_list_rv[i++]; + /* add neighbors to the connected component list */ + index_t e = first_edge[v]; + index_t l = index_in_comp[v]; + const index_t* adj_vert = adj_vertices; + while (adj_vert == adj_vertices || e < first_edge_r[l + 1]){ + if (adj_vert == adj_vertices){ + if (e == first_edge[v + 1]){ + e = first_edge_r[l]; + adj_vert = adj_vertices_r; + continue; + }else if (!is_bind(e)){ + e++; continue; + } + } + index_t w = adj_vert[e]; + if (comp_assign[w] == NOT_ASSIGNED){ + comp_assign[w] = ASSIGNED; + tmp_comp_list_rv[j++] = w; + size++; + if (size == max_comp_size_u){ + i = j; // swallow the queue + break; + } + } + e++; + } + } /* the current new component is complete */ + residual_comp_size -= size; + rV_new_par++; + } + + free(first_edge_r); free(adj_vertices_r); + + index_t* comp_list_rv = comp_list + first_vertex[rv]; + for (index_t i = 0; i < comp_size; i++){ + comp_list_rv[i] = tmp_comp_list_rv[i]; + } + + free(tmp_comp_list_rv); + } + + rV_new = rV_new_par; + + free(index_in_comp); index_in_comp = nullptr; + + comp_t rV_dif = rV_new - rV_big; + + if ((index_t) rV + rV_dif > MAX_NUM_COMP){ + cerr << "Cut-pursuit: number of balanced components (" << + (index_t) rV + rV_dif << ") greater " + << "than can be represented by comp_t (" << MAX_NUM_COMP << ")" + << endl; + exit(EXIT_FAILURE); + } + + /** first vertices of balanced components **/ + comp_t rV_bal = rV + rV_dif; + index_t* first_vertex_bal = (index_t*) + malloc_check(sizeof(index_t)*ADD1(rV_bal)); + + /* new components first vertices, and assignments for later */ + comp_t rv_new = (comp_t) -1; + for (index_t i = 0; i < first_vertex[rV_big]; i++){ + index_t v = comp_list[i]; + if (comp_assign[v] == ASSIGNED_ROOT){ first_vertex_bal[++rv_new] = i; } + comp_assign[v] = rv_new; + } + + /* add the small components first vertices */ + for (comp_t rv = rV_big; rv < ADD1(rV); rv++){ + first_vertex_bal[rv + rV_dif] = first_vertex[rv]; + } + + ///** set separation on edges between new components **/ + for (comp_t rv_new = 0; rv_new < rV_new; rv_new++){ + for (index_t i = first_vertex_bal[rv_new]; + i < first_vertex_bal[rv_new + 1]; i++){ + index_t v = comp_list[i]; + for (index_t e = first_edge[v]; e < first_edge[v + 1]; e++){ + if (is_bind(e) && rv_new != comp_assign[adj_vertices[e]]){ + separate(e); + } + } + } + } + + /** duplicate component values and saturation accordingly **/ + rX = (value_t*) realloc_check(rX, sizeof(value_t)*D*rV_bal); + is_saturated = (bool*) realloc_check(is_saturated, sizeof(bool)*rV_bal); + /* small components; in-place, start by the end */ + for (comp_t rv = rV - 1; rv >= rV_big; rv--){ // rVbig > 0 + value_t* rXv = rX + D*rv; + value_t* rXv_bal = rX + D*(rv + rV_dif); + for (size_t d = 0; d < D; d++){ rXv_bal[d] = rXv[d]; } + is_saturated[rv + rV_dif] = is_saturated[rv]; + } + /* big components; in-place, slightly more complicated */ + rv_new = rV_new - 1; + for (comp_t rv = rV_big; rv --> 0; ){ // nice trick for unsigned comp_t + value_t* rXv = rX + D*rv; + while (rv_new != 0 && first_vertex_bal[rv_new] >= first_vertex[rv]){ + value_t* rXv_bal = rX + D*rv_new; + for (size_t d = 0; d < D; d++){ rXv_bal[d] = rXv[d]; } + is_saturated[rv_new] = is_saturated[rv]; // should be false + rv_new--; + } + } + + /** replace the component list by the balanced one **/ + /* store info abount big components */ + first_vertex_big = (index_t*) realloc_check(first_vertex, + sizeof(index_t)*(rV_big + 1)); + first_vertex = first_vertex_bal; + rV = rV_bal; + + return (comp_t) rV; +} + +TPL index_t CP::remove_balance_separations(comp_t rV_new) +{ + index_t activation = 0; + + ///* reconstruct component assignment (only on new components) */ + for (comp_t rv_new = 0; rv_new < rV_new; rv_new++){ + for (index_t i = first_vertex[rv_new]; i < first_vertex[rv_new + 1]; + i++){ + comp_assign[comp_list[i]] = rv_new; + } + } + + for (comp_t rv_new = 0; rv_new < rV_new; rv_new++){ + const bool sat = is_saturated[rv_new]; + for (index_t i = first_vertex[rv_new]; i < first_vertex[rv_new + 1]; + i++){ + index_t v = comp_list[i]; + for (index_t e = first_edge[v]; e < first_edge[v + 1]; e++){ + if (is_separation(e)){ + if (sat && is_saturated[comp_assign[adj_vertices[e]]]){ + bind(e); + }else{ + cut(e); + activation++; + } + } + } + } + } + + return activation; +} + +TPL void CP::revert_balance_split(comp_t rV_big, comp_t rV_new, + index_t* first_vertex_big) +{ + index_t* first_vertex_bal = first_vertex; // make clear which one is which + comp_t rV_dif = rV_new - rV_big; // additional components due to balancing + comp_t rV_ini = rV - rV_dif; // number of components prior to balancing + + /** remove duplicated component values and aggregate saturation **/ + /* big components */ + comp_t rv_new = 0; + for (comp_t rv = 0; rv < rV_big; rv++){ + value_t* rXv = rX + D*rv; + value_t* rXv_bal = rX + D*rv_new; + for (size_t d = 0; d < D; d++){ rXv[d] = rXv_bal[d]; } + + /* each new component which has not been cut has been declared + * saturated; an original large component is declared saturated if all + * new components within are saturated */ + bool saturation = true; + while (first_vertex_bal[rv_new] < first_vertex_big[rv + 1]){ + saturation = saturation && is_saturated[rv_new]; + rv_new++; + } + is_saturated[rv] = saturation; + } + /* small components */ + for (comp_t rv = rV_big; rv < rV_ini; rv++){ + value_t* rXv = rX + D*rv; + value_t* rXv_bal = rX + D*(rv + rV_dif); + for (size_t d = 0; d < D; d++){ rXv[d] = rXv_bal[d]; } + is_saturated[rv] = is_saturated[rv + rV_dif]; + } + rX = (value_t*) realloc_check(rX, sizeof(value_t)*D*rV_ini); + is_saturated = (bool*) realloc_check(is_saturated, sizeof(bool)*rV_ini); + + /** revert to initial component list **/ + /* big components */ + for (comp_t rv = 0; rv < rV_big; rv++){ + first_vertex[rv] = first_vertex_big[rv]; + } + /* small components; in-place */ + for (comp_t rv = rV_big; rv <= rV_ini; rv++){ + first_vertex[rv] = first_vertex[rv + rV_dif]; + } + first_vertex = (index_t*) realloc_check(first_vertex, + sizeof(index_t)*(rV_ini + 1)); + free(first_vertex_big); + rV = rV_ini; +} + +TPL uintmax_t CP::split_values_complexity() const +{ + uintmax_t complexity = 0; + /* initialization: k-means++ */ + complexity += D*V*K*(K - 1)/2; // draw initialization + complexity += D*V*(K + 1)*split_values_iter_num; // k-means + complexity *= split_values_init_num; // repetition + /* updates */ + complexity += D*(K + V)*(split_iter_num - 1); + return complexity; +} + +TPL uintmax_t CP::split_complexity() const +{ + /* graph cut */ + uintmax_t complexity = D*V; // account unary split cost and final labeling + complexity += E; // account for binary split cost capacities + complexity += maxflow_complexity(); // graph cut + if (K > 2){ complexity *= K; } // K alternative labels + complexity *= split_iter_num; // repeated + /* all split value computations (init and updates) */ + complexity += split_values_complexity(); + return complexity*(V - saturated_vert)/V; // account saturation linearly +} + +TPL real_t CP::vert_split_cost(const Split_info& split_info, index_t v, + comp_t k, comp_t l) const +{ + if (k == l){ return 0.0; } + return vert_split_cost(split_info, v, k) + - vert_split_cost(split_info, v, l); +} + +TPL CP::Split_info::Split_info(comp_t rv) : rv(rv), K(0), first_k(0), + sX(nullptr) {} + +TPL CP::Split_info::~Split_info() { + //free(sX); +} + +TPL typename CP::Split_info CP::initialize_split_info(comp_t rv) +{ + + Split_info split_info(rv); + + split_info.sX = (value_t*) malloc_check(sizeof(value_t)*D*K); + value_t* sX = split_info.sX; + + index_t comp_size = first_vertex[rv + 1] - first_vertex[rv]; + const index_t* comp_list_rv = comp_list + first_vertex[rv]; + + /* split cost map and random device for k-means++ */ + real_t* near_cost = (real_t*) malloc_check(sizeof(real_t)*comp_size); + default_random_engine rand_gen; // default seed also enough for our purpose + + /* best centroids, assignment and corresponding sum of split costs */ + real_t current_sum_cost = real_inf(); + real_t best_sum_cost = real_inf(); + comp_t best_K = K; + comp_t* best_assign = split_values_init_num == 1 ? nullptr : + (comp_t*) malloc_check(sizeof(comp_t)*comp_size); + value_t* best_centroids = split_values_init_num == 1 ? nullptr : + (value_t*) malloc_check(sizeof(value_t)*D*K); + + /** kmeans ++ **/ + for (int init = 0; init < split_values_init_num; init++){ + split_info.K = K; + + /** initialization **/ + for (comp_t k = 0; k < split_info.K; k++){ + index_t rand_i; + if (k == 0){ /* draw a value uniformly */ + uniform_int_distribution unif_distr(0, comp_size - 1); + rand_i = unif_distr(rand_gen); + }else{ /* draw value with higher probability to vertices not + * satisfied with centroids already computed, that is + * with higher unary split costs of the centroids */ + for (index_t i = 0; i < comp_size; i++){ + index_t v = comp_list_rv[i]; + near_cost[i] = real_inf(); + for (comp_t l = 0; l < k; l++){ + real_t c = vert_split_cost(split_info, v, l); + if (c < near_cost[i]){ near_cost[i] = c; } + } + } + /* ensure positivity and deal with infinite costs; + * concerning positivity, absolute values of costs are not + * meaningful here anyway, only their differences are, so one + * can subtract the minimum; + * concerning infinite costs, they might concern + * non-informative centroids, so we do note encourage them; + * nonzero values ensures weights are not all zero, and prevent + * from drawing twice the same vertex */ + real_t min = near_cost[0]; + for (index_t i = 1; i < comp_size; i++){ + if (near_cost[i] < min){ min = near_cost[i]; } + } + for (index_t i = 0; i < comp_size; i++){ + if (near_cost[i] == real_inf()){ near_cost[i] = eps; } + else{ (near_cost[i] -= min) += eps; } + } + discrete_distribution split_cost_distr(near_cost, + near_cost + comp_size); + rand_i = split_cost_distr(rand_gen); + } + index_t rand_v = comp_list_rv[rand_i]; + set_split_value(split_info, k, rand_v); + } // end for k + + /** k-means **/ + for (int iter = 0; iter < split_values_iter_num; iter++){ + /* assign clusters to centroids */ + for (index_t i = 0; i < comp_size; i++){ + index_t v = comp_list_rv[i]; + real_t min_cost = real_inf(); + for (comp_t k = 0; k < split_info.K; k++){ + real_t c = vert_split_cost(split_info, v, k); + if (c < min_cost){ + min_cost = c; + label_assign[v] = k; + } + } + } + /* update centroids of clusters */ + update_split_info(split_info); + } + + if (split_values_init_num > 1){ /* keep the best sum of costs */ + real_t current_sum_cost = 0.0; + for (index_t i = 0; i < comp_size; i++){ + index_t v = comp_list_rv[i]; + comp_t k = label_assign[v]; + current_sum_cost += vert_split_cost(split_info, v, k); + } + if (current_sum_cost < best_sum_cost){ + best_sum_cost = current_sum_cost; + best_K = split_info.K; + for (size_t dk = 0; dk < D*split_info.K; dk++){ + best_centroids[dk] = sX[dk]; + } + for (index_t i = 0; i < comp_size; i++){ + index_t v = comp_list_rv[i]; + best_assign[i] = label_assign[v]; + } + } + } + + } // end for init + + free(near_cost); + + if (current_sum_cost != best_sum_cost){ + /* copy best centroids and assignment */ + split_info.K = best_K; + for (size_t dk = 0; dk < D*split_info.K; dk++){ + sX[dk] = best_centroids[dk]; + } + for (index_t i = 0; i < comp_size; i++){ + index_t v = comp_list_rv[i]; + label_assign[v] = best_assign[i]; + } + } + + free(best_centroids); + free(best_assign); + + if (split_info.K == 2){ split_info.first_k = 1; } + + return split_info; +} + +TPL void CP::split_component(comp_t rv, Maxflow* maxflow) +{ + index_t comp_size = first_vertex[rv + 1] - first_vertex[rv]; + const index_t* comp_list_rv = comp_list + first_vertex[rv]; + + Split_info split_info = initialize_split_info(rv); + + real_t damping = split_damp_ratio; + for (int split_it = 0; split_it < split_iter_num; split_it++){ + damping += (1.0 - split_damp_ratio)/split_iter_num; + + if (split_it > 0){ update_split_info(split_info); } + + bool no_reassignment = true; + + /** assign split values with graph cuts; + ** for K = 2, one graph cut 0 vs 1 in enough; otherwise iterate + ** over K alternative values like alpha-expansion **/ + for (comp_t k = split_info.first_k; k < split_info.K; k++){ + + /* set the source/sink capacities */ + for (index_t i = 0; i < comp_size; i++){ + index_t v = comp_list_rv[i]; + comp_t l = split_info.K == 2 ? 0 : label_assign[v]; + /* unary cost: choosing alternative k against alternative l */ + maxflow->terminal_capacity(i) = vert_split_cost(split_info, v, + k, l); + } + + /* set edge capacities */ + index_t e_in_comp = 0; + for (index_t i = 0; i < comp_size; i++){ + index_t u = comp_list_rv[i]; + comp_t lu = split_info.K == 2 ? 0 : label_assign[u]; + for (index_t e = first_edge[u]; e < first_edge[u + 1]; e++){ + if (!is_bind(e)){ continue; } + index_t v = adj_vertices[e]; + comp_t lv = split_info.K == 2 ? 0 : label_assign[v]; + if (lu == lv){ + /* special case useful for avoiding additional flow, + * and getting meaningful residual flows (e.g. for + * directionnaly differentiable problems, where they + * might represent subgradients) */ + real_t cap = damping*edge_split_cost(split_info, e, + lu, k); + maxflow->set_edge_capacities(e_in_comp++, cap, cap); + }else{ + /* horizontal and source/sink capacities are modified + * according to Kolmogorov & Zabih (2004); in their + * notations, functional E(u,v) is decomposed as + * + * E(0,0) | E(0,1) A | B + * --------------- = ------- + * E(1,0) | E(1,1) C | D + * + * 0 | 0 0 | D-C 0 |B+C-A-D + * = A + --------- + -------- + ----------- + * C-A | C-A 0 | D-C 0 | 0 + * + * constant + unary terms + binary term + */ + /* A = E(0,0) binary cost of the current assignment */ + real_t A = damping*edge_split_cost(split_info, e, + lu, lv); + /* B = E(0,1) binary cost of changing lv to k */ + real_t B = damping*edge_split_cost(split_info, e, + lu, k); + /* C = E(1,0) binary cost of changing lu to k */ + real_t C = damping*edge_split_cost(split_info, e, + k, lv); + /* D = E(1,1) = 0 binary cost for changing both to k */ + /* set capacities with horizontal orientation u -> v */ + maxflow->terminal_capacity(i) += C - A; + maxflow->terminal_capacity(index_in_comp[v]) -= C; + maxflow->set_edge_capacities(e_in_comp++, B + C - A, + 0.0); + } + } // end for all edges of vertex + } // end for all vertices + + /* find min cut and set assignment accordingly */ + maxflow->maxflow(); + + for (index_t i = 0; i < comp_size; i++){ + index_t v = comp_list_rv[i]; + comp_t l = maxflow->is_sink(i) ? k : + split_info.K == 2 ? 0 : label_assign[v]; + if (label_assign[v] != l){ + label_assign[v] = l; + no_reassignment = false; + } + } + } // end for k + + if (no_reassignment){ break; } + + } // end for split_it + +} + +TPL index_t CP::split() +{ + index_t activation = 0; + comp_t rV_new, rV_big; + index_t* first_vertex_big; + balance_split(rV_big, rV_new, first_vertex_big); + + /* components are processed in parallel but graph structure specifies edges + * ends with global indexing; the following table enables constant time + * conversion to indexing within components */ + index_in_comp = (index_t*) malloc_check(sizeof(index_t)*V); + + for (comp_t rv = 0; rv < rV; rv++){ + if (is_saturated[rv]){ continue; } + /** build flow graph structure **/ + /* set indexing within component and get number of binding edge */ + index_t comp_size = first_vertex[rv + 1] - first_vertex[rv]; + const index_t* comp_list_rv = comp_list + first_vertex[rv]; + index_t number_of_edges = 0; + for (index_t i = 0; i < comp_size; i++){ + index_t v = comp_list_rv[i]; + index_in_comp[v] = i; + for (index_t e = first_edge[v]; e < first_edge[v + 1]; e++){ + if (is_bind(e)){ number_of_edges++; } + } + } + /* build flow graph structure and set edges */ + Maxflow* maxflow = new Maxflow + (comp_size, number_of_edges); + for (index_t i = 0; i < comp_size; i++){ + index_t v = comp_list_rv[i]; + for (index_t e = first_edge[v]; e < first_edge[v + 1]; e++){ + index_t j = index_in_comp[adj_vertices[e]]; + if (is_bind(e)){ maxflow->add_edge(i, j); } + } + } + + /** set capacities and compute maximum flow **/ + split_component(rv, maxflow); + + /** activate edges accordingly **/ + index_t rv_activation = 0; + for (index_t i = first_vertex[rv]; i < first_vertex[rv + 1]; i++){ + index_t v = comp_list[i]; + comp_t l = label_assign[v]; + for (index_t e = first_edge[v]; e < first_edge[v + 1]; e++){ + if (is_bind(e) && l != label_assign[adj_vertices[e]]){ + cut(e); + rv_activation++; + } + } + } + + is_saturated[rv] = rv_activation == 0; + activation += rv_activation; + + delete maxflow; + } + + free(index_in_comp); index_in_comp = nullptr; + + if (rV_new != rV_big){ + activation += remove_balance_separations(rV_new); + revert_balance_split(rV_big, rV_new, first_vertex_big); + } + + /* reconstruct components assignment */ + for (comp_t rv = 0; rv < rV; rv++){ + for (index_t i = first_vertex[rv]; i < first_vertex[rv + 1]; i++){ + comp_assign[comp_list[i]] = rv; + } + } + + return activation; +} + +TPL comp_t CP::get_merge_chain_root(comp_t rv) const +{ + while (merge_chains_root[rv] != CHAIN_END){ rv = merge_chains_root[rv]; } + return rv; +} + +TPL comp_t CP::merge_components(comp_t ru, comp_t rv) +{ + /* ensure the component with smallest identifier will be the root of the + * merge chain */ + if (ru > rv){ comp_t tmp = ru; ru = rv; rv = tmp; } + /* link both chains; update leaf of the merge chain; update root info */ + merge_chains_next[merge_chains_leaf[ru]] = rv; + merge_chains_leaf[ru] = merge_chains_leaf[rv]; + merge_chains_root[rv] = merge_chains_root[merge_chains_leaf[rv]] = ru; + /* saturation considerations are taken care of in merge method */ + return ru; // root of the resulting merge chain +} + +TPL index_t CP::merge() +{ + /** create the chains representing the merged components **/ + merge_chains_root = (comp_t*) malloc_check(sizeof(comp_t)*rV); + merge_chains_next = (comp_t*) malloc_check(sizeof(comp_t)*rV); + merge_chains_leaf = (comp_t*) malloc_check(sizeof(comp_t)*rV); + for (comp_t rv = 0; rv < rV; rv++){ + merge_chains_root[rv] = CHAIN_END; + merge_chains_next[rv] = CHAIN_END; + merge_chains_leaf[rv] = rv; + } + comp_t merge_count = compute_merge_chains(); + + /** at this point, three different component assignments exists: + ** the one from previous iteration (in last_comp_assign), + ** the current one after the split (in comp_assign), and + ** the final one after the merge (to be computed now) **/ + + /** recompute saturation: compare previous iterate and final assignment, + ** and flag nonevolving components as saturated **/ + if (!last_rV){ /* first iteration, no previous assignment available */ + for (comp_t rv = 0; rv < rV; rv++){ is_saturated[rv] = false; } + }else{ + /* a previous component is flagged nonevolving if it can be assigned a + * unique final component */ + /* we can reuse storage since for now last_rV <= rV */ + comp_t* saturation_flag = merge_chains_leaf; + for (comp_t last_rv = 0; last_rv < last_rV; last_rv++){ + saturation_flag[last_rv] = NOT_ASSIGNED; + } + /* run along each final component, from their root */ + for (comp_t ru = 0; ru < rV; ru++){ + if (merge_chains_root[ru] != CHAIN_END){ continue; } + comp_t last_ru = last_comp_assign[comp_list[first_vertex[ru]]]; + if (saturation_flag[last_ru] == NOT_ASSIGNED){ + saturation_flag[last_ru] = ASSIGNED; + }else{ /* was already assigned another final component */ + saturation_flag[last_ru] = NOT_SATURATED; + } + /* run along the merge chain */ + comp_t rv = ru; + while (rv != CHAIN_END){ + comp_t last_rv = last_comp_assign[comp_list[first_vertex[rv]]]; + if (last_ru != last_rv){ /* previous components do not agree */ + saturation_flag[last_ru] = saturation_flag[last_rv] = + NOT_SATURATED; + } + rv = merge_chains_next[rv]; + } + } + /* resulting saturation for each final component */ + for (comp_t rv = 0; rv < rV; rv++){ + if (merge_chains_root[rv] != CHAIN_END){ continue; } + comp_t last_rv = last_comp_assign[comp_list[first_vertex[rv]]]; + is_saturated[rv] = saturation_flag[last_rv] != NOT_SATURATED; + } + } + free(merge_chains_leaf); // also storage of saturation_flag + + /** if no merge take place, no update needed **/ + if (!merge_count){ + free(merge_chains_root); + free(merge_chains_next); + return 0; + } + + /** construct the final component lists in temporary storage, and update + ** components saturation, values and first vertex indices in-place **/ + saturated_comp = 0; + saturated_vert = 0; + + /* auxiliary components lists */ + index_t* tmp_comp_list = (index_t*) malloc_check(sizeof(index_t)*V); + + comp_t rn = 0; // component number + index_t i = 0; // index in the final comp_list + /* each current component is assigned its final component; + * this can use the same storage as merge chains root, because the only + * required information is to flag roots (no need to get back to roots), + * and roots are processed before getting assigned a final component */ + comp_t* final_comp = merge_chains_root; + for (comp_t ru = 0; ru < rV; ru++){ + if (merge_chains_root[ru] != CHAIN_END){ continue; } + /** ru is a root, create the corresponding final component **/ + /* copy component value and saturation; + * can be done in-place because rn <= ru guaranteed */ + const value_t* rXu = rX + D*ru; + value_t* rXn = rX + D*rn; + for (size_t d = 0; d < D; d++){ rXn[d] = rXu[d]; } + if ((is_saturated[rn] = is_saturated[ru])){ saturated_comp++; } + /* run along the merge chain */ + index_t first = i; // holds index of first vertex of the component + comp_t rv = ru; + while (rv != CHAIN_END){ + final_comp[rv] = rn; + /* assign all vertices to final component */ + for (index_t j = first_vertex[rv]; j < first_vertex[rv + 1]; j++){ + tmp_comp_list[i++] = comp_list[j]; + } + if (is_saturated[rn]){ + saturated_vert += first_vertex[rv + 1] - first_vertex[rv]; + } + rv = merge_chains_next[rv]; + } + /* the root of each chain is the component with smallest id in the + * chain, and the current components are visited in increasing order, + * so now that 'rn' final components have been constructed, at least + * the first 'rn' current components have been copied, hence + * 'first_vertex' will not be accessed before position 'rn' anymore; + * can thus modify in-place */ + first_vertex[rn++] = first; + } + + /* finalize and shrink arrays to fit the reduced number of components */ + first_vertex[rV = rn] = V; + first_vertex = (index_t*) realloc_check(first_vertex, + sizeof(index_t)*(rV + 1)); + rX = (value_t*) realloc_check(rX, sizeof(value_t)*D*rV); + is_saturated = (bool*) realloc_check(is_saturated, sizeof(bool)*rV); + + /* update components assignments */ + for (index_t v = 0; v < V; v++){ + comp_list[v] = tmp_comp_list[v]; + comp_assign[v] = final_comp[comp_assign[v]]; + } + free(tmp_comp_list); + + /* deactivate edges between merged components */ + index_t deactivation = 0; + for (comp_t rv = 0; rv < rV; rv++){ + for (index_t i = first_vertex[rv]; i < first_vertex[rv + 1]; i++){ + index_t v = comp_list[i]; + for (index_t e = first_edge[v]; e < first_edge[v + 1]; e++){ + if (is_bind(e)){ continue; } + if (!is_bind(e) && rv == comp_assign[adj_vertices[e]]){ + bind(e); + deactivation++; + } + } + } + } + + /** update reduced edges **/ + + /* update current reduced edges ends with final components */ + comp_t* is_isolated = merge_chains_next; // reuse storage + for (comp_t rv = 0; rv < rV; rv++){ is_isolated[rv] = ((comp_t) true); } + + for (index_t re = 0; re < rE; re++){ + comp_t ru = final_comp[reduced_edges_u(re)]; + comp_t rv = final_comp[reduced_edges_v(re)]; + if (ru > rv){ comp_t tmp = ru; ru = rv; rv = tmp; } + reduced_edges_u(re) = ru; + reduced_edges_v(re) = rv; + if (ru != rv && reduced_edge_weights[ru] > 0.0){ + is_isolated[ru] = is_isolated[rv] = ((comp_t) false); + } + } + + free(merge_chains_root); // also storage of final_comp + + /* reorder by increasing lexicographic order on the components */ + index_t* permutation = (index_t*) malloc_check(sizeof(index_t)*rE); + for (index_t re = 0; re < rE; re++){ permutation[re] = re; } + sort(permutation, permutation + rE, + [this] (index_t re1, index_t re2) -> bool + { return reduced_edges_u(re1) < reduced_edges_u(re2) || + (reduced_edges_u(re1) == reduced_edges_u(re2) && + reduced_edges_v(re1) < reduced_edges_v(re2)); }); + + /* remove duplicates and accumulate edge weights */ + comp_t* new_red_edg = (comp_t*) malloc_check(sizeof(comp_t)*2*rE); + real_t* new_red_edg_wghts = (real_t*) malloc_check(sizeof(real_t)*rE); + index_t re = 0; + index_t final_re = 0; + while (re < rE){ + /* draw next edge */ + comp_t ru = reduced_edges_u(permutation[re]); + comp_t rv = reduced_edges_v(permutation[re]); + /* put it in the list if regular or isolated */ + if (ru != rv || is_isolated[ru]){ + new_red_edg[((size_t) 2)*final_re] = ru; + new_red_edg[((size_t) 2)*final_re + 1] = rv; + /* compute edge weight */ + if (is_isolated[ru]){ + new_red_edg_wghts[final_re] = eps; + do{ re++; } + while (re < rE && ru == reduced_edges_u(permutation[re])); + }else{ + real_t new_red_wght = 0.0; + do{ new_red_wght += reduced_edge_weights[permutation[re]]; + re++; } + while (re < rE && ru == reduced_edges_u(permutation[re]) + && rv == reduced_edges_v(permutation[re])); + new_red_edg_wghts[final_re] = new_red_wght; + } + final_re++; + }else{ + re++; + } + } + + free(permutation); + free(reduced_edges); + free(reduced_edge_weights); + free(merge_chains_next); // also storage of is_isolated + + rE = final_re; + reduced_edges = (comp_t*) realloc_check(new_red_edg, sizeof(comp_t)*2*rE); + reduced_edge_weights = (real_t*) realloc_check(new_red_edg_wghts, + sizeof(real_t)*rE); + + return deactivation; +} + +///** instantiate for compilation **/ +//#if defined _OPENMP && _OPENMP < 200805 +///* use of unsigned counter in parallel loops requires OpenMP 3.0; +// * although published in 2008, MSVC still does not support it as of 2020 */ +//template class Cp; +//template class Cp; +//template class Cp; +//template class Cp; +//#else +//template class Cp; +//template class Cp; +//template class Cp; +//template class Cp; +//#endif + + +/** instantiate for compilation **/ +template class Cp; +template class Cp; +template class Cp; +template class Cp; + diff --git a/src/cut_pursuit_d0.cpp b/src/cut_pursuit_d0.cpp new file mode 100644 index 0000000..1a4086f --- /dev/null +++ b/src/cut_pursuit_d0.cpp @@ -0,0 +1,334 @@ +/*============================================================================= + * Hugo Raguet 2019 + *===========================================================================*/ +#include "cut_pursuit_d0.hpp" +#include +#include + +#define EDGE_WEIGHTS_(e) (edge_weights ? edge_weights[(e)] : homo_edge_weight) + +#define TPL template +#define CP_D0 Cp_d0 + +using namespace std; + +TPL CP_D0::Cp_d0(index_t V, index_t E, const index_t* first_edge, + const index_t* adj_vertices, size_t D) + : Cp(V, E, first_edge, adj_vertices, D) +{ + K = 2; + split_iter_num = 2; + split_damp_ratio = 1.0; + split_values_init_num = 3; + split_values_iter_num = 3; + merge_gains = nullptr; + merge_values = nullptr; +} + +TPL real_t CP_D0::compute_graph_d0() const +{ + real_t weighted_contour_length = 0.0; + + for (index_t re = 0; re < rE; re++){ + weighted_contour_length += reduced_edge_weights[re]; + } + return weighted_contour_length; +} + +TPL real_t CP_D0::compute_f() const +{ + real_t f = 0.0; + for (comp_t rv = 0; rv < rV; rv++){ + real_t* rXv = rX + D*rv; + for (index_t i = first_vertex[rv]; i < first_vertex[rv + 1]; i++){ + f += fv(comp_list[i], rXv); + } + } + return f; +} + +TPL real_t CP_D0::compute_objective() const +{ return compute_f() + compute_graph_d0(); } // f(x) + ||x||_d0 + +TPL real_t CP_D0::vert_split_cost(const Split_info& split_info, index_t v, + comp_t k) const +{ return fv(v, split_info.sX + D*k); } + +/* compute binary cost of choosing alternatives lu and lv at edge e */ +TPL real_t CP_D0::edge_split_cost(const Split_info& split_info, index_t e, + comp_t lu, comp_t lv) const +{ return lu == lv ? 0.0 : EDGE_WEIGHTS_(e); } + +TPL comp_t CP_D0::accept_merge_candidate(index_t re) +{ + comp_t ru = reduced_edges_u(re); + comp_t rv = reduced_edges_v(re); + ru = merge_components(ru, rv); + value_t* rXu = rX + D*ru; + for (size_t d = 0; d < D; d++){ rXu[d] = merge_values[re][d]; } + delete_merge_candidate(re); + return ru; +} + +TPL void CP_D0::delete_merge_candidate(index_t re) +{ free(merge_values[re]); merge_values[re] = nullptr; } + +TPL comp_t CP_D0::compute_merge_chains() +{ + comp_t merge_count = 0; + + /* compute merge candidates in parallel */ + merge_gains = (real_t*) malloc_check(sizeof(real_t)*rE); + merge_values = (value_t**) malloc_check(sizeof(value_t*)*rE); + for (index_t re = 0; re < rE; re++){ merge_values[re] = nullptr; } + index_t num_pos_candidates = 0, num_neg_candidates = 0; + for (index_t re = 0; re < rE; re++){ + comp_t ru = reduced_edges_u(re); + comp_t rv = reduced_edges_v(re); + if (ru == rv){ continue; } + compute_merge_candidate(re); + if (merge_values[re]){ + if (merge_gains[re] > 0.0){ num_pos_candidates++; } + else{ num_neg_candidates++; } + } + } + + if (!(num_pos_candidates || num_neg_candidates)){ + free(merge_gains); free(merge_values); + return 0; + } + + /* local read-only access to merge_gains; useful for lambdas below, + * since one cannot directly capture member variables */ + const real_t* _merge_gains = merge_gains; + + if (num_pos_candidates){ + /** merge candidates with positive gains; + ** these are important enough to be merged in decreasing gain order, and + ** to update surrounding merge candidates after each merge: + ** 1) maintain candidates in a priority order on the gain + ** 2) maintain access to all reduced edges involving a given vertex, and + ** to their potential corresponding candidate in the priority order; + ** because of 2), the best choice for 1) is a binary search tree **/ + + /* 1) binary search tree on the gain */ + auto compare_candidates = [_merge_gains] (index_t mc1, index_t mc2) -> bool + { return _merge_gains[mc1] > _merge_gains[mc2] || + /* ensure unique identification of merge candidates */ + (_merge_gains[mc1] == _merge_gains[mc2] && mc1 < mc2); }; + set + candidates_queue(compare_candidates); + for (index_t re = 0; re < rE; re++){ + if (merge_values[re] && merge_gains[re] > 0.0){ + candidates_queue.insert(re); + } + } + + /* 2) linked list structure for updating reduced graph while merging */ + /* - given a component, we need access to the list of merge candidates + * whose corresponding reduced edge involves the considered component; + * - to that purpose, we maintain for each component a linked list of such + * merge candidates; we call "merge candidate cell" the data structure with + * the merge candidate identifier and the access to the next cell in such a + * linked list; + * - each active merge candidates is thus referenced in two such cells: one + * within both lists of starting and ending components of the corresponding + * reduced edge; + * - one can thus compact information mapping unequivocally each merge + * candidate mc to merge candidate cells identifiers 2*mc and 2*mc + 1; + * conversely, the merge candidate of a cell mcc is mcc/2 + * - the link list structure can thus be maintained with the following + * tables: + * first_candidate_cell[ru] is the index of the first merge candidate + * cell of the list of adjacent candidates for component ru + * next_candidate_cell[mcc] is the index of the merge candidate cell + * that comes after mcc within the list containing it + */ + typedef size_t Cell_id; + #define EMPTY_CELL (std::numeric_limits::max()) + Cell_id* first_candidate_cell = (Cell_id*) + malloc_check(sizeof(Cell_id*)*rV); + Cell_id* next_candidate_cell = (Cell_id*) + malloc_check(sizeof(Cell_id*)*2*rE); + for (comp_t rv = 0; rv < rV; rv++){ + first_candidate_cell[rv] = EMPTY_CELL; + } + for (Cell_id mcc = 0; mcc < ((Cell_id) 2)*rE; mcc++){ + next_candidate_cell[mcc] = EMPTY_CELL; + } + #define GET_REDUCED_EDGE(mcc) (*mcc/2) + #define FIRST_CELL(mcc, rv) (mcc = &first_candidate_cell[rv]) + #define NEXT_CELL(mcc) (mcc = &next_candidate_cell[*mcc]) + #define DELETE_CELL(mcc) (*mcc = next_candidate_cell[*mcc]) + #define IS_EMPTY(mcc) (*mcc == EMPTY_CELL) + + /* construct the linked list structure; + * last_candidate_cell[ru] is the index of the last merge candidate cell + * of the list of adjacent candidates for component ru; + * useful only for constructing the list in linear time */ + Cell_id* last_candidate_cell = (Cell_id*) + malloc_check(sizeof(index_t*)*rV); + for (comp_t rv = 0; rv < rV; rv++){ last_candidate_cell[rv] = EMPTY_CELL; } + for (index_t re = 0; re < rE; re++){ + comp_t ru = reduced_edges_u(re); + comp_t rv = reduced_edges_v(re); + if (ru == rv){ continue; } + #define INSERT_CELL(rv, mcc) \ + if (last_candidate_cell[rv] == EMPTY_CELL){ \ + first_candidate_cell[rv] = mcc; \ + last_candidate_cell[rv] = mcc; \ + }else{ \ + next_candidate_cell[last_candidate_cell[rv]] = mcc; \ + last_candidate_cell[rv] = mcc; \ + } + Cell_id mcc_ru = ((Cell_id) 2)*re, mcc_rv = ((Cell_id) 2)*re + 1; + INSERT_CELL(ru, mcc_ru); INSERT_CELL(rv, mcc_rv); + } + free(last_candidate_cell); + + /* iterative merge following the above order */ + while (!candidates_queue.empty()){ + typename set::iterator candidate = candidates_queue.begin(); + index_t re = *candidate; + comp_t ru = reduced_edges_u(re); + comp_t rv = reduced_edges_v(re); + + /** accept the merge and remove from the queue **/ + comp_t ro = accept_merge_candidate(re); // merge ru and rv + if (ro != ru){ rv = ru; ru = ro; } // makes sure ru is the root + candidates_queue.erase(candidate); + merge_count++; + + /** update reduced graph structure and adjacent merge candidates **/ + Cell_id *mcc_ru, *mcc_rv; + + /* first pass on the list of rv: cleanup deleted candidates, remove + * current merging candidate, update vertices by replacing rv by ru */ + FIRST_CELL(mcc_rv, rv); + while (!IS_EMPTY(mcc_rv)){ + index_t re_rv = GET_REDUCED_EDGE(mcc_rv); + if (!reduced_edge_weights[re_rv]){ DELETE_CELL(mcc_rv); continue; } + comp_t end_re_rv; + if (reduced_edges_u(re_rv) == rv){ + reduced_edges_u(re_rv) = ru; + end_re_rv = reduced_edges_v(re_rv); + }else{ + reduced_edges_v(re_rv) = ru; + end_re_rv = reduced_edges_u(re_rv); + } + if (end_re_rv == ru){ DELETE_CELL(mcc_rv); continue; } + NEXT_CELL(mcc_rv); + } + + /* cleanup deleted candidates and delete current merging candidate from + * ru list, and search candidates adjacent to both ru and rv with same + * end vertex; + * NOTA: bilinear time cost in orders of merging components cannot be + * avoided; in particular, ordering lists by end vertex identifiers + * would require reordering of all adjacent candidates of rv, bilinear + * in order of rv and sum of orders of its adjacent candidates + * NOTA: might be done in parallel along ru list, but current merging + * candidate must be removed before, and might not be worth it */ + FIRST_CELL(mcc_ru, ru); + while (!IS_EMPTY(mcc_ru)){ + index_t re_ru = GET_REDUCED_EDGE(mcc_ru); + if (!reduced_edge_weights[re_ru]){ DELETE_CELL(mcc_ru); continue; } + comp_t end_re_ru = reduced_edges_u(re_ru) == ru ? + reduced_edges_v(re_ru) : reduced_edges_u(re_ru); + if (end_re_ru == ru){ DELETE_CELL(mcc_ru); continue; } + for (FIRST_CELL(mcc_rv, rv); !IS_EMPTY(mcc_rv); NEXT_CELL(mcc_rv)){ + index_t re_rv = GET_REDUCED_EDGE(mcc_rv); + comp_t end_re_rv = reduced_edges_u(re_rv) == ru ? + reduced_edges_v(re_rv) : reduced_edges_u(re_rv); + if (end_re_ru == end_re_rv){ + reduced_edge_weights[re_ru] += reduced_edge_weights[re_rv]; + reduced_edge_weights[re_rv] = 0.0; // sum must be constant + if (merge_gains[re_rv] > 0.0){ /* remove from queue */ + candidate = candidates_queue.find(re_rv); + candidate = candidates_queue.erase(candidate); + merge_gains[re_rv] = 0.0; + } + delete_merge_candidate(re_rv); + DELETE_CELL(mcc_rv); + /* NOTA: sister candidate cell for re_rv still exists in + * the list of adjacent candidates of end_re_rv; but this + * situation is flagged with zero reduced edge weight */ + break; + } + } + NEXT_CELL(mcc_ru); + } + + /* at that point, mcc_ru is the last (empty) cell of the ru list; + * concatenate adjacent candidate list of rv after the one of ru */ + *mcc_ru = first_candidate_cell[rv]; + + /* update all adjacent candidates */ + for (FIRST_CELL(mcc_ru, ru); !IS_EMPTY(mcc_ru); NEXT_CELL(mcc_ru)){ + index_t re = GET_REDUCED_EDGE(mcc_ru); + if (merge_gains[re] > 0.0){ /* already in the queue */ + candidate = candidates_queue.find(re); + candidate = candidates_queue.erase(candidate); + }else{ + candidate = candidates_queue.end(); + } + compute_merge_candidate(re); + if (merge_gains[re] > 0.0){ + candidates_queue.insert(candidate, re); + } + } + } // end while candidates queue not empty + + free(first_candidate_cell); free(next_candidate_cell); + } // end if num_pos_candidates + + if (num_neg_candidates){ + /** merge candidates with negative gains; + ** these are less important, no update of adajacent candidates; + ** only sort once and merge in that order **/ + index_t bufsize = num_neg_candidates; + index_t* neg_candidates = (index_t*) malloc_check(sizeof(index_t)*bufsize); + num_neg_candidates = 0; // recounting + for (index_t re = 0; re < rE; re++){ + if (merge_values[re]){ + if (num_neg_candidates == bufsize){ + bufsize += bufsize/2 + 1; + neg_candidates = (index_t*) realloc_check(neg_candidates, + sizeof(index_t)*bufsize); + } + neg_candidates[num_neg_candidates++] = re; + } + } + sort(neg_candidates, neg_candidates + num_neg_candidates, + [_merge_gains] (index_t re1, index_t re2) -> bool + { return _merge_gains[re1] > _merge_gains[re2]; }); + for (index_t mc = 0; mc < num_neg_candidates; mc++){ + index_t re = neg_candidates[mc]; + /* ensure candidate info is up-to-date */ + comp_t ru = get_merge_chain_root(reduced_edges_u(re)); + comp_t rv = get_merge_chain_root(reduced_edges_v(re)); + if (ru == rv){ + delete_merge_candidate(re); + }else{ + reduced_edges_u(re) = ru; + reduced_edges_v(re) = rv; + compute_merge_candidate(re); + if (merge_values[re]){ + accept_merge_candidate(re); + merge_count++; + } + } + } + + free(neg_candidates); + } // end if num_neg_candidates + + free(merge_gains); free(merge_values); + return merge_count; +} + +template class Cp_d0; +template class Cp_d0; +template class Cp_d0; +template class Cp_d0; diff --git a/src/maxflow.cpp b/src/maxflow.cpp new file mode 100644 index 0000000..cbf1e37 --- /dev/null +++ b/src/maxflow.cpp @@ -0,0 +1,446 @@ +/* maxflow.cpp */ + +#include +#include +#include // for instantiation +#include "maxflow.hpp" + +/* special constants for parent arcs */ +#define TERMINAL terminal +#define ORPHAN orphan +/* infinite distance to the terminal */ +#define INFINITE_D (std::numeric_limits::max()) + +#define TPL template +#define MXFL Maxflow + +using namespace std; + +TPL MXFL::Maxflow(index_t node_num, index_t edge_num) + : terminal(&reserved_terminal_arc), orphan(&reserved_orphan_arc), + nodeptr_block(nullptr) +{ + nodes = (node*) malloc(sizeof(node)*node_num); + arcs = (arc*) malloc(sizeof(arc)*2*edge_num); + if (!nodes || !arcs) { + cerr << "Maxflow: not enough memory." << endl; + exit(EXIT_FAILURE); + } + + node_last = nodes + node_num; + arc_last = arcs; // arcs not created yet + + for (node* i = nodes; i < node_last; i++){ i->first = nullptr; } +} + +TPL MXFL::~Maxflow() +{ + if (nodeptr_block){ + delete nodeptr_block; + nodeptr_block = nullptr; + } + free(nodes); + free(arcs); +} + +/***********************************************************************/ + +/* + Functions for processing active list. + i->next points to the next node in the list + (or to i, if i is the last node in the list). + If i->next is nullptr iff i is not in the list. + + There are two queues. Active nodes are added + to the end of the second queue and read from + the front of the first queue. If the first queue + is empty, it is replaced by the second queue + (and the second queue becomes empty). +*/ + + +TPL inline void MXFL::set_active(node *i) +{ + if (!i->next){ + /* it's not in the list yet */ + if (queue_last[1]) queue_last[1]->next = i; + else queue_first[1] = i; + queue_last[1] = i; + i->next = i; + } +} + +/* + Returns the next active node. + If it is connected to the sink, it stays in the list, (???) + otherwise it is removed from the list +*/ +TPL inline typename MXFL::node* MXFL::next_active() +{ + node *i; + + while (true){ + if (!(i = queue_first[0])){ + queue_first[0] = i = queue_first[1]; + queue_last[0] = queue_last[1]; + queue_first[1] = nullptr; + queue_last[1] = nullptr; + if (!i) return nullptr; + } + + /* remove it from the active list */ + if (i->next == i) queue_first[0] = queue_last[0] = nullptr; + else queue_first[0] = i->next; + i->next = nullptr; + + /* a node in the list is active iff it has a parent */ + if (i->parent) return i; + } +} + +/***********************************************************************/ + +TPL inline void MXFL::set_orphan_front(node *i) +{ + nodeptr *np; + i->parent = ORPHAN; + np = nodeptr_block->New(); + np->ptr = i; + np->next = orphan_first; + orphan_first = np; +} + +TPL inline void MXFL::set_orphan_rear(node *i) +{ + nodeptr *np; + i->parent = ORPHAN; + np = nodeptr_block->New(); + np->ptr = i; + if (orphan_last) orphan_last->next = np; + else orphan_first = np; + orphan_last = np; + np->next = nullptr; +} + +/***********************************************************************/ + +TPL void MXFL::maxflow_init() +{ + node *i; + + queue_first[0] = queue_last[0] = nullptr; + queue_first[1] = queue_last[1] = nullptr; + orphan_first = nullptr; + + TIME = 0; + + for (i = nodes; i < node_last; i++){ + i->next = nullptr; + i->TS = TIME; + if (i->term_res_cap > 0){ + /* i is connected to the source */ + i->is_sink = false; + i->parent = TERMINAL; + set_active(i); + i->DIST = 1; + }else if (i->term_res_cap < 0){ + /* i is connected to the sink */ + i->is_sink = true; + i->parent = TERMINAL; + set_active(i); + i->DIST = 1; + }else{ + i->parent = nullptr; + } + } +} + +TPL void MXFL::augment(arc *middle_arc) +{ + node *i; + arc *a; + flow_t bottleneck; + + + /* 1. Finding bottleneck capacity */ + /* 1a - the source tree */ + bottleneck = middle_arc->res_cap; + for (i = middle_arc->sister->head; ; i = a->head) + { + a = i->parent; + if (a == TERMINAL) break; + if (bottleneck > a->sister->res_cap) bottleneck = a->sister->res_cap; + } + if (bottleneck > i->term_res_cap) bottleneck = i->term_res_cap; + /* 1b - the sink tree */ + for (i = middle_arc->head; ; i = a->head) + { + a = i->parent; + if (a == TERMINAL) break; + if (bottleneck > a->res_cap) bottleneck = a->res_cap; + } + if (bottleneck > - i->term_res_cap) bottleneck = - i->term_res_cap; + + + /* 2. Augmenting */ + /* 2a - the source tree */ + middle_arc->sister->res_cap += bottleneck; + middle_arc->res_cap -= bottleneck; + for (i = middle_arc->sister->head; ; i = a->head) + { + a = i->parent; + if (a == TERMINAL) break; + a->res_cap += bottleneck; + a->sister->res_cap -= bottleneck; + if (!a->sister->res_cap){ set_orphan_front(i); } + } + i->term_res_cap -= bottleneck; + if (!i->term_res_cap){ set_orphan_front(i); } + /* 2b - the sink tree */ + for (i = middle_arc->head; ; i = a->head) + { + a = i->parent; + if (a == TERMINAL) break; + a->sister->res_cap += bottleneck; + a->res_cap -= bottleneck; + if (!a->res_cap){ set_orphan_front(i); } + } + i->term_res_cap += bottleneck; + if (!i->term_res_cap){ set_orphan_front(i); } +} + +/***********************************************************************/ + +TPL void MXFL::process_source_orphan(node *i) +{ + node *j; + arc *a0, *a0_min = nullptr, *a; + index_t d, d_min = INFINITE_D; + + /* trying to find a new parent */ + for (a0 = i->first; a0; a0 = a0->next) + if (a0->sister->res_cap){ + j = a0->head; + if (!j->is_sink && (a = j->parent)){ + /* checking the origin of j */ + d = 0; + while (true){ + if (j->TS == TIME){ + d += j->DIST; + break; + } + a = j->parent; + d++; + if (a == TERMINAL){ + j->TS = TIME; + j->DIST = 1; + break; + } + if (a == ORPHAN){ + d = INFINITE_D; + break; + } + j = a->head; + } + if (d < INFINITE_D){ /* j originates from the source - done */ + if (d < d_min){ + a0_min = a0; + d_min = d; + } + /* set marks along the path */ + for (j = a0->head; j->TS != TIME; j = j->parent->head){ + j->TS = TIME; + j->DIST = d--; + } + } + } + } + + if ((i->parent = a0_min)){ + i->TS = TIME; + i->DIST = d_min + 1; + }else{ + /* process neighbors */ + for (a0 = i->first; a0; a0 = a0->next){ + j = a0->head; + if (!j->is_sink && (a = j->parent)){ + if (a0->sister->res_cap){ set_active(j); } + if (a != TERMINAL && a != ORPHAN && a->head == i){ + set_orphan_rear(j); + } + } + } + } +} + +TPL void MXFL::process_sink_orphan(node *i) +{ + node *j; + arc *a0, *a0_min = nullptr, *a; + index_t d, d_min = INFINITE_D; + + /* trying to find a new parent */ + for (a0 = i->first; a0; a0 = a0->next) + if (a0->res_cap){ + j = a0->head; + if (j->is_sink && (a = j->parent)){ + /* checking the origin of j */ + d = 0; + while (true){ + if (j->TS == TIME){ + d += j->DIST; + break; + } + a = j->parent; + d++; + if (a == TERMINAL){ + j->TS = TIME; + j->DIST = 1; + break; + } + if (a == ORPHAN){ + d = INFINITE_D; + break; + } + j = a->head; + } + if (d < INFINITE_D){ /* j originates from the sink - done */ + if (d < d_min){ + a0_min = a0; + d_min = d; + } + /* set marks along the path */ + for (j = a0->head; j->TS != TIME; j = j->parent->head){ + j->TS = TIME; + j->DIST = d--; + } + } + } + } + + if ((i->parent = a0_min)){ + i->TS = TIME; + i->DIST = d_min + 1; + }else{ + /* process neighbors */ + for (a0 = i->first; a0; a0 = a0->next){ + j = a0->head; + if (j->is_sink && (a = j->parent)){ + if (a0->res_cap) set_active(j); + if (a != TERMINAL && a != ORPHAN && a->head == i){ + set_orphan_rear(j); + } + } + } + } +} + +/***********************************************************************/ + +TPL void MXFL::maxflow() +{ + node *i, *j, *current_node = nullptr; + arc *a; + nodeptr *np, *np_next; + + if (!nodeptr_block){ + nodeptr_block = new DBlock(NODEPTR_BLOCK_SIZE); + } + + maxflow_init(); + + while (true){ + + if ((i = current_node)){ + i->next = nullptr; /* remove active flag */ + if (!i->parent) i = nullptr; + } + + if (!i){ if (!(i = next_active())){ break; } } + + /* growth */ + if (!i->is_sink){ + /* grow source tree */ + for (a = i->first; a; a = a->next) + if (a->res_cap){ + j = a->head; + if (!j->parent){ + j->is_sink = false; + j->parent = a->sister; + j->TS = i->TS; + j->DIST = i->DIST + 1; + set_active(j); + }else if (j->is_sink){ + break; + }else if (j->TS <= i->TS && j->DIST > i->DIST){ + /* heuristic - trying to make the distance from j to the + source shorter */ + j->parent = a->sister; + j->TS = i->TS; + j->DIST = i->DIST + 1; + } + } + }else{ + /* grow sink tree */ + for (a = i->first; a; a = a->next) + if (a->sister->res_cap){ + j = a->head; + if (!j->parent){ + j->is_sink = true; + j->parent = a->sister; + j->TS = i->TS; + j->DIST = i->DIST + 1; + set_active(j); + }else if (!j->is_sink){ + a = a->sister; break; + }else if (j->TS <= i->TS && j->DIST > i->DIST){ + /* heuristic - trying to make the distance from j to the + sink shorter */ + j->parent = a->sister; + j->TS = i->TS; + j->DIST = i->DIST + 1; + } + } + } + + if (++TIME <= 0){ + /* changed type from long to index_t */ + /* can't we prove this won't overflow? */ + cerr << "Maxflow: timestamp overflow." << endl; + exit(EXIT_FAILURE); + } + + if (a){ /* found a valid path; a is the middle arc */ + i->next = i; // set active flag + current_node = i; + + augment(a); + + /* adoption */ + while ((np = orphan_first)){ + np_next = np->next; + np->next = nullptr; + + while ((np = orphan_first)){ + orphan_first = np->next; + i = np->ptr; + nodeptr_block->Delete(np); + if (!orphan_first) orphan_last = nullptr; + if (i->is_sink) process_sink_orphan(i); + else process_source_orphan(i); + } + + orphan_first = np_next; + } + }else{ + current_node = nullptr; + } + + } // end main loop + + delete nodeptr_block; + nodeptr_block = nullptr; +} + +template class Maxflow; +template class Maxflow; diff --git a/src/qTreeIso.cpp b/src/qTreeIso.cpp index f3c40e4..0802286 100644 --- a/src/qTreeIso.cpp +++ b/src/qTreeIso.cpp @@ -32,7 +32,7 @@ //# # //####################################################################################### -// A Matlab version shared via: +// Matlab and python versions shared via: // https://github.com/truebelief/artemis_treeiso #include "qTreeIso.h" @@ -114,7 +114,8 @@ void qTreeIso::doAction() parameters.min_nn1 = treeisoDlg.spinBoxK1->value(); parameters.reg_strength1 = treeisoDlg.doubleSpinBoxLambda1->value();; parameters.decimate_res1 = treeisoDlg.doubleSpinBoxDecRes1->value(); - + parameters.threads1 = treeisoDlg.doubleSpinBoxThreads1->value(); + init_segs(parameters, &treeisoDlg); }); @@ -124,6 +125,7 @@ void qTreeIso::doAction() parameters.reg_strength2 = treeisoDlg.doubleSpinBoxLambda2->value(); parameters.decimate_res2 = treeisoDlg.doubleSpinBoxDecRes2->value(); parameters.max_gap = treeisoDlg.doubleSpinBoxMaxGap->value(); + parameters.threads2 = treeisoDlg.doubleSpinBoxThreads2->value(); intermediate_segs(parameters, &treeisoDlg); @@ -162,7 +164,7 @@ void qTreeIso::init_segs(const Parameters& parameters, QWidget* parent/*=nullptr progressDlg->setRange(0, 0); // infinite progress bar progressDlg->show(); - if (!TreeIso::Init_seg(parameters.min_nn1, parameters.reg_strength1, parameters.decimate_res1, m_app, progressDlg)) + if (!TreeIso::Init_seg(parameters.min_nn1, parameters.reg_strength1, parameters.decimate_res1, parameters.threads1, m_app, progressDlg)) { m_app->dispToConsole("Not enough memory", ccMainAppInterface::ERR_CONSOLE_MESSAGE); return; @@ -185,7 +187,7 @@ void qTreeIso::intermediate_segs(const Parameters& parameters, QWidget* parent/* progressDlg->setRange(0, 0); // infinite progress bar progressDlg->show(); - if (!TreeIso::Intermediate_seg(parameters.min_nn2, parameters.reg_strength2, parameters.decimate_res2, parameters.max_gap, m_app, progressDlg)) + if (!TreeIso::Intermediate_seg(parameters.min_nn2, parameters.reg_strength2, parameters.decimate_res2, parameters.max_gap, parameters.threads2, m_app, progressDlg)) { progressDlg->hide(); QApplication::processEvents(); diff --git a/ui/TreeIsoDlg.ui b/ui/TreeIsoDlg.ui index f6eb406..946feb7 100644 --- a/ui/TreeIsoDlg.ui +++ b/ui/TreeIsoDlg.ui @@ -23,24 +23,11 @@ true - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New'; font-size:9pt; font-weight:696;">TreeIso Plugin Instruction</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Courier New'; font-size:9pt; font-weight:696;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New'; font-size:9pt;">A graph-based tree point cloud isolator</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Courier New'; font-size:9pt;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New'; font-size:9pt;">Reference</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New'; font-size:9pt;">Xi, Z.; Hopkinson, C. 3D Graph-Based Individual-Tree Isolation (</span><span style=" font-family:'Courier New'; font-size:9pt; font-style:italic;">Treeiso</span><span style=" font-family:'Courier New'; font-size:9pt;">) from Terrestrial Laser Scanning Point Clouds. </span><span style=" font-family:'Courier New'; font-size:9pt; font-style:italic;">Remote Sens</span><span style=" font-family:'Courier New'; font-size:9pt;">. </span><span style=" font-family:'Courier New'; font-size:9pt; font-weight:696;">2022</span><span style=" font-family:'Courier New'; font-size:9pt;">, 14, 6116. https://doi.org/10.3390/rs14236116</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Courier New'; font-size:9pt;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New'; font-size:9pt;">Landrieu, Loic, and Guillaume Obozinski. Cut pursuit: Fast algorithms to learn piecewise constant functions on general weighted graphs. SIAM Journal on Imaging Sciences. </span><span style=" font-family:'Courier New'; font-size:9pt; font-weight:696;">2017, </span><span style=" font-family:'Courier New'; font-size:9pt;">10.4, 1724-1766.</span></p></body></html> - - - `TreeIso Plugin Instruction` + + ***Treeiso *plugin (v2)** -`A graph-based tree point cloud isolator` +`A graph-based tree point cloud isolator (assuming ground points have already +been removed)` `Reference` @@ -54,6 +41,20 @@ Imaging Sciences. 2017, 10.4, 1724-1766.` + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> +p, li { white-space: pre-wrap; } +hr { height: 1px; border-width: 0; } +li.unchecked::marker { content: "\2610"; } +li.checked::marker { content: "\2612"; } +</style></head><body style=" font-family:'Segoe UI'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:700; font-style:italic;">Treeiso </span><span style=" font-weight:700;">plugin (v2)</span></p> +<p style=" margin-top:6px; margin-bottom:6px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New';">A graph-based tree point cloud isolator (assuming ground points have already been removed)</span></p> +<p style=" margin-top:6px; margin-bottom:6px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New'; font-weight:700;">Reference</span></p> +<p style=" margin-top:6px; margin-bottom:6px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New';">Xi, Z.; Hopkinson, C. 3D Graph-Based Individual-Tree Isolation (Treeiso) from Terrestrial Laser Scanning Point Clouds. Remote Sens. 2022, 14, 6116. https://doi.org/10.3390/rs14236116</span></p> +<p style=" margin-top:6px; margin-bottom:6px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New';">Landrieu, Loic, and Guillaume Obozinski. Cut pursuit: Fast algorithms to learn piecewise constant functions on general weighted graphs. SIAM Journal on Imaging Sciences. 2017, 10.4, 1724-1766.</span></p></body></html> + @@ -89,7 +90,7 @@ Imaging Sciences. 2017, 10.4, 1724-1766.` - <html><head/><body><p>This step takes a bit long time, please be patient. <br>It will create small clusters with natural breaks.</p></body></html> + <html><head/><body><p>This step may take a little while—please be patient. <br> It will create small clusters with natural breaks.</p></body></html> @@ -211,6 +212,20 @@ Imaging Sciences. 2017, 10.4, 1724-1766.` + + + + <html><head/><body><p><span style=" font-size:10pt; font-weight:600; color:#aa0000;">Number of threads for KNN calculation </span></p></body></html> + + + + + + + 1 + + + @@ -304,7 +319,7 @@ Imaging Sciences. 2017, 10.4, 1724-1766.` 2 - 20 + 10 @@ -337,7 +352,7 @@ Imaging Sciences. 2017, 10.4, 1724-1766.` 0.050000000000000 - 20.000000000000000 + 10.000000000000000 @@ -423,6 +438,20 @@ Imaging Sciences. 2017, 10.4, 1724-1766.` + + + + <html><head/><body><p><span style=" font-size:10pt; font-weight:600; color:#aa0000;">Number of threads for KNN calculation </span></p></body></html> + + + + + + + 1 + + + @@ -611,7 +640,6 @@ Imaging Sciences. 2017, 10.4, 1724-1766.` Times New Roman - 50 false true