//--------------------------------------------------------------------------- // // Project: OpenWalnut ( http://www.openwalnut.org ) // // Copyright 2009 OpenWalnut Community, BSV-Leipzig and CNCF-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 "../../common/WAssert.h" #include "../../common/WPropertyHelper.h" #include "../../common/WLimits.h" #include "../../common/WThreadedFunction.h" #include "../../common/WConditionOneShot.h" #include "../../kernel/WKernel.h" #include "WMTeemGlyphs.xpm" #include "WMTeemGlyphs.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( WMTeemGlyphs ) WMTeemGlyphs::WMTeemGlyphs(): WModule(), m_recompute( boost::shared_ptr< WCondition >( new WCondition() ) ) { } namespace { // Helper routine to estimate normals in a triangle soup in which antipodal // vertices are subsequent // taken from teem (glyphElf.c) void estimateNormalsAntipodal( limnPolyData *glyph, const char normalize ) { unsigned int faceno = glyph->indxNum/3; unsigned int *faces = glyph->indx; unsigned int f; memset( glyph->norm, 0, sizeof( float )*3*glyph->xyzwNum ); for( f = 0; f < faceno/2; f++ ) { float diff1[3]; float diff2[3]; float cross[3]; ELL_3V_SUB( diff1, glyph->xyzw+4*faces[3*f+1], glyph->xyzw+4*faces[3*f] ); ELL_3V_SUB( diff2, glyph->xyzw+4*faces[3*f+2], glyph->xyzw+4*faces[3*f] ); ELL_3V_CROSS( cross, diff1, diff2 ); ELL_3V_INCR( glyph->norm+3*faces[3*f], cross ); ELL_3V_INCR( glyph->norm+3*faces[3*f+1], cross ); ELL_3V_INCR( glyph->norm+3*faces[3*f+2], cross ); /* same for anti-face */ if( faces[3*f]%2 == 0 ) { ELL_3V_SUB( glyph->norm+3*faces[3*f]+3, glyph->norm+3*faces[3*f]+3, cross ); } else { ELL_3V_SUB( glyph->norm+3*faces[3*f]-3, glyph->norm+3*faces[3*f]-3, cross ); } if( faces[3*f+1]%2 == 0 ) { ELL_3V_SUB( glyph->norm+3*faces[3*f+1]+3, glyph->norm+3*faces[3*f+1]+3, cross ); } else { ELL_3V_SUB( glyph->norm+3*faces[3*f+1]-3, glyph->norm+3*faces[3*f+1]-3, cross ); } if( faces[3*f+2]%2 == 0 ) { ELL_3V_SUB( glyph->norm+3*faces[3*f+2]+3, glyph->norm+3*faces[3*f+2]+3, cross ); } else { ELL_3V_SUB( glyph->norm+3*faces[3*f+2]-3, glyph->norm+3*faces[3*f+2]-3, cross ); } } if( normalize ) { float len; unsigned int i; for( i = 0; i < glyph->normNum; i++ ) { ELL_3V_NORM_TT( glyph->norm + 3*i, float, glyph->norm + 3*i, len ); } } } } WMTeemGlyphs::~WMTeemGlyphs() { // Cleanup! } boost::shared_ptr< WModule > WMTeemGlyphs::factory() const { return boost::shared_ptr< WModule >( new WMTeemGlyphs() ); } const char** WMTeemGlyphs::getXPMIcon() const { return teemGlyphs_xpm; } const std::string WMTeemGlyphs::getName() const { return "Teem Glyphs"; } const std::string WMTeemGlyphs::getDescription() const { return "Higher-Order Tensor Glyphs as described at http://www.ci.uchicago.edu/~schultz/sphinx/home-glyph.html"; } void WMTeemGlyphs::connectors() { m_input = boost::shared_ptr< WModuleInputData < WDataSetSphericalHarmonics > >( new WModuleInputData< WDataSetSphericalHarmonics >( shared_from_this(), "in", "The input dataset." ) ); addConnector( m_input ); m_inputGFA = boost::shared_ptr< WModuleInputData< WDataSetScalar > >( new WModuleInputData< WDataSetScalar >( shared_from_this(), "inGFA", "Generalized fractional anisotropy." ) ); addConnector( m_inputGFA ); // call WModules initialization WModule::connectors(); } void WMTeemGlyphs::properties() { m_sliceOrientations = boost::shared_ptr< WItemSelection >( new WItemSelection() ); m_sliceOrientations->addItem( "x", "x-slice" ); m_sliceOrientations->addItem( "y", "y-slice" ); m_sliceOrientations->addItem( "z", "z-slice" ); m_sliceOrientationSelectionProp = m_properties->addProperty( "Slice orientation", "Which slice will be shown?", m_sliceOrientations->getSelector( 1 ), m_recompute ); WPropertyHelper::PC_SELECTONLYONE::addTo( m_sliceOrientationSelectionProp ); m_sliceIdProp = m_properties->addProperty( "Slice ID", "Number of the slice to display", 100, m_recompute ); m_sliceIdProp->setMin( 0 ); m_sliceIdProp->setMax( 128 ); m_orders = boost::shared_ptr< WItemSelection >( new WItemSelection() ); m_orders->addItem( "2", "Order 2" ); m_orders->addItem( "4", "Order 4" ); m_orders->addItem( "6", "Order 6" ); m_orderProp = m_properties->addProperty( "Order", "Order of the displayed spherical harmonics." " If actual order is higer, the additional coefficients are ignored.", m_orders->getSelector( 1 ), m_recompute ); WPropertyHelper::PC_SELECTONLYONE::addTo( m_orderProp ); m_GFAThresholdProp = m_properties->addProperty( "GFA threshold", "Show only glyphs at voxels above the given generalized fractional" " anisotropy (GFA) threshold" " (if GFA data is present at input connector).", 0.0, m_recompute ); m_GFAThresholdProp->setMin( 0.0 ); m_GFAThresholdProp->setMax( 1.0 ); m_glyphSizeProp = m_properties->addProperty( "Glyph size", "Size of the displayed glyphs.", 1.0, m_recompute ); m_glyphSizeProp->setMin( 0 ); m_glyphSizeProp->setMax( 100. ); m_moduloProp = m_properties->addProperty( "Modulo", "Shows only every Modulo-th glyph in the two slice directions", 2, m_recompute ); m_moduloProp->setMin( 1 ); m_moduloProp->setMax( 10 ); m_subdivisionLevelProp = m_properties->addProperty( "Subdivision level", "Determines the glyph resolution. Subdivision level of" " icosahadra use as sphere approximations.", 2, m_recompute ); m_subdivisionLevelProp->setMin( 0 ); m_subdivisionLevelProp->setMax( 5 ); m_usePolarPlotProp = m_properties->addProperty( "Use polar plot", "Use polar plot for glyph instead of HOME?", true, m_recompute ); m_useNormalizationProp = m_properties->addProperty( "Min-max normalization", "Scale the radius of each glyph to be in [0,1].", true, m_recompute ); WModule::properties(); } void WMTeemGlyphs::moduleMain() { m_moduleState.add( m_input->getDataChangedCondition() ); m_moduleState.add( m_recompute ); ready(); // loop until the module container requests the module to quit while ( !m_shutdownFlag() ) { if( !m_input->getData().get() ) { // OK, the output has not yet sent data debugLog() << "Waiting for data ..."; m_moduleState.wait(); continue; } // std::cout << "FA: " << m_GFAThresholdProp->get( true ) << std::endl; if( m_input->getData().get() ) { bool dataChanged = false; if( m_dataSet != m_input->getData() ) { // acquire data from the input connector m_dataSet = m_input->getData(); dataChanged = true; } boost::shared_ptr< WGridRegular3D > gridReg = boost::shared_dynamic_cast< WGridRegular3D >( m_input->getData().get()->getGrid() ); switch( m_sliceOrientationSelectionProp->get( true ).getItemIndexOfSelected( 0 ) ) { case 0: m_sliceIdProp->setMax( gridReg->getNbCoordsX() - 1 ); break; case 1: m_sliceIdProp->setMax( gridReg->getNbCoordsY() - 1 ); break; case 2: m_sliceIdProp->setMax( gridReg->getNbCoordsZ() - 1 ); break; } if( dataChanged ) { m_sliceIdProp->set( m_sliceIdProp->getMax()->getMax() / 2 ); } boost::shared_ptr< WDataSetScalar > gfa = m_inputGFA->getData(); if( gfa ) { m_GFAThresholdProp->setMax( gfa->getMax() ); m_GFAThresholdProp->setMin( gfa->getMin() ); } renderSlice( m_sliceIdProp->get() ); } m_moduleState.wait(); } WKernel::getRunningKernel()->getGraphicsEngine()->getScene()->remove( m_moduleNode ); } void WMTeemGlyphs::renderSlice( size_t sliceId ) { boost::shared_ptr< WProgress > progress; progress = boost::shared_ptr< WProgress >( new WProgress( "Glyph Generation", 2 ) ); m_progress->addSubProgress( progress ); size_t sliceType = m_sliceOrientationSelectionProp->get( true ).getItemIndexOfSelected( 0 ); size_t order = boost::lexical_cast< float >( m_orders->getSelector( m_orderProp->get().getItemIndexOfSelected( 0 ) ) .at( 0 )->getName() ); // Please look here http://www.ci.uchicago.edu/~schultz/sphinx/home-glyph.htm if( m_moduleNode ) { WKernel::getRunningKernel()->getGraphicsEngine()->getScene()->remove( m_moduleNode ); } //----------------------------------------------------------- // run through the positions in the slice and draw the glyphs boost::shared_ptr< GlyphGeneration > generator; generator = boost::shared_ptr< GlyphGeneration >( new GlyphGeneration( boost::shared_dynamic_cast< WDataSetSphericalHarmonics >( m_input->getData() ), boost::shared_dynamic_cast< WDataSetScalar >( m_inputGFA->getData() ), m_GFAThresholdProp->get(), sliceId, order, m_subdivisionLevelProp->get(), m_moduloProp->get(), sliceType, m_usePolarPlotProp->get(), m_glyphSizeProp->get(), m_useNormalizationProp->get() ) ); WThreadedFunction< GlyphGeneration > generatorThreaded( W_AUTOMATIC_NB_THREADS, generator ); generatorThreaded.run(); generatorThreaded.wait(); ++*progress; m_moduleNode = osg::ref_ptr< WGEGroupNode >( new WGEGroupNode() ); osg::ref_ptr< osg::Geode > glyphsGeode = generator->getGraphics(); m_moduleNode->insert( glyphsGeode ); m_moduleNode->setName( "teem glyphs module node" ); m_shader = osg::ref_ptr< WShader > ( new WShader( "WMTeemGlyphs", m_localPath ) ); m_shader->apply( glyphsGeode ); WKernel::getRunningKernel()->getGraphicsEngine()->getScene()->insert( m_moduleNode ); progress->finish(); } void WMTeemGlyphs::activate() { if( m_moduleNode ) { if( m_active->get() ) { m_moduleNode->setNodeMask( 0xFFFFFFFF ); } else { m_moduleNode->setNodeMask( 0x0 ); } } WModule::activate(); } void WMTeemGlyphs::GlyphGeneration::minMaxNormalization( limnPolyData *glyph, const size_t& nbVertCoords ) { // double min = wlimits::MAX_DOUBLE; // double max = -wlimits::MAX_DOUBLE; double min = 1e15; double max = -1e15; for( size_t vertID = 0; vertID < glyph->xyzwNum; ++vertID ) { wmath::WPosition pos( glyph->xyzw[nbVertCoords*vertID], glyph->xyzw[nbVertCoords*vertID+1], glyph->xyzw[nbVertCoords*vertID+2] ); double norm = pos.norm(); if( norm < min ) { min = norm; } if( norm > max ) { max = norm; } } double dist = max - min; if( dist != 0 ) { WAssert( dist > 0, "Max has to be larger than min." ); for( size_t i = 0; i < glyph->xyzwNum; ++i ) { size_t coordIdBase = nbVertCoords * i; wmath::WPosition pos( glyph->xyzw[coordIdBase], glyph->xyzw[coordIdBase+1], glyph->xyzw[coordIdBase+2] ); double norm = pos.norm(); const double epsilon = 1e-9; wmath::WPosition newPos; // newPos = ( ( ( norm - min ) / dist ) + epsilon ) * pos.normalized(); newPos = ( ( ( norm - min ) / dist ) + epsilon ) * pos / norm; glyph->xyzw[coordIdBase] = newPos[0]; glyph->xyzw[coordIdBase+1] = newPos[1]; glyph->xyzw[coordIdBase+2] = newPos[2]; } } // else do nothing because all values are equal. } WMTeemGlyphs::GlyphGeneration::GlyphGeneration( boost::shared_ptr< WDataSetSphericalHarmonics > dataSet, boost::shared_ptr< WDataSetScalar > dataGFA, double thresholdGFA, const size_t& sliceId, const size_t& order, const size_t& subdivisionLevel, const size_t& modulo, const size_t& sliceType, const bool& usePolar, const float& scale, const bool& useNormalization ) : m_dataSet( dataSet ), m_dataGFA( dataGFA ), m_grid( boost::shared_dynamic_cast< WGridRegular3D >( dataSet->getGrid() ) ), m_thresholdGFA( thresholdGFA ), m_order( order ), m_sliceType( sliceType ), m_subdivisionLevel( subdivisionLevel ), m_modulo( modulo ), m_usePolar( usePolar ), m_scale( scale ), m_useNormalization( useNormalization ) { enum sliceTypeEnum { xSlice = 0, ySlice, zSlice }; // convenience names for grid dimensions m_nX = m_grid->getNbCoordsX(); m_nY = m_grid->getNbCoordsY(); m_nZ = m_grid->getNbCoordsZ(); m_sliceId = sliceId; switch( sliceType ) { case xSlice: m_nA = m_nY; m_nB = m_nZ; break; case ySlice: m_nA = m_nX; m_nB = m_nZ; break; case zSlice: m_nA = m_nX; m_nB = m_nY; break; } size_t nbGlyphs = ( m_nA * m_nB ) / ( m_modulo * m_modulo ); const unsigned int level = m_subdivisionLevel; // subdivision level of sphere unsigned int infoBitFlag = ( 1 << limnPolyDataInfoNorm ) | ( 1 << limnPolyDataInfoRGBA ); m_sphere = limnPolyDataNew(); limnPolyDataIcoSphere( m_sphere, infoBitFlag, level ); size_t nbVerts = m_sphere->xyzwNum; m_vertArray = osg::ref_ptr< osg::Vec3Array >( new osg::Vec3Array() ); m_normals = osg::ref_ptr< osg::Vec3Array >( new osg::Vec3Array() ); m_colors = osg::ref_ptr< osg::Vec4Array >( new osg::Vec4Array() ); m_vertArray->resize( nbVerts * nbGlyphs ); m_normals->resize( nbVerts * nbGlyphs ); m_colors->resize( nbVerts * nbGlyphs, osg::Vec4( 0, 0, 0, 1.0 ) ); m_glyphElements = osg::ref_ptr< osg::DrawElementsUInt >( new osg::DrawElementsUInt( osg::PrimitiveSet::TRIANGLES ) ); m_glyphElements->resize( m_sphere->indxNum * nbGlyphs ); } WMTeemGlyphs::GlyphGeneration::~GlyphGeneration() { // free memory m_sphere = limnPolyDataNix( m_sphere ); } void WMTeemGlyphs::GlyphGeneration::operator()( size_t id, size_t numThreads, WBoolFlag& /*b*/ ) { const size_t nbVertCoords = 4; //The teem limn data structure has 4 values for a coordinate: x, y, z, w. limnPolyData *localSphere = limnPolyDataNew(); limnPolyDataCopy( localSphere, m_sphere ); enum sliceTypeEnum { xSlice = 0, ySlice, zSlice }; WAssert( m_sphere->xyzwNum == m_sphere->normNum, "Wrong size of arrays." ); WAssert( m_sphere->xyzwNum == m_sphere->rgbaNum, "Wrong size of arrays." ); size_t nbVerts = m_sphere->xyzwNum; const tijk_type *type = 0; // Initialized to quiet compiler switch( m_order ) { case 2: type = tijk_2o3d_sym; break; case 4: type = tijk_4o3d_sym; break; case 6: type = tijk_6o3d_sym; break; default: WAssert( false, "order above 6 not supported yet." ); } // memory for the tensor and spherical harmonics data. float* ten = new float[type->num]; float* res = new float[type->num]; float* esh = new float[type->num]; //****************************************** // Please take care when changing something between here and the following mark. // Sizes and upper bound are carefully adjusted to avoid segfault with modulo // and to avoid unwanted gaps if computing with many threads. size_t chunkSize = m_nA / numThreads; size_t first = id * chunkSize; size_t last = ( id + 1 ) * chunkSize - 1; if( id == numThreads - 1 ) { last = m_nA - 2; } std::stringstream ss; ss << id << "/" << numThreads <<" (" << first << " ... " << last << ")[" << chunkSize << "/" << m_nA << "]" << std::endl; WLogger::getLogger()->addLogMessage( ss.str(), "______", LL_DEBUG ); for( size_t aId = first; aId <= last; ++aId ) { for( size_t bId = 0; bId < m_nB; ++bId ) { // Please take care when changing something between here and the above mark. //****************************************** if( !( ( aId % m_modulo == 0) && ( bId % m_modulo == 0 ) ) ) { continue; } size_t glyphId = ( aId / m_modulo ) * ( m_nB / m_modulo ) + ( bId / m_modulo ); size_t vertsUpToCurrentIteration = glyphId * nbVerts; size_t idsUpToCurrentIteration = glyphId * m_sphere->indxNum; size_t posId = 0; // initialized to quiet compiler switch( m_sliceType ) { case xSlice: posId = m_sliceId + aId * m_nX + bId * m_nX * m_nY; break; case ySlice: posId = aId + m_sliceId * m_nX + bId * m_nX * m_nY; break; case zSlice: posId = aId + bId * m_nX + m_sliceId * m_nX * m_nY; break; } //------------------------------- // vertex indices // We have to set them also if we do not draw the glyph because otherwise we would leave their // to be zero. If many indices are zero, they block each other because of synchronized // memory access to the same memory address. for( unsigned int vertId = 0; vertId < localSphere->indxNum; ++vertId ) { ( *m_glyphElements )[idsUpToCurrentIteration+vertId] = ( vertsUpToCurrentIteration + localSphere->indx[vertId] ); } // do not compute positions of vertices if GFA below threshold if( m_dataGFA && boost::shared_static_cast< WDataSetSingle >( m_dataGFA )->getValueAt( posId ) < m_thresholdGFA ) { continue; } wmath::WValue< double > coeffs = m_dataSet->getSphericalHarmonicAt( posId ).getCoefficients(); switch( m_order ) { case 2: coeffs.resize( 6 ); break; case 4: coeffs.resize( 15 ); break; case 6: coeffs.resize( 28 ); break; default: WAssert( false, "order above 6 not supported yet." ); } for( size_t coeffId = 0; coeffId < coeffs.size(); coeffId++ ) { esh[coeffId] = coeffs[coeffId]; } // convert even-order spherical harmonics to higher-order tensor tijk_esh_to_3d_sym_f( ten, esh, m_order ); // create positive approximation of the tensor tijk_refine_rankk_parm *parm = tijk_refine_rankk_parm_new(); parm->pos = 1; int ret = tijk_approx_rankk_3d_f( NULL, NULL, res, ten, type, 6, parm ); WAssert( ret == 0, "Error condition in call to tijk_approx_rankk_3d_f." ); parm = tijk_refine_rankk_parm_nix( parm ); tijk_sub_f( ten, ten, res, type ); const char normalize = 0; limnPolyData *glyph = limnPolyDataNew(); limnPolyDataCopy( glyph, localSphere ); double radius = 1.0; // some initialization if( m_usePolar ) { char isdef = 3; // some initialization radius = elfGlyphPolar( glyph, 1, ten, type, &isdef, 0, normalize, NULL, NULL ); WAssert( isdef != 0, "Tensor is non positive definite. Think about that." ); } else { radius = elfGlyphHOME( glyph, 1, ten, type, NULL, normalize ); } // ------------------------------------------------------------------------------------------------------- // One can insert per-peak coloring here (see http://www.ci.uchicago.edu/~schultz/sphinx/home-glyph.html ) // ------------------------------------------------------------------------------------------------------- float scale = m_scale; if( m_useNormalization ) { minMaxNormalization( glyph, nbVertCoords ); } else { // std::cout<< radius << " " << m_scale << std::endl; if( radius != 0 ) { scale = m_scale / radius; } // std::cout<< m_scale << std::endl<< std::endl; } estimateNormalsAntipodal( glyph, normalize ); wmath::WPosition glyphPos = m_grid->getPosition( posId ); for( unsigned int vertId = 0; vertId < glyph->xyzwNum; ++vertId ) { size_t globalVertexId = vertsUpToCurrentIteration + vertId; //------------------------------- // vertices ( *m_vertArray )[globalVertexId][0] = glyph->xyzw[nbVertCoords*vertId ] * scale + glyphPos[0]; ( *m_vertArray )[globalVertexId][1] = glyph->xyzw[nbVertCoords*vertId+1] * scale + glyphPos[1]; ( *m_vertArray )[globalVertexId][2] = glyph->xyzw[nbVertCoords*vertId+2] * scale + glyphPos[2]; // ------------------------------------------------ // normals ( *m_normals )[globalVertexId][0] = glyph->norm[3*vertId]; ( *m_normals )[globalVertexId][1] = glyph->norm[3*vertId+1]; ( *m_normals )[globalVertexId][2] = glyph->norm[3*vertId+2]; ( *m_normals )[globalVertexId].normalize(); // ------------------------------------------------ // colors const size_t nbColCoords = 4; ( *m_colors )[globalVertexId][0] = glyph->rgba[nbColCoords*vertId] / 255.0; ( *m_colors )[globalVertexId][1] = glyph->rgba[nbColCoords*vertId+1] / 255.0; ( *m_colors )[globalVertexId][2] = glyph->rgba[nbColCoords*vertId+2] / 255.0; ( *m_colors )[globalVertexId][3] = glyph->rgba[nbColCoords*vertId+3] / 255.0; } // free memory glyph = limnPolyDataNix( glyph ); } } // free memory localSphere = limnPolyDataNix( localSphere ); delete[] ten; delete[] res; delete[] esh; } osg::ref_ptr< osg::Geode > WMTeemGlyphs::GlyphGeneration::getGraphics() { osg::ref_ptr< osg::Geometry > glyphGeometry = new osg::Geometry(); glyphGeometry->setVertexArray( m_vertArray ); glyphGeometry->addPrimitiveSet( m_glyphElements ); glyphGeometry->setNormalArray( m_normals ); glyphGeometry->setNormalBinding( osg::Geometry::BIND_PER_VERTEX ); glyphGeometry->setColorArray( m_colors ); glyphGeometry->setColorBinding( osg::Geometry::BIND_PER_VERTEX ); osg::ref_ptr< osg::Geode > glyphsGeode; glyphsGeode = osg::ref_ptr< osg::Geode >( new osg::Geode ); glyphsGeode->setName( "teem glyphs" ); osg::StateSet* state = glyphsGeode->getOrCreateStateSet(); state->setMode( GL_BLEND, osg::StateAttribute::ON ); glyphsGeode->addDrawable( glyphGeometry ); return glyphsGeode; }