From c67a6a6584f97cb10571bb2f705bf5c6777ee550 Mon Sep 17 00:00:00 2001 From: Juan Hernando Date: Wed, 4 Nov 2015 12:34:52 +0100 Subject: [PATCH] Added a new library with a higher level interface. New brain::Circuit and brain::Morphology classes with convenience methods needed by the Fivox VSD and compartment loaders. --- CMakeLists.txt | 4 +- README.md | 23 +++- brain/CMakeLists.txt | 16 +++ brain/circuit.cpp | 204 ++++++++++++++++++++++++++++ brain/circuit.h | 92 +++++++++++++ brain/files.cmake | 18 +++ brain/morphology.cpp | 215 ++++++++++++++++++++++++++++++ brain/morphology.h | 121 +++++++++++++++++ brain/types.h | 55 ++++++++ brion/blueConfig.cpp | 27 ++-- brion/blueConfig.h | 31 +++-- brion/enums.h | 5 +- brion/files.cmake | 20 +-- brion/morphology.cpp | 16 +-- brion/morphology.h | 6 +- brion/morphologyPlugin.h | 4 +- brion/types.h | 1 - doc/Changelog.md | 6 +- tests/CMakeLists.txt | 2 +- tests/circuit.cpp | 135 +++++++++++++++++++ tests/data/h5/createNeuron.py | 67 ++++++++++ tests/data/h5/test_neuron.h5 | Bin 0 -> 5528 bytes tests/morphology.cpp | 301 ++++++++++++++++++++++++++++++++---------- 23 files changed, 1240 insertions(+), 129 deletions(-) create mode 100644 brain/CMakeLists.txt create mode 100644 brain/circuit.cpp create mode 100644 brain/circuit.h create mode 100644 brain/files.cmake create mode 100644 brain/morphology.cpp create mode 100644 brain/morphology.h create mode 100644 brain/types.h create mode 100644 tests/data/h5/createNeuron.py create mode 100644 tests/data/h5/test_neuron.h5 diff --git a/CMakeLists.txt b/CMakeLists.txt index e845ec0..9fe886b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,11 +36,13 @@ common_package_post() list(APPEND BRION_DEPENDENT_LIBRARIES Boost Lunchbox vmmlib) add_subdirectory(brion) +add_subdirectory(brain) add_subdirectory(apps) add_subdirectory(tests) include(CPackConfig) set(DOXYGEN_MAINPAGE_MD README.md) -set(DOXYGEN_EXTRA_INPUT ${PROJECT_SOURCE_DIR}/README.md) +set(DOXYGEN_EXPAND_AS_DEFINED "BRAIN_API BRION_API") +set(DOXYGEN_EXTRA_INPUT "${PROJECT_SOURCE_DIR}/README.md ${CMAKE_INSTALL_PREFIX}/include/brain") include(DoxygenRule) diff --git a/README.md b/README.md index 4d98da0..bd9df99 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ ![](doc/BBPLOGO350.jpg) -Welcome to Brion, a C++ library for read and write access to Blue Brain data +Welcome to Brion, a C++ project for read and write access to Blue Brain data structures, including BlueConfig/CircuitConfig, Circuit, CompartmentReport, Mesh, Morphology, Synapse and Target files. @@ -18,7 +18,15 @@ To keep track of the changes between releases check the [changelog](@ref Changel # Features {#Features} -Brion provides the following major features: +Brion provides two libraries Brion and Brain. The former is a collection of file +readers and writers intended for low level access to the data model. The latter +is a set of higher level classes that wrap low level data objects with a more +user friendly API. + +# IO library + +This is the core library provided by Brion. It includes classes for reading +and writing files which store the Blue Brain data model. * Fast and low-overhead read access to: * Blue configs (brion::BlueConfig) @@ -26,7 +34,8 @@ Brion provides the following major features: * H5 Synapses data (brion::SynapseSummary, brion::Synapse) * Target (brion::Target) * BBP binary meshes (brion::Mesh) - * BBP H5 morphologies and SWC morphologies (brion::Morphology) + * BBP H5 morphologies and SWC morphologies (brion::Morphology and + brion::morphologies) * Compartment reports (brion::CompartmentReport) * Spike reports (brion::SpikeReport) * Fast and low-overhead write access to: @@ -38,6 +47,14 @@ Brion provides the following major features: [Lunchbox](http://eyescale.github.io/Lunchbox-1.8/index.html), [Boost](http://www.boost.org/doc/libs). +## High level library + +The higher level library is called Brain and it provides: + +* A circuit class to facilitate loading information about cells and + morphologies in local and global circuit coordinates. +* A morphology class with higher level functions to deal with morphologies. + # Building {#Building} Brion is a cross-platform library, designed to run on any modern operating diff --git a/brain/CMakeLists.txt b/brain/CMakeLists.txt new file mode 100644 index 0000000..fc3bb37 --- /dev/null +++ b/brain/CMakeLists.txt @@ -0,0 +1,16 @@ +# Copyright (c) 2013-2015, EPFL/Blue Brain Project +# Juan Hernando +# +# This file is part of Brion +# + +source_group(\\ FILES CMakeLists.txt) + +include(files.cmake) + +set(BRAIN_PUBLIC_INCLUDE_DIRECTORIES ${Boost_INCLUDE_DIRS}) +set(BRAIN_LINK_LIBRARIES PUBLIC Brion vmmlib) +set(BRAIN_INCLUDE_NAME brain) +set(BRAIN_NAMESPACE brain) + +common_library(Brain) diff --git a/brain/circuit.cpp b/brain/circuit.cpp new file mode 100644 index 0000000..c437ac9 --- /dev/null +++ b/brain/circuit.cpp @@ -0,0 +1,204 @@ +/* Copyright (c) 2013-2015, EPFL/Blue Brain Project + * Juan Hernando + * + * This file is part of Brion + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License version 3.0 as published + * by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "circuit.h" +#include "morphology.h" + +#include +#include +#include +#include + +#include + +#include + +namespace brain +{ + +class Circuit::Impl +{ +public: + Impl( const brion::BlueConfig& config ) + : _circuit( config.getCircuitSource( )) + , _morphologySource( config.getMorphologySource( )) + , _circuitTarget( config.getCircuitTarget( )) + , _targetParsers( config.getTargets( )) + { + } + + GIDSet getGIDs( const std::string& target ) const + { + return brion::Target::parse( + _targetParsers, target.empty() ? _circuitTarget : target ); + } + + const brion::Circuit& getCircuit() const + { + return _circuit; + } + + URI getMorphologyURI( const std::string& name ) const + { + URI uri; + uri.setPath( _morphologySource.getPath() + "/" + name + ".h5" ); + uri.setScheme( "file" ); + return uri; + } + +private: + const brion::Circuit _circuit; + const brion::URI _morphologySource; + const std::string _circuitTarget; + const brion::Targets _targetParsers; + +}; + +Circuit::Circuit( const URI& source ) + : _impl( new Impl( brion::BlueConfig( source.getPath( )))) +{ +} + +Circuit::Circuit( const brion::BlueConfig& config ) + : _impl( new Impl( config )) +{ +} + +Circuit::~Circuit() +{ + delete _impl; +} + +GIDSet Circuit::getGIDs( const std::string& target ) const +{ + return _impl->getGIDs( target ); +} + +URIs Circuit::getMorphologyURIs( const GIDSet& gids ) const +{ + const brion::NeuronMatrix& matrix = + _impl->getCircuit().get( gids, brion::NEURON_MORPHOLOGY_NAME ); + + URIs uris; + uris.reserve( gids.size( )); + for( size_t index = 0; index != gids.size(); ++index ) + uris.push_back( _impl->getMorphologyURI( matrix[index][0] )); + return uris; +} + +Morphologies Circuit::loadMorphologies( const GIDSet& gids, + Coordinates coords ) const +{ + // Creating all the brion::Morphologies objects first to detect a missing + // morphology before loading anything. + const URIs& uris = getMorphologyURIs( gids ); + std::vector< boost::shared_ptr< brion::Morphology > > raw; + raw.reserve( uris.size( )); + BOOST_FOREACH( const URI& uri, uris ) + raw.push_back( boost::shared_ptr< brion::Morphology >( + new brion::Morphology( uri.getPath( )))); + + Morphologies result; + result.reserve( gids.size( )); + + if( coords == COORDINATES_GLOBAL ) + { + const Matrix4fs& transforms = getTransforms( gids ); + for( size_t i = 0; i != uris.size(); ++i ) + result.push_back( + MorphologyPtr( new Morphology( *raw[i], transforms[i] ))); + } + else + { + std::map< std::string, MorphologyPtr > loaded; + for( size_t i = 0; i != uris.size(); ++i ) + { + MorphologyPtr& morphology = loaded[uris[i].getPath()]; + if( !morphology ) + morphology.reset( new Morphology( *raw[i] )); + result.push_back( morphology ); + } + } + return result; +} + +Vector3fs Circuit::getPositions( const GIDSet& gids ) const +{ + const brion::NeuronMatrix& data = _impl->getCircuit().get( + gids, brion::NEURON_POSITION_X | brion::NEURON_POSITION_Y | + brion::NEURON_POSITION_Z ); + + brion::GIDSet::const_iterator gid = gids.begin(); + Vector3fs positions( gids.size( )); + #pragma omp parallel for + for( size_t i = 0; i < gids.size(); ++i ) + { + try + { + positions[i] = + brion::Vector3f( boost::lexical_cast< float >( data[i][0] ), + boost::lexical_cast< float >( data[i][1] ), + boost::lexical_cast< float >( data[i][2] )); + } + catch( const boost::bad_lexical_cast& ) + { + LBWARN << "Error parsing circuit position or orientation for gid " + << *gid << ". Morphology not transformed." << std::endl; + positions[i] = Vector3f::ZERO; + } + #pragma omp critical (brain_circuit_getPositions) + ++gid; + } + return positions; +} + +Matrix4fs Circuit::getTransforms( const GIDSet& gids ) const +{ + const brion::NeuronMatrix& data = _impl->getCircuit().get( + gids, brion::NEURON_POSITION_X | brion::NEURON_POSITION_Y | + brion::NEURON_POSITION_Z | brion::NEURON_ROTATION ); + + brion::GIDSet::const_iterator gid = gids.begin(); + Matrix4fs transforms( gids.size(), Matrix4f::IDENTITY ); + #pragma omp parallel for + for( size_t i = 0; i < gids.size(); ++i ) + { + Matrix4f& matrix = transforms[i]; + try + { + matrix.rotate_y( + boost::lexical_cast< float >( data[i][3] ) * ( M_PI/180.0f )); + matrix.set_translation( + Vector3f( boost::lexical_cast< float >( data[i][0] ), + boost::lexical_cast< float >( data[i][1] ), + boost::lexical_cast< float >( data[i][2] ))); + } + catch( const boost::bad_lexical_cast& ) + { + LBWARN << "Error parsing circuit position or orientation for gid " + << *gid << ". Morphology not transformed." << std::endl; + matrix = Matrix4f::IDENTITY; + } + #pragma omp critical (brain_circuit_getTransforms) + ++gid; + } + return transforms; +} + +} diff --git a/brain/circuit.h b/brain/circuit.h new file mode 100644 index 0000000..eef0c30 --- /dev/null +++ b/brain/circuit.h @@ -0,0 +1,92 @@ +/* Copyright (c) 2013-2015, EPFL/Blue Brain Project + * Juan Hernando + * + * This file is part of Brion + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License version 3.0 as published + * by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef BRAIN_CIRCUIT +#define BRAIN_CIRCUIT + +#include +#include + +#include + +namespace brain +{ + +/** Read access to a circuit database + * + * This class provides convenience functions to access information about the + * cells inside the circuit and their morphologies. + */ +class Circuit : public boost::noncopyable +{ +public: + /** Coordinate system to use for circuit morphologies */ + enum Coordinates + { + COORDINATES_GLOBAL, + COORDINATES_LOCAL + }; + + /** + * Opens a circuit for read access. + * @param source the URI to the CircuitConfig or BlueConfig file. + */ + BRAIN_API explicit Circuit( const URI& source ); + + /** + * Opens a circuit for read access. + * @param config The object representing the BlueConfig. + */ + BRAIN_API explicit Circuit( const brion::BlueConfig& blueConfig ); + + BRAIN_API ~Circuit(); + + /** + * @return The set of GIDs for the given target name. If empty it will + * return the circuit target specified on the + * BlueConfig/CircuitConfig file. + * If the target cannot be found or an empty string was given and + * there is no circuit target, the return value is an empty set. + */ + BRAIN_API GIDSet getGIDs( const std::string& target = "" ) const; + + /** @return The set of URIs to access the morphologies of the given cells */ + BRAIN_API URIs getMorphologyURIs( const GIDSet& gids ) const; + + /** + * @return The list of morpholgies for the GID set. If local coordinates + * are requested, morpholgies that are repeated in the circuit + * will shared the same Morphology object in the list. If global + * coordinates are requested, all Morphology objects are unique. + */ + BRAIN_API Morphologies loadMorphologies( const GIDSet& gids, + Coordinates coords ) const; + + /** @return The positions of the given cells. */ + BRAIN_API Vector3fs getPositions( const GIDSet& gids ) const; + /** @return The local to world transformations of the given cells. */ + BRAIN_API Matrix4fs getTransforms( const GIDSet& gids ) const; + +private: + class Impl; + Impl* _impl; +}; + +} +#endif diff --git a/brain/files.cmake b/brain/files.cmake new file mode 100644 index 0000000..bd86eaf --- /dev/null +++ b/brain/files.cmake @@ -0,0 +1,18 @@ +# Copyright (c) 2013-2015, EPFL/Blue Brain Project +# Juan Hernando +# +# This file is part of Brion +# + +set(BRAIN_PUBLIC_HEADERS + circuit.h + morphology.h + ) + +set(BRAIN_HEADERS + ) + +set(BRAIN_SOURCES + circuit.cpp + morphology.cpp + ) diff --git a/brain/morphology.cpp b/brain/morphology.cpp new file mode 100644 index 0000000..4feb440 --- /dev/null +++ b/brain/morphology.cpp @@ -0,0 +1,215 @@ + +/* Copyright (c) 2013-2015, EPFL/Blue Brain Project + * Juan Hernando + * + * This file is part of Brion + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License version 3.0 as published + * by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "morphology.h" + +#include +#include + +#include + +#include + +#include + +namespace brain +{ + +class Morphology::Impl +{ +public: + const brion::Vector4fsPtr points; + const brion::Vector2isPtr sections; + const brion::SectionTypesPtr types; + const brion::Vector2isPtr apicals; + Vector3f somaPosition; + + Impl( const brion::Morphology& morphology ) + : points( morphology.readPoints( brion::MORPHOLOGY_UNDEFINED )) + , sections( morphology.readSections( brion::MORPHOLOGY_UNDEFINED )) + , types( morphology.readSectionTypes( )) + , apicals( morphology.readApicals( )) + , somaPosition( Vector3f::ZERO ) // the soma is assumed to be + // centered at the 0, 0, 0 + { + } + + uint32_ts getSectionIDs( const SectionTypes& requestedTypes ) const + { + std::bitset< brion::SECTION_APICAL_DENDRITE > bits; + BOOST_FOREACH( const SectionType type, requestedTypes ) + bits[size_t( type )] = true; + + uint32_ts result; + for( size_t i = 0; i != types->size(); ++i ) + { + const SectionType type = ( *types )[i]; + if( type == brion::SECTION_ALL ) + LBWARN << "Unknown section type " << int(type) << std::endl; + else + if( bits[size_t( type )] ) + result.push_back( i ); + } + return result; + } + + Vector4fs getSectionSamples( + const size_t sectionID, const floats& samplePoints ) const + { + // If the section is the soma return directly the soma position. + if(( *types )[sectionID] == brion::SECTION_SOMA ) + { + Vector4fs result; + for( size_t i = 0; i != samplePoints.size(); ++i ) + result.push_back( somaPosition ); + return result; + } + + const size_t start = ( *sections )[sectionID][0]; + const size_t end = sectionID == sections->size() - 1 ? + points->size() : ( *sections )[sectionID + 1][0]; + + Vector4fs result; + + if( end <= start ) + { + LBWARN << "Trying to sample broken morphology or empty section " + << sectionID << std::endl; + return result; + } + + result.reserve( samplePoints.size( )); + + // Dealing with the degenerate case of single point sections. + if( start + 1 == end ) + { + for( size_t i = 0; i != samplePoints.size(); ++i) + result.push_back(( *points )[start] ); + return result; + } + + // Computing the accumulated length at each segment of the section. + floats accumLengths; + accumLengths.reserve( end - start ); + accumLengths.push_back(0); + for( size_t i = start; i != end - 1; ++i ) + { + const Vector4f& segmentStart = ( *points )[i]; + const Vector4f& segmentEnd = ( *points )[i + 1]; + accumLengths.push_back( accumLengths.back() + + ( segmentEnd - segmentStart ).length( )); + } + const float totalLength = accumLengths.back(); + + BOOST_FOREACH( const float point, samplePoints ) + { + // Finding the segment index for the requested sampling position. + const float length = + std::max( 0.f, std::min( 1.f, point )) * totalLength; + size_t index = 0; + for ( ; accumLengths[index + 1] < length && + index < accumLengths.size() - 1; ++index ) + ; + + // Interpolating the cross section at point. + const float alpha = ( length - accumLengths[index] ) / + ( accumLengths[index + 1] - accumLengths[index] ); + const Vector4f sample = ( *points )[start + index + 1] * alpha + + ( *points )[start + index] * (1 - alpha ); + result.push_back( sample ); + } + + return result; + } + + void transform( const Matrix4f& transformation ) + { + #pragma omp parallel for + for( size_t i = 0; i < points->size(); ++i) + { + Vector4f& p = ( *points )[i]; + const Vector3f pp = transformation * p.get_sub_vector< 3 >(); + p.get_sub_vector< 3 >() = pp; + } + somaPosition = transformation * somaPosition; + } +}; + +Morphology::Morphology( const URI& source, const Matrix4f& transform ) + : _impl( new Impl( brion::Morphology( source.getPath( )))) +{ + _impl->transform( transform ); +} + +Morphology::Morphology( const brion::Morphology& morphology, + const Matrix4f& transform ) + : _impl( new Impl( morphology )) +{ + _impl->transform( transform ); +} + +Morphology::Morphology( const URI& source ) + : _impl( new Impl( brion::Morphology( source.getPath( )))) +{ +} + +Morphology::Morphology( const brion::Morphology& morphology ) + : _impl( new Impl( morphology )) +{ +} + +Morphology::~Morphology() +{ + delete _impl; +} + +const Vector4fs& Morphology::getPoints() const +{ + return *_impl->points; +} + +const Vector2is& Morphology::getSections() const +{ + return *_impl->sections; +} + +const SectionTypes& Morphology::getSectionTypes() const +{ + return *_impl->types; +} + +const Vector2is& Morphology::getApicals() const +{ + return *_impl->apicals; +} + +uint32_ts Morphology::getSectionIDs( const SectionTypes& types ) const +{ + return _impl->getSectionIDs( types ); +} + +Vector4fs Morphology::getSectionSamples( + const size_t sectionID, const floats& points ) const +{ + return _impl->getSectionSamples( sectionID, points ); +} + +} + diff --git a/brain/morphology.h b/brain/morphology.h new file mode 100644 index 0000000..66c10d8 --- /dev/null +++ b/brain/morphology.h @@ -0,0 +1,121 @@ + +/* Copyright (c) 2013-2015, EPFL/Blue Brain Project + * Juan Hernando + * + * This file is part of Brion + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License version 3.0 as published + * by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef BRAIN_MORPHOLOGY +#define BRAIN_MORPHOLOGY + +#include +#include + +#include + +namespace brain +{ + +/** + * Wrapper around brion::Morphology with higher level functions. + * + * This class provides methods to facilitate some queries about morphologies + * in the context of circuits. + * Morphologies can be loaded with a transformation applied to its points, + * which is useful for operating in global circuit coordinates. + * The transformation is applied at construction so it cannot be modified or + * reverted. + * + * Access to the raw data fields is still provided by getter functions. + * + * @version unstable + */ +class Morphology : public boost::noncopyable +{ +public: + /** + * Create a morphology from a URI, load all the data and transform + * the points. + * @param source URI of the morphology data source. + * @param transform the transformation matrix to apply to the points. + * Radii will not be affected by this transformation. + */ + BRAIN_API Morphology( const URI& source, const Matrix4f& transform ); + + /** + * Create a morphology from a brion::Morphology, load all the data + * and transform the points. + * @param morphology the brion::Morphology to load from. + * @param transform the transformation matrix to apply to the points. + * Radii will not be affected by this transformation. + */ + BRAIN_API Morphology( const brion::Morphology& morphology, + const Matrix4f& transform ); + + /** + * Create a morphology from a URI and load all the data. + * @param source URI of the morphology data source. + */ + BRAIN_API Morphology( const URI& source ); + + /** + * Create a morphology from a brion::Morphology and load all the data. + * @param morphology the brion::Morphology to load from. + */ + BRAIN_API Morphology( const brion::Morphology& morphology ); + + BRAIN_API ~Morphology(); + + /** @copydoc brion::Morphology::getPoints */ + BRAIN_API const Vector4fs& getPoints() const; + + /** @copydoc brion::Morphology::getSections */ + BRAIN_API const Vector2is& getSections() const; + + /** @copydoc brion::Morphology::getSectionTypes */ + BRAIN_API const SectionTypes& getSectionTypes() const; + + /** @copydoc brion::Morphology::getApicals */ + BRAIN_API const Vector2is& getApicals() const; + + /** + * Return the list of ids for the given section types. + */ + BRAIN_API uint32_ts getSectionIDs( const SectionTypes& types ) const; + + /** + * Return a list of points sampling a sections at discrete locations or all + * the available points of no sample locations are given. + * + * @param sectionID Section to sample. If the section is the soma this + * function will return the soma position for all sampling positions. + * The soma position is assumed to be (0, 0, 0) unless the morphology + * has been transformed. + * @param points Normalized positions of the sample points along the + * section. Values will be clampled to [0, 1] before sampling. + * @return The vector with all the points of the section if points is empty. + * The section sampled at the given points otherwise. + */ + BRAIN_API Vector4fs getSectionSamples( + size_t sectionID, const floats& points = brion::floats( )) const; + +private: + class Impl; + Impl* const _impl; +}; + +} +#endif diff --git a/brain/types.h b/brain/types.h new file mode 100644 index 0000000..ee34ef8 --- /dev/null +++ b/brain/types.h @@ -0,0 +1,55 @@ +/* Copyright (c) 2013-2015, EPFL/Blue Brain Project + * Juan Hernando + * + * This file is part of Brion + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License version 3.0 as published + * by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef BRAIN_TYPES +#define BRAIN_TYPES + +#include + +/** @namespace brain Algorithmic interface to Blue Brain data model */ +namespace brain +{ + +class Circuit; +class Morphology; +typedef boost::shared_ptr< Morphology > MorphologyPtr; +typedef std::vector< MorphologyPtr > Morphologies; + +using vmml::Vector2i; +using vmml::Vector3f; +using vmml::Vector4f; +using vmml::Matrix4f; + +using brion::Vector2is; +using brion::Vector3fs; +using brion::Vector4fs; +typedef std::vector< Matrix4f > Matrix4fs; +using brion::uint32_ts; +using brion::floats; + +using brion::MorphologyRepairStage; +using brion::SectionType; +using brion::SectionTypes; +using brion::GIDSet; +using brion::URI; +typedef std::vector< URI > URIs; + +} +#endif + diff --git a/brion/blueConfig.cpp b/brion/blueConfig.cpp index fcc55d5..b24e2cf 100644 --- a/brion/blueConfig.cpp +++ b/brion/blueConfig.cpp @@ -233,6 +233,18 @@ const std::string& BlueConfig::get( const BlueConfigSection section, return _impl->get( section, sectionName, key ); } +brion::Targets BlueConfig::getTargets() const +{ + const std::string& run = _impl->getRun(); + brion::Targets targets; + targets.push_back( brion::Target( + get( brion::CONFIGSECTION_RUN, run, BLUECONFIG_NRN_PATH_KEY ) + + CIRCUIT_TARGET_FILE )); + targets.push_back( brion::Target( + get( brion::CONFIGSECTION_RUN, run, BLUECONFIG_TARGET_FILE_KEY ))); + return targets; +} + URI BlueConfig::getCircuitSource() const { URI uri; @@ -312,19 +324,10 @@ std::string BlueConfig::getCircuitTarget() const return _impl->getCircuitTarget(); } -GIDSet BlueConfig::parseTarget( std::string target ) const +GIDSet BlueConfig::parseTarget( const std::string& target ) const { - if( target.empty( )) - target = _impl->getCircuitTarget(); - - const std::string& run = _impl->getRun(); - brion::Targets targets; - targets.push_back( brion::Target( - get( brion::CONFIGSECTION_RUN, run, BLUECONFIG_NRN_PATH_KEY ) + - CIRCUIT_TARGET_FILE )); - targets.push_back( brion::Target( - get( brion::CONFIGSECTION_RUN, run, BLUECONFIG_TARGET_FILE_KEY ))); - return brion::Target::parse( targets, target ); + return brion::Target::parse( + getTargets(), target.empty() ? _impl->getCircuitTarget() : target ); } float BlueConfig::getTimestep() const diff --git a/brion/blueConfig.h b/brion/blueConfig.h index bbb1695..febbf34 100644 --- a/brion/blueConfig.h +++ b/brion/blueConfig.h @@ -74,6 +74,12 @@ class BlueConfig : public boost::noncopyable const std::string& sectionName, const std::string& key ) const; + /** + * @return the user and circuit file target parsers. + * @version 1.7 + */ + BRION_API Targets getTargets() const; + /** Get value as type T for key in given section. * * @param section type of section to get the value from @@ -92,20 +98,24 @@ class BlueConfig : public boost::noncopyable } //@} + /** @name Semantic read API */ //@{ - /** @return the URI to the circuit information. @sa Circuit + /** + * @return the URI to the circuit information. @sa Circuit * @version 1.7 */ BRION_API URI getCircuitSource() const; - /** @return the URI to the location of synapse nrn files. + /* + * @return the URI to the location of synapse nrn files. * @version 1.7 */ BRION_API URI getSynapseSource() const; - /** @return the full path to the morphology database. A suffix may be - * prepended to the to the bare path from the BlueConfig. + /** + * @return the full path to the morphology database. A suffix may be + * prepended to the to the bare path from the BlueConfig. * @version 1.7 */ BRION_API URI getMorphologySource() const; @@ -115,21 +125,24 @@ class BlueConfig : public boost::noncopyable */ BRION_API URI getReportSource( const std::string& report ) const; - /** @return the URI to the default spike data source. @sa SpikeReport + /** + * @return the URI to the default spike data source. @sa SpikeReport * @version 1.7 */ BRION_API URI getSpikeSource() const; - /** @return the name of the circuit target + /** + * @return the name of the circuit target * @version 1.7 */ std::string getCircuitTarget() const; - /** @return the set of GIDs for the given target, or the circuit target if - * the given target is empty. + /** + * @return the set of GIDs for the given target, or the circuit target if + * the given target is empty. * @version 1.7 */ - BRION_API brion::GIDSet parseTarget( std::string target ) const; + BRION_API GIDSet parseTarget( const std::string& target ) const; /** @return the simulation timestep in ms or NaN if undefined. * @version 1.7 diff --git a/brion/enums.h b/brion/enums.h index 388de03..c669ccd 100644 --- a/brion/enums.h +++ b/brion/enums.h @@ -70,7 +70,7 @@ enum MeshStructure MESH_PSD, //!< post-synaptic densities MESH_AZ, //!< active zones MESH_ENDO, //!< endoplasmic reticulum - MESH_ALL + MESH_ALL //!< @internal must be last }; /** The supported formats for mesh files. */ @@ -159,7 +159,7 @@ enum TargetType { TARGET_CELL = 0, TARGET_COMPARTMENT, - TARGET_ALL + TARGET_ALL //!< @internal must be last }; /** Classification of neuron substructures. */ @@ -170,6 +170,7 @@ enum SectionType SECTION_AXON, SECTION_DENDRITE, //!< general or basal dendrite (near to soma) SECTION_APICAL_DENDRITE, //!< apical dendrite (far from soma) + SECTION_ALL //!< @internal must be last }; /** The supported attributes of a synapse. */ diff --git a/brion/files.cmake b/brion/files.cmake index e362743..0485af4 100644 --- a/brion/files.cmake +++ b/brion/files.cmake @@ -43,6 +43,16 @@ set(BRION_HEADERS ) set(BRION_SOURCES + blueConfig.cpp + circuit.cpp + compartmentReport.cpp + mesh.cpp + morphology.cpp + spikeReport.cpp + synapseSummary.cpp + synapse.cpp + target.cpp + detail/lockHDF5.cpp plugin/compartmentReportCommon.cpp plugin/compartmentReportBinary.cpp plugin/compartmentReportHDF5.cpp @@ -55,14 +65,4 @@ set(BRION_SOURCES plugin/spikeReportFile.cpp plugin/spikeReportNEST.cpp plugin/spikeReportSimpleStreamer.cpp - blueConfig.cpp - circuit.cpp - compartmentReport.cpp - mesh.cpp - morphology.cpp - spikeReport.cpp - synapseSummary.cpp - synapse.cpp - target.cpp - detail/lockHDF5.cpp ) diff --git a/brion/morphology.cpp b/brion/morphology.cpp index 4e41b9e..7f00c4d 100644 --- a/brion/morphology.cpp +++ b/brion/morphology.cpp @@ -29,35 +29,31 @@ namespace brion { -namespace detail -{ -class Morphology +class Morphology::Impl { public: typedef lunchbox::PluginFactory< MorphologyPlugin, MorphologyInitData > MorphologyPluginFactory; - explicit Morphology( const MorphologyInitData& initData ) + explicit Impl( const MorphologyInitData& initData ) : plugin( MorphologyPluginFactory::getInstance().create( initData )) { } boost::scoped_ptr< MorphologyPlugin > plugin; }; -} Morphology::Morphology( const std::string& source ) - : _impl( new detail::Morphology( - MorphologyInitData( lunchbox::URI( source )))) + : _impl( new Impl( MorphologyInitData( lunchbox::URI( source )))) { } Morphology::Morphology( const std::string& target, const MorphologyVersion version, const bool overwrite ) - : _impl( new detail::Morphology( - MorphologyInitData( lunchbox::URI( target ), version, - overwrite ? MODE_OVERWRITE : MODE_WRITE ))) + : _impl( new Impl( MorphologyInitData( lunchbox::URI( target ), version, + overwrite ? MODE_OVERWRITE : + MODE_WRITE ))) { } diff --git a/brion/morphology.h b/brion/morphology.h index 5bd8727..eaa7836 100644 --- a/brion/morphology.h +++ b/brion/morphology.h @@ -27,8 +27,6 @@ namespace brion { -namespace detail { class Morphology; } - /** Read & write access to a Morphology file. * * Following RAII, this class is ready to use after the creation and will ensure @@ -143,9 +141,9 @@ class Morphology : public boost::noncopyable //@} private: - detail::Morphology* const _impl; + class Impl; + Impl* const _impl; }; } - #endif diff --git a/brion/morphologyPlugin.h b/brion/morphologyPlugin.h index e13c064..80cc482 100644 --- a/brion/morphologyPlugin.h +++ b/brion/morphologyPlugin.h @@ -38,11 +38,11 @@ namespace brion class MorphologyInitData : public PluginInitData { public: - explicit MorphologyInitData( const lunchbox::URI& uri ) + explicit MorphologyInitData( const URI& uri ) : PluginInitData( uri, MODE_READ ) {} - MorphologyInitData( const lunchbox::URI& uri, + MorphologyInitData( const URI& uri, const MorphologyVersion version, const unsigned int accessMode ) : PluginInitData( uri, accessMode ) diff --git a/brion/types.h b/brion/types.h index db4e1da..fc66484 100644 --- a/brion/types.h +++ b/brion/types.h @@ -53,7 +53,6 @@ class Mesh; class Morphology; class SpikeReport; class SpikeReportPlugin; - class Synapse; class SynapseSummary; class Target; diff --git a/doc/Changelog.md b/doc/Changelog.md index a28e92e..c876e61 100644 --- a/doc/Changelog.md +++ b/doc/Changelog.md @@ -1,10 +1,14 @@ Changelog {#Changelog} ========= -# git master {#master) +# git master * [31](https://github.com/BlueBrain/Brion/pull/31): Fix crash while reading more than `ulimit -Sn` (1024 default) NEST gdf files +* [30](https://github.com/BlueBrain/Brion/pull/30): + Added a new library, Brain, to provide higher level functions. The library + provides the Circuit and Morphology classes to deal with morphologies at + circuit level. * [29](https://github.com/BlueBrain/Brion/pull/29): New member functions in brion::BlueConfig to provide a semantic API. * [28](https://github.com/BlueBrain/Brion/pull/28): diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 0b66362..fb3fd57 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -15,7 +15,7 @@ endif() configure_file(paths.h.in ${PROJECT_BINARY_DIR}/tests/paths.h) include_directories(${PROJECT_BINARY_DIR}/tests) -set(TEST_LIBRARIES ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY} Brion BBPTestData) +set(TEST_LIBRARIES ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY} Brain Brion BBPTestData) if(LUNCHBOX_USE_SKV) common_package(skv REQUIRED) include_directories(${SKV_INCLUDE_DIRS}) diff --git a/tests/circuit.cpp b/tests/circuit.cpp index 5365031..ca7c5e7 100644 --- a/tests/circuit.cpp +++ b/tests/circuit.cpp @@ -29,6 +29,7 @@ */ #include +#include #include #define BOOST_TEST_MODULE Circuit @@ -148,3 +149,137 @@ BOOST_AUTO_TEST_CASE(test_types) BOOST_CHECK_EQUAL( etypes[6], "bAD" ); BOOST_CHECK_EQUAL( etypes[7], "cST" ); } + +BOOST_AUTO_TEST_CASE( brain_circuit_constructor ) +{ + brain::Circuit circuit( brion::URI( bbp::test::getBlueconfig( ))); + brain::Circuit circuit2( brion::BlueConfig( bbp::test::getBlueconfig( ))); + BOOST_CHECK_THROW( brain::Circuit( brion::URI( "pluto" )), + std::runtime_error ); +} + +BOOST_AUTO_TEST_CASE( brain_circuit_target ) +{ + const brain::Circuit circuit( brion::URI( bbp::test::getBlueconfig( ))); + const brion::BlueConfig config( bbp::test::getBlueconfig( )); + + brion::GIDSet first = circuit.getGIDs(); + brion::GIDSet second = config.parseTarget( "" ); + BOOST_CHECK_EQUAL_COLLECTIONS( first.begin(), first.end(), + second.begin(), second.end( )); + + first = circuit.getGIDs( "Column" ); + second = config.parseTarget( "Column" ); + BOOST_CHECK_EQUAL_COLLECTIONS( first.begin(), first.end(), + second.begin(), second.end( )); + + first = circuit.getGIDs( "AllL5CSPC" ); + second = config.parseTarget( "AllL5CSPC" ); + BOOST_CHECK_EQUAL_COLLECTIONS( first.begin(), first.end(), + second.begin(), second.end( )); +} + +BOOST_AUTO_TEST_CASE( brain_circuit_positions ) +{ + const brain::Circuit circuit( brion::URI( bbp::test::getBlueconfig( ))); + + brion::GIDSet gids; + gids.insert(1); + gids.insert(2); + // This call also tests brain::Circuit::getMorphologyURIs + const brain::Vector3fs positions = circuit.getPositions( gids ); + BOOST_CHECK_EQUAL( positions.size(), gids.size( )); + + typedef brain::Vector3f V3; + BOOST_CHECK_SMALL( + ( positions[0] - V3( 54.410675, 1427.669280, 124.882234 )).length(), + 0.000001f ); + BOOST_CHECK_SMALL( + ( positions[1] - V3( 28.758332, 1393.556264, 98.258210 )).length(), + 0.000001f ); +} + +namespace +{ +void _checkMorphology( const brain::Morphology& morphology, + const std::string& other ) +{ + const brion::Morphology reference( + BBP_TESTDATA + ( "/local/morphologies/01.07.08/h5/" + other )); + BOOST_CHECK( morphology.getPoints() == + *reference.readPoints( brion::MORPHOLOGY_UNDEFINED )); +} +void _checkMorphology( const brain::Morphology& morphology, + const std::string& other, + const brain::Matrix4f& transform ) +{ + const brain::Morphology reference( + brion::URI( + BBP_TESTDATA + ( "/local/morphologies/01.07.08/h5/" + other)), + transform ); + const brain::Vector4fs& p = morphology.getPoints(); + const brain::Vector4fs& q = reference.getPoints(); + BOOST_REQUIRE( p.size() == q.size( )); + for( size_t i = 0; i != p.size(); ++i ) + BOOST_CHECK_SMALL(( p[i] - q[i] ).length( ), 0.0001f); +} +} + +BOOST_AUTO_TEST_CASE( load_bad_morphologies ) +{ + const brain::Circuit circuit( brion::URI( bbp::test::getBlueconfig( ))); + + brion::GIDSet gids; + gids.insert( 10000000 ); + BOOST_CHECK_THROW( + circuit.loadMorphologies( gids, brain::Circuit::COORDINATES_LOCAL ), + std::runtime_error ); +} + +BOOST_AUTO_TEST_CASE( load_local_morphologies ) +{ + const brain::Circuit circuit( brion::URI( bbp::test::getBlueconfig( ))); + + brion::GIDSet gids; + for( uint32_t gid = 1; gid < 500; gid += 75) + gids.insert(gid); + // This call also tests brain::Circuit::getMorphologyURIs + const brain::Morphologies morphologies = + circuit.loadMorphologies( gids, brain::Circuit::COORDINATES_LOCAL ); + BOOST_CHECK_EQUAL( morphologies.size(), gids.size( )); + + // Checking the first morphology + _checkMorphology( *morphologies[0], "R-C010306G.h5" ); + + // Checking shared morphologies + gids.clear(); + gids.insert(2); + gids.insert(4); + gids.insert(6); + const brain::Morphologies repeated = + circuit.loadMorphologies( gids, brain::Circuit::COORDINATES_LOCAL ); + + BOOST_CHECK_EQUAL( repeated.size(), gids.size( )); + BOOST_CHECK_EQUAL( repeated[0].get(), repeated[2].get( )); + BOOST_CHECK( repeated[0].get() != repeated[1].get( )); +} + +BOOST_AUTO_TEST_CASE( load_global_morphologies ) +{ + const brain::Circuit circuit( brion::URI( bbp::test::getBlueconfig( ))); + + brion::GIDSet gids; + for( uint32_t gid = 1; gid < 500; gid += 75) + gids.insert(gid); + const brain::Morphologies morphologies = + circuit.loadMorphologies( gids, brain::Circuit::COORDINATES_GLOBAL ); + BOOST_CHECK_EQUAL( morphologies.size(), gids.size( )); + + // Checking the first morphology + brain::Matrix4f matrix( brain::Matrix4f::IDENTITY ); + matrix.rotate_y( -75.992327 * M_PI/180.0f ); + matrix.set_translation( + brain::Vector3f( 54.410675, 1427.669280, 124.882234 )); + + _checkMorphology( *morphologies[0], "R-C010306G.h5", matrix ); +} diff --git a/tests/data/h5/createNeuron.py b/tests/data/h5/createNeuron.py new file mode 100644 index 0000000..569403c --- /dev/null +++ b/tests/data/h5/createNeuron.py @@ -0,0 +1,67 @@ +# Copyright (c) 2013-2015, EPFL/Blue Brain Project +# Juan Hernando +# +# This file is part of Brion + +#!/usr/bin/env python + +import h5py +import numpy + +SOMA = 1 +AXON = 2 +BASAL = 3 +APICAL = 4 + +out = h5py.File('test_neuron.h5', 'w') + +def createTBranchPoints(): + # Creates a branch with a T shape + # The first section is a non-homogeneous distance sampling of 10 points + # on a straight line from (0, 0, 0) to (0, 5, 5). + first = [[0, i*i / 20.0, i*i / 20.0, 0.5 + i*i/1000.0] for i in range(11)] + # The second section is an homogeneous sampling from (0, 5, 5) to (-2, 5, 5) + second = [[-i * 0.4, 5, 5, 0.6 + i/50.0] for i in range(6)] + # The third section is an homogeneous sampling from (0, 5, 5) to (2, 5, 5) + third = [[i * 0.4, 5, 5, 0.6 + i/50.0] for i in range(6)] + return numpy.array(first + second + third) + +def createTBranchStructure(firstPoint, nextSection, sectionType): + return [[firstPoint, sectionType, 0], # connected to soma + [firstPoint + 11, sectionType, nextSection], + [firstPoint + 17, sectionType, nextSection]] + +points = numpy.array([[.1, .0, .0, .1], [.0, .1, .0, .1], + [-.1, .0, .0, .1], [.0, -.1, .0, .1]]) +structure = numpy.array([[0, 1, -1]]) + +# Axon section +structure = numpy.append( + structure, createTBranchStructure(points.shape[0], structure.shape[0], AXON), + axis=0) +points = numpy.append( + points, [[p[0], -p[1], p[2], p[3]] for p in createTBranchPoints()], axis=0) +# Basal dendrite to the right +structure = numpy.append( + structure, + createTBranchStructure(points.shape[0], structure.shape[0], BASAL), axis=0) +points = numpy.append( + points, [[p[1], p[0], p[2], p[3]] for p in createTBranchPoints()], axis=0) +# Basal dendrite to the left +structure = numpy.append( + structure, + createTBranchStructure(points.shape[0], structure.shape[0], BASAL), axis=0) +points = numpy.append( + points, [[-p[1], p[0], p[2], p[3]] for p in createTBranchPoints()], axis=0) +# Apical dendrite to the top +structure = numpy.append( + structure, + createTBranchStructure(points.shape[0], structure.shape[0], APICAL), axis=0) +points = numpy.append(points, createTBranchPoints(), axis=0) + +out.create_dataset('/points', data=points) +out.create_dataset('/structure', data=structure) + +out.close() + + diff --git a/tests/data/h5/test_neuron.h5 b/tests/data/h5/test_neuron.h5 new file mode 100644 index 0000000000000000000000000000000000000000..0f9184e13e769da075603342af4631e8785fd49c GIT binary patch literal 5528 zcmeHLO=wd=5S}EhCbeQ)B8YPlbY%_rz8!f>sS8B2w@Ny=dtVh$0k3&_fYV z_2(#pxrj&gk{}8e^rn=8V6|2Vf*0#SsMNg7e7nBct|^EY?H1m=nQvzH+kIa)&AZmq zwR2^|+6Ib7I!$%dRK3MzUdGMU9DxtKPi_)ouZq2&XjxUie2Em=R;^4VxnrO75qXmKQx)Y$na#O?($(CM+E-Bfe2NR}E71r~Cb?l+ z@>7XEPA6*QOun{Am#F4Te$OGI)4#%h{r=$IeO(Dk3Jao~tiv}kKCkENeHs5}yq2z3 z%C=y8wV$e-qmc%hDk_1lqLSa}Da$Q8qB1jFJ@p%G>1JsM{&WX!--^cLK=;vcpD*#^ zBI`|_?kDvl@jg?A7v~p>VuLs5%dMNz8Xp&YJjhSqyYnn(@mDXS@wO;7xD;$X_GGuk zxo=tO%R%qx*x8oL7H3|w!hbg4J{D(xPWsOUvp-%}7`|8hQzf1!6+X{B95{VL;~Bv- zVHnYC{9x+0R7X=uY*rcJ#MXFij%E?Ylwx4*MtVmiRS==zJ< z2fN;1_!zWTC;IBtufOtn|D*LYQvcn-pMGmQdj07P_<=WUc&a)+0q5z-^27RszvNk5 z@po{=-@z4s2Uq+wDJV z+{fx$bZv0>&^Px_g_s{c^v!-o;a1OJ}{p_2RC^PByQ!X14Z+@o)U^E}S;uZc4c zufFAb#jEd94Sh?!SKr_#HmlBWoQu3Va`i3!Cj-uK^bP(7SNaBjgDZW5zrmHh!9T?O z@y{nm-K+in=lw63^W5|QiQrb>=m}lJ;`|2A{mW9X&u?Vm9Jl%g0$s!6{07ebQz0zQ z?>L?j-0B-RbPXFnzmbJ=PV3u`GasvK&b0cFeJLUi3w>x^1BX7i-s&27Tpv_I9Cl%S zxa$-CMt7*k_mA**aK+!j6@Ld;{6owOtNG(cC*JGfeTjN~L#wz! + * Juan Hernando * * This file is part of Brion * @@ -30,6 +31,7 @@ #include "paths.h" #include +#include #define BOOST_TEST_MODULE Morphology #include @@ -39,17 +41,27 @@ #include -typedef brion::Vector4f V4f; // For brevity -typedef brion::Vector2i V2i; // For brevity +// typdef for brevity +typedef brion::Vector4f V4f; +typedef brion::Vector3f V3f; +typedef brion::Vector2i V2i; + +namespace +{ +const std::string TEST_MORPHOLOGY_FILENAME = + std::string( BRION_TESTDATA ) + "/h5/test_neuron.h5"; +const brion::URI TEST_MORPHOLOGY_URI = + brion::URI( "file://" + TEST_MORPHOLOGY_FILENAME ); -#pragma clang diagnostic ignored "-Wnon-pod-varargs" // Ellipsis promotes enums to ints, so we need to use int. +#pragma clang diagnostic ignored "-Wnon-pod-varargs" const int UNDEFINED = brion::SECTION_UNDEFINED; const int SOMA = brion::SECTION_SOMA; const int AXON = brion::SECTION_AXON; const int DENDRITE = brion::SECTION_DENDRITE; const int APICAL_DENDRITE = brion::SECTION_APICAL_DENDRITE; +} BOOST_AUTO_TEST_CASE( invalid_open ) { @@ -58,14 +70,16 @@ BOOST_AUTO_TEST_CASE( invalid_open ) boost::filesystem::path path( BBP_TESTDATA ); path /= "local/README"; - BOOST_CHECK_THROW( brion::Morphology( path.string( )), std::runtime_error ); + BOOST_CHECK_THROW( brion::Morphology( path.string( )), + std::runtime_error ); } BOOST_AUTO_TEST_CASE( h5_invalid_open ) { boost::filesystem::path path( BBP_TESTDATA ); path /= "local/simulations/may17_2011/Control/voltage.h5"; - BOOST_CHECK_THROW( brion::Morphology( path.string( )), std::runtime_error ); + BOOST_CHECK_THROW( brion::Morphology( path.string( )), + std::runtime_error ); } BOOST_AUTO_TEST_CASE( h5_illegal_write ) @@ -92,19 +106,17 @@ BOOST_AUTO_TEST_CASE( h5_overwrite ) const std::string file( "overwritetest.h5" ); boost::filesystem::remove( file ); - BOOST_CHECK_NO_THROW( brion::Morphology( file, - brion::MORPHOLOGY_VERSION_H5_2, - false )); - BOOST_CHECK_THROW( brion::Morphology( file, brion::MORPHOLOGY_VERSION_H5_2, - false ), std::runtime_error ); - BOOST_CHECK_NO_THROW( brion::Morphology( file, - brion::MORPHOLOGY_VERSION_H5_2, - true )); + BOOST_CHECK_NO_THROW( + brion::Morphology( file, brion::MORPHOLOGY_VERSION_H5_2, false )); + BOOST_CHECK_THROW( + brion::Morphology( file, brion::MORPHOLOGY_VERSION_H5_2, false ), + std::runtime_error ); + BOOST_CHECK_NO_THROW( + brion::Morphology( file, brion::MORPHOLOGY_VERSION_H5_2, true )); boost::filesystem::remove( file ); - BOOST_CHECK_NO_THROW( brion::Morphology( file, - brion::MORPHOLOGY_VERSION_H5_2, - true )); + BOOST_CHECK_NO_THROW( + brion::Morphology( file, brion::MORPHOLOGY_VERSION_H5_2, true )); boost::filesystem::remove( file ); } @@ -115,14 +127,15 @@ BOOST_AUTO_TEST_CASE( h5_read_v1 ) const brion::Morphology morphology( path.string( )); - brion::Vector4fsPtr points = morphology.readPoints( brion::MORPHOLOGY_RAW ); + const brion::Vector4fsPtr points = + morphology.readPoints( brion::MORPHOLOGY_RAW ); BOOST_CHECK_EQUAL( points->size(), 3272 ); BOOST_CHECK_CLOSE( (*points)[0].x(), -9.0625f, .000001f ); BOOST_CHECK_CLOSE( (*points)[0].y(), -4.97781f, .0001f ); BOOST_CHECK_CLOSE( (*points)[0].z(), 0.f, .000001f ); BOOST_CHECK_CLOSE( (*points)[0].w(), 0.37f, .000001f ); - brion::Vector2isPtr sections = + const brion::Vector2isPtr sections = morphology.readSections( brion::MORPHOLOGY_RAW ); BOOST_CHECK_EQUAL( sections->size(), 138 ); BOOST_CHECK_EQUAL( (*sections)[0].x(), 0 ); @@ -130,7 +143,7 @@ BOOST_AUTO_TEST_CASE( h5_read_v1 ) BOOST_CHECK_EQUAL( (*sections)[5].x(), 85 ); BOOST_CHECK_EQUAL( (*sections)[5].y(), 4 ); - brion::SectionTypesPtr types = morphology.readSectionTypes(); + const brion::SectionTypesPtr types = morphology.readSectionTypes(); BOOST_CHECK_EQUAL( types->size(), 138 ); BOOST_CHECK_EQUAL( (*types)[0], 1 ); BOOST_CHECK_EQUAL( (*types)[5], 2 ); @@ -214,8 +227,8 @@ BOOST_AUTO_TEST_CASE( h5_write_v2 ) brion::Vector2isPtr apicals = source.readApicals(); { // undefined should auto-select h5 v2 - brion::Morphology a( "testv2.h5", brion::MORPHOLOGY_VERSION_UNDEFINED, - true ); + brion::Morphology a( "testv2.h5", + brion::MORPHOLOGY_VERSION_UNDEFINED, true ); a.writePoints( *points, brion::MORPHOLOGY_REPAIRED ); a.writeSections( *sections, brion::MORPHOLOGY_REPAIRED ); a.writeSectionTypes( *types ); @@ -285,7 +298,7 @@ struct VaArgsType< brion::SectionType > }; template< typename T > -void verifyArray( const std::vector< T >& array, const size_t length, ... ) +void checkEqualArrays( const std::vector< T >& array, const size_t length, ... ) { // Create the reference array std::vector< T > ref; @@ -299,6 +312,39 @@ void verifyArray( const std::vector< T >& array, const size_t length, ... ) ref.begin(), ref.end( )); } +template< typename T > +void _checkCloseVectorArrays( const std::vector< T >& array, + const size_t length, va_list args ) +{ + for( size_t i = 0; i != length; ++i ) + { + const T& v = ( T )va_arg( args, typename VaArgsType< T >::type ); + BOOST_CHECK_SMALL(( array[i] - v ).length( ), 0.00001f); + } +} + +template< typename T > +void checkCloseVectorArrays( const std::vector< T >& array, + const size_t length, ... ) +{ + BOOST_CHECK_EQUAL( array.size(), length ); + va_list args; + va_start( args, length ); + _checkCloseVectorArrays( array, length, args ); + va_end( args ); +} + +template< typename T > +void checkCloseVectorArraysUptoN( const std::vector< T >& array, + const size_t length, ... ) +{ + BOOST_CHECK( array.size() >= length ); + va_list args; + va_start( args, length ); + _checkCloseVectorArrays( array, length, args ); + va_end( args ); +} + BOOST_AUTO_TEST_CASE( swc_soma ) { boost::filesystem::path path( BRION_TESTDATA ); @@ -306,9 +352,9 @@ BOOST_AUTO_TEST_CASE( swc_soma ) path /= "swc/soma.swc"; const brion::Morphology source( path.string( )); - verifyArray( *source.readPoints( stage ), 1, V4f( 0, 0, 0, 20 )); - verifyArray( *source.readSections( stage ), 1, V2i( 0, -1 )); - verifyArray( *source.readSectionTypes(), 1, SOMA ); + checkEqualArrays( *source.readPoints( stage ), 1, V4f( 0, 0, 0, 20 )); + checkEqualArrays( *source.readSections( stage ), 1, V2i( 0, -1 )); + checkEqualArrays( *source.readSectionTypes(), 1, SOMA ); } BOOST_AUTO_TEST_CASE( swc_soma_ring ) @@ -318,11 +364,11 @@ BOOST_AUTO_TEST_CASE( swc_soma_ring ) path /= "swc/soma_ring.swc"; const brion::Morphology source( path.string( )); - verifyArray( *source.readPoints( stage ), 5, V4f( 0, 0, 0, 20 ), - V4f( 0, 0, 1, 20 ), V4f( 0, 1, 0, 20 ), V4f( 0, 1, 1, 20 ), - V4f( 1, 0, 0, 20 )); - verifyArray( *source.readSections( stage ), 1, V2i( 0, -1 )); - verifyArray( *source.readSectionTypes(), 1, SOMA ); + checkEqualArrays( *source.readPoints( stage ), 5, V4f( 0, 0, 0, 20 ), + V4f( 0, 0, 1, 20 ), V4f( 0, 1, 0, 20 ), V4f( 0, 1, 1, 20 ), + V4f( 1, 0, 0, 20 )); + checkEqualArrays( *source.readSections( stage ), 1, V2i( 0, -1 )); + checkEqualArrays( *source.readSectionTypes(), 1, SOMA ); } BOOST_AUTO_TEST_CASE( swc_no_soma ) @@ -330,7 +376,8 @@ BOOST_AUTO_TEST_CASE( swc_no_soma ) boost::filesystem::path path( BRION_TESTDATA ); path /= "swc/no_soma.swc"; - BOOST_CHECK_THROW( brion::Morphology( path.string( )), std::runtime_error ); + BOOST_CHECK_THROW( brion::Morphology( path.string( )), + std::runtime_error ); } BOOST_AUTO_TEST_CASE( swc_two_somas ) @@ -338,7 +385,8 @@ BOOST_AUTO_TEST_CASE( swc_two_somas ) boost::filesystem::path path( BRION_TESTDATA ); path /= "swc/two_somas.swc"; - BOOST_CHECK_THROW( brion::Morphology( path.string( )), std::runtime_error ); + BOOST_CHECK_THROW( brion::Morphology( path.string( )), + std::runtime_error ); } BOOST_AUTO_TEST_CASE( swc_single_section ) @@ -349,11 +397,12 @@ BOOST_AUTO_TEST_CASE( swc_single_section ) const brion::Morphology source( path.string( )); - verifyArray( *source.readPoints( stage ), 5, V4f( 0, 0, 0, 20 ), - V4f( 0, 0, 1, 4 ), V4f( 0, 0, 2, 4 ), V4f( 0, 0, 3, 4 ), - V4f( 0, 0, 4, 4 )); - verifyArray( *source.readSections( stage ), 2, V2i( 0, -1 ), V2i( 1, 0 )); - verifyArray( *source.readSectionTypes(), 2, SOMA, AXON ); + checkEqualArrays( *source.readPoints( stage ), 5, V4f( 0, 0, 0, 20 ), + V4f( 0, 0, 1, 4 ), V4f( 0, 0, 2, 4 ), V4f( 0, 0, 3, 4 ), + V4f( 0, 0, 4, 4 )); + checkEqualArrays( *source.readSections( stage ), + 2, V2i( 0, -1 ), V2i( 1, 0 )); + checkEqualArrays( *source.readSectionTypes(), 2, SOMA, AXON ); } BOOST_AUTO_TEST_CASE( swc_single_section_unordered ) @@ -364,11 +413,12 @@ BOOST_AUTO_TEST_CASE( swc_single_section_unordered ) const brion::Morphology source( path.string( )); - verifyArray( *source.readPoints( stage ), 5, V4f( 0, 0, 0, 20 ), - V4f( 0, 0, 1, 4 ), V4f( 0, 0, 2, 4 ), V4f( 0, 0, 3, 4 ), - V4f( 0, 0, 4, 4 )); - verifyArray( *source.readSections( stage ), 2, V2i( 0, -1 ), V2i( 1, 0 )); - verifyArray( *source.readSectionTypes(), 2, SOMA, AXON ); + checkEqualArrays( *source.readPoints( stage ), 5, V4f( 0, 0, 0, 20 ), + V4f( 0, 0, 1, 4 ), V4f( 0, 0, 2, 4 ), V4f( 0, 0, 3, 4 ), + V4f( 0, 0, 4, 4 )); + checkEqualArrays( *source.readSections( stage ), + 2, V2i( 0, -1 ), V2i( 1, 0 )); + checkEqualArrays( *source.readSectionTypes(), 2, SOMA, AXON ); } BOOST_AUTO_TEST_CASE( swc_single_section_missing_segment ) @@ -376,7 +426,8 @@ BOOST_AUTO_TEST_CASE( swc_single_section_missing_segment ) boost::filesystem::path path( BRION_TESTDATA ); path /= "swc/single_section_missing_segment.swc"; - BOOST_CHECK_THROW( brion::Morphology( path.string( )), std::runtime_error ); + BOOST_CHECK_THROW( brion::Morphology( path.string( )), + std::runtime_error ); } BOOST_AUTO_TEST_CASE( swc_section_type_changes ) @@ -387,13 +438,13 @@ BOOST_AUTO_TEST_CASE( swc_section_type_changes ) const brion::Morphology source( path.string( )); - verifyArray( *source.readPoints( stage ), 7, V4f( 0, 0, 0, 20 ), - V4f( 0, 0, 1, 4 ), V4f( 0, 0, 2, 4 ), V4f( 0, 0, 2, 4 ), - V4f( 0, 0, 3, 4 ), V4f( 0, 0, 3, 4 ), V4f( 0, 0, 4, 4 )); - verifyArray( *source.readSections( stage ), 4, - V2i( 0, -1 ), V2i( 1, 0 ), V2i( 3, 1 ), V2i( 5, 2 )); - verifyArray( *source.readSectionTypes(), 4, - SOMA, AXON, DENDRITE, APICAL_DENDRITE ); + checkEqualArrays( *source.readPoints( stage ), 7, V4f( 0, 0, 0, 20 ), + V4f( 0, 0, 1, 4 ), V4f( 0, 0, 2, 4 ), V4f( 0, 0, 2, 4 ), + V4f( 0, 0, 3, 4 ), V4f( 0, 0, 3, 4 ), V4f( 0, 0, 4, 4 )); + checkEqualArrays( *source.readSections( stage ), 4, + V2i( 0, -1 ), V2i( 1, 0 ), V2i( 3, 1 ), V2i( 5, 2 )); + checkEqualArrays( *source.readSectionTypes(), 4, + SOMA, AXON, DENDRITE, APICAL_DENDRITE ); } BOOST_AUTO_TEST_CASE( swc_first_order_sections ) @@ -404,14 +455,14 @@ BOOST_AUTO_TEST_CASE( swc_first_order_sections ) const brion::Morphology source( path.string( )); - verifyArray( *source.readSections( stage ), 4, + checkEqualArrays( *source.readSections( stage ), 4, V2i( 0, -1 ), V2i( 1, 0 ), V2i( 2, 0 ), V2i( 3, 0 )); // The tree construction algorithm reserves the order of two sections // compared to how they appear in the file - verifyArray( *source.readPoints( stage ), 4, V4f( 0, 0, 0, 20 ), - V4f( 0, 0, 1, 4 ), V4f( 0, 0, 3, 4 ), V4f( 0, 0, 2, 4 )); - verifyArray( *source.readSectionTypes(), 4, - SOMA, AXON, APICAL_DENDRITE, DENDRITE ); + checkEqualArrays( *source.readPoints( stage ), 4, V4f( 0, 0, 0, 20 ), + V4f( 0, 0, 1, 4 ), V4f( 0, 0, 3, 4 ), V4f( 0, 0, 2, 4 )); + checkEqualArrays( *source.readSectionTypes(), 4, + SOMA, AXON, APICAL_DENDRITE, DENDRITE ); } @@ -423,14 +474,14 @@ BOOST_AUTO_TEST_CASE( swc_bifurcation ) const brion::Morphology source( path.string( )); - verifyArray( *source.readPoints( stage ), 9, V4f( 0, 0, 0, 20 ), - V4f( 0, 0, 2, 4 ), V4f( 0, 0, 3, 4 ), V4f( 0, 0, 3, 4 ), - V4f( 0, 0, 4, 4 ), V4f( 0, 0, 5, 4 ), V4f( 0, 0, 3, 4 ), - V4f( 0, 0, 6, 4 ), V4f( 0, 0, 7, 4 )); - verifyArray( *source.readSections( stage ), 4, - V2i( 0, -1 ), V2i( 1, 0 ), V2i( 3, 1 ), V2i( 6, 1 )); - verifyArray( *source.readSectionTypes(), 4, - SOMA, DENDRITE, APICAL_DENDRITE, APICAL_DENDRITE ); + checkEqualArrays( *source.readPoints( stage ), 9, V4f( 0, 0, 0, 20 ), + V4f( 0, 0, 2, 4 ), V4f( 0, 0, 3, 4 ), V4f( 0, 0, 3, 4 ), + V4f( 0, 0, 4, 4 ), V4f( 0, 0, 5, 4 ), V4f( 0, 0, 3, 4 ), + V4f( 0, 0, 6, 4 ), V4f( 0, 0, 7, 4 )); + checkEqualArrays( *source.readSections( stage ), 4, + V2i( 0, -1 ), V2i( 1, 0 ), V2i( 3, 1 ), V2i( 6, 1 )); + checkEqualArrays( *source.readSectionTypes(), 4, + SOMA, DENDRITE, APICAL_DENDRITE, APICAL_DENDRITE ); } BOOST_AUTO_TEST_CASE( swc_end_points ) @@ -441,12 +492,12 @@ BOOST_AUTO_TEST_CASE( swc_end_points ) const brion::Morphology source( path.string( )); - verifyArray( *source.readSections( stage ), 6, - V2i( 0, -1 ), V2i( 1, 0 ), V2i( 2, 1 ), V2i( 4, 1 ), - V2i( 7, 0 ), V2i( 8, 0 )); + checkEqualArrays( *source.readSections( stage ), 6, + V2i( 0, -1 ), V2i( 1, 0 ), V2i( 2, 1 ), V2i( 4, 1 ), + V2i( 7, 0 ), V2i( 8, 0 )); - verifyArray( *source.readSectionTypes(), 6, - SOMA, AXON, AXON, AXON, UNDEFINED, UNDEFINED ); + checkEqualArrays( *source.readSectionTypes(), 6, + SOMA, AXON, AXON, AXON, UNDEFINED, UNDEFINED ); } BOOST_AUTO_TEST_CASE( swc_fork_points ) @@ -457,12 +508,12 @@ BOOST_AUTO_TEST_CASE( swc_fork_points ) const brion::Morphology source( path.string( )); - verifyArray( *source.readSections( stage ), 6, - V2i( 0, -1 ), V2i( 1, 0 ), V2i( 2, 1 ), V2i( 4, 1 ), - V2i( 7, 0 ), V2i( 8, 0 )); + checkEqualArrays( *source.readSections( stage ), 6, + V2i( 0, -1 ), V2i( 1, 0 ), V2i( 2, 1 ), V2i( 4, 1 ), + V2i( 7, 0 ), V2i( 8, 0 )); - verifyArray( *source.readSectionTypes(), 6, - SOMA, AXON, AXON, AXON, UNDEFINED, UNDEFINED ); + checkEqualArrays( *source.readSectionTypes(), 6, + SOMA, AXON, AXON, AXON, UNDEFINED, UNDEFINED ); } BOOST_AUTO_TEST_CASE( swc_neuron ) @@ -475,3 +526,107 @@ BOOST_AUTO_TEST_CASE( swc_neuron ) 927 ); } +namespace +{ +void checkEqualMorphologies( const brain::Morphology& first, + const brion::Morphology& second ) +{ + BOOST_CHECK( *second.readPoints( brion::MORPHOLOGY_UNDEFINED ) == + first.getPoints( )); + BOOST_CHECK( *second.readSections( brion::MORPHOLOGY_UNDEFINED ) == + first.getSections( )); + BOOST_CHECK( *second.readSectionTypes() == first.getSectionTypes( )); + BOOST_CHECK( *second.readApicals() == first.getApicals( )); +} +} + +BOOST_AUTO_TEST_CASE( v2_morphology_constructors ) +{ + boost::shared_ptr< brion::Morphology > raw( + new brion::Morphology( TEST_MORPHOLOGY_FILENAME )); + + checkEqualMorphologies( brain::Morphology( TEST_MORPHOLOGY_URI ), *raw ); + checkEqualMorphologies( brain::Morphology( *raw ), *raw ); + + BOOST_CHECK_THROW( brain::Morphology( brion::URI( "/mars" )), + std::runtime_error); +} + +BOOST_AUTO_TEST_CASE( get_section_ids ) +{ + brain::Morphology morphology( TEST_MORPHOLOGY_URI ); + + brion::SectionTypes types; + types.push_back( brion::SECTION_SOMA ); + checkEqualArrays( morphology.getSectionIDs( types ), 1, 0 ); + types.push_back( brion::SECTION_DENDRITE ); + checkEqualArrays( morphology.getSectionIDs( types ), + 7, 0, 4, 5, 6, 7, 8, 9 ); + types.push_back( brion::SECTION_APICAL_DENDRITE ); + checkEqualArrays( morphology.getSectionIDs( types ), + 10, 0, 4, 5, 6, 7, 8, 9, 10, 11, 12 ); + types.clear(); + types.push_back( brion::SECTION_AXON ); + types.push_back( brion::SECTION_DENDRITE ); + checkEqualArrays( morphology.getSectionIDs( types ), + 9, 1, 2, 3, 4, 5, 6, 7, 8, 9 ); + +} + +BOOST_AUTO_TEST_CASE( get_section_samples ) +{ + brain::Morphology morphology( TEST_MORPHOLOGY_URI ); + + brion::floats points; + for( float p = 0.0; p <= 1.0; p += 0.2 ) + points.push_back( p ); + + checkCloseVectorArrays( morphology.getSectionSamples( 1, points ), 6, + V4f( 0, 0, 0, .5 ), V4f( 0, -1, 1, .52 ), V4f( 0, -2, 2, .54 ), + V4f( 0, -3, 3, .56 ), V4f( 0, -4, 4, .58 ), V4f( 0, -5, 5, .6 )); + + checkCloseVectorArrays( morphology.getSectionSamples( 4, points ), 6, + V4f( 0, 0, 0, .5 ), V4f( 1, 0, 1, .52 ), V4f( 2, 0, 2, .54 ), + V4f( 3, 0, 3, .56 ), V4f( 4, 0, 4, .58 ), V4f( 5, 0, 5, .6 )); + + checkCloseVectorArrays( morphology.getSectionSamples( 7, points ), 6, + V4f( 0, 0, 0, .5 ), V4f( -1, 0, 1, .52 ), V4f( -2, 0, 2, .54 ), + V4f( -3, 0, 3, .56 ), V4f( -4, 0, 4, .58 ), V4f( -5, 0, 5, .6 )); + + checkCloseVectorArrays( morphology.getSectionSamples( 10, points ), 6, + V4f( 0, 0, 0, .5 ), V4f( 0, 1, 1, .52 ), V4f( 0, 2, 2, .54 ), + V4f( 0, 3, 3, .56 ), V4f( 0, 4, 4, .58 ), V4f( 0, 5, 5, .6 )); +} + +BOOST_AUTO_TEST_CASE( soma_position ) +{ + brain::Morphology local( TEST_MORPHOLOGY_URI ); + brion::floats points; + points.push_back( 0.232 ); // arbitrary + BOOST_CHECK_EQUAL( local.getSectionSamples( 0, points )[0], V3f::ZERO ); + + brain::Matrix4f matrix( brain::Matrix4f::IDENTITY ); + matrix.set_translation( V3f( 2, 0, 0 )); + brain::Morphology transformed( TEST_MORPHOLOGY_URI, matrix ); + BOOST_CHECK_EQUAL( transformed.getSectionSamples( 0, points )[0], + V3f( 2, 0, 0 )); +} + +BOOST_AUTO_TEST_CASE( transform_with_matrix ) +{ + brain::Matrix4f matrix( brain::Matrix4f::IDENTITY ); + matrix.rotate_z( M_PI * 0.5 ); + brain::Morphology rotated( TEST_MORPHOLOGY_URI, matrix ); + checkCloseVectorArraysUptoN( rotated.getPoints(), 4, + V4f( .0, .1, .0, .1 ), V4f( -.1, .0, .0, .1 ), + V4f( .0, -.1, .0, .1 ), V4f( .1, .0, .0, .1 )); + + matrix = brain::Matrix4f::IDENTITY; + matrix.rotate_z( M_PI * 0.5 ); + matrix.set_translation( V3f( 2, 0, 0 )); + brain::Morphology transformed( TEST_MORPHOLOGY_URI, matrix ); + checkCloseVectorArraysUptoN( transformed.getPoints(), 4, + V4f( 2., .1, .0, .1 ), V4f( 1.9, .0, .0, .1 ), + V4f( 2., -.1, .0, .1 ), V4f( 2.1, .0, .0, .1 )); +} +