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

ASCII art for segment tree #2224

Merged
Merged
3 changes: 1 addition & 2 deletions arbor/include/arbor/morph/segment_tree.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
} // namespace arb
2 changes: 0 additions & 2 deletions arbor/morph/segment_tree.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
#include <stdexcept>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not needed anymore?

#include <map>
#include <vector>

Expand Down Expand Up @@ -246,6 +245,5 @@ ARB_ARBOR_API std::vector<msize_t> tag_roots(const segment_tree& t, int tag) {
return tag_roots;
}


} // namespace arb

3 changes: 2 additions & 1 deletion arborio/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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})

Expand Down
99 changes: 99 additions & 0 deletions arborio/debug.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#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"; });
}
}
16 changes: 16 additions & 0 deletions arborio/include/arborio/debug.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#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&);
}
69 changes: 66 additions & 3 deletions doc/cpp/morphology.rst
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

documentation under cpp?

Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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:

Expand Down Expand Up @@ -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 -->

thorstenhater marked this conversation as resolved.
Show resolved Hide resolved
.. _locsets-and-regions:

Expand Down
12 changes: 12 additions & 0 deletions doc/python/morphology.rst
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

documentation under python?

Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions python/morphology.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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);});

Expand Down Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions test/unit/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 0 additions & 3 deletions test/unit/test_asc.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
#include <iostream>
#include <fstream>

#include <arbor/cable_cell.hpp>
#include <arbor/morph/primitives.hpp>
#include <arbor/morph/segment_tree.hpp>
Expand Down
49 changes: 49 additions & 0 deletions test/unit/test_debug.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#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}));
}