#pragma once #include <exception> #include <iostream> #include <sstream> #include <type_traits> namespace nestmc { namespace io { static bool starts_with(const std::string &str, const std::string &prefix) { return (str.find(prefix) == 0); } class cell_record { public: // FIXME: enum's are not completely type-safe, since they can accept anything // that can be casted to their underlying type. // // More on SWC files: http://research.mssm.edu/cnic/swc.html enum kind { undefined = 0, soma, axon, dendrite, apical_dendrite, fork_point, end_point, custom }; // cell records assume zero-based indexing; root's parent remains -1 cell_record(kind type, int id, float x, float y, float z, float r, int parent_id) : type_(type) , id_(id) , x_(x) , y_(y) , z_(z) , r_(r) , parent_id_(parent_id) { // Check cell type as well; enum's do not offer complete type safety, // since you can cast anything that fits to its underlying type if (type_ < 0 || type_ > custom) throw std::invalid_argument("unknown cell type"); if (id_ < 0) throw std::invalid_argument("negative ids not allowed"); if (parent_id_ < -1) throw std::invalid_argument("parent_id < -1 not allowed"); if (parent_id_ >= id_) throw std::invalid_argument("parent_id >= id is not allowed"); if (r_ < 0) throw std::invalid_argument("negative radii are not allowed"); } cell_record() : type_(cell_record::undefined) , id_(0) , x_(0) , y_(0) , z_(0) , r_(0) , parent_id_(-1) { } cell_record(const cell_record &other) = default; cell_record &operator=(const cell_record &other) = default; // Equality and comparison operators friend bool operator==(const cell_record &lhs, const cell_record &rhs) { return lhs.id_ == rhs.id_; } friend bool operator<(const cell_record &lhs, const cell_record &rhs) { return lhs.id_ < rhs.id_; } friend bool operator<=(const cell_record &lhs, const cell_record &rhs) { return (lhs < rhs) || (lhs == rhs); } friend bool operator!=(const cell_record &lhs, const cell_record &rhs) { return !(lhs == rhs); } friend bool operator>(const cell_record &lhs, const cell_record &rhs) { return !(lhs < rhs) && (lhs != rhs); } friend bool operator>=(const cell_record &lhs, const cell_record &rhs) { return !(lhs < rhs); } friend std::ostream &operator<<(std::ostream &os, const cell_record &cell); kind type() const { return type_; } int id() const { return id_; } int parent() const { return parent_id_; } float x() const { return x_; } float y() const { return y_; } float z() const { return z_; } float radius() const { return r_; } float diameter() const { return 2*r_; } private: kind type_; // cell type int id_; // cell id float x_, y_, z_; // cell coordinates float r_; // cell radius int parent_id_; // cell parent's id }; class swc_parse_error : public std::runtime_error { public: explicit swc_parse_error(const char *msg) : std::runtime_error(msg) { } explicit swc_parse_error(const std::string &msg) : std::runtime_error(msg) { } }; class swc_parser { public: swc_parser(const std::string &delim, std::string comment_prefix) : delim_(delim) , comment_prefix_(comment_prefix) { } swc_parser() : delim_(" ") , comment_prefix_("#") { } std::istream &parse_record(std::istream &is, cell_record &cell) { while (!is.eof() && !is.bad()) { // consume empty and comment lines first std::getline(is, linebuff_); if (!linebuff_.empty() && !starts_with(linebuff_, comment_prefix_)) break; } if (is.bad()) // let the caller check for such events return is; if (is.eof() && (linebuff_.empty() || starts_with(linebuff_, comment_prefix_))) // last line is either empty or a comment; don't parse anything return is; if (is.fail()) throw swc_parse_error("too long line detected"); std::istringstream line(linebuff_); cell = parse_record(line); return is; } private: void check_parse_status(const std::istream &is) { if (is.fail()) // If we try to read past the eof; fail bit will also be set throw swc_parse_error("could not parse value"); } template<typename T> T parse_value_strict(std::istream &is) { T val; check_parse_status(is >> val); // everything's fine return val; } // Read the record from a string stream; will be treated like a single line cell_record parse_record(std::istringstream &is); std::string delim_; std::string comment_prefix_; std::string linebuff_; }; // specialize parsing for cell types template<> cell_record::kind swc_parser::parse_value_strict(std::istream &is) { int val; check_parse_status(is >> val); // Let cell_record's constructor check for the type validity return static_cast<cell_record::kind>(val); } cell_record swc_parser::parse_record(std::istringstream &is) { auto id = parse_value_strict<int>(is); auto type = parse_value_strict<cell_record::kind>(is); auto x = parse_value_strict<float>(is); auto y = parse_value_strict<float>(is); auto z = parse_value_strict<float>(is); auto r = parse_value_strict<float>(is); auto parent_id = parse_value_strict<int>(is); // Convert to zero-based, leaving parent_id as-is if -1 if (parent_id != -1) parent_id--; return cell_record(type, id-1, x, y, z, r, parent_id); } std::istream &operator>>(std::istream &is, cell_record &cell) { swc_parser parser; parser.parse_record(is, cell); return is; } std::ostream &operator<<(std::ostream &os, const cell_record &cell) { // output in one-based indexing os << cell.id_+1 << " " << cell.type_ << " " << std::setprecision(7) << cell.x_ << " " << std::setprecision(7) << cell.y_ << " " << std::setprecision(7) << cell.z_ << " " << std::setprecision(7) << cell.r_ << " " << ((cell.parent_id_ == -1) ? cell.parent_id_ : cell.parent_id_+1); return os; } } // end of nestmc::io } // end of nestmc