From 4296eb96be911fc6b1a04ee88029c4977fa15716 Mon Sep 17 00:00:00 2001 From: Adrien Berchet Date: Thu, 17 Nov 2022 17:23:43 +0100 Subject: [PATCH] WIP: Add options to disable some checks Add soma bifurcation for ASC files Add new options and tests Use proper warnings and fix tests Rename single point section into root bifurcation Add bindings Cleaning Clean tests Apply clang-format Improve readability of enum Option Remove duplicated test Restore add_test in else() block Add option for root point not equal to -1 in SWC files Add Python bindings for custom root id Add tests for conversions Check that converted files can be read --- include/morphio/enums.h | 18 +- include/morphio/errorMessages.h | 13 +- src/errorMessages.cpp | 26 +- src/readers/morphologyASC.cpp | 48 ++-- src/readers/morphologySWC.cpp | 47 +++- tests/CMakeLists.txt | 11 +- tests/data/edge_cases/bad_root_point.swc | 7 + tests/data/edge_cases/root_bifurcation.asc | 26 ++ tests/data/edge_cases/root_bifurcation.h5 | Bin 0 -> 2280 bytes tests/data/edge_cases/root_bifurcation.swc | 10 + tests/data/edge_cases/soma_bifurcation.asc | 22 ++ tests/data/edge_cases/soma_bifurcation.h5 | Bin 0 -> 2304 bytes tests/data/edge_cases/soma_bifurcation.swc | 11 + tests/data/multiple_soma.asc | 32 +++ tests/data/multiple_soma.h5 | Bin 0 -> 2368 bytes tests/data/multiple_soma.swc | 20 +- tests/test_reader_options.cpp | 308 +++++++++++++++++++++ 17 files changed, 550 insertions(+), 49 deletions(-) create mode 100644 tests/data/edge_cases/bad_root_point.swc create mode 100644 tests/data/edge_cases/root_bifurcation.asc create mode 100644 tests/data/edge_cases/root_bifurcation.h5 create mode 100644 tests/data/edge_cases/root_bifurcation.swc create mode 100644 tests/data/edge_cases/soma_bifurcation.asc create mode 100644 tests/data/edge_cases/soma_bifurcation.h5 create mode 100644 tests/data/edge_cases/soma_bifurcation.swc create mode 100644 tests/data/multiple_soma.asc create mode 100644 tests/data/multiple_soma.h5 create mode 100644 tests/test_reader_options.cpp diff --git a/include/morphio/enums.h b/include/morphio/enums.h index 048a8be30..19424cfa8 100644 --- a/include/morphio/enums.h +++ b/include/morphio/enums.h @@ -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 }; /** @@ -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 { diff --git a/include/morphio/errorMessages.h b/include/morphio/errorMessages.h index aaa15dbe9..3b4cdf708 100644 --- a/include/morphio/errorMessages.h +++ b/include/morphio/errorMessages.h @@ -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 */ @@ -244,7 +252,8 @@ class ErrorMessages std::string WARNING_WRONG_DUPLICATE(const std::shared_ptr& current, const std::shared_ptr& parent) const; /** Writing empty section warning message */ - std::string WARNING_APPENDING_EMPTY_SECTION(std::shared_ptr); + std::string WARNING_APPENDING_EMPTY_SECTION( + const std::shared_ptr& section) const; /** Writing single child section warning message */ std::string WARNING_ONLY_CHILD(const DebugInfo& info, unsigned int parentId, @@ -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& children) const; diff --git a/src/errorMessages.cpp b/src/errorMessages.cpp index 569e6bce8..70bbdd2ac 100644 --- a/src/errorMessages.cpp +++ b/src/errorMessages.cpp @@ -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, @@ -307,12 +315,26 @@ std::string ErrorMessages::WARNING_DISCONNECTED_NEURITE(const Sample& sample) co } std::string ErrorMessages::WARNING_APPENDING_EMPTY_SECTION( - std::shared_ptr section) { + const std::shared_ptr& 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& current, const std::shared_ptr& parent) const { @@ -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" diff --git a/src/readers/morphologyASC.cpp b/src/readers/morphologyASC.cpp index fdebb9c4a..33807d25c 100644 --- a/src/readers/morphologyASC.cpp +++ b/src/readers/morphologyASC.cpp @@ -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_; } @@ -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; @@ -130,7 +130,8 @@ class NeurolucidaParser int32_t _create_soma_or_section(const Header& header, std::vector& points, - std::vector& diameters) { + std::vector& diameters, + unsigned int& options) { int32_t return_id = -1; morphio::Property::PointLevel properties; properties._points = points; @@ -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(), + 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); @@ -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 diameters; auto section_id = static_cast(nb_.sections().size()); @@ -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)) { @@ -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; @@ -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())); @@ -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(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); } } @@ -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(); diff --git a/src/readers/morphologySWC.cpp b/src/readers/morphologySWC.cpp index bdc971f1a..0dc90afdd 100644 --- a/src/readers/morphologySWC.cpp +++ b/src/readers/morphologySWC.cpp @@ -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; } @@ -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)); + } } } @@ -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()) { @@ -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); } @@ -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 @@ -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)) { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 4058b597f..ee89e7298 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -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 @@ -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() diff --git a/tests/data/edge_cases/bad_root_point.swc b/tests/data/edge_cases/bad_root_point.swc new file mode 100644 index 000000000..dd6492fed --- /dev/null +++ b/tests/data/edge_cases/bad_root_point.swc @@ -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 diff --git a/tests/data/edge_cases/root_bifurcation.asc b/tests/data/edge_cases/root_bifurcation.asc new file mode 100644 index 000000000..218e9eaff --- /dev/null +++ b/tests/data/edge_cases/root_bifurcation.asc @@ -0,0 +1,26 @@ +; The axon and the basal dendrite have root sections with only one point +("CellBody" + (Color Red) + (CellBody) + (0 0 0 6) + ) + + ((Axon) + (3 4 5 6) +) + + ((Dendrite) + (4 5 6 6) +) + + ((Dendrite) + (5 6 7 6) + (6 6 7 6) + (7 6 7 6) + ( + (8 6 7 6) + | + (9 6 7 6) + (10 6 7 6) + ) +) diff --git a/tests/data/edge_cases/root_bifurcation.h5 b/tests/data/edge_cases/root_bifurcation.h5 new file mode 100644 index 0000000000000000000000000000000000000000..1fe544b63197efc4a1d839b3c5cc99419b520a59 GIT binary patch literal 2280 zcmeHGu};H441Je0=u`=*or+k<$jHb>Dm5q*Aj-gqgcw)=wW-vNjPMs3`3pMo86Eix zu?^J&28E2s|0pFM)= UJ0r}<*%&KwwngS+-dlde@AO|^Y5)KL literal 0 HcmV?d00001 diff --git a/tests/data/edge_cases/root_bifurcation.swc b/tests/data/edge_cases/root_bifurcation.swc new file mode 100644 index 000000000..fe78db337 --- /dev/null +++ b/tests/data/edge_cases/root_bifurcation.swc @@ -0,0 +1,10 @@ +# The axon and the basal dendrite have root sections with only one point +1 1 0 0 0 6 -1 +2 2 3 4 5 6 1 # one-point section +3 3 4 5 6 6 1 # one-point section +4 4 5 6 7 6 1 +5 4 6 6 7 6 4 +6 4 7 6 7 6 5 +7 4 8 6 7 6 6 # one-point section +8 4 9 6 7 6 6 +9 4 10 6 7 6 8 diff --git a/tests/data/edge_cases/soma_bifurcation.asc b/tests/data/edge_cases/soma_bifurcation.asc new file mode 100644 index 000000000..f005887dc --- /dev/null +++ b/tests/data/edge_cases/soma_bifurcation.asc @@ -0,0 +1,22 @@ +; The soma has a bifurcation +("CellBody" + (Color Red) + (CellBody) + (0 0 0 6) + (0 0 1 6) + ( + (0 0 2 6) + (0 0 3 6) + (0 0 4 6) + | + (0 0 5 6) + (0 0 6 6) + (0 0 7 6) + ) + ) + + ((Dendrite) + (0 0 3 6) + (0 1 3 6) + (0 2 3 6) +) diff --git a/tests/data/edge_cases/soma_bifurcation.h5 b/tests/data/edge_cases/soma_bifurcation.h5 new file mode 100644 index 0000000000000000000000000000000000000000..1960a105e0bc2868244b53f447d1755940e1d0e9 GIT binary patch literal 2304 zcmeHGF;2rk5S+6U$wZ1osvsJ!q)bVNBgH5cAfTW`LKHNB5=TmhOZWvPzaS-Lp1@CF z_imRQR~STN39^yY`ZV`A9vyT$+Z}LZ8CuwMR3GmW*zhZ#S_&=%jo^debK;2s zU%-~E_oRPZcXm<~pi1qpD6e7A^6cw$nqA{x-+^&4Jm!Bqmmjr)ui`V2xVyc+DW|}+ zoXjrE+2l$R>d!qb{2>~&oqzp<9lGn@G>Pxnl|Eum#5yJCb7jx&(Ana!=x6BPI}Yle zxqOoNhMAm{)KJ33QYsbZN*ck_!=sTwqQk+a^<(IHoww`j{#*7tU#XvTdt?23UoFK~ zgJ{c}^;I;?S~ju+i+uIjY9FZ6pxQ8>N4ko2)bAcYb#-wtIe?jL-MGM_sUmA=>C!sMDudyysRlR(>YHh<_FT9ynmY zKj2W-`=amX-CQLJC{y*-OBsf3hpM|%cIW^44NQ{Jl+So82aUvkNS{fF=ck9qbO9{V z`SLDZ&hLfL{yf#hA9{n1?O!)>ho0C<6F9{K(UJCqAGfIUIkO)f)7xT;FbFYtu^6Nt zT0RJzU??x8Wr(m=NT#}#f=2xH#pT!_(9I!g)o(-J>#9?q_uts>`bz$M==J*X@B69{ zshM}>&GwbInO)e(4P}L$yBd)Bx4XNP}%#%=L9Vf4|JDMn#X# km93qAOnPkWd2oJS!UbXQAnT22iYbT2u!N_Cxh`9O0XGw1jQ{`u literal 0 HcmV?d00001 diff --git a/tests/data/multiple_soma.swc b/tests/data/multiple_soma.swc index f223cf400..7557fe9de 100644 --- a/tests/data/multiple_soma.swc +++ b/tests/data/multiple_soma.swc @@ -1,11 +1,11 @@ # 2 soma, ids: 1, 10 - 1 1 0 0 0 1. -1 - 2 3 0 0 0 1. 1 - 3 3 0 5 0 1. 2 - 4 3 -5 5 0 1.5 3 - 5 3 6 5 0 1.5 3 - 6 2 0 0 0 1. 1 - 7 2 0 -4 0 1. 6 - 8 2 6 -4 0 2. 7 - 9 2 -5 -4 0 2. 7 - 10 1 0 0 0 1. -1 +1 1 0 0 0 1. -1 +2 3 0 0 0 1. 1 +3 3 0 5 0 1. 2 +4 3 -5 5 0 1. 3 +5 3 6 5 0 1. 3 +6 2 0 0 0 1. 1 +7 2 0 -4 0 1. 6 +8 2 6 -4 0 1. 7 +9 2 -5 -4 0 1. 7 +10 1 1 0 0 1. -1 diff --git a/tests/test_reader_options.cpp b/tests/test_reader_options.cpp new file mode 100644 index 000000000..1a50a4479 --- /dev/null +++ b/tests/test_reader_options.cpp @@ -0,0 +1,308 @@ +#include + +#include + +#include +#include + + +TEST_CASE("root bifurcation", "[loader_edge_cases]") { + std::vector exts{"asc", "h5", "swc"}; + for (auto&& ext : exts) { + SECTION("Testing mutable morphology with " + ext + " extension") { + auto mutable_morph_default_opts = morphio::mut::Morphology( + "data/edge_cases/root_bifurcation." + ext); + + if (ext == "swc") { + // By default, root bifurcations can be not loaded from SWC files + REQUIRE(mutable_morph_default_opts.rootSections().size() == 1); + REQUIRE(mutable_morph_default_opts.rootSections()[0]->points().size() == 3); + } else if (ext == "asc" || ext == "h5") { + // By default, root bifurcations can be loaded from ASC and H5 files + REQUIRE(mutable_morph_default_opts.rootSections().size() == 3); + REQUIRE(mutable_morph_default_opts.rootSections()[0]->points().size() == 1); + REQUIRE(mutable_morph_default_opts.rootSections()[1]->points().size() == 1); + REQUIRE(mutable_morph_default_opts.rootSections()[2]->points().size() == 3); + } else { + throw std::invalid_argument("Unknown extension: " + ext); + } + + auto mutable_morph = + morphio::mut::Morphology("data/edge_cases/root_bifurcation." + ext, + morphio::enums::Option::ALLOW_ROOT_BIFURCATIONS); + + REQUIRE(mutable_morph.rootSections().size() == 3); + REQUIRE(mutable_morph.rootSections()[0]->points().size() == 1); + REQUIRE(mutable_morph.rootSections()[1]->points().size() == 1); + REQUIRE(mutable_morph.rootSections()[2]->points().size() == 3); + } + + SECTION("Testing immutable morphology with " + ext + " extension") { + auto immutable_morph_default_opts = morphio::Morphology( + "data/edge_cases/root_bifurcation." + ext); + + if (ext == "swc") { + // By default, root bifurcations can be not loaded from SWC files + REQUIRE(immutable_morph_default_opts.rootSections().size() == 1); + REQUIRE(immutable_morph_default_opts.rootSections()[0].points().size() == 3); + } else if (ext == "asc" || ext == "h5") { + // By default, root bifurcations can be loaded from ASC and H5 files + REQUIRE(immutable_morph_default_opts.rootSections().size() == 3); + REQUIRE(immutable_morph_default_opts.rootSections()[0].points().size() == 1); + REQUIRE(immutable_morph_default_opts.rootSections()[1].points().size() == 1); + REQUIRE(immutable_morph_default_opts.rootSections()[2].points().size() == 3); + } else { + throw std::invalid_argument("Unknown extension: " + ext); + } + + auto immutable_morph = + morphio::Morphology("data/edge_cases/root_bifurcation." + ext, + morphio::enums::Option::ALLOW_ROOT_BIFURCATIONS); + + REQUIRE(immutable_morph.rootSections().size() == 3); + REQUIRE(immutable_morph.rootSections()[0].points().size() == 1); + REQUIRE(immutable_morph.rootSections()[1].points().size() == 1); + REQUIRE(immutable_morph.rootSections()[2].points().size() == 3); + } + } +} + + +TEST_CASE("soma bifurcation", "[loader_edge_cases]") { + std::vector exts{"asc", "swc"}; + for (auto&& ext : exts) { + SECTION("testing mutable morphology with " + ext + " extension") { + // By default, bifurcations in soma are considered as errors in ASC and SWC files + REQUIRE_THROWS(morphio::mut::Morphology("data/edge_cases/soma_bifurcation." + ext)); + + // Bifurcations in soma can optionaly be loaded from ASC and SWC files + auto mutable_morph = + morphio::mut::Morphology("data/edge_cases/soma_bifurcation." + ext, + morphio::enums::Option::ALLOW_SOMA_BIFURCATIONS); + + REQUIRE(mutable_morph.soma()->points().size() == 8); + REQUIRE(mutable_morph.rootSections().size() == 1); + } + + SECTION("testing immutable morphology with " + ext + " extension") { + // By default, bifurcations in soma are considered as errors in ASC and SWC files + REQUIRE_THROWS(morphio::Morphology("data/edge_cases/soma_bifurcation." + ext)); + + // Bifurcations in soma can optionaly be loaded from ASC and SWC files + auto immutable_morph = + morphio::Morphology("data/edge_cases/soma_bifurcation." + ext, + morphio::enums::Option::ALLOW_SOMA_BIFURCATIONS); + + REQUIRE(immutable_morph.soma().points().size() == 8); + REQUIRE(immutable_morph.rootSections().size() == 1); + } + } + SECTION("testing mutable morphology with h5 extension") { + // Bifurcations in soma are always considered as errors in H5 files + REQUIRE_THROWS(morphio::mut::Morphology("data/soma_bifurcation.h5")); + REQUIRE_THROWS(morphio::mut::Morphology("data/soma_bifurcation.h5", + morphio::enums::Option::ALLOW_SOMA_BIFURCATIONS)); + REQUIRE_THROWS(morphio::Morphology("data/soma_bifurcation.h5")); + REQUIRE_THROWS(morphio::Morphology("data/soma_bifurcation.h5", + morphio::enums::Option::ALLOW_SOMA_BIFURCATIONS)); + } +} + + +TEST_CASE("multiple somata", "[loader_edge_cases]") { + std::vector exts{"asc", "swc"}; + for (auto&& ext : exts) { + SECTION("testing mutable morphology with " + ext + " extension") { + // By default, multiple somata are considered as errors in ASC and SWC files + REQUIRE_THROWS(morphio::mut::Morphology("data/multiple_soma." + ext)); + + // Multiple somata can optionaly be loaded from ASC and SWC files + auto mutable_morph = + morphio::mut::Morphology("data/multiple_soma." + ext, + morphio::enums::Option::ALLOW_MULTIPLE_SOMATA); + + REQUIRE(mutable_morph.soma()->points().size() == 2); + REQUIRE(mutable_morph.rootSections().size() == 2); + } + + SECTION("testing immutable morphology with " + ext + " extension") { + // By default, multiple somata are considered as errors in ASC and SWC files + REQUIRE_THROWS(morphio::Morphology("data/multiple_soma." + ext)); + + // Multiple somata can optionaly be loaded from ASC and SWC files + auto immutable_morph = + morphio::Morphology("data/multiple_soma." + ext, + morphio::enums::Option::ALLOW_MULTIPLE_SOMATA); + + REQUIRE(immutable_morph.soma().points().size() == 2); + REQUIRE(immutable_morph.rootSections().size() == 2); + } + } + SECTION("testing mutable morphology with h5 extension") { + // Multiple somata are always considered as errors in H5 files + REQUIRE_THROWS(morphio::mut::Morphology("data/multiple_soma.h5")); + REQUIRE_THROWS(morphio::mut::Morphology("data/multiple_soma.h5", + morphio::enums::Option::ALLOW_MULTIPLE_SOMATA)); + REQUIRE_THROWS(morphio::Morphology("data/multiple_soma.h5")); + REQUIRE_THROWS(morphio::Morphology("data/multiple_soma.h5", + morphio::enums::Option::ALLOW_MULTIPLE_SOMATA)); + } +} + + +TEST_CASE("bad root point", "[loader_edge_cases]") { + std::vector exts{"swc"}; + for (auto&& ext : exts) { + SECTION("testing mutable morphology with " + ext + " extension") { + // By default, root points not equal to -1 are considered as errors in SWC files + REQUIRE_THROWS(morphio::mut::Morphology("data/edge_cases/bad_root_point." + ext)); + + // Bad root points can optionaly be loaded from SWC files + auto mutable_morph = + morphio::mut::Morphology("data/edge_cases/bad_root_point." + ext, + morphio::enums::Option::ALLOW_CUSTOM_ROOT_ID); + + REQUIRE(mutable_morph.soma()->points().size() == 0); + REQUIRE(mutable_morph.rootSections().size() == 2); + } + } +} + + +TEST_CASE("conversions", "[loader_edge_cases]") { + auto tmpDirectory = std::filesystem::temp_directory_path() / "test_reader_option_conversions"; + std::filesystem::create_directories(tmpDirectory); + + SECTION("testing SWC to other formats") { + SECTION("testing ALLOW_CUSTOM_ROOT_ID option") { + auto mutable_morph = + morphio::mut::Morphology("data/edge_cases/bad_root_point.swc", + morphio::enums::Option::ALLOW_CUSTOM_ROOT_ID); + + mutable_morph.write(tmpDirectory / "from_swc_ALLOW_CUSTOM_ROOT_ID.asc"); + morphio::mut::Morphology(tmpDirectory / "from_swc_ALLOW_CUSTOM_ROOT_ID.asc"); + + mutable_morph.write(tmpDirectory / "from_swc_ALLOW_CUSTOM_ROOT_ID.h5"); + morphio::mut::Morphology(tmpDirectory / "from_swc_ALLOW_CUSTOM_ROOT_ID.h5"); + + mutable_morph.write(tmpDirectory / "from_swc_ALLOW_CUSTOM_ROOT_ID.swc"); + morphio::mut::Morphology(tmpDirectory / "from_swc_ALLOW_CUSTOM_ROOT_ID.swc"); + } + + SECTION("testing ALLOW_MULTIPLE_SOMATA option") { + auto mutable_morph = + morphio::mut::Morphology("data/multiple_soma.swc", + morphio::enums::Option::ALLOW_MULTIPLE_SOMATA); + + mutable_morph.write(tmpDirectory / "from_swc_ALLOW_MULTIPLE_SOMATA.asc"); + morphio::mut::Morphology(tmpDirectory / "from_swc_ALLOW_MULTIPLE_SOMATA.asc"); + + mutable_morph.write(tmpDirectory / "from_swc_ALLOW_MULTIPLE_SOMATA.h5"); + morphio::mut::Morphology(tmpDirectory / "from_swc_ALLOW_MULTIPLE_SOMATA.h5"); + + mutable_morph.write(tmpDirectory / "from_swc_ALLOW_MULTIPLE_SOMATA.swc"); + morphio::mut::Morphology(tmpDirectory / "from_swc_ALLOW_MULTIPLE_SOMATA.swc"); + } + + SECTION("testing ALLOW_SOMA_BIFURCATIONS option") { + auto mutable_morph = + morphio::mut::Morphology("data/edge_cases/soma_bifurcation.swc", + morphio::enums::Option::ALLOW_SOMA_BIFURCATIONS); + + mutable_morph.write(tmpDirectory / "from_swc_ALLOW_SOMA_BIFURCATIONS.asc"); + morphio::mut::Morphology(tmpDirectory / "from_swc_ALLOW_SOMA_BIFURCATIONS.asc"); + + mutable_morph.write(tmpDirectory / "from_swc_ALLOW_SOMA_BIFURCATIONS.h5"); + morphio::mut::Morphology(tmpDirectory / "from_swc_ALLOW_SOMA_BIFURCATIONS.h5"); + + mutable_morph.write(tmpDirectory / "from_swc_ALLOW_SOMA_BIFURCATIONS.swc"); + morphio::mut::Morphology(tmpDirectory / "from_swc_ALLOW_SOMA_BIFURCATIONS.swc"); + } + + SECTION("testing ALLOW_ROOT_BIFURCATIONS option") { + auto mutable_morph = + morphio::mut::Morphology("data/edge_cases/root_bifurcation.swc", + morphio::enums::Option::ALLOW_ROOT_BIFURCATIONS); + REQUIRE_THROWS( + mutable_morph.write(tmpDirectory / "from_swc_ALLOW_ROOT_BIFURCATIONS.asc")); + REQUIRE_THROWS( + mutable_morph.write(tmpDirectory / "from_swc_ALLOW_ROOT_BIFURCATIONS.h5")); + REQUIRE_THROWS( + mutable_morph.write(tmpDirectory / "from_swc_ALLOW_ROOT_BIFURCATIONS.swc")); + } + } + + SECTION("testing ASC to other formats") { + SECTION("testing ALLOW_CUSTOM_ROOT_ID option") { + // Can not be loaded with ASC format + } + + SECTION("testing ALLOW_MULTIPLE_SOMATA option") { + auto mutable_morph = + morphio::mut::Morphology("data/multiple_soma.asc", + morphio::enums::Option::ALLOW_MULTIPLE_SOMATA); + + mutable_morph.write(tmpDirectory / "from_asc_ALLOW_MULTIPLE_SOMATA.asc"); + morphio::mut::Morphology(tmpDirectory / "from_asc_ALLOW_MULTIPLE_SOMATA.asc"); + + mutable_morph.write(tmpDirectory / "from_asc_ALLOW_MULTIPLE_SOMATA.h5"); + morphio::mut::Morphology(tmpDirectory / "from_asc_ALLOW_MULTIPLE_SOMATA.h5"); + + mutable_morph.write(tmpDirectory / "from_asc_ALLOW_MULTIPLE_SOMATA.swc"); + morphio::mut::Morphology(tmpDirectory / "from_asc_ALLOW_MULTIPLE_SOMATA.swc"); + } + + SECTION("testing ALLOW_SOMA_BIFURCATIONS option") { + auto mutable_morph = + morphio::mut::Morphology("data/edge_cases/soma_bifurcation.asc", + morphio::enums::Option::ALLOW_SOMA_BIFURCATIONS); + + mutable_morph.write(tmpDirectory / "from_asc_ALLOW_SOMA_BIFURCATIONS.asc"); + morphio::mut::Morphology(tmpDirectory / "from_asc_ALLOW_SOMA_BIFURCATIONS.asc"); + + mutable_morph.write(tmpDirectory / "from_asc_ALLOW_SOMA_BIFURCATIONS.h5"); + morphio::mut::Morphology(tmpDirectory / "from_asc_ALLOW_SOMA_BIFURCATIONS.h5"); + + mutable_morph.write(tmpDirectory / "from_asc_ALLOW_SOMA_BIFURCATIONS.swc"); + morphio::mut::Morphology(tmpDirectory / "from_asc_ALLOW_SOMA_BIFURCATIONS.swc"); + } + + SECTION("testing ALLOW_ROOT_BIFURCATIONS option") { + auto mutable_morph = + morphio::mut::Morphology("data/edge_cases/root_bifurcation.asc", + morphio::enums::Option::ALLOW_ROOT_BIFURCATIONS); + REQUIRE_THROWS( + mutable_morph.write(tmpDirectory / "from_asc_ALLOW_ROOT_BIFURCATIONS.asc")); + REQUIRE_THROWS( + mutable_morph.write(tmpDirectory / "from_asc_ALLOW_ROOT_BIFURCATIONS.h5")); + REQUIRE_THROWS( + mutable_morph.write(tmpDirectory / "from_asc_ALLOW_ROOT_BIFURCATIONS.swc")); + } + } + + SECTION("testing H5 to other formats") { + SECTION("testing ALLOW_CUSTOM_ROOT_ID option") { + // Can not be loaded with H5 format + } + + SECTION("testing ALLOW_MULTIPLE_SOMATA option") { + // Can not be loaded with H5 format + } + + SECTION("testing ALLOW_SOMA_BIFURCATIONS option") { + // Can not be loaded with H5 format + } + + SECTION("testing ALLOW_ROOT_BIFURCATIONS option") { + auto mutable_morph = + morphio::mut::Morphology("data/edge_cases/root_bifurcation.h5", + morphio::enums::Option::ALLOW_ROOT_BIFURCATIONS); + REQUIRE_THROWS( + mutable_morph.write(tmpDirectory / "from_asc_ALLOW_ROOT_BIFURCATIONS.asc")); + REQUIRE_THROWS( + mutable_morph.write(tmpDirectory / "from_asc_ALLOW_ROOT_BIFURCATIONS.h5")); + REQUIRE_THROWS( + mutable_morph.write(tmpDirectory / "from_asc_ALLOW_ROOT_BIFURCATIONS.swc")); + } + } +}