//---------------------------------------------------------------------------
//
// 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 "core/dataHandler/WDataSetFibers.h"
#include "core/dataHandler/WDataSetScalar.h"
#include "core/dataHandler/WDataSetVector.h"
#include "core/dataHandler/WGridRegular3D.h"
#include "core/graphicsEngine/WGEManagedGroupNode.h"
#include "core/kernel/WKernel.h"
#include "core/kernel/WModuleInputData.h"
#include "WMFiberStipples.h"
#include "WMFiberStipples.xpm"
#include "core/common/math/WMath.h"
#include "core/common/WPropertyHelper.h"
#include "core/graphicsEngine/callbacks/WGELinearTranslationCallback.h"
#include "core/graphicsEngine/callbacks/WGENodeMaskCallback.h"
#include "core/graphicsEngine/callbacks/WGEPropertyUniformCallback.h"
#include "core/graphicsEngine/shaders/WGEPropertyUniform.h"
#include "core/graphicsEngine/shaders/WGEShader.h"
#include "core/graphicsEngine/shaders/WGEShaderDefineOptions.h"
#include "core/graphicsEngine/shaders/WGEShaderPropertyDefineOptions.h"
#include "core/graphicsEngine/shaders/WGEPropertyUniform.h"
#include "core/graphicsEngine/WGEColormapping.h"
#include "core/graphicsEngine/WGEGeodeUtils.h"
#include "core/graphicsEngine/WGraphicsEngine.h"
#include "core/kernel/WSelectionManager.h"
// This line is needed by the module loader to actually find your module. Do not remove. Do NOT add a ";" here.
W_LOADABLE_MODULE( WMFiberStipples )
WMFiberStipples::WMFiberStipples()
: WModule(),
m_first( true )
{
}
WMFiberStipples::~WMFiberStipples()
{
// Cleanup!
}
boost::shared_ptr< WModule > WMFiberStipples::factory() const
{
return boost::shared_ptr< WModule >( new WMFiberStipples() );
}
const char** WMFiberStipples::getXPMIcon() const
{
return WMFiberStipples_xpm;
}
const std::string WMFiberStipples::getName() const
{
return "Fiber Stipples";
}
const std::string WMFiberStipples::getDescription() const
{
return "Slice based probabilistic tract display using Fiber Stipples. (see http://dx.doi.org/10.1109/BioVis.2011.6094044)";
}
void WMFiberStipples::connectors()
{
m_vectorIC = WModuleInputData< WDataSetVector >::createAndAdd( shared_from_this(), "vectors", "Principal diffusion direction." );
m_probIC = WModuleInputData< WDataSetScalar >::createAndAdd( shared_from_this(), "probTract", "Probabilistic tract." );
// call WModule's initialization
WModule::connectors();
}
void WMFiberStipples::properties()
{
// m_sliceGroup = m_properties->addPropertyGroup( "Slices", "Slice based probabilistic tractogram display." );
//
m_Pos = m_properties->addProperty( "Slice position", "Slice position.", 0.0 );
// we don't know anything about data dimensions yet => make slide unusable
m_Pos->setMax( 0 );
m_Pos->setMin( 0 );
m_color = m_properties->addProperty( "Color", "Color for the fiber stipples", WColor( 1.0, 0.0, 0.0, 1.0 ) );
m_threshold = m_properties->addProperty( "Threshold", "Connectivity scores below this threshold will be discarded.", 0.01 );
m_threshold->setMin( 0.0 );
m_threshold->setMax( 1.0 );
// WPropDouble spacing = m_vectorGroup->addProperty( "Spacing", "Spacing of the sprites", 1.0, m_sliceChanged );
// spacing->setMin( 0.25 );
// spacing->setMax( 5.0 );
// WPropDouble glyphSize = m_vectorGroup->addProperty( "Glyph size", "Size of the quads transformed to the glyphs", 1.0 );
// glyphSize->setMin( 0.25 );
// glyphSize->setMax( 5.0 );
// WPropDouble glyphSpacing = m_vectorGroup->addProperty( "Glyph Spacing", "Spacing ", 0.4, m_sliceChanged );
// glyphSpacing->setMin( 0.0 );
// glyphSpacing->setMax( 5.0 );
// WPropDouble glyphThickness = m_vectorGroup->addProperty( "Glyph Thickness", "Line thickness of the glyphs", 1.0 );
// glyphThickness->setMin( 0.01 );
// glyphThickness->setMax( 2.0 );
// call WModule's initialization
WModule::properties();
}
namespace {
osg::ref_ptr< osg::Geode > genScatteredDegeneratedQuads( size_t numSamples, osg::Vec3 const& base, osg::Vec3 const& a, osg::Vec3 const& b )
{
// the stuff needed by the OSG to create a geometry instance
osg::ref_ptr< osg::Vec3Array > vertices = new osg::Vec3Array( numSamples * 4 );
osg::ref_ptr< osg::Vec3Array > texcoords0 = new osg::Vec3Array( numSamples * 4 );
osg::ref_ptr< osg::Vec3Array > texcoords1 = new osg::Vec3Array( numSamples * 4 );
osg::ref_ptr< osg::Vec3Array > normals = new osg::Vec3Array;
osg::ref_ptr< osg::Vec4Array > colors = new osg::Vec4Array;
osg::Vec3 aCrossB = a ^ b;
aCrossB.normalize();
osg::Vec3 aNorm = a;
aNorm.normalize();
osg::Vec3 bNorm = b;
bNorm.normalize();
std::srand( time( NULL ) );
double lambda0, lambda1;
const double rndMax = RAND_MAX;
for( size_t i = 0; i < numSamples; ++i )
{
// The degenerated QUAD should have all points in its center
lambda0 = rand() / rndMax;
lambda1 = rand() / rndMax;
osg::Vec3 quadCenter = base + a * lambda0 + b * lambda1;
for( int j = 0; j < 4; ++j )
{
vertices->push_back( quadCenter );
}
texcoords0->push_back( ( -aNorm + -bNorm ) );
texcoords0->push_back( ( aNorm + -bNorm ) );
texcoords0->push_back( ( aNorm + bNorm ) );
texcoords0->push_back( ( -aNorm + bNorm ) );
texcoords1->push_back( osg::Vec3( 0.0, 0.0, 0.0 ) );
texcoords1->push_back( osg::Vec3( 1.0, 0.0, 0.0 ) );
texcoords1->push_back( osg::Vec3( 1.0, 1.0, 0.0 ) );
texcoords1->push_back( osg::Vec3( 0.0, 1.0, 0.0 ) );
}
normals->push_back( aCrossB );
colors->push_back( osg::Vec4( 1.0, 1.0, 1.0, 1.0 ) );
// put it all together
osg::ref_ptr< osg::Geometry > geometry = new osg::Geometry();
geometry->setVertexArray( vertices );
geometry->setTexCoordArray( 0, texcoords0 );
geometry->setTexCoordArray( 1, texcoords1 );
geometry->setNormalBinding( osg::Geometry::BIND_OVERALL );
geometry->setColorBinding( osg::Geometry::BIND_OVERALL );
geometry->setNormalArray( normals );
geometry->setColorArray( colors );
geometry->addPrimitiveSet( new osg::DrawArrays( osg::PrimitiveSet::QUADS, 0, vertices->size() ) );
osg::ref_ptr< osg::Geode > geode = new osg::Geode();
geode->addDrawable( geometry );
return geode;
}
}
void WMFiberStipples::initOSG( boost::shared_ptr< WDataSetScalar > probTract )
{
debugLog() << "Init OSG";
m_output->clear();
// no colormaps -> no slices
bool empty = !WGEColormapping::instance()->size();
if( empty )
{
// hide the slider properties.
m_Pos->setHidden();
return;
}
// grab the current bounding box for computing the size of the slice
WBoundingBox bb = probTract->getGrid()->getBoundingBox();
WVector3d minV = bb.getMin();
WVector3d maxV = bb.getMax();
WVector3d sizes = ( maxV - minV );
WVector3d midBB = minV + ( sizes * 0.5 );
// update the properties
m_Pos->setMin( minV[1] );
m_Pos->setMax( maxV[1] );
m_Pos->setHidden( false );
// if this is done the first time, set the slices to the center of the dataset
if( m_first )
{
m_first = false;
m_Pos->set( midBB[1] );
}
// create a new geode containing the slices
osg::ref_ptr< osg::Node > slice = genScatteredDegeneratedQuads( 100000, minV, osg::Vec3( sizes[0], 0.0, 0.0 ),
osg::Vec3( 0.0, 0.0, sizes[2] ) );
slice->setName( "Coronal Slice" );
osg::ref_ptr< osg::Uniform > u_aVec = new osg::Uniform( "u_aVec", osg::Vec3( sizes[0], 0.0, 0.0 ) );
osg::ref_ptr< osg::Uniform > u_bVec = new osg::Uniform( "u_bVec", osg::Vec3( 0.0, 0.0, sizes[2] ) );
osg::ref_ptr< osg::Uniform > u_WorldTransform = new osg::Uniform( "u_WorldTransform", osg::Matrix::identity() );
boost::shared_ptr< const WGridRegular3D > grid = boost::shared_dynamic_cast< const WGridRegular3D >( probTract->getGrid() );
if( !grid )
{
errorLog() << "This module can only process probabilistic Tracts with regular 3D grids, Hence you may see garbage from now on.";
}
boost::array< double, 3 > offsets = getOffsets( grid );
osg::ref_ptr< osg::Uniform > u_pixelSizeX = new osg::Uniform( "u_pixelSizeX", static_cast< float >( offsets[0] ) );
osg::ref_ptr< osg::Uniform > u_pixelSizeY = new osg::Uniform( "u_pixelSizeY", static_cast< float >( offsets[1] ) );
osg::ref_ptr< osg::Uniform > u_pixelSizeZ = new osg::Uniform( "u_pixelSizeZ", static_cast< float >( offsets[2] ) );
osg::ref_ptr< osg::Uniform > u_color = new WGEPropertyUniform< WPropColor >( "u_color", m_color );
osg::ref_ptr< osg::Uniform > u_threshold = new WGEPropertyUniform< WPropDouble >( "u_threshold", m_threshold );
osg::ref_ptr< osg::Uniform > u_maxConnectivityScore = new osg::Uniform( "u_maxConnectivityScore", static_cast< float >( probTract->getMax() ) );
osg::StateSet *states = slice->getOrCreateStateSet();
states->addUniform( u_aVec );
states->addUniform( u_bVec );
states->addUniform( u_WorldTransform );
states->addUniform( u_pixelSizeX );
states->addUniform( u_pixelSizeY );
states->addUniform( u_pixelSizeZ );
states->addUniform( u_color );
states->addUniform( u_threshold );
states->addUniform( u_maxConnectivityScore );
slice->setCullingActive( false );
// each slice is child of an transformation node
osg::ref_ptr< osg::MatrixTransform > mT = new osg::MatrixTransform();
mT->addChild( slice );
// Control transformation node by properties. We use an additional uniform here to provide the shader
// the transformation matrix used to translate the slice.
mT->addUpdateCallback( new WGELinearTranslationCallback< WPropDouble >( osg::Vec3( 0.0, 1.0, 0.0 ), m_Pos, u_WorldTransform ) );
m_output->getOrCreateStateSet()->setRenderingHint( osg::StateSet::TRANSPARENT_BIN );
m_output->getOrCreateStateSet()->setMode( GL_BLEND, osg::StateAttribute::ON );
m_output->getOrCreateStateSet()->setMode( GL_LIGHTING, osg::StateAttribute::OFF );
m_output->insert( mT );
m_output->dirtyBound();
}
void WMFiberStipples::moduleMain()
{
// get notified about data changes
m_moduleState.setResetable( true, true );
m_moduleState.add( m_probIC->getDataChangedCondition() );
m_moduleState.add( m_vectorIC->getDataChangedCondition() );
ready();
// graphics setup
m_output = osg::ref_ptr< WGEManagedGroupNode >( new WGEManagedGroupNode( m_active ) );
WKernel::getRunningKernel()->getGraphicsEngine()->getScene()->insert( m_output );
osg::ref_ptr< WGEShader > shader = new WGEShader( "WFiberStipples", m_localPath );
shader->apply( m_output ); // this automatically applies the shader
// main loop
while( !m_shutdownFlag() )
{
infoLog() << "Waiting ...";
m_moduleState.wait();
// woke up since the module is requested to finish?
if( m_shutdownFlag() )
{
break;
}
// save data behind connectors since it might change during processing
boost::shared_ptr< WDataSetVector > vectors = m_vectorIC->getData();
boost::shared_ptr< WDataSetScalar > probTract = m_probIC->getData();
boost::shared_ptr< WGridRegular3D > grid = boost::shared_dynamic_cast< WGridRegular3D >( probTract->getGrid() );
if( !( vectors && probTract ) || !grid ) // if data valid
{
continue;
}
initOSG( probTract );
wge::bindTexture( m_output, vectors->getTexture(), 0, "u_vectors" );
wge::bindTexture( m_output, probTract->getTexture(), 1, "u_probTract" );
// TODO(math): unbind textures, so we have a clean OSG root node for this module again
}
WKernel::getRunningKernel()->getGraphicsEngine()->getScene()->remove( m_output );
}