Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to use a preexisting graph in graph_4_adjacency_2_khalimsky #262

Merged
merged 1 commit into from
Mar 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions higra/image/graph_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
6 changes: 5 additions & 1 deletion higra/image/py_graph_image.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<value_t> &khalimsky,
const hg::ugraph &graph,
const std::vector<size_t> &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);
}
};
Expand Down
47 changes: 38 additions & 9 deletions include/higra/image/graph_image.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<typename T, typename result_type = typename T::value_type>
auto
khalimsky_2_graph_4_adjacency(const xt::xexpression<T> &xkhalimsky, bool extra_border = false) {
khalimsky_2_graph_4_adjacency(const xt::xexpression<T> &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!");
Expand All @@ -162,24 +165,50 @@ namespace hg {

index_t border = (extra_border) ? 0 : 1;

std::array<index_t, 2> res_shape{(index_t) shape[0] / 2 + border, (index_t) shape[1] / 2 + border};
embedding_grid_2d res_embedding(res_shape);
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 <result_type> weights = xt::zeros<result_type>({num_edges(g)});

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

return std::make_tuple(std::move(g), std::move(res_embedding), std::move(weights));
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<typename T, typename result_type = typename T::value_type>
auto
khalimsky_2_graph_4_adjacency(const xt::xexpression<T> &xkhalimsky, bool extra_border = false) {
HG_TRACE();
const auto &khalimsky = xkhalimsky.derived_cast();
hg_assert(khalimsky.dimension() == 2, "Only 2d khalimsky grids are supported!");

auto &shape = khalimsky.shape();

index_t border = (extra_border) ? 0 : 1;

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

auto g = get_4_adjacency_graph(res_embedding);
array_1d <result_type> weights = 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));
};
}
2 changes: 1 addition & 1 deletion test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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 ()
5 changes: 4 additions & 1 deletion test/cpp/image/test_graph_image.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
}
3 changes: 3 additions & 0 deletions test/python/test_image/test_graph_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down