Commit 3c7b9261 authored by Floris Berendsen's avatar Floris Berendsen
Browse files

ENH: Refactor Blueprint using the Pimple pattern and shared pointers

parent e9cc6f51
......@@ -28,4 +28,4 @@ set( CompilerFlags
CMAKE_C_FLAGS_MINSIZEREL
CMAKE_C_FLAGS_RELEASE
CMAKE_C_FLAGS_RELWITHDEBINFO
)
\ No newline at end of file
)
......@@ -20,119 +20,59 @@
#ifndef Blueprint_h
#define Blueprint_h
#include "boost/graph/graph_traits.hpp"
#include "boost/graph/directed_graph.hpp"
#include "boost/graph/labeled_graph.hpp"
#include "itkObjectFactory.h"
#include "itkDataObject.h"
#include "selxMacro.h"
#include <string>
#include <vector>
#include <map>
#include <memory>
namespace selx
{
class Blueprint : public itk::DataObject
class Blueprint
{
public:
selxNewMacro( Blueprint, itk::DataObject );
typedef std::string ParameterKeyType;
typedef std::vector< std::string > ParameterValueType;
typedef std::map< ParameterKeyType, ParameterValueType > ParameterMapType;
typedef std::string ComponentNameType;
typedef std::vector< ComponentNameType > ComponentNamesType;
typedef std::string ComponentNameType;
// Component parameter map that sits on a node in the graph
// and holds component configuration settings
struct ComponentPropertyType
{
ComponentNameType name;
ParameterMapType parameterMap;
};
// Component parameter map that sits on an edge in the graph
// and holds component connection configuration settings
struct ConnectionPropertyType
{
ParameterMapType parameterMap;
};
typedef boost::labeled_graph< boost::adjacency_list<
boost::vecS,
boost::vecS,
boost::bidirectionalS,
ComponentPropertyType,
ConnectionPropertyType
>,
ComponentNameType > GraphType;
typedef std::vector< ComponentNameType > ComponentNamesType;
typedef boost::graph_traits< GraphType >::vertex_descriptor ComponentIndexType;
typedef boost::graph_traits< GraphType >::vertex_iterator ComponentIteratorType;
typedef std::pair< ComponentIteratorType, ComponentIteratorType > ComponentIteratorPairType;
typedef boost::graph_traits< GraphType >::edge_descriptor ConnectionIndexType;
typedef boost::graph_traits< GraphType >::edge_iterator ConnectionIteratorType;
typedef std::pair< ConnectionIteratorType, ConnectionIteratorType > ConnectionIteratorPairType;
typedef boost::graph_traits< GraphType >::in_edge_iterator InputIteratorType;
typedef std::pair< InputIteratorType, InputIteratorType > InputIteratorPairType;
typedef boost::graph_traits< GraphType >::out_edge_iterator OutputIteratorType;
typedef std::pair< OutputIteratorType, OutputIteratorType > OutputIteratorPairType;
// Interface for managing components
bool AddComponent( ComponentNameType name );
bool AddComponent( ComponentNameType name, ParameterMapType parameterMap );
Blueprint( void );
~Blueprint( void );
bool SetComponent( ComponentNameType, ParameterMapType parameterMap );
ParameterMapType GetComponent( ComponentNameType name ) const;
ParameterMapType GetComponent( ComponentNameType componentName ) const;
void SetComponent( ComponentNameType, ParameterMapType parameterMap );
// TODO: Let user delete component. Before we do this, we need a proper way of
// checking that a vertex exist. Otherwise a call to GetComponent() on
// a deleted vertex will result in segfault. It is not really a in issue
// _before_ release since typically we (the developers) will use blueprint
// interface procedurally.
// void DeleteComponent( ComponentIndexType );
bool DeleteComponent( ComponentNameType componentName );
bool ComponentExists( ComponentNameType componentName ) const;
// Returns a vector of the all Component names in the graph.
// TODO: should this be an iterator over the names?
ComponentNamesType GetComponentNames( void ) const;
// Interface for managing connections between components in which we
// deliberately avoid using connection indexes, but instead force
// the user to think in terms of components (which is conceptually simpler)
bool AddConnection( ComponentNameType upstream, ComponentNameType downstream );
bool AddConnection( ComponentNameType upstream, ComponentNameType downstream, ParameterMapType parameterMap );
bool SetConnection( ComponentNameType upstream, ComponentNameType downstream, ParameterMapType parameterMap );
ParameterMapType GetConnection( ComponentNameType upstream, ComponentNameType downstream ) const;
bool SetConnection( ComponentNameType upstream, ComponentNameType downstream, ParameterMapType parameterMap );
bool DeleteConnection( ComponentNameType upstream, ComponentNameType downstream );
bool ComponentExists( ComponentNameType componentName ) const;
bool ConnectionExists( ComponentNameType upstream, ComponentNameType downstream ) const;
// Returns a vector of the Component names at the outgoing direction
ComponentNamesType GetOutputNames( const ComponentNameType name ) const;
// Returns a vector of the Component names at the incoming direction
ComponentNamesType GetInputNames( const ComponentNameType name ) const;
// Returns a vector of the Component names at the outgoing direction
ComponentNamesType GetOutputNames( const ComponentNameType name ) const;
void WriteBlueprint( const std::string filename );
void Write( const std::string filename );
private:
class BlueprintImpl;
std::unique_ptr< BlueprintImpl > m_Pimple;
ConnectionIndexType GetConnectionIndex( ComponentNameType upsteam, ComponentNameType downstream ) const;
GraphType m_Graph;
};
}
......
......@@ -17,185 +17,40 @@
*
*=========================================================================*/
#ifndef Blueprint_cxx
#define Blueprint_cxx
#include "boost/graph/graphviz.hpp"
#include "selxBlueprint.h"
#include "selxBlueprintImpl.h"
namespace selx
{
// Declared outside of the class body, so it is a free function
std::ostream &
operator<<( std::ostream & out, const Blueprint::ParameterMapType & val )
{
for( auto const & mapPair : val )
{
out << mapPair.first << " : [ ";
for( auto const & value : mapPair.second )
{
out << value << " ";
}
out << "]\\n";
}
return out;
}
template< class NameType, class ParameterMapType >
class vertex_label_writer
{
public:
vertex_label_writer( NameType _name, ParameterMapType _parameterMap ) : name( _name ), parameterMap( _parameterMap ) {}
template< class VertexOrEdge >
void operator()( std::ostream & out, const VertexOrEdge & v ) const
{
out << "[label=\"" << name[ v ] << "\n" << parameterMap[ v ] << "\"]";
}
namespace selx {
private:
NameType name;
ParameterMapType parameterMap;
};
template< class NameType, class ParameterMapType >
inline vertex_label_writer< NameType, ParameterMapType >
make_vertex_label_writer( NameType n, ParameterMapType p )
{
return vertex_label_writer< NameType, ParameterMapType >( n, p );
}
template< class ParameterMapType >
class edge_label_writer
{
public:
edge_label_writer( ParameterMapType _parameterMap ) : parameterMap( _parameterMap ) {}
template< class VertexOrEdge >
void operator()( std::ostream & out, const VertexOrEdge & v ) const
{
out << "[label=\"" << parameterMap[ v ] << "\"]";
}
private:
ParameterMapType parameterMap;
};
template< class ParameterMapType >
inline edge_label_writer< ParameterMapType >
make_edge_label_writer( ParameterMapType p )
{
return edge_label_writer< ParameterMapType >( p );
}
bool
Blueprint
::AddComponent( ComponentNameType name )
{
this->Modified();
// Returns true is addition was successful
return this->m_Graph.insert_vertex( name, { name, { {} } } ).second;
}
::Blueprint( void ) : m_Pimple( new Blueprint::BlueprintImpl ) {};
bool
Blueprint
::AddComponent( ComponentNameType name, ParameterMapType parameterMap )
{
this->Modified();
// Returns true is addition was successful
return this->m_Graph.insert_vertex( name, { name, parameterMap } ).second;
}
::~Blueprint( void ) = default;
Blueprint::ParameterMapType
Blueprint
::GetComponent( ComponentNameType name ) const
{
if( !this->ComponentExists( name ) )
{
itkExceptionMacro( "Blueprint does not contain component " << name );
}
this->Modified();
return this->m_Graph[ name ].parameterMap;
return this->m_Pimple->GetComponent( name );
}
void
bool
Blueprint
::SetComponent( ComponentNameType name, ParameterMapType parameterMap )
{
this->Modified();
this->m_Graph[ name ].parameterMap = parameterMap;
return this->m_Pimple->SetComponent( name, parameterMap );
}
// TODO: See explanation in selxBlueprint.h
// void
// Blueprint
// ::DeleteComponent( const ComponentIndexType index )
// {
// this->Modified();
//
// clear_vertex( index, this->m_Graph );
// remove_vertex( index, this->m_Graph );
// }
Blueprint::ComponentNamesType
Blueprint::GetComponentNames( void ) const
{
ComponentNamesType container;
for( auto it = boost::vertices( this->m_Graph.graph() ).first; it != boost::vertices( this->m_Graph.graph() ).second; ++it )
{
container.push_back( this->m_Graph.graph()[ *it ].name );
}
return container;
}
bool
Blueprint
::AddConnection( ComponentNameType upstream, ComponentNameType downstream )
{
this->Modified();
if( this->ConnectionExists( upstream, downstream ) )
{
return false;
}
// Adds directed connection from upstream component to downstream component
return boost::add_edge_by_label( upstream, downstream, this->m_Graph ).second;
}
bool
Blueprint
::AddConnection( ComponentNameType upstream, ComponentNameType downstream, ParameterMapType parameterMap )
{
this->Modified();
if( !this->ConnectionExists( upstream, downstream ) )
{
// TODO check if vertices exist
boost::add_edge_by_label( upstream, downstream, { parameterMap }, this->m_Graph );
return true;
}
// If the connection does not exist don't do anything because previous settings
// will be overwritten. If the user do want to overwrite current settings,
// she should use SetConnection() instead where the intent is explicit.
return false;
return this->m_Pimple->GetComponentNames();
}
......@@ -203,9 +58,7 @@ Blueprint::ParameterMapType
Blueprint
::GetConnection( ComponentNameType upstream, ComponentNameType downstream ) const
{
this->Modified();
return this->m_Graph[ this->GetConnectionIndex( upstream, downstream ) ].parameterMap;
return this->m_Pimple->GetConnection( upstream, downstream );
}
......@@ -213,17 +66,7 @@ bool
Blueprint
::SetConnection( ComponentNameType upstream, ComponentNameType downstream, ParameterMapType parameterMap )
{
this->Modified();
if( !this->ConnectionExists( upstream, downstream ) )
{
return this->AddConnection( upstream, downstream, parameterMap );
}
else
{
this->m_Graph[ this->GetConnectionIndex( upstream, downstream ) ].parameterMap = parameterMap;
return true;
}
return this->m_Pimple->SetConnection( upstream, downstream, parameterMap );
}
......@@ -231,14 +74,7 @@ bool
Blueprint
::DeleteConnection( ComponentNameType upstream, ComponentNameType downstream )
{
this->Modified();
if( this->ConnectionExists( upstream, downstream ) )
{
boost::remove_edge_by_label( upstream, downstream, this->m_Graph );
}
return !this->ConnectionExists( upstream, downstream );
return this->m_Pimple->DeleteConnection( upstream, downstream );
}
......@@ -246,8 +82,7 @@ bool
Blueprint
::ComponentExists( ComponentNameType componentName ) const
{
const GraphType::vertex_descriptor descriptor = this->m_Graph.vertex( componentName );
return descriptor != boost::graph_traits< GraphType >::null_vertex();
return this->m_Pimple->ComponentExists( componentName );
}
......@@ -255,16 +90,7 @@ bool
Blueprint
::ConnectionExists( ComponentNameType upstream, ComponentNameType downstream ) const
{
if( !this->ComponentExists( upstream ) )
{
itkExceptionMacro( "Blueprint does not contain component " << upstream );
}
if( !this->ComponentExists( downstream ) )
{
itkExceptionMacro( "Blueprint does not contain component " << downstream );
}
return boost::edge_by_label( upstream, downstream, this->m_Graph ).second;
return this->m_Pimple->ConnectionExists( upstream, downstream );
}
......@@ -272,14 +98,7 @@ Blueprint::ComponentNamesType
Blueprint
::GetOutputNames( const ComponentNameType name ) const
{
ComponentNamesType container;
OutputIteratorPairType outputIteratorPair = boost::out_edges( this->m_Graph.vertex( name ), this->m_Graph );
for( auto it = outputIteratorPair.first; it != outputIteratorPair.second; ++it )
{
container.push_back( this->m_Graph.graph()[ it->m_target ].name );
}
return container;
return this->m_Pimple->GetOutputNames( name );
}
......@@ -287,43 +106,15 @@ Blueprint::ComponentNamesType
Blueprint
::GetInputNames( const ComponentNameType name ) const
{
ComponentNamesType container;
//auto vertex = this->m_Graph.vertex(name);
//boost::in_edges(vertex, this->m_Graph);
InputIteratorPairType inputIteratorPair = boost::in_edges( this->m_Graph.vertex( name ), this->m_Graph );
for( auto it = inputIteratorPair.first; it != inputIteratorPair.second; ++it )
{
container.push_back( this->m_Graph.graph()[ it->m_source ].name );
}
return container;
}
Blueprint::ConnectionIndexType
Blueprint
::GetConnectionIndex( ComponentNameType upstream, ComponentNameType downstream ) const
{
// This function is part of the internal API and should fail hard if we use it incorrectly
if( !this->ConnectionExists( upstream, downstream ) )
{
itkExceptionMacro( "Blueprint does not contain connection from component " << upstream << " to " << downstream );
}
return boost::edge_by_label( upstream, downstream, this->m_Graph ).first;
return this->m_Pimple->GetInputNames( name );
}
void
Blueprint
::WriteBlueprint( const std::string filename )
::Write( const std::string filename )
{
std::ofstream dotfile( filename.c_str() );
boost::write_graphviz( dotfile, this->m_Graph,
make_vertex_label_writer( boost::get( &ComponentPropertyType::name, this->m_Graph ),
boost::get( &ComponentPropertyType::parameterMap, this->m_Graph ) ),
make_edge_label_writer( boost::get( &ConnectionPropertyType::parameterMap, this->m_Graph ) ) );
this->m_Pimple->Write( filename );
}
} // namespace selx
#endif // Blueprint_cxx
} // namespace selx
\ No newline at end of file
/*=========================================================================
*
* Copyright Leiden University Medical Center, Erasmus University Medical
* Center and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0.txt
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*=========================================================================*/
#include "selxBlueprint.h"
#include "selxBlueprintImpl.h"
namespace selx
{
bool
BlueprintImpl
::SetComponent( ComponentNameType name, ParameterMapType parameterMap )
{
if( this->ComponentExists( name ) )
{
this->m_Graph[ name ].parameterMap = parameterMap;
return true;
}
else
{
return this->m_Graph.insert_vertex( name, { name, parameterMap } ).second;
}
}
Blueprint::ParameterMapType
BlueprintImpl
::GetComponent( ComponentNameType name ) const
{
if( !this->ComponentExists( name ) )
{
std::stringstream msg;
msg << "Blueprint does not contain component " << name << std::endl;
throw std::runtime_error( msg.str() );
}
return this->m_Graph[ name ].parameterMap;
}
bool
BlueprintImpl
::DeleteComponent( ComponentNameType name )
{
if( !this->ComponentExists( name ) )
{
clear_vertex( name, this->m_Graph );
remove_vertex( name, this->m_Graph );
return true;
}
return false;
}
BlueprintImpl::ComponentNamesType
BlueprintImpl::GetComponentNames( void ) const
{
ComponentNamesType container;
for( auto it = boost::vertices( this->m_Graph.graph() ).first; it != boost::vertices( this->m_Graph.graph() ).second; ++it )
{
container.push_back( this->m_Graph.graph()[ *it ].name );
}
return container;
}
bool
BlueprintImpl
::SetConnection( ComponentNameType upstream, ComponentNameType downstream, ParameterMapType parameterMap )
{
if( !this->ComponentExists( upstream ) || !this->ComponentExists( downstream )
{
return false;
}
if( !this->ConnectionExists( upstream, downstream ) )
{
boost::add_edge_by_label( upstream, downstream, { parameterMap }, this->m_Graph );
}
else
{
this->m_Graph[ this->GetConnectionIndex( upstream, downstream ) ].parameterMap = parameterMap;
}
return true;
}
Blueprint::ParameterMapType
BlueprintImpl
::GetConnection( ComponentNameType upstream, ComponentNameType downstream ) const
{
return this->m_Graph[ this->GetConnectionIndex( upstream, downstream ) ].parameterMap;
}
bool
BlueprintImpl
::DeleteConnection( ComponentNameType upstream, ComponentNameType downstream )
{
if( this->ConnectionExists( upstream, downstream ) )
{
boost::remove_edge_by_label( upstream, downstream, this->m_Graph );
}
return !this->ConnectionExists( upstream, downstream );
}
bool
Bl