diff --git a/data/ball_and_stick.swc b/data/ball_and_stick.swc index 1737539b1b4d73d202cecab2ecb7e5599d93064e..298398678a91ec8141dd60ce77796dc54155d3f3 100644 --- a/data/ball_and_stick.swc +++ b/data/ball_and_stick.swc @@ -3,6 +3,5 @@ # - dendrite with length 200 and radius 0.5 1 1 0.0 0.0 0.0 6.30785 -1 -2 2 6.30785 0.0 0.0 0.5 1 -3 2 206.30785 0.0 0.0 0.5 2 - +2 3 6.30785 0.0 0.0 0.5 1 +3 3 206.30785 0.0 0.0 0.5 2 diff --git a/src/profiling/profiler.cpp b/src/profiling/profiler.cpp index 247c8c949958e46c2285d0c9c79341c4699a32bb..b5d2ad0d8d7bd9efe7fd8e55e5f6720ab051b3f1 100644 --- a/src/profiling/profiler.cpp +++ b/src/profiling/profiler.cpp @@ -1,3 +1,5 @@ +#include <numeric> + #include "profiler.hpp" #include "util/debug.hpp" @@ -373,4 +375,3 @@ void profiler_output(double threshold) {} } // namespace util } // namespace mc } // namespace nest - diff --git a/src/swcio.cpp b/src/swcio.cpp index 05cb61170221f3fc715ddef7c8271606c1f359d9..3f38f8e791308819ac2fe3eeecde4c78ab585799 100644 --- a/src/swcio.cpp +++ b/src/swcio.cpp @@ -18,7 +18,7 @@ namespace io { // // swc_record implementation // -void swc_record::renumber(id_type new_id, std::map<id_type, id_type> &idmap) +void swc_record::renumber(id_type new_id, std::map<id_type, id_type>& idmap) { auto old_id = id_; id_ = new_id; @@ -59,7 +59,7 @@ void swc_record::check_consistency() const } } -std::istream &operator>>(std::istream &is, swc_record &record) +std::istream& operator>>(std::istream& is, swc_record& record) { swc_parser parser; parser.parse_record(is, record); @@ -67,7 +67,7 @@ std::istream &operator>>(std::istream &is, swc_record &record) } -std::ostream &operator<<(std::ostream &os, const swc_record &record) +std::ostream& operator<<(std::ostream& os, const swc_record& record) { // output in one-based indexing os << record.id_+1 << " " @@ -86,12 +86,28 @@ std::ostream &operator<<(std::ostream &os, const swc_record &record) // Utility functions // -bool starts_with(const std::string &str, const std::string &prefix) +std::string::size_type find_first_non_whitespace(const std::string& str) { - return (str.find(prefix) == 0); + return str.find_first_not_of(" \f\n\r\t\v"); } -void check_parse_status(const std::istream &is, const swc_parser &parser) +bool starts_with(const std::string& str, const std::string& prefix) +{ + // ignore leading whitespace + auto pos = find_first_non_whitespace(str); + if (pos == std::string::npos) { + return false; + } + + return str.find(prefix, pos) == pos; +} + +bool is_space(const std::string& str) +{ + return find_first_non_whitespace(str) == std::string::npos; +} + +void check_parse_status(const std::istream& is, const swc_parser& parser) { if (is.fail()) { // If we try to read past the eof; fail bit will also be set @@ -99,8 +115,23 @@ void check_parse_status(const std::istream &is, const swc_parser &parser) } } +nest::mc::segmentKind convert_kind(const swc_record::kind& kind) +{ + switch (kind) { + case swc_record::kind::soma: + return segmentKind::soma; + case swc_record::kind::dendrite: + return segmentKind::dendrite; + case swc_record::kind::axon: + return segmentKind::axon; + default: + throw swc_parse_error("no known conversion for swc record type", 0); + } +} + + template<typename T> -T parse_value_strict(std::istream &is, const swc_parser &parser) +T parse_value_strict(std::istream& is, const swc_parser& parser) { T val; check_parse_status(is >> val, parser); @@ -111,7 +142,7 @@ T parse_value_strict(std::istream &is, const swc_parser &parser) // specialize parsing for record types template<> -swc_record::kind parse_value_strict(std::istream &is, const swc_parser &parser) +swc_record::kind parse_value_strict(std::istream& is, const swc_parser& parser) { swc_record::id_type val; check_parse_status(is >> val, parser); @@ -124,17 +155,15 @@ swc_record::kind parse_value_strict(std::istream &is, const swc_parser &parser) // swc_parser implementation // -std::istream &swc_parser::parse_record(std::istream &is, swc_record &record) +std::istream& swc_parser::parse_record(std::istream& is, swc_record& record) { while (!is.eof() && !is.bad()) { // consume empty and comment lines first std::getline(is, linebuff_); ++lineno_; - if (!linebuff_.empty() && - !starts_with(linebuff_, comment_prefix_) && - !starts_with(linebuff_, "\r")) - { + if (!is_space(linebuff_) && + !starts_with(linebuff_, comment_prefix_)) { break; } } @@ -145,7 +174,7 @@ std::istream &swc_parser::parse_record(std::istream &is, swc_record &record) } if (is.eof() && - (linebuff_.empty() || starts_with(linebuff_, comment_prefix_))) { + (is_space(linebuff_) || starts_with(linebuff_, comment_prefix_))) { // last line is either empty or a comment; don't parse anything return is; } @@ -157,7 +186,7 @@ std::istream &swc_parser::parse_record(std::istream &is, swc_record &record) std::istringstream line(linebuff_); try { record = parse_record(line); - } catch (std::invalid_argument &e) { + } catch (std::invalid_argument& e) { // Rethrow as a parse error throw swc_parse_error(e.what(), lineno_); } @@ -165,7 +194,7 @@ std::istream &swc_parser::parse_record(std::istream &is, swc_record &record) return is; } -swc_record swc_parser::parse_record(std::istringstream &is) +swc_record swc_parser::parse_record(std::istringstream& is) { auto id = parse_value_strict<int>(is, *this); auto type = parse_value_strict<swc_record::kind>(is, *this); @@ -184,7 +213,7 @@ swc_record swc_parser::parse_record(std::istringstream &is) } -swc_record_range_clean::swc_record_range_clean(std::istream &is) +swc_record_range_clean::swc_record_range_clean(std::istream& is) { std::unordered_set<swc_record::id_type> ids; @@ -218,7 +247,7 @@ swc_record_range_clean::swc_record_range_clean(std::istream &is) // Renumber records if necessary std::map<swc_record::id_type, swc_record::id_type> idmap; swc_record::id_type next_id = 0; - for (auto &r : records_) { + for (auto& r : records_) { if (r.id() != next_id) { r.renumber(next_id, idmap); } @@ -237,14 +266,14 @@ swc_record_range_clean::swc_record_range_clean(std::istream &is) } } -cell swc_read_cell(std::istream &is) +cell swc_read_cell(std::istream& is) { using namespace nest::mc; cell newcell; std::vector<swc_record::id_type> parent_index; std::vector<swc_record> swc_records; - for (const auto &r : swc_get_records<swc_io_clean>(is)) { + for (const auto& r : swc_get_records<swc_io_clean>(is)) { swc_records.push_back(r); parent_index.push_back(r.parent()); } @@ -290,7 +319,7 @@ cell swc_read_cell(std::istream &is) // add the new cable newcell.add_cable(new_parent_index[i], - nest::mc::segmentKind::dendrite, radii, points); + convert_kind(b_start->type()), radii, points); } return newcell; diff --git a/src/swcio.hpp b/src/swcio.hpp index 4b6457123e062c65a2091853bc3e581e3c1eb8e7..6530cba35190049d58be20dcebdcf9d81570053e 100644 --- a/src/swcio.hpp +++ b/src/swcio.hpp @@ -13,8 +13,7 @@ namespace nest { namespace mc { namespace io { -class swc_record -{ +class swc_record { public: using id_type = int; using coord_type = double; @@ -56,10 +55,10 @@ public: , parent_id_(-1) { } - swc_record(const swc_record &other) = default; - swc_record &operator=(const swc_record &other) = default; + swc_record(const swc_record& other) = default; + swc_record& operator=(const swc_record& other) = default; - bool strict_equals(const swc_record &other) const + bool strict_equals(const swc_record& other) const { return id_ == other.id_ && x_ == other.x_ && @@ -70,43 +69,43 @@ public: } // Equality and comparison operators - friend bool operator==(const swc_record &lhs, - const swc_record &rhs) + friend bool operator==(const swc_record& lhs, + const swc_record& rhs) { return lhs.id_ == rhs.id_; } - friend bool operator<(const swc_record &lhs, - const swc_record &rhs) + friend bool operator<(const swc_record& lhs, + const swc_record& rhs) { return lhs.id_ < rhs.id_; } - friend bool operator<=(const swc_record &lhs, - const swc_record &rhs) + friend bool operator<=(const swc_record& lhs, + const swc_record& rhs) { return (lhs < rhs) || (lhs == rhs); } - friend bool operator!=(const swc_record &lhs, - const swc_record &rhs) + friend bool operator!=(const swc_record& lhs, + const swc_record& rhs) { return !(lhs == rhs); } - friend bool operator>(const swc_record &lhs, - const swc_record &rhs) + friend bool operator>(const swc_record& lhs, + const swc_record& rhs) { return !(lhs < rhs) && (lhs != rhs); } - friend bool operator>=(const swc_record &lhs, - const swc_record &rhs) + friend bool operator>=(const swc_record& lhs, + const swc_record& rhs) { return !(lhs < rhs); } - friend std::ostream &operator<<(std::ostream &os, const swc_record &record); + friend std::ostream& operator<<(std::ostream& os, const swc_record& record); kind type() const { @@ -153,7 +152,7 @@ public: return nest::mc::point<coord_type>(x_, y_, z_); } - void renumber(id_type new_id, std::map<id_type, id_type> &idmap); + void renumber(id_type new_id, std::map<id_type, id_type>& idmap); private: void check_consistency() const; @@ -169,12 +168,12 @@ private: class swc_parse_error : public std::runtime_error { public: - explicit swc_parse_error(const char *msg, std::size_t lineno) + explicit swc_parse_error(const char* msg, std::size_t lineno) : std::runtime_error(msg) , lineno_(lineno) { } - explicit swc_parse_error(const std::string &msg, std::size_t lineno) + explicit swc_parse_error(const std::string& msg, std::size_t lineno) : std::runtime_error(msg) , lineno_(lineno) { } @@ -188,11 +187,9 @@ private: std::size_t lineno_; }; -class swc_parser -{ +class swc_parser { public: - swc_parser(const std::string &delim, - std::string comment_prefix) + swc_parser(const std::string& delim, std::string comment_prefix) : delim_(delim) , comment_prefix_(comment_prefix) , lineno_(0) @@ -209,12 +206,11 @@ public: return lineno_; } - std::istream &parse_record(std::istream &is, swc_record &record); + std::istream& parse_record(std::istream& is, swc_record& record); private: - // Read the record from a string stream; will be treated like a single line - swc_record parse_record(std::istringstream &is); + swc_record parse_record(std::istringstream& is); std::string delim_; std::string comment_prefix_; @@ -223,15 +219,14 @@ private: }; -std::istream &operator>>(std::istream &is, swc_record &record); +std::istream& operator>>(std::istream& is, swc_record& record); class swc_record_stream_iterator : - public std::iterator<std::forward_iterator_tag, swc_record> -{ + public std::iterator<std::forward_iterator_tag, swc_record> { public: struct eof_tag { }; - swc_record_stream_iterator(std::istream &is) + swc_record_stream_iterator(std::istream& is) : is_(is) , eof_(false) { @@ -240,19 +235,19 @@ public: read_next_record(); } - swc_record_stream_iterator(std::istream &is, eof_tag) + swc_record_stream_iterator(std::istream& is, eof_tag) : is_(is) , eof_(true) { } - swc_record_stream_iterator(const swc_record_stream_iterator &other) + swc_record_stream_iterator(const swc_record_stream_iterator& other) : is_(other.is_) , parser_(other.parser_) , curr_record_(other.curr_record_) , eof_(other.eof_) { } - swc_record_stream_iterator &operator++() + swc_record_stream_iterator& operator++() { if (eof_) { throw std::out_of_range("attempt to read past eof"); @@ -278,7 +273,7 @@ public: return curr_record_; } - bool operator==(const swc_record_stream_iterator &other) const + bool operator==(const swc_record_stream_iterator& other) const { if (eof_ && other.eof_) { return true; @@ -287,13 +282,13 @@ public: } } - bool operator!=(const swc_record_stream_iterator &other) const + bool operator!=(const swc_record_stream_iterator& other) const { return !(*this == other); } - friend std::ostream &operator<<(std::ostream &os, - const swc_record_stream_iterator &iter) + friend std::ostream& operator<<(std::ostream& os, + const swc_record_stream_iterator& iter) { os << "{ is_.tellg(): " << iter.is_.tellg() << ", " << "curr_record_: " << iter.curr_record_ << ", " @@ -311,7 +306,7 @@ private: } } - std::istream &is_; + std::istream& is_; swc_parser parser_; swc_record curr_record_; @@ -321,16 +316,15 @@ private: }; -class swc_record_range_raw -{ +class swc_record_range_raw { public: using value_type = swc_record; - using reference = value_type &; - using const_reference = const value_type &; + using reference = value_type&; + using const_reference = const value_type&; using iterator = swc_record_stream_iterator; using const_iterator = const swc_record_stream_iterator; - swc_record_range_raw(std::istream &is) + swc_record_range_raw(std::istream& is) : is_(is) { } @@ -351,7 +345,7 @@ public: } private: - std::istream &is_; + std::istream& is_; }; // @@ -362,16 +356,15 @@ private: // https://github.com/eth-cscs/cell_algorithms/wiki/SWC-file-parsing // -class swc_record_range_clean -{ +class swc_record_range_clean { public: using value_type = swc_record; - using reference = value_type &; - using const_referene = const value_type &; + using reference = value_type&; + using const_referene = const value_type&; using iterator = std::vector<swc_record>::iterator; using const_iterator = std::vector<swc_record>::const_iterator; - swc_record_range_clean(std::istream &is); + swc_record_range_clean(std::istream& is); iterator begin() { @@ -408,12 +401,12 @@ struct swc_io_clean }; template<typename T = swc_io_clean> -typename T::record_range_type swc_get_records(std::istream &is) +typename T::record_range_type swc_get_records(std::istream& is) { return typename T::record_range_type(is); } -cell swc_read_cell(std::istream &is); +cell swc_read_cell(std::istream& is); } // namespace io } // namespace mc diff --git a/tests/test_swcio.cpp b/tests/test_swcio.cpp index 0ebe9c84b06ff7c22db289ebcb79c21eb50239d4..32eaff75b967ccf9c5f8b7ef7f06c8b3d8b10f3e 100644 --- a/tests/test_swcio.cpp +++ b/tests/test_swcio.cpp @@ -13,12 +13,12 @@ // Path to data directory can be overriden at compile time. #if !defined(DATADIR) -#define DATADIR "../data" +# define DATADIR "../data" #endif // SWC tests -void expect_record_equals(const nest::mc::io::swc_record &expected, - const nest::mc::io::swc_record &actual) +void expect_record_equals(const nest::mc::io::swc_record& expected, + const nest::mc::io::swc_record& actual) { EXPECT_EQ(expected.id(), actual.id()); EXPECT_EQ(expected.type(), actual.type()); @@ -159,7 +159,7 @@ TEST(swc_parser, invalid_input) } FAIL() << "expected swc_parse_error, none was thrown\n"; - } catch (const swc_parse_error &e) { + } catch (const swc_parse_error& e) { SUCCEED(); } } @@ -187,6 +187,58 @@ TEST(swc_parser, valid_input) expect_record_equals(record_orig, record); } + { + // check comment not starting at first character + swc_record record, record_orig; + std::istringstream is(" #comment"); + EXPECT_NO_THROW(is >> record); + expect_record_equals(record_orig, record); + } + + { + // check whitespace lines + swc_record record; + std::stringstream is; + is << "#comment\n"; + is << " \t\n"; + is << "1 1 14.566132 34.873772 7.857000 0.717830 -1\n"; + + EXPECT_NO_THROW(is >> record); + swc_record record_expected( + swc_record::kind::soma, + 0, 14.566132, 34.873772, 7.857000, 0.717830, -1); + + expect_record_equals(record_expected, record); + } + + { + // check windows eol + swc_record record; + std::stringstream is; + is << "#comment\r\n"; + is << "\r\n"; + is << "1 1 14.566132 34.873772 7.857000 0.717830 -1\r\n"; + + EXPECT_NO_THROW(is >> record); + swc_record record_expected( + swc_record::kind::soma, + 0, 14.566132, 34.873772, 7.857000, 0.717830, -1); + + expect_record_equals(record_expected, record); + } + + { + // check old-style mac eol; these eol are treated as simple whitespace + // characters, so in the following case no parse error shall be thrown + // and no record shall be read + swc_record record, record_expected; + std::stringstream is; + is << "#comment\r"; + is << "1 1 14.566132 34.873772 7.857000 0.717830 -1\r"; + + EXPECT_NO_THROW(is >> record); + expect_record_equals(record_expected, record); + } { // check last line case (no newline at the end) @@ -233,9 +285,9 @@ TEST(swc_parser, from_allen_db) using namespace nest::mc; std::string datadir{DATADIR}; - auto fname = datadir+"/example.swc"; + auto fname = datadir + "/example.swc"; std::ifstream fid(fname); - if(!fid.is_open()) { + if (!fid.is_open()) { std::cerr << "unable to open file " << fname << "... skipping test\n"; return; } @@ -400,7 +452,7 @@ TEST(swc_record_ranges, raw) } ADD_FAILURE() << "expected an exception\n"; - } catch (const swc_parse_error &e) { + } catch (const swc_parse_error& e) { EXPECT_EQ(3u, e.lineno()); } } @@ -432,11 +484,11 @@ TEST(swc_io, cell_construction) std::stringstream is; is << "1 1 0 0 0 2.1 -1\n"; - is << "2 2 0.1 1.2 1.2 1.3 1\n"; - is << "3 2 1.0 2.0 2.2 1.1 2\n"; - is << "4 2 1.5 3.3 1.3 2.2 3\n"; - is << "5 2 2.5 5.3 2.5 0.7 3\n"; - is << "6 2 3.5 2.3 3.7 3.4 5\n"; + is << "2 3 0.1 1.2 1.2 1.3 1\n"; + is << "3 3 1.0 2.0 2.2 1.1 2\n"; + is << "4 3 1.5 3.3 1.3 2.2 3\n"; + is << "5 3 2.5 5.3 2.5 0.7 3\n"; + is << "6 3 3.5 2.3 3.7 3.4 5\n"; using point_type = point<double>; std::vector<point_type> points = { @@ -500,9 +552,9 @@ TEST(swc_io, cell_construction) TEST(swc_parser, from_file_ball_and_stick) { std::string datadir{DATADIR}; - auto fname = datadir+"/ball_and_stick.swc"; + auto fname = datadir + "/ball_and_stick.swc"; std::ifstream fid(fname); - if(!fid.is_open()) { + if (!fid.is_open()) { std::cerr << "unable to open file " << fname << "... skipping test\n"; return; } @@ -521,35 +573,3 @@ TEST(swc_parser, from_file_ball_and_stick) EXPECT_TRUE(nest::mc::cell_basic_equality(local_cell, cell)); } - -// check that windows EOL are supported in linux. -// This test is based on the ball_and_stick.swc with windows endings inserted -// manually in a file stream, regression test for issue_34 -TEST(swc_parser, windows_eol) -{ - - // Check valid usage - std::stringstream is; - is << "# ball and stick model with\r\n"; - is << "# - soma with radius 12.6157 2\r\n"; - is << "# - dendrite with length 200 and radius 0.5\r\n"; - is << "\r\n"; // parser stubles over empty line with \r\n - is << "1 1 0.0 0.0 0.0 6.30785 -1\r\n"; - is << "2 2 6.30785 0.0 0.0 0.5 1\r\n"; - is << "3 2 206.30785 0.0 0.0 0.5 2\r\n"; - is << "\n"; - - // read the file into a cell object - auto cell = nest::mc::io::swc_read_cell(is); - - // verify that the correct number of nodes was read - EXPECT_EQ(cell.num_segments(), 2); - EXPECT_EQ(cell.num_compartments(), 2u); - - // make an equivalent cell via C++ interface - nest::mc::cell local_cell; - local_cell.add_soma(6.30785); - local_cell.add_cable(0, nest::mc::segmentKind::dendrite, 0.5, 0.5, 200); - - EXPECT_TRUE(nest::mc::cell_basic_equality(local_cell, cell)); -}