Commit 7670d295 authored by Cornelius Müller's avatar Cornelius Müller
Browse files

[ADD #163] The time positions where dipoles exists is marked in the EEG view;...

[ADD #163] The time positions where dipoles exists is marked in the EEG view; the magnitude of the dipoles is color coded. Added new property snapToDipole. If it is enabled and a time position is marked in the EEG view, the position is snapped to the nearest position where an active dipole exists.
parent c4ebcb7d
......@@ -30,18 +30,21 @@
boost::shared_ptr< WPrototyped > WDataSetDipoles::m_prototype = boost::shared_ptr< WPrototyped >();
WDataSetDipoles::WDataSetDipoles()
WDataSetDipoles::WDataSetDipoles() :
m_maxMagnitude( 0.0f )
{
}
WDataSetDipoles::WDataSetDipoles( WPosition dipPos, std::vector<float> mags, std::vector<float> times )
WDataSetDipoles::WDataSetDipoles( WPosition dipPos, std::vector<float> mags, std::vector<float> times,
size_t firstTimeStep, size_t lastTimeStep ) :
m_maxMagnitude( 0.0f )
{
WAssert( mags.size() == times.size(), "There has to be a magnitude for every time and vice versa." );
for( size_t id = 0; id < times.size() - 1; ++id )
{
WAssert( times[id] < times[id+1], "Times need to be ascending." );
}
addDipole( dipPos, mags, times );
addDipole( dipPos, mags, times, firstTimeStep, lastTimeStep );
}
WDataSetDipoles::~WDataSetDipoles()
......@@ -58,13 +61,25 @@ boost::shared_ptr< WPrototyped > WDataSetDipoles::getPrototype()
return m_prototype;
}
size_t WDataSetDipoles::addDipole( WPosition dipPos, std::vector<float> mags, std::vector<float> times )
size_t WDataSetDipoles::addDipole( WPosition dipPos, std::vector<float> mags, std::vector<float> times,
size_t firstTimeStep, size_t lastTimeStep )
{
Dipole dipole;
dipole.m_dipolePosition = dipPos;
dipole.m_magnitudes = mags;
dipole.m_times = times;
dipole.m_firstTimeStep = firstTimeStep;
dipole.m_lastTimeStep = lastTimeStep;
m_dipoles.push_back( dipole );
for( size_t id = 0u; id < mags.size(); ++id )
{
if( mags[id] > m_maxMagnitude )
{
m_maxMagnitude = mags[id];
}
}
return m_dipoles.size() - 1;
}
......@@ -73,11 +88,42 @@ WPosition WDataSetDipoles::getPosition( size_t dipoleId )
return m_dipoles[dipoleId].m_dipolePosition;
}
float WDataSetDipoles::getStartTime( size_t dipoleId ) const
{
return m_dipoles[dipoleId].m_times[m_dipoles[dipoleId].m_firstTimeStep];
}
float WDataSetDipoles::getEndTime( size_t dipoleId ) const
{
return m_dipoles[dipoleId].m_times[m_dipoles[dipoleId].m_lastTimeStep];
}
std::vector<float> WDataSetDipoles::getTimes( size_t dipoleId ) const
{
const Dipole& dipole = m_dipoles[dipoleId];
const std::vector<float>::const_iterator& begin = dipole.m_times.begin();
return std::vector<float>( begin + dipole.m_firstTimeStep, begin + ( dipole.m_lastTimeStep + 1u ) );
}
std::vector<float> WDataSetDipoles::getMagnitudes( size_t dipoleId ) const
{
const Dipole& dipole = m_dipoles[dipoleId];
const std::vector<float>::const_iterator& begin = dipole.m_magnitudes.begin();
return std::vector<float>( begin + dipole.m_firstTimeStep, begin + ( dipole.m_lastTimeStep + 1u ) );
}
size_t WDataSetDipoles::getNumberOfDipoles()
{
return m_dipoles.size();
}
float WDataSetDipoles::getMaxMagnitude() const
{
return m_maxMagnitude;
}
float WDataSetDipoles::getMagnitude( float time, size_t dipoleId )
{
std::vector<float>& times = m_dipoles[dipoleId].m_times;
......@@ -89,12 +135,11 @@ float WDataSetDipoles::getMagnitude( float time, size_t dipoleId )
}
else
{
size_t upperBoundId;
for( size_t id = 0; id < times.size(); ++id )
size_t upperBoundId = 1u;
for( ; upperBoundId < times.size() - 1u; ++upperBoundId )
{
if( time < times[id] )
if( time < times[upperBoundId] )
{
upperBoundId = id - 1;
break;
}
}
......
......@@ -49,8 +49,11 @@ public:
* \param dipPos Spatial location of the dipole
* \param mags Magnitudes of dipole over time
* \param times Times for the dipole activity
* \param firstTimeStep First time where the magnitude is not 0
* \param lastTimeStep Last time where the magnitude is not 0
*/
explicit WDataSetDipoles( WPosition dipPos, std::vector<float> mags, std::vector<float> times );
explicit WDataSetDipoles( WPosition dipPos, std::vector<float> mags, std::vector<float> times,
size_t firstTimeStep, size_t lastTimeStep );
/**
* Destructs this dataset.
......@@ -70,10 +73,13 @@ public:
* \param dipPos Spatial location of the dipole
* \param mags Magnitudes of dipole over time
* \param times Times for the dipole activity
* \param firstTimeStep First time where the magnitude is not 0
* \param lastTimeStep Last time where the magnitude is not 0
*
* \return Id of the added dipole.
*/
size_t addDipole( WPosition dipPos, std::vector<float> mags, std::vector<float> times );
size_t addDipole( WPosition dipPos, std::vector<float> mags, std::vector<float> times,
size_t firstTimeStep, size_t lastTimeStep );
/**
* Return position of dipole.
......@@ -92,6 +98,38 @@ public:
*/
float getMagnitude( float time, size_t dipoleId = 0 );
/**
* Return first time where the magnitude is not 0.
*
* \param dipoleId Id number of dipole
* \return First time where the magnitude is not 0.
*/
float getStartTime( size_t dipoleId = 0u ) const;
/**
* Return last time where the magnitude is not 0.
*
* \param dipoleId Id number of dipole
* \return Last time where the magnitude is not 0.
*/
float getEndTime( size_t dipoleId = 0u ) const;
/**
* Return the times where the magnitude is not 0.
*
* \param dipoleId Id number of dipole
* \return Times where the magnitude is not 0.
*/
std::vector<float> getTimes( size_t dipoleId = 0u ) const;
/**
* Return the magnitudes where the magnitude is not 0.
*
* \param dipoleId Id number of dipole
* \return Magnitudes where the magnitude is not 0.
*/
std::vector<float> getMagnitudes( size_t dipoleId = 0u ) const;
/**
* Return number of dipoles in this dataset
*
......@@ -99,6 +137,13 @@ public:
*/
size_t getNumberOfDipoles();
/**
* Return the biggest magnitude of all dipoles.
*
* \return Biggest magnitude of all dipoles.
*/
float getMaxMagnitude() const;
protected:
/**
* The prototype as singleton.
......@@ -115,9 +160,12 @@ private:
WPosition m_dipolePosition; //!< The location of the dipole
std::vector<float> m_magnitudes; //!< The magnitude of the dipole
std::vector<float> m_times; //!< Times for the different magnitudes
size_t m_firstTimeStep; //!< First time where the magnitude is not 0
size_t m_lastTimeStep; //!< Last time where the magnitude is not 0
};
std::vector< Dipole > m_dipoles; //!< List of dipoles representeing this dipoles dataset
float m_maxMagnitude; //!< Biggest magnitude of all dipoles
};
#endif // WDATASETDIPOLES_H
......@@ -24,6 +24,7 @@
#include <cstddef>
#include <limits>
#include <sstream>
#include <vector>
......@@ -40,6 +41,7 @@
#include "core/common/exceptions/WOutOfBounds.h"
#include "core/common/WStringUtils.h"
#include "core/dataHandler/WDataSetDipoles.h"
#include "core/dataHandler/WEEG2.h"
#include "core/dataHandler/WEEG2Segment.h"
#include "core/dataHandler/WEEGValueMatrix.h"
......@@ -51,14 +53,50 @@ WEEGEvent::WEEGEvent( double time,
double yPos,
boost::shared_ptr< WEEG2 > eeg,
std::size_t segmentID,
osg::ref_ptr< WGEGroupNode > parentNode ) throw( WOutOfBounds )
osg::ref_ptr< WGEGroupNode > parentNode,
bool snapToDipole,
bool proofOfConcept,
boost::shared_ptr< WDataSetDipoles > dipoles ) throw( WOutOfBounds )
: m_time( time ),
m_parentNode( parentNode )
{
if( segmentID < eeg->getNumberOfSegments() )
{
boost::shared_ptr< WEEG2Segment > segment = eeg->getSegment( segmentID );
// snap to dipole
if( snapToDipole && !proofOfConcept && dipoles.get() )
{
const double epsilon = 1e-4;
double error = std::numeric_limits<double>::infinity();
for( size_t dipoleId = 0u; dipoleId < dipoles->getNumberOfDipoles(); ++dipoleId )
{
if( time < dipoles->getStartTime( dipoleId ) )
{
if( dipoles->getStartTime( dipoleId ) - time < error )
{
m_time = dipoles->getStartTime( dipoleId ) + epsilon;
error = dipoles->getStartTime( dipoleId ) - time;
}
}
else if( time > dipoles->getEndTime( dipoleId ) )
{
if( time - dipoles->getEndTime( dipoleId ) < error )
{
m_time = dipoles->getEndTime( dipoleId ) - epsilon;
error = time - dipoles->getEndTime( dipoleId );
}
}
else
{
m_time = time;
// error = 0.0;
break;
}
}
}
const double sampleIDAsDouble = m_time * eeg->getSamplingRate();
boost::shared_ptr< WEEG2Segment > segment = eeg->getSegment( segmentID );
if( 0.0 <= sampleIDAsDouble && sampleIDAsDouble < segment->getNumberOfSamples() - 1 )
{
// calculate value of each channel at the given time position using
......@@ -81,8 +119,8 @@ WEEGEvent::WEEGEvent( double time,
osg::Vec3Array* vertices = new osg::Vec3Array();
vertices->reserve( 2 );
vertices->push_back( osg::Vec3( time, -1048576.0f, 0.0f ) );
vertices->push_back( osg::Vec3( time, 1024.0f, 0.0f ) );
vertices->push_back( osg::Vec3( m_time, -1048576.0f, 0.0f ) );
vertices->push_back( osg::Vec3( m_time, 1024.0f, 0.0f ) );
geometry->setVertexArray( vertices );
osg::Vec4Array* colors = new osg::Vec4Array;
......@@ -94,8 +132,8 @@ WEEGEvent::WEEGEvent( double time,
// create text for the time label
osgText::Text* text = new osgText::Text;
text->setText( string_utils::toString( time ).c_str() );
text->setPosition( osg::Vec3( time, yPos + 10.0, 0.0 ) );
text->setText( string_utils::toString( m_time ).c_str() );
text->setPosition( osg::Vec3( m_time, yPos + 10.0, 0.0 ) );
text->setAlignment( osgText::Text::LEFT_CENTER );
text->setAxisAlignment( osgText::Text::SCREEN );
text->setCharacterSize( 12 );
......
......@@ -35,6 +35,7 @@
#include <osg/ref_ptr>
#include "core/common/exceptions/WOutOfBounds.h"
#include "core/dataHandler/WDataSetDipoles.h"
#include "core/dataHandler/WEEG2.h"
#include "core/graphicsEngine/WGEGroupNode.h"
......@@ -48,17 +49,23 @@ public:
/**
* Constructor
*
* \param time time position in seconds
* \param yPos y position in pixels
* \param eeg pointer to the loaded EEG dataset
* \param segmentID number of the segment
* \param parentNode node where m_node is inserted and removed from
* \param time time position in seconds
* \param yPos y position in pixels
* \param eeg pointer to the loaded EEG dataset
* \param segmentID number of the segment
* \param parentNode node where m_node is inserted and removed from
* \param snapToDipole whether the selected time position should be snapped to an active dipole
* \param proofOfConcept whether we only show the proof of concept or the real dipoles
* \param dipoles pointer to the loaded dipoles dataset
*/
WEEGEvent( double time,
double yPos,
boost::shared_ptr< WEEG2 > eeg,
std::size_t segmentID,
osg::ref_ptr< WGEGroupNode > parentNode ) throw( WOutOfBounds );
osg::ref_ptr< WGEGroupNode > parentNode,
bool snapToDipole,
bool proofOfConcept,
boost::shared_ptr< WDataSetDipoles > dipoles ) throw( WOutOfBounds );
/**
* Constructor for an empty event
......@@ -86,7 +93,7 @@ public:
protected:
private:
const double m_time; //!< time position in seconds
double m_time; //!< time position in seconds
std::vector< double > m_values; //!< the value of each channel at the given time position
......
......@@ -35,6 +35,7 @@
#include "core/common/WFlag.h"
#include "core/common/WPropertyTypes.h"
#include "core/common/WPropertyVariable.h"
#include "core/dataHandler/WDataSetDipoles.h"
#include "core/dataHandler/WEEG2.h"
#include "core/graphicsEngine/WGEGroupNode.h"
#include "WEEGEvent.h"
......@@ -52,7 +53,10 @@ WEEGViewHandler::WEEGViewHandler( WPropInt labelsWidth,
boost::shared_ptr< WFlag< boost::shared_ptr< WEEGEvent > > > event,
osg::ref_ptr< WGEGroupNode > eventParentNode,
boost::shared_ptr< WEEG2 > eeg,
std::size_t segmentID )
std::size_t segmentID,
WPropBool snapToDipole,
WPropBool proofOfConcept,
boost::shared_ptr< WDataSetDipoles > dipoles )
: m_labelsWidth( labelsWidth ),
m_timePos( timePos ),
m_timeRange( timeRange ),
......@@ -64,7 +68,10 @@ WEEGViewHandler::WEEGViewHandler( WPropInt labelsWidth,
m_event( event ),
m_eventParentNode( eventParentNode ),
m_eeg( eeg ),
m_segmentID( segmentID )
m_segmentID( segmentID ),
m_snapToDipole( snapToDipole ),
m_proofOfConcept( proofOfConcept ),
m_dipoles( dipoles )
{
WAssert( eventParentNode.valid(), "No Event Parent Node" );
WAssert( eeg, "No EEG data" );
......@@ -232,7 +239,10 @@ bool WEEGViewHandler::markEvent( float x )
m_yPos->get(),
m_eeg,
m_segmentID,
m_eventParentNode ) );
m_eventParentNode,
m_snapToDipole->get(),
m_proofOfConcept->get(),
m_dipoles ) );
m_event->set( event );
handled = true;
}
......
......@@ -36,6 +36,7 @@
#include "core/common/WFlag.h"
#include "core/common/WPropertyTypes.h"
#include "core/dataHandler/WDataSetDipoles.h"
#include "core/dataHandler/WEEG2.h"
#include "core/graphicsEngine/WGEGroupNode.h"
#include "WEEGEvent.h"
......@@ -70,6 +71,12 @@ public:
* inserted and removed from
* \param eeg pointer to the loaded EEG dataset
* \param segmentID number of the segment
* \param snapToDipole Property determining whether the selected time
* position should be snapped to an active
* dipole
* \param proofOfConcept Property determining whether we only show the
* proof of concept or the real dipoles
* \param dipoles pointer to the loaded dipoles dataset
*/
WEEGViewHandler( WPropInt labelsWidth,
WPropDouble timePos,
......@@ -82,7 +89,10 @@ public:
boost::shared_ptr< WFlag< boost::shared_ptr< WEEGEvent > > > event,
osg::ref_ptr< WGEGroupNode > eventParentNode,
boost::shared_ptr< WEEG2 > eeg,
std::size_t segmentID );
std::size_t segmentID,
WPropBool snapToDipole,
WPropBool proofOfConcept,
boost::shared_ptr< WDataSetDipoles > dipoles );
/**
* Handle events.
......@@ -156,6 +166,21 @@ private:
*/
std::size_t m_segmentID;
/**
* Property determining whether the selected time position should be snapped to an active dipole
*/
WPropBool m_snapToDipole;
/**
* Property determining whether we only show the proof of concept or the real dipoles
*/
WPropBool m_proofOfConcept;
/**
* Pointer to the loaded dipoles dataset
*/
boost::shared_ptr< WDataSetDipoles > m_dipoles;
float m_oldX; //!< previous mouse x position
float m_oldY; //!< previous mouse y position
......
......@@ -25,6 +25,7 @@
#include <string>
#include <vector>
#include <osg/BlendFunc>
#include <osg/LightModel>
#include <osg/ShapeDrawable>
#include <osgSim/ColorRange>
......@@ -93,13 +94,13 @@ const char** WMEEGView::getXPMIcon() const
void WMEEGView::connectors()
{
m_input = boost::shared_ptr< WModuleInputData< WEEG2 > >( new WModuleInputData< WEEG2 >(
m_eeg_input = boost::shared_ptr< WModuleInputData< WEEG2 > >( new WModuleInputData< WEEG2 >(
shared_from_this(), "EEG recording", "Loaded EEG-dataset." ) );
addConnector( m_input );
addConnector( m_eeg_input );
m_dipoles = boost::shared_ptr< WModuleInputData< WDataSetDipoles > >( new WModuleInputData< WDataSetDipoles >(
m_dipoles_input = boost::shared_ptr< WModuleInputData< WDataSetDipoles > >( new WModuleInputData< WDataSetDipoles >(
shared_from_this(), "Dipoles", "The reconstructed dipoles for the EEG." ) );
addConnector( m_dipoles );
addConnector( m_dipoles_input );
// call WModules initialization
WModule::connectors();
......@@ -124,6 +125,10 @@ void WMEEGView::properties()
"Use proof of concept (POC) ROI positioning instead of real dipoles position.",
false,
m_propCondition );
m_snapToDipole = m_properties->addProperty( "Snap to dipole",
"If a time is selected, snap to the nearest time position with an active dipole.",
true,
m_propCondition );
m_butterfly = m_properties->addProperty( "Butterfly plot",
"Overlay all curves in one row.",
false,
......@@ -231,9 +236,13 @@ void WMEEGView::moduleMain()
{
debugLog() << "Data changed";
m_dataChanged.set( false );
if( m_input.get() )
if( m_eeg_input.get() )
{
m_eeg = m_eeg_input->getData();
}
if( m_dipoles_input.get() )
{
m_eeg = m_input->getData();
m_dipoles = m_dipoles_input->getData();
}
redraw();
}
......@@ -318,17 +327,16 @@ void WMEEGView::moduleMain()
position + WVector3d( halfWidth, halfWidth, halfWidth ) ) );
WKernel::getRunningKernel()->getRoiManager()->addRoi( m_rois.back() );
}
else if( m_dipoles->getData() )
else if( m_dipoles.get() )
{
boost::shared_ptr< WDataSetDipoles > dipoles = m_dipoles->getData();
debugLog() << "Number of Dipoles: " << dipoles->getNumberOfDipoles();
for( size_t dipoleId = 0; dipoleId < dipoles->getNumberOfDipoles(); ++dipoleId )
debugLog() << "Number of Dipoles: " << m_dipoles->getNumberOfDipoles();
for( size_t dipoleId = 0u; dipoleId < m_dipoles->getNumberOfDipoles(); ++dipoleId )
{
debugLog() << "Dipole[" << dipoleId << "]: " << dipoles->getMagnitude( event->getTime(), dipoleId );
if( dipoles->getMagnitude( event->getTime(), dipoleId ) != 0 )
debugLog() << "Dipole[" << dipoleId << "]: " << m_dipoles->getMagnitude( event->getTime(), dipoleId );
if( m_dipoles->getMagnitude( event->getTime(), dipoleId ) != 0 )
{
float halfWidth = m_ROIsize->get( true ) * 0.5;
WPosition position = dipoles->getPosition( dipoleId );
WPosition position = m_dipoles->getPosition( dipoleId );
m_rois.push_back( new WROIBox( position - WVector3d( halfWidth, halfWidth, halfWidth ),
position + WVector3d( halfWidth, halfWidth, halfWidth ) ) );
WKernel::getRunningKernel()->getRoiManager()->addRoi( m_rois.back() );
......@@ -585,6 +593,59 @@ void WMEEGView::redraw()
WGEGroupNode* eventParentNode = new WGEGroupNode;
panTransform->addChild( eventParentNode );
// create geode to draw the area were the dipole is active
if( m_dipoles.get() )
{
for( size_t dipoleId = 0u; dipoleId < m_dipoles->getNumberOfDipoles(); ++dipoleId )
{
debugLog() << "Dipole[" << dipoleId << "]: " << m_dipoles->getStartTime( dipoleId )
<< " to " << m_dipoles->getEndTime( dipoleId );
const osg::Vec4 color_min( 1.0f, 1.0f, 1.0f, 0.0f );
const osg::Vec4 color_max( 1.0f, 0.0f, 0.0f, 1.0f );
const std::vector<float> times = m_dipoles->getTimes( dipoleId );
const std::vector<float> magnitudes = m_dipoles->getMagnitudes( dipoleId );
osg::Geometry* geometry = new osg::Geometry;
osg::Vec3Array* vertices = new osg::Vec3Array;
vertices->reserve( 2u * times.size() );
for( size_t id = 0u; id < times.size(); ++id )
{
vertices->push_back( osg::Vec3( times[id], -1048576.0f, -1.0f - id ) );
vertices->push_back( osg::Vec3( times[id], 1024.0f, -1.0f - id ) );
}
geometry->setVertexArray( vertices );
osg::Vec4Array* colors = new osg::Vec4Array;
colors->reserve( 2u * magnitudes.size() );
for( size_t id = 0u; id < magnitudes.size(); ++id )
{
const float scale = magnitudes[id] / m_dipoles->getMaxMagnitude();
const osg::Vec4 color = color_min * ( 1.0f - scale ) + color_max * scale;
colors->push_back( color );
colors->push_back( color );
}
geometry->setColorArray( colors );
geometry->setColorBinding( osg::Geometry::BIND_PER_VERTEX );
geometry->addPrimitiveSet( new osg::DrawArrays( osg::PrimitiveSet::QUAD_STRIP, 0, 2u * times.size() ) );
osg::StateSet* state = geometry->getOrCreateStateSet();
state->setMode( GL_BLEND, osg::StateAttribute::ON );
state->setMode( GL_DEPTH_TEST, osg::StateAttribute::ON );
state->setRenderingHint( osg::StateSet::TRANSPARENT_BIN );
state->setAttributeAndModes( new osg::BlendFunc( osg::BlendFunc::SRC_ALPHA,
osg::BlendFunc::ONE_MINUS_SRC_ALPHA ) );
osg::Geode* geode = new osg::Geode;
geode->addDrawable( geometry );
panTransform->addChild( geode );
}
}
// add labels and graph to the root node
m_rootNode2d->addChild( labelsTransform );
m_rootNode2d->addChild( panTransform );
......@@ -601,7 +662,10 @@ void WMEEGView::redraw()
m_event,
eventParentNode,
m_eeg,
0 );
0,
m_snapToDipole,
m_proofOfConcept,
m_dipoles );
// draw the electrode positions in 3D
if( m_drawElectrodes->get( true ) )
......
......@@ -125,12 +125,12 @@ private:
/**
* Input connector for a EEG dataset
*/
boost::shared_ptr< WModuleInputData< WEEG2 > > m_input;
boost::shared_ptr< WModuleInputData< WEEG2 > > m_eeg_input;
/**
* Input connector for dipoles of EEG data
*/
boost::shared_ptr< WModuleInputData< WDataSetDipoles > > m_dipoles;
boost::shared_ptr< WModuleInputData< WDataSetDipoles > > m_dipoles_input;
/**
* A condition used to notify about changes in several properties.
......@@ -167,6 +167,11 @@ private:
*/
WPropBool m_proofOfConcept;
/**
* Property determining whether the selected time position should be snapped to an active dipole
*/
WPropBool m_snapToDipole;
/**