From fadc82f13092eb64457b08bd15263bc0619467c8 Mon Sep 17 00:00:00 2001 From: aleksei sanin Date: Thu, 6 May 2021 15:02:25 +0200 Subject: [PATCH 1/5] Support multiple soma stack in ASC --- include/morphio/properties.h | 1 + src/properties.cpp | 6 +++ src/readers/morphologyASC.cpp | 30 ++++++++++++-- tests/test_2_neurolucida.py | 77 +++++++++++++++++++++++++++-------- 4 files changed, 94 insertions(+), 20 deletions(-) diff --git a/include/morphio/properties.h b/include/morphio/properties.h index a25c19797..f4efe5062 100644 --- a/include/morphio/properties.h +++ b/include/morphio/properties.h @@ -75,6 +75,7 @@ struct PointLevel { PointLevel(const PointLevel& data); PointLevel(const PointLevel& data, SectionRange range); PointLevel& operator=(const PointLevel& other); + void append(const PointLevel& other); }; struct SectionLevel { diff --git a/src/properties.cpp b/src/properties.cpp index 8d688c263..7d9bccb56 100644 --- a/src/properties.cpp +++ b/src/properties.cpp @@ -47,6 +47,12 @@ PointLevel& PointLevel::operator=(const PointLevel& other) { return *this; } +void PointLevel::append(const PointLevel& other) { + _appendVector(this->_points, other._points, 0); + _appendVector(this->_diameters, other._diameters, 0); + _appendVector(this->_perimeters, other._perimeters, 0); +} + template bool compare(const std::vector& vec1, const std::vector& vec2, diff --git a/src/readers/morphologyASC.cpp b/src/readers/morphologyASC.cpp index 624a04377..2e3fd50fe 100644 --- a/src/readers/morphologyASC.cpp +++ b/src/readers/morphologyASC.cpp @@ -51,6 +51,15 @@ bool skip_sexp(size_t id) { } } // namespace +bool throw_if_multiple_soma_z(std::vector& points, unsigned int line) { + for (size_t i = 0; i < points.size() - 1; ++i) { + if (points[i][2] != points[i + 1][2]) + throw RawDataError("Stack of multiple soma on line " + std::to_string(line) + + " has multiple Z levels between points: " + dumpPoint(points[i]) + + ", " + dumpPoint(points[i + 1])); + } +} + class NeurolucidaParser { public: @@ -141,9 +150,23 @@ class NeurolucidaParser nb_.addMarker(marker); return_id = -1; } else if (header.token == Token::CELLBODY) { - if (!nb_.soma()->points().empty()) - throw SomaError(err_.ERROR_SOMA_ALREADY_DEFINED(lex_.line_num())); - nb_.soma()->properties() = properties; + const auto& soma = nb_.soma(); + if (!soma->points().empty()) { + if (!is_multiple_soma_) { + is_multiple_soma_ = true; + throw_if_multiple_soma_z(soma->points(), 1); + } + throw_if_multiple_soma_z(properties._points, lex_.line_num()); + for (const Point& point : soma->points()) { + if (point[2] == properties._points[0][2]) + throw RawDataError( + "Stack of multiple soma on line " + std::to_string(lex_.line_num()) + + " has the same Z level as a previous point: " + dumpPoint(point)); + } + soma->properties().append(properties); + } else { + soma->properties() = properties; + } return_id = -1; } else { SectionType section_type = TokenSectionTypeMap.at(header.token); @@ -351,6 +374,7 @@ class NeurolucidaParser private: ErrorMessages err_; + bool is_multiple_soma_ = false; }; Property::Properties load(const std::string& uri, unsigned int options) { diff --git a/tests/test_2_neurolucida.py b/tests/test_2_neurolucida.py index 11832553e..da5f876e8 100644 --- a/tests/test_2_neurolucida.py +++ b/tests/test_2_neurolucida.py @@ -102,24 +102,67 @@ def test_unfinished_point(): ':4:error') -def test_multiple_soma(): +def test_multiple_soma_stack(): _test_asc_exception(''' - ("CellBody" - (Color Red) - (CellBody) - (1 1 0 1 S1) - (-1 1 0 1 S2) - ) - - ("CellBody" - (Color Red) - (CellBody) - (1 1 0 1 S1) - (-1 1 0 1 S2) - )''', - SomaError, - 'A soma is already defined', - ':14:error') + ( + (CellBody) + ( 5.55 5.05 1.0 2.35) + ( 2.86 5.72 1.0 2.35) + ) + ( + (CellBody) + ( 0.84 1.52 2.0 2.35) + ( -4.54 2.36 2.0 2.35) + ( -6.55 0.17 10.0 2.35) ; Wrong Z + ) + ''', + RawDataError, + 'Stack of multiple soma on line 12 has multiple Z levels between points', + '-4.54 2.36 2, -6.55 0.17 10') + + _test_asc_exception(''' + ( + (CellBody) + ( 5.88 0.84 1.0 2.35) + ( 6.05 2.53 1.0 2.35) + ( 6.39 4.38 1.0 2.35) + ) + ( + (CellBody) + ( 1.85 0.67 1.0 2.35) + ( 0.84 1.52 1.0 2.35) + ( -4.54 2.36 1.0 2.35) + ) + ''', + RawDataError, + 'Stack of multiple soma on line 13 has the same Z level as a previous point', + '5.88 0.84 1', + ) + + with tmp_asc_file(''' + ( + (CellBody) + ( 5.88 0.84 1.0 2.35) + ( 6.05 2.53 1.0 2.35) + ( 6.39 4.38 1.0 2.35) + ) + ( + (CellBody) + ( 1.85 0.67 2.0 2.35) + ( 0.84 1.52 2.0 2.35) + ( -4.54 2.36 2.0 2.35) + ) + ''') as tmp_file: + m = Morphology(tmp_file.name) + assert_array_equal(m.soma.points, + np.array([[5.88, 0.84, 1.], + [6.05, 2.53, 1.], + [6.39, 4.38, 1.], + [1.85, 0.67, 2.], + [0.84, 1.52, 2.], + [-4.54, 2.36, 2.]], + dtype=np.float32)) + assert_array_equal(m.soma.diameters, np.array([2.35] * 6, dtype=np.float32)) def test_single_neurite_no_soma(): From 0a8c04ea28a096df8968d715e593a2bf78c74738 Mon Sep 17 00:00:00 2001 From: aleksei sanin Date: Thu, 6 May 2021 15:06:16 +0200 Subject: [PATCH 2/5] make CI happy --- src/readers/morphologyASC.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/readers/morphologyASC.cpp b/src/readers/morphologyASC.cpp index 2e3fd50fe..e45db1841 100644 --- a/src/readers/morphologyASC.cpp +++ b/src/readers/morphologyASC.cpp @@ -51,7 +51,7 @@ bool skip_sexp(size_t id) { } } // namespace -bool throw_if_multiple_soma_z(std::vector& points, unsigned int line) { +bool throw_if_multiple_soma_z(std::vector& points, long unsigned int line) { for (size_t i = 0; i < points.size() - 1; ++i) { if (points[i][2] != points[i + 1][2]) throw RawDataError("Stack of multiple soma on line " + std::to_string(line) + From e147060d369e5323a923d09af37bf5ddd8bdda6a Mon Sep 17 00:00:00 2001 From: aleksei sanin Date: Thu, 6 May 2021 15:08:56 +0200 Subject: [PATCH 3/5] fix return type of throw_if_multiple_soma_z --- src/readers/morphologyASC.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/readers/morphologyASC.cpp b/src/readers/morphologyASC.cpp index e45db1841..ed19db0c5 100644 --- a/src/readers/morphologyASC.cpp +++ b/src/readers/morphologyASC.cpp @@ -51,7 +51,7 @@ bool skip_sexp(size_t id) { } } // namespace -bool throw_if_multiple_soma_z(std::vector& points, long unsigned int line) { +void throw_if_multiple_soma_z(std::vector& points, long unsigned int line) { for (size_t i = 0; i < points.size() - 1; ++i) { if (points[i][2] != points[i + 1][2]) throw RawDataError("Stack of multiple soma on line " + std::to_string(line) + From f874861fd0d5a6fcb3faa4f72ec4ec1bf44a3840 Mon Sep 17 00:00:00 2001 From: aleksei sanin Date: Thu, 6 May 2021 15:14:22 +0200 Subject: [PATCH 4/5] move throw_if_multiple_soma_z into namespace --- src/readers/morphologyASC.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/readers/morphologyASC.cpp b/src/readers/morphologyASC.cpp index ed19db0c5..fbfc4fdbd 100644 --- a/src/readers/morphologyASC.cpp +++ b/src/readers/morphologyASC.cpp @@ -49,7 +49,6 @@ bool skip_sexp(size_t id) { id == +Token::HIGH || id == +Token::INCOMPLETE || id == +Token::LOW || id == +Token::NORMAL || id == +Token::FONT); } -} // namespace void throw_if_multiple_soma_z(std::vector& points, long unsigned int line) { for (size_t i = 0; i < points.size() - 1; ++i) { @@ -59,6 +58,7 @@ void throw_if_multiple_soma_z(std::vector& points, long unsigned int line ", " + dumpPoint(points[i + 1])); } } +} // namespace class NeurolucidaParser { From ee494c94f9bb41d1ec0b533bebfc844bf9a0291d Mon Sep 17 00:00:00 2001 From: aleksei sanin Date: Fri, 7 May 2021 11:11:20 +0200 Subject: [PATCH 5/5] describe multiple ASC soma in docs --- doc/source/specification.rst | 2 +- doc/source/specification_neurolucida.rst | 21 ++++++++++++++++++--- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/doc/source/specification.rst b/doc/source/specification.rst index 34df91839..b22a71cb9 100644 --- a/doc/source/specification.rst +++ b/doc/source/specification.rst @@ -177,7 +177,7 @@ The only allowed bifurcation point in Soma is its root point. An error is thrown Multiple Soma ************* -Multiple somas are not supported +Multiple somas are supported only for ASC. See :ref:`its specification `. Soma with 4 and more points *************************** diff --git a/doc/source/specification_neurolucida.rst b/doc/source/specification_neurolucida.rst index 30eca4b70..662b64da2 100644 --- a/doc/source/specification_neurolucida.rst +++ b/doc/source/specification_neurolucida.rst @@ -10,9 +10,24 @@ Soma ---- * Files without a soma are valid. -* Multiple soma - ASC files with *multiple* CellBody tags will raise an error. - `Unit test `_ +* Multiple soma is supported via multiple CellBody S-exps, with each of them being the soma contour at a given Z + altitude. A single CellBody in this definition is called a soma stack. All Z coordinates within a soma stack must + be the same. Z coordinates among different soma stacks must be different. An example. + + .. code-block:: lisp + + ( + (CellBody) ; <- first soma stack + ( 5.88 0.84 1.0 2.35) + ( 6.05 2.53 1.0 2.35) + ( 6.39 4.38 1.0 2.35) + ) + ( + (CellBody) ; <- second soma stack + ( 1.85 0.67 2.0 1.35) + ( 0.84 1.52 2.0 1.35) + ( -4.54 2.36 2.0 1.35) + ) Duplicate points ----------------