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

Support multiple soma stack in ASC #306

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion doc/source/specification.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <specification-neurolucida>`.

Soma with 4 and more points
***************************
Expand Down
21 changes: 18 additions & 3 deletions doc/source/specification_neurolucida.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <https://github.com/BlueBrain/MorphIO/blob/5e111f3141f7a1ee72e0260111ce569741d80acb/tests/test_neurolucida.py#L58>`_
* 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
----------------
Expand Down
1 change: 1 addition & 0 deletions include/morphio/properties.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
6 changes: 6 additions & 0 deletions src/properties.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <typename T>
bool compare(const std::vector<T>& vec1,
const std::vector<T>& vec2,
Expand Down
30 changes: 27 additions & 3 deletions src/readers/morphologyASC.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,15 @@ bool skip_sexp(size_t id) {
id == +Token::HIGH || id == +Token::INCOMPLETE || id == +Token::LOW ||
id == +Token::NORMAL || id == +Token::FONT);
}

void throw_if_multiple_soma_z(std::vector<Point>& 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) +
" has multiple Z levels between points: " + dumpPoint(points[i]) +
", " + dumpPoint(points[i + 1]));
}
}
} // namespace

class NeurolucidaParser
Expand Down Expand Up @@ -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();
Copy link
Contributor

Choose a reason for hiding this comment

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

I think a new SomaType is required for a stack, so it can be distinguished from a simple contour?

Copy link
Contributor Author

@asanin-epfl asanin-epfl May 11, 2021

Choose a reason for hiding this comment

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

I can't say for sure. This type of soma is still a contour. MorphIO does not calculate surface or volume for contour somas. All other properties are not affected by stack. That hesitates me to introduce a new soma type. What for? MorphIO offers access to points and morphology, that's it. I would actually drop surface and volume properties entirely as MorphIO does not calculate them for all cases.

Copy link
Contributor

Choose a reason for hiding this comment

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

That hesitates me to introduce a new soma type. What for?

For one thing, so that a morph read from ASC can be written back to ASC w/ the same structure.

The other problem that I can forsee is the contour might be really strange: if the orientation of the z slices are rotated 180 to each other, then the 'contour' points would cross through the soma.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok, it makes sense to create a new soma type. I will follow the approach of morphologySWC.cpp but in general the current soma type detection has a problem #314.

Copy link
Contributor

Choose a reason for hiding this comment

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

Hold off for now; I'm not sure there's a clean solution, and I think we need to ask around first.

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);
Expand Down Expand Up @@ -351,6 +374,7 @@ class NeurolucidaParser

private:
ErrorMessages err_;
bool is_multiple_soma_ = false;
};

Property::Properties load(const std::string& uri, unsigned int options) {
Expand Down
77 changes: 60 additions & 17 deletions tests/test_2_neurolucida.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down