diff --git a/arborio/include/arborio/swcio.hpp b/arborio/include/arborio/swcio.hpp
index 9fff6d9020d895cf60bebe6698380b23c1654cb9..cd7de7657bc03ca95b67409f206e982e9c1053a1 100644
--- a/arborio/include/arborio/swcio.hpp
+++ b/arborio/include/arborio/swcio.hpp
@@ -125,8 +125,17 @@ struct swc_record {
 };
 
 struct swc_data {
-    std::string metadata;
-    std::vector<swc_record> records;
+private:
+    std::string metadata_;
+    std::vector<swc_record> records_;
+
+public:
+    swc_data() = delete;
+    swc_data(std::vector<arborio::swc_record>);
+    swc_data(std::string, std::vector<arborio::swc_record>);
+
+    const std::vector<swc_record>& records() const {return records_;};
+    std::string metadata() const {return metadata_;};
 };
 
 // Read SWC records from stream, collecting any initial metadata represented
@@ -150,50 +159,28 @@ struct swc_data {
 //
 // SWC records are returned in id order.
 
-enum class swc_mode { relaxed, strict };
-
-swc_data parse_swc(std::istream&, swc_mode = swc_mode::strict);
-swc_data parse_swc(const std::string& text, swc_mode mode = swc_mode::strict);
-
-// Parse a series of existing SWC records.
-
-swc_data parse_swc(std::vector<swc_record>, swc_mode = swc_mode::strict);
+swc_data parse_swc(std::istream&);
+swc_data parse_swc(const std::string&);
 
 // Convert a valid, ordered sequence of SWC records to a morphological segment tree.
 //
-// Note that 'one-point soma' SWC files are explicitly not supported; the swc_data
-// is expected to abide by the restrictions of `strict` mode parsing as described
-// above.
+// Note that 'one-point soma' SWC files are explicitly not supported.
 //
 // The generated segment tree will be contiguous. There will be one segment for
 // each SWC record after the first: this record defines the tag and distal point
 // of the segment, while the proximal point is taken from the parent record.
 
-arb::segment_tree as_segment_tree(const std::vector<swc_record>&);
-
-inline arb::segment_tree as_segment_tree(const swc_data& data) {
-    return as_segment_tree(data.records);
-}
+arb::segment_tree load_swc_arbor(const swc_data& data);
 
 // As above, will convert a valid, ordered sequence of SWC records to a morphological
 // segment tree.
 //
-// Note that 'one-point soma' SWC files are supported here; the swc_data is expected
-// to abide by the restrictions of `relaxed` mode parsing as described above.
+// Note that 'one-point soma' SWC files are supported here
 //
 // These functions comply with inferred SWC rules from the Allen institute and Neuron.
 // These rules are explicitly listed in the docs.
 
-arb::segment_tree load_swc_neuron(const std::vector<swc_record>& records);
-
-inline arb::segment_tree load_swc_neuron(const swc_data& data) {
-    return load_swc_neuron(data.records);
-}
-
-arb::segment_tree load_swc_allen(std::vector<swc_record>& records, bool no_gaps=false);
-
-inline arb::segment_tree load_swc_allen(swc_data& data) {
-    return load_swc_allen(data.records);
-}
+arb::segment_tree load_swc_neuron(const swc_data& data);
+arb::segment_tree load_swc_allen(const swc_data& data, bool no_gaps=false);
 
 } // namespace arborio
diff --git a/arborio/swcio.cpp b/arborio/swcio.cpp
index 984dc61ad520df5f2885766ae452637a3e4dd4c6..9bf47ea08959918af4f6e2994faa5bfd822877c0 100644
--- a/arborio/swcio.cpp
+++ b/arborio/swcio.cpp
@@ -118,17 +118,11 @@ std::istream& operator>>(std::istream& in, swc_record& record) {
 
 // Parse SWC format data (comments and sequence of SWC records).
 
-static std::vector<swc_record> sort_and_validate_swc(std::vector<swc_record> records, swc_mode mode) {
+static std::vector<swc_record> sort_and_validate_swc(std::vector<swc_record> records) {
     if (records.empty()) return {};
 
     std::unordered_set<int> seen;
     std::size_t n_rec = records.size();
-    int first_id = records[0].id;
-    int first_tag = records[0].tag;
-
-    if (records.size()<2) {
-        throw swc_spherical_soma(first_id);
-    }
 
     for (std::size_t i = 0; i<n_rec; ++i) {
         swc_record& r = records[i];
@@ -143,28 +137,33 @@ static std::vector<swc_record> sort_and_validate_swc(std::vector<swc_record> rec
     }
 
     std::sort(records.begin(), records.end(), [](const auto& lhs, const auto& rhs) { return lhs.id < rhs.id; });
-    bool first_tag_match = false;
 
     for (std::size_t i = 0; i<n_rec; ++i) {
         const swc_record& r = records[i];
-        first_tag_match |= r.parent_id==first_id && r.tag==first_tag;
-
         if ((i==0 && r.parent_id!=-1) || (i>0 && !seen.count(r.parent_id))) {
             throw swc_no_such_parent(r.id);
         }
     }
 
-    if (mode==swc_mode::strict && !first_tag_match) {
-        throw swc_spherical_soma(first_id);
-    }
-
     return records;
 }
 
-swc_data parse_swc(std::istream& in, swc_mode mode) {
+// swc_data
+swc_data::swc_data(std::vector<arborio::swc_record> recs) :
+    metadata_(),
+    records_(sort_and_validate_swc(std::move(recs))) {};
+
+swc_data::swc_data(std::string meta, std::vector<arborio::swc_record> recs) :
+    metadata_(meta),
+    records_(sort_and_validate_swc(std::move(recs))) {};
+
+// Parse and validate swc data
+
+swc_data parse_swc(std::istream& in) {
     // Collect any initial comments (lines beginning with '#').
 
-    swc_data data;
+    std::string metadata;
+    std::vector<swc_record> records;
     std::string line;
 
     while (in) {
@@ -173,9 +172,9 @@ swc_data parse_swc(std::istream& in, swc_mode mode) {
             getline(in, line, '\n');
             auto from = line.find_first_not_of(" \t");
             if (from != std::string::npos) {
-                data.metadata.append(line, from);
+                metadata.append(line, from);
             }
-            data.metadata += '\n';
+            metadata += '\n';
         }
         else {
             in.unget();
@@ -185,27 +184,22 @@ swc_data parse_swc(std::istream& in, swc_mode mode) {
 
     swc_record r;
     while (in && in >> r) {
-        data.records.push_back(r);
+        records.push_back(r);
     }
 
-    data.records = sort_and_validate_swc(std::move(data.records), mode);
-    return data;
+    return swc_data(metadata, std::move(records));
 }
 
-swc_data parse_swc(const std::string& text, swc_mode mode) {
+swc_data parse_swc(const std::string& text) {
     std::istringstream is(text);
-    return parse_swc(is, mode);
+    return parse_swc(is);
 }
 
-swc_data parse_swc(std::vector<swc_record> records, swc_mode mode) {
-    swc_data data;
-    data.records = sort_and_validate_swc(std::move(records), mode);
-    return data;
-}
+arb::segment_tree load_swc_arbor(const swc_data& data) {
+    const auto& records = data.records();
 
-arb::segment_tree as_segment_tree(const std::vector<swc_record>& records) {
-    if (records.empty()) return {};
-    if (records.size()<2) throw swc_bad_description{records.front().id};
+    if (records.empty())  return {};
+    if (records.size()<2) throw swc_spherical_soma(records[0].tag);
 
     arb::segment_tree tree;
     std::size_t n_seg = records.size()-1;
@@ -214,9 +208,15 @@ arb::segment_tree as_segment_tree(const std::vector<swc_record>& records) {
     std::unordered_map<int, std::size_t> id_to_index;
     id_to_index[records[0].id] = 0;
 
+    // Check whether the first sample has at least one child with the same tag
+    bool first_tag_match = false;
+    int first_id = records[0].id;
+    int first_tag = records[0].tag;
+
     // ith segment is built from i+1th SWC record and its parent.
     for (std::size_t i = 1; i<n_seg+1; ++i) {
         const auto& dist = records[i];
+        first_tag_match |= dist.parent_id==first_id && dist.tag==first_tag;
 
         auto iter = id_to_index.find(dist.parent_id);
         if (iter==id_to_index.end()) throw swc_no_such_parent{dist.id};
@@ -233,10 +233,17 @@ arb::segment_tree as_segment_tree(const std::vector<swc_record>& records) {
         id_to_index[dist.id] = i;
     }
 
+    if (!first_tag_match) {
+        throw swc_spherical_soma(first_id);
+    }
+
     return tree;
 }
 
-arb::segment_tree load_swc_neuron(const std::vector<swc_record>& records) {
+arb::segment_tree load_swc_neuron(const swc_data& data) {
+    const auto& records = data.records();
+
+    // Assert that the file contains at least one sample.
     if (records.empty()) return {};
 
     const int soma_tag = 1;
@@ -412,7 +419,9 @@ arb::segment_tree load_swc_neuron(const std::vector<swc_record>& records) {
     return tree;
 }
 
-arb::segment_tree load_swc_allen(std::vector<swc_record>& records, bool no_gaps) {
+arb::segment_tree load_swc_allen(const swc_data& data, bool no_gaps) {
+    auto records = data.records();
+
     // Assert that the file contains at least one sample.
     if (records.empty()) return {};
 
diff --git a/doc/concepts/morphology.rst b/doc/concepts/morphology.rst
index e5b03e06a4e683a86ab19f798322f9b6ee7416f1..accf25681151cf0ec641ac377a644c76372a9020 100644
--- a/doc/concepts/morphology.rst
+++ b/doc/concepts/morphology.rst
@@ -501,11 +501,11 @@ interpretations.
 
 The SWC file format specifications are not very detailed, which has lead different simulators to interpret
 SWC files in different ways, especially when it comes to the soma. Arbor has its own an interpretation that
-is powerful, and simple to understand at the same time. However, we have also developed functions that will
+is powerful and simple to understand at the same time. However, we have also developed functions that will
 interpret SWC files similarly to how the NEURON simulator would, and how the Allen Institute would.
 
 Despite the differences between the interpretations, there is a common set of checks that are always performed
-to check the validity of SWC files:
+to validate an SWC file:
    * Check that there are no duplicate ids.
    * Check that the parent id of a sample is less than the id of the sample.
    * Check that the parent id of a sample refers to an existing sample.
@@ -562,25 +562,25 @@ and all samples are translated in space towards the origin.
 
 NEURON interpretation:
 """"""""""""""""""""""
-The NEURON interpretation was obtained by experimenting with the `Import3d_SWC_read` function. We came up with the
+The NEURON interpretation was obtained by experimenting with the ``Import3d_SWC_read`` function. We came up with the
 following set of rules that govern NEURON's SWC behavior and enforced them in arbor's NEURON-complaint SWC
 interpreter:
    * SWC files must contain a soma sample and it must to be the first sample.
    * A soma is represented by a series of n≥1 unbranched, serially listed samples.
    * A soma is constructed as a single cylinder with diameter equal to the piecewise average diameter of all the
-   segments forming the soma.
+     segments forming the soma.
    * A single-sample soma at is constructed as a cylinder with length=diameter.
    * If a non-soma sample is to have a soma sample as its parent, it must have the most distal sample of the soma
-   as the parent.
+     as the parent.
    * Every non-soma sample that has a soma sample as its parent, attaches to the created soma cylinder at its midpoint.
    * If a non-soma sample has a soma sample as its parent, no segment is created between the sample and its parent,
-   instead that sample is the proximal point of a new segment, and there is a gap in the morphology (represented
-   electrically as a zero-resistance wire)
+     instead that sample is the proximal point of a new segment, and there is a gap in the morphology (represented
+     electrically as a zero-resistance wire)
    * To create a segment with a certain tag, that is to be attached to the soma, we need at least 2 samples with that
-   tag.
+     tag.
 
 API
 ---
 
 * :ref:`Python <py_morphology>`
-* :ref:`C++ <cpp_morphology>`
+* :ref:`C++ <morphology-construction>`
diff --git a/doc/cpp/cable_cell.rst b/doc/cpp/cable_cell.rst
index 29320f6b5f58195970cffc733953ce29c2c864ae..6e7d146f6e0912625b74e19b3aadd2496578acfe 100644
--- a/doc/cpp/cable_cell.rst
+++ b/doc/cpp/cable_cell.rst
@@ -145,8 +145,8 @@ by two stitches:
    cell.paint("\"soma\"", "hh");
 
 
-Supported morphology formats:
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Supported morphology formats
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 Arbor supports morphologies described using the SWC file format and the NeuroML file format.
 
@@ -163,7 +163,7 @@ basic checks performed on them. The :cpp:type:`swc_data` object can then be used
 :cpp:type:`segment_tree` object using one of the following functions: (See the morphology concepts
 :ref:`page <morph-formats>` for more details).
 
-  * :cpp:func:`as_segment_tree`
+  * :cpp:func:`load_swc_arbor`
   * :cpp:func:`load_swc_allen`
   * :cpp:func:`load_swc_neuron`
 
@@ -209,17 +209,19 @@ basic checks performed on them. The :cpp:type:`swc_data` object can then be used
 
 .. cpp:function:: swc_data parse_swc(std::istream&)
 
-   Returns an `swc_data` object given an std::istream object.
+   Returns an :cpp:type:`swc_data` object given an std::istream object.
 
-.. cpp:function:: segment_tree as_segment_tree(const swc_data& data)
+.. cpp:function:: segment_tree load_swc_arbor(const swc_data& data)
 
    Returns a segment tree constructed according to Arbor's SWC specifications.
 
-.. cpp:function:: segment_tree load_swc_allen(swc_data& data)
+.. cpp:function:: segment_tree load_swc_allen(const swc_data& data, bool no_gaps=false)
 
    Returns a segment tree constructed according to the Allen Institute's SWC specifications.
+   By default, gaps in the segment tree are allowed, this can be toggled using the ``no_gaps``
+   argument.
 
-.. cpp:function:: segment_tree load_swc_neuron(swc_data& data)
+.. cpp:function:: segment_tree load_swc_neuron(const swc_data& data)
 
    Returns a segment tree constructed according to NEURON's SWC specifications.
 
diff --git a/doc/python/morphology.rst b/doc/python/morphology.rst
index f183f28064bca2ff017ba2d031703ff981d5ee9d..84a4d566609f2bf4b918ebf365b940da2c2c6ff6 100644
--- a/doc/python/morphology.rst
+++ b/doc/python/morphology.rst
@@ -285,7 +285,7 @@ Cell morphology
         the use of cylinders or fustrums to describe morphologies, and it is not possible to
         infer how branches attached to the soma should be connected.
 
-        The :func:`load_swc_allen` function provides support for interpreting
+        The :func:`load_swc_allen` and :func:`load_swc_neuron` functions provide support for interpreting
         such SWC files.
 
 
diff --git a/doc/scripts/inputs.py b/doc/scripts/inputs.py
index 1a323c3ae012acfe155d41950d7719c3111b2a2e..b05c35a864b237d6e9d9b16fc6e211526a6a3fca 100644
--- a/doc/scripts/inputs.py
+++ b/doc/scripts/inputs.py
@@ -85,7 +85,7 @@ tmp = [
 ysoma_morph3 = representation.make_morph(tmp)
 
 tmp = [
-    [[Segment((-3.0, 0.0, 0.7), (0.0, 0.0, 1.0), 2)], [Segment((0.0, 0.0, 1.0), (2.0, 0.0,  1.0), 1)], [Segment((2.0, 0.0, 1.0), (20.0, 0.0, 1.0), 3)]],
+    [[Segment((0.0, 0.0, 1.0), (2.0, 0.0,  1.0), 1)], [Segment((-3.0, 0.0, 0.7), (0.0, 0.0, 1.0), 2)], [Segment((2.0, 0.0, 1.0), (20.0, 0.0, 1.0), 3)]],
 ]
 swc_morph = representation.make_morph(tmp)
 
diff --git a/example/single/single.cpp b/example/single/single.cpp
index f859009f24585996c62bc6fa8fafd8b95d8940d3..6d8e2d4607b5c88f26ce71fe4ee8f7ef6fcab08c 100644
--- a/example/single/single.cpp
+++ b/example/single/single.cpp
@@ -158,5 +158,5 @@ arb::morphology read_swc(const std::string& path) {
     std::ifstream f(path);
     if (!f) throw std::runtime_error("unable to open SWC file: "+path);
 
-    return arb::morphology(arborio::as_segment_tree(arborio::parse_swc(f)));
+    return arb::morphology(arborio::load_swc_arbor(arborio::parse_swc(f)));
 }
diff --git a/python/morphology.cpp b/python/morphology.cpp
index dd420097ce4d97f22ab3e4bd111bc580d6399bcd..dba6f59494896250562d37ef034a5258b10c7bd6 100644
--- a/python/morphology.cpp
+++ b/python/morphology.cpp
@@ -14,39 +14,6 @@
 
 namespace pyarb {
 
-arb::segment_tree load_swc_neuron(const std::string& fname) {
-    std::ifstream fid{fname};
-    if (!fid.good()) {
-        throw pyarb_error(util::pprintf("can't open file '{}'", fname));
-    }
-    try {
-        auto records = arborio::parse_swc(fid, arborio::swc_mode::relaxed).records;
-        return arborio::load_swc_neuron(records);
-    }
-    catch (arborio::swc_error& e) {
-        // Try to produce helpful error messages for SWC parsing errors.
-        throw pyarb_error(
-                util::pprintf("NEURON SWC: error parsing {}: {}", fname, e.what()));
-    }
-}
-
-arb::segment_tree load_swc_allen(const std::string& fname, bool no_gaps=false) {
-        std::ifstream fid{fname};
-        if (!fid.good()) {
-            throw pyarb_error(util::pprintf("can't open file '{}'", fname));
-        }
-        try {
-            using namespace arb;
-            auto records = arborio::parse_swc(fid, arborio::swc_mode::relaxed).records;
-            return arborio::load_swc_allen(records, no_gaps);
-        }
-        catch (arborio::swc_error& e) {
-            // Try to produce helpful error messages for SWC parsing errors.
-            throw pyarb_error(
-                util::pprintf("Allen SWC: error parsing {}: {}", fname, e.what()));
-        }
-}
-
 void register_morphology(pybind11::module& m) {
     using namespace pybind11::literals;
 
@@ -167,7 +134,7 @@ void register_morphology(pybind11::module& m) {
                 return util::pprintf("<arbor.segment_tree:\n{}>", s);});
 
     // Function that creates a segment_tree from an swc file.
-    // Wraps calls to C++ functions arb::parse_swc_file() and arb::swc_as_segment_tree().
+    // Wraps calls to C++ functions arborio::parse_swc() and arborio::load_swc_arbor().
     m.def("load_swc",
         [](std::string fname) {
             std::ifstream fid{fname};
@@ -175,35 +142,90 @@ void register_morphology(pybind11::module& m) {
                 throw pyarb_error(util::pprintf("can't open file '{}'", fname));
             }
             try {
-                auto records = arborio::parse_swc(fid).records;
-                return arborio::as_segment_tree(records);
+                return arborio::load_swc_arbor(arborio::parse_swc(fid));
+            }
+            catch (arborio::swc_error& e) {
+                // Try to produce helpful error messages for SWC parsing errors.
+                throw pyarb_error(util::pprintf("error parsing {}: {}", fname, e.what()));
+            }
+        },
+        "filename"_a,
+        "Generate a segment tree from an SWC file following the rules prescribed by\n"
+        "Arbor. Specifically:\n"
+        "* Single-segment somas are disallowed. These are usually interpreted as spherical somas\n"
+        "  and are a special case. This behavior is not allowed using this SWC loader.\n"
+        "* There are no special rules related to somata. They can be one or multiple branches\n"
+        "  and other segments can connect anywhere along them.\n"
+        "* A segment is always created between a sample and its parent, meaning there\n"
+        "  are no gaps in the resulting segment tree.");
+
+    m.def("load_swc_allen",
+        [](std::string fname, bool no_gaps=false) {
+            std::ifstream fid{fname};
+            if (!fid.good()) {
+                throw pyarb_error(util::pprintf("can't open file '{}'", fname));
+            }
+            try {
+                return arborio::load_swc_allen(arborio::parse_swc(fid), no_gaps);
+            }
+            catch (arborio::swc_error& e) {
+                // Try to produce helpful error messages for SWC parsing errors.
+                throw pyarb_error(
+                        util::pprintf("Allen SWC: error parsing {}: {}", fname, e.what()));
+            }
+        },
+        "filename"_a, "no_gaps"_a=false,
+        "Generate a segment tree from an SWC file following the rules prescribed by\n"
+        "AllenDB and Sonata. Specifically:\n"
+        "* The first sample (the root) is treated as the center of the soma.\n"
+        "* The first morphology is translated such that the soma is centered at (0,0,0).\n"
+        "* The first sample has tag 1 (soma).\n"
+        "* All other samples have tags 2, 3 or 4 (axon, apic and dend respectively)\n"
+        "SONATA prescribes that there should be no gaps, however the models in AllenDB\n"
+        "have gaps between the start of sections and the soma. The flag no_gaps can be\n"
+        "used to enforce this requirement.\n"
+        "\n"
+        "Arbor does not support modelling the soma as a sphere, so a cylinder with length\n"
+        "equal to the soma diameter is used. The cylinder is centered on the origin, and\n"
+        "aligned along the z axis.\n"
+        "Axons and apical dendrites are attached to the proximal end of the cylinder, and\n"
+        "dendrites to the distal end, with a gap between the start of each branch and the\n"
+        "end of the soma cylinder to which it is attached.");
+
+    m.def("load_swc_neuron",
+        [](std::string fname) {
+            std::ifstream fid{fname};
+            if (!fid.good()) {
+                throw pyarb_error(util::pprintf("can't open file '{}'", fname));
+            }
+            try {
+                return arborio::load_swc_neuron(arborio::parse_swc(fid));
             }
             catch (arborio::swc_error& e) {
                 // Try to produce helpful error messages for SWC parsing errors.
                 throw pyarb_error(
-                    util::pprintf("error parsing {}: {}", fname, e.what()));
+                    util::pprintf("NEURON SWC: error parsing {}: {}", fname, e.what()));
             }
         },
-        "Load an swc file and as a segment_tree.");
-
-    m.def("load_swc_allen", &load_swc_allen,
-            "filename"_a, "no_gaps"_a=false,
-            "Generate a segment tree from an SWC file following the rules prescribed by\n"
-            "AllenDB and Sonata. Specifically:\n"
-            "* The first sample (the root) is treated as the center of the soma.\n"
-            "* The first morphology is translated such that the soma is centered at (0,0,0).\n"
-            "* The first sample has tag 1 (soma).\n"
-            "* All other samples have tags 2, 3 or 4 (axon, apic and dend respectively)\n"
-            "SONATA prescribes that there should be no gaps, however the models in AllenDB\n"
-            "have gaps between the start of sections and the soma. The flag no_gaps can be\n"
-            "used to enforce this requirement.\n"
-            "\n"
-            "Arbor does not support modelling the soma as a sphere, so a cylinder with length\n"
-            "equal to the soma diameter is used. The cylinder is centered on the origin, and\n"
-            "aligned along the z axis.\n"
-            "Axons and apical dendrites are attached to the proximal end of the cylinder, and\n"
-            "dendrites to the distal end, with a gap between the start of each branch and the\n"
-            "end of the soma cylinder to which it is attached.");
+        "filename"_a,
+        "Generate a segment tree from an SWC file following the rules prescribed by\n"
+        "NEURON. Specifically:\n"
+        "* The first sample must be a soma sample.\n"
+        "* The soma is represented by a series of n≥1 unbranched, serially listed samples.\n"
+        "* The soma is constructed as a single cylinder with diameter equal to the piecewise\n"
+        "  average diameter of all the segments forming the soma.\n"
+        "* A single-sample soma at is constructed as a cylinder with length=diameter.\n"
+        "* If a non-soma sample is to have a soma sample as its parent, it must have the\n"
+        "  most distal sample of the soma as the parent.\n"
+        "* Every non-soma sample that has a soma sample as its parent, attaches to the\n"
+        "  created soma cylinder at its midpoint.\n"
+        "* If a non-soma sample has a soma sample as its parent, no segment is created\n"
+        "  between the sample and its parent, instead that sample is the proximal point of\n"
+        "  a new segment, and there is a gap in the morphology (represented electrically as a\n"
+        "  zero-resistance wire)\n"
+        "* To create a segment with a certain tag, that is to be attached to the soma,\n"
+        "  we need at least 2 samples with that tag."
+        );
 
     // arb::morphology
 
diff --git a/test/ubench/fvm_discretize.cpp b/test/ubench/fvm_discretize.cpp
index 8c353b5ca5b3dd59f38f34cc19f2eea94b810f19..dbfa258c7875f00bed5736cff433240b0bb14995 100644
--- a/test/ubench/fvm_discretize.cpp
+++ b/test/ubench/fvm_discretize.cpp
@@ -5,7 +5,9 @@
 
 #include <arbor/cable_cell.hpp>
 #include <arbor/morph/morphology.hpp>
-#include <arbor/swcio.hpp>
+
+#include <arborio/swcio.hpp>
+
 #include <benchmark/benchmark.h>
 
 #include "event_queue.hpp"
@@ -26,7 +28,7 @@ arb::morphology from_swc(const std::string& path) {
     std::ifstream in(path);
     if (!in) throw std::runtime_error("could not open "+path);
 
-    return morphology(arb::as_segment_tree(parse_swc(in)));
+    return morphology(arborio::load_swc_arbor(arborio::parse_swc(in)));
 }
 
 void run_cv_geom(benchmark::State& state) {
diff --git a/test/unit/test_morphology.cpp b/test/unit/test_morphology.cpp
index 4f110981f03574841cb17857e01a5dc9fb41f8aa..b4648c6e12005332e0e340bb332c309ad6818c5b 100644
--- a/test/unit/test_morphology.cpp
+++ b/test/unit/test_morphology.cpp
@@ -319,10 +319,10 @@ TEST(morphology, swc) {
     }
 
     // Load swc samples from file.
-    auto swc = arborio::parse_swc(fid, arborio::swc_mode::strict);
+    auto swc = arborio::parse_swc(fid);
 
     // Build a segmewnt_tree from swc samples.
-    auto sm = arborio::as_segment_tree(swc);
+    auto sm = arborio::load_swc_arbor(swc);
     EXPECT_EQ(5798u, sm.size()); // SWC data contains 5799 samples.
 
     // Test that the morphology contains the expected number of branches.
diff --git a/test/unit/test_swcio.cpp b/test/unit/test_swcio.cpp
index 9de95c337c6d77d3ed36f1ba1d8069f8844d85da..d836bc0d3c8e97bdbec282ede206c0ed3a765476 100644
--- a/test/unit/test_swcio.cpp
+++ b/test/unit/test_swcio.cpp
@@ -66,7 +66,7 @@ TEST(swc_record, invalid_input) {
     }
 }
 
-TEST(swc_parser, bad_relaxed) {
+TEST(swc_parser, bad_parse) {
     {
         std::string bad1 =
             "1 1 0.1 0.2 0.3 0.4 -1\n"
@@ -74,7 +74,7 @@ TEST(swc_parser, bad_relaxed) {
             "3 1 0.1 0.2 0.3 0.4 2\n"
             "5 1 0.1 0.2 0.3 0.4 4\n";
 
-        EXPECT_THROW(parse_swc(bad1, swc_mode::relaxed), swc_no_such_parent);
+        EXPECT_THROW(parse_swc(bad1), swc_no_such_parent);
     }
 
     {
@@ -84,7 +84,7 @@ TEST(swc_parser, bad_relaxed) {
             "3 1 0.1 0.2 0.3 0.4 2\n"
             "4 1 0.1 0.2 0.3 0.4 -1\n";
 
-        EXPECT_THROW(parse_swc(bad2, swc_mode::relaxed), swc_no_such_parent);
+        EXPECT_THROW(parse_swc(bad2), swc_no_such_parent);
     }
 
     {
@@ -94,7 +94,7 @@ TEST(swc_parser, bad_relaxed) {
             "3 1 0.1 0.2 0.3 0.4 1\n"
             "4 1 0.1 0.2 0.3 0.4 3\n";
 
-        EXPECT_THROW(parse_swc(bad3, swc_mode::relaxed), swc_record_precedes_parent);
+        EXPECT_THROW(parse_swc(bad3), swc_record_precedes_parent);
     }
 
     {
@@ -104,7 +104,7 @@ TEST(swc_parser, bad_relaxed) {
             "3 1 0.1 0.2 0.3 0.4 1\n"
             "4 1 0.1 0.2 0.3 0.4 3\n";
 
-        EXPECT_THROW(parse_swc(bad4, swc_mode::relaxed), swc_duplicate_record_id);
+        EXPECT_THROW(parse_swc(bad4), swc_duplicate_record_id);
     }
 
     {
@@ -114,68 +114,47 @@ TEST(swc_parser, bad_relaxed) {
             "3 1 0.1 0.2 0.3 0.4 2\n"
             "4 1 0.1 0.2 0.3 0.4 -1\n";
 
-        EXPECT_THROW(parse_swc(bad5, swc_mode::relaxed), swc_no_such_parent);
+        EXPECT_THROW(parse_swc(bad5), swc_no_such_parent);
     }
 }
 
-TEST(swc_parser, bad_strict) {
-    {
-        std::string bad6 =
-            "1 7 0.1 0.2 0.3 0.4 -1\n"; // just one record
-
-        EXPECT_THROW(parse_swc(bad6, swc_mode::relaxed), swc_spherical_soma);
-    }
-    {
-        std::string bad3 =
-            "1 4 0.1 0.2 0.3 0.4 -1\n" // solitary tag
-            "2 6 0.1 0.2 0.3 0.4 1\n"
-            "3 6 0.1 0.2 0.3 0.4 2\n"
-            "4 6 0.1 0.2 0.3 0.4 1\n";
-
-        EXPECT_THROW(parse_swc(bad3, swc_mode::strict), swc_spherical_soma);
-        EXPECT_NO_THROW(parse_swc(bad3, swc_mode::relaxed));
-    }
-}
-
-TEST(swc_parser, valid_relaxed) {
+TEST(swc_parser, valid_parse) {
     // Non-contiguous is okay.
     {
-        std::string bad1 =
+        std::string valid1 =
             "1 1 0.1 0.2 0.3 0.4 -1\n"
             "2 1 0.1 0.2 0.3 0.4 1\n"
             "3 1 0.1 0.2 0.3 0.4 2\n"
             "5 1 0.1 0.2 0.3 0.4 3\n"; // non-contiguous
 
-        EXPECT_NO_THROW(parse_swc(bad1, swc_mode::relaxed));
+        EXPECT_NO_THROW(parse_swc(valid1));
     }
 
     // As is out of order.
     {
-        std::string bad2 =
+        std::string valid2 =
             "1 1 0.1 0.2 0.3 0.4 -1\n"
             "3 1 0.1 0.2 0.3 0.4 2\n" // out of order
             "2 1 0.1 0.2 0.3 0.4 1\n"
             "4 1 0.1 0.2 0.3 0.4 3\n";
 
-        EXPECT_NO_THROW(parse_swc(bad2, swc_mode::relaxed));
+        EXPECT_NO_THROW(parse_swc(valid2));
     }
 
-}
-
-TEST(swc_parser, valid_strict) {
+    //  With comments
     {
-        std::string valid1 =
+        std::string valid3 =
             "# Hello\n"
             "# world.\n";
 
-        swc_data data = parse_swc(valid1, swc_mode::strict);
-        EXPECT_EQ("Hello\nworld.\n", data.metadata);
-        EXPECT_TRUE(data.records.empty());
+        swc_data data = parse_swc(valid3);
+        EXPECT_EQ("Hello\nworld.\n", data.metadata());
+        EXPECT_TRUE(data.records().empty());
     }
 
+    // Non-contiguous, out of order records with comments.
     {
-        // Non-contiguous, out of order records are fine.
-        std::string valid2 =
+        std::string valid4 =
             "# Some people put\n"
             "# <xml /> in here!\n"
             "1 1 0.1 0.2 0.3 0.4 -1\n"
@@ -183,13 +162,13 @@ TEST(swc_parser, valid_strict) {
             "5 2 0.2 0.6 0.8 0.2 2\n"
             "4 0 0.2 0.8 0.6 0.3 2";
 
-        swc_data data = parse_swc(valid2, swc_mode::strict);
-        EXPECT_EQ("Some people put\n<xml /> in here!\n", data.metadata);
-        ASSERT_EQ(4u, data.records.size());
-        EXPECT_EQ(swc_record(1, 1, 0.1, 0.2, 0.3, 0.4, -1), data.records[0]);
-        EXPECT_EQ(swc_record(2, 1, 0.3, 0.4, 0.5, 0.3, 1), data.records[1]);
-        EXPECT_EQ(swc_record(4, 0, 0.2, 0.8, 0.6, 0.3, 2), data.records[2]);
-        EXPECT_EQ(swc_record(5, 2, 0.2, 0.6, 0.8, 0.2, 2), data.records[3]);
+        swc_data data = parse_swc(valid4);
+        EXPECT_EQ("Some people put\n<xml /> in here!\n", data.metadata());
+        ASSERT_EQ(4u, data.records().size());
+        EXPECT_EQ(swc_record(1, 1, 0.1, 0.2, 0.3, 0.4, -1), data.records()[0]);
+        EXPECT_EQ(swc_record(2, 1, 0.3, 0.4, 0.5, 0.3, 1), data.records()[1]);
+        EXPECT_EQ(swc_record(4, 0, 0.2, 0.8, 0.6, 0.3, 2), data.records()[2]);
+        EXPECT_EQ(swc_record(5, 2, 0.2, 0.6, 0.8, 0.2, 2), data.records()[3]);
 
         // Trailing garbage is ignored in data records.
         std::string valid3 =
@@ -200,27 +179,13 @@ TEST(swc_parser, valid_strict) {
             "3 2 0.2 0.6 0.8 0.2 2 # it is a cow!\n"
             "4 0 0.2 0.8 0.6 0.3 2";
 
-        swc_data data2 = parse_swc(valid2, swc_mode::strict);
-        EXPECT_EQ(data.records, data2.records);
+        swc_data data2 = parse_swc(valid4);
+        EXPECT_EQ(data.records(), data2.records());
     }
+
 }
 
-TEST(swc_parser, segment_tree) {
-    {
-        // Missing parent record will throw.
-        std::vector<swc_record> swc{
-            {1, 1, 0., 0., 0., 1., -1},
-            {5, 3, 1., 1., 1., 1., 2}
-        };
-        EXPECT_THROW(as_segment_tree(swc), swc_no_such_parent);
-    }
-    {
-        // A single SWC record will throw.
-        std::vector<swc_record> swc{
-            {1, 1, 0., 0., 0., 1., -1}
-        };
-        EXPECT_THROW(as_segment_tree(swc), swc_bad_description);
-    }
+TEST(swc_parser, arbor_complaint) {
     {
         // Otherwise, ensure segment ends and tags correspond.
         mpoint p0{0.1, 0.2, 0.3, 0.4};
@@ -237,7 +202,7 @@ TEST(swc_parser, segment_tree) {
             {7, 3, p4.x, p4.y, p4.z, p4.radius, 4}
         };
 
-        segment_tree tree = as_segment_tree(swc);
+        segment_tree tree = load_swc_arbor(swc);
         ASSERT_EQ(4u, tree.segments().size());
 
         EXPECT_EQ(mnpos, tree.parents()[0]);
@@ -260,7 +225,67 @@ TEST(swc_parser, segment_tree) {
         EXPECT_EQ(p2, tree.segments()[3].prox);
         EXPECT_EQ(p4, tree.segments()[3].dist);
     }
+    {
+        // Otherwise, ensure segment ends and tags correspond.
+        mpoint p0{0.1, 0.2, 0.3, 0.4};
+        mpoint p1{0.3, 0.4, 0.5, 0.3};
+        mpoint p2{0.2, 0.8, 0.6, 0.3};
+        mpoint p3{0.2, 0.6, 0.8, 0.2};
+
+        std::vector<swc_record> swc{
+            {1, 1, p0.x, p0.y, p0.z, p0.radius, -1},
+            {2, 1, p1.x, p1.y, p1.z, p1.radius, 1},
+            {3, 2, p2.x, p2.y, p2.z, p2.radius, 1},
+            {4, 3, p3.x, p3.y, p3.z, p3.radius, 2},
+        };
+
+        segment_tree tree = load_swc_arbor(swc);
+        ASSERT_EQ(3u, tree.segments().size());
+
+        EXPECT_EQ(mnpos, tree.parents()[0]);
+        EXPECT_EQ(1,  tree.segments()[0].tag);
+        EXPECT_EQ(p0, tree.segments()[0].prox);
+        EXPECT_EQ(p1, tree.segments()[0].dist);
+
+        EXPECT_EQ(mnpos, tree.parents()[1]);
+        EXPECT_EQ(2,  tree.segments()[1].tag);
+        EXPECT_EQ(p0, tree.segments()[1].prox);
+        EXPECT_EQ(p2, tree.segments()[1].dist);
+
+        EXPECT_EQ(0u, tree.parents()[2]);
+        EXPECT_EQ(3,  tree.segments()[2].tag);
+        EXPECT_EQ(p1, tree.segments()[2].prox);
+        EXPECT_EQ(p3, tree.segments()[2].dist);
+    }
+}
+
+TEST(swc_parser, not_arbor_complaint) {
+    {
+        // Missing parent record will throw.
+        std::vector<swc_record> swc{
+            {1, 1, 0., 0., 0., 1., -1},
+            {5, 3, 1., 1., 1., 1., 2}
+        };
+        EXPECT_THROW(load_swc_arbor(swc), swc_no_such_parent);
+    }
+    {
+        // A single SWC record will throw.
+        std::vector<swc_record> swc{
+            {1, 1, 0., 0., 0., 1., -1}
+        };
+        EXPECT_THROW(load_swc_arbor(swc), swc_spherical_soma);
+    }
+    {
+        std::vector<swc_record> swc{
+            {1, 4, 0.1, 0.2, 0.3, 0.4, -1},
+            {2, 6, 0.1, 0.2, 0.3, 0.4,  1},
+            {3, 6, 0.1, 0.2, 0.3, 0.4,  2},
+            {4, 6, 0.1, 0.2, 0.3, 0.4,  1}
+        };
+        EXPECT_THROW(load_swc_arbor(swc), swc_spherical_soma);
+    }
 }
+
 TEST(swc_parser, allen_compliant) {
     using namespace arborio;
     {
@@ -420,7 +445,7 @@ TEST(swc_parser, not_allen_compliant) {
             {1, 1, p0.x, p0.y, p0.z, p0.radius, -1},
             {2, 3, p1.x, p1.y, p1.z, p1.radius,  4}
         };
-        EXPECT_THROW(load_swc_allen(swc), swc_no_such_parent);
+        EXPECT_THROW(load_swc_allen(swc), swc_record_precedes_parent);
     }
     {
         // parent sample is self
@@ -431,7 +456,7 @@ TEST(swc_parser, not_allen_compliant) {
             {1, 1, p0.x, p0.y, p0.z, p0.radius, -1},
             {2, 1, p1.x, p1.y, p1.z, p1.radius,  2}
         };
-        EXPECT_THROW(load_swc_allen(swc), swc_no_such_parent);
+        EXPECT_THROW(load_swc_allen(swc), swc_record_precedes_parent);
     }
 }
 
@@ -905,7 +930,7 @@ TEST(swc_parser, not_neuron_compliant) {
                 {2, 1, p1.x, p1.y, p1.z, p1.radius,  1},
                 {3, 3, p2.x, p2.y, p2.z, p2.radius,  4}
         };
-        EXPECT_THROW(load_swc_neuron(swc), swc_no_such_parent);
+        EXPECT_THROW(load_swc_neuron(swc), swc_record_precedes_parent);
     }
     {
         // parent sample is self
@@ -918,7 +943,7 @@ TEST(swc_parser, not_neuron_compliant) {
                 {2, 1, p1.x, p1.y, p1.z, p1.radius,  1},
                 {3, 3, p2.x, p2.y, p2.z, p2.radius,  3}
         };
-        EXPECT_THROW(load_swc_neuron(swc), swc_no_such_parent);
+        EXPECT_THROW(load_swc_neuron(swc), swc_record_precedes_parent);
     }
 }
 
@@ -934,7 +959,7 @@ TEST(swc_parser, from_neuromorpho)
         return;
     }
 
-    auto data = parse_swc(fid, swc_mode::strict);
-    EXPECT_EQ(5799u, data.records.size());
+    auto data = parse_swc(fid);
+    EXPECT_EQ(5799u, data.records().size());
 }
 #endif