//--------------------------------------------------------------------------- // // Project: OpenWalnut ( http://www.openwalnut.org ) // // Copyright 2009 OpenWalnut Community, BSV@Uni-Leipzig and CNCF@MPI-CBS // For more information see http://www.openwalnut.org/copying // // This file is part of OpenWalnut. // // OpenWalnut is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // OpenWalnut 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 OpenWalnut. If not, see . // //--------------------------------------------------------------------------- #include #include #include #include #include #include #include #include #include #include "../../common/datastructures/WDXtLookUpTable.h" #include "../../common/datastructures/WFiber.h" #include "../../common/WAssert.h" #include "../../common/WColor.h" #include "../../common/WIOTools.h" #include "../../common/WLogger.h" #include "../../common/WProgress.h" #include "../../common/WStringUtils.h" #include "../../dataHandler/datastructures/WFiberCluster.h" #include "../../dataHandler/exceptions/WDHIOFailure.h" #include "../../dataHandler/io/WReaderLookUpTableVTK.h" #include "../../dataHandler/io/WWriterLookUpTableVTK.h" #include "../../dataHandler/WDataSetFiberVector.h" #include "../../dataHandler/WSubject.h" #include "../../graphicsEngine/WGEUtils.h" #include "../../kernel/WKernel.h" #include "detTractClustering.xpm" #include "WMDetTractClustering.h" // This line is needed by the module loader to actually find your module. W_LOADABLE_MODULE( WMDetTractClustering ) WMDetTractClustering::WMDetTractClustering() : WModule(), m_lastTractsSize( 0 ), m_dLtTableExists( false ), m_update( new WCondition() ) { } WMDetTractClustering::~WMDetTractClustering() { } boost::shared_ptr< WModule > WMDetTractClustering::factory() const { return boost::shared_ptr< WModule >( new WMDetTractClustering() ); } const char** WMDetTractClustering::getXPMIcon() const { return detTractClustering_xpm; } void WMDetTractClustering::moduleMain() { m_moduleState.setResetable( true, true ); // modules state remembers fired events while not waiting m_moduleState.add( m_tractInput->getDataChangedCondition() ); m_moduleState.add( m_update ); ready(); while ( !m_shutdownFlag() ) { m_moduleState.wait(); if ( !m_tractInput->getData().get() ) // ok, the output has not yet sent data { continue; } if( m_rawTracts != m_tractInput->getData() ) // in case data has changed { m_rawTracts = m_tractInput->getData(); boost::shared_ptr< WProgress > convertProgress( new WProgress( "Converting tracts", 1 ) ); m_progress->addSubProgress( convertProgress ); m_tracts = boost::shared_ptr< WDataSetFiberVector >( new WDataSetFiberVector( m_rawTracts ) ); m_numTracts->set( static_cast< int32_t >( m_tracts->size() ) ); convertProgress->finish(); } if( m_run->get( true ) == WPVBaseTypes::PV_TRIGGER_TRIGGERED ) { infoLog() << "Start processing tracts"; update(); infoLog() << "Processing finished"; m_run->set( WPVBaseTypes::PV_TRIGGER_READY, false ); } if( m_clusterOutputID->changed() ) { m_clusterOutputID->get( true ); updateOutput(); } } } void WMDetTractClustering::properties() { m_maxDistance_t = m_properties->addProperty( "Max cluster distance", "Maximum distance of two tracts in one cluster.", 6.5 ); m_proximity_t = m_properties->addProperty( "Min point distance", "Min distance of points of two tracts which should be considered", 0.0 ); m_minClusterSize = m_properties->addProperty( "Min cluster size", "Minium of tracts per cluster", 10 ); m_clusterOutputID = m_properties->addProperty( "Output cluster ID", "This cluster ID will be connected to the output.", 0, m_update ); m_run = m_properties->addProperty( "Start clustering", "Start", WPVBaseTypes::PV_TRIGGER_READY, m_update ); // information properties m_numTracts = m_infoProperties->addProperty( "#Tracts:", "Number of tracts beeing processed", 0 ); m_numTracts->setMin( 0 ); m_numTracts->setMax( wlimits::MAX_INT32_T ); m_numUsedTracts = m_infoProperties->addProperty( "#Tracts used:", "Number of tracts beeing finally used in the clustering", 0 ); m_numUsedTracts->setMin( 0 ); m_numUsedTracts->setMax( wlimits::MAX_INT32_T ); m_numClusters = m_infoProperties->addProperty( "#Clusters:", "Number of clusters beeing computed", 0 ); m_numClusters->setMin( 0 ); m_numClusters->setMax( wlimits::MAX_INT32_T ); m_numValidClusters = m_infoProperties->addProperty( "#Clusters used:", "Number of clusters beeing bigger than the given threshold", 0 ); m_numValidClusters->setMin( 0 ); m_numValidClusters->setMax( wlimits::MAX_INT32_T ); m_clusterSizes = m_infoProperties->addProperty( "Cluster sizes:", "Size of each valid cluster", std::string() ); } void WMDetTractClustering::updateOutput() { if( m_clusters.empty() ) { warnLog() << "There are no clusters for the output, leave output connecter untouched"; return; } if( m_clusterOutputID->get() >= static_cast< int >( m_clusters.size() ) || m_clusterOutputID->get() < 0 ) { warnLog() << "Invalid cluster ID for output selected: " << m_clusterOutputID->get() << " using default ID 0"; m_clusterOutputID->set( 0, true ); } m_output->updateData( boost::shared_ptr< WFiberCluster >( new WFiberCluster( m_clusters[ m_clusterOutputID->get() ] ) ) ); } void WMDetTractClustering::update() { if( !( m_dLtTableExists = dLtTableExists() ) ) { debugLog() << "Consider old table as invalid."; m_dLtTable.reset( new WDXtLookUpTable( m_tracts->size() ) ); } cluster(); boost::shared_ptr< WProgress > saveProgress( new WProgress( "Saving tracts", 1 ) ); m_progress->addSubProgress( saveProgress ); if( !wiotools::fileExists( lookUpTableFileName() ) ) { WWriterLookUpTableVTK w( lookUpTableFileName(), true ); try { w.writeTable( m_dLtTable->getData(), m_lastTractsSize ); } catch( const WDHIOFailure& e ) { errorLog() << "Could not write dlt file: " << e.what(); } } saveProgress->finish(); // reset min max of the slider for the cluster ID m_clusterOutputID->setMin( 0 ); m_clusterOutputID->setMax( m_clusters.size() - 1 ); WKernel::getRunningKernel()->getGraphicsEngine()->getScene()->remove( m_osgNode ); m_osgNode = paint(); WKernel::getRunningKernel()->getGraphicsEngine()->getScene()->insert( m_osgNode ); // TODO(math): For reasons of simplicity (no multiple input connectors possible) just forward one cluster to the voxelizer for( size_t i = 0; i < m_clusters.size(); ++i ) { m_clusters[ i ].setDataSetReference( m_tracts ); m_clusters[ i ].generateCenterLine(); } updateOutput(); } bool WMDetTractClustering::dLtTableExists() { boost::shared_ptr< WProgress > readProgress( new WProgress( "Try to read dLt table", 1 ) ); m_progress->addSubProgress( readProgress ); std::string dLtFileName = lookUpTableFileName(); if( wiotools::fileExists( dLtFileName ) ) { try { debugLog() << "trying to read table from: " << dLtFileName; WReaderLookUpTableVTK r( dLtFileName ); boost::shared_ptr< std::vector< double > > data( new std::vector< double >() ); r.readTable( data ); m_dLtTable.reset( new WDXtLookUpTable( static_cast< size_t >( data->back() ) ) ); m_lastTractsSize = static_cast< size_t >( data->back() ); // remove the dimension from data array since it's not representing any distance data->pop_back(); m_dLtTable->setData( *data ); readProgress->finish(); return true; } catch( const WDHException& e ) { debugLog() << e.what() << std::endl; } } readProgress->finish(); return false; } void WMDetTractClustering::cluster() { double proximity_t = m_proximity_t->get(); double maxDistance_t = m_maxDistance_t->get(); size_t minClusterSize = m_minClusterSize->get(); size_t numTracts = m_tracts->size(); infoLog() << "Start clustering with " << numTracts << " tracts."; m_clusters.clear(); // remove evtl. old clustering m_clusterIDs = std::vector< size_t >( numTracts, 0 ); for( size_t i = 0; i < numTracts; ++i ) { m_clusters.push_back( WFiberCluster( i ) ); m_clusterIDs[i] = i; } boost::shared_ptr< WProgress > progress( new WProgress( "Tract clustering", numTracts ) ); m_progress->addSubProgress( progress ); boost::function< double ( const wmath::WFiber& q, const wmath::WFiber& r ) > dLt; // NOLINT const double proxSquare = proximity_t * proximity_t; dLt = boost::bind( wmath::WFiber::distDLT, proxSquare, _1, _2 ); for( size_t q = 0; q < numTracts; ++q ) // loop over all "symmetric" tract pairs { for( size_t r = q + 1; r < numTracts; ++r ) { if( m_clusterIDs[q] != m_clusterIDs[r] ) // both tracts are in different clusters { if( !m_dLtTableExists ) { (*m_dLtTable)( q, r ) = dLt( (*m_tracts)[q], (*m_tracts)[r] ); } if( (*m_dLtTable)( q, r ) < maxDistance_t ) // q and r provide an inter-cluster-link { meld( m_clusterIDs[q], m_clusterIDs[r] ); } } } ++*progress; } progress->finish(); m_dLtTableExists = true; boost::shared_ptr< WProgress > eraseProgress( new WProgress( "Erasing clusters", 1 ) ); m_progress->addSubProgress( eraseProgress ); // remove empty clusters WFiberCluster emptyCluster; m_clusters.erase( std::remove( m_clusters.begin(), m_clusters.end(), emptyCluster ), m_clusters.end() ); // determine #clusters and #small_clusters which are below a certain size size_t numSmallClusters = 0; for( size_t i = 0; i < m_clusters.size(); ++i ) { m_clusters[i].sort(); // Refactor: why do we need sorting here? if( m_clusters[i].size() < minClusterSize ) { m_clusters[i].clear(); // make small clusters empty to remove them easier ++numSmallClusters; } } m_numClusters->set( static_cast< int32_t >( m_clusters.size() ) ); m_clusters.erase( std::remove( m_clusters.begin(), m_clusters.end(), emptyCluster ), m_clusters.end() ); m_numValidClusters->set( static_cast< int32_t >( m_clusters.size() ) ); m_lastTractsSize = m_tracts->size(); eraseProgress->finish(); } osg::ref_ptr< osg::Geode > WMDetTractClustering::genTractGeode( const WFiberCluster &cluster, const WColor& color ) const { using osg::ref_ptr; ref_ptr< osg::Vec3Array > vertices = ref_ptr< osg::Vec3Array >( new osg::Vec3Array ); ref_ptr< osg::Geometry > geometry = ref_ptr< osg::Geometry >( new osg::Geometry ); std::list< size_t >::const_iterator cit = cluster.getIndices().begin(); for( ; cit != cluster.getIndices().end(); ++cit ) { const wmath::WFiber &fib = (*m_tracts)[ *cit ]; for( size_t i = 0; i < fib.size(); ++i ) { vertices->push_back( osg::Vec3( fib[i][0], fib[i][1], fib[i][2] ) ); } geometry->addPrimitiveSet( new osg::DrawArrays( osg::PrimitiveSet::LINE_STRIP, vertices->size() - fib.size(), fib.size() ) ); } geometry->setVertexArray( vertices ); ref_ptr< osg::Vec4Array > colors = ref_ptr< osg::Vec4Array >( new osg::Vec4Array ); colors->push_back( wge::osgColor( color ) ); geometry->setColorArray( colors ); geometry->setColorBinding( osg::Geometry::BIND_OVERALL ); osg::ref_ptr< osg::Geode > geode = osg::ref_ptr< osg::Geode >( new osg::Geode ); geode->addDrawable( geometry.get() ); return geode; } osg::ref_ptr< WGEManagedGroupNode > WMDetTractClustering::paint() const { // get different colors via HSV color model for each cluster double hue = 0.0; double hue_increment = 1.0 / m_clusters.size(); WColor color; osg::ref_ptr< WGEManagedGroupNode > result = osg::ref_ptr< WGEManagedGroupNode >( new WGEManagedGroupNode( m_active ) ); size_t numUsedTracts = 0; std::stringstream clusterLog; for( size_t i = 0; i < m_clusters.size(); ++i, hue += hue_increment ) { color.setHSV( hue, 1.0, 0.75 ); result->insert( genTractGeode( m_clusters[i], color ).get() ); clusterLog << m_clusters[i].size() << " "; numUsedTracts += m_clusters[i].size(); } m_clusterSizes->set( std::string( clusterLog.str() ) ); m_numUsedTracts->set( static_cast< int32_t >( numUsedTracts ) ); result->getOrCreateStateSet()->setMode( GL_LIGHTING, osg::StateAttribute::OFF ); return result; } void WMDetTractClustering::meld( size_t qClusterID, size_t rClusterID ) { // first choose the cluster with the smaller ID if( qClusterID > rClusterID ) // merge always to the cluster with the smaller id { std::swap( qClusterID, rClusterID ); } WFiberCluster& qCluster = m_clusters[ qClusterID ]; WFiberCluster& rCluster = m_clusters[ rClusterID ]; WAssert( !qCluster.empty() && !rCluster.empty(), "At least one cluster was empty while trying to merge!" ); // second update m_clusterIDs array std::list< size_t >::const_iterator cit = rCluster.getIndices().begin(); std::list< size_t >::const_iterator cit_end = rCluster.getIndices().end(); for( ; cit != cit_end; ++cit ) { m_clusterIDs[*cit] = qClusterID; } // and at last merge them qCluster.merge( rCluster ); WAssert( rCluster.empty(), "The right cluster was no empty after melting!" ); } void WMDetTractClustering::connectors() { typedef WModuleInputData< WDataSetFibers > InputData; // just an alias typedef WModuleOutputData< WFiberCluster > OutputData; // -"- m_tractInput = boost::shared_ptr< InputData >( new InputData( shared_from_this(), "tractInput", "A deterministic tract dataset." ) ); m_output = boost::shared_ptr< OutputData >( new OutputData( shared_from_this(), "clusterOutput", "A set of tract indices aka cluster" ) ); addConnector( m_tractInput ); addConnector( m_output ); WModule::connectors(); // call WModules initialization } std::string WMDetTractClustering::lookUpTableFileName() const { std::stringstream newExtension; newExtension << std::fixed << std::setprecision( 2 ); newExtension << ".pt-" << m_proximity_t->get() << ".dlt"; boost::filesystem::path tractFileName( m_tracts->getFileName() ); return tractFileName.replace_extension( newExtension.str() ).string(); } WMDetTractClustering::OutputIDBound::OutputIDBound( const std::vector< WFiberCluster >& clusters ) : m_clusters( clusters ) { } bool WMDetTractClustering::OutputIDBound::accept( boost::shared_ptr< WPropertyVariable< WPVBaseTypes::PV_INT > > /* property */, WPVBaseTypes::PV_INT value ) { return ( value >= 0 ) && ( value < static_cast< int >( m_clusters.size() ) ); }