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

Add more options for ASC and SWC loaders #431

Open
wants to merge 1 commit 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
18 changes: 13 additions & 5 deletions include/morphio/enums.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,15 @@ enum LogLevel { ERROR, WARNING, INFO, DEBUG };
/** The list of modifier flags that can be passed when loading a morphology
* See morphio::mut::modifiers for more information **/
enum Option {
NO_MODIFIER = 0x00, //!< Read morphology as is without any modification
TWO_POINTS_SECTIONS = 0x01, //!< Read sections only with 2 or more points
SOMA_SPHERE = 0x02, //!< Interpret morphology soma as a sphere
NO_DUPLICATES = 0x04, //!< Skip duplicating points
NRN_ORDER = 0x08 //!< Order of neurites will be the same as in NEURON simulator
NO_MODIFIER = 0, //!< Read morphology as is without any modification
TWO_POINTS_SECTIONS = 1 << 0, //!< Keep only the first and last points of sections
SOMA_SPHERE = 1 << 1, //!< Interpret morphology soma as a sphere
NO_DUPLICATES = 1 << 2, //!< Skip duplicating points
NRN_ORDER = 1 << 3, //!< Order of neurites will be the same as in NEURON simulator
ALLOW_ROOT_BIFURCATIONS = 1 << 4, //!< Bifurcations at first point are allowed
ALLOW_SOMA_BIFURCATIONS = 1 << 5, //!< Bifurcations in soma are allowed
ALLOW_MULTIPLE_SOMATA = 1 << 6, //!< Multiple somata are allowed
ALLOW_CUSTOM_ROOT_ID = 1 << 7 //!< Custom root points are allowed
};

/**
Expand All @@ -41,6 +45,10 @@ enum Warning {
ZERO_DIAMETER, //!< Zero section diameter
SOMA_NON_CONTOUR, //!< Soma must be a contour for ASC and H5
SOMA_NON_CYLINDER_OR_POINT, //!< Soma must be stacked cylinders or a point
ROOT_BIFURCATION, //!< Bifurcation at root point
SOMA_BIFURCATION, //!< Bifurcation in soma
MULTIPLE_SOMATA, //!< Multiple somata
CUSTOM_ROOT_ID, //!< Custom root ID
};

enum AnnotationType {
Expand Down
13 changes: 11 additions & 2 deletions include/morphio/errorMessages.h
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,14 @@ class ErrorMessages
std::string WARNING_MITOCHONDRIA_WRITE_NOT_SUPPORTED() const;
/** Writing without soma warning message */
std::string WARNING_WRITE_NO_SOMA() const;
/** Loading soma with a bifurcation */
std::string WARNING_SOMA_BIFURCATION() const;
/** Loading multiple somata */
std::string WARNING_MULTIPLE_SOMATA() const;
/** Loading point with root ID no equal to -1 */
std::string WARNING_CUSTOM_ROOT_ID(const Sample& sample) const;
/** Loading section with only one point */
std::string WARNING_ROOT_BIFURCATION(const Sample& sample) const;
/** Writing empty morphology warning message */
std::string WARNING_WRITE_EMPTY_MORPHOLOGY() const;
/** Soma not found warning message */
Expand All @@ -244,7 +252,8 @@ class ErrorMessages
std::string WARNING_WRONG_DUPLICATE(const std::shared_ptr<morphio::mut::Section>& current,
const std::shared_ptr<morphio::mut::Section>& parent) const;
/** Writing empty section warning message */
std::string WARNING_APPENDING_EMPTY_SECTION(std::shared_ptr<morphio::mut::Section>);
std::string WARNING_APPENDING_EMPTY_SECTION(
const std::shared_ptr<morphio::mut::Section>& section) const;
/** Writing single child section warning message */
std::string WARNING_ONLY_CHILD(const DebugInfo& info,
unsigned int parentId,
Expand All @@ -253,7 +262,7 @@ class ErrorMessages
/** Soma does not conform NeuroMorpho warning message */
std::string WARNING_NEUROMORPHO_SOMA_NON_CONFORM(const Sample& root,
const Sample& child1,
const Sample& child2);
const Sample& child2) const;

/** Wrong root point warning message */
std::string WARNING_WRONG_ROOT_POINT(const std::vector<Sample>& children) const;
Expand Down
26 changes: 24 additions & 2 deletions src/errorMessages.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,14 @@ std::string ErrorMessages::WARNING_WRITE_NO_SOMA() const {
return errorMsg(0, ErrorLevel::WARNING, "Warning: writing file without a soma");
}

std::string ErrorMessages::WARNING_SOMA_BIFURCATION() const {
return errorMsg(0, ErrorLevel::WARNING, "Warning: found soma bifurcation");
}

std::string ErrorMessages::WARNING_MULTIPLE_SOMATA() const {
return errorMsg(0, ErrorLevel::WARNING, "Warning: found multiple somata");
}

std::string ErrorMessages::WARNING_WRITE_EMPTY_MORPHOLOGY() const {
return errorMsg(0,
ErrorLevel::WARNING,
Expand All @@ -307,12 +315,26 @@ std::string ErrorMessages::WARNING_DISCONNECTED_NEURITE(const Sample& sample) co
}

std::string ErrorMessages::WARNING_APPENDING_EMPTY_SECTION(
std::shared_ptr<morphio::mut::Section> section) {
const std::shared_ptr<morphio::mut::Section>& section) const {
return errorMsg(0,
ErrorLevel::WARNING,
"Warning: appending empty section with id: " + std::to_string(section->id()));
}

std::string ErrorMessages::WARNING_CUSTOM_ROOT_ID(const Sample& sample) const {
return errorMsg(sample.lineNumber,
ErrorLevel::WARNING,
"Warning: root id changed from " + std::to_string(sample.parentId) +
" to -1 for point with id: " + std::to_string(sample.id));
}

std::string ErrorMessages::WARNING_ROOT_BIFURCATION(const Sample& sample) const {
return errorMsg(sample.lineNumber,
ErrorLevel::WARNING,
"Warning: root bifurcation kept for point with id: " +
std::to_string(sample.id));
}

std::string ErrorMessages::WARNING_WRONG_DUPLICATE(
const std::shared_ptr<morphio::mut::Section>& current,
const std::shared_ptr<morphio::mut::Section>& parent) const {
Expand Down Expand Up @@ -374,7 +396,7 @@ std::string ErrorMessages::WARNING_ONLY_CHILD(const DebugInfo& info,

std::string ErrorMessages::WARNING_NEUROMORPHO_SOMA_NON_CONFORM(const Sample& root,
const Sample& child1,
const Sample& child2) {
const Sample& child2) const {
floatType x = root.point[0], y = root.point[1], z = root.point[2], r = root.diameter / 2;
std::stringstream ss;
ss << "Warning: the soma does not conform the three point soma spec\n"
Expand Down
48 changes: 32 additions & 16 deletions src/readers/morphologyASC.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,9 @@ class NeurolucidaParser
NeurolucidaParser(NeurolucidaParser const&) = delete;
NeurolucidaParser& operator=(NeurolucidaParser const&) = delete;

morphio::mut::Morphology& parse(const std::string& input) {
morphio::mut::Morphology& parse(const std::string& input, unsigned int& options) {
lex_.start_parse(input);
parse_root_sexps();
parse_root_sexps(options);
return nb_;
}

Expand Down Expand Up @@ -112,12 +112,12 @@ class NeurolucidaParser
return {{point[0], point[1], point[2]}, point[3]};
}

bool parse_neurite_branch(Header& header) {
bool parse_neurite_branch(Header& header, unsigned int& options) {
lex_.consume(Token::LPAREN, "New branch should start with LPAREN");

bool ret = true;
while (true) {
ret &= parse_neurite_section(header);
ret &= parse_neurite_section(header, options);
if (lex_.ended() ||
(lex_.current()->id != +Token::PIPE && lex_.current()->id != +Token::LPAREN)) {
break;
Expand All @@ -130,7 +130,8 @@ class NeurolucidaParser

int32_t _create_soma_or_section(const Header& header,
std::vector<Point>& points,
std::vector<morphio::floatType>& diameters) {
std::vector<morphio::floatType>& diameters,
unsigned int& options) {
int32_t return_id = -1;
morphio::Property::PointLevel properties;
properties._points = points;
Expand All @@ -144,9 +145,24 @@ 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;
if (!nb_.soma()->points().empty()) {
if (options & ALLOW_SOMA_BIFURCATIONS) {
printError(Warning::SOMA_BIFURCATION, err_.WARNING_SOMA_BIFURCATION());
} else if (options & ALLOW_MULTIPLE_SOMATA) {
printError(Warning::MULTIPLE_SOMATA, err_.WARNING_MULTIPLE_SOMATA());
} else {
throw SomaError(err_.ERROR_SOMA_ALREADY_DEFINED(lex_.line_num()));
}
nb_.soma()->properties()._points.insert(nb_.soma()->properties()._points.end(),
matz-e marked this conversation as resolved.
Show resolved Hide resolved
properties._points.begin(),
properties._points.end());
nb_.soma()->properties()._diameters.insert(
nb_.soma()->properties()._diameters.end(),
properties._diameters.begin(),
properties._diameters.end());
} else {
nb_.soma()->properties() = properties;
}
return_id = -1;
} else {
SectionType section_type = TokenToSectionType(header.token);
Expand Down Expand Up @@ -273,7 +289,7 @@ class NeurolucidaParser
}


bool parse_neurite_section(const Header& header) {
bool parse_neurite_section(const Header& header, unsigned int& options) {
Points points;
std::vector<morphio::floatType> diameters;
auto section_id = static_cast<int>(nb_.sections().size());
Expand All @@ -286,7 +302,7 @@ class NeurolucidaParser
throw RawDataError(err_.ERROR_EOF_IN_NEURITE(lex_.line_num()));
} else if (is_end_of_section(id)) {
if (!points.empty()) {
_create_soma_or_section(header, points, diameters);
_create_soma_or_section(header, points, diameters, options);
}
return true;
} else if (is_end_of_branch(id)) {
Expand Down Expand Up @@ -320,7 +336,7 @@ class NeurolucidaParser
marker_header.token = Token::STRING;
marker_header.label = lex_.peek()->str();
lex_.consume_until(Token::LPAREN);
parse_neurite_section(marker_header);
parse_neurite_section(marker_header, options);
lex_.consume(Token::RPAREN, "Marker should end with RPAREN");
} else if (peek_id == +Token::NUMBER) {
Point point;
Expand All @@ -330,11 +346,11 @@ class NeurolucidaParser
diameters.push_back(radius);
} else if (peek_id == +Token::LPAREN) {
if (!points.empty()) {
section_id = _create_soma_or_section(header, points, diameters);
section_id = _create_soma_or_section(header, points, diameters, options);
}
Header child_header = header;
child_header.parent_id = section_id;
parse_neurite_branch(child_header);
parse_neurite_branch(child_header, options);
} else {
throw RawDataError(
err_.ERROR_UNKNOWN_TOKEN(lex_.line_num(), lex_.peek()->str()));
Expand All @@ -347,14 +363,14 @@ class NeurolucidaParser
}
}

void parse_root_sexps() {
void parse_root_sexps(unsigned int& options) {
// parse the top level blocks, and if they are a neurite, otherwise skip
while (!lex_.ended()) {
if (static_cast<Token>(lex_.current()->id) == Token::LPAREN) {
lex_.consume();
const Header header = parse_root_sexp_header();
if (lex_.current()->id != +Token::RPAREN) {
parse_neurite_section(header);
parse_neurite_section(header, options);
}
}

Expand All @@ -378,7 +394,7 @@ Property::Properties load(const std::string& path,
unsigned int options) {
NeurolucidaParser parser(path);

morphio::mut::Morphology& nb_ = parser.parse(contents);
morphio::mut::Morphology& nb_ = parser.parse(contents, options);
nb_.applyModifiers(options);

Property::Properties properties = nb_.buildReadOnly();
Expand Down
47 changes: 38 additions & 9 deletions src/readers/morphologySWC.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ class SWCBuilder
return somata;
}

void raiseIfBrokenSoma(const Sample& sample) {
void raiseIfBrokenSoma(const Sample& sample, const unsigned int& options) {
if (sample.type != SECTION_SOMA) {
return;
}
Expand All @@ -149,7 +149,11 @@ class SWCBuilder
}

if (soma_bifurcations.size() > 1) {
throw morphio::SomaError(err.ERROR_SOMA_BIFURCATION(sample, soma_bifurcations));
if (options & ALLOW_SOMA_BIFURCATIONS) {
printError(Warning::SOMA_BIFURCATION, err.WARNING_SOMA_BIFURCATION());
} else {
throw morphio::SomaError(err.ERROR_SOMA_BIFURCATION(sample, soma_bifurcations));
}
}
}

Expand All @@ -171,11 +175,15 @@ class SWCBuilder
}
}

void checkSoma() {
void checkSoma(const unsigned int& options) {
auto somata = _potentialSomata();

if (somata.size() > 1) {
throw morphio::SomaError(err.ERROR_MULTIPLE_SOMATA(somata));
if (options & ALLOW_MULTIPLE_SOMATA) {
printError(Warning::MULTIPLE_SOMATA, err.WARNING_MULTIPLE_SOMATA());
} else {
throw morphio::SomaError(err.ERROR_MULTIPLE_SOMATA(somata));
}
}

if (somata.empty()) {
Expand Down Expand Up @@ -241,9 +249,9 @@ class SWCBuilder
return ret;
}

void raiseIfNonConform(const Sample& sample) {
void raiseIfNonConform(const Sample& sample, const unsigned int& options) {
raiseIfSelfParent(sample);
raiseIfBrokenSoma(sample);
raiseIfBrokenSoma(sample, options);
raiseIfNoParent(sample);
warnIfZeroDiameter(sample);
}
Expand Down Expand Up @@ -327,12 +335,29 @@ class SWCBuilder
Property::Properties buildProperties(const std::string& contents, unsigned int options) {
_readSamples(contents);

if (options & ALLOW_CUSTOM_ROOT_ID) {
for (auto& sample_pair : samples) {
auto& sample = sample_pair.second;
if (sample.parentId != SWC_ROOT && samples.count(sample.parentId) == 0) {
printError(Warning::CUSTOM_ROOT_ID, err.WARNING_CUSTOM_ROOT_ID(sample));
// Remove the sample from the children
children[sample.parentId].erase(std::remove(children[sample.parentId].begin(),
children[sample.parentId].end(),
sample.id),
children[sample.parentId].end());
// Update the sample and add it to the proper children
sample.parentId = SWC_ROOT;
children[SWC_ROOT].push_back(sample.id);
}
}
}

for (const auto& sample_pair : samples) {
const auto& sample = sample_pair.second;
raiseIfNonConform(sample);
raiseIfNonConform(sample, options);
}

checkSoma();
checkSoma(options);

// The process might occasionally creates empty section before
// filling them so the warning is ignored
Expand All @@ -345,7 +370,11 @@ class SWCBuilder

// Bifurcation right at the start
if (isRootPoint(sample) && isSectionEnd(sample)) {
continue;
if (options & ALLOW_ROOT_BIFURCATIONS) {
printError(Warning::ROOT_BIFURCATION, err.WARNING_ROOT_BIFURCATION(sample));
} else {
continue;
}
}

if (isSectionStart(sample)) {
Expand Down
11 changes: 6 additions & 5 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ set(TESTS_SRC
test_mutable_morphology.cpp
test_point_utils.cpp
test_properties.cpp
test_reader_options.cpp
test_soma.cpp
test_swc_reader.cpp
test_utilities.cpp
Expand Down Expand Up @@ -53,13 +54,13 @@ target_link_libraries(unittests
${TESTS_LINK_LIBRAIRIES}
)

add_test(NAME unittests
COMMAND unittests
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
)

if (NOT EXTERNAL_CATCH2)
catch_discover_tests( unittests
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
)
else()
add_test(NAME unittests
COMMAND unittests
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
)
endif()
7 changes: 7 additions & 0 deletions tests/data/edge_cases/bad_root_point.swc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# The root point is not -1
1 2 0 0 0 6 0
2 2 0 0 1 6 1
3 2 0 0 2 6 2
4 2 1 0 0 6 0
5 2 1 0 1 6 1
6 2 1 0 2 6 2
Loading