mirror of https://github.com/CGAL/cgal
Merge all three versions with one unique function + separate calls
This commit is contained in:
parent
4cd9282f79
commit
f0df168096
|
|
@ -50,21 +50,23 @@
|
|||
#include <CGAL/boost/graph/named_function_params.h>
|
||||
|
||||
#include <boost/version.hpp>
|
||||
#ifdef CGAL_DO_NOT_USE_BOYKOV_KOLMOGOROV_MAXFLOW_SOFTWARE
|
||||
#include <boost/graph/adjacency_list.hpp>
|
||||
#include <boost/graph/compressed_sparse_row_graph.hpp>
|
||||
#if BOOST_VERSION >= 104400 // at this version kolmogorov_max_flow become depricated.
|
||||
#include <boost/graph/boykov_kolmogorov_max_flow.hpp>
|
||||
#else
|
||||
#include <boost/graph/kolmogorov_max_flow.hpp>
|
||||
#endif
|
||||
#else
|
||||
|
||||
#ifndef CGAL_DO_NOT_USE_BOYKOV_KOLMOGOROV_MAXFLOW_SOFTWARE
|
||||
namespace MaxFlow
|
||||
{
|
||||
#include <CGAL/internal/auxiliary/graph.h>
|
||||
}
|
||||
#endif
|
||||
|
||||
#include <boost/graph/adjacency_list.hpp>
|
||||
#include <boost/graph/compressed_sparse_row_graph.hpp>
|
||||
|
||||
#if BOOST_VERSION >= 104400 // at this version kolmogorov_max_flow become depricated.
|
||||
# include <boost/graph/boykov_kolmogorov_max_flow.hpp>
|
||||
#else
|
||||
# include <boost/graph/kolmogorov_max_flow.hpp>
|
||||
#endif
|
||||
|
||||
#include <vector>
|
||||
|
||||
|
||||
|
|
@ -196,14 +198,15 @@ struct Alpha_expansion_old_API_wrapper_graph
|
|||
// since it is done by exploring all edges to find opposite
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifdef CGAL_DO_NOT_USE_BOYKOV_KOLMOGOROV_MAXFLOW_SOFTWARE
|
||||
} // namespace internal
|
||||
|
||||
/**
|
||||
* @brief Implements alpha-expansion graph cut algorithm.
|
||||
*
|
||||
* For representing graph, it uses adjacency_list with OutEdgeList = vecS, VertexList = listS.
|
||||
* Also no pre-allocation is made for vertex-list.
|
||||
*/
|
||||
class Alpha_expansion_graph_cut_boost
|
||||
class Alpha_expansion_boost_adjacency_list
|
||||
{
|
||||
private:
|
||||
typedef boost::adjacency_list_traits<boost::vecS, boost::listS, boost::directedS>
|
||||
|
|
@ -225,11 +228,73 @@ private:
|
|||
typedef boost::graph_traits<Graph> Traits;
|
||||
typedef boost::color_traits<boost::default_color_type> ColorTraits;
|
||||
|
||||
public:
|
||||
|
||||
typedef Traits::vertex_descriptor Vertex_descriptor;
|
||||
typedef Traits::vertex_iterator Vertex_iterator;
|
||||
typedef Traits::edge_descriptor Edge_descriptor;
|
||||
typedef Traits::edge_iterator Edge_iterator;
|
||||
|
||||
private:
|
||||
|
||||
Graph graph;
|
||||
Vertex_descriptor cluster_source;
|
||||
Vertex_descriptor cluster_sink;
|
||||
|
||||
public:
|
||||
|
||||
void clear_graph()
|
||||
{
|
||||
graph.clear();
|
||||
cluster_source = boost::add_vertex(graph);
|
||||
cluster_sink = boost::add_vertex(graph);
|
||||
}
|
||||
|
||||
Vertex_descriptor add_vertex()
|
||||
{
|
||||
return boost::add_vertex(graph);
|
||||
}
|
||||
|
||||
void add_tweight (Vertex_descriptor& v, double w1, double w2)
|
||||
{
|
||||
add_edge (cluster_source, v, w1, 0);
|
||||
add_edge (v, cluster_sink, w2, 0);
|
||||
}
|
||||
|
||||
void init_vertices()
|
||||
{
|
||||
// initialize vertex indices, it is necessary since we are using VertexList = listS
|
||||
Vertex_iterator v_begin, v_end;
|
||||
Traits::vertices_size_type index = 0;
|
||||
for(boost::tie(v_begin, v_end) = vertices(graph); v_begin != v_end; ++v_begin) {
|
||||
boost::put(boost::vertex_index, graph, *v_begin, index++);
|
||||
}
|
||||
}
|
||||
|
||||
double max_flow()
|
||||
{
|
||||
#if BOOST_VERSION >= 104400
|
||||
return boost::boykov_kolmogorov_max_flow(graph, cluster_source,
|
||||
cluster_sink);
|
||||
#else
|
||||
return boost::kolmogorov_max_flow(graph, cluster_source, cluster_sink);
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename VertexLabelMap, typename InputVertexDescriptor>
|
||||
void update(VertexLabelMap vertex_label_map,
|
||||
const std::vector<Vertex_descriptor>& inserted_vertices,
|
||||
InputVertexDescriptor vd,
|
||||
std::size_t vertex_i,
|
||||
std::size_t alpha)
|
||||
{
|
||||
boost::default_color_type color = boost::get(boost::vertex_color, graph,
|
||||
inserted_vertices[vertex_i]);
|
||||
if(get (vertex_label_map, vd) != alpha
|
||||
&& color == ColorTraits::white()) //new comers (expansion occurs)
|
||||
put (vertex_label_map, vd, alpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds two directional edges between @a v1 and @a v2
|
||||
* @param v1 first vertex
|
||||
|
|
@ -239,9 +304,8 @@ private:
|
|||
* @param graph to be added
|
||||
* @return pair of added edges, first: v1->v2 and second: v2->v1
|
||||
*/
|
||||
boost::tuple<Edge_descriptor, Edge_descriptor>
|
||||
add_edge_and_reverse(Vertex_descriptor& v1, Vertex_descriptor& v2, double w1,
|
||||
double w2, Graph& graph) const {
|
||||
void add_edge (Vertex_descriptor& v1, Vertex_descriptor& v2, double w1, double w2)
|
||||
{
|
||||
Edge_descriptor v1_v2, v2_v1;
|
||||
bool v1_v2_added, v2_v1_added;
|
||||
|
||||
|
|
@ -257,194 +321,13 @@ private:
|
|||
boost::put(boost::edge_capacity, graph, v1_v2, w1);
|
||||
boost::put(boost::edge_capacity, graph, v2_v1, w2);
|
||||
|
||||
return boost::make_tuple(v1_v2, v2_v1);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public:
|
||||
/**
|
||||
* Applies alpha-expansion graph-cut for energy minimization.
|
||||
* @param edges contains incident vertex-id pairs for each edge (vertex-ids should be between [0, number of vertices -1])
|
||||
* @param edge_weights contains weights for each edge in @a edges (correspondence according to order)
|
||||
* @param probability_matrix contains responsibility of the center on the vertex probability[center][vertex]
|
||||
* @param[in, out] labels as input it contains initial labeling of vertices (i.e. a center-id between [0, number of centers -1]),
|
||||
* and as output it returns final labeling of vertices (i.e. assigned cluster-id to each facet)
|
||||
* @return result of energy function
|
||||
*/
|
||||
double operator()(const std::vector<std::pair<std::size_t, std::size_t> >&
|
||||
edges,
|
||||
const std::vector<double>& edge_weights,
|
||||
const std::vector<std::vector<double> >& probability_matrix,
|
||||
std::vector<std::size_t>& labels) const
|
||||
{
|
||||
Alpha_expansion_old_API_wrapper_graph graph (edges, edge_weights, probability_matrix, labels);
|
||||
|
||||
return (*this)(graph,
|
||||
graph.edge_weight_map(),
|
||||
graph.vertex_index_map(),
|
||||
graph.vertex_label_map(),
|
||||
graph.vertex_label_probability_map());
|
||||
}
|
||||
|
||||
template <typename InputGraph,
|
||||
typename Edge_weight_map,
|
||||
typename Vertex_index_map,
|
||||
typename Vertex_label_map,
|
||||
typename Vertex_label_probability_map>
|
||||
double operator()(const InputGraph& input_graph,
|
||||
Edge_weight_map edge_weight_map,
|
||||
Vertex_index_map vertex_index_map,
|
||||
Vertex_label_map vertex_label_map,
|
||||
Vertex_label_probability_map vertex_label_probability_map) const
|
||||
{
|
||||
typedef boost::graph_traits<InputGraph> GT;
|
||||
typedef typename GT::edge_descriptor input_edge_descriptor;
|
||||
typedef typename GT::vertex_descriptor input_vertex_descriptor;
|
||||
|
||||
// TODO: check this hardcoded parameter
|
||||
const double tolerance = 1e-10;
|
||||
|
||||
double min_cut = (std::numeric_limits<double>::max)();
|
||||
|
||||
#ifdef CGAL_SEGMENTATION_BENCH_GRAPHCUT
|
||||
double vertex_creation_time, edge_creation_time, cut_time;
|
||||
vertex_creation_time = edge_creation_time = cut_time = 0.0;
|
||||
#endif
|
||||
|
||||
std::vector<Vertex_descriptor> inserted_vertices;
|
||||
inserted_vertices.resize(num_vertices (input_graph));
|
||||
|
||||
std::size_t number_of_labels = get(vertex_label_probability_map, *std::begin(vertices(input_graph))).size();
|
||||
|
||||
Graph graph;
|
||||
|
||||
bool success;
|
||||
do {
|
||||
success = false;
|
||||
|
||||
for (std::size_t alpha = 0; alpha < number_of_labels; ++ alpha)
|
||||
{
|
||||
graph.clear();
|
||||
|
||||
Vertex_descriptor cluster_source = boost::add_vertex(graph);
|
||||
Vertex_descriptor cluster_sink = boost::add_vertex(graph);
|
||||
|
||||
#ifdef CGAL_SEGMENTATION_BENCH_GRAPHCUT
|
||||
Timer timer;
|
||||
timer.start();
|
||||
#endif
|
||||
|
||||
// For E-Data
|
||||
// add every input vertex as a vertex to the graph, put edges to source & sink vertices
|
||||
for (input_vertex_descriptor vd : vertices(input_graph))
|
||||
{
|
||||
std::size_t vertex_i = get(vertex_index_map, vd);
|
||||
Vertex_descriptor new_vertex = boost::add_vertex(graph);
|
||||
inserted_vertices[vertex_i] = new_vertex;
|
||||
double source_weight = get(vertex_label_probability_map, vd)[alpha];
|
||||
// since it is expansion move, current alpha labeled vertices will be assigned to alpha again,
|
||||
// making sink_weight 'infinity' guarantee this.
|
||||
double sink_weight = (get(vertex_label_map, vd) == alpha ?
|
||||
(std::numeric_limits<double>::max)()
|
||||
: get(vertex_label_probability_map, vd)[get(vertex_label_map, vd)]);
|
||||
|
||||
add_edge_and_reverse(cluster_source, new_vertex, source_weight, 0.0, graph);
|
||||
add_edge_and_reverse(new_vertex, cluster_sink, sink_weight, 0.0, graph);
|
||||
}
|
||||
#ifdef CGAL_SEGMENTATION_BENCH_GRAPHCUT
|
||||
vertex_creation_time += timer.time();
|
||||
timer.reset();
|
||||
#endif
|
||||
|
||||
// For E-Smooth
|
||||
// add edge between every vertex,
|
||||
for (input_edge_descriptor ed : edges(input_graph))
|
||||
{
|
||||
input_vertex_descriptor vd1 = source(ed, input_graph);
|
||||
input_vertex_descriptor vd2 = target(ed, input_graph);
|
||||
std::size_t idx1 = get (vertex_index_map, vd1);
|
||||
std::size_t idx2 = get (vertex_index_map, vd2);
|
||||
|
||||
double weight = get (edge_weight_map, ed);
|
||||
|
||||
Vertex_descriptor v1 = inserted_vertices[idx1],
|
||||
v2 = inserted_vertices[idx2];
|
||||
|
||||
std::size_t label_1 = get (vertex_label_map, vd1);
|
||||
std::size_t label_2 = get (vertex_label_map, vd2);
|
||||
if(label_1 == label_2) {
|
||||
if(label_1 != alpha) {
|
||||
add_edge_and_reverse(v1, v2, weight, weight, graph);
|
||||
}
|
||||
} else {
|
||||
Vertex_descriptor inbetween = boost::add_vertex(graph);
|
||||
|
||||
double w1 = (label_1 == alpha) ? 0 : weight;
|
||||
double w2 = (label_2 == alpha) ? 0 : weight;
|
||||
add_edge_and_reverse(inbetween, v1, w1, w1, graph);
|
||||
add_edge_and_reverse(inbetween, v2, w2, w2, graph);
|
||||
add_edge_and_reverse(inbetween, cluster_sink, weight, 0.0, graph);
|
||||
}
|
||||
}
|
||||
#ifdef CGAL_SEGMENTATION_BENCH_GRAPHCUT
|
||||
edge_creation_time += timer.time();
|
||||
#endif
|
||||
|
||||
// initialize vertex indices, it is necessary since we are using VertexList = listS
|
||||
Vertex_iterator v_begin, v_end;
|
||||
Traits::vertices_size_type index = 0;
|
||||
for(boost::tie(v_begin, v_end) = vertices(graph); v_begin != v_end; ++v_begin) {
|
||||
boost::put(boost::vertex_index, graph, *v_begin, index++);
|
||||
}
|
||||
|
||||
#ifdef CGAL_SEGMENTATION_BENCH_GRAPHCUT
|
||||
timer.reset();
|
||||
#endif
|
||||
#if BOOST_VERSION >= 104400
|
||||
double flow = boost::boykov_kolmogorov_max_flow(graph, cluster_source,
|
||||
cluster_sink);
|
||||
#else
|
||||
double flow = boost::kolmogorov_max_flow(graph, cluster_source, cluster_sink);
|
||||
#endif
|
||||
#ifdef CGAL_SEGMENTATION_BENCH_GRAPHCUT
|
||||
cut_time += timer.time();
|
||||
#endif
|
||||
|
||||
if(min_cut - flow <= flow * tolerance) {
|
||||
continue;
|
||||
}
|
||||
min_cut = flow;
|
||||
success = true;
|
||||
//update labeling
|
||||
for (input_vertex_descriptor vd : vertices (input_graph))
|
||||
{
|
||||
std::size_t vertex_i = get (vertex_index_map, vd);
|
||||
boost::default_color_type color = boost::get(boost::vertex_color, graph,
|
||||
inserted_vertices[vertex_i]);
|
||||
if(get (vertex_label_map, vd) != alpha
|
||||
&& color == ColorTraits::white()) { //new comers (expansion occurs)
|
||||
put (vertex_label_map, vd, alpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
} while(success);
|
||||
|
||||
#ifdef CGAL_SEGMENTATION_BENCH_GRAPHCUT
|
||||
CGAL_TRACE_STREAM << "vertex creation time: " << vertex_creation_time <<
|
||||
std::endl;
|
||||
CGAL_TRACE_STREAM << "edge creation time: " << edge_creation_time << std::endl;
|
||||
CGAL_TRACE_STREAM << "max flow algorithm time: " << cut_time << std::endl;
|
||||
#endif
|
||||
|
||||
return min_cut;
|
||||
}
|
||||
};
|
||||
|
||||
// another implementation using compressed_sparse_row_graph
|
||||
// for now there is a performance problem while setting reverse edges
|
||||
// if that can be solved, it is faster than Alpha_expansion_graph_cut_boost
|
||||
class Alpha_expansion_graph_cut_boost_CSR
|
||||
class Alpha_expansion_boost_compressed_sparse_row
|
||||
{
|
||||
private:
|
||||
// CSR only accepts bundled props
|
||||
|
|
@ -470,217 +353,127 @@ private:
|
|||
typedef boost::graph_traits<Graph> Traits;
|
||||
typedef boost::color_traits<boost::default_color_type> ColorTraits;
|
||||
|
||||
public:
|
||||
|
||||
typedef Traits::vertex_descriptor Vertex_descriptor;
|
||||
typedef Traits::vertex_iterator Vertex_iterator;
|
||||
typedef Traits::edge_descriptor Edge_descriptor;
|
||||
typedef Traits::edge_iterator Edge_iterator;
|
||||
|
||||
void
|
||||
add_edge_and_reverse(std::size_t v1 , std::size_t v2, double w1, double w2,
|
||||
std::vector<std::pair<std::size_t, std::size_t> >& edge_map,
|
||||
std::vector<EdgeP>& edge_weights) const {
|
||||
private:
|
||||
|
||||
Graph graph;
|
||||
std::size_t nb_vertices;
|
||||
std::vector<std::pair<std::size_t, std::size_t> > edge_map;
|
||||
std::vector<EdgeP> edge_map_weights;
|
||||
|
||||
public:
|
||||
void clear_graph()
|
||||
{
|
||||
nb_vertices = 2;
|
||||
edge_map.clear();
|
||||
edge_map_weights.clear();
|
||||
// edge_map.reserve(labels.size() *
|
||||
// 8); // there is no way to know exact edge count, it is a heuristic value
|
||||
// edge_map_weights.reserve(labels.size() * 8);
|
||||
}
|
||||
|
||||
Vertex_descriptor add_vertex()
|
||||
{
|
||||
return (nb_vertices ++);
|
||||
}
|
||||
|
||||
void add_tweight (Vertex_descriptor& v, double w1, double w2)
|
||||
{
|
||||
add_edge (0, v, w1, 0);
|
||||
add_edge (v, 1, w2, 0);
|
||||
}
|
||||
|
||||
void init_vertices()
|
||||
{
|
||||
#if BOOST_VERSION >= 104000
|
||||
graph = Graph(boost::edges_are_unsorted, edge_map.begin(), edge_map.end(),
|
||||
edge_map_weights.begin(), nb_vertices);
|
||||
#else
|
||||
graph= Graph(edge_map.begin(), edge_map.end(),
|
||||
edge_map_weights.begin(), nb_vertices);
|
||||
#endif
|
||||
|
||||
// PERFORMANCE PROBLEM
|
||||
// need to set reverse edge map, I guess there is no way to do that before creating the graph
|
||||
// since we do not have edge_descs
|
||||
// however from our edge_map, we know that each (2i, 2i + 1) is reverse pairs, how to facilitate that ?
|
||||
// will look it back
|
||||
Graph::edge_iterator ei, ee;
|
||||
for(boost::tie(ei, ee) = boost::edges(graph); ei != ee; ++ei) {
|
||||
Graph::vertex_descriptor v1 = boost::source(*ei, graph);
|
||||
Graph::vertex_descriptor v2 = boost::target(*ei, graph);
|
||||
std::pair<Graph::edge_descriptor, bool> opp_edge = boost::edge(v2, v1, graph);
|
||||
|
||||
CGAL_assertion(opp_edge.second);
|
||||
graph[opp_edge.first].edge_reverse =
|
||||
*ei; // and edge_reverse of *ei will be (or already have been) set by the opp_edge
|
||||
}
|
||||
}
|
||||
|
||||
double max_flow()
|
||||
{
|
||||
#if BOOST_VERSION >= 104400
|
||||
// since properties are bundled, defaults does not work need to specify them
|
||||
return boost::boykov_kolmogorov_max_flow
|
||||
(graph,
|
||||
boost::get(&EdgeP::edge_capacity, graph),
|
||||
boost::get(&EdgeP::edge_residual_capacity, graph),
|
||||
boost::get(&EdgeP::edge_reverse, graph),
|
||||
boost::get(&VertexP::vertex_predecessor, graph),
|
||||
boost::get(&VertexP::vertex_color, graph),
|
||||
boost::get(&VertexP::vertex_distance_t, graph),
|
||||
boost::get(boost::vertex_index,
|
||||
graph), // this is not bundled, get it from graph (CRS provides one)
|
||||
0, 1);
|
||||
#else
|
||||
return boost::kolmogorov_max_flow
|
||||
(graph,
|
||||
boost::get(&EdgeP::edge_capacity, graph),
|
||||
boost::get(&EdgeP::edge_residual_capacity, graph),
|
||||
boost::get(&EdgeP::edge_reverse, graph),
|
||||
boost::get(&VertexP::vertex_predecessor, graph),
|
||||
boost::get(&VertexP::vertex_color, graph),
|
||||
boost::get(&VertexP::vertex_distance_t, graph),
|
||||
boost::get(boost::vertex_index,
|
||||
graph), // this is not bundled, get it from graph
|
||||
0, 1);
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename VertexLabelMap, typename InputVertexDescriptor>
|
||||
void update(VertexLabelMap vertex_label_map,
|
||||
const std::vector<Vertex_descriptor>& inserted_vertices,
|
||||
InputVertexDescriptor vd,
|
||||
std::size_t vertex_i,
|
||||
std::size_t alpha)
|
||||
{
|
||||
boost::default_color_type color = graph[vertex_i + 2].vertex_color;
|
||||
if(get(vertex_label_map, vd)!= alpha
|
||||
&& color == ColorTraits::white()) //new comers (expansion occurs)
|
||||
put(vertex_label_map, vd, alpha);
|
||||
}
|
||||
|
||||
void add_edge(Vertex_descriptor v1, Vertex_descriptor v2, double w1, double w2)
|
||||
{
|
||||
edge_map.push_back(std::make_pair(v1, v2));
|
||||
EdgeP p1;
|
||||
p1.edge_capacity = w1;
|
||||
edge_weights.push_back(p1);
|
||||
edge_map_weights.push_back(p1);
|
||||
|
||||
edge_map.push_back(std::make_pair(v2, v1));
|
||||
EdgeP p2;
|
||||
p2.edge_capacity = w2;
|
||||
edge_weights.push_back(p2);
|
||||
edge_map_weights.push_back(p2);
|
||||
}
|
||||
|
||||
public:
|
||||
/**
|
||||
* Applies alpha-expansion graph-cut for energy minimization.
|
||||
* @param edges contains incident vertex-id pairs for each edge (vertex-ids should be between [0, number of vertices -1])
|
||||
* @param edge_weights contains weights for each edge in @a edges (correspondence according to order)
|
||||
* @param probability_matrix contains responsibility of the center on the vertex probability[center][vertex]
|
||||
* @param[in, out] labels as input it contains initial labeling of vertices (i.e. a center-id between [0, number of centers -1]),
|
||||
* and as output it returns final labeling of vertices (i.e. assigned cluster-id to each facet)
|
||||
* @return result of energy function
|
||||
*/
|
||||
double operator()(const std::vector<std::pair<std::size_t, std::size_t> >&
|
||||
edges,
|
||||
const std::vector<double>& edge_weights,
|
||||
const std::vector<std::vector<double> >& probability_matrix,
|
||||
std::vector<std::size_t>& labels) const {
|
||||
const double tolerance = 1e-10;
|
||||
|
||||
double min_cut = (std::numeric_limits<double>::max)();
|
||||
|
||||
#ifdef CGAL_SEGMENTATION_BENCH_GRAPHCUT
|
||||
double vertex_creation_time, edge_creation_time, graph_creation_time,
|
||||
reverse_mapping_time, cut_time;
|
||||
vertex_creation_time = edge_creation_time = graph_creation_time =
|
||||
reverse_mapping_time = cut_time = 0.0;
|
||||
#endif
|
||||
|
||||
Graph graph;
|
||||
|
||||
bool success;
|
||||
do {
|
||||
success = false;
|
||||
std::size_t alpha = 0;
|
||||
|
||||
for(std::vector<std::vector<double> >::const_iterator it =
|
||||
probability_matrix.begin();
|
||||
it != probability_matrix.end(); ++it, ++alpha) {
|
||||
std::vector<std::pair<std::size_t, std::size_t> > edge_map;
|
||||
std::vector<EdgeP> edge_map_weights;
|
||||
edge_map.reserve(labels.size() *
|
||||
8); // there is no way to know exact edge count, it is a heuristic value
|
||||
edge_map_weights.reserve(labels.size() * 8);
|
||||
|
||||
std::size_t cluster_source = 0;
|
||||
std::size_t cluster_sink = 1;
|
||||
|
||||
#ifdef CGAL_SEGMENTATION_BENCH_GRAPHCUT
|
||||
Timer timer;
|
||||
timer.start();
|
||||
#endif
|
||||
// For E-Data
|
||||
// add every facet as a vertex to the graph, put edges to source & sink vertices
|
||||
for(std::size_t vertex_i = 0; vertex_i < labels.size(); ++vertex_i) {
|
||||
double source_weight = probability_matrix[alpha][vertex_i];
|
||||
// since it is expansion move, current alpha labeled vertices will be assigned to alpha again,
|
||||
// making sink_weight 'infinity' guarantee this.
|
||||
double sink_weight = (labels[vertex_i] == alpha) ?
|
||||
(std::numeric_limits<double>::max)()
|
||||
: probability_matrix[labels[vertex_i]][vertex_i];
|
||||
|
||||
add_edge_and_reverse(cluster_source, vertex_i + 2, source_weight, 0.0, edge_map,
|
||||
edge_map_weights);
|
||||
add_edge_and_reverse(vertex_i + 2, cluster_sink, sink_weight, 0.0, edge_map,
|
||||
edge_map_weights);
|
||||
}
|
||||
#ifdef CGAL_SEGMENTATION_BENCH_GRAPHCUT
|
||||
vertex_creation_time += timer.time();
|
||||
timer.reset();
|
||||
#endif
|
||||
// For E-Smooth
|
||||
// add edge between every vertex,
|
||||
std::size_t num_vert = labels.size() + 2;
|
||||
std::vector<double>::const_iterator weight_it = edge_weights.begin();
|
||||
for(std::vector<std::pair<std::size_t, std::size_t> >::const_iterator edge_it =
|
||||
edges.begin(); edge_it != edges.end();
|
||||
++edge_it, ++weight_it) {
|
||||
std::size_t v1 = edge_it->first + 2, v2 = edge_it->second + 2;
|
||||
std::size_t label_1 = labels[edge_it->first], label_2 = labels[edge_it->second];
|
||||
if(label_1 == label_2) {
|
||||
if(label_1 != alpha) {
|
||||
add_edge_and_reverse(v1, v2, *weight_it, *weight_it, edge_map,
|
||||
edge_map_weights);
|
||||
}
|
||||
} else {
|
||||
std::size_t inbetween = num_vert++;
|
||||
|
||||
double w1 = (label_1 == alpha) ? 0 : *weight_it;
|
||||
double w2 = (label_2 == alpha) ? 0 : *weight_it;
|
||||
add_edge_and_reverse(inbetween, v1, w1, w1, edge_map, edge_map_weights);
|
||||
add_edge_and_reverse(inbetween, v2, w2, w2, edge_map, edge_map_weights);
|
||||
add_edge_and_reverse(inbetween, cluster_sink, *weight_it, 0.0, edge_map,
|
||||
edge_map_weights);
|
||||
}
|
||||
}
|
||||
#ifdef CGAL_SEGMENTATION_BENCH_GRAPHCUT
|
||||
edge_creation_time += timer.time();
|
||||
timer.reset();
|
||||
#endif
|
||||
#if BOOST_VERSION >= 104000
|
||||
Graph graph(boost::edges_are_unsorted, edge_map.begin(), edge_map.end(),
|
||||
edge_map_weights.begin(), num_vert);
|
||||
#else
|
||||
Graph graph(edge_map.begin(), edge_map.end(),
|
||||
edge_map_weights.begin(), num_vert);
|
||||
#endif
|
||||
#ifdef CGAL_SEGMENTATION_BENCH_GRAPHCUT
|
||||
graph_creation_time += timer.time();
|
||||
timer.reset();
|
||||
#endif
|
||||
|
||||
// PERFORMANCE PROBLEM
|
||||
// need to set reverse edge map, I guess there is no way to do that before creating the graph
|
||||
// since we do not have edge_descs
|
||||
// however from our edge_map, we know that each (2i, 2i + 1) is reverse pairs, how to facilitate that ?
|
||||
// will look it back
|
||||
Graph::edge_iterator ei, ee;
|
||||
for(boost::tie(ei, ee) = boost::edges(graph); ei != ee; ++ei) {
|
||||
Graph::vertex_descriptor v1 = boost::source(*ei, graph);
|
||||
Graph::vertex_descriptor v2 = boost::target(*ei, graph);
|
||||
std::pair<Graph::edge_descriptor, bool> opp_edge = boost::edge(v2, v1, graph);
|
||||
|
||||
CGAL_assertion(opp_edge.second);
|
||||
graph[opp_edge.first].edge_reverse =
|
||||
*ei; // and edge_reverse of *ei will be (or already have been) set by the opp_edge
|
||||
}
|
||||
#ifdef CGAL_SEGMENTATION_BENCH_GRAPHCUT
|
||||
reverse_mapping_time += timer.time();
|
||||
timer.reset();
|
||||
#endif
|
||||
|
||||
#if BOOST_VERSION >= 104400
|
||||
// since properties are bundled, defaults does not work need to specify them
|
||||
double flow = boost::boykov_kolmogorov_max_flow(graph,
|
||||
boost::get(&EdgeP::edge_capacity, graph),
|
||||
boost::get(&EdgeP::edge_residual_capacity, graph),
|
||||
boost::get(&EdgeP::edge_reverse, graph),
|
||||
boost::get(&VertexP::vertex_predecessor, graph),
|
||||
boost::get(&VertexP::vertex_color, graph),
|
||||
boost::get(&VertexP::vertex_distance_t, graph),
|
||||
boost::get(boost::vertex_index,
|
||||
graph), // this is not bundled, get it from graph (CRS provides one)
|
||||
cluster_source,
|
||||
cluster_sink
|
||||
);
|
||||
#else
|
||||
double flow = boost::kolmogorov_max_flow(graph,
|
||||
boost::get(&EdgeP::edge_capacity, graph),
|
||||
boost::get(&EdgeP::edge_residual_capacity, graph),
|
||||
boost::get(&EdgeP::edge_reverse, graph),
|
||||
boost::get(&VertexP::vertex_predecessor, graph),
|
||||
boost::get(&VertexP::vertex_color, graph),
|
||||
boost::get(&VertexP::vertex_distance_t, graph),
|
||||
boost::get(boost::vertex_index,
|
||||
graph), // this is not bundled, get it from graph
|
||||
cluster_source,
|
||||
cluster_sink
|
||||
);
|
||||
#endif
|
||||
#ifdef CGAL_SEGMENTATION_BENCH_GRAPHCUT
|
||||
cut_time += timer.time();
|
||||
#endif
|
||||
|
||||
if(min_cut - flow <= flow * tolerance) {
|
||||
continue;
|
||||
}
|
||||
min_cut = flow;
|
||||
success = true;
|
||||
//update labeling
|
||||
for(std::size_t vertex_i = 0; vertex_i < labels.size(); ++vertex_i) {
|
||||
boost::default_color_type color = graph[vertex_i + 2].vertex_color;
|
||||
if(labels[vertex_i] != alpha
|
||||
&& color == ColorTraits::white()) { //new comers (expansion occurs)
|
||||
labels[vertex_i] = alpha;
|
||||
}
|
||||
}
|
||||
}
|
||||
} while(success);
|
||||
|
||||
#ifdef CGAL_SEGMENTATION_BENCH_GRAPHCUT
|
||||
CGAL_TRACE_STREAM << "vertex creation time: " << vertex_creation_time <<
|
||||
std::endl;
|
||||
CGAL_TRACE_STREAM << "edge creation time: " << edge_creation_time << std::endl;
|
||||
CGAL_TRACE_STREAM << "graph creation time: " << graph_creation_time <<
|
||||
std::endl;
|
||||
CGAL_TRACE_STREAM << "reverse mapping time: " << reverse_mapping_time <<
|
||||
std::endl;
|
||||
CGAL_TRACE_STREAM << "max flow algorithm time: " << cut_time << std::endl;
|
||||
#endif
|
||||
return min_cut;
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifndef CGAL_DO_NOT_USE_BOYKOV_KOLMOGOROV_MAXFLOW_SOFTWARE
|
||||
/**
|
||||
|
|
@ -689,132 +482,71 @@ public:
|
|||
* For underlying max-flow algorithm, it uses the MAXFLOW software implemented by Boykov & Kolmogorov.
|
||||
* Also no pre-allocation is made.
|
||||
*/
|
||||
class Alpha_expansion_graph_cut_boykov_kolmogorov
|
||||
class Alpha_expansion_MaxFlow
|
||||
{
|
||||
public:
|
||||
|
||||
typedef MaxFlow::Graph::node_id Vertex_descriptor;
|
||||
|
||||
private:
|
||||
|
||||
MaxFlow::Graph graph;
|
||||
|
||||
public:
|
||||
|
||||
void clear_graph()
|
||||
{
|
||||
graph = MaxFlow::Graph();
|
||||
}
|
||||
|
||||
Vertex_descriptor add_vertex()
|
||||
{
|
||||
return graph.add_node();
|
||||
}
|
||||
|
||||
void add_tweight (Vertex_descriptor& v, double w1, double w2)
|
||||
{
|
||||
graph.add_tweights(v, w1, w2);
|
||||
}
|
||||
|
||||
void init_vertices()
|
||||
{
|
||||
}
|
||||
|
||||
double max_flow()
|
||||
{
|
||||
return graph.maxflow();
|
||||
}
|
||||
|
||||
template <typename VertexLabelMap, typename InputVertexDescriptor>
|
||||
void update(VertexLabelMap vertex_label_map,
|
||||
const std::vector<Vertex_descriptor>& inserted_vertices,
|
||||
InputVertexDescriptor vd,
|
||||
std::size_t vertex_i,
|
||||
std::size_t alpha)
|
||||
{
|
||||
if(get(vertex_label_map, vd) != alpha
|
||||
&& graph.what_segment(inserted_vertices[vertex_i]) == MaxFlow::Graph::SINK)
|
||||
put(vertex_label_map, vd, alpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies alpha-expansion graph-cut for energy minimization.
|
||||
* @param edges contains incident vertex-id pairs for each edge (vertex-ids should be between [0, number of vertices -1])
|
||||
* @param edge_weights contains weights for each edge in @a edges (correspondence according to order)
|
||||
* @param probability_matrix contains responsibility of the center on the vertex probability[center][vertex]
|
||||
* @param[in, out] labels as input it contains initial labeling of vertices (i.e. a center-id between [0, number of centers -1]),
|
||||
* and as output it returns final labeling of vertices
|
||||
* @return result of energy function
|
||||
* Adds two directional edges between @a v1 and @a v2
|
||||
* @param v1 first vertex
|
||||
* @param v2 second vertex
|
||||
* @param w1 weight for edge from v1 to v2 (v1->v2)
|
||||
* @param w2 weight for edge from v2 to v1 (v2->v1)
|
||||
* @param graph to be added
|
||||
* @return pair of added edges, first: v1->v2 and second: v2->v1
|
||||
*/
|
||||
double operator()(const std::vector<std::pair<std::size_t, std::size_t> >&
|
||||
edges,
|
||||
const std::vector<double>& edge_weights,
|
||||
const std::vector<std::vector<double> >& probability_matrix,
|
||||
std::vector<std::size_t>& labels) const {
|
||||
const double tolerance = 1e-10;
|
||||
|
||||
double min_cut = (std::numeric_limits<double>::max)();
|
||||
|
||||
#ifdef CGAL_SEGMENTATION_BENCH_GRAPHCUT
|
||||
double vertex_creation_time, edge_creation_time, cut_time;
|
||||
vertex_creation_time = edge_creation_time = cut_time = 0.0;
|
||||
#endif
|
||||
|
||||
std::vector<MaxFlow::Graph::node_id> inserted_vertices;
|
||||
inserted_vertices.resize(labels.size());
|
||||
bool success;
|
||||
do {
|
||||
success = false;
|
||||
std::size_t alpha = 0;
|
||||
for(std::vector<std::vector<double> >::const_iterator it =
|
||||
probability_matrix.begin();
|
||||
it != probability_matrix.end(); ++it, ++alpha) {
|
||||
MaxFlow::Graph graph;
|
||||
#ifdef CGAL_SEGMENTATION_BENCH_GRAPHCUT
|
||||
Timer timer;
|
||||
timer.start();
|
||||
#endif
|
||||
// For E-Data
|
||||
// add every facet as a vertex to graph, put edges to source-sink vertices
|
||||
for(std::size_t vertex_i = 0; vertex_i < labels.size(); ++vertex_i) {
|
||||
MaxFlow::Graph::node_id new_vertex = graph.add_node();
|
||||
inserted_vertices[vertex_i] = new_vertex;
|
||||
|
||||
double source_weight = probability_matrix[alpha][vertex_i];
|
||||
// since it is expansion move, current alpha labeled vertices will be assigned to alpha again,
|
||||
// making sink_weight 'infinity' guarantee this.
|
||||
double sink_weight = (labels[vertex_i] == alpha) ?
|
||||
(std::numeric_limits<double>::max)()
|
||||
: probability_matrix[labels[vertex_i]][vertex_i];
|
||||
graph.add_tweights(new_vertex, source_weight, sink_weight);
|
||||
}
|
||||
#ifdef CGAL_SEGMENTATION_BENCH_GRAPHCUT
|
||||
vertex_creation_time += timer.time();
|
||||
timer.reset();
|
||||
#endif
|
||||
// For E-Smooth
|
||||
// add edge between every vertex,
|
||||
std::vector<double>::const_iterator weight_it = edge_weights.begin();
|
||||
for(std::vector<std::pair<std::size_t, std::size_t> >::const_iterator edge_it =
|
||||
edges.begin(); edge_it != edges.end();
|
||||
++edge_it, ++weight_it) {
|
||||
MaxFlow::Graph::node_id v1 = inserted_vertices[edge_it->first];
|
||||
MaxFlow::Graph::node_id v2 = inserted_vertices[edge_it->second];
|
||||
std::size_t label_1 = labels[edge_it->first], label_2 = labels[edge_it->second];
|
||||
if(label_1 == label_2) {
|
||||
if(label_1 != alpha) {
|
||||
graph.add_edge(v1, v2, *weight_it, *weight_it);
|
||||
}
|
||||
} else {
|
||||
MaxFlow::Graph::node_id inbetween = graph.add_node();
|
||||
|
||||
double w1 = (label_1 == alpha) ? 0 : *weight_it;
|
||||
double w2 = (label_2 == alpha) ? 0 : *weight_it;
|
||||
graph.add_edge(inbetween, v1, w1, w1);
|
||||
graph.add_edge(inbetween, v2, w2, w2);
|
||||
|
||||
graph.add_tweights(inbetween, 0.0, *weight_it);
|
||||
}
|
||||
}
|
||||
#ifdef CGAL_SEGMENTATION_BENCH_GRAPHCUT
|
||||
edge_creation_time += timer.time();
|
||||
timer.reset();
|
||||
#endif
|
||||
|
||||
double flow = graph.maxflow();
|
||||
#ifdef CGAL_SEGMENTATION_BENCH_GRAPHCUT
|
||||
cut_time += timer.time();
|
||||
#endif
|
||||
|
||||
if(min_cut - flow <= flow * tolerance) {
|
||||
continue;
|
||||
}
|
||||
|
||||
min_cut = flow;
|
||||
success = true;
|
||||
//update labeling
|
||||
for(std::size_t vertex_i = 0; vertex_i < labels.size(); ++vertex_i) {
|
||||
if(labels[vertex_i] != alpha
|
||||
&& graph.what_segment(inserted_vertices[vertex_i]) == MaxFlow::Graph::SINK) {
|
||||
labels[vertex_i] = alpha;
|
||||
}
|
||||
}
|
||||
}
|
||||
} while(success);
|
||||
|
||||
#ifdef CGAL_SEGMENTATION_BENCH_GRAPHCUT
|
||||
CGAL_TRACE_STREAM << "vertex creation time: " << vertex_creation_time <<
|
||||
std::endl;
|
||||
CGAL_TRACE_STREAM << "edge creation time: " << edge_creation_time << std::endl;
|
||||
CGAL_TRACE_STREAM << "max flow algorithm time: " << cut_time << std::endl;
|
||||
#endif
|
||||
return min_cut;
|
||||
void add_edge (Vertex_descriptor& v1, Vertex_descriptor& v2, double w1, double w2)
|
||||
{
|
||||
graph.add_edge(v1, v2, w1, w2);
|
||||
}
|
||||
};
|
||||
#endif //CGAL_DO_NOT_USE_BOYKOV_KOLMOGOROV_MAXFLOW_SOFTWARE
|
||||
}//namespace internal
|
||||
/// @endcond
|
||||
|
||||
struct Alpha_expansion_boost_adjacency_list { };
|
||||
struct Alpha_expansion_boost_compressed_sparse_row { };
|
||||
struct Alpha_expansion_MaxFlow { };
|
||||
|
||||
|
||||
template <typename InputGraph,
|
||||
typename Edge_weight_map,
|
||||
typename Vertex_index_map,
|
||||
|
|
@ -826,14 +558,132 @@ double alpha_expansion_graph_cut (const InputGraph& input_graph,
|
|||
Vertex_index_map vertex_index_map,
|
||||
Vertex_label_map vertex_label_map,
|
||||
Vertex_label_probability_map vertex_label_probability_map,
|
||||
const AlphaExpansionImplementation& = AlphaExpansionImplementation())
|
||||
AlphaExpansionImplementation alpha_expansion = AlphaExpansionImplementation())
|
||||
{
|
||||
if (std::is_same<AlphaExpansionImplementation, Alpha_expansion_boost_adjacency_list>::value)
|
||||
return internal::Alpha_expansion_graph_cut_boost()(input_graph,
|
||||
edge_weight_map,
|
||||
vertex_index_map,
|
||||
vertex_label_map,
|
||||
vertex_label_probability_map);
|
||||
typedef boost::graph_traits<InputGraph> GT;
|
||||
typedef typename GT::edge_descriptor input_edge_descriptor;
|
||||
typedef typename GT::vertex_descriptor input_vertex_descriptor;
|
||||
|
||||
typedef AlphaExpansionImplementation Alpha_expansion;
|
||||
typedef typename Alpha_expansion::Vertex_descriptor Vertex_descriptor;
|
||||
|
||||
// TODO: check this hardcoded parameter
|
||||
const double tolerance = 1e-10;
|
||||
|
||||
double min_cut = (std::numeric_limits<double>::max)();
|
||||
|
||||
#ifdef CGAL_SEGMENTATION_BENCH_GRAPHCUT
|
||||
double vertex_creation_time, edge_creation_time, cut_time;
|
||||
vertex_creation_time = edge_creation_time = cut_time = 0.0;
|
||||
#endif
|
||||
|
||||
std::vector<Vertex_descriptor> inserted_vertices;
|
||||
inserted_vertices.resize(num_vertices (input_graph));
|
||||
|
||||
std::size_t number_of_labels = get(vertex_label_probability_map, *std::begin(vertices(input_graph))).size();
|
||||
|
||||
bool success;
|
||||
do {
|
||||
success = false;
|
||||
|
||||
for (std::size_t alpha = 0; alpha < number_of_labels; ++ alpha)
|
||||
{
|
||||
alpha_expansion.clear_graph();
|
||||
|
||||
#ifdef CGAL_SEGMENTATION_BENCH_GRAPHCUT
|
||||
Timer timer;
|
||||
timer.start();
|
||||
#endif
|
||||
|
||||
// For E-Data
|
||||
// add every input vertex as a vertex to the graph, put edges to source & sink vertices
|
||||
for (input_vertex_descriptor vd : vertices(input_graph))
|
||||
{
|
||||
std::size_t vertex_i = get(vertex_index_map, vd);
|
||||
Vertex_descriptor new_vertex = alpha_expansion.add_vertex();
|
||||
inserted_vertices[vertex_i] = new_vertex;
|
||||
double source_weight = get(vertex_label_probability_map, vd)[alpha];
|
||||
// since it is expansion move, current alpha labeled vertices will be assigned to alpha again,
|
||||
// making sink_weight 'infinity' guarantee this.
|
||||
double sink_weight = (get(vertex_label_map, vd) == alpha ?
|
||||
(std::numeric_limits<double>::max)()
|
||||
: get(vertex_label_probability_map, vd)[get(vertex_label_map, vd)]);
|
||||
|
||||
alpha_expansion.add_tweight(new_vertex, source_weight, sink_weight);
|
||||
}
|
||||
#ifdef CGAL_SEGMENTATION_BENCH_GRAPHCUT
|
||||
vertex_creation_time += timer.time();
|
||||
timer.reset();
|
||||
#endif
|
||||
|
||||
// For E-Smooth
|
||||
// add edge between every vertex,
|
||||
for (input_edge_descriptor ed : edges(input_graph))
|
||||
{
|
||||
input_vertex_descriptor vd1 = source(ed, input_graph);
|
||||
input_vertex_descriptor vd2 = target(ed, input_graph);
|
||||
std::size_t idx1 = get (vertex_index_map, vd1);
|
||||
std::size_t idx2 = get (vertex_index_map, vd2);
|
||||
|
||||
double weight = get (edge_weight_map, ed);
|
||||
|
||||
Vertex_descriptor v1 = inserted_vertices[idx1],
|
||||
v2 = inserted_vertices[idx2];
|
||||
|
||||
std::size_t label_1 = get (vertex_label_map, vd1);
|
||||
std::size_t label_2 = get (vertex_label_map, vd2);
|
||||
if(label_1 == label_2) {
|
||||
if(label_1 != alpha) {
|
||||
alpha_expansion.add_edge(v1, v2, weight, weight);
|
||||
}
|
||||
} else {
|
||||
Vertex_descriptor inbetween = alpha_expansion.add_vertex();
|
||||
|
||||
double w1 = (label_1 == alpha) ? 0 : weight;
|
||||
double w2 = (label_2 == alpha) ? 0 : weight;
|
||||
alpha_expansion.add_edge(inbetween, v1, w1, w1);
|
||||
alpha_expansion.add_edge(inbetween, v2, w2, w2);
|
||||
alpha_expansion.add_tweight(inbetween, 0., weight);
|
||||
}
|
||||
}
|
||||
#ifdef CGAL_SEGMENTATION_BENCH_GRAPHCUT
|
||||
edge_creation_time += timer.time();
|
||||
#endif
|
||||
|
||||
alpha_expansion.init_vertices();
|
||||
|
||||
#ifdef CGAL_SEGMENTATION_BENCH_GRAPHCUT
|
||||
timer.reset();
|
||||
#endif
|
||||
|
||||
double flow = alpha_expansion.max_flow();
|
||||
|
||||
#ifdef CGAL_SEGMENTATION_BENCH_GRAPHCUT
|
||||
cut_time += timer.time();
|
||||
#endif
|
||||
|
||||
if(min_cut - flow <= flow * tolerance) {
|
||||
continue;
|
||||
}
|
||||
min_cut = flow;
|
||||
success = true;
|
||||
//update labeling
|
||||
for (input_vertex_descriptor vd : vertices (input_graph))
|
||||
{
|
||||
std::size_t vertex_i = get (vertex_index_map, vd);
|
||||
alpha_expansion.update(vertex_label_map, inserted_vertices, vd, vertex_i, alpha);
|
||||
}
|
||||
}
|
||||
} while(success);
|
||||
|
||||
#ifdef CGAL_SEGMENTATION_BENCH_GRAPHCUT
|
||||
CGAL_TRACE_STREAM << "vertex creation time: " << vertex_creation_time <<
|
||||
std::endl;
|
||||
CGAL_TRACE_STREAM << "edge creation time: " << edge_creation_time << std::endl;
|
||||
CGAL_TRACE_STREAM << "max flow algorithm time: " << cut_time << std::endl;
|
||||
#endif
|
||||
|
||||
return min_cut;
|
||||
}
|
||||
|
||||
template <typename InputGraph,
|
||||
|
|
@ -856,5 +706,20 @@ double alpha_expansion_graph_cut (const InputGraph& input_graph,
|
|||
(input_graph, edge_weight_map, vertex_index_map, vertex_label_map, vertex_label_probability_map);
|
||||
}
|
||||
|
||||
// Old API
|
||||
inline double alpha_expansion_graph_cut (const std::vector<std::pair<std::size_t, std::size_t> >& edges,
|
||||
const std::vector<double>& edge_weights,
|
||||
const std::vector<std::vector<double> >& probability_matrix,
|
||||
std::vector<std::size_t>& labels)
|
||||
{
|
||||
internal::Alpha_expansion_old_API_wrapper_graph graph (edges, edge_weights, probability_matrix, labels);
|
||||
|
||||
return alpha_expansion_graph_cut(graph,
|
||||
graph.edge_weight_map(),
|
||||
graph.vertex_index_map(),
|
||||
graph.vertex_label_map(),
|
||||
graph.vertex_label_probability_map());
|
||||
}
|
||||
|
||||
}//namespace CGAL
|
||||
#endif //CGAL_SURFACE_MESH_SEGMENTATION_ALPHA_EXPANSION_GRAPH_CUT_H
|
||||
|
|
|
|||
Loading…
Reference in New Issue