Skip to content
This repository was archived by the owner on Feb 26, 2025. It is now read-only.

Commit 7a3b294

Browse files
authored
Section change (#510)
* A section within MorphIO is defined as a series of segments between bifurcation/multifurcation points. However, SWC files allow section change without a branch. Normal parsing of this will raise an exception, but can be allowed using the option `UNIFURCATED_SECTION_CHANGE` * Create a warning if the section type changes without a bifurcation * Add option to allow for sections changes in SWC files without bifurcation
1 parent 4375467 commit 7a3b294

8 files changed

Lines changed: 132 additions & 21 deletions

File tree

binds/python/bind_enums.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ void bind_enums(py::module& m) {
7070
.value("soma_sphere", morphio::enums::Option::SOMA_SPHERE)
7171
.value("no_duplicates", morphio::enums::Option::NO_DUPLICATES)
7272
.value("nrn_order", morphio::enums::Option::NRN_ORDER)
73+
.value("allow_unifurcated_section_change",
74+
morphio::enums::Option::ALLOW_UNIFURCATED_SECTION_CHANGE)
7375
.export_values();
7476

7577

@@ -95,7 +97,8 @@ void bind_enums(py::module& m) {
9597
.value("write_empty_morphology", morphio::enums::WRITE_EMPTY_MORPHOLOGY)
9698
.value("zero_diameter", morphio::enums::Warning::ZERO_DIAMETER)
9799
.value("soma_non_contour", morphio::enums::Warning::SOMA_NON_CONTOUR)
98-
.value("soma_non_cylinder_or_point", morphio::enums::Warning::SOMA_NON_CYLINDER_OR_POINT);
100+
.value("soma_non_cylinder_or_point", morphio::enums::Warning::SOMA_NON_CYLINDER_OR_POINT)
101+
.value("type_changed_within_section", morphio::enums::Warning::SECTION_TYPE_CHANGED);
99102

100103
py::enum_<morphio::enums::SomaType>(m, "SomaType", py::arithmetic())
101104
.value("SOMA_UNDEFINED", morphio::enums::SomaType::SOMA_UNDEFINED)

binds/python/generated/docstrings.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -770,6 +770,18 @@ static const char *mkd_doc_morphio_SectionBuilderError = R"doc()doc";
770770

771771
static const char *mkd_doc_morphio_SectionBuilderError_SectionBuilderError = R"doc()doc";
772772

773+
static const char *mkd_doc_morphio_SectionTypeChanged = R"doc()doc";
774+
775+
static const char *mkd_doc_morphio_SectionTypeChanged_SectionTypeChanged = R"doc()doc";
776+
777+
static const char *mkd_doc_morphio_SectionTypeChanged_errorLevel = R"doc()doc";
778+
779+
static const char *mkd_doc_morphio_SectionTypeChanged_lineNumber = R"doc()doc";
780+
781+
static const char *mkd_doc_morphio_SectionTypeChanged_msg = R"doc()doc";
782+
783+
static const char *mkd_doc_morphio_SectionTypeChanged_warning = R"doc()doc";
784+
773785
static const char *mkd_doc_morphio_Section_Section = R"doc()doc";
774786

775787
static const char *mkd_doc_morphio_Section_breadth_begin = R"doc(Breadth first iterator)doc";
@@ -1094,6 +1106,8 @@ static const char *mkd_doc_morphio_enums_Option =
10941106
R"doc(The list of modifier flags that can be passed when loading a
10951107
morphology See morphio::mut::modifiers for more information)doc";
10961108

1109+
static const char *mkd_doc_morphio_enums_Option_ALLOW_UNIFURCATED_SECTION_CHANGE = R"doc(Allow section type to change without bifurcation)doc";
1110+
10971111
static const char *mkd_doc_morphio_enums_Option_NO_DUPLICATES = R"doc(Skip duplicating points)doc";
10981112

10991113
static const char *mkd_doc_morphio_enums_Option_NO_MODIFIER = R"doc(Read morphology as is without any modification)doc";
@@ -1211,6 +1225,8 @@ static const char *mkd_doc_morphio_enums_Warning_NO_SOMA_FOUND = R"doc(No soma f
12111225

12121226
static const char *mkd_doc_morphio_enums_Warning_ONLY_CHILD = R"doc(Single child sections are not allowed in SWC format)doc";
12131227

1228+
static const char *mkd_doc_morphio_enums_Warning_SECTION_TYPE_CHANGED = R"doc(In SWC, the type changed within a section, not post bifurcation)doc";
1229+
12141230
static const char *mkd_doc_morphio_enums_Warning_SOMA_NON_CONFORM = R"doc(Soma does not conform the three point soma spec from NeuroMorpho.org)doc";
12151231

12161232
static const char *mkd_doc_morphio_enums_Warning_SOMA_NON_CONTOUR = R"doc(Soma must be a contour for ASC and H5)doc";

doc/source/morphology.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ The following flags are supported:
172172
each section is no longer the last point of the parent section.
173173
* ``morphio::NRN_ORDER``\: Neurite are reordered according to the
174174
`NEURON simulator ordering <https://github.com/neuronsimulator/nrn/blob/2dbf2ebf95f1f8e5a9f0565272c18b1c87b2e54c/share/lib/hoc/import3d/import3d_gui.hoc#L874>`_
175+
* ``morphio::UNIFURCATED_SECTION_CHANGE``\: Allow section type to change without bifurcation, emits warning
175176

176177
Multiple flags can be passed by using the standard bit flag manipulation (works the same way in C++
177178
and Python):

include/morphio/enums.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ enum Option {
1919
TWO_POINTS_SECTIONS = 0x01, //!< Read sections only with 2 or more points
2020
SOMA_SPHERE = 0x02, //!< Interpret morphology soma as a sphere
2121
NO_DUPLICATES = 0x04, //!< Skip duplicating points
22-
NRN_ORDER = 0x08 //!< Order of neurites will be the same as in NEURON simulator
22+
NRN_ORDER = 0x08, //!< Order of neurites will be the same as in NEURON simulator
23+
ALLOW_UNIFURCATED_SECTION_CHANGE = 0x10 //!< Allow section type to change without bifurcation
2324
};
2425

2526
/**
@@ -42,6 +43,7 @@ enum Warning {
4243
ZERO_DIAMETER, //!< Zero section diameter
4344
SOMA_NON_CONTOUR, //!< Soma must be a contour for ASC and H5
4445
SOMA_NON_CYLINDER_OR_POINT, //!< Soma must be stacked cylinders or a point
46+
SECTION_TYPE_CHANGED, //!< In SWC, the type changed within a section, not post bifurcation
4547
};
4648

4749
enum AnnotationType {

include/morphio/warning_handling.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,23 @@ struct ZeroDiameter: public WarningMessage {
5454
uint64_t lineNumber;
5555
};
5656

57+
struct SectionTypeChanged: public WarningMessage {
58+
SectionTypeChanged(std::string uri_, uint64_t lineNumber_)
59+
: WarningMessage(std::move(uri_))
60+
, lineNumber(lineNumber_) {}
61+
morphio::enums::Warning warning() const final {
62+
return Warning::SECTION_TYPE_CHANGED;
63+
}
64+
morphio::readers::ErrorLevel errorLevel = morphio::readers::ErrorLevel::WARNING;
65+
std::string msg() const final {
66+
static const char* description =
67+
"Warning: Type changed within section, without bifurcation";
68+
return "\n" + details::errorLink(uri, lineNumber, errorLevel) + description;
69+
}
70+
71+
uint64_t lineNumber;
72+
};
73+
5774
struct DisconnectedNeurite: public WarningMessage {
5875
DisconnectedNeurite(std::string uri_, uint64_t lineNumber_)
5976
: WarningMessage(std::move(uri_))

src/readers/morphologySWC.cpp

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,6 @@ class SWCTokenizer
144144
struct SWCSample {
145145
enum : unsigned int { UNKNOWN_ID = 0xFFFFFFFE };
146146

147-
SWCSample() = default; // XXX
148147
floatType diameter = -1.;
149148
Point point{};
150149
SectionType type = SECTION_UNDEFINED;
@@ -234,14 +233,15 @@ class SWCBuilder
234233
using Samples = std::vector<SWCSample>;
235234

236235
public:
237-
SWCBuilder(std::string path, WarningHandler* warning_handler)
236+
SWCBuilder(std::string path, WarningHandler* warning_handler, unsigned int options)
238237
: path_(std::move(path))
239-
, warning_handler_(warning_handler) {}
238+
, warning_handler_(warning_handler)
239+
, options_(options) {}
240240

241-
Property::Properties buildProperties(const std::string& contents, unsigned int options) {
241+
Property::Properties buildProperties(const std::string& contents) {
242242
const Samples samples = readSamples(contents, path_);
243243
buildSWC(samples);
244-
morph_.applyModifiers(options);
244+
morph_.applyModifiers(options_);
245245
return morph_.buildReadOnly();
246246
}
247247

@@ -305,10 +305,6 @@ class SWCBuilder
305305
for (const auto& s : soma_samples) {
306306
if (s.parentId == SWC_ROOT) {
307307
parent_count++;
308-
} else if (samples_.count(s.parentId) == 0) {
309-
details::ErrorMessages err_(path_);
310-
throw MissingParentError(
311-
err_.ERROR_MISSING_PARENT(s.id, static_cast<int>(s.parentId), s.lineNumber));
312308
} else if (samples_.at(s.parentId).type != SECTION_SOMA) {
313309
details::ErrorMessages err_(path_);
314310
throw SomaError(err_.ERROR_SOMA_WITH_NEURITE_PARENT(s.lineNumber));
@@ -477,7 +473,14 @@ class SWCBuilder
477473
while (children_count == 1) {
478474
sample = &samples_.at(id);
479475
if(sample->type != samples_.at(children_.at(id)[0]).type){
480-
break;
476+
if (options_ & ALLOW_UNIFURCATED_SECTION_CHANGE) {
477+
warning_handler_->emit(
478+
std::make_unique<SectionTypeChanged>(path_, sample->lineNumber));
479+
break;
480+
}
481+
throw RawDataError("Section type changed without a bifucation at line: " +
482+
std::to_string(sample->lineNumber) +
483+
", consider using UNIFURCATED_SECTION_CHANGE option");
481484
}
482485
points.push_back(sample->point);
483486
diameters.push_back(sample->diameter);
@@ -518,9 +521,9 @@ class SWCBuilder
518521
mut::Morphology morph_;
519522
std::string path_;
520523
WarningHandler* warning_handler_;
524+
unsigned int options_;
521525
};
522526

523-
524527
} // namespace details
525528

526529
namespace readers {
@@ -530,7 +533,7 @@ Property::Properties load(const std::string& path,
530533
unsigned int options,
531534
std::shared_ptr<WarningHandler>& warning_handler) {
532535
auto properties =
533-
details::SWCBuilder(path, warning_handler.get()).buildProperties(contents, options);
536+
details::SWCBuilder(path, warning_handler.get(), options).buildProperties(contents);
534537

535538
properties._cellLevel._cellFamily = NEURON;
536539
properties._cellLevel._version = {"swc", 1, 0};

tests/test_1_swc.py

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
strip_color_codes)
1111

1212
from morphio import (MorphioError, Morphology, RawDataError, SomaError,
13-
SomaType, Warning, ostream_redirect, set_raise_warnings)
13+
SomaType, Warning, ostream_redirect, set_raise_warnings, Option)
1414

1515

1616
DATA_DIR = Path(__file__).parent / "data"
@@ -568,13 +568,42 @@ def test_throw_on_negative_id():
568568
Morphology(content, extension='swc')
569569

570570

571+
def test_axon_carrying_dendrite():
572+
contents =('''
573+
1 1 0 0 1 1 -1
574+
2 2 0 0 2 2 1
575+
3 2 0 0 3 3 2
576+
577+
4 3 0 0 4 4 3 # dendrite splits off
578+
5 3 0 0 5 5 4
579+
580+
6 2 0 0 6 6 3 # axon carries on
581+
7 2 0 0 7 7 3
582+
''')
583+
Morphology(contents, "swc")
584+
585+
571586
def test_multi_type_section():
587+
"""
588+
A section within MorphIO is defined as a series of segments between
589+
bifurcation/multifurcation points. However, SWC files allow section
590+
change without a branch. Normal parsing of this will raise an
591+
exception, but can be allowed using the option `UNIFURCATED_SECTION_CHANGE`
592+
"""
572593
contents =('''1 1 0 4 0 3.0 -1
573594
2 6 0 0 2 0.5 1 # <- type 6
574595
3 7 0 0 3 0.5 2 # <- type 7
575596
4 8 0 0 4 0.5 3 # <- type 8
576597
5 9 0 0 5 0.5 4''') # <- type 9
577-
n = Morphology(contents, "swc")
598+
599+
with pytest.raises(RawDataError):
600+
Morphology(contents, "swc")
601+
602+
warnings = morphio.WarningHandlerCollector()
603+
n = Morphology(contents,
604+
"swc",
605+
warning_handler=warnings,
606+
options=Option.allow_unifurcated_section_change)
578607
assert_array_equal(n.soma.points, [[0, 4, 0]])
579608
assert_array_equal(n.soma.diameters, [6.0])
580609
assert len(n.root_sections) == 1
@@ -583,6 +612,10 @@ def test_multi_type_section():
583612
np.array([[0, 0, 2], ]))
584613
assert len(n.sections) == 4
585614
assert_array_equal(n.section_offsets, [0, 1, 3, 5, 7])
615+
warnings = [f.warning for f in warnings.get_all()]
616+
assert len(warnings) == 3 # type 7, 8, and 9
617+
for warning in warnings:
618+
assert warning.warning() == Warning.type_changed_within_section
586619

587620

588621
def test_missing_parent():

tests/test_swc_reader.cpp

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
*
33
* SPDX-License-Identifier: Apache-2.0
44
*/
5+
#include <morphio/enums.h>
56
#include <morphio/exceptions.h>
67
#include <morphio/morphology.h>
78
#include <morphio/section.h>
@@ -112,6 +113,27 @@ TEST_CASE("morphio::swc::errors") {
112113
)";
113114
CHECK_THROWS_AS(Morphology(multiple_soma, "swc"), SomaError);
114115
}
116+
SECTION("multiple_soma") {
117+
const auto* multiple_soma = R"(
118+
1 2 0 0 1 .5 -1
119+
2 1 0 0 1 .5 1
120+
)";
121+
CHECK_THROWS_AS(Morphology(multiple_soma, "swc"), SomaError);
122+
}
123+
124+
SECTION("large_id") {
125+
const auto* multiple_soma = R"(
126+
01234567890123456789 1 0 0 1 .5 -1
127+
)";
128+
CHECK_THROWS_AS(Morphology(multiple_soma, "swc"), RawDataError);
129+
}
130+
131+
SECTION("large_parent_id") {
132+
const auto* multiple_soma = R"(
133+
1 1 0 0 1 .5 01234567890123456789
134+
)";
135+
CHECK_THROWS_AS(Morphology(multiple_soma, "swc"), RawDataError);
136+
}
115137
}
116138

117139
TEST_CASE("morphio::swc::working") {
@@ -130,15 +152,29 @@ TEST_CASE("morphio::swc::working") {
130152
}
131153

132154
SECTION("chimera-axon-on-dendrite") {
133-
const auto* no_soma = R"(
155+
const auto* aod = R"(
134156
1 1 0 0 1 1 -1
135157
2 2 0 0 2 2 1
136158
3 2 0 0 3 3 2
137159
4 3 0 0 4 4 3
138-
5 3 0 0 5 5 4
160+
5 3 0 0 5 5 3
139161
)";
140-
const auto m = Morphology(no_soma, "swc");
141-
REQUIRE(m.sections().size() == 2);
142-
REQUIRE(m.diameters().size() == 5);
162+
const auto m = Morphology(aod, "swc");
163+
REQUIRE(m.sections().size() == 3);
164+
REQUIRE(m.diameters().size() == 6);
165+
}
166+
167+
SECTION("section_type_change") {
168+
const auto* changes = R"(
169+
1 1 0 4 0 3.0 -1
170+
2 6 0 0 2 0.5 1 # <- type 6
171+
3 7 0 0 3 0.5 2 # <- type 7
172+
4 8 0 0 4 0.5 3 # <- type 8
173+
5 9 0 0 5 0.5 4 # <- type 9
174+
)";
175+
CHECK_THROWS_AS(Morphology(changes, "swc"), RawDataError);
176+
const auto m =
177+
Morphology(changes, "swc", morphio::Option::ALLOW_UNIFURCATED_SECTION_CHANGE);
178+
REQUIRE(m.sections().size() == 4);
143179
}
144180
}

0 commit comments

Comments
 (0)