From 80c6faf1c6fa09c80425eb16045842065473ea0e Mon Sep 17 00:00:00 2001 From: Benjamin Perret Date: Thu, 30 Mar 2023 15:00:50 +0200 Subject: [PATCH] Add option to use a preexisting graph in graph_4_adjacency_2_khalimsky --- higra/image/graph_image.py | 22 ++++++++-- higra/image/py_graph_image.cpp | 6 ++- include/higra/image/graph_image.hpp | 47 +++++++++++++++++----- test/CMakeLists.txt | 2 +- test/cpp/image/test_graph_image.cpp | 5 ++- test/python/test_image/test_graph_image.py | 3 ++ 6 files changed, 69 insertions(+), 16 deletions(-) diff --git a/higra/image/graph_image.py b/higra/image/graph_image.py index 68f72a4b..26b6b0b0 100644 --- a/higra/image/graph_image.py +++ b/higra/image/graph_image.py @@ -27,19 +27,33 @@ def graph_4_adjacency_2_khalimsky(graph, edge_weights, shape, add_extra_border=F return hg.cpp._graph_4_adjacency_2_khalimsky(graph, shape, edge_weights, add_extra_border) -def khalimsky_2_graph_4_adjacency(khalimsky, extra_border=False): +def khalimsky_2_graph_4_adjacency(khalimsky, extra_border=False, graph=None): """ Create a 4 adjacency edge-weighted graph from a contour image in the Khalimsky grid. :param khalimsky: a 2d array :param extra_border: if False the shape of the Khalimsky image is 2 * shape - 1 and 2 * shape + 1 otherwise, where shape is the shape of the resulting grid graph + :param graph: a 4-adjacency graph (Concept :class:`~higra.CptGridGraph`) of the correct shape (optional). If not given, a new graph is created. :return: a graph (Concept :class:`~higra.CptGridGraph`) and its edge weights """ - graph, embedding, edge_weights = hg.cpp._khalimsky_2_graph_4_adjacency(khalimsky, extra_border) + border = 0 if extra_border else 1 + res_shape = khalimsky.shape[0] // 2 + border, khalimsky.shape[1] // 2 + border + + if graph is not None: + graph_shape = hg.CptGridGraph.get_shape(graph) + if graph_shape is None: + raise ValueError("The given graph must be a grid graph.") + if len(graph_shape) != 2: + raise ValueError("The given graph must be a 2d grid graph.") + if hg.get_attribute(graph, "no_border_vertex_out_degree") != 4: + raise ValueError("The given graph must be a 4 adjacency graph.") + if graph_shape != res_shape: + raise ValueError("The given graph shape is " + str(graph_shape) + " but the expected shape is " + str(res_shape) + ".") + else: + graph = hg.get_4_adjacency_graph(res_shape) - hg.CptGridGraph.link(graph, hg.normalize_shape(embedding.shape())) - hg.set_attribute(graph, "no_border_vertex_out_degree", 4) + edge_weights = hg.cpp._khalimsky_2_graph_4_adjacency(khalimsky, graph, res_shape, extra_border) return graph, edge_weights diff --git a/higra/image/py_graph_image.cpp b/higra/image/py_graph_image.cpp index 088bd45d..647e864a 100644 --- a/higra/image/py_graph_image.cpp +++ b/higra/image/py_graph_image.cpp @@ -26,11 +26,15 @@ struct def_kalhimsky_2_contour { static void def(pybind11::module &m, const char *doc) { m.def("_khalimsky_2_graph_4_adjacency", [](const pyarray &khalimsky, + const hg::ugraph &graph, + const std::vector &shape, bool extra_border) { - return hg::khalimsky_2_graph_4_adjacency(khalimsky, extra_border); + return hg::khalimsky_2_graph_4_adjacency(khalimsky, graph, hg::embedding_grid_2d(shape), extra_border); }, doc, py::arg("khalimsky"), + py::arg("graph"), + py::arg("embedding"), py::arg("extra_border") = false); } }; diff --git a/include/higra/image/graph_image.hpp b/include/higra/image/graph_image.hpp index 30910ea2..63425f9c 100644 --- a/include/higra/image/graph_image.hpp +++ b/include/higra/image/graph_image.hpp @@ -148,12 +148,15 @@ namespace hg { /** * Transforms a contour map represented in 2d Khalimsky space into a weighted 4 adjacency edge weighted regular graph * (0-face and 2-face of the Khalimsky space are ignored). - * @param embedding - * @return + * @param xkhalimsky 2d array representing the khalimsky space + * @param g 4 adjacency graph, with the same number of vertices as the embedding + * @param embedding embedding of the graph, dimension is half the size of the khalimsky space + * @param extra_border if true, the khalimsky space will have an extra border of 0-faces and 1-faces + * @return 2d array representing the weights of the edges in the khalimsky space */ template auto - khalimsky_2_graph_4_adjacency(const xt::xexpression &xkhalimsky, bool extra_border = false) { + khalimsky_2_graph_4_adjacency(const xt::xexpression &xkhalimsky, const ugraph & g, const embedding_grid_2d& embedding, bool extra_border = false) { HG_TRACE(); const auto &khalimsky = xkhalimsky.derived_cast(); hg_assert(khalimsky.dimension() == 2, "Only 2d khalimsky grids are supported!"); @@ -162,16 +165,16 @@ namespace hg { index_t border = (extra_border) ? 0 : 1; - std::array res_shape{(index_t) shape[0] / 2 + border, (index_t) shape[1] / 2 + border}; - embedding_grid_2d res_embedding(res_shape); + hg_assert(num_vertices(g) == embedding.size(), "Graph size does not match."); + hg_assert(embedding.shape()[0] == (index_t) shape[0] / 2 + border, "Embedding shape[0] does not match."); + hg_assert(embedding.shape()[1] == (index_t) shape[1] / 2 + border, "Embedding shape[1] does not match."); - auto g = get_4_adjacency_graph(res_embedding); array_1d weights = xt::zeros({num_edges(g)}); point_2d_i one{{1, 1}}; for (auto e : edge_iterator(g)) { - auto s = res_embedding.lin2grid(source(e, g)); - auto t = res_embedding.lin2grid(target(e, g)); + auto s = embedding.lin2grid(source(e, g)); + auto t = embedding.lin2grid(target(e, g)); if (extra_border) { weights(e) = khalimsky[s + t + one]; } else { @@ -179,7 +182,33 @@ namespace hg { } } - return std::make_tuple(std::move(g), std::move(res_embedding), std::move(weights)); + return weights; }; + /** + * Transforms a contour map represented in 2d Khalimsky space into a weighted 4 adjacency edge weighted regular graph + * (0-face and 2-face of the Khalimsky space are ignored). + * @param xkhalimsky 2d array representing the khalimsky space + * @param extra_border if true, the khalimsky space will have an extra border of 0-faces and 1-faces + * @return A tuple composed of the 4 adjacency graph, the embedding of the graph, and the weights of the edges in the khalimsky space + */ + template + auto + khalimsky_2_graph_4_adjacency(const xt::xexpression &xkhalimsky, bool extra_border = false) { + HG_TRACE(); + const auto &khalimsky = xkhalimsky.derived_cast(); + hg_assert(khalimsky.dimension() == 2, "Only 2d khalimsky grids are supported!"); + + auto &shape = khalimsky.shape(); + + index_t border = (extra_border) ? 0 : 1; + + std::array res_shape{(index_t) shape[0] / 2 + border, (index_t) shape[1] / 2 + border}; + embedding_grid_2d res_embedding(res_shape); + + auto g = get_4_adjacency_graph(res_embedding); + array_1d weights = khalimsky_2_graph_4_adjacency(khalimsky, g, res_embedding, extra_border); + + return std::make_tuple(std::move(g), std::move(res_embedding), std::move(weights)); + }; } \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 4db46a56..3c96e4bf 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -27,7 +27,7 @@ add_custom_target(all_tests ALL if (DO_AUTO_TEST) add_custom_command(TARGET all_tests COMMENT "Run tests" - POST_BUILD COMMAND ctest + POST_BUILD COMMAND ctest -V WORKING_DIRECTORY ${CMAKE_BINARY_DIR} ) endif () diff --git a/test/cpp/image/test_graph_image.cpp b/test/cpp/image/test_graph_image.cpp index 5f3359e1..7b50e226 100644 --- a/test/cpp/image/test_graph_image.cpp +++ b/test/cpp/image/test_graph_image.cpp @@ -175,10 +175,13 @@ namespace graph_image { {0, 0, 0, 0, 0, 0, 2, 0, 3, 0, 0} }; auto r2 = khalimsky_2_graph_4_adjacency(ref2, true); - //auto & graph2 = std::get<0>(r2); + auto &graph2 = std::get<0>(r2); auto &embedding2 = std::get<1>(r2); auto &weights2 = std::get<2>(r2); REQUIRE(xt::allclose(embedding2.shape(), ref_shape)); REQUIRE(xt::allclose(data, weights2)); + + auto weights3 = khalimsky_2_graph_4_adjacency(ref2, graph2, embedding2, true); + REQUIRE(xt::allclose(data, weights3)); } } \ No newline at end of file diff --git a/test/python/test_image/test_graph_image.py b/test/python/test_image/test_graph_image.py index 7850bcfd..6cc40d4b 100644 --- a/test/python/test_image/test_graph_image.py +++ b/test/python/test_image/test_graph_image.py @@ -36,6 +36,9 @@ def test_khalimsky_2_graph_4_adjacency(self): self.assertTrue(np.allclose(shape, (2, 3))) self.assertTrue(np.allclose(data, weights)) + _, weights2 = hg.khalimsky_2_graph_4_adjacency(ref, graph=graph) + self.assertTrue(np.allclose(data, weights2)) + def test_get_4_adjacency_graph(self): shape = (2, 3) graph = hg.get_4_adjacency_graph(shape)