Files
GoldenCheetah/src/FileIO/FilterHRV.cpp
2020-01-27 10:57:39 -03:00

310 lines
11 KiB
C++

/*
*
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program 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 General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "FilterHRV.h"
#include "Settings.h"
#include "DataProcessor.h"
#include "Context.h"
#include "HelpWhatsThis.h"
#include "HrvMeasuresDownload.h"
#include "HrvMeasures.h"
void FilterHrv(XDataSeries *rr, double rr_min, double rr_max, double filt, int hwin)
{
int n = rr->datapoints.count();
int win = 0;
int idx_lead = hwin;
int idx_lag = -hwin;
double average, filtlim;
double sum = 0.0;
if (rr->valuename.length()==1)
{
rr->valuename << "R-R flag";
rr->unitname << "bool";
}
// Flag R-R values which are outside min/max with -1. The values
// flagged with -1 are *NOT* included when calculating the window
// average. This deviates from the filtering methodology used
// by https://physionet.org/tutorials/hrv-toolkit/ where all are
// included.
for (int idx=0; idx < n; idx++)
{
if (rr_min < rr->datapoints[idx]->number[0] &&
rr_max > rr->datapoints[idx]->number[0])
{
rr->datapoints[idx]->number[1] = 1;
}
else
{
rr->datapoints[idx]->number[1] = -1;
}
}
if (n>2*hwin)
{
// Initialize R-R sum for the value in the window around
// current value.
for (int idx=0; idx < hwin; idx++)
{
if (rr->datapoints[idx]->number[1] == 1)
{
sum += rr->datapoints[idx]->number[0];
win++;
}
}
win--;
for (int idx=0; idx < n; idx++)
{
// Append new values to the window
if (idx_lead < n && rr->datapoints[idx_lead]->number[1] == 1)
{
sum += rr->datapoints[idx_lead]->number[0];
win++;
}
// Remove trailing values from the window
if (idx_lag >= 0 && rr->datapoints[idx_lag]->number[1] >= 0)
{
sum -= rr->datapoints[idx_lag]->number[0];
win--;
}
// Flag values which are outside +- (filt * 100) percent
// of the average value in a window around current value
// with 0.
if (rr->datapoints[idx]->number[1] == 1)
{
// Don't include current when calculating average.
sum -= rr->datapoints[idx]->number[0];
average = sum / win;
filtlim = filt * average;
if (rr->datapoints[idx]->number[0] <= average + filtlim &&
rr->datapoints[idx]->number[0] >= average - filtlim)
{
rr->datapoints[idx]->number[1] = 1;
}
else
{
rr->datapoints[idx]->number[1] = 0;
}
// Add current value to the window
sum += rr->datapoints[idx]->number[0];
}
idx_lead++;
idx_lag++;
}
}
}
class FilterHrvOutliers;
class FilterHrvOutliersConfig : public DataProcessorConfig
{
Q_DECLARE_TR_FUNCTIONS(FilterHrvOutliersConfig)
friend class ::FilterHrvOutliers;
protected:
QHBoxLayout *layout;
QLabel *hrvMaxLabel;
QDoubleSpinBox *hrvMax;
QLabel *hrvMinLabel;
QDoubleSpinBox *hrvMin;
QLabel *hrvFiltLabel;
QDoubleSpinBox *hrvFilt;
QLabel *hrvWindowLabel;
QDoubleSpinBox *hrvWindow;
QCheckBox *setRestHrv;
public:
FilterHrvOutliersConfig(QWidget *parent) : DataProcessorConfig(parent) {
HelpWhatsThis *help = new HelpWhatsThis(parent);
parent->setWhatsThis(help->getWhatsThisText(HelpWhatsThis::MenuBar_Edit_FilterHrv));
layout = new QHBoxLayout(this);
layout->setContentsMargins(0,0,0,0);
setContentsMargins(0,0,0,0);
hrvMaxLabel = new QLabel(tr("R-R maximum (msec)"));
hrvMinLabel = new QLabel(tr("R-R minimum (msec)"));
hrvFiltLabel = new QLabel(tr("Filter range"));
hrvWindowLabel = new QLabel(tr("Filter window size (#)"));
hrvMax = new QDoubleSpinBox();
hrvMax->setMaximum(5000);
hrvMax->setMinimum(0);
hrvMax->setSingleStep(1);
hrvMax->setDecimals(0);
hrvMin = new QDoubleSpinBox();
hrvMin->setMaximum(5000);
hrvMin->setMinimum(0);
hrvMin->setSingleStep(1);
hrvMin->setDecimals(0);
hrvFilt = new QDoubleSpinBox();
hrvFilt->setMaximum(10.0);
hrvFilt->setMinimum(-10.0);
hrvFilt->setSingleStep(0.1);
hrvFilt->setDecimals(2);
hrvWindow = new QDoubleSpinBox();
hrvWindow->setMaximum(50);
hrvWindow->setMinimum(4);
hrvWindow->setSingleStep(1);
hrvWindow->setDecimals(0);
setRestHrv = new QCheckBox(tr("Set Rest Hrv"));
layout->addWidget(hrvMaxLabel);
layout->addWidget(hrvMax);
layout->addWidget(hrvMinLabel);
layout->addWidget(hrvMin);
layout->addWidget(hrvFiltLabel);
layout->addWidget(hrvFilt);
layout->addWidget(hrvWindowLabel);
layout->addWidget(hrvWindow);
layout->addWidget(setRestHrv);
layout->addStretch();
}
QString explain() {
return(QString(tr("Filter R-R outliers (see \"R-R flag\" in HRV Xdata). Non outliers are marked \"1\".\n"
" - \"R-R min and maximum\" exclude samples outside (flag -1). Also excluded when filtering range.\n"
" - \"Filter range\" of the average within a window (flag 0)\n"
" - \"Filter window size\" distance on either side of the current interval\n"
" - \"Set Rest HRV\" if checked the computed HRV metrics are set as Rest HRV Measures\n"
""
)));
}
void readConfig() {
double MaxVal = appsettings->value(NULL, GC_RR_MAX, "2000.0").toDouble();
double MinVal = appsettings->value(NULL, GC_RR_MIN, "270.0").toDouble();
double FiltVal = appsettings->value(NULL, GC_RR_FILT, "0.20").toDouble();
double Window = appsettings->value(NULL, GC_RR_WINDOW, "20").toDouble();
bool SetRestHrv = appsettings->value(NULL, GC_RR_SET_REST_HRV, Qt::Checked).toBool();
hrvMax->setValue(MaxVal);
hrvMin->setValue(MinVal);
hrvFilt->setValue(FiltVal);
hrvWindow->setValue(Window);
setRestHrv->setCheckState(SetRestHrv ? Qt::Checked : Qt::Unchecked);
}
void saveConfig() {
appsettings->setValue(GC_RR_MAX, hrvMax->value());
appsettings->setValue(GC_RR_MIN, hrvMin->value());
appsettings->setValue(GC_RR_FILT, hrvFilt->value());
appsettings->setValue(GC_RR_WINDOW, hrvWindow->value());
appsettings->setValue(GC_RR_SET_REST_HRV, setRestHrv->checkState());
}
};
class FilterHrvOutliers : public DataProcessor {
Q_DECLARE_TR_FUNCTIONS(FilterHrvOutliers)
public:
FilterHrvOutliers() {}
~FilterHrvOutliers() {}
// the processor
bool postProcess(RideFile *, DataProcessorConfig* config, QString op);
// the config widget
DataProcessorConfig* processorConfig(QWidget *parent, const RideFile * ride = NULL) {
Q_UNUSED(ride);
return new FilterHrvOutliersConfig(parent);
}
// Localized Name
QString name() {
return (tr("Filter R-R Outliers"));
}
};
static bool FilterHrvOutliersAdded = DataProcessorFactory::instance().registerProcessor(QString("Filter R-R Outliers"), new FilterHrvOutliers());
bool
FilterHrvOutliers::postProcess(RideFile *ride, DataProcessorConfig *config=0, QString op="")
{
Q_UNUSED(op)
XDataSeries *series = ride->xdata("HRV");
if (series && series->datapoints.count() > 0) {
// Read settings
double rrMax;
double rrMin;
double rrFilt;
int rrWindow;
bool setRestHrv;
if (config == NULL) { // being called automatically
rrMax = appsettings->value(NULL, GC_RR_MAX, "2000.0").toDouble();
rrMin = appsettings->value(NULL, GC_RR_MIN, "270.0").toDouble();
rrFilt = appsettings->value(NULL, GC_RR_FILT, "0.2").toDouble();
rrWindow = appsettings->value(NULL, GC_RR_WINDOW, "20").toInt();
setRestHrv = appsettings->value(NULL, GC_RR_SET_REST_HRV, Qt::Unchecked).toBool();
}
else { // being called manually
rrMax = ((FilterHrvOutliersConfig*)(config))->hrvMax->value();
rrMin = ((FilterHrvOutliersConfig*)(config))->hrvMin->value();
rrFilt = ((FilterHrvOutliersConfig*)(config))->hrvFilt->value();
rrWindow = (int) ((FilterHrvOutliersConfig*)(config))->hrvWindow->value();
setRestHrv = (bool) ((FilterHrvOutliersConfig*)(config))->setRestHrv->checkState();
}
FilterHrv(series, rrMin, rrMax, rrFilt, rrWindow);
ride->context->rideItem()->refresh();
// Set HRV Measures according to user request
if (setRestHrv) {
RideItem *rideItem = ride->context->rideItem();
double avnn = rideItem->getForSymbol("AVNN");
HrvMeasure hrvMeasure;
hrvMeasure.when = rideItem->dateTime;
hrvMeasure.hr = !qFuzzyIsNull(avnn) ? 60000 / avnn : 0;
hrvMeasure.avnn = avnn;
hrvMeasure.sdnn = rideItem->getForSymbol("SDNN");
hrvMeasure.rmssd = rideItem->getForSymbol("rMSSD");
hrvMeasure.pnn50 = rideItem->getForSymbol("pNN50");
hrvMeasure.recovery_points = 1.5 * log(hrvMeasure.rmssd) + 2;
QList<HrvMeasure> hrvMeasures;
hrvMeasures.append(hrvMeasure);
HrvMeasuresDownload::updateMeasures(ride->context, hrvMeasures);
}
return true;
}
else {
return false;
}
}