diff --git a/applications/mne_scan/plugins/hpi/hpi.cpp b/applications/mne_scan/plugins/hpi/hpi.cpp index 3eed247158e..60842226a16 100644 --- a/applications/mne_scan/plugins/hpi/hpi.cpp +++ b/applications/mne_scan/plugins/hpi/hpi.cpp @@ -79,7 +79,7 @@ using namespace INVERSELIB; // DEFINE LOCAL CONSTANTS //============================================================================================================= -constexpr const int defaultFittingWindowSize(300); +constexpr const int defaultFittingWindowSize(2000); //============================================================================================================= // DEFINE MEMBER METHODS @@ -87,7 +87,7 @@ constexpr const int defaultFittingWindowSize(300); Hpi::Hpi() : m_iNumberBadChannels(0) -, m_iFittingWindowSize(defaultFittingWindowSize) +, m_iRepetitionTimeInSamples(defaultFittingWindowSize) , m_dAllowedMeanErrorDist(10) , m_dAllowedMovement(3) , m_dAllowedRotation(5) @@ -322,7 +322,9 @@ void Hpi::initPluginControlWidgets() this, &Hpi::onAllowedMovementChanged); connect(pHpiSettingsView, &HpiSettingsView::allowedRotationChanged, this, &Hpi::onAllowedRotationChanged); - connect(pHpiSettingsView, &HpiSettingsView::fittingWindowSizeChanged, + connect(pHpiSettingsView, &HpiSettingsView::repetitionTimeChanged, + this, &Hpi::setTimeBetweenFits); + connect(pHpiSettingsView, &HpiSettingsView::fittingWindowTimeChanged, this, &Hpi::setFittingWindowSize); connect(this, &Hpi::errorsChanged, pHpiSettingsView, &HpiSettingsView::setErrorLabels, Qt::BlockingQueuedConnection); @@ -330,14 +332,15 @@ void Hpi::initPluginControlWidgets() pHpiSettingsView, &HpiSettingsView::setMovementResults, Qt::BlockingQueuedConnection); connect(this, &Hpi::newDigitizerList, pHpiSettingsView, &HpiSettingsView::newDigitizerList); - + connect(this, &Hpi::minimumWindowSizeChanged, + pHpiSettingsView, &HpiSettingsView::setMinimumWindowSize); onSspStatusChanged(pHpiSettingsView->getSspStatusChanged()); onCompStatusChanged(pHpiSettingsView->getCompStatusChanged()); onAllowedMeanErrorDistChanged(pHpiSettingsView->getAllowedMeanErrorDistChanged()); onAllowedMovementChanged(pHpiSettingsView->getAllowedMovementChanged()); onAllowedRotationChanged(pHpiSettingsView->getAllowedRotationChanged()); - setFittingWindowSize(pHpiSettingsView->getFittingWindowSize()); + setFittingWindowSize(pHpiSettingsView->getFittingRepetitionTime()); onContHpiStatusChanged(pHpiSettingsView->continuousHPIChecked()); plControlWidgets.append(pHpiSettingsView); @@ -481,6 +484,12 @@ void Hpi::onCoilFrequenciesChanged(const QVector& vCoilFreqs) m_mutex.lock(); m_vCoilFreqs = vCoilFreqs; m_mutex.unlock(); + + const int iMinimalWindowSize = computeMinimalWindowsize(); + + m_mutex.lock(); + m_iFittingWindowSize = iMinimalWindowSize; + m_mutex.unlock(); } //============================================================================================================= @@ -515,10 +524,18 @@ void Hpi::onContHpiStatusChanged(bool bChecked) //============================================================================================================= -void Hpi::setFittingWindowSize(int winSize) +void Hpi::setTimeBetweenFits(double dRepetitionTime) { QMutexLocker locker(&m_mutex); - m_iFittingWindowSize = winSize; + m_iRepetitionTimeInSamples = dRepetitionTime * m_pFiffInfo->sfreq; +} + +//============================================================================================================= + +void Hpi::setFittingWindowSize(double dFittingWindowSizeInMillisecons) +{ + QMutexLocker locker(&m_mutex); + m_iFittingWindowSize = dFittingWindowSizeInMillisecons / 1000.0 * m_pFiffInfo->sfreq; } //============================================================================================================= @@ -530,6 +547,45 @@ void Hpi::onDevHeadTransAvailable(const FIFFLIB::FiffCoordTrans& devHeadTrans) //============================================================================================================= +int Hpi::computeMinimalWindowsize() +{ + m_mutex.lock(); + double dSFreq = m_pFiffInfo->sfreq; + double dLineFreq = m_pFiffInfo->linefreq; + QVector vecFreqs = m_vCoilFreqs; + m_mutex.unlock(); + + // get number of harmonics to take into account + int iMaxHpiFreq = *std::max_element(vecFreqs.constBegin(), vecFreqs.constEnd()); + int nHarmonics = ceil(iMaxHpiFreq/dLineFreq); + + // append frequency vector + for(int i = 1; i <= nHarmonics; i++) { + vecFreqs << dLineFreq * i; + } + + // sort vector in increasing order + std::sort(vecFreqs.begin(), vecFreqs.end()); + + // get minimimal required frequency difference + int iMinDeltaF = dSFreq; + + for(int i = 0; i < vecFreqs.size() - 1; i++) { + int iSubsequentDeltaF = vecFreqs[i+1] - vecFreqs[i]; + if(iSubsequentDeltaF< iMinDeltaF) { + iMinDeltaF = iSubsequentDeltaF; + } + } + + // compute buffersize needed to provide this resolution in frequency space N = FS/df + int iWindowSize = ceil(dSFreq/iMinDeltaF); + emit minimumWindowSizeChanged(iWindowSize/dSFreq); + + return iWindowSize; +} + +//============================================================================================================= + void Hpi::run() { // Wait for fiff info @@ -571,31 +627,46 @@ void Hpi::run() double dMovement = 0.0; double dRotation = 0.0; + int iRepetitionIndexCounter = 0; int iDataIndexCounter = 0; MatrixXd matData; m_mutex.lock(); - int fittingWindowSize = m_iFittingWindowSize; + int iFittingWindowSize = m_iFittingWindowSize; + int iRepetitionTimeInSamples = m_iRepetitionTimeInSamples; + m_mutex.unlock(); - MatrixXd matDataMerged(m_pFiffInfo->chs.size(), fittingWindowSize); + MatrixXd matDataMerged(m_pFiffInfo->chs.size(), iFittingWindowSize); bool bOrder = false; + QElapsedTimer timer; + timer.start(); while(!isInterruptionRequested()) { m_mutex.lock(); - if(fittingWindowSize != m_iFittingWindowSize) { - fittingWindowSize = m_iFittingWindowSize; - std::cout << "Fitting window size: " << fittingWindowSize << "\n"; - matDataMerged.resize(m_pFiffInfo->chs.size(), fittingWindowSize); - iDataIndexCounter = 0; + // check if fitting window size has changed and resize matData if necessary + if(iFittingWindowSize != m_iFittingWindowSize) { + iFittingWindowSize = m_iFittingWindowSize; + matDataMerged.resize(m_pFiffInfo->chs.size(), iFittingWindowSize); + iRepetitionIndexCounter = 0; + } + + // check if time between fits has changed + if(iRepetitionTimeInSamples != m_iRepetitionTimeInSamples) { + iRepetitionTimeInSamples = m_iRepetitionTimeInSamples; + iRepetitionIndexCounter = 0; } m_mutex.unlock(); //pop matrix if(m_pCircularBuffer->pop(matData)) { - if(iDataIndexCounter + matData.cols() < matDataMerged.cols()) { + if(iRepetitionIndexCounter + matData.cols() < iRepetitionTimeInSamples) { + iRepetitionIndexCounter += matData.cols(); + + } else if(iDataIndexCounter + matData.cols() < iFittingWindowSize) { matDataMerged.block(0, iDataIndexCounter, matData.rows(), matData.cols()) = matData; iDataIndexCounter += matData.cols(); + } else { m_mutex.lock(); if(m_bDoSingleHpi) { @@ -671,6 +742,7 @@ void Hpi::run() } } + iRepetitionIndexCounter = 0; iDataIndexCounter = 0; } } diff --git a/applications/mne_scan/plugins/hpi/hpi.h b/applications/mne_scan/plugins/hpi/hpi.h index 30f9e390bb0..089a4f6a60b 100644 --- a/applications/mne_scan/plugins/hpi/hpi.h +++ b/applications/mne_scan/plugins/hpi/hpi.h @@ -239,13 +239,22 @@ class HPISHARED_EXPORT Hpi : public SCSHAREDLIB::AbstractAlgorithm */ void onDevHeadTransAvailable(const FIFFLIB::FiffCoordTrans& devHeadTrans); + + //========================================================================================================= + /** + * Set repetition time between hpi fits. + * + * @param[in] dRepetitionTime Repetition time in seconds + */ + void setTimeBetweenFits(double dRepetitionTimeInSeconds); + //========================================================================================================= /** - * Set fitting window size when doing continuous hpi. + * Set hpi fitting window size. * - * @param[in] winSize window size in samples + * @param[in] dFittingWindowSizeInSeconds Fitting window size in seconds */ - void setFittingWindowSize(int winSize); + void setFittingWindowSize(double dFittingWindowSizeInSeconds); //========================================================================================================= /** @@ -289,6 +298,19 @@ class HPISHARED_EXPORT Hpi : public SCSHAREDLIB::AbstractAlgorithm */ void updateDigitizerInfo(); + //========================================================================================================= + /** + * Returns the optimal window size for hpi computation + * + * Compute the optimal window size based on minimal detectable frequency difference between HPI + * freqeuencies and line frequency (+ harmonics). + * + * @return optimal Windowsize + */ + int computeMinimalWindowsize(); + + + void resetState(); QMutex m_mutex; /**< The threads mutex.*/ @@ -298,8 +320,8 @@ class HPISHARED_EXPORT Hpi : public SCSHAREDLIB::AbstractAlgorithm QString m_sFilePathDigitzers; /**< The file path to the current digitzers. */ qint16 m_iNumberBadChannels; /**< The number of bad channels.*/ - qint16 m_iFittingWindowSize; /**< The number of samples in each fitting window.*/ - + qint16 m_iRepetitionTimeInSamples; /**< The number of samples to wait between fits.*/ + int m_iFittingWindowSize; /**< The number of samples in each fitting window.*/ double m_dAllowedMeanErrorDist; /**< The allowed error distance in order for the last fit to be counted as a good fit.*/ double m_dAllowedMovement; /**< The allowed head movement regarding reference head position.*/ double m_dAllowedRotation; /**< The allowed head rotation regarding reference head position in degree.*/ @@ -321,6 +343,8 @@ class HPISHARED_EXPORT Hpi : public SCSHAREDLIB::AbstractAlgorithm SCSHAREDLIB::PluginOutputData::SPtr m_pHpiOutput; /**< The RealTimeHpiResult of the Hpi output.*/ signals: + void minimumWindowSizeChanged(const double dWindowSizeInSeconds); + void errorsChanged(const QVector& vErrors, double dMeanErrorDist); void movementResultsChanged(double dMovement, diff --git a/libraries/disp/viewers/formfiles/hpisettingsview.ui b/libraries/disp/viewers/formfiles/hpisettingsview.ui index c588529c29f..87e096fb94d 100644 --- a/libraries/disp/viewers/formfiles/hpisettingsview.ui +++ b/libraries/disp/viewers/formfiles/hpisettingsview.ui @@ -319,7 +319,7 @@ - Do continous HPI fitting at + Do continous HPI fitting @@ -327,49 +327,63 @@ - - - - 61 - 35 - - - - QFrame::StyledPanel + + + + 0 + 0 + - - QFrame::Raised + + - - - 4 - - - 4 - - - - - - 70 - 16777215 - + + + + + 200 + + + 1000 + + + 100 + + + 333 + + + + + + + 1 - 10 + 0.200000000000000 - 1000000 + 30.000000000000000 + + + 0.100000000000000 - 10 + 2.000000000000000 - - + + - Win Size [Samples] + Time between fits [seconds] + + + + + + + Window size [ms] @@ -556,6 +570,12 @@ + + + 0 + 0 + + mm @@ -576,6 +596,12 @@ + + + 0 + 0 + + ° @@ -605,6 +631,12 @@ + + + 0 + 0 + + true @@ -619,6 +651,12 @@ + + + 0 + 0 + + true diff --git a/libraries/disp/viewers/hpisettingsview.cpp b/libraries/disp/viewers/hpisettingsview.cpp index 2f83f4a1974..aa42895931a 100644 --- a/libraries/disp/viewers/hpisettingsview.cpp +++ b/libraries/disp/viewers/hpisettingsview.cpp @@ -98,8 +98,10 @@ HpiSettingsView::HpiSettingsView(const QString& sSettingsPath, this, &HpiSettingsView::compStatusChanged); connect(m_pUi->m_checkBox_continousHPI, &QCheckBox::clicked, this, &HpiSettingsView::contHpiStatusChanged); - connect(m_pUi->m_spinBox_samplesToFit, QOverload::of(&QSpinBox::valueChanged), - this, &HpiSettingsView::fittingWindowSizeChanged); + connect(m_pUi->m_doubleSpinBox_repTime, static_cast(&QDoubleSpinBox::valueChanged), + this, &HpiSettingsView::repetitionTimeChanged); + connect(m_pUi->m_spinBox_windowSize, QOverload::of(&QSpinBox::valueChanged), + this, &HpiSettingsView::fittingWindowTimeChanged); connect(m_pUi->m_doubleSpinBox_maxHPIContinousDist, static_cast(&QDoubleSpinBox::valueChanged), this, &HpiSettingsView::allowedMeanErrorDistChanged); connect(m_pUi->m_doubleSpinBox_moveThreshold, static_cast(&QDoubleSpinBox::valueChanged), @@ -171,6 +173,27 @@ void HpiSettingsView::setMovementResults(double dMovement, //============================================================================================================= +void HpiSettingsView::setMinimumWindowSize(double dWindowSize) +{ + QSignalBlocker blockeWindowr(m_pUi->m_spinBox_windowSize); + QSignalBlocker blockerTime(m_pUi->m_doubleSpinBox_repTime); + + int iMinimumInMs = ceil(dWindowSize*1000); + m_pUi->m_spinBox_windowSize->setMinimum(iMinimumInMs); + m_pUi->m_doubleSpinBox_repTime->setMinimum(dWindowSize); + + if(m_pUi->m_spinBox_windowSize->value() < iMinimumInMs) { + m_pUi->m_spinBox_windowSize->setValue(iMinimumInMs); + } + + if(m_pUi->m_doubleSpinBox_repTime->value() < dWindowSize) { + m_pUi->m_doubleSpinBox_repTime->setValue(dWindowSize); + } + +} + +//============================================================================================================= + bool HpiSettingsView::getSspStatusChanged() { return m_pUi->m_checkBox_useSSP->isChecked(); @@ -214,9 +237,9 @@ bool HpiSettingsView::continuousHPIChecked() //============================================================================================================= -int HpiSettingsView::getFittingWindowSize() +double HpiSettingsView::getFittingRepetitionTime() { - return m_pUi->m_spinBox_samplesToFit->value(); + return m_pUi->m_doubleSpinBox_repTime->value(); } //============================================================================================================= @@ -238,14 +261,11 @@ void HpiSettingsView::saveSettings() settings.setValue(m_sSettingsPath + QString("/HpiSettingsView/useCOMP"), QVariant::fromValue(m_pUi->m_checkBox_useComp->isChecked())); - settings.setValue(m_sSettingsPath + QString("/HpiSettingsView/continousHPI"),\ - QVariant::fromValue(m_pUi->m_checkBox_continousHPI->isChecked())); - settings.setValue(m_sSettingsPath + QString("/HpiSettingsView/maxError"), QVariant::fromValue(m_pUi->m_doubleSpinBox_maxHPIContinousDist->value())); settings.setValue(m_sSettingsPath + QString("/HpiSettingsView/fittingWindowSize"), - QVariant::fromValue(m_pUi->m_spinBox_samplesToFit->value())); + QVariant::fromValue(m_pUi->m_doubleSpinBox_repTime->value())); } //============================================================================================================= @@ -265,9 +285,8 @@ void HpiSettingsView::loadSettings() m_pUi->m_checkBox_useSSP->setChecked(settings.value(m_sSettingsPath + QString("/HpiSettingsView/useSSP"), false).toBool()); m_pUi->m_checkBox_useComp->setChecked(settings.value(m_sSettingsPath + QString("/HpiSettingsView/useCOMP"), false).toBool()); - m_pUi->m_checkBox_continousHPI->setChecked(settings.value(m_sSettingsPath + QString("/HpiSettingsView/continousHPI"), false).toBool()); m_pUi->m_doubleSpinBox_maxHPIContinousDist->setValue(settings.value(m_sSettingsPath + QString("/HpiSettingsView/maxError"), 10.0).toDouble()); - m_pUi->m_spinBox_samplesToFit->setValue(settings.value(m_sSettingsPath + QString("/HpiSettingsView/fittingWindowSize"), 300).toInt()); + m_pUi->m_doubleSpinBox_repTime->setValue(settings.value(m_sSettingsPath + QString("/HpiSettingsView/fittingWindowSize"), 300).toInt()); } //============================================================================================================= diff --git a/libraries/disp/viewers/hpisettingsview.h b/libraries/disp/viewers/hpisettingsview.h index ffaafb58ac9..4f7eaecdc49 100644 --- a/libraries/disp/viewers/hpisettingsview.h +++ b/libraries/disp/viewers/hpisettingsview.h @@ -117,6 +117,14 @@ class DISPSHARED_EXPORT HpiSettingsView : public AbstractView void setMovementResults(double dMovement, double dRotation); + //========================================================================================================= + /** + * Updates the minimal window time for hpi fits. + * + * @param[in] dMinimumWindowTime in seconds. + */ + void setMinimumWindowSize(double dMinimumWindowTime); + //========================================================================================================= /** * Get the SSP checked status. @@ -167,11 +175,11 @@ class DISPSHARED_EXPORT HpiSettingsView : public AbstractView //========================================================================================================= /** - * Get number of fits per second to do when performing continuous hpi + * Get the time between hpi fits in seconds. * - * @return Number of fits per second + * @return Repetition time between fits in seconds. */ - int getFittingWindowSize(); + double getFittingRepetitionTime(); //========================================================================================================= /** @@ -382,11 +390,19 @@ class DISPSHARED_EXPORT HpiSettingsView : public AbstractView //========================================================================================================= /** - * Emit this signal when 'fits per second' control gets updated. + * Emit this signal when 'Time between fits' control gets updated. + * + * @param[in] dTimeBetweenFitsInSecs The time between fits in seconds. + */ + void repetitionTimeChanged(double dTimeBetweenFitsInSecs); + + //========================================================================================================= + /** + * Emit this signal when 'Time between fits' control gets updated. * - * @param[in] iFitsPerSecond How many fits per second we should do. + * @param[in] dWindowSizeInSeconds The window size to use for hpi fits in seconds. */ - void fittingWindowSizeChanged(int iFitsPerSecond); + void fittingWindowTimeChanged(double dWindowSizeInSeconds); //========================================================================================================= /**