diff --git a/arbor/include/arbor/morph/segment_tree.hpp b/arbor/include/arbor/morph/segment_tree.hpp
index 0d9c6b564a0827313ca9a5c6eaad44b558727ea3..814d7d2cb0c797239f0698ae1003963c85ebc0dc 100644
--- a/arbor/include/arbor/morph/segment_tree.hpp
+++ b/arbor/include/arbor/morph/segment_tree.hpp
@@ -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
diff --git a/arbor/morph/segment_tree.cpp b/arbor/morph/segment_tree.cpp
index 92eb13d6fbd9516355cbb1f7429be3455deb985f..551e213ed142ceec97367c1905d9485abf0c6617 100644
--- a/arbor/morph/segment_tree.cpp
+++ b/arbor/morph/segment_tree.cpp
@@ -1,4 +1,3 @@
-#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
 
diff --git a/arborio/CMakeLists.txt b/arborio/CMakeLists.txt
index 5a4bae4254405b0808c3dd9173a9369423d18cca..6016a3417d9fb5c925d15ae52e6190d9c9383d6a 100644
--- a/arborio/CMakeLists.txt
+++ b/arborio/CMakeLists.txt
@@ -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})
 
diff --git a/arborio/debug.cpp b/arborio/debug.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..17becc18ca5bf8593c663163b1b7f81dee32e580
--- /dev/null
+++ b/arborio/debug.cpp
@@ -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"; });
+}
+}
diff --git a/arborio/include/arborio/debug.hpp b/arborio/include/arborio/debug.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..84faf5299dc280f54174af1cf2324e896784f657
--- /dev/null
+++ b/arborio/include/arborio/debug.hpp
@@ -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&);
+}
diff --git a/doc/cpp/morphology.rst b/doc/cpp/morphology.rst
index 3bf52f7f7417474cf8edb2f0909e869c037d157d..0de4df537a3c327b463aadc4014efd30ceea7cee 100644
--- a/doc/cpp/morphology.rst
+++ b/doc/cpp/morphology.rst
@@ -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:
 
diff --git a/doc/python/morphology.rst b/doc/python/morphology.rst
index b649a3c58095f3034880fa65303e15206e045a81..691c4042b26d946f8fa7526f323e3995424deea5 100644
--- a/doc/python/morphology.rst
+++ b/doc/python/morphology.rst
@@ -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
diff --git a/python/morphology.cpp b/python/morphology.cpp
index 1a3c305e488ed4e213d79a592cdff3bdd502b00e..4ab870ab8831a2483b1ac8484ac1e799b1f89544 100644
--- a/python/morphology.cpp
+++ b/python/morphology.cpp
@@ -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);
diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt
index b75a4555bc297fa17173f632b2caa5d1140b5aae..96f7e6d328b1c6c7bd2e43a97cd12440c2c8796b 100644
--- a/test/unit/CMakeLists.txt
+++ b/test/unit/CMakeLists.txt
@@ -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
diff --git a/test/unit/test_asc.cpp b/test/unit/test_asc.cpp
index 0c06fbdc4058a74ef1fcdffd5cac329610a499e1..5e0443b742eaf2866939c8c864222c924d620cff 100644
--- a/test/unit/test_asc.cpp
+++ b/test/unit/test_asc.cpp
@@ -1,6 +1,3 @@
-#include <iostream>
-#include <fstream>
-
 #include <arbor/cable_cell.hpp>
 #include <arbor/morph/primitives.hpp>
 #include <arbor/morph/segment_tree.hpp>
diff --git a/test/unit/test_debug.cpp b/test/unit/test_debug.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..eb944e99f5a9d7881ff6aa702a531bdf72cba538
--- /dev/null
+++ b/test/unit/test_debug.cpp
@@ -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}));
+}