diff --git a/.gitignore b/.gitignore index 28c3bf0dbdb0fb025f6aaaeaf2428651691653b5..2f5c12e033920f950017f13929ca8e16bbf1d9e9 100644 --- a/.gitignore +++ b/.gitignore @@ -24,5 +24,6 @@ httpxx* neurolots* nsol* vmmlib* +SimIL* nlrender/Shaders.h diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d7b3c5fcd72dcfd1bd4ecf008668fd90a73c993a..5b5042850394f12cdd0e666a4b9d85563d598d97 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -10,7 +10,7 @@ Ubuntu-OptionalDeps-master: - export DOCKER_REPO="gitlabci@vg-lab.es:apps" - export CMAKE_EXTRA_TARGETS="install" - export BUILD_OPTIONALS_SUBPROJECTS=1 - - export CMAKE_EXTRA_ARGS="-DCLONE_SUBPROJECTS=ON -DNEUROTESSMESH_OPTIONALS_AS_REQUIRED=ON" + - export CMAKE_EXTRA_ARGS="-DCLONE_SUBPROJECTS=ON -DNEUROTESSMESH_OPTIONALS_AS_REQUIRED=ON -DSIMIL_BRION_ENABLED=ON" - export BUILD_GENERATOR="Ninja" - bash .gitlab-ci.sh only: @@ -24,7 +24,7 @@ Ubuntu-OptionalDeps: - module load Qt-5.15.2 - export CMAKE_EXTRA_TARGETS="install" - export BUILD_OPTIONALS_SUBPROJECTS=1 - - export CMAKE_EXTRA_ARGS="-DCLONE_SUBPROJECTS=ON -DNEUROTESSMESH_OPTIONALS_AS_REQUIRED=ON" + - export CMAKE_EXTRA_ARGS="-DCLONE_SUBPROJECTS=ON -DNEUROTESSMESH_OPTIONALS_AS_REQUIRED=ON -DSIMIL_BRION_ENABLED=ON" - export BUILD_GENERATOR="Ninja" - bash .gitlab-ci.sh except: diff --git a/.gitsubprojects b/.gitsubprojects index bed78cb7920717f337edb4af5e286ba99779cfb7..d2c6037390be2097ffc268e75b8f8236daf9ab93 100644 --- a/.gitsubprojects +++ b/.gitsubprojects @@ -1,8 +1,9 @@ #-*- mode: cmake -*- -# git_subproject(Brion https://github.com/BlueBrain/Brion.git f74dcc5) -# git_subproject(ZeroEQ https://github.com/HBPVis/ZeroEQ.git 1e66ee3) -# git_subproject(Lexis https://github.com/HBPVis/Lexis.git 617eedb) -# git_subproject(gmrvlex git@gitlab.gmrv.es:nsviz/gmrvlex.git c20b194) -git_subproject(neurolots https://github.com/gmrvvis/neurolots.git fcab672a) +#git_subproject(Brion https://github.com/BlueBrain/Brion.git f74dcc5) +#git_subproject(ZeroEQ https://github.com/HBPVis/ZeroEQ.git 1e66ee3) +#git_subproject(Lexis https://github.com/HBPVis/Lexis.git 617eedb) +#git_subproject(gmrvlex git@gitlab.gmrv.es:nsviz/gmrvlex.git c20b194) +git_subproject(neurolots https://github.com/gmrvvis/neurolots.git f8ea6d1e) git_subproject(ReTo https://github.com/gmrvvis/ReTo.git 211c49a) git_subproject(acuterecorder https://github.com/vg-lab/AcuteRecorder.git 0b84c2b1) +#git_subproject( SimIL https://github.com/gmrvvis/SimIL.git 1921f818 ) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0c4887d96364296adce936972ee76b40192a9889..6869902bec97188a281e30291ed1a68e531a89f4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # cmake_minimum_required(VERSION 3.1 FATAL_ERROR) -project(NeuroTessMesh VERSION 0.3.2) +project(NeuroTessMesh VERSION 0.4.0) set(NeuroTessMesh_VERSION_ABI 1) # Disable in source building @@ -55,10 +55,17 @@ common_find_package(ZeroEQ ${NEUROTESSMESH_OPTS_FIND_ARGS}) common_find_package(gmrvlex ${NEUROTESSMESH_OPTS_FIND_ARGS}) common_find_package(GLUT SYSTEM) common_find_package(Boost COMPONENTS system filesystem SYSTEM) -common_find_package( acuterecorder REQUIRED ) +common_find_package(acuterecorder REQUIRED ) list(APPEND NEUROTESSMESH_DEPENDENT_LIBRARIES Qt5Core Qt5Widget Qt5OpenGL GLEW neurolots acuterecorder) +if(NEUROTESSMESH_OPTIONALS_AS_REQUIRED) + common_find_package(Brion REQUIRED) + common_find_package(SimIL REQUIRED) + add_compile_definitions(SIMIL_USE_BRION) + list( APPEND NEUROTESSMESH_DEPENDENT_LIBRARIES SimIL QSimIL Brion Brain) +endif() + if ( ZEROEQ_FOUND ) common_find_package(Threads REQUIRED) list( APPEND NEUROTESSMESH_DEPENDENT_LIBRARIES ZeroEQ ) @@ -78,3 +85,4 @@ if (GLUT_FOUND AND BOOST_FOUND) endif( ) include(CPackConfig) include(DoxygenRule) + diff --git a/neurotessmesh/CMakeLists.txt b/neurotessmesh/CMakeLists.txt index 9fbfdb0435b01561876d00d182b0f46fac7ead53..289f1a5a5d44e5ad7157238a8f57e76bdf04de9c 100644 --- a/neurotessmesh/CMakeLists.txt +++ b/neurotessmesh/CMakeLists.txt @@ -22,11 +22,13 @@ set( NEUROTESSMESH_SOURCES ColorSelectionWidget.cpp CMakeSetup.rc Scene.cpp + LoaderThread.cpp ) set( NEUROTESSMESH_HEADERS ${PROJECT_BINARY_DIR}/include/neurotessmesh/version.h Scene.h + LoaderThread.h ) set(NEUROTESSMESH_MOC_HEADERS @@ -48,7 +50,10 @@ set( NEUROTESSMESH_LINK_LIBRARIES nlrender acuterecorder ) - + +if (NEUROTESSMESH_OPTIONALS_AS_REQUIRED) + list( APPEND NEUROTESSMESH_LINK_LIBRARIES SimIL QSimIL Brion Brain) +endif() if ( ZEROEQ_FOUND ) list( APPEND NEUROTESSMESH_LINK_LIBRARIES ZeroEQ ) diff --git a/neurotessmesh/LoaderThread.cpp b/neurotessmesh/LoaderThread.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c65474680fa31675a97703daab7ad786839fd0e9 --- /dev/null +++ b/neurotessmesh/LoaderThread.cpp @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2022 VG-Lab/URJC. + * + * Authors: Felix de las Pozas Alvarez <felix.delaspozas@urjc.es> + * + * This file is part of SimIL <https://github.com/vg-lab/SimIL> + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License version 3.0 as published + * by the Free Software Foundation. + * + * This library 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 this library; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include "LoaderThread.h" + +// deps +#include <nsol/nsol.h> +#ifdef NEUROTESSMESH_USE_SIMIL + #include <simil/simil.h> +#endif + +// Qt +#include <QString> +#include <QFileInfo> +#include <QVBoxLayout> +#include <QProgressBar> +#include <QIcon> + +using namespace neurotessmesh; + +LoaderThread::LoaderThread(const std::string &arg1, const std::string &arg2, + const LoaderThread::DataFileType type) +: QThread() +, m_fileName{arg1} +, m_target{arg2} +, m_type{type} +, m_dataset{nullptr} +, m_player{nullptr} +{ +} + +void LoaderThread::run() +{ + try + { + m_dataset = new nsol::DataSet(); + QFileInfo fi(QString::fromStdString(m_fileName)); + emit progress(QString("Loading %1").arg(fi.fileName()), 10); + + switch(m_type) + { + case DataFileType::BlueConfig: +#ifdef NSOL_USE_BRION + + emit progress(tr("Loading Hierarchy"), 25); + + m_dataset->loadBlueConfigHierarchy< nsol::Node, + nsol::NeuronMorphologySection, + nsol::Dendrite, + nsol::Axon, + nsol::Soma, + nsol::NeuronMorphology, + nsol::Neuron, + nsol::MiniColumn, + nsol::Column >( m_fileName, m_target ); + + emit progress(tr("Loading Morphologies"), 50); + + m_dataset->loadAllMorphologies< nsol::Node, + nsol::NeuronMorphologySection, + nsol::Dendrite, + nsol::Axon, + nsol::Soma, + nsol::NeuronMorphology, + nsol::Neuron, + nsol::MiniColumn, + nsol::Column >( ); + + emit progress(tr("Loading Spikes"), 75); +#ifdef NEUROTESSMESH_USE_SIMIL + { // load the spikes data with SimIL, only for blueconfig. + auto spikesData = new simil::SpikeData(m_fileName, simil::TDataType::TBlueConfig, m_target); + spikesData->reduceDataToGIDS(); + + m_player = new simil::SpikesPlayer( ); + m_player->LoadData( spikesData ); + } +#endif +#else + std::cerr << "Error: Brion support not built-in" << std::endl; +#endif + break; + + case DataFileType::SWC: + emit progress(tr("Loading Neuron"), 50); + m_dataset->loadNeuronFromFile< nsol::Node, + nsol::NeuronMorphologySection, + nsol::Dendrite, + nsol::Axon, + nsol::Soma, + nsol::NeuronMorphology, + nsol::Neuron >( m_fileName, 1 ); + break; + + case DataFileType::NsolScene: + emit progress(tr("Loading Scene"), 50); + m_dataset->loadXmlScene< nsol::Node, + nsol::NeuronMorphologySection, + nsol::Dendrite, + nsol::Axon, + nsol::Soma, + nsol::NeuronMorphology, + nsol::Neuron >( m_fileName ); + break; + + default: + throw std::runtime_error( "Data file type not supported" ); + } + + emit progress("Generating Meshes", 100); + + } + catch(const std::exception &e) + { + if(m_dataset) delete m_dataset; + m_errors = QString::fromStdString(e.what()); + } +} + +LoadingDialog::LoadingDialog( QWidget* p ) + : QDialog( p , Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint ) +{ + setWindowIcon(QIcon( ":/icons/rsc/neurotessmesh.png" )); + + auto layout = new QVBoxLayout( ); + m_progress = new QProgressBar( this ); + m_progress->setMinimumWidth( 590 ); + m_progress->setValue(0); + m_progress->setFormat(""); + layout->addWidget( m_progress , 1 , Qt::AlignHCenter | Qt::AlignVCenter ); + layout->setMargin( 4 ); + setLayout( layout ); + + setSizePolicy( QSizePolicy::MinimumExpanding , + QSizePolicy::MinimumExpanding ); + setFixedSize( 600 , sizeHint( ).height( )); +} + +void LoadingDialog::progress( const QString& message , const unsigned int value ) +{ + m_progress->setValue( value ); + + if ( !message.isEmpty( )) + m_progress->setFormat( tr( "%1 - %p%" ).arg( message )); + else + m_progress->setFormat("%p%"); +} + +void LoadingDialog::closeDialog( ) +{ + close( ); + deleteLater( ); +} diff --git a/neurotessmesh/LoaderThread.h b/neurotessmesh/LoaderThread.h new file mode 100644 index 0000000000000000000000000000000000000000..153772e8032ffa929aa9fc1ba9c4760468697378 --- /dev/null +++ b/neurotessmesh/LoaderThread.h @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2022 VG-Lab/URJC. + * + * Authors: Felix de las Pozas Alvarez <felix.delaspozas@urjc.es> + * + * This file is part of SimIL <https://github.com/vg-lab/SimIL> + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License version 3.0 as published + * by the Free Software Foundation. + * + * This library 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 this library; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#ifndef NEUROTESSMESH_LOADERTHREAD_H_ +#define NEUROTESSMESH_LOADERTHREAD_H_ + +// Qt +#include <QThread> +#include <QDialog> + +class QString; +class QProgressBar; + +namespace nsol +{ + class DataSet; +} + +namespace simil +{ + class SpikesPlayer; +} + +namespace neurotessmesh +{ + /** \class LoaderThread + * \brief Loads the dataset data in a thread. + * + */ + class LoaderThread + : public QThread + { + Q_OBJECT + public: + enum class DataFileType + { BlueConfig, SWC, NsolScene }; + + /** \brief LoaderThread class constructor. + * \param[in] arg1 Dataset filename. + * \param[in] arg1 Blueconfig target. + * \param[in] type Dataset type. + * + */ + explicit LoaderThread(const std::string &arg1, const std::string &arg2, + const DataFileType type); + + /** \brief LoaderThread class virtual destructor. + * + */ + virtual ~LoaderThread() + {}; + + /** \brief Returns the dataset. + * + */ + nsol::DataSet *getDataset() const + { return m_dataset; } + + /** \brief Returns the spikes player. + * + */ + simil::SpikesPlayer *getPlayer() const + { return m_player; } + + virtual void run(); + + /** \brief Returns the error description or empty if none. + * + */ + QString errors() const + { return m_errors; } + + signals: + void progress(const QString &text, const unsigned int value); + + private: + const std::string m_fileName; /** name of file to load. */ + const std::string m_target; /** blueconfig target. */ + const DataFileType m_type; /** type of file to load. */ + + nsol::DataSet *m_dataset; /** nsol dataset with data. */ + simil::SpikesPlayer *m_player; /** spikes data or null if none. */ + + QString m_errors; + }; + + class LoadingDialog + : public QDialog + { + Q_OBJECT + public: + /** \brief LoadingDialog class constructor. + * \param[in] p Raw pointer of the widget parent of this one. + * \param[in] f QDialog flags. + * + */ + explicit LoadingDialog( QWidget* p = nullptr ); + + /** \brief LoadingDialog class virtual destructor. + * + */ + virtual ~LoadingDialog( ) + { }; + + public slots: + + /** \brief Updates the dialog with the message and progress value + * \param[in] message Progress message. + * \param[in] value Progress value in [0,100]. + * + */ + void progress( const QString& message , const unsigned int value ); + + /** \brief Closes and deletes the dialog. + * + */ + void closeDialog( ); + + private: + QProgressBar* m_progress; /** progress bar. */ + }; + +} +#endif /* NEUROTESSMESH_LOADERTHREAD_H_ */ diff --git a/neurotessmesh/MainWindow.cpp b/neurotessmesh/MainWindow.cpp index 1d9461e99dc85c9144947494ad2e32162c52f669..06c0402cef85ac8ffdc1b0de1d394a62e8107eaa 100644 --- a/neurotessmesh/MainWindow.cpp +++ b/neurotessmesh/MainWindow.cpp @@ -10,8 +10,11 @@ #include "MainWindow.h" +#include "LoaderThread.h" #include <neurotessmesh/version.h> #include <nsol/nsol.h> +#include <neurotessmesh/Scene.h> + #ifdef NEUROLOTS_USE_GMRVZEQ #include <gmrvzeq/version.h> #endif @@ -20,6 +23,10 @@ #endif #include <acuterecorder/acuterecorder.h> +#ifdef NEUROTESSMESH_USE_SIMIL + #include <qsimil/qsimil.h> + #include <simil/simil.h> +#endif #include <QFileDialog> #include <QInputDialog> @@ -33,21 +40,24 @@ constexpr const char* POSITION_KEY = "positionData"; -MainWindow::MainWindow( QWidget* parent_, bool updateOnIdle_ ) +MainWindow::MainWindow( QWidget* parent_ , bool updateOnIdle_ ) : QMainWindow( parent_ ) , _lastOpenedFileName( "" ) , _ui( new Ui::MainWindow ) , _openGLWidget( nullptr ) + , _scene( nullptr ) , _recorder( nullptr ) + , m_dataLoader{ nullptr } { _ui->setupUi( this ); - auto recorderAction = RecorderUtils::recorderAction(); - _ui->menuTools->insertAction(_ui->menuTools->actions().first(), recorderAction); - _ui->toolBar->addAction(recorderAction); + auto recorderAction = RecorderUtils::recorderAction( ); + _ui->menuTools->insertAction( _ui->menuTools->actions( ).first( ) , + recorderAction ); + _ui->toolBar->addAction( recorderAction ); - connect(recorderAction, SIGNAL(triggered(bool)), - this, SLOT(openRecorder())); + connect( recorderAction , SIGNAL( triggered( bool )) , + this , SLOT( openRecorder( ))); _ui->actionUpdateOnIdle->setChecked( updateOnIdle_ ); _ui->actionShowFPSOnIdleUpdate->setChecked( false ); @@ -55,8 +65,7 @@ MainWindow::MainWindow( QWidget* parent_, bool updateOnIdle_ ) #ifdef NSOL_USE_BRION _ui->actionOpenBlueConfig->setEnabled( true ); #else - //_ui->actionOpenBlueConfig->setEnabled( false ); - _ui->actionOpenBlueConfig->setVisible(false); + _ui->actionOpenBlueConfig->setEnabled( false ); #endif #ifdef NSOL_USE_QT5CORE @@ -65,34 +74,35 @@ MainWindow::MainWindow( QWidget* parent_, bool updateOnIdle_ ) _ui->actionOpenXMLScene->setEnabled( false ); #endif - connect( _ui->actionQuit, SIGNAL( triggered( )), - QApplication::instance(), SLOT( quit( ))); + connect( _ui->actionQuit , SIGNAL( triggered( )) , + QApplication::instance( ) , SLOT( quit( ))); - connect( _ui->actionAbout, SIGNAL(triggered( )), - this, SLOT( showAbout( ))); + connect( _ui->actionAbout , SIGNAL( triggered( )) , + this , SLOT( showAbout( ))); + + _openGLWidget = new OpenGLWidget( ); + this->setCentralWidget( _openGLWidget ); + _openGLWidget->setMinimumSize( QSize( 100 , 100 )); _initExtractionDock( ); _initConfigurationDock( ); _initRenderOptionsDock( ); + _initPlayerDock( ); - _openGLWidget = new OpenGLWidget(); - this->setCentralWidget( _openGLWidget ); - _openGLWidget->setMinimumSize( QSize( 100, 100 )); - - if( _openGLWidget->format( ).version( ).first < 4 ) + if ( _openGLWidget->format( ).version( ).first < 4 ) { std::cerr << "This application requires at least OpenGL 4.0" << std::endl; exit( -1 ); } - auto positionsMenu = new QMenu(); - positionsMenu->setTitle("Camera positions"); - _ui->actionCamera_Positions->setMenu(positionsMenu); + auto positionsMenu = new QMenu( ); + positionsMenu->setTitle( "Camera positions" ); + _ui->actionCamera_Positions->setMenu( positionsMenu ); } -MainWindow::~MainWindow( void ) +MainWindow::~MainWindow( ) { - delete _ui; + delete _ui; } void MainWindow::init( const std::string& zeqSession_ ) @@ -103,135 +113,126 @@ void MainWindow::init( const std::string& zeqSession_ ) if ( !zeqSession_.empty( )) _openGLWidget->setZeqSession( zeqSession_ ); - connect( _ui->actionHome, SIGNAL( triggered( )), - this, SLOT( home( ))); + connect( _ui->actionHome , SIGNAL( triggered( )) , + this , SLOT( home( ))); - connect( _ui->actionUpdateOnIdle, SIGNAL( triggered( )), - _openGLWidget, SLOT( toggleUpdateOnIdle( ))); + connect( _ui->actionUpdateOnIdle , SIGNAL( triggered( )) , + _openGLWidget , SLOT( toggleUpdateOnIdle( ))); - connect( _ui->actionShowFPSOnIdleUpdate, SIGNAL( triggered( )), - _openGLWidget, SLOT( toggleShowFPS( ))); + connect( _ui->actionShowFPSOnIdleUpdate , SIGNAL( triggered( )) , + _openGLWidget , SLOT( toggleShowFPS( ))); - connect( _ui->actionWireframe, SIGNAL( triggered( )), - _openGLWidget, SLOT( toggleWireframe( ))); + connect( _ui->actionWireframe , SIGNAL( triggered( )) , + _openGLWidget , SLOT( toggleWireframe( ))); - connect( _ui->actionOpenBlueConfig, SIGNAL( triggered( )), - this, SLOT( openBlueConfigThroughDialog( ))); + connect( _ui->actionOpenBlueConfig , SIGNAL( triggered( )) , + this , SLOT( openBlueConfigThroughDialog( ))); - connect( _ui->actionOpenXMLScene, SIGNAL( triggered( )), - this, SLOT( openXMLSceneThroughDialog( ))); + connect( _ui->actionOpenXMLScene , SIGNAL( triggered( )) , + this , SLOT( openXMLSceneThroughDialog( ))); - connect( _ui->actionOpenSWCFile, SIGNAL( triggered( )), - this, SLOT( openSWCFileThroughDialog( ))); + connect( _ui->actionOpenSWCFile , SIGNAL( triggered( )) , + this , SLOT( openSWCFileThroughDialog( ))); - connect( _radiusSlider, SIGNAL( valueChanged( int )), - this, SLOT( onActionGenerate( int ))); + connect( _radiusSlider , SIGNAL( valueChanged( int )) , + this , SLOT( onActionGenerate( int ))); - connect( _extractButton, SIGNAL( clicked( )), - _openGLWidget, SLOT( extractEditNeuronMesh( ))); + connect( _extractButton , SIGNAL( clicked( )) , + _openGLWidget , SLOT( extractEditNeuronMesh( ))); - connect( _lotSlider, SIGNAL( valueChanged( int )), - _openGLWidget, SLOT( onLotValueChanged( int ))); + connect( _lotSlider , SIGNAL( valueChanged( int )) , + _openGLWidget , SLOT( onLotValueChanged( int ))); _lotSlider->valueChanged( _lotSlider->value( )); - connect( _distanceSlider, SIGNAL( valueChanged( int )), - _openGLWidget, SLOT( onDistanceValueChanged( int ))); + connect( _distanceSlider , SIGNAL( valueChanged( int )) , + _openGLWidget , SLOT( onDistanceValueChanged( int ))); _distanceSlider->valueChanged( _distanceSlider->value( )); - connect( _radioHomogeneous, SIGNAL( clicked( )), - _openGLWidget, SLOT( onHomogeneousClicked( ))); + connect( _radioHomogeneous , SIGNAL( clicked( )) , + _openGLWidget , SLOT( onHomogeneousClicked( ))); - connect( _radioLinear, SIGNAL( clicked( )), - _openGLWidget, SLOT( onLinearClicked( ))); + connect( _radioLinear , SIGNAL( clicked( )) , + _openGLWidget , SLOT( onLinearClicked( ))); _radioLinear->clicked( ); - connect( _backGroundColor, SIGNAL( colorChanged( QColor )), - _openGLWidget, SLOT( changeClearColor( QColor ))); - _backGroundColor->color( QColor( 255, 255, 255 )); + connect( _neuronRender , SIGNAL( currentIndexChanged( int )) , + _openGLWidget , SLOT( changeNeuronPiece( int ))); + _neuronRender->currentIndexChanged( 1 ); - connect( _neuronColor, SIGNAL( colorChanged( QColor )), - _openGLWidget, SLOT( changeNeuronColor( QColor ))); - _neuronColor->color( QColor( 0, 120, 250 )); + connect( _selectedNeuronRender , SIGNAL( currentIndexChanged( int )) , + _openGLWidget , SLOT( changeSelectedNeuronPiece( int ))); + _selectedNeuronRender->currentIndexChanged( 0 ); - connect( _selectedNeuronColor, SIGNAL( colorChanged( QColor )), - _openGLWidget, SLOT( changeSelectedNeuronColor( QColor ))); - _selectedNeuronColor->color( QColor( 250, 120, 0 )); + connect( _ui->actionLoad_camera_positions , SIGNAL( triggered( bool )) , + this , + SLOT( loadCameraPositions( ))); - connect( _neuronRender, SIGNAL( currentIndexChanged( int )), - _openGLWidget, SLOT( changeNeuronPiece( int ))); - _neuronRender->currentIndexChanged( 1 ); + connect( _ui->actionSave_camera_positions , SIGNAL( triggered( bool )) , + this , + SLOT( saveCameraPositions( ))); - connect( _selectedNeuronRender, SIGNAL( currentIndexChanged( int )), - _openGLWidget, SLOT( changeSelectedNeuronPiece( int ))); - _selectedNeuronRender->currentIndexChanged( 0 ); + connect( _ui->actionAdd_camera_position , SIGNAL( triggered( bool )) , this , + SLOT( addCameraPosition( ))); - connect(_ui->actionLoad_camera_positions, SIGNAL(triggered(bool)), this, - SLOT(loadCameraPositions())); + connect( _ui->actionRemove_camera_position , SIGNAL( triggered( bool )) , + this , + SLOT( removeCameraPosition( ))); - connect(_ui->actionSave_camera_positions, SIGNAL(triggered(bool)), this, - SLOT(saveCameraPositions())); + connect( _backGroundColor , SIGNAL( colorChanged( QColor )) , + _openGLWidget , SLOT( changeClearColor( QColor ))); - connect(_ui->actionAdd_camera_position, SIGNAL(triggered(bool)), this, - SLOT(addCameraPosition())); + connect( _neuronColor , SIGNAL( colorChanged( QColor )) , + _openGLWidget , SLOT( changeNeuronColor( QColor ))); - connect(_ui->actionRemove_camera_position, SIGNAL(triggered(bool)), this, - SLOT(removeCameraPosition())); + connect( _selectedNeuronColor , SIGNAL( colorChanged( QColor )) , + _openGLWidget , SLOT( changeSelectedNeuronColor( QColor ))); } -void MainWindow::showStatusBarMessage ( const QString& message ) +void MainWindow::showStatusBarMessage( const QString& message ) { _ui->statusbar->showMessage( message ); } -void MainWindow::openBlueConfig( const std::string& fileName, +void MainWindow::openBlueConfig( const std::string& fileName , const std::string& targetLabel ) { - _openGLWidget->loadData( fileName, - neurotessmesh::Scene::TDataFileType::BlueConfig, - targetLabel ); - updateNeuronList( ); - _openGLWidget->changeNeuronPiece(_neuronRender->currentIndex()); - _openGLWidget->changeSelectedNeuronPiece(_selectedNeuronRender->currentIndex()); + loadData( fileName , targetLabel , + neurotessmesh::LoaderThread::DataFileType::BlueConfig ); } void MainWindow::openXMLScene( const std::string& fileName ) { - _openGLWidget->loadData( fileName, - neurotessmesh::Scene::TDataFileType::NsolScene ); - updateNeuronList( ); - _openGLWidget->changeNeuronPiece(_neuronRender->currentIndex()); - _openGLWidget->changeSelectedNeuronPiece(_selectedNeuronRender->currentIndex()); + loadData( fileName , std::string( ) , + neurotessmesh::LoaderThread::DataFileType::NsolScene ); } void MainWindow::openSWCFile( const std::string& fileName ) { - _openGLWidget->loadData( fileName, - neurotessmesh::Scene::TDataFileType::SWC ); - updateNeuronList( ); - _openGLWidget->changeNeuronPiece(_neuronRender->currentIndex()); - _openGLWidget->changeSelectedNeuronPiece(_selectedNeuronRender->currentIndex()); + loadData( fileName , std::string( ) , + neurotessmesh::LoaderThread::DataFileType::SWC ); } -void MainWindow::updateNeuronList( void ) +void MainWindow::updateNeuronList( ) { _neuronList->clear( ); - const std::vector< unsigned int >& ids = _openGLWidget->neuronIdList( ); + const std::vector< unsigned int >& ids = _scene->neuronIndices( ); - for( const auto& id: ids ) + for ( const auto& id: ids ) { _neuronList->addItem( QString::number( id )); } } -void MainWindow::home( void ) +void MainWindow::home( ) { + _scene->home( ); _openGLWidget->home( ); _generateNeuritesLayout( ); _extractButton->setEnabled( false ); _somaGroup->hide( ); } -void MainWindow::openBlueConfigThroughDialog( void ) +void MainWindow::openBlueConfigThroughDialog( ) { #ifdef NSOL_USE_BRION @@ -258,12 +259,12 @@ void MainWindow::openBlueConfigThroughDialog( void ) #endif } -void MainWindow::openXMLSceneThroughDialog( void ) +void MainWindow::openXMLSceneThroughDialog( ) { #ifdef NSOL_USE_QT5CORE QString path = QFileDialog::getOpenFileName( - this, tr( "Open XML Scene" ), _lastOpenedFileName, - tr( "XML ( *.xml);; All files (*)" ), nullptr, + this , tr( "Open XML Scene" ) , _lastOpenedFileName , + tr( "XML ( *.xml);; All files (*)" ) , nullptr , QFileDialog::DontUseNativeDialog ); if ( path != QString( "" )) @@ -275,11 +276,11 @@ void MainWindow::openXMLSceneThroughDialog( void ) } -void MainWindow::openSWCFileThroughDialog( void ) +void MainWindow::openSWCFileThroughDialog( ) { QString path = QFileDialog::getOpenFileName( - this, tr( "Open Swc File" ), _lastOpenedFileName, - tr( "swc ( *.swc);; All files (*)" ), nullptr, + this , tr( "Open Swc File" ) , _lastOpenedFileName , + tr( "swc ( *.swc);; All files (*)" ) , nullptr , QFileDialog::DontUseNativeDialog ); if ( path != QString( "" )) @@ -289,11 +290,11 @@ void MainWindow::openSWCFileThroughDialog( void ) } } -void MainWindow::showAbout( void ) +void MainWindow::showAbout( ) { QMessageBox::about( - this, tr( "About " ) + tr( "NeuroTessMesh" ), + this , tr( "About " ) + tr( "NeuroTessMesh" ) , tr( "<p><BIG><b>" ) + tr( "NeuroTessMesh" ) + tr( "</b></BIG><br><br>" ) + tr( "version " ) + tr( neurotessmesh::Version::getString( ).c_str( )) + @@ -301,73 +302,73 @@ void MainWindow::showAbout( void ) tr( std::to_string( neurotessmesh::Version::getRevision( )).c_str( )) + tr( ")" ) + tr( "<br><br>Using: " ) + - tr( "<ul>") + + tr( "<ul>" ) + tr( "<li>nsol " ) + tr( nsol::Version::getString( ).c_str( )) + tr( " (" ) + tr( std::to_string( nsol::Version::getRevision( )).c_str( )) + tr( ")</li> " ) + -#ifdef NSOL_USE_BRION + #ifdef NSOL_USE_BRION tr( "<li>Brion " ) + tr( brion::Version::getString( ).c_str( )) + tr( " (" ) + tr( std::to_string( brion::Version::getRevision( )).c_str( )) + tr( ")" ) + tr ( "</li> " ) + -#endif -#ifdef NEUROLOTS_USE_ZEROEQ + #endif + #ifdef NEUROLOTS_USE_ZEROEQ tr( "<li>ZEQ " ) + tr( zeroeq::Version::getString( ).c_str( )) + tr( " (" ) + tr( std::to_string( zeroeq::Version::getRevision( )).c_str( )) + tr( ")" ) + tr ( "</li> " ) + -#endif -#ifdef NEUROLOTS_USE_GMRVLEX + #endif + #ifdef NEUROLOTS_USE_GMRVLEX tr( "<li>gmrvzeq " ) + tr( gmrvlex::Version::getString( ).c_str( )) + tr( " (" ) + tr( std::to_string( gmrvlex::Version::getRevision( )).c_str( )) + tr( ")" ) + tr ( "</li> " ) + -#endif -#ifdef NEUROLOTS_USE_DEFLECT + #endif + #ifdef NEUROLOTS_USE_DEFLECT tr( "<li>Deflect " ) + tr( deflect::Version::getString( ).c_str( )) + tr( " (" ) + tr( std::to_string( deflect::Version::getRevision( )).c_str( )) + tr( ")" ) + tr ( "</li> " ) + -#endif + #endif tr( "<li>AcuteRecorder " ) + - tr( acuterecorder::Version::getString().c_str( )) + + tr( acuterecorder::Version::getString( ).c_str( )) + tr( " (" ) + tr( std::to_string( acuterecorder::Version::getRevision( )).c_str( )) + tr( ")" ) + - tr ( "</li> " ) + + tr( "</li> " ) + - tr ( "</ul>" ) + + tr( "</ul>" ) + tr( "<br>VG-Lab - Universidad Rey Juan Carlos<br>" - "<a href=www.vg-lab.es>www.vg-lab.es</a><br>" - "<a href='mailto:dev@vg-lab.es'>dev@vg-lab.es</a><br><br>" - "<br>(C) 2015-2022. Universidad Rey Juan Carlos<br><br>" - "<img src=':/icons/rsc/logoVGLab.png' > " - "<img src=':/icons/rsc/logoURJC.png' ><br><br> " - "</p>" - "") - ); + "<a href=www.vg-lab.es>www.vg-lab.es</a><br>" + "<a href='mailto:dev@vg-lab.es'>dev@vg-lab.es</a><br><br>" + "<br>(C) 2015-2022. Universidad Rey Juan Carlos<br><br>" + "<img src=':/icons/rsc/logoVGLab.png' > " + "<img src=':/icons/rsc/logoURJC.png' ><br><br> " + "</p>" + "" ) + ); } -void MainWindow::openRecorder() +void MainWindow::openRecorder( ) { - auto action = qobject_cast<QAction *>(sender()); + auto action = qobject_cast< QAction* >( sender( )); // The button stops the recorder if found. - if( _recorder ) + if ( _recorder ) { - if(action) action->setDisabled( true ); + if ( action ) action->setDisabled( true ); - RecorderUtils::stopAndWait(_recorder, this); + RecorderUtils::stopAndWait( _recorder , this ); // Recorder will be deleted after finishing. _recorder = nullptr; @@ -379,7 +380,7 @@ void MainWindow::openRecorder() params.widgetsToRecord.emplace_back( "Main Widget" , this ); params.includeScreens = false; - if(!_ui->actionAdvancedRecorderOptions->isChecked()) + if ( !_ui->actionAdvancedRecorderOptions->isChecked( )) { params.showWorker = false; params.showWidgetSourceMode = false; @@ -389,23 +390,24 @@ void MainWindow::openRecorder() RecorderDialog dialog( nullptr , params , true ); dialog.setWindowIcon( QIcon( ":/icons/rsc/neurotessmesh.png" )); dialog.setFixedSize( 800 , 600 ); - if ( dialog.exec( ) == QDialog::Accepted) + if ( dialog.exec( ) == QDialog::Accepted ) { _recorder = dialog.getRecorder( ); connect( _recorder , SIGNAL( finished( )) , _recorder , SLOT( deleteLater( ))); connect( _recorder , SIGNAL( finished( )) , this , SLOT( finishRecording( ))); - if(action) action->setChecked( true ); - } else + if ( action ) action->setChecked( true ); + } + else { - if(action) action->setChecked( false ); + if ( action ) action->setChecked( false ); } } void MainWindow::updateExtractMeshDock( void ) { - if( _ui->actionEditSave->isChecked( )) + if ( _ui->actionEditSave->isChecked( )) _extractMeshDock->show( ); else _extractMeshDock->close( ); @@ -413,7 +415,7 @@ void MainWindow::updateExtractMeshDock( void ) void MainWindow::updateConfigurationDock( void ) { - if( _ui->actionConfiguration->isChecked( )) + if ( _ui->actionConfiguration->isChecked( )) _configurationDock->show( ); else _configurationDock->close( ); @@ -421,384 +423,434 @@ void MainWindow::updateConfigurationDock( void ) void MainWindow::updateRenderOptionsDock( void ) { - if( _ui->actionRenderOptions->isChecked( )) + if ( _ui->actionRenderOptions->isChecked( )) _renderOptionsDock->show( ); else _renderOptionsDock->close( ); } +void MainWindow::updatePlayerOptionsDock( ) +{ +#ifdef NEUROTESSMESH_USE_SIMIL + auto playerWidget = qobject_cast< qsimil::QSimControlWidget* >( + _playerDock->widget( )); + bool enabled = false; + if ( playerWidget ) + { + auto player = dynamic_cast<simil::SpikesPlayer*>(playerWidget->getSimulationPlayer( )); + enabled = player && !player->spikes( ).empty( ); + } + + _playerDock->setEnabled( enabled ); + if ( _ui->actionSimulation_player_options->isChecked( )) + _playerDock->show( ); + else + _playerDock->hide( ); +#else + _ui->actionSimulation_player_options->setEnabled(false); + _playerDock->setVisible(false); +#endif +} void MainWindow::onListClicked( QListWidgetItem* item ) { int id = item->text( ).toInt( ); - _openGLWidget->neuronToEdit( id ); + _scene->setNeuronToEdit( id ); + _openGLWidget->update( ); _generateNeuritesLayout( ); _extractButton->setEnabled( true ); _somaGroup->show( ); - } void MainWindow::onActionGenerate( int /*value_*/ ) { - float alphaRadius = ( float )_radiusSlider->value( ) / 100.0f; + float alphaRadius = static_cast<float>(_radiusSlider->value( )) / 100.0f; std::vector< float > alphaNeurites; - for ( unsigned int i = 0; i < _neuriteSliders.size( ); i++ ) + for ( auto& _neuriteSlider: _neuriteSliders ) { - alphaNeurites.push_back(( float ) _neuriteSliders[i]->value( ) / 100.0f ); + alphaNeurites.push_back( + static_cast<float>(_neuriteSlider->value( )) / 100.0f ); } - _openGLWidget->regenerateNeuronToEdit( alphaRadius, alphaNeurites ); + + _openGLWidget->makeCurrent( ); + _openGLWidget->update( ); + _scene->regenerateEditNeuronMesh( alphaRadius , alphaNeurites ); } void MainWindow::finishRecording( ) { - auto actionRecorder = _ui->menuTools->actions().first(); - if(actionRecorder) + auto actionRecorder = _ui->menuTools->actions( ).first( ); + if ( actionRecorder ) { actionRecorder->setEnabled( true ); actionRecorder->setChecked( false ); } } -void MainWindow::loadCameraPositions() +void MainWindow::loadCameraPositions( ) { const QString title = "Load camera positions"; - auto actions = _ui->actionCamera_Positions->menu()->actions(); - const auto numActions = actions.size(); - if(numActions > 0) + auto actions = _ui->actionCamera_Positions->menu( )->actions( ); + const auto numActions = actions.size( ); + if ( numActions > 0 ) { - const auto warnText = tr("Loading new camera positions will remove" - " %1 existing position%2. Are you sure?").arg(numActions).arg(numActions > 1 ? "s":""); - if(QMessageBox::Ok != QMessageBox::warning(this, title, warnText, QMessageBox::Cancel|QMessageBox::Ok)) + const auto warnText = tr( "Loading new camera positions will remove" + " %1 existing position%2. Are you sure?" ).arg( + numActions ).arg( + numActions > 1 ? "s" : "" ); + if ( QMessageBox::Ok != QMessageBox::warning( this , title , warnText , + QMessageBox::Cancel | + QMessageBox::Ok )) return; } const QString nameFilter = "Camera positions (*.json)"; QDir directory; - if(_lastOpenedFileName.isEmpty()) - directory = QDir::home(); + if ( _lastOpenedFileName.isEmpty( )) + directory = QDir::home( ); else - directory = QFileInfo(_lastOpenedFileName).dir(); - - QFileDialog fDialog(this); - fDialog.setWindowIcon(QIcon(":/icons/rsc/neurotessmesh.png")); - fDialog.setWindowTitle(title); - fDialog.setAcceptMode(QFileDialog::AcceptMode::AcceptOpen); - fDialog.setDefaultSuffix("json"); - fDialog.setDirectory(directory); - fDialog.setOption(QFileDialog::Option::DontUseNativeDialog, true); - fDialog.setFileMode(QFileDialog::FileMode::ExistingFile); - fDialog.setNameFilters(QStringList{nameFilter}); - fDialog.setNameFilter(nameFilter); - - if(fDialog.exec() != QFileDialog::Accepted) + directory = QFileInfo( _lastOpenedFileName ).dir( ); + + QFileDialog fDialog( this ); + fDialog.setWindowIcon( QIcon( ":/icons/rsc/neurotessmesh.png" )); + fDialog.setWindowTitle( title ); + fDialog.setAcceptMode( QFileDialog::AcceptMode::AcceptOpen ); + fDialog.setDefaultSuffix( "json" ); + fDialog.setDirectory( directory ); + fDialog.setOption( QFileDialog::Option::DontUseNativeDialog , true ); + fDialog.setFileMode( QFileDialog::FileMode::ExistingFile ); + fDialog.setNameFilters( QStringList{ nameFilter } ); + fDialog.setNameFilter( nameFilter ); + + if ( fDialog.exec( ) != QFileDialog::Accepted ) return; - if(fDialog.selectedFiles().empty()) return; + if ( fDialog.selectedFiles( ).empty( )) return; - auto file = fDialog.selectedFiles().first(); + auto file = fDialog.selectedFiles( ).first( ); - QFile posFile{file}; - if(!posFile.open(QIODevice::ReadOnly|QIODevice::Text)) + QFile posFile{ file }; + if ( !posFile.open( QIODevice::ReadOnly | QIODevice::Text )) { - const QString errorText = tr("Unable to open: %1").arg(file); - QMessageBox::critical(this, title, errorText); + const QString errorText = tr( "Unable to open: %1" ).arg( file ); + QMessageBox::critical( this , title , errorText ); return; } - const auto contents = posFile.readAll(); + const auto contents = posFile.readAll( ); QJsonParseError parserError; - const auto jsonDoc = QJsonDocument::fromJson(contents, &parserError); - if(jsonDoc.isNull() || !jsonDoc.isObject()) + const auto jsonDoc = QJsonDocument::fromJson( contents , &parserError ); + if ( jsonDoc.isNull( ) || !jsonDoc.isObject( )) { - const auto message = tr("Couldn't read the contents of %1 or parsing error.").arg(file); - - QMessageBox msgbox{this}; - msgbox.setWindowTitle(title); - msgbox.setIcon(QMessageBox::Icon::Critical); - msgbox.setText(message); - msgbox.setWindowIcon(QIcon(":/icons/rsc/neurotessmesh.png")); - msgbox.setStandardButtons(QMessageBox::Ok); - msgbox.setDetailedText(parserError.errorString()); - msgbox.exec(); + const auto message = tr( + "Couldn't read the contents of %1 or parsing error." ).arg( file ); + + QMessageBox msgbox{ this }; + msgbox.setWindowTitle( title ); + msgbox.setIcon( QMessageBox::Icon::Critical ); + msgbox.setText( message ); + msgbox.setWindowIcon( QIcon( ":/icons/rsc/neurotessmesh.png" )); + msgbox.setStandardButtons( QMessageBox::Ok ); + msgbox.setDetailedText( parserError.errorString( )); + msgbox.exec( ); return; } - const auto jsonObj = jsonDoc.object(); - if(jsonObj.isEmpty()) + const auto jsonObj = jsonDoc.object( ); + if ( jsonObj.isEmpty( )) { - const auto message = tr("Error parsing the contents of %1.").arg(file); - - QMessageBox msgbox{this}; - msgbox.setWindowTitle(title); - msgbox.setIcon(QMessageBox::Icon::Critical); - msgbox.setText(message); - msgbox.setWindowIcon(QIcon(":/icons/rsc/neurotessmesh.png")); - msgbox.setStandardButtons(QMessageBox::Ok); - msgbox.exec(); + const auto message = tr( "Error parsing the contents of %1." ).arg( file ); + + QMessageBox msgbox{ this }; + msgbox.setWindowTitle( title ); + msgbox.setIcon( QMessageBox::Icon::Critical ); + msgbox.setText( message ); + msgbox.setWindowIcon( QIcon( ":/icons/rsc/neurotessmesh.png" )); + msgbox.setStandardButtons( QMessageBox::Ok ); + msgbox.exec( ); return; } - const QFileInfo currentFile{_lastOpenedFileName}; - const QString jsonPositionsFile = jsonObj.value("filename").toString(); - if(!jsonPositionsFile.isEmpty() && jsonPositionsFile.compare(currentFile.fileName(), Qt::CaseInsensitive) != 0) + const QFileInfo currentFile{ _lastOpenedFileName }; + const QString jsonPositionsFile = jsonObj.value( "filename" ).toString( ); + if ( !jsonPositionsFile.isEmpty( ) && + jsonPositionsFile.compare( currentFile.fileName( ) , + Qt::CaseInsensitive ) != 0 ) { - const auto message = tr("This positions are from file '%1'. Current file" - " is '%2'. Do you want to continue?").arg(jsonPositionsFile).arg(currentFile.fileName()); - - QMessageBox msgbox{this}; - msgbox.setWindowTitle(title); - msgbox.setIcon(QMessageBox::Icon::Question); - msgbox.setText(message); - msgbox.setWindowIcon(QIcon(":/icons/rsc/neurotessmesh.png")); - msgbox.setStandardButtons(QMessageBox::Cancel|QMessageBox::Ok); - msgbox.setDefaultButton(QMessageBox::Ok); - - if(QMessageBox::Ok != msgbox.exec()) + const auto message = tr( "This positions are from file '%1'. Current file" + " is '%2'. Do you want to continue?" ) + .arg( jsonPositionsFile ) + .arg( currentFile.fileName( )); + + QMessageBox msgbox{ this }; + msgbox.setWindowTitle( title ); + msgbox.setIcon( QMessageBox::Icon::Question ); + msgbox.setText( message ); + msgbox.setWindowIcon( QIcon( ":/icons/rsc/neurotessmesh.png" )); + msgbox.setStandardButtons( QMessageBox::Cancel | QMessageBox::Ok ); + msgbox.setDefaultButton( QMessageBox::Ok ); + + if ( QMessageBox::Ok != msgbox.exec( )) return; } // Clear existing actions before entering new ones. - for(auto action: actions) + for ( auto action: actions ) { - _ui->actionCamera_Positions->menu()->removeAction(action); + _ui->actionCamera_Positions->menu( )->removeAction( action ); delete action; } - const auto jsonPositions = jsonObj.value("positions").toArray(); + const auto jsonPositions = jsonObj.value( "positions" ).toArray( ); - auto createPosition = [this](const QJsonValue &v) + auto createPosition = [ this ]( const QJsonValue& v ) { - const auto o = v.toObject(); + const auto o = v.toObject( ); - const auto name = o.value("name").toString(); - const auto position = o.value("position").toString(); - const auto radius = o.value("radius").toString(); - const auto rotation = o.value("rotation").toString(); + const auto name = o.value( "name" ).toString( ); + const auto position = o.value( "position" ).toString( ); + const auto radius = o.value( "radius" ).toString( ); + const auto rotation = o.value( "rotation" ).toString( ); - auto action = new QAction(name); - action->setProperty(POSITION_KEY, position + ";" + radius + ";" + rotation); + auto action = new QAction( name ); + action->setProperty( POSITION_KEY , + position + ";" + radius + ";" + rotation ); - connect(action, SIGNAL(triggered(bool)), this, SLOT(applyCameraPosition())); + connect( action , SIGNAL( triggered( bool )) , + this , SLOT( applyCameraPosition( ))); - _ui->actionCamera_Positions->menu()->addAction(action); + _ui->actionCamera_Positions->menu( )->addAction( action ); }; - std::for_each(jsonPositions.constBegin(), jsonPositions.constEnd(), createPosition); + std::for_each( jsonPositions.constBegin( ) , jsonPositions.constEnd( ) , + createPosition ); - const bool positionsExist = !_ui->actionCamera_Positions->menu()->actions().isEmpty(); - _ui->actionSave_camera_positions->setEnabled(positionsExist); - _ui->actionRemove_camera_position->setEnabled(positionsExist); - _ui->actionCamera_Positions->setEnabled(positionsExist); + const bool positionsExist = !_ui->actionCamera_Positions->menu( )->actions( ).isEmpty( ); + _ui->actionSave_camera_positions->setEnabled( positionsExist ); + _ui->actionRemove_camera_position->setEnabled( positionsExist ); + _ui->actionCamera_Positions->setEnabled( positionsExist ); } -void MainWindow::saveCameraPositions() +void MainWindow::saveCameraPositions( ) { const QString nameFilter = "Camera positions (*.json)"; QDir directory; QString filename; - if(_lastOpenedFileName.isEmpty()) + if ( _lastOpenedFileName.isEmpty( )) { - directory = QDir::home(); + directory = QDir::home( ); filename = "positions.json"; } else { - QFileInfo fi(_lastOpenedFileName); - directory = fi.dir(); - filename = QString("%1_positions.json").arg(fi.baseName()); + QFileInfo fi( _lastOpenedFileName ); + directory = fi.dir( ); + filename = QString( "%1_positions.json" ).arg( fi.baseName( )); } - QFileDialog fDialog(this); - fDialog.setWindowIcon(QIcon(":/icons/rsc/neurotessmesh.png")); - fDialog.setWindowTitle("Save camera positions"); - fDialog.setAcceptMode(QFileDialog::AcceptMode::AcceptSave); - fDialog.setDefaultSuffix("json"); - fDialog.selectFile(filename); - fDialog.setDirectory(directory); - fDialog.setOption(QFileDialog::Option::DontUseNativeDialog, true); - fDialog.setOption(QFileDialog::Option::DontConfirmOverwrite, false); - fDialog.setFileMode(QFileDialog::FileMode::AnyFile); - fDialog.setNameFilters(QStringList{nameFilter}); - fDialog.setNameFilter(nameFilter); - - if(fDialog.exec() != QFileDialog::Accepted) + QFileDialog fDialog( this ); + fDialog.setWindowIcon( QIcon( ":/icons/rsc/neurotessmesh.png" )); + fDialog.setWindowTitle( "Save camera positions" ); + fDialog.setAcceptMode( QFileDialog::AcceptMode::AcceptSave ); + fDialog.setDefaultSuffix( "json" ); + fDialog.selectFile( filename ); + fDialog.setDirectory( directory ); + fDialog.setOption( QFileDialog::Option::DontUseNativeDialog , true ); + fDialog.setOption( QFileDialog::Option::DontConfirmOverwrite , false ); + fDialog.setFileMode( QFileDialog::FileMode::AnyFile ); + fDialog.setNameFilters( QStringList{ nameFilter } ); + fDialog.setNameFilter( nameFilter ); + + if ( fDialog.exec( ) != QFileDialog::Accepted ) return; - if(fDialog.selectedFiles().empty()) return; + if ( fDialog.selectedFiles( ).empty( )) return; - filename = fDialog.selectedFiles().first(); + filename = fDialog.selectedFiles( ).first( ); - QFile wFile{filename}; - if(!wFile.open(QIODevice::WriteOnly|QIODevice::Text|QIODevice::Truncate)) + QFile wFile{ filename }; + if ( !wFile.open( + QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate )) { - const auto message = tr("Unable to open file %1 for writing.").arg(filename); - - QMessageBox msgbox{this}; - msgbox.setWindowTitle(tr("Save camera positions")); - msgbox.setIcon(QMessageBox::Icon::Critical); - msgbox.setText(message); - msgbox.setWindowIcon(QIcon(":/icons/rsc/neurotessmesh.png")); - msgbox.setDefaultButton(QMessageBox::Ok); - msgbox.exec(); + const auto message = tr( "Unable to open file %1 for writing." ).arg( + filename ); + + QMessageBox msgbox{ this }; + msgbox.setWindowTitle( tr( "Save camera positions" )); + msgbox.setIcon( QMessageBox::Icon::Critical ); + msgbox.setText( message ); + msgbox.setWindowIcon( QIcon( ":/icons/rsc/neurotessmesh.png" )); + msgbox.setDefaultButton( QMessageBox::Ok ); + msgbox.exec( ); return; } - QApplication::setOverrideCursor(Qt::WaitCursor); + QApplication::setOverrideCursor( Qt::WaitCursor ); - const auto actions = _ui->actionCamera_Positions->menu()->actions(); + const auto actions = _ui->actionCamera_Positions->menu( )->actions( ); QJsonArray positionsObjs; - auto insertPosition = [&positionsObjs, this](const QAction *a) + auto insertPosition = [ &positionsObjs ]( const QAction* a ) { - if(!a) return; - const auto posData = a->property(POSITION_KEY).toString(); - const auto parts = posData.split(";"); - Q_ASSERT(parts.size() == 3); - const auto position = parts.first(); - const auto radius = parts.at(1); - const auto rotation = parts.last(); + if ( !a ) return; + const auto posData = a->property( POSITION_KEY ).toString( ); + const auto parts = posData.split( ";" ); + Q_ASSERT( parts.size( ) == 3 ); + const auto& position = parts.first( ); + const auto& radius = parts.at( 1 ); + const auto& rotation = parts.last( ); QJsonObject positionObj; - positionObj.insert("name", a->text()); - positionObj.insert("position", position); - positionObj.insert("radius", radius); - positionObj.insert("rotation", rotation); + positionObj.insert( "name" , a->text( )); + positionObj.insert( "position" , position ); + positionObj.insert( "radius" , radius ); + positionObj.insert( "rotation" , rotation ); positionsObjs << positionObj; }; - std::for_each(actions.cbegin(), actions.cend(), insertPosition); + std::for_each( actions.cbegin( ) , actions.cend( ) , insertPosition ); QJsonObject obj; - obj.insert("filename", QFileInfo{_lastOpenedFileName}.fileName()); - obj.insert("positions", positionsObjs); + obj.insert( "filename" , QFileInfo{ _lastOpenedFileName }.fileName( )); + obj.insert( "positions" , positionsObjs ); - QJsonDocument doc{obj}; - wFile.write(doc.toJson()); + QJsonDocument doc{ obj }; + wFile.write( doc.toJson( )); - QApplication::restoreOverrideCursor(); + QApplication::restoreOverrideCursor( ); - if(wFile.error() != QFile::NoError) + if ( wFile.error( ) != QFile::NoError ) { - const auto message = tr("Error saving file %1.").arg(filename); - - QMessageBox msgbox{this}; - msgbox.setWindowTitle(tr("Save camera positions")); - msgbox.setIcon(QMessageBox::Icon::Critical); - msgbox.setText(message); - msgbox.setDetailedText(wFile.errorString()); - msgbox.setWindowIcon(QIcon(":/icons/rsc/neurotessmesh.png")); - msgbox.setDefaultButton(QMessageBox::Ok); - msgbox.exec(); + const auto message = tr( "Error saving file %1." ).arg( filename ); + + QMessageBox msgbox{ this }; + msgbox.setWindowTitle( tr( "Save camera positions" )); + msgbox.setIcon( QMessageBox::Icon::Critical ); + msgbox.setText( message ); + msgbox.setDetailedText( wFile.errorString( )); + msgbox.setWindowIcon( QIcon( ":/icons/rsc/neurotessmesh.png" )); + msgbox.setDefaultButton( QMessageBox::Ok ); + msgbox.exec( ); } - wFile.flush(); - wFile.close(); + wFile.flush( ); + wFile.close( ); } -void MainWindow::addCameraPosition() +void MainWindow::addCameraPosition( ) { QStringList items; - auto actions = _ui->actionCamera_Positions->menu()->actions(); - auto insertItemName = [&items](const QAction *a){ items << a->text(); }; - std::for_each(actions.cbegin(), actions.cend(), insertItemName); + auto actions = _ui->actionCamera_Positions->menu( )->actions( ); + auto insertItemName = [ &items ]( const QAction* a ) + { items << a->text( ); }; + std::for_each( actions.cbegin( ) , actions.cend( ) , insertItemName ); - const QString title = tr("Add camera position"); + const QString title = tr( "Add camera position" ); bool ok = false; QString name; - while(!ok || name.isEmpty()) + while ( !ok || name.isEmpty( )) { - name = QInputDialog::getText(this, title, tr("Position name:"), QLineEdit::Normal, tr("New position"), &ok); + name = QInputDialog::getText( this , title , tr( "Position name:" ) , + QLineEdit::Normal , tr( "New position" ) , + &ok ); - if(ok && !name.isEmpty()) + if ( ok && !name.isEmpty( )) { - QString tempName(name); + QString tempName( name ); int collision = 0; - while(items.contains(tempName, Qt::CaseInsensitive)) + while ( items.contains( tempName , Qt::CaseInsensitive )) { ++collision; - tempName = tr("%1 (%2)").arg(name).arg(collision); + tempName = tr( "%1 (%2)" ).arg( name ).arg( collision ); } name = tempName; } } - auto action = new QAction(name); + auto action = new QAction( name ); - const auto position = _openGLWidget->cameraPosition(); - action->setProperty(POSITION_KEY, position.toString()); + const auto position = _openGLWidget->cameraPosition( ); + action->setProperty( POSITION_KEY , position.toString( )); - connect(action, SIGNAL(triggered(bool)), this, SLOT(applyCameraPosition())); - _ui->actionCamera_Positions->menu()->addAction(action); - _ui->actionCamera_Positions->setEnabled(true); - _ui->actionSave_camera_positions->setEnabled(true); - _ui->actionRemove_camera_position->setEnabled(true); + connect( action , SIGNAL( triggered( bool )) , + this , SLOT( applyCameraPosition( ))); + _ui->actionCamera_Positions->menu( )->addAction( action ); + _ui->actionCamera_Positions->setEnabled( true ); + _ui->actionSave_camera_positions->setEnabled( true ); + _ui->actionRemove_camera_position->setEnabled( true ); } -void MainWindow::removeCameraPosition() +void MainWindow::removeCameraPosition( ) { bool ok = false; QStringList items; - auto actions = _ui->actionCamera_Positions->menu()->actions(); - auto insertItemName = [&items](const QAction *a){ items << a->text(); }; - std::for_each(actions.cbegin(), actions.cend(), insertItemName); + auto actions = _ui->actionCamera_Positions->menu( )->actions( ); + auto insertItemName = [ &items ]( const QAction* a ) + { items << a->text( ); }; + std::for_each( actions.cbegin( ) , actions.cend( ) , insertItemName ); - auto item = QInputDialog::getItem(this, tr("Remove camera position"), tr("Position name:"), items, 0, false, &ok); - if (ok && !item.isEmpty()) + auto item = QInputDialog::getItem( this , tr( "Remove camera position" ) , + tr( "Position name:" ) , items , 0 , + false , &ok ); + if ( ok && !item.isEmpty( )) { - auto actionOfName = [&item](const QAction *a){ return a->text() == item; }; - const auto it = std::find_if(actions.cbegin(), actions.cend(), actionOfName); - auto distance = std::distance(actions.cbegin(), it); - auto action = actions.at(distance); - _ui->actionCamera_Positions->menu()->removeAction(action); + auto actionOfName = [ &item ]( const QAction* a ) + { return a->text( ) == item; }; + const auto it = std::find_if( actions.cbegin( ) , actions.cend( ) , + actionOfName ); + auto distance = std::distance( actions.cbegin( ) , it ); + auto action = actions.at( static_cast<int>(distance)); + _ui->actionCamera_Positions->menu( )->removeAction( action ); delete action; - const auto enabled = actions.size() > 1; - _ui->actionRemove_camera_position->setEnabled(enabled); - _ui->actionSave_camera_positions->setEnabled(enabled); - _ui->actionCamera_Positions->setEnabled(enabled); + const auto enabled = actions.size( ) > 1; + _ui->actionRemove_camera_position->setEnabled( enabled ); + _ui->actionSave_camera_positions->setEnabled( enabled ); + _ui->actionCamera_Positions->setEnabled( enabled ); } } -void MainWindow::applyCameraPosition() +void MainWindow::applyCameraPosition( ) { - auto action = qobject_cast<QAction *>(sender()); - if(action) + auto action = qobject_cast< QAction* >( sender( )); + if ( action ) { - auto positionString = action->property(POSITION_KEY).toString(); - CameraPosition position(positionString); - _openGLWidget->setCameraPosition(position); + auto positionString = action->property( POSITION_KEY ).toString( ); + CameraPosition position( positionString ); + _scene->cameraPosition( position.position , position.radius , + position.rotation ); } } -void MainWindow::_generateNeuritesLayout( void ) +void MainWindow::_generateNeuritesLayout( ) { - const unsigned int numDendrites = _openGLWidget->numNeuritesToEdit( ); + const unsigned int numDendrites = _scene->numEditMorphologyNeurites( ); _neuriteSliders.clear( ); - QLayoutItem* child; - while(( child = _neuritesLayout->takeAt( 0 )) != 0 ) + QLayoutItem * child; + while (( child = _neuritesLayout->takeAt( 0 )) != 0 ) { delete child->widget( ); } QSlider* _neuriteSlider; - for( unsigned int i = 0; i < numDendrites; i++ ) + for ( unsigned int i = 0; i < numDendrites; i++ ) { _neuriteSlider = new QSlider( Qt::Horizontal ); - _neuriteSlider->setMinimum(0); - _neuriteSlider->setMaximum(200); - _neuriteSlider->setValue(100); + _neuriteSlider->setMinimum( 0 ); + _neuriteSlider->setMaximum( 200 ); + _neuriteSlider->setValue( 100 ); _neuriteSlider->setToolTip( "Scales the distance between the position of the first tracing point of\n" "neurite n and the surface of the initial sphere used to generate the\n" @@ -812,40 +864,38 @@ void MainWindow::_generateNeuritesLayout( void ) _neuritesLayout->addWidget( _neuriteSlider ); _neuriteSliders.push_back( _neuriteSlider ); - // connect( _neuriteSlider, SIGNAL( sliderReleased( )), - // this, SLOT( onActionGenerate( ))); - connect( _neuriteSlider, SIGNAL( valueChanged( int )), - this, SLOT( onActionGenerate( int ))); + connect( _neuriteSlider , SIGNAL( valueChanged( int )) , + this , SLOT( onActionGenerate( int ))); } - _radiusSlider->setValue(100); + _radiusSlider->setValue( 100 ); } -void MainWindow::_initExtractionDock( void ) +void MainWindow::_initExtractionDock( ) { _extractMeshDock = new QDockWidget( ); - this->addDockWidget( Qt::DockWidgetAreas::enum_type::RightDockWidgetArea, - _extractMeshDock, Qt::Vertical ); - _extractMeshDock->setSizePolicy(QSizePolicy::MinimumExpanding, - QSizePolicy::Expanding); - - _extractMeshDock->setFeatures(QDockWidget::DockWidgetClosable | - QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); + this->addDockWidget( Qt::DockWidgetAreas::enum_type::RightDockWidgetArea , + _extractMeshDock , Qt::Vertical ); + _extractMeshDock->setSizePolicy( QSizePolicy::MinimumExpanding , + QSizePolicy::Expanding ); + + _extractMeshDock->setFeatures( QDockWidget::DockWidgetClosable | + QDockWidget::DockWidgetMovable | + QDockWidget::DockWidgetFloatable ); _extractMeshDock->setWindowTitle( QString( "Edit And Save" )); - _extractMeshDock->setMinimumSize( 200, 200 ); + _extractMeshDock->setMinimumSize( 200 , 200 ); _extractMeshDock->close( ); - QWidget* newWidget = new QWidget( ); + auto* newWidget = new QWidget( ); _extractMeshDock->setWidget( newWidget ); - QVBoxLayout* _meshDockLayout = new QVBoxLayout( ); + auto* _meshDockLayout = new QVBoxLayout( ); _meshDockLayout->setAlignment( Qt::AlignTop ); newWidget->setLayout( _meshDockLayout ); //Neurons group - QGroupBox* _neuronsGroup = new QGroupBox( QString( "Select Neuron" )); - QVBoxLayout* _neuronsLayout = new QVBoxLayout( ); + auto* _neuronsGroup = new QGroupBox( QString( "Select Neuron" )); + auto* _neuronsLayout = new QVBoxLayout( ); _neuronsGroup->setLayout( _neuronsLayout ); _meshDockLayout->addWidget( _neuronsGroup ); @@ -855,7 +905,7 @@ void MainWindow::_initExtractionDock( void ) // Soma reconstruction group _somaGroup = new QGroupBox( QString( "Parameters" )); - QVBoxLayout* _somaGroupLayout = new QVBoxLayout( ); + auto* _somaGroupLayout = new QVBoxLayout( ); _somaGroup->setLayout( _somaGroupLayout ); _meshDockLayout->addWidget( _somaGroup ); _somaGroup->hide( ); @@ -866,68 +916,69 @@ void MainWindow::_initExtractionDock( void ) _radiusSlider->setValue( 100 ); _radiusSlider->setToolTip( "Scales the radius of the initial sphere used to generate the soma. [0-1]." - ); + ); _somaGroupLayout->addWidget( new QLabel( QString( "Radius factor" ))); _somaGroupLayout->addWidget( _radiusSlider ); - QScrollArea* _neuritesArea = new QScrollArea( ); - _neuritesArea->setSizePolicy( QSizePolicy::MinimumExpanding, + auto* _neuritesArea = new QScrollArea( ); + _neuritesArea->setSizePolicy( QSizePolicy::MinimumExpanding , QSizePolicy::Expanding ); _neuritesArea->setVerticalScrollBarPolicy( Qt::ScrollBarAsNeeded ); _neuritesArea->setHorizontalScrollBarPolicy( Qt::ScrollBarAsNeeded ); _neuritesArea->setWidgetResizable( true ); _neuritesArea->setFrameShape( QFrame::NoFrame ); _somaGroupLayout->addWidget( _neuritesArea ); - QWidget* _neuritesWidget = new QWidget( ); + auto* _neuritesWidget = new QWidget( ); _neuritesArea->setWidget( _neuritesWidget ); _neuritesLayout = new QVBoxLayout( ); _neuritesWidget->setLayout( _neuritesLayout ); _extractButton = new QPushButton( QString( "Save" )); - _extractButton->setSizePolicy( QSizePolicy::Fixed, - QSizePolicy::Fixed); + _extractButton->setSizePolicy( QSizePolicy::Fixed , + QSizePolicy::Fixed ); _extractButton->setEnabled( false ); _meshDockLayout->addWidget( _extractButton ); - connect( _neuronList, SIGNAL( itemClicked( QListWidgetItem* )), - this, SLOT( onListClicked( QListWidgetItem* ))); + connect( _neuronList , SIGNAL( itemClicked( QListWidgetItem * )) , + this , SLOT( onListClicked( QListWidgetItem * ))); + + connect( _extractMeshDock->toggleViewAction( ) , SIGNAL( toggled( bool )) , + _ui->actionEditSave , SLOT( setChecked( bool ))); - connect( _extractMeshDock->toggleViewAction( ), SIGNAL( toggled( bool )), - _ui->actionEditSave, SLOT( setChecked( bool ))); - connect( _ui->actionEditSave, SIGNAL( triggered( )), - this, SLOT( updateExtractMeshDock( ))); + connect( _ui->actionEditSave , SIGNAL( triggered( )) , + this , SLOT( updateExtractMeshDock( ))); } -void MainWindow::_initConfigurationDock( void ) +void MainWindow::_initConfigurationDock( ) { _configurationDock = new QDockWidget( ); - this->addDockWidget( Qt::DockWidgetAreas::enum_type::LeftDockWidgetArea, - _configurationDock, Qt::Vertical ); - _configurationDock->setSizePolicy(QSizePolicy::MinimumExpanding, - QSizePolicy::Expanding); - - _configurationDock->setFeatures(QDockWidget::DockWidgetClosable | - QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); + this->addDockWidget( Qt::DockWidgetAreas::enum_type::LeftDockWidgetArea , + _configurationDock , Qt::Vertical ); + _configurationDock->setSizePolicy( QSizePolicy::MinimumExpanding , + QSizePolicy::Expanding ); + + _configurationDock->setFeatures( QDockWidget::DockWidgetClosable | + QDockWidget::DockWidgetMovable | + QDockWidget::DockWidgetFloatable ); _configurationDock->setWindowTitle( QString( "Configuration" )); - _configurationDock->setMinimumSize( 200, 200 ); + _configurationDock->setMinimumSize( 200 , 200 ); _configurationDock->close( ); - QWidget* newWidget = new QWidget( ); + auto* newWidget = new QWidget( ); _configurationDock->setWidget( newWidget ); - QVBoxLayout* _configDockLayout = new QVBoxLayout( ); + auto* _configDockLayout = new QVBoxLayout( ); _configDockLayout->setAlignment( Qt::AlignTop ); newWidget->setLayout( _configDockLayout ); - QGroupBox* tessParamsGroup = new QGroupBox( QString( "Tessellation params" )); - tessParamsGroup->setSizePolicy( QSizePolicy::Fixed, - QSizePolicy::Fixed); + auto* tessParamsGroup = new QGroupBox( QString( "Tessellation params" )); + tessParamsGroup->setSizePolicy( QSizePolicy::Fixed , + QSizePolicy::Fixed ); _configDockLayout->addWidget( tessParamsGroup ); - QVBoxLayout* vbox = new QVBoxLayout; + auto* vbox = new QVBoxLayout; tessParamsGroup->setLayout( vbox ); _lotSlider = new QSlider( Qt::Horizontal ); @@ -936,7 +987,7 @@ void MainWindow::_initConfigurationDock( void ) _lotSlider->setValue( 4 ); _lotSlider->setToolTip( "Maximum level of subdivisions for the visualization [1-30]." - ); + ); vbox->addWidget( new QLabel( QString( "Subdivision Level" ))); vbox->addWidget( _lotSlider ); @@ -946,8 +997,8 @@ void MainWindow::_initConfigurationDock( void ) _distanceSlider->setMaximum( 1000 ); _distanceSlider->setValue( 10 ); _distanceSlider->setToolTip( - "Further distance to which the subdivision is applied [0-1], being 1\n" - "the camera maximum visibility distance." ); + "Further distance to which the subdivision is applied [0-1], being 1\n" + "the camera maximum visibility distance." ); vbox->addWidget( new QLabel( QString( "Distance threshold" ))); vbox->addWidget( _distanceSlider ); @@ -965,67 +1016,67 @@ void MainWindow::_initConfigurationDock( void ) _radioLinear->setChecked( true ); - connect( _radioLinear, SIGNAL( toggled( bool )), - _distanceSlider, SLOT( setEnabled( bool ))); + connect( _radioLinear , SIGNAL( toggled( bool )) , + _distanceSlider , SLOT( setEnabled( bool ))); + connect( _configurationDock->toggleViewAction( ) , SIGNAL( toggled( bool )) , + _ui->actionConfiguration , SLOT( setChecked( bool ))); - connect( _configurationDock->toggleViewAction( ), SIGNAL( toggled( bool )), - _ui->actionConfiguration, SLOT( setChecked( bool ))); - connect( _ui->actionConfiguration, SIGNAL( triggered( )), - this, SLOT( updateConfigurationDock( ))); + connect( _ui->actionConfiguration , SIGNAL( triggered( )) , + this , SLOT( updateConfigurationDock( ))); } -void MainWindow::_initRenderOptionsDock( void ) +void MainWindow::_initRenderOptionsDock( ) { _renderOptionsDock = new QDockWidget( ); - this->addDockWidget( Qt::DockWidgetAreas::enum_type::LeftDockWidgetArea, - _renderOptionsDock, Qt::Vertical ); - _renderOptionsDock->setSizePolicy(QSizePolicy::Fixed, - QSizePolicy::Fixed); - _renderOptionsDock->setFeatures(QDockWidget::DockWidgetClosable | - QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); + this->addDockWidget( Qt::DockWidgetAreas::enum_type::LeftDockWidgetArea , + _renderOptionsDock , Qt::Vertical ); + _renderOptionsDock->setSizePolicy( QSizePolicy::Fixed , + QSizePolicy::Fixed ); + _renderOptionsDock->setFeatures( QDockWidget::DockWidgetClosable | + QDockWidget::DockWidgetMovable | + QDockWidget::DockWidgetFloatable ); _renderOptionsDock->setWindowTitle( QString( "Render Options" )); - _renderOptionsDock->setMinimumSize( 200, 200 ); + _renderOptionsDock->setMinimumSize( 200 , 200 ); _renderOptionsDock->close( ); - QWidget* newWidget = new QWidget( ); + auto* newWidget = new QWidget( ); _renderOptionsDock->setWidget( newWidget ); - QVBoxLayout* roDockLayout = new QVBoxLayout( ); + auto* roDockLayout = new QVBoxLayout( ); roDockLayout->setAlignment( Qt::AlignTop ); newWidget->setLayout( roDockLayout ); - QGroupBox* colorGroup = new QGroupBox( QString( "Color" )); - colorGroup->setSizePolicy( QSizePolicy(QSizePolicy::Fixed, - QSizePolicy::Fixed)); + auto* colorGroup = new QGroupBox( QString( "Color" )); + colorGroup->setSizePolicy( QSizePolicy( QSizePolicy::Fixed , + QSizePolicy::Fixed )); roDockLayout->addWidget( colorGroup ); - QGridLayout* gridbox = new QGridLayout; + auto* gridbox = new QGridLayout; colorGroup->setLayout( gridbox ); - gridbox->addWidget( new QLabel( QString("Background color")), 0, 0); + gridbox->addWidget( new QLabel( QString( "Background color" )) , 0 , 0 ); _backGroundColor = new ColorSelectionWidget( this ); - gridbox->addWidget( _backGroundColor, 0, 1 ); + gridbox->addWidget( _backGroundColor , 0 , 1 ); - gridbox->addWidget( new QLabel( QString("Neuron color")), 1, 0); + gridbox->addWidget( new QLabel( QString( "Neuron color" )) , 1 , 0 ); _neuronColor = new ColorSelectionWidget( this ); - gridbox->addWidget( _neuronColor, 1, 1 ); + gridbox->addWidget( _neuronColor , 1 , 1 ); - gridbox->addWidget( new QLabel( QString("Selected neuron color")), 2, 0); + gridbox->addWidget( new QLabel( QString( "Selected neuron color" )) , 2 , 0 ); _selectedNeuronColor = new ColorSelectionWidget( this ); - gridbox->addWidget( _selectedNeuronColor, 2, 1 ); + gridbox->addWidget( _selectedNeuronColor , 2 , 1 ); - QGroupBox* renderGroup = new QGroupBox( QString( "Render piece selection" )); + auto* renderGroup = new QGroupBox( QString( "Render piece selection" )); roDockLayout->addWidget( renderGroup ); - QVBoxLayout* vbox = new QVBoxLayout; + auto* vbox = new QVBoxLayout; renderGroup->setLayout( vbox ); _neuronRender = new QComboBox( ); - _neuronRender->setSizePolicy( QSizePolicy(QSizePolicy::Fixed, - QSizePolicy::Fixed)); + _neuronRender->setSizePolicy( QSizePolicy( QSizePolicy::Fixed , + QSizePolicy::Fixed )); vbox->addWidget( new QLabel( QString( "Neuron" ))); vbox->addWidget( _neuronRender ); _neuronRender->addItem( QString( "all" )); @@ -1033,39 +1084,165 @@ void MainWindow::_initRenderOptionsDock( void ) _neuronRender->addItem( QString( "neurites" )); _selectedNeuronRender = new QComboBox( ); - _selectedNeuronRender->setSizePolicy( QSizePolicy(QSizePolicy::Fixed, - QSizePolicy::Fixed)); + _selectedNeuronRender->setSizePolicy( QSizePolicy( QSizePolicy::Fixed , + QSizePolicy::Fixed )); vbox->addWidget( new QLabel( QString( "Selected neuron" ))); vbox->addWidget( _selectedNeuronRender ); _selectedNeuronRender->addItem( QString( "all" )); _selectedNeuronRender->addItem( QString( "soma" )); _selectedNeuronRender->addItem( QString( "neurites" )); - connect( _renderOptionsDock->toggleViewAction( ), SIGNAL( toggled( bool )), - _ui->actionRenderOptions, SLOT( setChecked( bool ))); - connect( _ui->actionRenderOptions, SIGNAL( triggered( )), - this, SLOT( updateRenderOptionsDock( ))); + connect( _renderOptionsDock->toggleViewAction( ) , SIGNAL( toggled( bool )) , + _ui->actionRenderOptions , SLOT( setChecked( bool ))); + + connect( _ui->actionRenderOptions , SIGNAL( triggered( )) , + this , SLOT( updateRenderOptionsDock( ))); } -void MainWindow::closeEvent(QCloseEvent *e) +void MainWindow::closeEvent( QCloseEvent* e ) { - if(_recorder) + if ( _recorder ) { - QMessageBox msgBox(this); - msgBox.setWindowTitle(tr("Exit NeuroTessMesh")); + QMessageBox msgBox( this ); + msgBox.setWindowTitle( tr( "Exit NeuroTessMesh" )); msgBox.setWindowIcon( QIcon( ":/icons/rsc/neurotessmesh.png" )); - msgBox.setText(tr("A recording is being made. Do you really want to exit NeuroTessMesh?")); - msgBox.setStandardButtons(QMessageBox::Cancel|QMessageBox::Yes); + msgBox.setText( tr( + "A recording is being made. Do you really want to exit NeuroTessMesh?" )); + msgBox.setStandardButtons( QMessageBox::Cancel | QMessageBox::Yes ); - if(msgBox.exec() != QMessageBox::Yes) + if ( msgBox.exec( ) != QMessageBox::Yes ) { - e->ignore(); + e->ignore( ); return; } - RecorderUtils::stopAndWait(_recorder, this); + RecorderUtils::stopAndWait( _recorder , this ); _recorder = nullptr; } - QMainWindow::closeEvent(e); + QMainWindow::closeEvent( e ); +} + +void MainWindow::_initPlayerDock( ) +{ + _playerDock = new QDockWidget( ); + this->addDockWidget( Qt::DockWidgetAreas::enum_type::BottomDockWidgetArea , + _playerDock , Qt::Horizontal ); + _playerDock->setSizePolicy( QSizePolicy::MinimumExpanding , + QSizePolicy::Expanding ); + + _playerDock->setFeatures( QDockWidget::DockWidgetClosable | + QDockWidget::DockWidgetMovable | + QDockWidget::DockWidgetFloatable ); + _playerDock->setAllowedAreas(Qt::DockWidgetAreas::enum_type::BottomDockWidgetArea| + Qt::DockWidgetAreas::enum_type::TopDockWidgetArea); + _playerDock->setWindowTitle( QString( "Player Options" )); + _playerDock->show( ); + _playerDock->close( ); + +#ifdef NEUROTESSMESH_USE_SIMIL + auto playerControls = new qsimil::QSimControlWidget( _playerDock ); + _playerDock->setWidget( playerControls ); + + connect( playerControls , SIGNAL( frame( )) , + _openGLWidget , SLOT( update( ))); + + connect( _playerDock->toggleViewAction( ) , SIGNAL( toggled( bool )) , + _ui->actionSimulation_player_options , SLOT( setChecked( bool ))); + + connect( _ui->actionSimulation_player_options , SIGNAL( triggered( )) , + this , SLOT( updatePlayerOptionsDock( ))); +#endif +} + +void MainWindow::loadData( const std::string& arg1 , const std::string& arg2 , + const neurotessmesh::LoaderThread::DataFileType type ) +{ + if ( m_dataLoader ) return; // already loading? + + m_dataLoader = std::make_shared< neurotessmesh::LoaderThread >( arg1 , arg2 , + type ); + auto dialog = new neurotessmesh::LoadingDialog{ this }; + + connect( m_dataLoader.get( ) , SIGNAL( finished( )) , + this , SLOT( onDataLoaded( )) , Qt::QueuedConnection ); + + connect( m_dataLoader.get( ) , SIGNAL( destroyed( QObject * )) , + dialog , SLOT( closeDialog( ))); + + connect( m_dataLoader.get( ) , + SIGNAL( progress(const QString & , const unsigned int)) , + dialog , SLOT( progress(const QString & , const unsigned int))); + + dialog->show( ); + + m_dataLoader->start( ); +} + +void MainWindow::onDataLoaded( ) +{ + const auto errors = m_dataLoader->errors( ); + if ( !errors.isEmpty( )) + { + m_dataLoader = nullptr; + + QMessageBox msgbox{ this }; + msgbox.setWindowTitle( tr( "Error loading dataset" )); + msgbox.setIcon( QMessageBox::Icon::Critical ); + msgbox.setText( errors ); + msgbox.setWindowIcon( QIcon( ":/icons/rsc/neurotessmesh.png" )); + msgbox.setStandardButtons( QMessageBox::Ok ); + msgbox.exec( ); + return; + } + + try + { + _openGLWidget->makeCurrent(); + _openGLWidget->update( ); + + _scene = std::make_shared< neurotessmesh::Scene >( + _openGLWidget->getCamera( ) , m_dataLoader->getDataset( ) +#ifdef NEUROTESSMESH_USE_SIMIL + , m_dataLoader->getPlayer( ) +#endif + ); + _openGLWidget->setScene( _scene ); + } + catch ( const std::exception& e ) + { + QMessageBox msgbox{ this }; + msgbox.setWindowTitle( tr( "Error loading dataset" )); + msgbox.setIcon( QMessageBox::Icon::Critical ); + msgbox.setText( tr( "Unable to load dataset. Geometry error." )); + msgbox.setWindowIcon( QIcon( ":/icons/rsc/neurotessmesh.png" )); + msgbox.setStandardButtons( QMessageBox::Ok ); + msgbox.exec( ); + return; + } + + _openGLWidget->onLotValueChanged( _lotSlider->value( )); + _openGLWidget->onDistanceValueChanged( _distanceSlider->value( )); + + updateNeuronList( ); + _backGroundColor->color( QColor( 255 , 255 , 255 )); + _neuronColor->color( QColor( 0 , 120 , 250 )); + _selectedNeuronColor->color( QColor( 250 , 120 , 0 )); + + _openGLWidget->home( ); + _openGLWidget->changeNeuronPiece( _neuronRender->currentIndex( )); + _openGLWidget->changeSelectedNeuronPiece( + _selectedNeuronRender->currentIndex( )); +#ifdef NEUROTESSMESH_USE_SIMIL + auto playerWidget = qobject_cast< qsimil::QSimControlWidget* >( + _playerDock->widget( )); + if ( playerWidget ) + { + + // disable hasta que hagamos el merge a master de SimIL + playerWidget->init( m_dataLoader->getPlayer( )); + } +#endif + + m_dataLoader = nullptr; } diff --git a/neurotessmesh/MainWindow.h b/neurotessmesh/MainWindow.h index 960597eae6af66fed865f23a28e02d50b011b026..d76464f6175e1c2ea1b02cc724eb104223f2ca89 100644 --- a/neurotessmesh/MainWindow.h +++ b/neurotessmesh/MainWindow.h @@ -12,7 +12,7 @@ #include <QMainWindow> #include "OpenGLWidget.h" #include "ColorSelectionWidget.h" - +#include "LoaderThread.h" #include <QDockWidget> #include <QListWidget> @@ -24,47 +24,67 @@ namespace Ui { -class MainWindow; + class MainWindow; } class Recorder; + class QCloseEvent; +namespace neurotessmesh +{ + class LoaderThread; +} + class MainWindow : public QMainWindow { - Q_OBJECT +Q_OBJECT public: - explicit MainWindow( QWidget* parent_ = nullptr, + explicit MainWindow( QWidget* parent_ = nullptr , bool updateOnIdle_ = false ); - ~MainWindow( void ); + + ~MainWindow( ); void init( const std::string& zeqSession_ ); - void showStatusBarMessage ( const QString& message ); + void showStatusBarMessage( const QString& message ); + + void openBlueConfig( const std::string& fileName , + const std::string& target ); - void openBlueConfig( const std::string& fileName, - const std::string& target); void openXMLScene( const std::string& fileName ); + void openSWCFile( const std::string& fileName ); - void updateNeuronList( void ); + void updateNeuronList( ); public slots: - void home( void ); - void openBlueConfigThroughDialog( void ); - void openXMLSceneThroughDialog( void ); - void openSWCFileThroughDialog( void ); - void showAbout( void ); - void openRecorder( void ); - - void updateExtractMeshDock( void ); - void updateConfigurationDock( void ); - void updateRenderOptionsDock( void ); - void onListClicked( QListWidgetItem *item ); + void home( ); + + void openBlueConfigThroughDialog( ); + + void openXMLSceneThroughDialog( ); + + void openSWCFileThroughDialog( ); + + void showAbout( ); + + void openRecorder( ); + + void updateExtractMeshDock( ); + + void updateConfigurationDock( ); + + void updateRenderOptionsDock( ); + + void updatePlayerOptionsDock( ); + + void onListClicked( QListWidgetItem* item ); + void onActionGenerate( int value_ ); protected slots: @@ -74,46 +94,70 @@ protected slots: /** \brief Loads camera positions from a file. * */ - void loadCameraPositions(); + void loadCameraPositions( ); /** \brief Saves camera positions to a file on disk. * */ - void saveCameraPositions(); + void saveCameraPositions( ); /** \brief Stores current camera position in the positions list. * */ - void addCameraPosition(); + void addCameraPosition( ); /** \brief Lets the user select a position to remove from the positions list. * */ - void removeCameraPosition(); + void removeCameraPosition( ); /** \brief Changes the camera position to the one specified by the user. * */ - void applyCameraPosition(); + void applyCameraPosition( ); + + /** \brief Gets the dataset and player from the loader thread and initializes + * openGL widget and scene. + * + */ + void onDataLoaded( ); protected: - virtual void closeEvent(QCloseEvent *e) override; + virtual void closeEvent( QCloseEvent* e ) override; QString _lastOpenedFileName; private: - void _generateNeuritesLayout( void ); - void _initExtractionDock( void ); - void _initConfigurationDock( void ); - void _initRenderOptionsDock( void ); + void _generateNeuritesLayout( ); + + void _initExtractionDock( ); + + void _initConfigurationDock( ); + + void _initRenderOptionsDock( ); + + void _initPlayerDock( ); + + /** \brief Launches a LoaderThread with the gicen arguments. + * \param[in] arg1 First argument,required: dataset filename. + * \param[in] arg2 Second argument, only required for BlueConfig (target). + * \param[in] type Dataset type. + * + */ + void loadData( const std::string& arg1 , + const std::string& arg2 , + neurotessmesh::LoaderThread::DataFileType type ); Ui::MainWindow* _ui; OpenGLWidget* _openGLWidget; + std::shared_ptr< neurotessmesh::Scene > _scene; + QDockWidget* _extractMeshDock; QDockWidget* _configurationDock; QDockWidget* _renderOptionsDock; + QDockWidget* _playerDock; QListWidget* _neuronList; QSlider* _radiusSlider; @@ -137,4 +181,5 @@ private: // Recorder Recorder* _recorder; + std::shared_ptr< neurotessmesh::LoaderThread > m_dataLoader; }; diff --git a/neurotessmesh/OpenGLWidget.cpp b/neurotessmesh/OpenGLWidget.cpp index 858c4e2c8a6f931436b3cb5f964d28d6f6a458a7..206707889ac2d94e5dc97e80562eb2ef71610853 100644 --- a/neurotessmesh/OpenGLWidget.cpp +++ b/neurotessmesh/OpenGLWidget.cpp @@ -11,30 +11,32 @@ #include "OpenGLWidget.h" #include "MainWindow.h" +#include "Scene.h" #include <QOpenGLContext> #include <QMouseEvent> #include <QColorDialog> #include <QFileDialog> #include <QMessageBox> +#include <QDebug> #include <sstream> #include <string> #include <iostream> +#include <utility> #include <nlrender/nlrender.h> -const float OpenGLWidget::_colorFactor = 1 / 255.0f; const QString FPSLABEL_STYLESHEET = "QLabel { background-color : #333;" "color : white;" "padding: 3px;" "margin: 10px;" "border-radius: 10px;}"; -OpenGLWidget::OpenGLWidget( QWidget* parent_, +OpenGLWidget::OpenGLWidget( QWidget* parent_ , Qt::WindowFlags windowsFlags_ ) - : QOpenGLWidget( parent_, windowsFlags_ ) - , _scene{nullptr} + : QOpenGLWidget( parent_ , windowsFlags_ ) + , _scene( nullptr ) , _mouseX( 0 ) , _mouseY( 0 ) , _rotation( false ) @@ -47,23 +49,24 @@ OpenGLWidget::OpenGLWidget( QWidget* parent_, , _showFps( false ) , _frameCount( 0 ) #ifdef NEUROTESSMESH_USE_LEXIS - , _subscriber( nullptr ) - , _subscriberThread( nullptr ) +, _subscriber( nullptr ) +, _subscriberThread( nullptr ) #endif { try { _camera = new reto::OrbitalCameraController( ); } - catch(...) + catch ( ... ) { - _camera = new reto::OrbitalCameraController(nullptr, reto::Camera::NO_ZEROEQ); + _camera = new reto::OrbitalCameraController( nullptr , + reto::Camera::NO_ZEROEQ ); } _cameraTimer = new QTimer( ); _cameraTimer->start(( 1.0f / 60.f ) * 100 ); - connect( _cameraTimer, SIGNAL( timeout( )), this, SLOT( timerUpdate( ))); - _fpsLabel.setStyleSheet(FPSLABEL_STYLESHEET); + connect( _cameraTimer , SIGNAL( timeout( )) , this , SLOT( timerUpdate( ))); + _fpsLabel.setStyleSheet( FPSLABEL_STYLESHEET ); // This is needed to get key events this->setFocusPolicy( Qt::WheelFocus ); @@ -71,36 +74,16 @@ OpenGLWidget::OpenGLWidget( QWidget* parent_, _lastSavedFileName = QDir::currentPath( ); } -OpenGLWidget::~OpenGLWidget( void ) +OpenGLWidget::~OpenGLWidget( ) { delete _camera; - delete _scene; delete _cameraTimer; } -bool OpenGLWidget::loadData( - const std::string& fileName_, - const neurotessmesh::Scene::TDataFileType fileType_, - const std::string& target_ ) +void OpenGLWidget::setScene( std::shared_ptr< neurotessmesh::Scene > scene ) { + _scene = std::move( scene ); makeCurrent( ); - const auto errorString = _scene->loadData( fileName_, fileType_, target_ ); - - if(!errorString.empty()) - { - const QString message = QString("Error loading file '%1'.\n%2") - .arg(QString::fromStdString(fileName_)) - .arg(QString::fromStdString(errorString)); - - QMessageBox msgBox(this); - msgBox.setWindowTitle(tr("Error Loading Data")); - msgBox.setWindowIcon(QIcon(":/icons/rsc/neurotessmesh.png")); - msgBox.setText(message); - msgBox.setIcon(QMessageBox::Icon::Critical); - msgBox.exec(); - } - - return errorString.empty(); } void OpenGLWidget::idleUpdate( bool idleUpdate_ ) @@ -108,60 +91,35 @@ void OpenGLWidget::idleUpdate( bool idleUpdate_ ) _idleUpdate = idleUpdate_; } -const std::vector< unsigned int > OpenGLWidget::neuronIdList( void ) const -{ - return _scene->neuronIndices( ); -} - -void OpenGLWidget::neuronToEdit( const unsigned int id_ ) +void OpenGLWidget::home( ) { - _scene->setNeuronToEdit( id_ ); - update( ); -} - -unsigned int OpenGLWidget::numNeuritesToEdit( void ) const -{ - return _scene->numEditMorphologyNeurites( ); -} - -void OpenGLWidget::regenerateNeuronToEdit( - const float alphaRadius_, - const std::vector< float >& alphaNeurites_ ) -{ - makeCurrent( ); - _scene->regenerateEditNeuronMesh( alphaRadius_, alphaNeurites_ ); - update( ); -} - -void OpenGLWidget::home( void ) -{ - _scene->home( ); update( ); } void OpenGLWidget::setZeqSession( const std::string& #ifdef NEUROTESSMESH_USE_LEXIS - session_ - ) + session_ +) { - if ( !session_.empty( )) + if (!session_.empty()) { - if ( _camera ) + if (_camera) { delete _camera; } try { - _camera = new reto::OrbitalCameraController( nullptr, session_ ); + _camera = new reto::OrbitalCameraController(nullptr, session_); } - catch(...) + catch (...) { - _camera = new reto::OrbitalCameraController(nullptr, reto::Camera::NO_ZEROEQ); + _camera = new reto::OrbitalCameraController(nullptr, + reto::Camera::NO_ZEROEQ); } - if ( _subscriberThread ) + if (_subscriberThread) { _subscriberThread->~thread(); delete _subscriberThread; @@ -170,119 +128,82 @@ void OpenGLWidget::setZeqSession( const std::string& try { - _subscriber = new zeroeq::Subscriber( session_ ); + _subscriber = new zeroeq::Subscriber(session_); - _subscriber->subscribe( - lexis::data::SelectedIDs::ZEROBUF_TYPE_IDENTIFIER( ), - [&]( const void* selectedData, size_t selectedSize ) - { _onSelectionEvent( lexis::data::SelectedIDs::create( - selectedData, selectedSize ));}); + _subscriber->subscribe( + lexis::data::SelectedIDs::ZEROBUF_TYPE_IDENTIFIER(), + [&](const void *selectedData, size_t selectedSize) + { _onSelectionEvent( lexis::data::SelectedIDs::create( + selectedData, selectedSize ));}); #ifdef NEUROTESSMESH_USE_GMRVLEX - _subscriber->subscribe( - zeroeq::gmrv::FocusedIDs::ZEROBUF_TYPE_IDENTIFIER( ), - [&]( const void* focusedData, size_t focusedSize ) - { _onFocusEvent( zeroeq::gmrv::FocusedIDs::create( - focusedData, focusedSize ));}); + _subscriber->subscribe( + zeroeq::gmrv::FocusedIDs::ZEROBUF_TYPE_IDENTIFIER(), + [&](const void *focusedData, size_t focusedSize) + { _onFocusEvent( zeroeq::gmrv::FocusedIDs::create( + focusedData, focusedSize ));}); #endif - _subscriberThread = - new std::thread( [&](){ - try - { - while ( true ) - _subscriber->receive( 10000 ); - } - catch( ... ) - { - std::cerr << "Connexion closed" << std::endl; - } - }); + _subscriberThread = new std::thread([&]() + { + try + { + while ( true ) + _subscriber->receive( 10000 ); + } + catch( ... ) + { + std::cerr << "EXCEPTION: ZeroEQ Conection closed -> " + << __FILE__ << ":" << __LINE__ << std::endl; + } + }); } - catch ( ... ) + catch (...) { std::cerr << "Zeroeq Session Error: could not connect to " << session_ - << " session" << std::endl; + << " session" << std::endl; } } } - #else - - ) + ) { std::cerr << "Zeq not supported " << std::endl; } -#endif - -CameraPosition OpenGLWidget::cameraPosition() const -{ - CameraPosition pos; - pos.position = _camera->position(); - pos.radius = _camera->radius(); - pos.rotation = _camera->rotation(); - - return pos; -} -void OpenGLWidget::setCameraPosition(const CameraPosition &pos) -{ - if(_camera) - { - _scene->cameraPosition(pos.position, pos.radius, pos.rotation); - _camera->position(pos.position); - _camera->radius(pos.radius); - _camera->rotation(pos.rotation); - update(); - } -} +#endif -void OpenGLWidget::changeClearColor( QColor color ) +reto::OrbitalCameraController* OpenGLWidget::getCamera( ) const { - makeCurrent( ); - glClearColor( float( color.red( )) * _colorFactor, - float( color.green( )) * _colorFactor, - float( color.blue( )) * _colorFactor, - float( color.alpha( )) * _colorFactor); - update( ); + return _camera; } -void OpenGLWidget::changeNeuronColor( QColor color ) +CameraPosition OpenGLWidget::cameraPosition( ) const { - makeCurrent( ); - _scene->unselectedNeuronColor( - Eigen::Vector3f( float( color.red( )) * _colorFactor, - float( color.green( )) * _colorFactor, - float( color.blue( )) * _colorFactor )); - update( ); -} + CameraPosition pos; + pos.position = _camera->position( ); + pos.radius = _camera->radius( ); + pos.rotation = _camera->rotation( ); -void OpenGLWidget::changeSelectedNeuronColor( QColor color ) -{ - makeCurrent( ); - _scene->selectedNeuronColor( - Eigen::Vector3f( float( color.red( )) * _colorFactor, - float( color.green( )) * _colorFactor, - float( color.blue( )) * _colorFactor )); - update( ); + return pos; } -void OpenGLWidget::toggleUpdateOnIdle( void ) +void OpenGLWidget::toggleUpdateOnIdle( ) { _idleUpdate = !_idleUpdate; if ( _idleUpdate ) update( ); } -void OpenGLWidget::toggleShowFPS( void ) +void OpenGLWidget::toggleShowFPS( ) { _showFps = !_showFps; if ( _idleUpdate ) update( ); } -void OpenGLWidget::toggleWireframe( void ) +void OpenGLWidget::toggleWireframe( ) { makeCurrent( ); _wireframe = !_wireframe; @@ -290,45 +211,45 @@ void OpenGLWidget::toggleWireframe( void ) if ( _wireframe ) { glEnable( GL_POLYGON_OFFSET_LINE ); - glPolygonOffset( -1, -1 ); + glPolygonOffset( -1 , -1 ); glLineWidth( 1.5 ); - glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); + glPolygonMode( GL_FRONT_AND_BACK , GL_LINE ); } else { - glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); + glPolygonMode( GL_FRONT_AND_BACK , GL_FILL ); glDisable( GL_POLYGON_OFFSET_LINE ); } update( ); } -void OpenGLWidget::timerUpdate( void ) +void OpenGLWidget::timerUpdate( ) { - if( _camera->isAniming() ) + if ( _camera->isAniming( )) { - _camera->anim(); + _camera->anim( ); this->update( ); } } -void OpenGLWidget::extractEditNeuronMesh( void ) +void OpenGLWidget::extractEditNeuronMesh( ) { - if( _scene->isEditNeuronMeshExtraction( )) + if ( _scene->isEditNeuronMeshExtraction( )) { QString path; const QString filter( tr( "OBJ (*.obj);; All files (*)" )); - QFileDialog fd(this, QString( "Save mesh to OBJ file" ), - _lastSavedFileName, filter ); + QFileDialog fd( this , QString( "Save mesh to OBJ file" ) , + _lastSavedFileName , filter ); - fd.setOption( QFileDialog::DontUseNativeDialog, true ); - fd.setDefaultSuffix( "obj") ; + fd.setOption( QFileDialog::DontUseNativeDialog , true ); + fd.setDefaultSuffix( "obj" ); fd.setFileMode( QFileDialog/*::FileMode*/::AnyFile ); fd.setAcceptMode( QFileDialog/*::AcceptMode*/::AcceptSave ); if ( fd.exec( )) - path = fd.selectedFiles( )[0]; + path = fd.selectedFiles( )[ 0 ]; - if ( !path.isEmpty() ) + if ( !path.isEmpty( )) { _lastSavedFileName = QFileInfo( path ).path( ); this->makeCurrent( ); @@ -340,73 +261,87 @@ void OpenGLWidget::extractEditNeuronMesh( void ) void OpenGLWidget::onLotValueChanged( int value_ ) { - _scene->levelOfDetail(static_cast<float>(value_) * 0.1 ); - update( ); + if ( _scene ) + { + _scene->levelOfDetail( static_cast<float>(value_) * 0.1f ); + update( ); + } } void OpenGLWidget::onDistanceValueChanged( int value_ ) { - _scene->maximumDistance(static_cast<float>(value_) / 1000.0f ); - update( ); + if ( _scene ) + { + _scene->maximumDistance( static_cast<float>(value_) / 1000.0f ); + update( ); + } } -void OpenGLWidget::onHomogeneousClicked( void ) +void OpenGLWidget::onHomogeneousClicked( ) { - _scene->subdivisionCriteria( nlrender::Renderer::HOMOGENEOUS ); - update( ); + if ( _scene ) + { + _scene->subdivisionCriteria( nlrender::Renderer::HOMOGENEOUS ); + update( ); + } } -void OpenGLWidget::onLinearClicked( void ) +void OpenGLWidget::onLinearClicked( ) { - _scene->subdivisionCriteria( nlrender::Renderer::LINEAR ); - update( ); + if ( _scene ) + { + _scene->subdivisionCriteria( nlrender::Renderer::LINEAR ); + update( ); + } } + void OpenGLWidget::changeNeuronPiece( int index_ ) { - switch( index_ ) + if ( !_scene ) return; + switch ( index_ ) { - case 0: - _scene->paintUnselectedSoma( true ); - _scene->paintUnselectedNeurites( true ); - break; - case 1: - _scene->paintUnselectedSoma( true ); - _scene->paintUnselectedNeurites( false ); - break; - case 2: - _scene->paintUnselectedSoma( false ); - _scene->paintUnselectedNeurites( true ); - break; + case 0: + _scene->paintUnselectedSoma( true ); + _scene->paintUnselectedNeurites( true ); + break; + case 1: + _scene->paintUnselectedSoma( true ); + _scene->paintUnselectedNeurites( false ); + break; + case 2: + _scene->paintUnselectedSoma( false ); + _scene->paintUnselectedNeurites( true ); + break; } update( ); } void OpenGLWidget::changeSelectedNeuronPiece( int index_ ) { - switch( index_ ) + if ( !_scene ) return; + switch ( index_ ) { - case 0: - _scene->paintSelectedSoma( true ); - _scene->paintSelectedNeurites( true ); - break; - case 1: - _scene->paintSelectedSoma( true ); - _scene->paintSelectedNeurites( false ); - break; - case 2: - _scene->paintSelectedSoma( false ); - _scene->paintSelectedNeurites( true ); - break; + case 0: + _scene->paintSelectedSoma( true ); + _scene->paintSelectedNeurites( true ); + break; + case 1: + _scene->paintSelectedSoma( true ); + _scene->paintSelectedNeurites( false ); + break; + case 2: + _scene->paintSelectedSoma( false ); + _scene->paintSelectedNeurites( true ); + break; } update( ); } -void OpenGLWidget::initializeGL( void ) +void OpenGLWidget::initializeGL( ) { initializeOpenGLFunctions( ); - glEnable( GL_DEPTH_TEST ); - glClearColor( 1.0f, 1.0f, 1.0f, 1.0f ); + glClearColor( 1.0f , 1.0f , 1.0f , 1.0f ); glPolygonMode( GL_FRONT_AND_BACK , GL_FILL ); glEnable( GL_CULL_FACE ); @@ -414,35 +349,40 @@ void OpenGLWidget::initializeGL( void ) QOpenGLWidget::initializeGL( ); - const GLubyte* vendor = glGetString(GL_VENDOR); // Returns the vendor - const GLubyte* renderer = glGetString(GL_RENDERER); // Returns a hint to the model - const GLubyte* version = glGetString(GL_VERSION); - const GLubyte* shadingVer = glGetString(GL_SHADING_LANGUAGE_VERSION); + const GLubyte* vendor = glGetString( GL_VENDOR ); // Returns the vendor + const GLubyte* renderer = glGetString( + GL_RENDERER ); // Returns a hint to the model + const GLubyte* version = glGetString( GL_VERSION ); + const GLubyte* shadingVer = glGetString( GL_SHADING_LANGUAGE_VERSION ); - std::cout << "OpenGL Hardware: " << vendor << " (" << renderer << ")" << std::endl; - std::cout << "OpenGL Version: " << version << " (shading ver. " << shadingVer << ")" << std::endl; + std::cout << "OpenGL Hardware: " << vendor << " (" << renderer << ")" + << std::endl; + std::cout << "OpenGL Version: " << version << " (shading ver. " << shadingVer + << ")" << std::endl; nlrender::Config::init( ); - _scene = new neurotessmesh::Scene( _camera ); } -void OpenGLWidget::paintGL( void ) +void OpenGLWidget::paintGL( ) { - makeCurrent( ); glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); - _scene->render( ); + if ( _scene != nullptr ) + { + _scene->update(); + _scene->render( ); + } glUseProgram( 0 ); glFlush( ); #define FRAMES_PAINTED_TO_MEASURE_FPS 10 - if ( _frameCount % FRAMES_PAINTED_TO_MEASURE_FPS == 0 ) + if ( _frameCount % FRAMES_PAINTED_TO_MEASURE_FPS == 0 ) { const auto now = std::chrono::system_clock::now( ); const auto duration = - std::chrono::duration_cast<std::chrono::milliseconds>( now - _then ); + std::chrono::duration_cast< std::chrono::milliseconds >( now - _then ); _then = now; auto mainWindow = dynamic_cast< MainWindow* >( parent( )); @@ -450,9 +390,11 @@ void OpenGLWidget::paintGL( void ) { const unsigned int ellapsedMiliseconds = duration.count( ); - const unsigned int fps = roundf( 1000.0f * - static_cast<float>( FRAMES_PAINTED_TO_MEASURE_FPS ) / - static_cast<float>( ellapsedMiliseconds )); + const auto fps = static_cast<unsigned int>(roundf( + 1000.0f * + static_cast<float>( FRAMES_PAINTED_TO_MEASURE_FPS ) / + static_cast<float>( ellapsedMiliseconds )) + ); if ( _showFps ) { @@ -477,13 +419,12 @@ void OpenGLWidget::paintGL( void ) void OpenGLWidget::resizeGL( int width_ , int height_ ) { - _camera->windowSize(width_, height_); - glViewport( 0, 0, width_, height_ ); + _camera->windowSize( width_ , height_ ); + glViewport( 0 , 0 , width_ , height_ ); } void OpenGLWidget::mousePressEvent( QMouseEvent* event_ ) { - if ( event_->button( ) == Qt::LeftButton ) { _rotation = true; @@ -491,49 +432,47 @@ void OpenGLWidget::mousePressEvent( QMouseEvent* event_ ) _mouseY = event_->y( ); } - if ( event_->button( ) == Qt::MidButton ) + if ( event_->button( ) == Qt::MidButton ) { _translation = true; _mouseX = event_->x( ); _mouseY = event_->y( ); } - - update( ); } void OpenGLWidget::mouseReleaseEvent( QMouseEvent* event_ ) { - if ( event_->button( ) == Qt::LeftButton) + if ( event_->button( ) == Qt::LeftButton ) { _rotation = false; } - if ( event_->button( ) == Qt::MidButton ) + if ( event_->button( ) == Qt::MidButton ) { _translation = false; } - - update( ); } void OpenGLWidget::mouseMoveEvent( QMouseEvent* event_ ) { - if( _rotation ) + if ( _rotation ) { - _camera->rotate( Eigen::Vector3f{ -( _mouseX - event_->x( )) * _rotationScale, - -( _mouseY - event_->y( )) * _rotationScale, - 0.f } ); + _camera->rotate( + Eigen::Vector3f{ -( _mouseX - event_->x( )) * _rotationScale , + -( _mouseY - event_->y( )) * _rotationScale , + 0.f } ); - _mouseX = event_->x( ); - _mouseY = event_->y( ); } - if( _translation ) + _mouseX = event_->x( ); + _mouseY = event_->y( ); + } + if ( _translation ) { - float xDis = ( event_->x() - _mouseX ) * _translationScale; - float yDis = ( event_->y() - _mouseY ) * _translationScale; + float xDis = ( event_->x( ) - _mouseX ) * _translationScale; + float yDis = ( event_->y( ) - _mouseY ) * _translationScale; - _camera->translate( Eigen::Vector3f( -xDis, yDis, 0.0f )); - _mouseX = event_->x( ); - _mouseY = event_->y( ); + _camera->translate( Eigen::Vector3f( -xDis , yDis , 0.0f )); + _mouseX = event_->x( ); + _mouseY = event_->y( ); } this->update( ); @@ -541,7 +480,6 @@ void OpenGLWidget::mouseMoveEvent( QMouseEvent* event_ ) void OpenGLWidget::wheelEvent( QWheelEvent* event_ ) { - int delta = event_->angleDelta( ).y( ); if ( delta > 0 ) @@ -550,7 +488,6 @@ void OpenGLWidget::wheelEvent( QWheelEvent* event_ ) _camera->radius( _camera->radius( ) * 1.1f ); update( ); - } void OpenGLWidget::keyPressEvent( QKeyEvent* event_ ) @@ -559,15 +496,34 @@ void OpenGLWidget::keyPressEvent( QKeyEvent* event_ ) switch ( event_->key( )) { - case Qt::Key_C: - _camera->position( Eigen::Vector3f( 0.f, 0.f, 0.f )); - _camera->radius( 1000.0f ); - _camera->rotation( Eigen::Vector3f{0.f, 0.f,0.f} ); - update( ); - break; + case Qt::Key_C: + _camera->position( Eigen::Vector3f( 0.f , 0.f , 0.f )); + _camera->radius( 1000.0f ); + _camera->rotation( Eigen::Vector3f{ 0.f , 0.f , 0.f } ); + update( ); + break; } } +void OpenGLWidget::changeClearColor( QColor qColor ) +{ + makeCurrent( ); + glClearColor( qColor.redF( ) , qColor.greenF( ) , qColor.blueF( ) , 1.0f ); + update( ); +} + +void OpenGLWidget::changeNeuronColor( QColor qColor ) +{ + _scene->unselectedNeuronColor( qColor ); + update( ); +} + +void OpenGLWidget::changeSelectedNeuronColor( QColor qColor ) +{ + _scene->selectedNeuronColor( qColor ); + update( ); +} + #ifdef NEUROTESSMESH_USE_LEXIS void OpenGLWidget::_onSelectionEvent( lexis::data::ConstSelectedIDsPtr selectedIndices_ ) @@ -586,4 +542,5 @@ void OpenGLWidget::_onFocusEvent( _scene->focusOnIndices( focusedIndices ); update( ); } + #endif diff --git a/neurotessmesh/OpenGLWidget.h b/neurotessmesh/OpenGLWidget.h index 252459adf739c34042fd779134e4742b1d72a9d0..c86e3a7f831abb81083872a41189284d3a6cc627 100644 --- a/neurotessmesh/OpenGLWidget.h +++ b/neurotessmesh/OpenGLWidget.h @@ -16,10 +16,12 @@ #include <QLabel> #include <QTimer> #include <QColor> -#include <chrono> -#include <reto/reto.h> -#include "Scene.h" +#include <Eigen/Eigen> + +#include <chrono> +#include <memory> +#include <iostream> #ifdef NEUROTESSMESH_USE_LEXIS #include <zeroeq/zeroeq.h> @@ -30,9 +32,25 @@ #endif #endif -struct streamDotSeparator: std::numpunct<char> +namespace neurotessmesh +{ + class Scene; +} + +namespace reto +{ + class OrbitalCameraController; +} + +namespace nsol +{ + class DataSet; +} + +struct streamDotSeparator : std::numpunct< char > { - char do_decimal_point() const { return '.'; } + char do_decimal_point( ) const override + { return '.'; } }; /** \class CameraPosition @@ -41,144 +59,143 @@ struct streamDotSeparator: std::numpunct<char> */ class CameraPosition { - public: - Eigen::Vector3f position; /** position point. */ - Eigen::Matrix3f rotation; /** rotation matrix. */ - float radius; /** aperture. */ - - /** \brief CameraPosition class constructor. - * - */ - CameraPosition() - : position{Eigen::Vector3f()} - , rotation{Eigen::Matrix3f::Zero()} - , radius{0} - {}; - - /** \brief CameraPosition class constructor. - * \param[in] data Camera position serialized data. - * - */ - CameraPosition(const QString &data) - { - const auto separator = std::use_facet<std::numpunct<char>>(std::cout.getloc()).decimal_point(); - const bool needSubst = (separator == ','); - - auto parts = data.split(";"); - Q_ASSERT(parts.size() == 3); - const auto posData = parts.first(); - const auto rotData = parts.last(); - auto radiusData = parts.at(1); - - auto posParts = posData.split(","); - Q_ASSERT(posParts.size() == 3); - auto rotParts = rotData.split(","); - Q_ASSERT(rotParts.size() == 9); - - if(needSubst) - { - for(auto &part: posParts) part.replace('.', ','); - for(auto &part: rotParts) part.replace('.', ','); - radiusData.replace('.', ','); - } - - position = Eigen::Vector3f(posParts[0].toFloat(), posParts[1].toFloat(), posParts[2].toFloat()); - radius = radiusData.toFloat(); - rotation.block<1,3>(0,0) = Eigen::Vector3f{rotParts[0].toFloat(), rotParts[1].toFloat(), rotParts[2].toFloat()}; - rotation.block<1,3>(1,0) = Eigen::Vector3f{rotParts[3].toFloat(), rotParts[4].toFloat(), rotParts[5].toFloat()}; - rotation.block<1,3>(2,0) = Eigen::Vector3f{rotParts[6].toFloat(), rotParts[7].toFloat(), rotParts[8].toFloat()}; - } +public: + Eigen::Vector3f position; /** position point. */ + Eigen::Matrix3f rotation; /** rotation matrix. */ + float radius; /** aperture. */ - /** \brief Returns the serialized camera position. - * - */ - QString toString() const + /** \brief CameraPosition class constructor. + * + */ + CameraPosition( ) + : position{ Eigen::Vector3f( ) } + , rotation{ Eigen::Matrix3f::Zero( ) } + , radius{ 0 } + { }; + + /** \brief CameraPosition class constructor. + * \param[in] data Camera position serialized data. + * + */ + explicit CameraPosition( const QString& data ) + { + const auto separator = std::use_facet< std::numpunct< char>>( + std::cout.getloc( )).decimal_point( ); + const bool needSubst = ( separator == ',' ); + + auto parts = data.split( ";" ); + Q_ASSERT( parts.size( ) == 3 ); + const auto posData = parts.first( ); + const auto rotData = parts.last( ); + auto radiusData = parts.at( 1 ); + + auto posParts = posData.split( "," ); + Q_ASSERT( posParts.size( ) == 3 ); + auto rotParts = rotData.split( "," ); + Q_ASSERT( rotParts.size( ) == 9 ); + + if ( needSubst ) { - std::stringstream stream; - stream.imbue(std::locale(stream.getloc(), new streamDotSeparator())); - stream << position << ";" << radius << ";" - << rotation(0,0) << "," << rotation(0,1) << "," << rotation(0,2) << "," - << rotation(1,0) << "," << rotation(1,1) << "," << rotation(1,2) << "," - << rotation(2,0) << "," << rotation(2,1) << "," << rotation(2,2); - - auto serialization = QString::fromStdString(stream.str()); - serialization.replace('\n',',').remove(' '); - - return serialization; + for ( auto& part: posParts ) part.replace( '.' , ',' ); + for ( auto& part: rotParts ) part.replace( '.' , ',' ); + radiusData.replace( '.' , ',' ); } + + position = Eigen::Vector3f( posParts[ 0 ].toFloat( ) , + posParts[ 1 ].toFloat( ) , + posParts[ 2 ].toFloat( )); + radius = radiusData.toFloat( ); + rotation.block< 1 , 3 >( 0 , 0 ) = Eigen::Vector3f{ + rotParts[ 0 ].toFloat( ) , rotParts[ 1 ].toFloat( ) , + rotParts[ 2 ].toFloat( ) }; + rotation.block< 1 , 3 >( 1 , 0 ) = Eigen::Vector3f{ + rotParts[ 3 ].toFloat( ) , rotParts[ 4 ].toFloat( ) , + rotParts[ 5 ].toFloat( ) }; + rotation.block< 1 , 3 >( 2 , 0 ) = Eigen::Vector3f{ + rotParts[ 6 ].toFloat( ) , rotParts[ 7 ].toFloat( ) , + rotParts[ 8 ].toFloat( ) }; + } + + /** \brief Returns the serialized camera position. + * + */ + QString toString( ) const + { + std::stringstream stream; + stream.imbue( std::locale( stream.getloc( ) , new streamDotSeparator( ))); + stream << position << ";" << radius << ";" + << rotation( 0 , 0 ) << "," << rotation( 0 , 1 ) << "," + << rotation( 0 , 2 ) << "," + << rotation( 1 , 0 ) << "," << rotation( 1 , 1 ) << "," + << rotation( 1 , 2 ) << "," + << rotation( 2 , 0 ) << "," << rotation( 2 , 1 ) << "," + << rotation( 2 , 2 ); + + auto serialization = QString::fromStdString( stream.str( )); + serialization.replace( '\n' , ',' ).remove( ' ' ); + + return serialization; + } }; class OpenGLWidget - : public QOpenGLWidget - , public QOpenGLFunctions + : public QOpenGLWidget , public QOpenGLFunctions { - Q_OBJECT; +Q_OBJECT; public: - OpenGLWidget( QWidget* parent_ = nullptr, - Qt::WindowFlags windowFlags_ = Qt::WindowFlags() ); + explicit OpenGLWidget( QWidget* parent_ = nullptr , + Qt::WindowFlags windowFlags_ = Qt::WindowFlags( )); - ~OpenGLWidget( void ); + ~OpenGLWidget( ) override; - /** \brief Loads a dataset with the given parameters. - * \param[in] fileName_ Dataset filename. - * \param[in] fileType_ Dataset type. - * \param[in] target_ BlueConfig target if Blueconfig or empty otherwise. - * - */ - bool loadData( const std::string& fileName_, - const neurotessmesh::Scene::TDataFileType fileType_ = - neurotessmesh::Scene::TDataFileType::BlueConfig, - const std::string& target_ = std::string( "" )); + void setScene( std::shared_ptr< neurotessmesh::Scene > scene ); void idleUpdate( bool idleUpdate_ = true ); - const std::vector< unsigned int > neuronIdList( void ) const; - - void neuronToEdit( const unsigned int id_ ); - - unsigned int numNeuritesToEdit( void ) const; - - void regenerateNeuronToEdit( const float alphaRadius_, - const std::vector< float >& alphaNeurites_ ); - - void home( void ); + void home( ); void setZeqSession( const std::string& session_ ); - /** \brief Returns the current camera position. - * - */ - CameraPosition cameraPosition() const; + reto::OrbitalCameraController* getCamera( ) const; - /** \brief Moves the camera to the given position. - * \param[in] pos CameraPosition reference. + /** \brief Returns the current camera position. * */ - void setCameraPosition(const CameraPosition &pos); + CameraPosition cameraPosition( ) const; public slots: - void changeClearColor( QColor color ); - void changeNeuronColor( QColor color ); - void changeSelectedNeuronColor( QColor color ); - void toggleUpdateOnIdle( void ); - void toggleShowFPS( void ); - void toggleWireframe( void ); - void timerUpdate( void ); - void extractEditNeuronMesh( void ); + void toggleUpdateOnIdle( ); + + void toggleShowFPS( ); + + void toggleWireframe( ); + + void timerUpdate( ); + + void extractEditNeuronMesh( ); void onLotValueChanged( int value_ ); + void onDistanceValueChanged( int value_ ); - void onHomogeneousClicked( void ); - void onLinearClicked( void ); + void onHomogeneousClicked( ); + + void onLinearClicked( ); void changeNeuronPiece( int index_ ); + void changeSelectedNeuronPiece( int index_ ); + void changeClearColor( QColor ); + + void changeNeuronColor( QColor ); + + void changeSelectedNeuronColor( QColor ); + protected: #ifdef NEUROTESSMESH_USE_LEXIS @@ -190,21 +207,27 @@ protected: #endif - virtual void initializeGL( void ); - virtual void paintGL( void ); - virtual void resizeGL( int width_, int height_ ); + void initializeGL( ) override; + + void paintGL( ) override; - virtual void mousePressEvent( QMouseEvent* event_ ); - virtual void mouseReleaseEvent( QMouseEvent* event_ ); - virtual void wheelEvent( QWheelEvent* event_ ); - virtual void mouseMoveEvent( QMouseEvent* event_ ); - virtual void keyPressEvent( QKeyEvent* event_ ); + void resizeGL( int width_ , int height_ ) override; + void mousePressEvent( QMouseEvent* event_ ) override; + + void mouseReleaseEvent( QMouseEvent* event_ ) override; + + void wheelEvent( QWheelEvent* event_ ) override; + + void mouseMoveEvent( QMouseEvent* event_ ) override; + + void keyPressEvent( QKeyEvent* event_ ) override; + + std::shared_ptr< neurotessmesh::Scene > _scene; reto::OrbitalCameraController* _camera; - neurotessmesh::Scene* _scene; - int _mouseX, _mouseY; - bool _rotation, _translation; + int _mouseX , _mouseY; + bool _rotation , _translation; float _translationScale; float _rotationScale; @@ -220,12 +243,10 @@ protected: QString _lastSavedFileName; - const static float _colorFactor; - #ifdef NEUROTESSMESH_USE_LEXIS - zeroeq::Subscriber* _subscriber; + zeroeq::Subscriber* _subscriber; - std::thread* _subscriberThread; + std::thread* _subscriberThread; #endif }; diff --git a/neurotessmesh/Scene.cpp b/neurotessmesh/Scene.cpp index ce2d386eb8a99aedf694c6437d68dcd4c31a81a4..66ce7f645a3592eb62b26699107537c0c2a3db46 100644 --- a/neurotessmesh/Scene.cpp +++ b/neurotessmesh/Scene.cpp @@ -21,37 +21,64 @@ */ #include "Scene.h" +#include <QColor> +#include <QDebug> +#include <utility> #include <nlgenerator/nlgenerator.h> +constexpr float COLOR_FACTOR = 1 / 255.0f; + namespace neurotessmesh { - - Scene::Scene( reto::OrbitalCameraController* camera_ ) + Scene::Scene( reto::OrbitalCameraController* camera , + nsol::DataSet* dataset +#ifdef NEUROTESSMESH_USE_SIMIL + , simil::SpikesPlayer* player +#endif + ) : _mode( VISUALIZATION ) - , _camera( camera_ ) - , _animation{nullptr} - , _unselectedColor( 0.5f, 0.5f, 0.8f ) - , _selectedColor( 0.8f, 0.5f, 0.5f ) + , _camera( camera ) + , _animation( nullptr ) + , _renderer( new nlrender::Renderer( )) + , _unselectedColor( 0.5f , 0.5f , 0.8f ) + , _selectedColor( 0.8f , 0.5f , 0.5f ) + , _dataSet( dataset ) +#ifdef NEUROTESSMESH_USE_SIMIL + , _simulationPlayer( player ) +#endif , _paintUnselectedSoma( true ) , _paintUnselectedNeurites( true ) , _paintSelectedSoma( true ) , _paintSelectedNeurites( true ) , _editNeuron( nullptr ) , _editMesh( nullptr ) - , _boundingBox( Eigen::Vector3f::Zero( ), Eigen::Vector3f::Zero( )) + , _boundingBox( Eigen::Vector3f::Zero( ) , Eigen::Vector3f::Zero( )) + , _activationTimestamps( ) + , _gradient( {{ 0.0f , Eigen::Vector3f{ 1.0f , 0.0f , 0.0f }} , + { 1.0f , Eigen::Vector3f{ 0.0f , 0.0f , 1.0f }}} ) + , _delay( 20.0f ) { _attribsFormat.resize( 3 ); - _attribsFormat[0] = nlgeometry::TAttribType::POSITION; - _attribsFormat[1] = nlgeometry::TAttribType::CENTER; - _attribsFormat[2] = nlgeometry::TAttribType::TANGENT; - _renderer = new nlrender::Renderer( ); - _dataSet = new nsol::DataSet( ); + _attribsFormat[ 0 ] = nlgeometry::TAttribType::POSITION; + _attribsFormat[ 1 ] = nlgeometry::TAttribType::CENTER; + _attribsFormat[ 2 ] = nlgeometry::TAttribType::TANGENT; + + generateMeshes( ); + _boundingBox = computeBoundingBox( ); + _camera->position( _boundingBox.center( )); + _camera->radius( + _boundingBox.radius( ) / std::sin( _camera->camera( )->fieldOfView( ))); + conformRenderTuples( ); } - Scene::~Scene( void ) + Scene::~Scene( ) { - if(_renderer) delete _renderer; - if(_dataSet) delete _dataSet; + delete _renderer; + if ( _dataSet ) + { + _dataSet->close( ); + } + delete _dataSet; } void Scene::mode( const Scene::TSceneMode mode_ ) @@ -59,42 +86,96 @@ namespace neurotessmesh _mode = mode_; } - Scene::TSceneMode Scene::mode( void ) const + Scene::TSceneMode Scene::mode( ) const { return _mode; } - void Scene::render( void ) + void Scene::update( ) { - Eigen::Matrix4f projection( _camera->camera()->projectionMatrix( )); - _renderer->projectionMatrix( ) = projection; - Eigen::Matrix4f view( _camera->camera()->viewMatrix( )); - _renderer->viewMatrix( ) = view; +#ifdef NEUROTESSMESH_USE_SIMIL + static float timeStamp = -1; - switch( _mode ) + if ( _simulationPlayer != nullptr && _simulationPlayer->isPlaying( )) { - case VISUALIZATION: - _renderer->render( std::get<0>( _unselectedNeurons ), - std::get<1>( _unselectedNeurons ), - _unselectedColor , true, _paintUnselectedSoma, - _paintUnselectedNeurites ); - _renderer->render( std::get<0>( _selectedNeurons ), - std::get<1>( _selectedNeurons ), - _selectedColor , true, _paintSelectedSoma, - _paintSelectedNeurites ); - break; - case EDITION: - if ( isEditNeuronMeshExtraction( )) + const auto currentTime = _simulationPlayer->currentTime(); + + if(currentTime - timeStamp > std::numeric_limits<float>::epsilon()) { - _renderer->render( _editMesh, _editNeuron->transform( ), - _unselectedColor, true, - _paintUnselectedSoma, _paintUnselectedNeurites ); + timeStamp = currentTime; + + auto spikes = _simulationPlayer->spikesNow( ); + + for ( auto spike = spikes.first; spike != spikes.second; ++spike ) + { + auto neuronIt = _dataSet->neurons( ).find( spike->second ); + if ( neuronIt == _dataSet->neurons( ).end( )) continue; + auto morphIt = _neuronMeshes.find( neuronIt->second->morphology( )); + if ( morphIt == _neuronMeshes.end( )) continue; + + _activationTimestamps[ morphIt->second ] = spike->first; + } } - break; + } +#endif + } + + void Scene::render( ) + { + Eigen::Matrix4f projection( _camera->camera( )->projectionMatrix( )); + _renderer->projectionMatrix( ) = projection; + Eigen::Matrix4f view( _camera->camera( )->viewMatrix( )); + _renderer->viewMatrix( ) = view; + + switch ( _mode ) + { + case VISUALIZATION: +#ifdef NEUROTESSMESH_USE_SIMIL + if ( _simulationPlayer != nullptr ) + { + const auto timeStamp = _simulationPlayer->currentTime(); + _renderer->render( std::get< 0 >( _unselectedNeurons ) , + std::get< 1 >( _unselectedNeurons ) , + _unselectedColor, + calculateUnselectedColors(timeStamp) , true , + _paintUnselectedSoma , + _paintUnselectedNeurites ); + } + else + { + _renderer->render( std::get< 0 >( _unselectedNeurons ) , + std::get< 1 >( _unselectedNeurons ) , + _unselectedColor, true , + _paintUnselectedSoma , + _paintUnselectedNeurites ); + } +#else + _renderer->render( std::get< 0 >( _unselectedNeurons ) , + std::get< 1 >( _unselectedNeurons ) , + _unselectedColor, true , + _paintUnselectedSoma , + _paintUnselectedNeurites ); +#endif + + _renderer->render( std::get< 0 >( _selectedNeurons ) , + std::get< 1 >( _selectedNeurons ) , + _selectedColor , true , _paintSelectedSoma , + _paintSelectedNeurites ); + break; + case EDITION: + if ( isEditNeuronMeshExtraction( )) + { + _renderer->render( _editMesh , _editNeuron->transform( ) , + _unselectedColor , true , + _paintUnselectedSoma , _paintUnselectedNeurites ); + } + break; + default: + assert( false ); } } - void Scene::close( void ) + void Scene::close( ) { _editNeuron = nullptr; _editMesh = nullptr; @@ -102,35 +183,44 @@ namespace neurotessmesh delete neuronMesh.second; _neuronMeshes.clear( ); - std::get<0>( _unselectedNeurons ).clear( ); - std::get<1>( _unselectedNeurons ).clear( ); - std::get<0>( _selectedNeurons ).clear( ); - std::get<1>( _selectedNeurons ).clear( ); + std::get< 0 >( _unselectedNeurons ).clear( ); + std::get< 1 >( _unselectedNeurons ).clear( ); + std::get< 0 >( _selectedNeurons ).clear( ); + std::get< 1 >( _selectedNeurons ).clear( ); _dataSet->close( ); +#ifdef NEUROTESSMESH_USE_SIMIL + if ( _simulationPlayer ) + { + delete _simulationPlayer; + _simulationPlayer = nullptr; + } +#endif mode( Scene::VISUALIZATION ); } - void Scene::home( void ) + void Scene::home( ) { mode( Scene::VISUALIZATION ); _editNeuron = nullptr; _editMesh = nullptr; - const float FOV = sin( _camera->camera()->fieldOfView() ); - const auto position = _boundingBox.center(); + const float FOV = std::sin( _camera->camera( )->fieldOfView( )); + const auto position = _boundingBox.center( ); const auto radius = _boundingBox.radius( ) / FOV; - animateCamera(position, radius); + animateCamera( position , radius ); } - void Scene::cameraPosition(const Eigen::Vector3f &position, const float radius, const Eigen::Matrix3f &rotation) + void + Scene::cameraPosition( const Eigen::Vector3f& position , const float radius , + const Eigen::Matrix3f& rotation ) { - animateCamera(position, radius, rotation, true); + animateCamera( position , radius , rotation , true ); } nlgeometry::AxisAlignedBoundingBox Scene::computeBoundingBox( - std::vector< unsigned int > indices_ ) + const std::vector< unsigned int >& indices_ ) { Eigen::Array3f minimum = Eigen::Array3f::Constant( std::numeric_limits< float >::max( )); @@ -149,20 +239,23 @@ namespace neurotessmesh auto radius = morphology->soma( )->maxRadius( ); auto center = morphology->soma( )->center( ); Eigen::Vector4f position = neuron->transform( ) * - nsol::Vec4f( center.x( ) , center.y( ), center.z( ), 1.0f ); - Eigen::Array3f minVec( position.x( ) - radius, position.y( ) - radius, + nsol::Vec4f( center.x( ) , center.y( ) , + center.z( ) , 1.0f ); + Eigen::Array3f minVec( position.x( ) - radius , + position.y( ) - radius , position.z( ) - radius ); - Eigen::Array3f maxVec( position.x( ) + radius, position.y( ) + radius, + Eigen::Array3f maxVec( position.x( ) + radius , + position.y( ) + radius , position.z( ) + radius ); minimum = minimum.min( minVec ); maximum = maximum.max( maxVec ); } } } - return nlgeometry::AxisAlignedBoundingBox( minimum, maximum ); + return { minimum , maximum }; } - nlgeometry::AxisAlignedBoundingBox Scene::computeBoundingBox( void ) + nlgeometry::AxisAlignedBoundingBox Scene::computeBoundingBox( ) { std::vector< unsigned int > indices; for ( const auto& neuronIt: _dataSet->neurons( )) @@ -172,113 +265,33 @@ namespace neurotessmesh return computeBoundingBox( indices ); } - void Scene::generateMeshes( void ) + void Scene::generateMeshes( ) { for ( auto neuronIt: _dataSet->neurons( )) { auto morphology = neuronIt.second->morphology( ); - if(morphology) + if ( morphology ) { if ( _neuronMeshes.find( morphology ) == _neuronMeshes.end( )) { auto simplifier = nsol::Simplifier::Instance( ); simplifier->adaptSoma( morphology ); - simplifier->simplify( morphology, nsol::Simplifier::DIST_NODES_RADIUS ); + simplifier->simplify( morphology , + nsol::Simplifier::DIST_NODES_RADIUS ); auto mesh = nlgenerator::MeshGenerator::generateMesh( morphology ); - mesh->uploadGPU( _attribsFormat, nlgeometry::Facet::PATCHES ); + mesh->uploadGPU( _attribsFormat , nlgeometry::Facet::PATCHES ); mesh->clearCPUData( ); _neuronMeshes[ morphology ] = mesh; } } else { - throw std::runtime_error("Unable to load neuron morphology"); + throw std::runtime_error( "Unable to load neuron morphology" ); } } } - std::string Scene::loadData( const std::string& fileName_, - const TDataFileType fileType_, -#ifdef NSOL_USE_BRION - const std::string& target_ -#else - const std::string& /*target_*/ -#endif - ) - { - close( ); - std::string errorString; - try{ - switch( fileType_ ) - { - case TDataFileType::BlueConfig: -#ifdef NSOL_USE_BRION - _dataSet->loadBlueConfigHierarchy< nsol::Node, - nsol::NeuronMorphologySection, - nsol::Dendrite, - nsol::Axon, - nsol::Soma, - nsol::NeuronMorphology, - nsol::Neuron, - nsol::MiniColumn, - nsol::Column >( fileName_, - target_ ); - - _dataSet->loadAllMorphologies< nsol::Node, - nsol::NeuronMorphologySection, - nsol::Dendrite, - nsol::Axon, - nsol::Soma, - nsol::NeuronMorphology, - nsol::Neuron, - nsol::MiniColumn, - nsol::Column >( ); -#else - std::cerr << "Error: Brion support not built-in" << std::endl; -#endif - break; - - case TDataFileType::SWC: - _dataSet->loadNeuronFromFile< nsol::Node, - nsol::NeuronMorphologySection, - nsol::Dendrite, - nsol::Axon, - nsol::Soma, - nsol::NeuronMorphology, - nsol::Neuron >( fileName_, 1 ); - break; - - case TDataFileType::NsolScene: - _dataSet->loadXmlScene< nsol::Node, - nsol::NeuronMorphologySection, - nsol::Dendrite, - nsol::Axon, - nsol::Soma, - nsol::NeuronMorphology, - nsol::Neuron >( fileName_ ); - break; - - default: - throw std::runtime_error( "Data file type not supported" ); - } - - generateMeshes( ); - _boundingBox = computeBoundingBox( ); - _camera->position( _boundingBox.center( )); - _camera->radius( _boundingBox.radius( ) / sin( _camera->camera()->fieldOfView())); - conformRenderTuples( ); - } - catch( const std::exception &excep ) - { - std::cerr << "Error: can't load file: " << fileName_ << std::endl; - std::cerr << excep.what( ) << std::endl; - errorString = std::string(excep.what()); - } - - return errorString; - } - void Scene::paintUnselectedSoma( bool paint_ ) { _paintUnselectedSoma = paint_; @@ -303,24 +316,45 @@ namespace neurotessmesh void Scene::unselectedNeuronColor( Eigen::Vector3f color_ ) { - _unselectedColor = color_; + _unselectedColor = std::move( color_ ); + + _gradient[1] = { 1.0f , { _unselectedColor[0], _unselectedColor[1], _unselectedColor[2] } }; + } + + void Scene::unselectedNeuronColor( const QColor& color_ ) + { + unselectedNeuronColor( Eigen::Vector3f( + float( color_.red( )) * COLOR_FACTOR , + float( color_.green( )) * COLOR_FACTOR , + float( color_.blue( )) * COLOR_FACTOR + )); } void Scene::selectedNeuronColor( Eigen::Vector3f color_ ) { - _selectedColor = color_; + _selectedColor = std::move( color_ ); + } + + void Scene::selectedNeuronColor( const QColor& color_ ) + { + selectedNeuronColor( Eigen::Vector3f( + float( color_.red( )) * COLOR_FACTOR , + float( color_.green( )) * COLOR_FACTOR , + float( color_.blue( )) * COLOR_FACTOR + )); } void Scene::levelOfDetail( float lod_ ) { - if(_renderer) + if ( _renderer ) _renderer->lod( ) = lod_; } void Scene::maximumDistance( float maximumDistance_ ) { - if(_renderer) - _renderer->maximumDistance( ) = maximumDistance_ * _camera->camera()->farPlane( ); + if ( _renderer ) + _renderer->maximumDistance( ) = + maximumDistance_ * _camera->camera( )->farPlane( ); } void Scene::subdivisionCriteria( @@ -329,7 +363,7 @@ namespace neurotessmesh _renderer->tessCriteria( subdivisionCriteria_ ); } - std::vector< unsigned int > Scene::neuronIndices( void ) + std::vector< unsigned int > Scene::neuronIndices( ) { std::vector< unsigned int > indices; @@ -348,14 +382,15 @@ namespace neurotessmesh _editNeuron = neuronIt->second; if ( _editNeuron ) { - auto meshIt = _neuronMeshes.find( _editNeuron->morphology( ) ); + auto meshIt = _neuronMeshes.find( _editNeuron->morphology( )); if ( meshIt != _neuronMeshes.end( )) { _editMesh = meshIt->second; mode( Scene::EDITION ); - std::vector< unsigned int >indices = { id_ }; + std::vector< unsigned int > indices = { id_ }; auto aabb = computeBoundingBox( indices ); - animateCamera(aabb.center(), aabb.radius( ) / sin( _camera->camera()->fieldOfView())); + animateCamera( aabb.center( ) , aabb.radius( ) / std::sin( + _camera->camera( )->fieldOfView( ))); } else _editNeuron = nullptr; @@ -363,7 +398,7 @@ namespace neurotessmesh } } - unsigned int Scene::numEditMorphologyNeurites( void ) const + unsigned int Scene::numEditMorphologyNeurites( ) const { if ( _editNeuron ) return static_cast<unsigned int>(_editNeuron->morphology( )->neurites( ).size( )); @@ -371,26 +406,26 @@ namespace neurotessmesh } void Scene::regenerateEditNeuronMesh( - const float alphaRadius_, + const float alphaRadius_ , const std::vector< float >& alphaNeurites_ ) { - if ( isEditNeuronMeshExtraction( )) + if ( isEditNeuronMeshExtraction( )) { auto mesh = nlgenerator::MeshGenerator::generateMesh( - _editNeuron->morphology( ), alphaRadius_, alphaNeurites_ ); + _editNeuron->morphology( ) , alphaRadius_ , alphaNeurites_ ); if ( mesh ) { - mesh->uploadGPU( _attribsFormat, nlgeometry::Facet::PATCHES ); + mesh->uploadGPU( _attribsFormat , nlgeometry::Facet::PATCHES ); mesh->clearCPUData( ); delete _editMesh; _editMesh = mesh; - _neuronMeshes[ _editNeuron->morphology( )] = mesh; + _neuronMeshes[ _editNeuron->morphology( ) ] = mesh; conformRenderTuples( ); } } } - bool Scene::isEditNeuronMeshExtraction( void ) + bool Scene::isEditNeuronMeshExtraction( ) { return ( _editNeuron != nullptr ) && ( _editMesh != nullptr ); } @@ -398,13 +433,13 @@ namespace neurotessmesh void Scene::extractEditNeuronMesh( const std::string& path_ ) { auto extractedMesh = _renderer->extract( - _editMesh, _editNeuron->transform( ), _paintUnselectedSoma, + _editMesh , _editNeuron->transform( ) , _paintUnselectedSoma , _paintUnselectedNeurites ); - nlgeometry::ObjWriter::writeMesh( extractedMesh, path_ ); + nlgeometry::ObjWriter::writeMesh( extractedMesh , path_ ); delete extractedMesh; } - void Scene::conformRenderTuples( void ) + void Scene::conformRenderTuples( ) { nlgeometry::Meshes unselectedMeshes; std::vector< Eigen::Matrix4f > unselectedModels; @@ -428,56 +463,118 @@ namespace neurotessmesh } } } - _unselectedNeurons = std::make_tuple( unselectedMeshes, unselectedModels ); - _selectedNeurons = std::make_tuple( selectedMeshes, selectedModels ); + _unselectedNeurons = std::make_tuple( unselectedMeshes , unselectedModels ); + _selectedNeurons = std::make_tuple( selectedMeshes , selectedModels ); } void Scene::changeSelectedIndices( const std::vector< unsigned int >& indices_ ) { _selectedIndices = std::set< unsigned int >( - indices_.begin( ), indices_.end( )); + indices_.begin( ) , indices_.end( )); conformRenderTuples( ); } void Scene::focusOnIndices( const std::vector< unsigned int >& indices_ ) { - if ( indices_.size( ) > 0 ) + if ( !indices_.empty( )) { auto aabb = computeBoundingBox( indices_ ); - animateCamera( aabb.center( ), aabb.radius( ) / sin( _camera->camera()->fieldOfView())); + animateCamera( aabb.center( ) , + aabb.radius( ) / + std::sin( _camera->camera( )->fieldOfView( ))); } else { - animateCamera( _boundingBox.center( ), _boundingBox.radius( ) / sin( _camera->camera()->fieldOfView())); + animateCamera( _boundingBox.center( ) , _boundingBox.radius( ) / std::sin( + _camera->camera( )->fieldOfView( ))); } } - void Scene::animateCamera(const Eigen::Vector3f &position, const float radius, const Eigen::Matrix3f &rotation, - bool rotAnimation) + void + Scene::animateCamera( const Eigen::Vector3f& position , const float radius , + const Eigen::Matrix3f& rotation , + bool rotAnimation ) { - if(_camera->isAniming()) + if ( _camera->isAniming( )) { - _camera->stopAnim(); - if(_animation) delete _animation; + _camera->stopAnim( ); + delete _animation; } constexpr float CAMERA_ANIMATION_DURATION = 2.f; - const auto rotInterpolation = rotAnimation ? reto::CameraAnimation::LINEAR : reto::CameraAnimation::NONE; + const auto rotInterpolation = rotAnimation ? reto::CameraAnimation::LINEAR + : reto::CameraAnimation::NONE; - _animation = new reto::CameraAnimation(reto::CameraAnimation::LINEAR, - rotInterpolation, - reto::CameraAnimation::LINEAR); + _animation = new reto::CameraAnimation( reto::CameraAnimation::LINEAR , + rotInterpolation , + reto::CameraAnimation::LINEAR ); - auto startCam = new reto::KeyCamera(0.f, _camera->position(), - _camera->rotation(), - _camera->radius()); - _animation->addKeyCamera(startCam); + auto startCam = new reto::KeyCamera( 0.f , _camera->position( ) , + _camera->rotation( ) , + _camera->radius( )); + _animation->addKeyCamera( startCam ); - auto targetCam = new reto::KeyCamera(CAMERA_ANIMATION_DURATION, - position, rotation, radius); - _animation->addKeyCamera(targetCam); + auto targetCam = new reto::KeyCamera( CAMERA_ANIMATION_DURATION , + position , rotation , radius ); + _animation->addKeyCamera( targetCam ); - _camera->startAnim(_animation); + _camera->startAnim( _animation ); + } + + std::vector< Eigen::Vector3f > Scene::calculateUnselectedColors( float timestamp ) + { + auto calculateGradientColor = [ ]( const Gradient& gradient , float t ) + { + if ( gradient.empty( )) return Eigen::Vector3f( 1.0f , 0.0f , 1.0f ); + // Return last if t < 0. + if ( t < 0 ) return gradient[ gradient.size( ) - 1 ].second; + int size = static_cast<int>(gradient.size( )); + int first = size - 1; + for ( int i = 0; i < size; i++ ) + { + if ( gradient[ i ].first > t ) + { + first = i - 1; + break; + } + } + + if ( first == -1 ) return gradient[ 0 ].second; + if ( first == size - 1 ) return gradient[ first ].second; + + float start = gradient[ first ].first; + float end = gradient[ first + 1 ].first; + float normalizedT = ( t - start ) / ( end - start ); + + const Eigen::Vector3f mixedColor = (gradient[ first ].second * (1. - normalizedT)) + (gradient[ first + 1 ].second * normalizedT); + return mixedColor; + }; + + auto& neurons = std::get< 0 >( _unselectedNeurons ); + + auto colors = std::vector< Eigen::Vector3f >(neurons.size(), _unselectedColor); + if(timestamp < 0 +#ifdef NEUROTESSMESH_USE_SIMIL + || !_simulationPlayer +#endif + ) return colors; + + uint32_t index = 0; + for ( const auto& mesh: neurons ) + { + auto it = _activationTimestamps.find( mesh ); + if ( it == _activationTimestamps.end( )) + { + colors[ index ] = calculateGradientColor( _gradient , INFINITY); + } + else + { + colors[ index ] = calculateGradientColor( _gradient , + ( timestamp - it->second ) / _delay ); + } + ++index; + } + return colors; } } diff --git a/neurotessmesh/Scene.h b/neurotessmesh/Scene.h index 640c037faaa58fee2f0a528222b35653830f8570..ed64517cdd9069af528646ddf6755cb85bf2b9e8 100644 --- a/neurotessmesh/Scene.h +++ b/neurotessmesh/Scene.h @@ -28,6 +28,14 @@ #include <nlrender/nlrender.h> #include <neurotessmesh/api.h> +#ifdef NEUROTESSMESH_USE_SIMIL + #include <simil/simil.h> +#endif +#include <QPalette> + +class QColor; + +typedef std::vector< std::pair< float , Eigen::Vector3f >> Gradient; namespace neurotessmesh { @@ -40,69 +48,80 @@ namespace neurotessmesh typedef enum { - VISUALIZATION = 0, + VISUALIZATION = 0 , EDITION - }TSceneMode; + } TSceneMode; typedef enum { - BlueConfig, - SWC, + BlueConfig , + SWC , NsolScene } TDataFileType; - typedef std::tuple< nlgeometry::Meshes, std::vector< Eigen::Matrix4f >> + typedef std::tuple< nlgeometry::Meshes , std::vector< Eigen::Matrix4f >> NeuronMeshes; /** * Default constructor */ NEUROTESSMESH_API - Scene( reto::OrbitalCameraController* camera_ ); + explicit Scene( reto::OrbitalCameraController* camera = nullptr, + nsol::DataSet* dataset = nullptr +#ifdef NEUROTESSMESH_USE_SIMIL + , simil::SpikesPlayer* player = nullptr +#endif + ); /** * Default destructor */ NEUROTESSMESH_API - ~Scene( void ); + ~Scene( ); /** * Method to set the scene mode * @param mode_ new scnene mode */ NEUROTESSMESH_API - void mode( const TSceneMode mode_ ); + void mode( TSceneMode mode_ ); /** * Method to get the scene mode * @return the current scene mode */ NEUROTESSMESH_API - TSceneMode mode( void ) const; + TSceneMode mode( ) const; + + /** + * This method updates the scene with the current spikes. + */ + void update(); /** - * Method to renderize the scene based on the current mode + * Method to rendering the scene based on the current mode */ NEUROTESSMESH_API - void render( void ); + void render( ); /** * Method to close and deleted data from dataSet */ NEUROTESSMESH_API - void close( void ); + void close( ); /** * Method to set the scene params to default */ NEUROTESSMESH_API - void home( void ); + void home( ); /** * Method to animate the camera to the given position, radius and rotation */ NEUROTESSMESH_API - void cameraPosition(const Eigen::Vector3f &position, const float radius, const Eigen::Matrix3f &rotation); + void cameraPosition( const Eigen::Vector3f& position , float radius , + const Eigen::Matrix3f& rotation ); /** * Method to compute axis align bounding box @@ -111,32 +130,20 @@ namespace neurotessmesh */ NEUROTESSMESH_API nlgeometry::AxisAlignedBoundingBox - computeBoundingBox( std::vector< unsigned int > indices_ ); + computeBoundingBox( const std::vector< unsigned int >& indices_ ); /** * Method to compute axis align bounding box * @return axis align bounding box for all the current neurons */ NEUROTESSMESH_API - nlgeometry::AxisAlignedBoundingBox computeBoundingBox( void ); + nlgeometry::AxisAlignedBoundingBox computeBoundingBox( ); /** * Method to generate the meshes associated to the loaded neurons */ NEUROTESSMESH_API - void generateMeshes( void ); - - /** - * Method to load neurons data returns an empty string on success - * or an error string otherwise. - * @param fileName path to the file - * @param fileType type of file - * @param target to load, specific param to BlueConfig data - */ - NEUROTESSMESH_API - std::string loadData( const std::string& fileName_, - const TDataFileType fileType_, - const std::string& target_ = std::string( "" )); + void generateMeshes( ); /** * Method to set the render options of unseletected and selected neurons @@ -144,10 +151,13 @@ namespace neurotessmesh */ NEUROTESSMESH_API void paintUnselectedSoma( bool paint_ ); + NEUROTESSMESH_API void paintUnselectedNeurites( bool paint_ ); + NEUROTESSMESH_API void paintSelectedSoma( bool paint_ ); + NEUROTESSMESH_API void paintSelectedNeurites( bool paint_ ); @@ -158,6 +168,13 @@ namespace neurotessmesh NEUROTESSMESH_API void unselectedNeuronColor( Eigen::Vector3f color_ ); + /** + * Method to change the unselected neuron color + * @param color_ color of the unselected neuron + */ + NEUROTESSMESH_API + void unselectedNeuronColor( const QColor& color_ ); + /** * Method to change the selected neuron color * @param color_ color of the selected neuron @@ -165,6 +182,13 @@ namespace neurotessmesh NEUROTESSMESH_API void selectedNeuronColor( Eigen::Vector3f color_ ); + /** + * Method to change the selected neuron color + * @param color_ color of the selected neuron + */ + NEUROTESSMESH_API + void selectedNeuronColor( const QColor& color_ ); + /** * Method to set the scene level of subdivision * @param lod_ scene level of detail @@ -188,32 +212,33 @@ namespace neurotessmesh subdivisionCriteria_ ); NEUROTESSMESH_API - std::vector< unsigned int > neuronIndices( void ); + std::vector< unsigned int > neuronIndices( ); NEUROTESSMESH_API void setNeuronToEdit( unsigned int id_ ); NEUROTESSMESH_API - unsigned int numEditMorphologyNeurites( void ) const; + unsigned int numEditMorphologyNeurites( ) const; NEUROTESSMESH_API - void regenerateEditNeuronMesh( const float alphaRadius, + void regenerateEditNeuronMesh( float alphaRadius , const std::vector< float >& alphaNeurites_ ); NEUROTESSMESH_API - bool isEditNeuronMeshExtraction( void ); + bool isEditNeuronMeshExtraction( ); NEUROTESSMESH_API void extractEditNeuronMesh( const std::string& path_ ); NEUROTESSMESH_API - void conformRenderTuples( void ); + void conformRenderTuples( ); NEUROTESSMESH_API void changeSelectedIndices( const std::vector< unsigned int >& indices_ ); NEUROTESSMESH_API void focusOnIndices( const std::vector< unsigned int >& indices_ ); + protected: /** \brief Animates the camera to the given position, radius and rotation. * \param[in] position Focus position. @@ -222,17 +247,23 @@ namespace neurotessmesh * \param[in] rotAnimation true to animate rotation and false otherwise. * */ - void animateCamera(const Eigen::Vector3f &position, - const float radius, - const Eigen::Matrix3f &rotation = Eigen::Matrix3f::Zero(), - bool rotAnimation = false); + void animateCamera( const Eigen::Vector3f& position , + float radius , + const Eigen::Matrix3f& rotation = Eigen::Matrix3f::Zero( ) , + bool rotAnimation = false ); + + /** \brief Updates the color and time array of the unselected neurons. + * \param[in] timestamp Current player time. + * + */ + std::vector< Eigen::Vector3f > calculateUnselectedColors( float timestamp ); //! Scene mode TSceneMode _mode; //! Scene camera reto::OrbitalCameraController* _camera; - reto::CameraAnimation *_animation; + reto::CameraAnimation* _animation; //! Neurolots engine to render morphological data nlrender::Renderer* _renderer; @@ -249,9 +280,14 @@ namespace neurotessmesh //! Nsol DataSet, contains neurons nsol::DataSet* _dataSet; +#ifdef NEUROTESSMESH_USE_SIMIL + // SimIL spikes data. + simil::SpikesPlayer* _simulationPlayer; +#endif + //! Neuron Meshes - std::unordered_map< nsol::MorphologyPtr, nlgeometry::MeshPtr > - _neuronMeshes; + std::unordered_map< nsol::MorphologyPtr , nlgeometry::MeshPtr > + _neuronMeshes; //! Unselected neuron meshes NeuronMeshes _unselectedNeurons; @@ -276,6 +312,11 @@ namespace neurotessmesh //! Scene bonunding box nlgeometry::AxisAlignedBoundingBox _boundingBox; + + //! Activation timestamps. + std::unordered_map< nlgeometry::MeshPtr , float > _activationTimestamps; + Gradient _gradient; + float _delay; }; } diff --git a/neurotessmesh/mainwindow.ui b/neurotessmesh/mainwindow.ui index 68ed7db7749586dccddf843f88f24f11955a4cc7..9f16aca12530b7ea84b70d02e76bccc2c9493521 100644 --- a/neurotessmesh/mainwindow.ui +++ b/neurotessmesh/mainwindow.ui @@ -59,6 +59,7 @@ <addaction name="actionShowFPSOnIdleUpdate"/> <addaction name="actionWireframe"/> <addaction name="actionRenderOptions"/> + <addaction name="actionSimulation_player_options"/> <addaction name="actionEditSave"/> <addaction name="actionConfiguration"/> </widget> @@ -94,6 +95,7 @@ <addaction name="actionWireframe"/> <addaction name="actionCloseData"/> <addaction name="actionRenderOptions"/> + <addaction name="actionSimulation_player_options"/> <addaction name="separator"/> </widget> <action name="actionQuit"> @@ -351,6 +353,21 @@ <string>Camera positions list.</string> </property> </action> + <action name="actionSimulation_player_options"> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="icon"> + <iconset resource="resources.qrc"> + <normaloff>:/icons/rsc/play.svg</normaloff>:/icons/rsc/play.svg</iconset> + </property> + <property name="text"> + <string>Simulation player options</string> + </property> + <property name="toolTip"> + <string>Shows/hides the player options.</string> + </property> + </action> </widget> <resources> <include location="resources.qrc"/> diff --git a/neurotessmesh/neurotessmesh.cpp b/neurotessmesh/neurotessmesh.cpp index bc39a29a13c02f9230378d15107113b398185062..c191e78d11f7abfb8fda5215b9f2f4a72331a50f 100644 --- a/neurotessmesh/neurotessmesh.cpp +++ b/neurotessmesh/neurotessmesh.cpp @@ -10,13 +10,16 @@ #include <QApplication> #include <QDir> -#include "MainWindow.h" -#include <QDebug> #include <QOpenGLWidget> #include <QErrorMessage> +#include "MainWindow.h" + #include <neurotessmesh/version.h> + +#include <cstring> #include <sstream> +#include <iostream> #include <locale> #define GL_MINIMUM_REQUIRED_MAJOR 4 @@ -25,7 +28,7 @@ bool setFormat( int ctxOpenGLMajor, int ctxOpenGLMinor, int ctxOpenGLSamples, int ctxOpenGLVSync ); -void usageMessage( char* progName ); +void usageMessage(const char* progName); void dumpVersion( void ); bool atLeastTwo( bool a, bool b, bool c ); @@ -41,6 +44,8 @@ int main( int argc, char** argv ) QApplication application(argc,argv); + const auto programName = argv[0]; + std::string blueConfig; std::string swcFile; std::string sceneFile; @@ -61,7 +66,7 @@ int main( int argc, char** argv ) if ( std::strcmp( argv[i], "--help" ) == 0 || std::strcmp( argv[i], "-h" ) == 0 ) { - usageMessage( argv[0] ); + usageMessage(programName); return 0; } if ( std::strcmp( argv[i], "--version" ) == 0 ) @@ -88,7 +93,7 @@ int main( int argc, char** argv ) blueConfig = std::string( argv[ i ]); } else - usageMessage( argv[0] ); + usageMessage(programName); } if( std::strcmp( argv[ i ], "-swc" ) == 0 ) @@ -98,7 +103,7 @@ int main( int argc, char** argv ) swcFile = std::string( argv[ i ]); } else - usageMessage( argv[0] ); + usageMessage(programName); } if( std::strcmp( argv[ i ], "-xml" ) == 0 ) @@ -108,7 +113,7 @@ int main( int argc, char** argv ) sceneFile = std::string( argv[ i ]); } else - usageMessage( argv[0] ); + usageMessage(programName); } if( std::strcmp( argv[ i ], "-target" ) == 0 ) @@ -118,7 +123,7 @@ int main( int argc, char** argv ) target = std::string( argv[ i ]); } else - usageMessage( argv[0] ); + usageMessage(programName); } if ( strcmp( argv[i], "--fullscreen" ) == 0 || @@ -136,7 +141,7 @@ int main( int argc, char** argv ) { initWindowSize = true; if ( i + 2 >= argc ) - usageMessage( argv[0] ); + usageMessage(programName); initWindowWidth = atoi( argv[ ++i ] ); initWindowHeight = atoi( argv[ ++i ] ); @@ -145,7 +150,7 @@ int main( int argc, char** argv ) strcmp( argv[i],"-cv") == 0 ) { if ( i + 2 >= argc ) - usageMessage( argv[0] ); + usageMessage(programName); ctxOpenGLMajor = atoi( argv[ ++i ] ); ctxOpenGLMinor = atoi( argv[ ++i ] ); @@ -154,7 +159,7 @@ int main( int argc, char** argv ) strcmp( argv[i],"-s") == 0 ) { if ( i + 1 >= argc ) - usageMessage( argv[0] ); + usageMessage(programName); ctxOpenGLSamples = atoi( argv[ ++i ] ); } @@ -189,7 +194,7 @@ int main( int argc, char** argv ) { std::cerr << "Error: -swc, -xml and -bc options are exclusive" << std::endl; - usageMessage( argv[0] ); + usageMessage(programName); } if ( !blueConfig.empty() ) @@ -223,7 +228,7 @@ int main( int argc, char** argv ) return returnVal; } -void usageMessage( char* progName ) +void usageMessage(const char* progName) { std::cerr << std::endl << "Usage: " diff --git a/neurotessmesh/resources.qrc b/neurotessmesh/resources.qrc index 2aa7cbf5663bb4c692efc5c3033dcad9ae612463..535db9d3199d79664d5887f074fd72bcf6c4c016 100644 --- a/neurotessmesh/resources.qrc +++ b/neurotessmesh/resources.qrc @@ -14,5 +14,6 @@ <file>rsc/settings.png</file> <file>rsc/neurotessmesh.png</file> <file>rsc/eye.svg</file> + <file>rsc/play.svg</file> </qresource> </RCC> diff --git a/neurotessmesh/rsc/play.svg b/neurotessmesh/rsc/play.svg new file mode 100644 index 0000000000000000000000000000000000000000..35d12e78d473858a7c89724e22696d4047b973e5 --- /dev/null +++ b/neurotessmesh/rsc/play.svg @@ -0,0 +1,229 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="48" + height="48" + id="svg7854" + sodipodi:version="0.32" + inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)" + version="1.0" + sodipodi:docname="play.svg" + inkscape:output_extension="org.inkscape.output.svg.inkscape" + inkscape:export-filename="/home/lapo/Desktop/media-icons.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <defs + id="defs7856"> + <linearGradient + id="linearGradient7577"> + <stop + style="stop-color:#000000;stop-opacity:0.3137255;" + offset="0" + id="stop7579" /> + <stop + style="stop-color:#ffffff;stop-opacity:1" + offset="1" + id="stop7581" /> + </linearGradient> + <linearGradient + id="linearGradient7305"> + <stop + style="stop-color:#ffffff;stop-opacity:1" + offset="0" + id="stop7307" /> + <stop + id="stop7313" + offset="0.20971029" + style="stop-color:#ffffff;stop-opacity:0;" /> + <stop + style="stop-color:#ffffff;stop-opacity:0.81065089;" + offset="0.34936365" + id="stop7329" /> + <stop + id="stop7321" + offset="0.42850056" + style="stop-color:#ffffff;stop-opacity:0;" /> + <stop + style="stop-color:#ffffff;stop-opacity:1;" + offset="0.52134049" + id="stop7323" /> + <stop + id="stop7317" + offset="0.55746967" + style="stop-color:#ffffff;stop-opacity:0;" /> + <stop + style="stop-color:#ffffff;stop-opacity:0.56804734;" + offset="0.71001518" + id="stop7319" /> + <stop + id="stop7416" + offset="0.74394959" + style="stop-color:#ffffff;stop-opacity:0;" /> + <stop + style="stop-color:#ffffff;stop-opacity:0" + offset="1" + id="stop7309" /> + </linearGradient> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#afafaf" + borderopacity="1" + gridtolerance="10000" + guidetolerance="10" + objecttolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:zoom="11.313708" + inkscape:cx="11.71144" + inkscape:cy="30.327794" + inkscape:document-units="px" + inkscape:current-layer="layer1" + width="48px" + height="48px" + inkscape:showpageshadow="false" + inkscape:window-width="1920" + inkscape:window-height="1018" + inkscape:window-x="-8" + inkscape:window-y="-8" + showgrid="false" + inkscape:grid-points="true" + showborder="true" + showguides="false" + inkscape:guide-bbox="true" + borderlayer="true" + inkscape:document-rotation="0" + inkscape:window-maximized="1"> + <sodipodi:guide + orientation="horizontal" + position="13.125" + id="guide7377" /> + <sodipodi:guide + orientation="horizontal" + position="5.4800776" + id="guide7379" /> + <sodipodi:guide + orientation="horizontal" + position="35" + id="guide7492" /> + <sodipodi:guide + orientation="horizontal" + position="48" + id="guide7046" /> + <sodipodi:guide + orientation="horizontal" + position="-17.5" + id="guide7233" /> + <sodipodi:guide + orientation="horizontal" + position="-29" + id="guide7235" /> + <sodipodi:guide + orientation="horizontal" + position="22.097087" + id="guide7556" /> + <sodipodi:guide + orientation="vertical" + position="-76.125" + id="guide7644" /> + <sodipodi:guide + orientation="vertical" + position="-26.125" + id="guide7646" /> + <sodipodi:guide + orientation="vertical" + position="24" + id="guide7648" /> + <sodipodi:guide + orientation="vertical" + position="-125.28125" + id="guide7665" /> + <sodipodi:guide + orientation="vertical" + position="-175.125" + id="guide7667" /> + <sodipodi:guide + orientation="vertical" + position="-225.83223" + id="guide7685" /> + <sodipodi:guide + orientation="vertical" + position="-326.06462" + id="guide7695" /> + <inkscape:grid + id="GridFromPre046Settings" + type="xygrid" + originx="0" + originy="0" + spacingx="0.5" + spacingy="0.5" + color="#3f3fff" + empcolor="#3f3fff" + opacity="0.15" + empopacity="0.38" + empspacing="2" /> + </sodipodi:namedview> + <metadata + id="metadata7859"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:creator> + <cc:Agent> + <dc:title>Lapo Calamandrei</dc:title> + </cc:Agent> + </dc:creator> + <dc:source /> + <cc:license + rdf:resource="http://creativecommons.org/licenses/GPL/2.0/" /> + <dc:title>Play</dc:title> + <dc:subject> + <rdf:Bag> + <rdf:li>play</rdf:li> + <rdf:li>playback</rdf:li> + <rdf:li>start</rdf:li> + <rdf:li>begin</rdf:li> + </rdf:Bag> + </dc:subject> + </cc:Work> + <cc:License + rdf:about="http://creativecommons.org/licenses/GPL/2.0/"> + <cc:permits + rdf:resource="http://web.resource.org/cc/Reproduction" /> + <cc:permits + rdf:resource="http://web.resource.org/cc/Distribution" /> + <cc:requires + rdf:resource="http://web.resource.org/cc/Notice" /> + <cc:permits + rdf:resource="http://web.resource.org/cc/DerivativeWorks" /> + <cc:requires + rdf:resource="http://web.resource.org/cc/ShareAlike" /> + <cc:requires + rdf:resource="http://web.resource.org/cc/SourceCode" /> + </cc:License> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <path + id="path7671" + style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dashoffset:0.7;stroke-opacity:1" + inkscape:original="M -186.5 10.5 L -186.5 37.5 L -163.5 24 L -186.5 10.5 z " + inkscape:radius="1.5026019" + sodipodi:type="inkscape:offset" + d="M -186.52734,8.9980469 A 1.5027521,1.5027521 0 0 0 -188.00195,10.5 v 27 a 1.5027521,1.5027521 0 0 0 2.26172,1.294922 l 23,-13.5 a 1.5027521,1.5027521 0 0 0 0,-2.589844 l -23,-13.4999999 a 1.5027521,1.5027521 0 0 0 -0.78711,-0.2070312 z" + transform="translate(200)" /> + </g> +</svg>