Skip to content
This repository was archived by the owner on Feb 26, 2025. It is now read-only.
Closed
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
3 changes: 1 addition & 2 deletions include/morphio/errorMessages.h
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,7 @@ class ErrorMessages {
////////////////////////////////////////////////////////////////////////////////
// NEUROLUCIDA
////////////////////////////////////////////////////////////////////////////////
const std::string ERROR_SOMA_ALREADY_DEFINED(int lineNumber) const;

const std::string ERROR_MULTIPLE_SOMA_STACKS_WITH_SAME_Z(int line1, int line2, float z) const;

const std::string ERROR_PARSING_POINT(int lineNumber,
const std::string& point) const;
Expand Down
12 changes: 9 additions & 3 deletions src/errorMessages.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -143,9 +143,15 @@ const std::string _col(float val1, float val2) {
////////////////////////////////////////////////////////////////////////////////
// NEUROLUCIDA
////////////////////////////////////////////////////////////////////////////////
const std::string ErrorMessages::ERROR_SOMA_ALREADY_DEFINED(int lineNumber) const {
return errorMsg(lineNumber, ErrorLevel::ERROR,
"A soma is already defined");
const std::string ErrorMessages::ERROR_MULTIPLE_SOMA_STACKS_WITH_SAME_Z(int line1, int line2, float z) const {
std::string msg = errorMsg(line1, ErrorLevel::ERROR,
"\nThere is already a soma stack for Z == "+std::to_string(z) + " at line:");
msg += errorMsg(line2, ErrorLevel::INFO, "");
msg += "\nNote: Multiple blocks of type CellBody means the soma is represented as a soma stack.\n"
"(More info on soma stacks: https://www.neuron.yale.edu/phpBB/viewtopic.php?t=3833)\n"
"In this case, each CellBody block is the soma contour for a given Z position\n"
" and each block must have a different Z value";
return msg;
}


Expand Down
52 changes: 46 additions & 6 deletions src/plugin/morphologyASC.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
#include <fstream>
#include <unordered_map>
#include <unordered_set>

#include <morphio/mut/morphology.h>
#include <morphio/mut/section.h>
Expand Down Expand Up @@ -71,12 +73,44 @@ class NeurolucidaParser

parse_block();

create_soma();

return nb_;

}


void create_soma() {
if(somaStacks.size() == 1) {
nb_.soma() -> properties() = somaStacks[0];
} else if(somaStacks.size() > 1) {
_raiseIfEachStackHasMultipleZ();
for (const auto& stack: somaStacks) {
morphio::Property::_appendVector(nb_.soma() -> properties()._points, stack._points, 0);
morphio::Property::_appendVector(nb_.soma() -> properties()._diameters, stack._diameters, 0);
}
}
}

private:

void _raiseIfEachStackHasMultipleZ() {
for (const auto& stack: somaStacks) {
std::unordered_set<float> level;
for(const Point& point: stack._points) {
level.insert(point[2]);
}

if (level.size() > 1) {
std::string msg("Soma stack has multiple Z levels:\n");
for (float z: level)
msg += " * " + std::to_string(z) + "\n";

throw RawDataError(msg);
}
}
}

std::tuple<Point, float> parse_point(NeurolucidaLexer& lex)
{
lex.expect(Token::LPAREN, "Point should start in LPAREN");
Expand Down Expand Up @@ -125,16 +159,19 @@ class NeurolucidaParser
int32_t _create_soma_or_section(Token token, int32_t parent_id,
std::vector<Point> &points, std::vector<float> &diameters)
{
lex_.current_section_start_ = lex_.line_num();
int32_t return_id;
morphio::Property::PointLevel properties;
properties._points = points;
properties._diameters = diameters;
if(token == Token::CELLBODY){
if(nb_.soma()->points().size() != 0)
throw SomaError(err_.ERROR_SOMA_ALREADY_DEFINED(lex_.line_num()));
nb_.soma() -> properties() = properties;

float z = points[0][2];
if (soma_z_levels.count(z))
throw RawDataError(err_.ERROR_MULTIPLE_SOMA_STACKS_WITH_SAME_Z(lex_.current_section_start_,
soma_z_levels[z],
z));
soma_z_levels[z] = lex_.current_section_start_;

somaStacks.push_back(properties);
return_id = -1;
} else {
SectionType section_type = TokenSectionTypeMap.at(token);
Expand Down Expand Up @@ -199,6 +236,8 @@ class NeurolucidaParser
Points points;
std::vector<float> diameters;
uint32_t section_id = nb_.sections().size();
lex_.current_section_start_ = lex_.line_num();


while (true)
{
Expand Down Expand Up @@ -297,7 +336,8 @@ class NeurolucidaParser
DebugInfo debugInfo_;
private:
ErrorMessages err_;

std::vector<morphio::Property::PointLevel> somaStacks;
std::unordered_map<float, uint32_t> soma_z_levels;
};

Property::Properties load(const URI& uri, unsigned int options)
Expand Down
91 changes: 71 additions & 20 deletions tests/test_2_neurolucida.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,26 +55,6 @@ def test_unfinished_point():
':4:error')


def test_multiple_soma():
_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')


def test_single_neurite_no_soma():
with tmp_asc_file('''
( (Color Yellow)
Expand Down Expand Up @@ -516,3 +496,74 @@ def test_markers():
[-268.17, -130.62, -24.75],
[-266.79, -131.77, -26.13]],
dtype=np.float32))


def test_multiple_soma_stack():
_test_asc_exception('''
(
(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,
"Soma stack has multiple Z levels:",
''' * 10.000000
* 2.000000'''
)

_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,
':11:error',
'There is already a soma stack for Z == 1.000000 at line:',
':4:info'
)

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))
21 changes: 14 additions & 7 deletions tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,17 +66,24 @@ def assert_string_equal(str1, str2):
str1.join(sep), str2.join(sep)))


def _test_exception(content, exception, str1, str2, extension):
'''Create tempfile with given content and check that the exception is raised'''
with _tmp_file(content, extension) as tmp_file:
def _test_swc_exception(content, exception, *messages):
'''Create tempfile with given content, check that the exception is raised
and that messages are part of the error message'''
with _tmp_file(content, 'swc') as tmp_file:
with assert_raises(exception) as obj:
Morphology(tmp_file.name)
assert_substring(str1, str(obj.exception))
assert_substring(str2, str(obj.exception))
for msg in messages:
assert_substring(msg, str(obj.exception))

def _test_asc_exception(content, exception, *messages):
'''Create tempfile with given content, check that the exception is raised
and that messages are part of the error message'''
with _tmp_file(content, 'asc') as tmp_file:
with assert_raises(exception) as obj:
Morphology(tmp_file.name)
for msg in messages:
assert_substring(msg, str(obj.exception))

_test_asc_exception = partial(_test_exception, extension='asc')
_test_swc_exception = partial(_test_exception, extension='swc')


@contextmanager
Expand Down