diff --git a/.gitsubprojects b/.gitsubprojects index 76d0322..4565791 100644 --- a/.gitsubprojects +++ b/.gitsubprojects @@ -1,3 +1,3 @@ # -*- mode: cmake -*- -git_subproject(Lunchbox https://github.com/Eyescale/Lunchbox.git 04e0089) +git_subproject(Lunchbox https://github.com/Eyescale/Lunchbox.git 21f4ed2) git_subproject(vmmlib https://github.com/Eyescale/vmmlib.git 2bec113) diff --git a/apps/spikeConverter.cpp b/apps/spikeConverter.cpp index 0db2d1e..4be723d 100644 --- a/apps/spikeConverter.cpp +++ b/apps/spikeConverter.cpp @@ -1,5 +1,6 @@ /* Copyright (c) 2015, EPFL/Blue Brain Project * Stefan Eilemann + * Raphael Dumusc * * This file is part of Brion * @@ -19,6 +20,19 @@ #include #include +#include + +#define STREAM_READ_TIMEOUT_MS 500 +#define STREAM_SEND_DELAY_MS 1000 +#define STREAM_SEND_FREQ_MS 500 +#define STREAM_FRAME_LENGTH_MS 10 + +void printSentFrame( const unsigned int index, const size_t count ) +{ + LBINFO << "Sent frame " << index << ": " << index * STREAM_FRAME_LENGTH_MS + << "-" << (index+1) * STREAM_FRAME_LENGTH_MS << " [ms], " + << count << " spikes" << std::endl; +} int main( int argc, char* argv[] ) { @@ -29,11 +43,53 @@ int main( int argc, char* argv[] ) } lunchbox::Clock clock; - const brion::SpikeReport in( brion::URI( argv[1] ), brion::MODE_READ ); + brion::SpikeReport in( brion::URI( argv[1] ), brion::MODE_READ ); const float readTime = clock.resetTimef(); brion::SpikeReport out( brion::URI( argv[2] ), brion::MODE_WRITE ); - out.writeSpikes( in.getSpikes( )); + + if( in.getReadMode() == brion::SpikeReport::STREAM ) + { + // Stream-to-File conversion + while( in.getNextSpikeTime() != brion::UNDEFINED_TIMESTAMP ) + { + in.waitUntil( brion::UNDEFINED_TIMESTAMP, STREAM_READ_TIMEOUT_MS ); + out.writeSpikes( in.getSpikes( )); + in.clear( in.getStartTime( ), in.getEndTime( )); + } + } + else if( out.getReadMode() == brion::SpikeReport::STREAM ) + { + // File-to-Stream conversion + + // leave time for a Zeq connection to establish + lunchbox::sleep( STREAM_SEND_DELAY_MS ); + + brion::Spikes outSpikes; + unsigned int frameIndex = 0; + + const brion::Spikes& inSpikes = in.getSpikes(); + for( brion::Spikes::const_iterator spikeIt = inSpikes.begin(); + spikeIt != inSpikes.end(); ++spikeIt ) + { + if( spikeIt->first >= (frameIndex+1) * STREAM_FRAME_LENGTH_MS ) + { + out.writeSpikes( outSpikes ); + printSentFrame( frameIndex++, outSpikes.size( )); + outSpikes.clear(); + lunchbox::sleep( STREAM_SEND_FREQ_MS ); + } + outSpikes.insert( *spikeIt ); + } + out.writeSpikes( outSpikes ); + printSentFrame( frameIndex, outSpikes.size( )); + } + else + { + // File-to-File conversion + out.writeSpikes( in.getSpikes( )); + } + out.close(); const float writeTime = clock.resetTimef(); diff --git a/brion/plugin/spikeReportSimpleStreamer.h b/brion/plugin/spikeReportSimpleStreamer.h index c82c12d..ee205f1 100644 --- a/brion/plugin/spikeReportSimpleStreamer.h +++ b/brion/plugin/spikeReportSimpleStreamer.h @@ -63,10 +63,10 @@ class SpikeReportSimpleStreamer : public SpikeReportPlugin /** @copydoc brion::SpikeReport::waitUntil */ bool waitUntil( const float timeStamp, const uint32_t timeout ) final; - /** @copydoc brion::SpikeReport::waitUntil */ + /** @copydoc brion::SpikeReport::getNextSpikeTime */ float getNextSpikeTime() final; - /** @copydoc brion::SpikeReport::waitUntil */ + /** @copydoc brion::SpikeReport::getLatestSpikeTime */ float getLatestSpikeTime() final; /** @copydoc brion::SpikeReport::clear */ diff --git a/brion/spikeReport.cpp b/brion/spikeReport.cpp index 04f96cf..91404ea 100644 --- a/brion/spikeReport.cpp +++ b/brion/spikeReport.cpp @@ -22,15 +22,20 @@ #include "spikeReportPlugin.h" #include "pluginInitData.h" +#include #include #include #include #include -namespace brion +namespace { +const std::string spikePluginDSONamePattern( "Brion.*SpikeReport" ); +} +namespace brion +{ namespace detail { class SpikeReport @@ -39,9 +44,22 @@ class SpikeReport typedef lunchbox::PluginFactory< SpikeReportPlugin, SpikeReportInitData > SpikePluginFactory; + class DSOPluginsLoader + { + public: + DSOPluginsLoader() + { + auto& factory = SpikePluginFactory::getInstance(); + factory.load( BRION_VERSION_ABI, lunchbox::getLibraryPaths(), + spikePluginDSONamePattern ); + } + }; + explicit SpikeReport( const SpikeReportInitData& initData ) - : plugin( SpikePluginFactory::getInstance().create( initData )) - {} + { + static DSOPluginsLoader loader; + plugin.reset( SpikePluginFactory::getInstance().create( initData )); + } boost::scoped_ptr< SpikeReportPlugin > plugin; }; diff --git a/brion/spikeReport.h b/brion/spikeReport.h index a872775..91f7a83 100644 --- a/brion/spikeReport.h +++ b/brion/spikeReport.h @@ -81,6 +81,8 @@ class SpikeReport : public boost::noncopyable * - NEST ('gdf' extension). NEST file based reports. In read mode, * shell wildcards are accepted at the file path leaf to load * multiple report files. + * Support for additional types can be added through plugins; see + * SpikeReportPlugin for the details. * * @param mode the brion::AccessMode bitmask * @throw std::runtime_error if the input URI is not handled by any diff --git a/brion/spikeReportPlugin.h b/brion/spikeReportPlugin.h index 9d73fd9..68d3e2d 100644 --- a/brion/spikeReportPlugin.h +++ b/brion/spikeReportPlugin.h @@ -54,6 +54,24 @@ typedef PluginInitData SpikeReportInitData; * } * @endcode * + * Plugins can also be provided by shared libraries discovered at runtime. + * + * In this case, the registration must be occur from an extern C function named + * LunchboxPluginRegister(): + * @code + * // in the .cpp file + * extern "C" int LunchboxPluginGetVersion() { return BRION_VERSION_ABI; } + * extern "C" bool LunchboxPluginRegister() + * { + * PluginRegisterer< MyReport > registerer; + * return true; + * } + * @endcode + * + * Plugin libraries in the LD_LIBRARY_PATH will be automatically registered if + * they provide the abovementioned C functions and follow the naming convention: + * BrionSpikeReport. + * * @version 1.4 */ class SpikeReportPlugin : public boost::noncopyable diff --git a/doc/Changelog.md b/doc/Changelog.md index 8ac0b25..4a10e2a 100644 --- a/doc/Changelog.md +++ b/doc/Changelog.md @@ -12,6 +12,10 @@ Changelog {#Changelog} Add brion::Target::parse() to resolve a given target name * [#12](https://github.com/BlueBrain/Brion/pull/12): Extended brion::Synapse to also support non-merged synapse files +* [#22](https://github.com/BlueBrain/Brion/pull/22): + SpikeReport DSO plugins in the LD_LIBRARY_PATH are loaded automatically. +* [#22](https://github.com/BlueBrain/Brion/pull/22): + spikeConverter can process spikes to and from stream-type SpikeReport plugins. ## Release 1.5.0 (2015-07-07) {changelog_1_5_0} diff --git a/tests/spikeReportStreaming.cpp b/tests/spikeReportStreaming.cpp index eaf8580..d2b26c1 100644 --- a/tests/spikeReportStreaming.cpp +++ b/tests/spikeReportStreaming.cpp @@ -81,7 +81,7 @@ BOOST_AUTO_TEST_CASE( test_stream_no_read ) boost::filesystem::path path( BBP_TESTDATA ); path /= NEST_SPIKE_REPORT_FILE; brion::SpikeReport report( brion::URI( "spikes://" + path.string( )), - brion::MODE_READ); + brion::MODE_READ ); BOOST_CHECK_EQUAL( report.getStartTime(), UNDEFINED_TIMESTAMP ); BOOST_CHECK_EQUAL( report.getEndTime(), UNDEFINED_TIMESTAMP ); @@ -93,7 +93,7 @@ BOOST_AUTO_TEST_CASE( test_stream_read_all ) boost::filesystem::path path( BBP_TESTDATA ); path /= NEST_SPIKE_REPORT_FILE; brion::SpikeReport report( brion::URI( "spikes://" + path.string( )), - brion::MODE_READ); + brion::MODE_READ ); report.waitUntil( UNDEFINED_TIMESTAMP ); @@ -107,7 +107,7 @@ BOOST_AUTO_TEST_CASE( test_stream_read_timeout ) boost::filesystem::path path( BBP_TESTDATA ); path /= NEST_SPIKE_REPORT_FILE; brion::SpikeReport report( brion::URI( "spikes://" + path.string( )), - brion::MODE_READ); + brion::MODE_READ ); BOOST_CHECK( !report.waitUntil( 1000, 1 )); }