Commit 1d332ac2 authored by Quentin Kaci's avatar Quentin Kaci
Browse files

Add some documentation on watershed hierarchy

parent bb4e0f36
Pipeline #28511 passed with stages
in 11 minutes and 20 seconds
......@@ -36,6 +36,7 @@ Segmentation
:maxdepth: 1
morpho/watershed
morpho/watershed_hierarchy
Component Trees & Hierarchical Representations
......
Hierarchical Watershed
==========
* Include :file:`<mln/morpho/watershed_hierarchy.hpp>`
.. cpp:namespace:: mln::morpho
.. cpp:function:: auto watershed_hierarchy(Image f, Accumulator acc, Neighborhood nbh, F dist);
Compute the watershed hierarchy and returns a pair `(tree, node_map)`.
See :doc:`component_tree` for more information about the representation of tree.
The implementation is based on [Naj13]_.
:param input: The input image
:param acc: The accumulator that define the attribute computation
:param nbh: The neighborhood relation
:param dist: The function weighting the edges between two pixels.
:return: A pair `(tree, node_map)` where *tree* is of type ``component_tree<std::invoke_result_t<F, image_value_t<Image>, image_value_t<Image>>>`` and *node_map* is a mapping between the image pixels and the node of the tree.
.. rubric:: Requirements
* ``std::invoke_result_t<F, image_value_t<Image>, image_value_t<Image>>`` is :cpp:concept:`std::totally_ordered`
Definition
----------
Watershed hierarchies, also called attribute-based hierarchies, are constructed by filtering and re-weighting the
Minimum Spanning Tree based on an attribute computation (area, volume, dynamic, etc.) on the Binary Partition Tree. This
process can be imagined as the filling of branches of the Binary Partition Tree with labeled water where the attribute
drives the speed of the filling.
The tree that represent a watershed hierarchy is an Alpha tree, also known as quasi-flat zone hierarchy.
The Alpha tree is the tree of :math:`\alpha`-connected components. An :math:`\alpha`-connected component in an image :math:`f`, for a pixel :math:`p`, is defined as
.. math::
\alpha-CC(p) = \{p\}\ \cup\ \{q\ |\ \text{there exists a path}\ \Pi = \{\pi_0, ..., \pi_n\}\ \text{between}\ p\ \text{and}\ q\ \text{such that}\ d(f(\pi_i), f(\pi_{i+1})) \leq \alpha\}
where :math:`d` is a dissimilarity function between two pixels of the image :math:`f`.
The :math:`\alpha`-connected components form an ordered sequence when :math:`\alpha` is growing, such that for :math:`\alpha_i < \alpha_j`,
:math:`\alpha_i-CC(p) \subseteq \alpha_j-CC(p)`. Thus, the alphatree is the hierarchy where the parenthood relationship represents the inclusion of the
:math:`\alpha`-connected components.
Representation
--------------
.. image:: /figures/morpho/alphatree_repr.svg
:align: center
:width: 30%
The ``watershed_hierarchy`` function returns a tree and a node map. The tree has two attributes:
* ``parent``: The parenthood relationship of the node :math:`i`, representing the inclusion relationship of the :math:`\alpha`-connected components.
* ``values``: The value of :math:`\alpha` assigned to a node :math:`i`.
Then, the node map is the relation between a pixel of the image and its related node in the tree, a leaf for the case of the watershed hierarchy.
The image above illustrates the representation of the watershed hierarchy in Pylene, the parenthood relationship being illustrated in arrows, the values of alpha, assigned to each node, being in red, and the relation
between a node of the tree and a pixel of the image being represented by blue dashed lines.
Example
-------
This example is used to generate the grayscale lena watershed hierarchy by area with a cut at a threshold of 25 below.
::
#include <mln/accu/accumulators/count.hpp>
#include <mln/accu/accumulators/mean.hpp>
#include <mln/core/image/ndimage.hpp>
#include <mln/core/neighborhood/c4.hpp>
#include <mln/morpho/watershed_hierarchy.hpp>
mln::image2d<uint8_t> input = ...;
// Compute the watershed hierarchy by area
auto area_acc = mln::accu::features::count<>();
auto [tree, node_map] = mln::morpho::watershed_hierarchy(input, area_acc, mln::c4);
// Compute an attribute (for example the average pixels value at each node, as below)
auto mean = tree.compute_attribute_on_values(node_map, input, mln::accu::accumulators::mean<uint8_t>());
// Making an horizontal cut of the tree
const auto threshold = 25; // Threshold of the horizontal cut, that means the lowest alpha in the cut
auto node_map_cut = tree.horizontal_cut(threshold, node_map); // Return a new node map associated to the cut
// Labeling the cut with the mean values of each node
auto out = tree.reconstruct_from(node_map_cut, ranges::make_span(mean)); // Using range-v3 span
.. list-table::
* - .. figure:: /images/watershed_hierarchy_area_color.png
Watershed hierarchy by area with a cut at a threshold of 100
- .. figure:: /images/watershed_hierarchy_area_gray.png
Watershed hierarchy by area with a cut at a threshold of 25
Notes
-----
Complexity
----------
References
----------
.. [Naj13] Laurent Najman, Jean Cousty, and Benjamin Perret (2013). Playing with kruskal: algorithms for morphological trees in edge-weighted graphs. *International Symposium on Mathematical Morphology and Its Applications to Signal and Image Processing*. Springer, Berlin, Heidelberg. 135-146
\ No newline at end of file
......@@ -6,20 +6,20 @@ set(PYLENE_IMAGE_DIR ${PROJECT_SOURCE_DIR}/img)
set(DOCUMENTATION_IMAGES "")
function(add_image EXECUTABLE INPUT)
# add_image(EXECUTABLE INPUT OUTPUT1 [ [OUTPUT2 [OUTPUT3 ...] ] ]
set(outputs "")
foreach (output IN LISTS ARGN)
set(output "${DOCUMENTATION_IMAGE_DIR}/${output}")
list(APPEND outputs "${output}")
list(APPEND DOCUMENTATION_IMAGES "${output}")
endforeach ()
set(DOCUMENTATION_IMAGES ${DOCUMENTATION_IMAGES} PARENT_SCOPE)
add_custom_command(OUTPUT ${outputs}
COMMAND ${EXECUTABLE} ${INPUT} ${outputs}
COMMAND_EXPAND_LISTS
)
# add_image(EXECUTABLE INPUT OUTPUT1 [ [OUTPUT2 [OUTPUT3 ...] ] ]
set(outputs "")
foreach (output IN LISTS ARGN)
set(output "${DOCUMENTATION_IMAGE_DIR}/${output}")
list(APPEND outputs "${output}")
list(APPEND DOCUMENTATION_IMAGES "${output}")
endforeach ()
set(DOCUMENTATION_IMAGES ${DOCUMENTATION_IMAGES} PARENT_SCOPE)
add_custom_command(OUTPUT ${outputs}
COMMAND ${EXECUTABLE} ${INPUT} ${outputs}
COMMAND_EXPAND_LISTS
)
endfunction()
add_image("intro-1" "${DOCUMENTATION_IMAGE_DIR}/Olena-c6gradi.png" intro-1-1.png)
......@@ -40,19 +40,18 @@ add_image("cdt;4" "${DOCUMENTATION_IMAGE_DIR}/F.png" F-5-7-11.png)
add_image("staff_lines" "${DOCUMENTATION_IMAGE_DIR}/staff_lines.pbm"
morpho_hitormiss_1.png morpho_hitormiss_2.png staff_lines_markers.png morpho_reconstruction_1.png morpho_reconstruction_2.png)
morpho_hitormiss_1.png morpho_hitormiss_2.png staff_lines_markers.png morpho_reconstruction_1.png morpho_reconstruction_2.png)
add_image("reconstruction"
"${DOCUMENTATION_IMAGE_DIR}/blobs2_binary.png"
morpho_reconstruction_dilated.png
morpho_reconstruction_markers.png
morpho_reconstruction_rec.png
morpho_reconstruction_out.png)
"${DOCUMENTATION_IMAGE_DIR}/blobs2_binary.png"
morpho_reconstruction_dilated.png
morpho_reconstruction_markers.png
morpho_reconstruction_rec.png
morpho_reconstruction_out.png)
add_image("area_filter"
"${DOCUMENTATION_IMAGE_DIR}/blobs2_binary.png"
morpho_area_filter_dilated.png
morpho_area_filter_opening.png
morpho_area_filter_out.png)
"${DOCUMENTATION_IMAGE_DIR}/blobs2_binary.png"
morpho_area_filter_dilated.png
morpho_area_filter_opening.png
morpho_area_filter_out.png)
add_image("blobs_watershed" "${DOCUMENTATION_IMAGE_DIR}/blobs_binary.png" blobs_distance_transform.png blobs_segmentation.png)
......@@ -60,19 +59,21 @@ add_image("blobs_watershed" "${DOCUMENTATION_IMAGE_DIR}/blobs_binary.png" blobs_
add_image("alphatree_example" "${PYLENE_IMAGE_DIR}/lena.ppm" alphatree_cut_color.png)
add_image("alphatree_example" "${PYLENE_IMAGE_DIR}/lena.pgm" alphatree_cut_gray.png)
add_image("watershed_hierarchy_example" "${PYLENE_IMAGE_DIR}/lena.ppm" watershed_hierarchy_area_color.png)
add_image("watershed_hierarchy_example" "${PYLENE_IMAGE_DIR}/lena.pgm" watershed_hierarchy_area_gray.png)
add_custom_target(build-images
DEPENDS "${DOCUMENTATION_IMAGES}")
DEPENDS "${DOCUMENTATION_IMAGES}")
add_library(doc-lib lut.cpp)
target_link_libraries(doc-lib Pylene::Pylene)
link_libraries(Pylene::Pylene)
link_libraries(doc-lib)
add_executable(alphatree_example alphatree_example.cpp)
add_executable(watershed_hierarchy_example watershed_hierarchy_example.cpp)
add_executable(erosion-cli erosion-cli.cpp)
add_executable(staff_lines staff_lines.cpp)
add_executable(component_tree_1 component_tree_1.cpp)
......@@ -87,7 +88,7 @@ target_compile_definitions(erosion-cli PRIVATE BOOST_ALL_NO_LIB)
# for program_options, need to separate CONAN and regular FindBoost
if (TARGET Boost::program_options)
target_link_libraries(erosion-cli PRIVATE Boost::program_options)
target_link_libraries(erosion-cli PRIVATE Boost::program_options)
elseif (TARGET Boost::Boost)
target_link_libraries(erosion-cli PRIVATE Boost::Boost)
endif()
\ No newline at end of file
target_link_libraries(erosion-cli PRIVATE Boost::Boost)
endif ()
\ No newline at end of file
#include <mln/accu/accumulators/mean.hpp>
#include <mln/core/colors.hpp>
#include <mln/core/image/ndimage.hpp>
#include <mln/core/image/view/cast.hpp>
#include <mln/core/neighborhood/c4.hpp>
#include <mln/io/imread.hpp>
#include <mln/io/imsave.hpp>
#include <mln/morpho/watershed_hierarchy.hpp>
#include <iostream>
template <typename V>
void process_example(const mln::image2d<V>& img, const std::string& output_filename, const double threshold)
{
// 2. Build the watershed hierarchy
auto [t, nm] = mln::morpho::watershed_hierarchy(img, mln::accu::features::count<>(), mln::c4);
// 3. Compute the mean attribute
auto mean = t.compute_attribute_on_values(nm, img, mln::accu::accumulators::mean<V>());
// 4. Compute a cut of the watershed hierarchy
auto cut_nm = t.horizontal_cut(threshold, nm);
// 5. Label the cut
auto cut = t.reconstruct_from(cut_nm, ::ranges::make_span(mean));
// 5. Save the output
mln::io::imsave(mln::view::cast<V>(cut), output_filename);
}
int main(int argc, char* argv[])
{
if (argc < 3)
{
std::cerr << "Invalid number of argument\nUsage: " << argv[0] << " input_filename output_filename\n";
return 1;
}
auto in_filename = std::string(argv[1]);
auto out_filename = std::string(argv[2]);
// 1. Read the input image
mln::ndbuffer_image in;
in = mln::io::imread(in_filename);
if (in.sample_type() == mln::sample_type_id::UINT8)
{
const auto* img = in.cast_to<std::uint8_t, 2>();
process_example(*img, out_filename, 25);
}
else if (in.sample_type() == mln::sample_type_id::RGB8)
{
const auto* img = in.cast_to<mln::rgb8, 2>();
process_example(*img, out_filename, 100);
}
else
{
std::cerr << "Unhandled sample type format\n";
return 1;
}
return 0;
}
\ No newline at end of file
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment