From 37602d56136afbee59f9f53361c88b3ddc3a1ee1 Mon Sep 17 00:00:00 2001 From: Juan Hernando Vieites Date: Fri, 5 May 2017 13:59:07 +0200 Subject: [PATCH] Updated brain::SpikeReportReader Python wrapping Now it can accept numpy arrays as input and it includes the docstrings. --- brain/python/CMakeLists.txt | 4 +- brain/python/arrayHelpers.cpp | 27 ++++++++ brain/python/arrayHelpers.h | 4 ++ brain/python/helpers.h | 4 +- brain/python/neuron/morphology.cpp | 134 ++++++++++++++++++------------------- brain/python/spikeReportReader.cpp | 35 +++++----- brain/python/spikeReportWriter.cpp | 59 ++++++++++++++-- brain/spikeReportReader.h | 3 + brain/spikeReportWriter.cpp | 6 ++ brain/spikeReportWriter.h | 7 ++ brion/plugin/spikeReportASCII.cpp | 11 +-- brion/plugin/spikeReportASCII.h | 2 +- brion/plugin/spikeReportBinary.cpp | 12 ++-- brion/plugin/spikeReportBinary.h | 2 +- brion/plugin/spikeReportBluron.cpp | 4 +- brion/plugin/spikeReportBluron.h | 2 +- brion/plugin/spikeReportNEST.cpp | 4 +- brion/plugin/spikeReportNEST.h | 2 +- brion/spikeReport.cpp | 15 +++-- brion/spikeReport.h | 9 ++- brion/spikeReportPlugin.h | 2 +- doc/Changelog.md | 4 +- 22 files changed, 229 insertions(+), 123 deletions(-) diff --git a/brain/python/CMakeLists.txt b/brain/python/CMakeLists.txt index a85e501..9577830 100644 --- a/brain/python/CMakeLists.txt +++ b/brain/python/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2013-2016, EPFL/Blue Brain Project +# Copyright (c) 2013-2017, EPFL/Blue Brain Project # ahmet.bilgili@epfl.ch # juan.hernando@epfl.ch # @@ -20,6 +20,7 @@ set(BRAIN_PYTHON_SOURCES circuit.cpp compartmentReport.cpp spikeReportReader.cpp + spikeReportWriter.cpp synapses.cpp neuron/morphology.cpp ) @@ -30,7 +31,6 @@ docstrings(BRAIN_PYTHON_SOURCES BRAIN_PUBLIC_HEADERS list(APPEND BRAIN_PYTHON_SOURCES arrayHelpers.cpp helpers.cpp - spikeReportWriter.cpp spikes.cpp submodules.cpp test.cpp diff --git a/brain/python/arrayHelpers.cpp b/brain/python/arrayHelpers.cpp index b284770..b70696f 100644 --- a/brain/python/arrayHelpers.cpp +++ b/brain/python/arrayHelpers.cpp @@ -351,6 +351,33 @@ bool gidsFromNumpy(const boost::python::object& object, uint32_ts& result) return false; // Unreachable } +std::pair spikesFromNumpy( + const boost::python::object& object) +{ + if (!PyArray_Check(object.ptr())) + { + PyErr_SetString(PyExc_ValueError, + "Cannot convert argument to Spike array"); + boost::python::throw_error_already_set(); + } + + PyArrayObject* array = reinterpret_cast(object.ptr()); + PyArray_Descr* desc = PyArray_DESCR(array); + PyArray_Descr* expected = createDtype("f4, u4"); + const bool equiv = PyArray_EquivTypes(desc, expected); + Py_DECREF(expected); + if (PyArray_NDIM(array) != 1 || !equiv) + { + PyErr_SetString(PyExc_ValueError, + "Cannot convert argument to Spike array"); + boost::python::throw_error_already_set(); + } + + return std::pair(static_cast( + PyArray_GETPTR1(array, 0)), + PyArray_DIMS(array)[0]); +} + template <> Matrix4f fromNumpy(const bp::object& o) { diff --git a/brain/python/arrayHelpers.h b/brain/python/arrayHelpers.h index d57dff3..6b30732 100644 --- a/brain/python/arrayHelpers.h +++ b/brain/python/arrayHelpers.h @@ -56,6 +56,9 @@ boost::python::object toNumpy(const Matrix4f& matrix); */ bool gidsFromNumpy(const boost::python::object& object, uint32_ts& result); +std::pair spikesFromNumpy( + const boost::python::object& object); + /// tuple(timestamp : double, data : 1D ndarray ) boost::python::object frameToTuple(CompartmentReportFrame&& frame); @@ -63,6 +66,7 @@ boost::python::object frameToTuple(CompartmentReportFrame&& frame); /// or tuple(timestamp : double, data : 1D ndarray) if frames.size == 1 boost::python::object framesToTuple(CompartmentReportFrames&& frames); +// Implemented for: Matrix4f template T fromNumpy(const boost::python::object& object); } diff --git a/brain/python/helpers.h b/brain/python/helpers.h index 8bcb2f4..7c77817 100644 --- a/brain/python/helpers.h +++ b/brain/python/helpers.h @@ -30,8 +30,8 @@ namespace brain { template -inline std::vector vectorFromPython(boost::python::object o, - const char* errorMessage) +inline std::vector vectorFromIterable(boost::python::object o, + const char* errorMessage) { std::vector vector; try diff --git a/brain/python/neuron/morphology.cpp b/brain/python/neuron/morphology.cpp index 00695e9..1dd24a7 100644 --- a/brain/python/neuron/morphology.cpp +++ b/brain/python/neuron/morphology.cpp @@ -73,8 +73,8 @@ bp::object Section_getSamplesFromPositions(const SectionWrapper& section, bp::object points) { const floats pointVector = - vectorFromPython(points, - "Cannot convert argument to float vector"); + vectorFromIterable(points, + "Cannot convert argument to float vector"); return toNumpy(section.getSamples(pointVector)); } @@ -120,7 +120,7 @@ GET_MORPHOLOGY_ARRAY(Apicals) bp::object Morphology_getSectionIDs(const MorphologyPtr& morphology, bp::object types) { - const SectionTypes typeVector = vectorFromPython( + const SectionTypes typeVector = vectorFromIterable( types, "Cannot convert argument to SectionType list"); return toNumpy(morphology->getSectionIDs(typeVector)); } @@ -181,75 +181,73 @@ bp::object Morphology_getTransformation(const MorphologyPtr& morphology) void export_Morphology() { -const auto selfarg = bp::arg( "self" ); +const auto selfarg = bp::arg("self"); // Do not modify whitespace on DOXY_FN lines -bp::class_< SomaWrapper >( - "Soma", DOXY_CLASS( brain::neuron::Soma ), bp::no_init ) - .def( "profile_points", Soma_getProfilePoints, ( selfarg ), - DOXY_FN( brain::neuron::Soma::getProfilePoints )) - .def( "mean_radius", &Soma::getMeanRadius, ( selfarg ), - DOXY_FN( brain::neuron::Soma::getMeanRadius )) - .def( "centroid", &Soma::getCentroid, ( selfarg ), - DOXY_FN( brain::neuron::Soma::getCentroid )) - ; - -bp::class_< SectionWrapper >( - "Section", DOXY_CLASS( brain::neuron::Section ), bp::no_init ) - .def( bp::self == bp::self ) - .def( "id", &Section::getID, ( selfarg ), - DOXY_FN( brain::neuron::Section::getID )) - .def( "type", &Section::getType, ( selfarg ), - DOXY_FN( brain::neuron::Section::getType )) - .def( "length", &Section::getLength, ( selfarg ), - DOXY_FN( brain::neuron::Section::getLength )) - .def( "samples", Section_getSamples, ( selfarg ), - DOXY_FN( brain::neuron::Section::getSamples() const)) - .def( "samples", Section_getSamplesFromPositions, - ( selfarg, bp::arg( "positions" )), - DOXY_FN( brain::neuron::Section::getSamples(const floats&) const)) - .def( "distance_to_soma", &Section::getDistanceToSoma, ( selfarg ), - DOXY_FN( brain::neuron::Section::getDistanceToSoma )) - .def( "sample_distances_to_soma", Section_getSampleDistancesToSoma, - ( selfarg ), - DOXY_FN( brain::neuron::Section::getSampleDistancesToSoma )) - .def( "parent", Section_getParent, ( selfarg ), - DOXY_FN( brain::neuron::Section::getParent )) - .def( "children", Section_getChildren, ( selfarg ), - DOXY_FN( brain::neuron::Section::getChildren )) - ; - -bp::class_< Morphology, boost::noncopyable, MorphologyPtr >( - "Morphology", DOXY_CLASS( brain::neuron::Morphology ), bp::no_init ) - .def( "__init__", bp::make_constructor( Morphology_initFromURI ), - DOXY_FN( brain::neuron::Morphology::Morphology(const URI&))) - .def( "__init__", bp::make_constructor( Morphology_initFromURIAndTransform ), - DOXY_FN( brain::neuron::Morphology::Morphology(const URI&, const Matrix4f&))) +bp::class_( + "Soma", DOXY_CLASS(brain::neuron::Soma), bp::no_init) + .def("profile_points", Soma_getProfilePoints, (selfarg), + DOXY_FN(brain::neuron::Soma::getProfilePoints)) + .def("mean_radius", &Soma::getMeanRadius, (selfarg), + DOXY_FN(brain::neuron::Soma::getMeanRadius)) + .def("centroid", &Soma::getCentroid, (selfarg), + DOXY_FN(brain::neuron::Soma::getCentroid)); + +bp::class_( + "Section", DOXY_CLASS(brain::neuron::Section), bp::no_init) + .def(bp::self == bp::self) + .def("id", &Section::getID, (selfarg), + DOXY_FN(brain::neuron::Section::getID)) + .def("type", &Section::getType, (selfarg), + DOXY_FN(brain::neuron::Section::getType)) + .def("length", &Section::getLength, (selfarg), + DOXY_FN(brain::neuron::Section::getLength)) + .def("samples", Section_getSamples, (selfarg), + DOXY_FN(brain::neuron::Section::getSamples() const)) + .def("samples", Section_getSamplesFromPositions, + (selfarg, bp::arg("positions")), + DOXY_FN(brain::neuron::Section::getSamples(const floats&) const)) + .def("distance_to_soma", &Section::getDistanceToSoma, (selfarg), + DOXY_FN(brain::neuron::Section::getDistanceToSoma)) + .def("sample_distances_to_soma", Section_getSampleDistancesToSoma, (selfarg), + DOXY_FN(brain::neuron::Section::getSampleDistancesToSoma)) + .def("parent", Section_getParent, (selfarg), + DOXY_FN(brain::neuron::Section::getParent)) + .def("children", Section_getChildren, (selfarg), + DOXY_FN(brain::neuron::Section::getChildren)); + +bp::class_( + "Morphology", DOXY_CLASS(brain::neuron::Morphology), bp::no_init) + .def("__init__", bp::make_constructor(Morphology_initFromURI), + DOXY_FN(brain::neuron::Morphology::Morphology(const URI&))) + .def("__init__", + bp::make_constructor(Morphology_initFromURIAndTransform), + DOXY_FN(brain::neuron::Morphology::Morphology(const URI&, const Matrix4f&))) // The following docstrings are given explictly because the return types - // are special and the original documentation uses @sa, which points nowhere. - .def( "points", Morphology_getPoints, ( selfarg ), - "Return a 4xN numpy array with the x,y,z and radius of all the points of this morphology.") - .def( "sections", Morphology_getSections, ( selfarg ), - "Return a 2xN numpy array with the parent ID and first point offset of each section." ) - .def( "section_types", Morphology_getSectionTypes, ( selfarg ), - "Return a numpy array with the section types." ) - .def( "apicals", Morphology_getApicals, ( selfarg ), - "Return a 2xN numpy array with the section id and point index of apical points." ) - .def( "section_ids", Morphology_getSectionIDs, - ( selfarg, bp::arg( "types" )), - DOXY_FN( brain::neuron::Morphology::getSectionIDs )) - .def( "sections", Morphology_getSectionsByType, - ( selfarg, bp::arg( "type" )), - DOXY_FN( brain::neuron::Morphology::getSections(const SectionTypes&) const)) - .def( "section", Morphology_getSection, ( selfarg, bp::arg( "id" )), - DOXY_FN( brain::neuron::Morphology::getSections(SectionType) const)) - .def( "soma", Morphology_getSoma, ( selfarg ), - DOXY_FN( brain::neuron::Morphology::getSoma )) - .def( "transformation", Morphology_getTransformation, ( selfarg ), - DOXY_FN( brain::neuron::Morphology::getTransformation )) - ; - + // are special and the original documentation uses @sa, which points + // nowhere. + .def("points", Morphology_getPoints, (selfarg), + "Return a 4xN numpy array with the x,y,z and radius of all the " + "points of this morphology.") + .def("sections", Morphology_getSections, (selfarg), + "Return a 2xN numpy array with the parent ID and first point " + "offset of each section.") + .def("section_types", Morphology_getSectionTypes, (selfarg), + "Return a numpy array with the section types.") + .def("apicals", Morphology_getApicals, (selfarg), + "Return a 2xN numpy array with the section id and point index of " + "apical points.") + .def("section_ids", Morphology_getSectionIDs, (selfarg, bp::arg("types")), + DOXY_FN(brain::neuron::Morphology::getSectionIDs)) + .def("sections", Morphology_getSectionsByType, (selfarg, bp::arg("type")), + DOXY_FN(brain::neuron::Morphology::getSections(const SectionTypes&) const)) + .def("section", Morphology_getSection, (selfarg, bp::arg("id")), + DOXY_FN(brain::neuron::Morphology::getSections(SectionType) const)) + .def("soma", Morphology_getSoma, (selfarg), + DOXY_FN(brain::neuron::Morphology::getSoma)) + .def("transformation", Morphology_getTransformation, (selfarg), + DOXY_FN(brain::neuron::Morphology::getTransformation)); } // clang-format on } diff --git a/brain/python/spikeReportReader.cpp b/brain/python/spikeReportReader.cpp index 56954e7..96e8ea1 100644 --- a/brain/python/spikeReportReader.cpp +++ b/brain/python/spikeReportReader.cpp @@ -53,25 +53,24 @@ bp::object SpikeReportReader_getSpikes(SpikeReportReader& reader, void export_SpikeReportReader() { - const auto selfarg = bp::arg("self"); - // clang-format off -bp::class_< SpikeReportReader, boost::noncopyable >( - "SpikeReportReader", bp::no_init ) - .def( "__init__", bp::make_constructor( _initURI ), - DOXY_FN( brain::SpikeReportReader::SpikeReportReader( const brion::URI& ))) - .def( "__init__", bp::make_constructor( _initURIandGIDSet ), - DOXY_FN( brain::SpikeReportReader::SpikeReportReader( const brion::URI&, const GIDSet& ))) - .def( "close", &SpikeReportReader::close, - DOXY_FN( brain::SpikeReportReader::close )) - .def( "get_spikes", SpikeReportReader_getSpikes, - ( selfarg, bp::arg( "start_time" ), bp::arg( "stop_time" )), - DOXY_FN( brain::SpikeReportReader::getSpikes )) - .add_property( "end_time", &SpikeReportReader::getEndTime, - DOXY_FN( brain::SpikeReportReader::getEndTime )) - .add_property( "has_ended", &SpikeReportReader::hasEnded, - DOXY_FN( brain::SpikeReportReader::hasEnded )) - ; +const auto selfarg = bp::arg("self"); + +bp::class_( + "SpikeReportReader", bp::no_init) + .def("__init__", bp::make_constructor(_initURI), + DOXY_FN(brain::SpikeReportReader::SpikeReportReader(const brion::URI&))) + .def("__init__", bp::make_constructor(_initURIandGIDSet), + DOXY_FN(brain::SpikeReportReader::SpikeReportReader(const brion::URI&, const GIDSet&))) + .def("close", &SpikeReportReader::close, + DOXY_FN(brain::SpikeReportReader::close)) + .def("get_spikes", SpikeReportReader_getSpikes, + (selfarg, bp::arg("start_time"), bp::arg("stop_time")), + DOXY_FN(brain::SpikeReportReader::getSpikes)) + .add_property("end_time", &SpikeReportReader::getEndTime, + DOXY_FN(brain::SpikeReportReader::getEndTime)) + .add_property("has_ended", &SpikeReportReader::hasEnded, + DOXY_FN(brain::SpikeReportReader::hasEnded)); // clang-format on } } diff --git a/brain/python/spikeReportWriter.cpp b/brain/python/spikeReportWriter.cpp index 805fcd2..18d47de 100644 --- a/brain/python/spikeReportWriter.cpp +++ b/brain/python/spikeReportWriter.cpp @@ -1,4 +1,4 @@ -/* Copyright (c) 2006-2016, Ahmet Bilgili +/* Copyright (c) 2006-2017, Ahmet Bilgili * Juan Hernando * * This file is part of Brion @@ -18,10 +18,14 @@ */ #include +#include #include #include +#include "arrayHelpers.h" +#include "docstrings.h" + namespace bp = boost::python; namespace brain @@ -32,17 +36,60 @@ SpikeReportWriterPtr _initURI(const std::string& uri) { return SpikeReportWriterPtr(new SpikeReportWriter(brion::URI(uri))); } + +void SpikeReportWriter_writeSpikes(SpikeReportWriter& writer, + const bp::object& object) +{ + try + { + const auto spikes = spikesFromNumpy(object); + writer.writeSpikes(spikes.first, spikes.second); + } + catch (bp::error_already_set&) + { + PyErr_Clear(); + // Try again extracting the spikes for a list of tuples + Spikes spikes; + try + { + bp::stl_input_iterator i(object), end; + while (i != end) + { + bp::tuple t = *i++; + if (bp::len(t) != 2) + boost::python::throw_error_already_set(); + const float time = bp::extract(t[0]); + const uint32_t gid = bp::extract(t[1]); + spikes.push_back(std::make_pair(time, gid)); + } + } + catch (bp::error_already_set&) + { + PyErr_SetString( + PyExc_ValueError, + "Cannot convert argument Spike array. Only " + "numpy arrays of dtype=\"f4, u4\" and iterables of (float, int)" + " tuples are accepted"); + throw; + } + writer.writeSpikes(spikes); + } +} } // clang-format off void export_SpikeReportWriter() { -bp::class_< SpikeReportWriter, boost::noncopyable >( - "SpikeReportWriter", bp::no_init ) - .def( "__init__", bp::make_constructor( _initURI )) - .def( "close", &SpikeReportWriter::close ) - .def( "writeSpikes", &SpikeReportWriter::writeSpikes ) +bp::class_( + "SpikeReportWriter", bp::no_init) + .def("__init__", bp::make_constructor(_initURI), + DOXY_FN(brain::SpikeReportWriter::SpikeReportWriter(const brion::URI&))) + .def("close", &SpikeReportWriter::close, + DOXY_FN(brain::SpikeReportWriter::close)) + .def("write_spikes", &SpikeReportWriter_writeSpikes, + (bp::arg("self"), bp::arg("spikes")), + DOXY_FN(brain::SpikeReportWriter::writeSpikes(const Spikes&))) ; } // clang-format on diff --git a/brain/spikeReportReader.h b/brain/spikeReportReader.h index af18fa6..5ba61c6 100644 --- a/brain/spikeReportReader.h +++ b/brain/spikeReportReader.h @@ -73,6 +73,9 @@ class SpikeReportReader : public boost::noncopyable * open on the right, so assuming that spikes arrive in order, this * method will return a full snapshot of the spikes between [start, end). * Precondition : start < end + * \if pybind + * @return A numpy array of dytpe = "f4, u4" + * \endif * @throw std::logic_error if the precondition is not fulfilled. * @version 1.0 */ diff --git a/brain/spikeReportWriter.cpp b/brain/spikeReportWriter.cpp index 1d3858a..97ad09c 100644 --- a/brain/spikeReportWriter.cpp +++ b/brain/spikeReportWriter.cpp @@ -51,6 +51,12 @@ void SpikeReportWriter::writeSpikes(const brion::Spikes& spikes) _impl->_report.write(spikes); } +void SpikeReportWriter::writeSpikes(const brion::Spike* spikes, + const size_t size) +{ + _impl->_report.write(spikes, size); +} + const lunchbox::URI& SpikeReportWriter::getURI() const { return _impl->_report.getURI(); diff --git a/brain/spikeReportWriter.h b/brain/spikeReportWriter.h index 39c7a45..ff34479 100644 --- a/brain/spikeReportWriter.h +++ b/brain/spikeReportWriter.h @@ -56,7 +56,11 @@ class SpikeReportWriter : public boost::noncopyable /** * Writes the spike times and cell GIDs. * + * \if pybind * @param spikes Spikes to write. + * \else + * @param spikes Spikes to write. + * \endif * @version 1.0 */ void writeSpikes(const Spikes& spikes); @@ -78,6 +82,9 @@ class SpikeReportWriter : public boost::noncopyable */ void close(); + /** @internal Use by the Python wrapping */ + void writeSpikes(const Spike* spike, size_t size); + private: class _Impl; _Impl* _impl; diff --git a/brion/plugin/spikeReportASCII.cpp b/brion/plugin/spikeReportASCII.cpp index cd7f060..48e2ae8 100644 --- a/brion/plugin/spikeReportASCII.cpp +++ b/brion/plugin/spikeReportASCII.cpp @@ -233,9 +233,10 @@ Spikes SpikeReportASCII::parse(const std::string& filename, return spikes; } -void SpikeReportASCII::append(const Spikes& spikes, const WriteFunc& writefunc) +void SpikeReportASCII::append(const Spike* spikes, const size_t size, + const WriteFunc& writeFunc) { - if (!spikes.size()) + if (size == 0) return; std::fstream file{getURI().getPath(), @@ -246,12 +247,12 @@ void SpikeReportASCII::append(const Spikes& spikes, const WriteFunc& writefunc) return; } - for (const Spike& spike : spikes) - writefunc(file, spike); + for (size_t i = 0; i != size; ++i) + writeFunc(file, spikes[i]); file.flush(); - const float lastTimestamp = spikes.rbegin()->first; + const float lastTimestamp = spikes[size - 1].first; _currentTime = std::nextafter(lastTimestamp, std::numeric_limits::max()); _endTime = std::max(_endTime, lastTimestamp); diff --git a/brion/plugin/spikeReportASCII.h b/brion/plugin/spikeReportASCII.h index 92b28ad..5325e20 100644 --- a/brion/plugin/spikeReportASCII.h +++ b/brion/plugin/spikeReportASCII.h @@ -50,7 +50,7 @@ class SpikeReportASCII : public SpikeReportPlugin static Spikes parse(const Strings& files, const ParseFunc& parse); static Spikes parse(const std::string& filename, const ParseFunc& parse); - void append(const Spikes& spikes, const WriteFunc& parse); + void append(const Spike* spikes, size_t size, const WriteFunc& writeFunc); }; } } diff --git a/brion/plugin/spikeReportBinary.cpp b/brion/plugin/spikeReportBinary.cpp index bc5b271..974fdec 100644 --- a/brion/plugin/spikeReportBinary.cpp +++ b/brion/plugin/spikeReportBinary.cpp @@ -217,11 +217,11 @@ void SpikeReportBinary::writeSeek(float toTimeStamp) readSeek(toTimeStamp); } -void SpikeReportBinary::write(const Spikes& spikes) +void SpikeReportBinary::write(const Spike* spikes, const size_t size) { - size_t totalSpikes = _startIndex + spikes.size(); + size_t totalSpikes = _startIndex + size; - if (spikes.empty()) + if (size == 0) return; // create or resize the file @@ -229,10 +229,10 @@ void SpikeReportBinary::write(const Spikes& spikes) _memFile->resize(totalSpikes); Spike* spikeArray = _memFile->getWritableSpikes(); - for (const Spike& spike : spikes) - spikeArray[_startIndex++] = spike; + for (size_t i = 0; i != size; ++i) + spikeArray[_startIndex++] = spikes[i]; - const float lastTimestamp = spikes.rbegin()->first; + const float lastTimestamp = spikes[size - 1].first; _currentTime = std::nextafter(lastTimestamp, std::numeric_limits::max()); _endTime = std::max(_endTime, lastTimestamp); diff --git a/brion/plugin/spikeReportBinary.h b/brion/plugin/spikeReportBinary.h index a62b93e..9eef199 100644 --- a/brion/plugin/spikeReportBinary.h +++ b/brion/plugin/spikeReportBinary.h @@ -51,7 +51,7 @@ class SpikeReportBinary : public SpikeReportPlugin Spikes readUntil(float max) final; void readSeek(float toTimeStamp) final; void writeSeek(float toTimeStamp) final; - void write(const Spikes& spikes) final; + void write(const Spike* spikes, size_t size) final; bool supportsBackwardSeek() const final { return true; } private: std::unique_ptr _memFile; diff --git a/brion/plugin/spikeReportBluron.cpp b/brion/plugin/spikeReportBluron.cpp index b322c59..3028bce 100644 --- a/brion/plugin/spikeReportBluron.cpp +++ b/brion/plugin/spikeReportBluron.cpp @@ -80,9 +80,9 @@ void SpikeReportBluron::close() { } -void SpikeReportBluron::write(const Spikes& spikes) +void SpikeReportBluron::write(const Spike* spikes, const size_t size) { - append(spikes, [](std::ostream& file, const Spike& spike) { + append(spikes, size, [](std::ostream& file, const Spike& spike) { file << spike.first << " " << spike.second << "\n"; }); } diff --git a/brion/plugin/spikeReportBluron.h b/brion/plugin/spikeReportBluron.h index 9b9ad8d..cdad1e3 100644 --- a/brion/plugin/spikeReportBluron.h +++ b/brion/plugin/spikeReportBluron.h @@ -39,7 +39,7 @@ class SpikeReportBluron : public SpikeReportASCII static std::string getDescription(); void close() final; - virtual void write(const Spikes& spikes) final; + virtual void write(const Spike* spikes, size_t size) final; }; } } diff --git a/brion/plugin/spikeReportNEST.cpp b/brion/plugin/spikeReportNEST.cpp index dc2797d..e8bdf00 100644 --- a/brion/plugin/spikeReportNEST.cpp +++ b/brion/plugin/spikeReportNEST.cpp @@ -125,9 +125,9 @@ void SpikeReportNEST::close() { } -void SpikeReportNEST::write(const Spikes& spikes) +void SpikeReportNEST::write(const Spike* spikes, const size_t size) { - append(spikes, [](std::ostream& file, const Spike& spike) { + append(spikes, size, [](std::ostream& file, const Spike& spike) { file << spike.second << " " << spike.first << '\n'; }); } diff --git a/brion/plugin/spikeReportNEST.h b/brion/plugin/spikeReportNEST.h index aa4b633..968d068 100644 --- a/brion/plugin/spikeReportNEST.h +++ b/brion/plugin/spikeReportNEST.h @@ -39,7 +39,7 @@ class SpikeReportNEST : public SpikeReportASCII static std::string getDescription(); void close() final; - void write(const Spikes& spikes) final; + void write(const Spike* spikes, size_t size) final; }; } } diff --git a/brion/spikeReport.cpp b/brion/spikeReport.cpp index db446e0..168430e 100644 --- a/brion/spikeReport.cpp +++ b/brion/spikeReport.cpp @@ -244,6 +244,11 @@ std::future SpikeReport::seek(const float toTimeStamp) void SpikeReport::write(const Spikes& spikes) { + write(spikes.data(), spikes.size()); +} + +void SpikeReport::write(const Spike* spikes, const size_t size) +{ _impl->plugin->_checkCanWrite(); _impl->plugin->_checkNotClosed(); @@ -251,16 +256,16 @@ void SpikeReport::write(const Spikes& spikes) LBTHROW( std::runtime_error("Can't write spikes: Pending seek operation")); - if (!spikes.empty() && spikes.front().first < getCurrentTime()) + if (size != 0 && spikes[0].first < getCurrentTime()) { LBTHROW(std::logic_error("Can't write spikes: first spike at " + - std::to_string(spikes.front().first) + + std::to_string(spikes[0].first) + " time inferior to current time " + std::to_string(getCurrentTime()))); } - if (!spikes.empty() && - !std::is_sorted(spikes.begin(), spikes.end(), + if (size != 0 && + !std::is_sorted(spikes, spikes + size, [](const Spike& x, const Spike& y) { return x.first < y.first; })) @@ -269,7 +274,7 @@ void SpikeReport::write(const Spikes& spikes) std::logic_error("Can't write spikes: Expecting a sorted spikes")); } - _impl->plugin->write(spikes); + _impl->plugin->write(spikes, size); } bool SpikeReport::supportsBackwardSeek() const diff --git a/brion/spikeReport.h b/brion/spikeReport.h index e9e3c2d..f96bca6 100644 --- a/brion/spikeReport.h +++ b/brion/spikeReport.h @@ -292,12 +292,19 @@ class SpikeReport * Upon return getCurrenTime() is the greatest of all the spike times * plus an epsilon. * - * @param spikes A collection of spikes sorted by timestamp in ascending + * @param spikes An array of spikes sorted by timestamp in ascending * order. For every spike, its timestamp must be >= getCurrentTime(). + * @param size The size of the spies array. * @throw std::runtime_error if the report is read-only, * @throw std::logic_error if a precondition does not hold * @version 2.0 */ + BRION_API void write(const Spike* spikes, size_t size); + + /** + * Overload of the function above provided for convenience + * @version 2.0 + */ BRION_API void write(const Spikes& spikes); /** diff --git a/brion/spikeReportPlugin.h b/brion/spikeReportPlugin.h index e3a69e0..8dd7b4c 100644 --- a/brion/spikeReportPlugin.h +++ b/brion/spikeReportPlugin.h @@ -132,7 +132,7 @@ class SpikeReportPlugin : public boost::noncopyable } /** @copydoc brion::SpikeReport::write */ - virtual void write(const Spikes& spikes LB_UNUSED) + virtual void write(const Spike* spikes LB_UNUSED, size_t size LB_UNUSED) { LBTHROW(std::runtime_error( "Operation not supported in spike report plugin")); diff --git a/doc/Changelog.md b/doc/Changelog.md index f5a6cbc..3385099 100644 --- a/doc/Changelog.md +++ b/doc/Changelog.md @@ -3,7 +3,9 @@ Changelog {#Changelog} # git master -* [152](https://github.com/BlueBrain/Brion/pull/151): +* [153](https://github.com/BlueBrain/Brion/pull/153): + Updated Python wrapping of SpikeReportWriter to handle nympy arrays of spikes +* [152](https://github.com/BlueBrain/Brion/pull/152): Added the function brain.neuron.compute_morphological_samples to help getting sample positions from morphologies * [151](https://github.com/BlueBrain/Brion/pull/151):