Skip to content
Snippets Groups Projects
Unverified Commit 2c08a566 authored by Thorsten Hater's avatar Thorsten Hater Committed by GitHub
Browse files

ASCII art for segment tree (#2224)

Allow ASCII rendering of segment trees and morphologies for debugging
Example `segment_tree`
```
                      |              |                +-[-- id=1161 --]-+-[-- id=1709 --]---[-- id=2234 --]-+-[-- id=2625 --]---[-- id=3172 --]
                      |              |                |                 |                                   +-[-- id=2928 --]---[-- id=3765 --]
                      |              |                |                 |
                      |              |                |                 +-[-- id=1721 --]---[-- id=3504 --]---[-- id=3846 --]
                      |              |                |
                      |              |                +-[-- id=1867 --]
                      |              |
                      |              +-[-- id=1451 --]-+-[-- id=2356 --]
                      |                                +-[-- id=2471 --]---[-- id=2670 --]---[-- id=4031 --]
                      |
             ...
parent 1050ff45
No related branches found
No related tags found
No related merge requests found
......@@ -88,5 +88,4 @@ apply(const segment_tree&, const isometry&);
// Roots of regions of specific tag in segment tree
ARB_ARBOR_API std::vector<msize_t> tag_roots(const segment_tree& in, int tag);
} // namespace arb
\ No newline at end of file
} // namespace arb
#include <stdexcept>
#include <map>
#include <vector>
......@@ -246,6 +245,5 @@ ARB_ARBOR_API std::vector<msize_t> tag_roots(const segment_tree& t, int tag) {
return tag_roots;
}
} // namespace arb
......@@ -7,7 +7,8 @@ set(arborio-sources
label_parse.cpp
neuroml.cpp
networkio.cpp
nml_parse_morphology.cpp)
nml_parse_morphology.cpp
debug.cpp)
add_library(arborio ${arborio-sources})
......
#include <arborio/debug.hpp>
#include <arbor/morph/primitives.hpp>
#include <map>
#include <numeric>
namespace arborio {
template <typename T, typename P>
std::vector<std::string> render(const T& tree,
arb::msize_t root,
const std::multimap<arb::msize_t, arb::msize_t>& children,
P print) {
// ASCII art elements
// TODO these could be customizable, but need conformant lengths
const std::string vline = " | ";
const std::string hline = "---";
const std::string blank = " ";
const std::string split = "-+-";
const std::string start = " +-";
auto n_child = children.count(root);
auto seg = print(root, tree);
if (0 == n_child) return {seg};
auto sep = std::string(seg.size(), ' ');
const auto& [beg, end] = children.equal_range(root);
std::vector res = {seg};
arb::msize_t cdx = 0;
for (auto it = beg; it != end; ++it) {
const auto& [parent, child] = *it;
auto rows = render(tree, child, children, print);
auto rdx = 0;
for (const auto& row: rows) {
// Append the first row directly onto our segments, this [- -] -- [- -]
if (rdx == 0) {
// The first child of a node may span a sub-tree
if (cdx == 0) {
res.back() += split + row;
} else {
// Other children get connected to the vertical line
res.push_back(sep + start + row);
}
cdx++;
} else {
// If there are more children, extend the subtree by showing a
// vertical line
res.push_back(sep + (cdx < n_child ? vline : blank) + row);
}
++rdx;
}
}
// res.push_back(sep);
return res;
}
ARB_ARBORIO_API std::string default_segment_printer(const arb::msize_t id, const arb::segment_tree&) {
auto lbl = (id == arb::mnpos) ? "(root)" : std::to_string(id);
return "[-- id=" + lbl + " --]" ;
}
std::string ARB_ARBORIO_API default_branch_printer(const arb::msize_t id, const arb::morphology& mrf) {
auto lbl = (id == arb::mnpos) ? std::string("(root)") : std::to_string(id);
return "<-- id=" + std::to_string(id) + " len=" + std::to_string(mrf.branch_segments(id).size()) + " -->" ;
}
ARB_ARBORIO_API std::string show(const arb::segment_tree& tree) {
if (tree.empty()) return "";
std::multimap<arb::msize_t, arb::msize_t> children;
const auto& ps = tree.parents();
for (arb::msize_t idx = 0; idx < tree.size(); ++idx) {
auto parent = ps[idx];
children.emplace(parent, idx);
}
auto res = render(tree, 0, children, default_segment_printer);
return std::accumulate(res.begin(), res.end(),
std::string{},
[](auto lhs, auto rhs) { return lhs + rhs + "\n"; });
}
ARB_ARBORIO_API std::string show(const arb::morphology& mrf) {
if (mrf.empty()) return "";
std::multimap<arb::msize_t, arb::msize_t> children;
for (arb::msize_t idx = 0; idx < mrf.num_branches(); ++idx) {
auto parent = mrf.branch_parent(idx);
children.emplace(parent, idx);
}
auto res = render(mrf, 0, children, default_branch_printer);
return std::accumulate(res.begin(), res.end(),
std::string{},
[](auto lhs, auto rhs) { return lhs + rhs + "\n"; });
}
}
#pragma once
#include <string>
#include <functional>
#include <vector>
#include <arbor/export.hpp>
#include <arborio/export.hpp>
#include <arbor/morph/segment_tree.hpp>
#include <arbor/morph/morphology.hpp>
namespace arborio {
ARB_ARBORIO_API std::string show(const arb::segment_tree&);
ARB_ARBORIO_API std::string show(const arb::morphology&);
}
......@@ -31,7 +31,6 @@ consistent parent-child indexing, and with ``n`` segments numbered from ``0`` to
.. cpp:class:: segment_tree
.. cpp:function:: segment_tree()
Construct an empty segment tree.
......@@ -66,6 +65,10 @@ consistent parent-child indexing, and with ``n`` segments numbered from ``0`` to
A list of the segments.
.. cpp:function:: std::string show(const arb::segment_tree&)
Return a string representation of the tree.
.. cpp:function:: std::pair<segment_tree, segment_tree> split_at(const segment_tree& t, msize_t id)
Split a segment_tree into a pair of subtrees at the given id,
......@@ -100,9 +103,47 @@ consistent parent-child indexing, and with ``n`` segments numbered from ``0`` to
Morphology API
--------------
.. todo::
.. cpp:class:: morphology
.. cpp:function:: morphology()
Construct an empty morphology.
.. cpp:function:: morphology(const segment_tree&)
Construct a morphology from a segment tree.
.. cpp:function:: segment_tree to_segment_tree() const
Reconcstruct the underlying segment tree.
.. cpp:function:: bool empty() const
Is this the trivial morphology?
.. cpp:function:: msize_t num_branches() const
The number of branches in the morphology.
.. cpp:function:: msize_t branch_parent(msize_t b) const
The parent branch of branch ``b``. Return ``mnpos`` if branch has no parent.
Describe morphology methods.
.. cpp:function:: const std::vector<msize_t>& branch_children(msize_t b) const
The child branches of branch ``b``. If b is ``mnpos``, return root branches.
.. cpp:function:: const std::vector<msize_t>& terminal_branches() const
Branches with no children.
.. cpp:function:: const std::vector<msegment>& branch_segments(msize_t b) const
Range of segments in a branch.
.. cpp:function:: std::string show(const arb::morphology&)
Return a string representation of the tree underlying the morphology.
.. _cppcablecell-morphology-construction:
......@@ -200,6 +241,28 @@ by two stitches:
cable_cell cell(stitched.morphology(), dec, stitched.labels());
Debug Ouput
-----------
Tree representations of :cpp:type:`segment_tree` and :cpp:type:`morphology` can
be obtained by including ``arborio/debug.hpp`` which contains a series of
:cpp:func:`show` functions that return ASCII renderings of the given object.
Example for an arbitrary segment tree
.. code::
[-- id=0 --]-+-[-- id=1 --]
+-[-- id=2 --]-+-[-- id=3 --]
+-[-- id=4 --]
and for the equivalent morphology
.. code::
<-- id=0 len=1 -->-+-<-- id=1 len=1 -->
+-<-- id=2 len=1 -->-+-<-- id=3 len=1 -->
+-<-- id=4 len=1 -->
.. _locsets-and-regions:
......
......@@ -299,6 +299,12 @@ Cable cell morphology
A list of the segments.
.. method:: show
Return a string containing an ASCII rendering of the tree.
:return: string
.. py:class:: morphology
A *morphology* describes the geometry of a cell as unbranched cables
......@@ -352,6 +358,12 @@ Cable cell morphology
:param int i: branch index
:rtype: list[msegment]
.. method:: show
Return a string containing an ASCII rendering of the morphology.
:return: string
.. py:class:: place_pwlin
A :class:`place_pwlin` object allows the querying of the 3-d location of locations and cables
......
......@@ -18,6 +18,7 @@
#include <arborio/swcio.hpp>
#include <arborio/neurolucida.hpp>
#include <arborio/neuroml.hpp>
#include <arborio/debug.hpp>
#include "util.hpp"
#include "error.hpp"
......@@ -292,6 +293,9 @@ void register_morphology(py::module& m) {
.def("tag_roots",
[](const arb::segment_tree& t, int tag) { return arb::tag_roots(t, tag); },
"Get roots of tag region of this segment tree.")
.def("show",
[] (const arb::segment_tree& t) { return arborio::show(t); },
"Return an ASCII representation of this segment tree.")
.def("__str__", [](const arb::segment_tree& s) {
return util::pprintf("<arbor.segment_tree:\n{}>", s);});
......@@ -321,6 +325,9 @@ void register_morphology(py::module& m) {
"i"_a, "A list of the segments in branch i, ordered from proximal to distal ends of the branch.")
.def("to_segment_tree", &arb::morphology::to_segment_tree,
"Convert this morphology to a segment_tree.")
.def("show",
[] (const arb::morphology& t) { return arborio::show(t); },
"Return an ASCII representation.")
.def("__str__",
[](const arb::morphology& m) {
return util::pprintf("<arbor.morphology:\n{}>", m);
......
......@@ -149,6 +149,7 @@ set(unit_sources
test_vector.cpp
test_version.cpp
test_v_clamp.cpp
test_debug.cpp
# unit test driver
test.cpp
......
#include <iostream>
#include <fstream>
#include <arbor/cable_cell.hpp>
#include <arbor/morph/primitives.hpp>
#include <arbor/morph/segment_tree.hpp>
......
#include <arbor/morph/morphology.hpp>
#include <arbor/morph/segment_tree.hpp>
#include <arborio/debug.hpp>
#include <gtest/gtest.h>
TEST(debug_io, single) {
arb::segment_tree tree;
arb::msize_t par = arb::mnpos;
tree.append(par, {0, 0, 0, 5}, {0, 0, 10, 5}, 42);
EXPECT_EQ("[-- id=0 --]\n", arborio::show(tree));
EXPECT_EQ("<-- id=0 len=1 -->\n", arborio::show(arb::morphology{tree}));
}
TEST(debug_io, fork) {
arb::segment_tree tree;
arb::msize_t par = arb::mnpos;
par = tree.append(par, {0, 0, 0, 5}, {0, 0, 10, 5}, 42);
tree.append(par, {0, 0, 10, 5}, {0, 1, 10, 5}, 23);
tree.append(par, {0, 0, 10, 5}, {0, -1, 10, 5}, 23);
EXPECT_EQ("[-- id=0 --]-+-[-- id=1 --]\n"
" +-[-- id=2 --]\n",
arborio::show(tree));
EXPECT_EQ("<-- id=0 len=1 -->-+-<-- id=1 len=1 -->\n"
" +-<-- id=2 len=1 -->\n",
arborio::show(arb::morphology{tree}));
}
TEST(debug_io, complex) {
arb::segment_tree tree;
arb::msize_t lvl0 = arb::mnpos;
lvl0 = tree.append(lvl0, {0, 0, 0, 5}, {0, 0, 10, 5}, 42);
tree.append(lvl0, {0, 0, 10, 5}, {0, 1, 10, 5}, 23);
auto lvl1 = tree.append(lvl0, {0, 0, 10, 5}, {0, -1, 10, 5}, 23);
tree.append(lvl1, {0, -1, 10, 5}, { 1, -1, 10, 5}, 23);
tree.append(lvl1, {0, -1, 10, 5}, {-1, -1, 10, 5}, 23);
EXPECT_EQ("[-- id=0 --]-+-[-- id=1 --]\n"
" +-[-- id=2 --]-+-[-- id=3 --]\n"
" +-[-- id=4 --]\n",
arborio::show(tree));
EXPECT_EQ("<-- id=0 len=1 -->-+-<-- id=1 len=1 -->\n"
" +-<-- id=2 len=1 -->-+-<-- id=3 len=1 -->\n"
" +-<-- id=4 len=1 -->\n",
arborio::show(arb::morphology{tree}));
}
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