diff --git a/binds/python/bind_immutable.cpp b/binds/python/bind_immutable.cpp index 4b0ec5bb8..c6dcf36e3 100644 --- a/binds/python/bind_immutable.cpp +++ b/binds/python/bind_immutable.cpp @@ -124,9 +124,6 @@ void bind_immutable_module(py::module& m) { "Return the graph connectivity of the morphology " "where each section is seen as a node\nNote: -1 is the soma node") .def_property_readonly("soma_type", &morphio::Morphology::somaType, "Returns the soma type") - // .def_property_readonly("cell_family", - // &morphio::Morphology::cellFamily, - // "Returns the cell family (neuron or glia)") .def_property_readonly("version", &morphio::Morphology::version, "Returns the version") // Iterators @@ -252,9 +249,6 @@ void bind_immutable_module(py::module& m) { "Return the graph connectivity of the GlialCell " "where each section is seen as a node\nNote: -1 is the soma node") .def_property_readonly("soma_type", &morphio::GlialCell::somaType, "Returns the soma type") - // .def_property_readonly("cell_family", - // &morphio::GlialCell::cellFamily, - // "Returns the cell family (neuron or glia)") .def_property_readonly("version", &morphio::GlialCell::version, "Returns the version") // Iterators diff --git a/binds/python/bind_misc.cpp b/binds/python/bind_misc.cpp index 7e488e841..e3909de58 100644 --- a/binds/python/bind_misc.cpp +++ b/binds/python/bind_misc.cpp @@ -200,9 +200,6 @@ void bind_misc(py::module& m) { "CellLevel", "Container class for information available at the " "cell level (cell type, file version, soma type)") - // .def_readwrite("cell_family", - // &morphio::Property::CellLevel::_cellFamily, - // "Returns the cell family (neuron or glia)") .def_readwrite("soma_type", &morphio::Property::CellLevel::_somaType, "Returns the soma type") diff --git a/binds/python/bind_mutable.cpp b/binds/python/bind_mutable.cpp index efbdc6255..78263e155 100644 --- a/binds/python/bind_mutable.cpp +++ b/binds/python/bind_mutable.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include @@ -42,6 +43,7 @@ void bind_mutable_module(py::module& m) { "Additional Ctor that accepts as filename any python " "object that implements __repr__ or __str__") + // Cell sub-part accessors .def_property_readonly("sections", &morphio::mut::Morphology::sections, @@ -122,10 +124,6 @@ void bind_mutable_module(py::module& m) { "Return the graph connectivity of the morphology " "where each section is seen as a node\nNote: -1 is the soma node") - // .def_property_readonly("cell_family", - // &morphio::mut::Morphology::cellFamily, - // "Returns the cell family (neuron or glia)") - .def_property_readonly("soma_type", &morphio::mut::Morphology::somaType, "Returns the soma type") @@ -178,6 +176,161 @@ void bind_mutable_module(py::module& m) { "If recursive == true, all descendent will be appended as well", "mutable_section"_a, "recursive"_a = false); + + py::class_(m, "GlialCell") + .def(py::init<>()) + .def(py::init(), + "filename"_a, + "options"_a = morphio::enums::Option::NO_MODIFIER) + .def(py::init(), + "morphology"_a, + "options"_a = morphio::enums::Option::NO_MODIFIER) + .def(py::init(), + "morphology"_a, + "options"_a = morphio::enums::Option::NO_MODIFIER) + .def(py::init([](py::object arg, unsigned int options) { + return std::unique_ptr( + new morphio::mut::GlialCell(py::str(arg), options)); + }), + "filename"_a, + "options"_a = morphio::enums::Option::NO_MODIFIER, + "Additional Ctor that accepts as filename any python " + "object that implements __repr__ or __str__") + + + // Cell sub-part accessors + .def_property_readonly("sections", + &morphio::mut::GlialCell::sections, + "Returns a list containing IDs of all sections. " + "The first section of the vector is the soma section") + .def_property_readonly("root_sections", + &morphio::mut::GlialCell::rootSections, + "Returns a list of all root sections IDs " + "(sections whose parent ID are -1)", + py::return_value_policy::reference) + .def_property_readonly( + "soma", + static_cast& (morphio::mut::GlialCell::*) ()>( + &morphio::mut::GlialCell::soma), + "Returns a reference to the soma object\n\n" + "Note: multiple morphologies can share the same Soma " + "instance") + .def_property_readonly( + "mitochondria", + static_cast( + &morphio::mut::GlialCell::mitochondria), + "Returns a reference to the mitochondria container class") + .def_property_readonly( + "endoplasmic_reticulum", + static_cast( + &morphio::mut::GlialCell::endoplasmicReticulum), + "Returns a reference to the endoplasmic reticulum container class") + .def_property_readonly("annotations", + &morphio::mut::GlialCell::annotations, + "Returns a list of annotations") + .def_property_readonly("markers", + &morphio::mut::GlialCell::markers, + "Returns the list of NeuroLucida markers") + .def("section", + &morphio::mut::GlialCell::section, + "Returns the section with the given id\n\n" + "Note: multiple morphologies can share the same Section " + "instances", + "section_id"_a) + .def("build_read_only", + &morphio::mut::GlialCell::buildReadOnly, + "Returns the data structure used to create read-only " + "morphologies") + .def("append_root_section", + static_cast (morphio::mut::GlialCell::*)( + const morphio::Property::PointLevel&, morphio::GlialSectionType)>( + &morphio::mut::GlialCell::appendRootSection), + "Append a root Section\n", + "point_level_properties"_a, + "section_type"_a) + .def("append_root_section", + static_cast (morphio::mut::GlialCell::*)( + const morphio::GlialSection&, bool)>(&morphio::mut::GlialCell::appendRootSection), + "Append the existing immutable Section as a root section\n" + "If recursive == true, all descendent will be appended as " + "well", + "immutable_section"_a, + "recursive"_a = false) + + .def("delete_section", + &morphio::mut::GlialCell::deleteSection, + "Delete the given section\n" + "\n" + "Will silently fail if the section is not part of the " + "tree\n" + "\n" + "If recursive == true, all descendent sections will be " + "deleted as well\n" + "Else, children will be re-attached to their grand-parent", + "section"_a, + "recursive"_a = true) + + .def("as_immutable", + [](const morphio::mut::GlialCell* morph) { return morphio::GlialCell(*morph); }) + + .def_property_readonly("connectivity", + &morphio::mut::GlialCell::connectivity, + "Return the graph connectivity of the morphology " + "where each section is seen as a node\nNote: -1 is the soma node") + + .def_property_readonly("soma_type", + &morphio::mut::GlialCell::somaType, + "Returns the soma type") + + .def_property_readonly("version", &morphio::mut::GlialCell::version, "Returns the version") + + .def("sanitize", + static_cast( + &morphio::mut::GlialCell::sanitize), + "Fixes the morphology single child sections and issues warnings" + "if the section starts and ends are inconsistent") + + .def( + "write", + [](morphio::mut::GlialCell* morph, py::object arg) { morph->write(py::str(arg)); }, + "Write file to H5, SWC, ASC format depending on filename " + "extension", + "filename"_a) + + // Iterators + .def( + "iter", + [](morphio::mut::GlialCell* morph, IterType type) { + switch (type) { + case IterType::DEPTH_FIRST: + return py::make_iterator(morph->depth_begin(), morph->depth_end()); + case IterType::BREADTH_FIRST: + return py::make_iterator(morph->breadth_begin(), morph->breadth_end()); + case IterType::UPSTREAM: + default: + throw morphio::MorphioError("Only iteration types depth_first and " + "breadth_first are supported"); + } + }, + py::keep_alive<0, 1>() /* Essential: keep object alive + while iterator exists */ + , + "Section iterator that runs successively on every " + "neurite\n" + "iter_type controls the order of iteration on sections of " + "a given neurite. 2 values can be passed:\n" + "- morphio.IterType.depth_first (default)\n" + "- morphio.IterType.breadth_first", + "iter_type"_a = IterType::DEPTH_FIRST) + .def("append_root_section", + static_cast ( + morphio::mut::GlialCell::*)(const std::shared_ptr&, bool)>( + &morphio::mut::GlialCell::appendRootSection), + "Append the existing mutable Section as a root section\n" + "If recursive == true, all descendent will be appended as well", + "mutable_section"_a, + "recursive"_a = false); + py::class_(m, "Mitochondria") .def(py::init<>()) @@ -476,6 +629,118 @@ void bind_mutable_module(py::module& m) { "center", [](morphio::mut::Soma* soma) { return py::array(3, soma->center().data()); }, "Returns the center of gravity of the soma points"); + + py::class_>(m, "GlialSection") + .def("__str__", + [](const morphio::mut::GlialSection& section) { + std::stringstream ss; + ss << section; + return ss.str(); + }) + + .def_property_readonly("id", &morphio::mut::GlialSection::id, "Return the section ID") + .def_property( + "type", + static_cast( + &morphio::mut::GlialSection::type), + [](morphio::mut::GlialSection* section, morphio::GlialSectionType _type) { + section->type() = _type; + }, + "Returns the morphological type of this section " + "(dendrite, axon, ...)") + .def_property( + "points", + [](morphio::mut::GlialSection* section) { + return py::array(static_cast(section->points().size()), + section->points().data()); + }, + [](morphio::mut::GlialSection* section, py::array_t _points) { + section->points() = array_to_points(_points); + }, + "Returns the coordinates (x,y,z) of all points of this section") + .def_property( + "diameters", + [](morphio::mut::GlialSection* section) { + return py::array(static_cast(section->diameters().size()), + section->diameters().data()); + }, + [](morphio::mut::GlialSection* section, py::array_t _diameters) { + section->diameters() = _diameters.cast>(); + }, + "Returns the diameters of all points of this section") + .def_property( + "perimeters", + [](morphio::mut::GlialSection* section) { + return py::array(static_cast(section->perimeters().size()), + section->perimeters().data()); + }, + [](morphio::mut::GlialSection* section, py::array_t _perimeters) { + section->perimeters() = _perimeters.cast>(); + }, + "Returns the perimeters of all points of this section") + .def_property_readonly("is_root", + &morphio::mut::GlialSection::isRoot, + "Return True if section is a root section") + .def_property_readonly("parent", + &morphio::mut::GlialSection::parent, + "Get the parent ID\n\n" + "Note: Root sections return -1") + .def_property_readonly("children", + &morphio::mut::GlialSection::children, + "Returns a list of children IDs") + // Iterators + .def( + "iter", + [](morphio::mut::GlialSection* section, IterType type) { + switch (type) { + case IterType::DEPTH_FIRST: + return py::make_iterator(section->depth_begin(), section->depth_end()); + case IterType::BREADTH_FIRST: + return py::make_iterator(section->breadth_begin(), section->breadth_end()); + case IterType::UPSTREAM: + return py::make_iterator(section->upstream_begin(), section->upstream_end()); + default: + throw morphio::MorphioError("Only iteration types depth_first, breadth_first and " + "upstream are supported"); + } + }, + py::keep_alive<0, 1>() /* Essential: keep object alive while iterator exists */, + "GlialSection iterator\n" + "\n" + "iter_type controls the iteration order. 3 values can be passed:\n" + "- morphio.IterType.depth_first (default)\n" + "- morphio.IterType.breadth_first\n" + "- morphio.IterType.upstream\n", + "iter_type"_a = IterType::DEPTH_FIRST) + + // Editing + .def("append_section", + static_cast (morphio::mut::GlialSection::*)( + const morphio::GlialSection&, bool)>(&morphio::mut::GlialSection::appendSection), + "Append the existing immutable GlialSection to this section" + "If recursive == true, all descendent will be appended as well", + "immutable_section"_a, + "recursive"_a = false) + + .def("append_section", + static_cast ( + morphio::mut::GlialSection::*)(const std::shared_ptr&, bool)>( + &morphio::mut::GlialSection::appendSection), + "Append the existing mutable GlialSection to this section\n" + "If recursive == true, all descendent will be appended as well", + "mutable_section"_a, + "recursive"_a = false) + + .def("append_section", + static_cast (morphio::mut::GlialSection::*)( + const morphio::Property::PointLevel&, morphio::GlialSectionType)>( + &morphio::mut::GlialSection::appendSection), + "Append a new GlialSection to this section\n" + " If section_type is omitted or set to 'undefined'" + " the type of the parent section will be used", + "point_level_properties"_a, + "section_type"_a = morphio::GlialSectionType::UNDEFINED); + py::class_(m, "EndoplasmicReticulum") .def(py::init<>()) diff --git a/include/morphio/enums.h b/include/morphio/enums.h index 52d4ebe0f..18e3a6485 100644 --- a/include/morphio/enums.h +++ b/include/morphio/enums.h @@ -41,9 +41,6 @@ enum AnnotationType { SINGLE_CHILD, }; -/** The cell family represented by morphio::Morphology. */ -//enum CellFamily { NEURON = 0, GLIA = 1 }; - enum SomaType { SOMA_UNDEFINED = 0, SOMA_SINGLE_POINT, diff --git a/include/morphio/mut/glial_cell.h b/include/morphio/mut/glial_cell.h index 1d15f7b04..0d4c300c2 100644 --- a/include/morphio/mut/glial_cell.h +++ b/include/morphio/mut/glial_cell.h @@ -26,7 +26,7 @@ bool _checkDuplicatePoint(const std::shared_ptr& parent, class GlialCell { public: - using Family = CellFamily::GLIA; + using CellType = CellFamily::GLIA; GlialCell() : _counter(0) diff --git a/include/morphio/mut/morphology.h b/include/morphio/mut/morphology.h index 2420b55b6..dfc2bb052 100644 --- a/include/morphio/mut/morphology.h +++ b/include/morphio/mut/morphology.h @@ -26,7 +26,7 @@ bool _checkDuplicatePoint(const std::shared_ptr
& parent, class Morphology { public: - using Family = CellFamily::NEURON; + using CellType = CellFamily::NEURON; Morphology() : _counter(0) diff --git a/include/morphio/section.h b/include/morphio/section.h index 8b627611f..dd96db5cc 100644 --- a/include/morphio/section.h +++ b/include/morphio/section.h @@ -30,14 +30,14 @@ namespace morphio { class Morphology; -template -class Node: public SectionBase> +template +class Node: public SectionBase> { using SectionId = Property::Section; using PointAttribute = Property::Point; public: - using Type = typename Family::Type; + using Type = typename CellType::Type; /** Depth first search iterator @@ -81,7 +81,7 @@ class Node: public SectionBase> /** * Return the morphological type of this section (dendrite, axon, ...) */ - typename Family::Type type() const; + typename CellType::Type type() const; friend class mut::Section; friend class mut::GlialSection; @@ -91,8 +91,8 @@ class Node: public SectionBase> friend class SectionBase; protected: - Node(uint32_t id_, const std::shared_ptr& properties) - : SectionBase>(id_, properties) {} + Node(uint32_t id_, const std::shared_ptr& properties) + : SectionBase>(id_, properties) {} }; // explicit instanciation @@ -101,6 +101,6 @@ extern template class Node; } // namespace morphio -template -std::ostream& operator<<(std::ostream& os, const morphio::Node& section); +template +std::ostream& operator<<(std::ostream& os, const morphio::Node& section); std::ostream& operator<<(std::ostream& os, const morphio::range& points); diff --git a/include/morphio/section_base.h b/include/morphio/section_base.h index 54edf60a0..f300ff095 100644 --- a/include/morphio/section_base.h +++ b/include/morphio/section_base.h @@ -79,8 +79,8 @@ inline uint32_t SectionBase::id() const noexcept { } } // namespace morphio -template -std::ostream& operator<<(std::ostream& os, const morphio::Node& section); +template +std::ostream& operator<<(std::ostream& os, const morphio::Node& section); std::ostream& operator<<(std::ostream& os, const morphio::range& points); #include "section_base.tpp" diff --git a/include/morphio/tools.h b/include/morphio/tools.h index f1452304c..3c51a0658 100644 --- a/include/morphio/tools.h +++ b/include/morphio/tools.h @@ -12,9 +12,9 @@ bool diff(const Morphology& left, /** Perform a diff on 2 sections, returns True if items differ **/ -template -bool diff(const Node& left, - const Node& right, +template +bool diff(const Node& left, + const Node& right, morphio::enums::LogLevel verbose = morphio::enums::LogLevel::INFO); namespace mut { diff --git a/include/morphio/types.h b/include/morphio/types.h index a18575b78..3d35588f8 100644 --- a/include/morphio/types.h +++ b/include/morphio/types.h @@ -17,7 +17,7 @@ using namespace enums; class EndoplasmicReticulum; class MitoSection; class Mitochondria; -template +template class Node; template diff --git a/morphio/mut/__init__.py b/morphio/mut/__init__.py index eef569bd4..369a07279 100644 --- a/morphio/mut/__init__.py +++ b/morphio/mut/__init__.py @@ -1,3 +1,3 @@ from .._morphio.mut import (Morphology, Section, Soma, MitoSection, - Mitochondria, EndoplasmicReticulum , # GlialCell + Mitochondria, EndoplasmicReticulum, GlialCell, GlialSection ) diff --git a/src/mut/morphology.cpp b/src/mut/morphology.cpp index dd8712f27..3b192c1c0 100644 --- a/src/mut/morphology.cpp +++ b/src/mut/morphology.cpp @@ -74,17 +74,6 @@ bool _checkDuplicatePoint(const std::shared_ptr
& parent, if (parent->points().back() != current->points().front()) return false; - // // As perimeter is optional, it must either be defined for parent and - // current - // // or not be defined at all - // if(parent->perimeters().empty() != current->perimeters().empty()) - // return false; - - // if(!parent->perimeters().empty() && - // parent->perimeters()[parent->perimeters().size()-1] != - // current->perimeters()[0]) - // return false; - return true; } diff --git a/src/mut/writers.cpp b/src/mut/writers.cpp index a6574823c..f39266a6d 100644 --- a/src/mut/writers.cpp +++ b/src/mut/writers.cpp @@ -379,7 +379,7 @@ void h5(const Cell& morpho, const std::string& filename) { HighFive::Group g_metadata = h5_file.createGroup("metadata"); write_attribute(g_metadata, "version", std::vector{1, 2}); - write_attribute(g_metadata, "cell_family", std::vector{Cell::Family::value}); + write_attribute(g_metadata, "cell_family", std::vector{Cell::CellType::value}); write_attribute(h5_file, "comment", std::vector{version_string()}); if (hasPerimeterData_) { diff --git a/src/section.cpp b/src/section.cpp index f54c02e70..5dd1d7269 100644 --- a/src/section.cpp +++ b/src/section.cpp @@ -5,55 +5,55 @@ namespace morphio { -template -typename Family::Type Node::type() const { - return static_cast( +template +typename CellType::Type Node::type() const { + return static_cast( this -> _properties-> template get()[this->_id] ); } -template -depth_iterator_t> Node::depth_begin() const { - return depth_iterator_t>(*this); +template +depth_iterator_t> Node::depth_begin() const { + return depth_iterator_t>(*this); } -template -depth_iterator_t> Node::depth_end() const { - return depth_iterator_t>(); +template +depth_iterator_t> Node::depth_end() const { + return depth_iterator_t>(); } -template -breadth_iterator_t> Node::breadth_begin() const { - return breadth_iterator_t>(*this); +template +breadth_iterator_t> Node::breadth_begin() const { + return breadth_iterator_t>(*this); } -template -breadth_iterator_t> Node::breadth_end() const { - return breadth_iterator_t>(); +template +breadth_iterator_t> Node::breadth_end() const { + return breadth_iterator_t>(); } -template -upstream_iterator_t> Node::upstream_begin() const { - return upstream_iterator_t>(*this); +template +upstream_iterator_t> Node::upstream_begin() const { + return upstream_iterator_t>(*this); } -template -upstream_iterator_t> Node::upstream_end() const { - return upstream_iterator_t>(); +template +upstream_iterator_t> Node::upstream_end() const { + return upstream_iterator_t>(); } -template -range Node::points() const { +template +range Node::points() const { return this-> template get(); } -template -range Node::diameters() const { +template +range Node::diameters() const { return this -> template get(); } -template -range Node::perimeters() const { +template +range Node::perimeters() const { return this -> template get(); } @@ -62,8 +62,8 @@ template class Node; } // namespace morphio -template -std::ostream& operator<<(std::ostream& os, const morphio::Node& section) { +template +std::ostream& operator<<(std::ostream& os, const morphio::Node& section) { const auto& points = section.points(); if (points.empty()) { os << "Section(id=" << section.id() << ", points=[])"; diff --git a/tests/test_4_immut.py b/tests/test_4_immut.py index edbe4b9b4..e0d0b747d 100644 --- a/tests/test_4_immut.py +++ b/tests/test_4_immut.py @@ -7,7 +7,7 @@ from pathlib import Path from morphio import IterType, Morphology, RawDataError, GlialCell -#CellFamily + _path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "data") diff --git a/tests/test_5_mut.py b/tests/test_5_mut.py index 9c8ee41c5..58bc37b5c 100644 --- a/tests/test_5_mut.py +++ b/tests/test_5_mut.py @@ -12,10 +12,10 @@ from morphio import MitochondriaPointLevel, MorphioError, RawDataError from morphio import Morphology as ImmutableMorphology from morphio import (PointLevel, SectionBuilderError, SectionType, - IterType, ostream_redirect, # CellFamily + IterType, ostream_redirect, ) -from morphio.mut import Morphology -# , GlialCell +from morphio.mut import Morphology, GlialCell + from . utils import assert_substring, captured_output, tmp_asc_file, setup_tempdir _path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "data") @@ -469,24 +469,20 @@ def test_sanitize(): 'Warning: while appending section: 2 to parent: 0\nThe section first point should be parent section last point: \n : X Y Z Diameter\nparent last point :[2.000000, 0.000000, 0.000000, 2.000000]\nchild first point :[2.000000, 1.000000, 0.000000, 2.000000]') -# def test_glia(): -# g = GlialCell() -# assert_equal(g.cell_family, CellFamily.GLIA) - -# g = GlialCell(os.path.join(_path, 'astrocyte.h5')) -# assert_equal(g.cell_family, CellFamily.GLIA) +def test_glia(): + g = GlialCell() + g = GlialCell(os.path.join(_path, 'astrocyte.h5')) -# g = GlialCell(Path(_path, 'astrocyte.h5')) -# assert_equal(g.cell_family, CellFamily.GLIA) + g = GlialCell(Path(_path, 'astrocyte.h5')) -# assert_raises(RawDataError, GlialCell, Path(_path, 'simple.swc')) -# assert_raises(RawDataError, GlialCell, Path(_path, 'h5/v1/simple.h5')) + assert_raises(RawDataError, GlialCell, Path(_path, 'simple.swc')) + assert_raises(RawDataError, GlialCell, Path(_path, 'h5/v1/simple.h5')) -# def test_glia_round_trip(): -# with TemporaryDirectory() as folder: -# g = GlialCell(os.path.join(_path, 'astrocyte.h5')) -# filename = Path(folder, 'glial-cell.h5') -# g.write(filename) -# g2 = GlialCell(filename) -# assert_equal(len(g.sections), len(g2.sections)) +def test_glia_round_trip(): + with TemporaryDirectory() as folder: + g = GlialCell(os.path.join(_path, 'astrocyte.h5')) + filename = Path(folder, 'glial-cell.h5') + g.write(filename) + g2 = GlialCell(filename) + assert_equal(len(g.sections), len(g2.sections))