c++
How to load a TIFF image like a graph in C++ BOOST
I want do load a tiff image (GEOTIFF with pixels with float values) like graph in boost C++ (i'm a newbie in C++). My goal is use the bidirectional Dijkstra from source A to target B to get more performance. Boost:GIL load tiif images: std::string filename( "raster_clip.tif" ); rgb8_image_t img; read_image( filename, img, tiff_tag() ); But how convert to Boost graph? I am reading the documentation and looking for examples but I have not yet been able to implement it. Similar questions and examples that i found: Shortest path graph algorithm help Boost; http://www.geeksforgeeks.org/shortest-path-for-directed-acyclic-graphs/ I am currently using the scikit-image library and use skimage.graph.route_through_array function to load graph with array in python. I use GDAL to get an array by load image as suggested by #ustroetz in this example Here: raster = gdal.Open("raster.tiff") band = raster.GetRasterBand(1) array = band.ReadAsArray() Example of TIFF (was converted to PNG after upload) is:
Ok, so read the PNG: I've cropped the whitespace border since it wasn't consistent anyways Reading And Sampling The Image using Img = boost::gil::rgb8_image_t; // gray8_image_t; using Px = Img::value_type; Img img; //boost::gil::png_read_image("graph.png", img); boost::gil::png_read_and_convert_image("graph.png", img); auto vw = view(img); Next up, make sure we know the dimensions and how to address the center pixels for each cell: double constexpr cell_w = 30.409; double constexpr cell_h = 30.375; auto pixel_sample = [=](boost::array<size_t, 2> xy) -> auto& { return vw((xy[0]+.5)*cell_w, (xy[1]+.5)*cell_h); }; auto const w= static_cast<size_t>(img.dimensions()[0] / cell_w); auto const h= static_cast<size_t>(img.dimensions()[1] / cell_h); Constructing The Graph Now let's make the graph. For this task a grid-graph seems in order. It should be w×h and not wrap around at the edges (if it should, change false to true): using Graph = boost::grid_graph<2>; Graph graph({{w,h}}, false); We want to attach weights at each edge. We can either use an old-fashioned external property map that's sized up-front: std::vector<double> weight_v(num_edges(graph)); auto weights = boost::make_safe_iterator_property_map(weight_v.begin(), weight_v.size(), get(boost::edge_index, graph)); Alternatively, we can use a dynamically allocating and growing property-map: auto weights = boost::make_vector_property_map<float>(get(boost::edge_index, graph)); As a bonus, here's the equivalent approach using an associative property-map: std::map<Graph::edge_descriptor, double> weight_m; auto weights = boost::make_assoc_property_map(weight_m); Each of these are drop-in compatible and the choice is yours. Filling The Graph We simply iterate all edges, setting the cost from the colour difference: BGL_FORALL_EDGES(e, graph, Graph) { auto& from = pixel_sample(e.first); auto& to = pixel_sample(e.second); // compare RED channels only auto cost = std::abs(from[0] - to[0]); put(weights, e, cost); } Note Consider normalizing weight to e.g. [0.0, 1.0) using the actual bit-depth of the source image Let's create a verification TIF so we can actually see where the samples were taken in the image: { BGL_FORALL_VERTICES(v, graph, Graph) { pixel_sample(v) = Px(255, 0, 123); // mark the center pixels so we can verify the sampling } boost::gil::tiff_write_view("/tmp/verification.tif", const_view(img)); } The verification.tif ends up like (note the center pixel for each cell): Bonus: Visualize The Grid Graph Let's write it to a Graphviz file: { auto calc_color = [&](size_t v) { std::ostringstream oss; oss << std::hex << std::noshowbase << std::setfill('0'); auto const& from = pixel_sample(vertex(v, graph)); oss << "#" << std::setw(2) << static_cast<int>(from[0]) << std::setw(2) << static_cast<int>(from[1]) << std::setw(2) << static_cast<int>(from[2]); return oss.str(); }; write_dot_file(graph, weights, calc_color); } This calculates the color from the same sample pixel and uses some Graphviz-specific magic to write to a file: template <typename Graph, typename Weights, typename ColorFunction> void write_dot_file(Graph const& graph, Weights const& weights, ColorFunction calc_color) { boost::dynamic_properties dp; dp.property("node_id", get(boost::vertex_index, graph)); dp.property("fillcolor", boost::make_transform_value_property_map(calc_color, get(boost::vertex_index, graph))); dp.property("style", boost::make_static_property_map<typename Graph::vertex_descriptor>(std::string("filled"))); std::ofstream ofs("grid.dot"); auto vpw = boost::dynamic_vertex_properties_writer { dp, "node_id" }; auto epw = boost::make_label_writer(weights); auto gpw = boost::make_graph_attributes_writer( std::map<std::string, std::string> { }, std::map<std::string, std::string> { {"shape", "rect"} }, std::map<std::string, std::string> { } ); boost::write_graphviz(ofs, graph, vpw, epw, gpw); } Which results in a grid.dot file like this. Next, let's layout using neato: neato -T png grid.dot -o grid.png And the result is: FULL CODE LISTING #include <boost/gil/extension/io/png_dynamic_io.hpp> #include <boost/gil/extension/io/tiff_dynamic_io.hpp> #include <boost/graph/grid_graph.hpp> #include <boost/graph/iteration_macros.hpp> #include <boost/graph/graphviz.hpp> #include <iostream> template <typename Graph, typename Weights, typename ColorFunction> void write_dot_file(Graph const& graph, Weights const& weights, ColorFunction); int main() try { using Img = boost::gil::rgb8_image_t; // gray8_image_t; using Px = Img::value_type; Img img; //boost::gil::png_read_image("/home/sehe/graph.png", img); boost::gil::png_read_and_convert_image("/home/sehe/graph.png", img); auto vw = view(img); double constexpr cell_w = 30.409; double constexpr cell_h = 30.375; auto pixel_sample = [=](boost::array<size_t, 2> xy) -> auto& { return vw((xy[0]+.5)*cell_w, (xy[1]+.5)*cell_h); }; auto const w= static_cast<size_t>(img.dimensions()[0] / cell_w); auto const h= static_cast<size_t>(img.dimensions()[1] / cell_h); using Graph = boost::grid_graph<2>; Graph graph({{w,h}}, false); #if 0 // dynamic weight map auto weights = boost::make_vector_property_map<float>(get(boost::edge_index, graph)); std::cout << "Edges: " << (weights.storage_end() - weights.storage_begin()) << "\n"; #elif 1 // fixed vector weight map std::vector<double> weight_v(num_edges(graph)); auto weights = boost::make_safe_iterator_property_map(weight_v.begin(), weight_v.size(), get(boost::edge_index, graph)); #else // associative weight map std::map<Graph::edge_descriptor, double> weight_m; auto weights = boost::make_assoc_property_map(weight_m); #endif auto debug_vertex = [] (auto& v) -> auto& { return std::cout << "{" << v[0] << "," << v[1] << "}"; }; auto debug_edge = [&](auto& e) -> auto& { debug_vertex(e.first) << " -> "; return debug_vertex(e.second); }; BGL_FORALL_EDGES(e, graph, Graph) { //debug_edge(e) << "\n"; auto& from = pixel_sample(e.first); auto& to = pixel_sample(e.second); // compare RED channels only auto cost = std::abs(from[0] - to[0]); put(weights, e, cost); } { auto calc_color = [&](size_t v) { std::ostringstream oss; oss << std::hex << std::noshowbase << std::setfill('0'); auto const& from = pixel_sample(vertex(v, graph)); oss << "#" << std::setw(2) << static_cast<int>(from[0]) << std::setw(2) << static_cast<int>(from[1]) << std::setw(2) << static_cast<int>(from[2]); return oss.str(); }; write_dot_file(graph, weights, calc_color); } { BGL_FORALL_VERTICES(v, graph, Graph) { pixel_sample(v) = Px(255, 0, 123); // mark the center pixels so we can verify the sampling } boost::gil::tiff_write_view("/tmp/verification.tif", const_view(img)); } } catch(std::exception const& e) { std::cout << "Exception occured: " << e.what() << "\n"; } template <typename Graph, typename Weights, typename ColorFunction> void write_dot_file(Graph const& graph, Weights const& weights, ColorFunction calc_color) { boost::dynamic_properties dp; dp.property("node_id", get(boost::vertex_index, graph)); dp.property("fillcolor", boost::make_transform_value_property_map(calc_color, get(boost::vertex_index, graph))); dp.property("style", boost::make_static_property_map<typename Graph::vertex_descriptor>(std::string("filled"))); std::ofstream ofs("grid.dot"); auto vpw = boost::dynamic_vertex_properties_writer { dp, "node_id" }; auto epw = boost::make_label_writer(weights); auto gpw = boost::make_graph_attributes_writer( std::map<std::string, std::string> { }, std::map<std::string, std::string> { {"shape", "rect"} }, std::map<std::string, std::string> { } ); boost::write_graphviz(ofs, graph, vpw, epw, gpw); }
Related Links
ioService.post(boost::bind(&myFunction, this, attr1, attri2) does not post the work
Propagating onPositionChanged events to background items in QtQuick 1.1
Constexpr vs macros
Why clang is not able to instantiate nested variadic template with defaulted integer_sequence?
forward declaration of classes, splitting variables and functions [duplicate]
How to use templates to avoid global variables in a multi file program in C++? [duplicate]
CMake find_library nvEncodeAPI.dll
trying to distinguish between different kinds of rvalues - literals and non-literals
C++ Error C2678 binary '<': no operator found which takes a left-hand operand of type 'const State' [duplicate]
Fade library Delaunay Triangulation site neighbors
Declare abstract class in c++
Compiler error of clang++ 8.0 — Segmentation fault — on Mac OSX Sierra 10.12.3
SIFT orientations in OpenCV implementation
How to detect thread entry/spawn point in LLVM pass?
Insert array pointer to a 2 dimension vector in C++
How to simulate user input in c++? [duplicate]