peaktools
Version:
Peak curvature tools(experimental)
407 lines (350 loc) • 10.9 kB
JavaScript
// Remove
var regression = require('regression-js');
//
// Only required for testing.
//
var x = document.createElement("INPUT");
x.setAttribute("type", "file");
//
// Configuration variables.
//
var config = {
'trackingSize': 30, // number of adjacent samples to use for linear regression.
'peakStartAdjust': 15, // number of prior samples to include in area when peak starts.
'detectThreshold': 0.50, // min absolute value of slope for detection.
};
var trackingSize,
detectThreshold,
peakStartAdjust,
sensorData,
sensorLatest,
peakInfo,
curSlope,
curIntercept,
curLine,
curChannel,
slopeData,
slopeDetect,
slopeLatest,
peakTracking,
peakAscending,
peakDescending,
peakStart,
peakEnd,
peakArea,
peakDirection,
peakValue,
peakIndex,
channelData,
channelRepo;
resetPeakTools();
function getChannelRepository()
{
return channelRepo;
} // getChannelRepository()
function newChannel(channel)
{
console.log("Creating new channel: " + channel);
return {
'chanID': channel,
'trackingSize': config.trackingSize, // number of samples to use for linear regression.
'peakStartAdjust': config.peakStartAdjust, // number of samples prior to peakStart to include in current peak area.
'slopeDetect': config.detectThreshold, // abs(curSlope) >= slopeDetect triggers a peak start/stop.
'sensorData': [],
'sensorLatest': [],
'peakInfo': [],
'curSlope': undefined,
'curIntercept': undefined,
'curLine': undefined,
'slopeData': [],
'slopeLatest': [],
'peakTracking': false,
'peakAscending': false,
'peakDescending': false,
'peakStart': undefined,
'peakEnd': undefined,
'peakArea': undefined,
'peakDirection': 0,
'peakValue': undefined,
'peakIndex': undefined,
};
} // newChannel()
function activateChannel(id)
{
//
// Makes indicated id the current channel.
// If id is undefined, sets id to 0 (zero).
// If id does not exist, creates an empty channel with designated id.
//
if (channelRepo == undefined)
{
channelRepo = new Map();
}
if (id == undefined)
{
id = 0;
}
if (channelRepo.has(id))
{
channelData = channelRepo.get(id);
}
else
{
channelData = newChannel(id);
channelRepo.set(id, channelData);
}
trackingSize = channelData.trackingSize;
peakStartAdjust = channelData.peakStartAdjust;
slopeDetect = channelData.slopeDetect;
sensorData = channelData.sensorData;
sensorLatest = channelData.sensorLatest;
peakInfo = channelData.peakInfo;
curSlope = channelData.curSlope;
curIntercept = channelData.curIntercept;
curLine = channelData.curLine;
slopeData = channelData.slopeData;
slopeLatest = channelData.slopeLatest;
peakTracking = channelData.peakTracking;
peakAscending = channelData.peakAscending;
peakDescending = channelData.peakDescending;
peakStart = channelData.peakStart;
peakEnd = channelData.peakEnd;
peakArea = channelData.peakArea
peakDirection = channelData.peakDirection;
peakValue = channelData.peakValue;
peakIndex = channelData.peakIndex;
return channelData;
} // activateChannel()
function saveChannel()
{
if (channelData == undefined)
{
return;
}
channelData.trackingSize = trackingSize;
channelData.peakStartAdjust = peakStartAdjust;
channelData.slopeDetect = slopeDetect;
channelData.sensorData = sensorData;
channelData.sensorLatest = sensorLatest;
channelData.peakInfo = peakInfo;
channelData.curSlope = curSlope;
channelData.curIntercept = curIntercept;
channelData.curLine = curLine;
channelData.slopeData = slopeData;
channelData.slopeLatest = slopeLatest;
channelData.peakTracking = peakTracking;
channelData.peakAscending = peakAscending;
channelData.peakDescending = peakDescending;
channelData.peakStart = peakStart;
channelData.peakEnd = peakEnd;
channelData.peakArea = peakArea
channelData.peakDirection = peakDirection;
channelData.peakValue = peakValue;
channelData.peakIndex = peakIndex;
} // saveChannel()
function resetPeakTools()
{
channelRepo = undefined;
activateChannel();
} // resetPeakTools()
function getTestFile()
{
var x = document.getElementById("peakTestFile");
return x.value;
} // getTestFile()
function startPeak()
{
console.log("Start peak: " + sensorData.length);
peakTracking = true;
peakStart = sensorData[sensorData.length - peakStartAdjust - 1];
peakIdxStart = sensorData.length - peakStartAdjust - 1;
peakBaseStart = peakStart[1];
peakValue = peakBaseStart;
peakIndex = peakIdxStart;
peakArea = 0;
peakDirection = Math.sign(curSlope);
if (peakDirection > 0)
{
peakAscending = true;
peakDescending = false;
}
else
{
peakDescending = true; // possible support for 'negative' peaks.... FUTURE DEVELOPMENT.
peakAscending = false;
}
var curEntry;
for (var i = 0; i < peakStartAdjust; i++)
{
curEntry = sensorData[peakIdxStart + i];
peakArea += (curEntry[1] - peakBaseStart);
if (curEntry[1] > peakValue)
{
peakIndex = peakStartAdjust + i;
peakValue = curEntry[1];
}
}
} // startPeak()
function savePeak(reading)
{
console.log("SavePeak: " + sensorData.length);
peakEnd = reading;
var peak = {'start': peakStart,
'end': peakEnd,
'area': peakArea,
'peak': peakValue,
'peakIdx': peakIndex,
'idxStart': peakStart[0],
'idxEnd': peakEnd [0],
'baseStart': peakStart[1],
'baseEnd': peakEnd [1]
};
console.log("Peak: " + peak);
peakInfo.push(peak);
peakTracking = false;
peakAscending = false;
peakDescending = false;
} // savePeak()
function newData(reading, channel)
{
if (channel == undefined)
{
channel = 0;
}
activateChannel(channel);
sensorData .push(reading);
sensorLatest.push([sensorData.length, reading[1]]);
if (sensorLatest.length > trackingSize)
{
sensorLatest.shift();
curLine = regression.linear(sensorLatest);
curSlope = curLine.equation[0];
curIntercept = curLine.equation[1];
slopeData.push(curSlope);
if (peakTracking)
{
/*
* Collect current data.
* Check for:
* + switched direction (peak-is-ending)
* + dropped below slopeDetect (peak-has-ended)
* + switched direction again (new-peak-is-starting).
*
* QUESTION: Are 'plateaus' on the side of a peak
* a secondary peak, or just part of the current peak?
*/
peakArea += reading[1] - peakBaseStart;
if (reading[1] > peakValue)
{
peakIndex = sensorData.length - 1;
peakValue = reading[1];
}
if (peakAscending
&& (Math.abs(curSlope) >= slopeDetect)
&& ((Math.sign(curSlope) * peakDirection) < 0))
{
/*
* Peak has switched directions.
* Only consider it a significant change if > slopeDetect.
*/
peakAscending = false;
peakDescending = true;
}
else if (peakDescending
&& (Math.abs(curSlope) < slopeDetect))
{
/*
* Peak has ended.
* End and store the current peak.
*/
savePeak(reading);
}
else if (peakDescending
&& (Math.abs(curSlope) >= slopeDetect)
&& (Math.sign(curSlope) == peakDirection))
{
/*
* Peak has switched directions again.
* Adjacent peaks.
* End (and store) the current peak and start another.
*
* NOTE: Need to potentially adjust the area
* based on whether peakEnd sample == peakStart sample.
*/
savePeak(reading);
startPeak();
}
}
else
{
/*
*
* Start collecting peak information.
* Track the start of the peak.
* Set the direction.
* We track a peak through one reversal of direction
* back to below slopeDetect or to another reversal.
* Example:
* + to - to below slopeDetect // non-adjacent peaks.
* + to - to + // adjacent peaks.
*
*/
console.log("Slope is " + curSlope + ", threshold is " + slopeDetect);
if (Math.abs(curSlope) >= slopeDetect)
{
startPeak();
}
}
}
saveChannel();
return peakInfo;
} // newData()
var datastream;
/*
function runPeakTest(datafile)
{
console.log("Running peak tests");
var readings = 0;
if (datastream != undefined)
{
datastream.destroy();
}
if (datafile == undefined)
{
datafile = getTestFile();
}
resetPeakTools();
console.log("Data file is: " + datafile);
var fs = require('fs');
var parse = require('csv-parse');
var parser = parse({delimiter: ',',
columns: true,
function(err, data)
{
console.log(data);
console.log(err);
}
});
datastream = fs.createReadStream(datafile)
.pipe(parser)
.on('data', function (data)
{
readings += 1;
newData([parseFloat(data.Time), parseFloat(data.Sensor)]);
}
)
.on('headers', function (headerList)
{
console.log('Headers: ' + headerList);
}
)
.on('end', function()
{
console.log("Slope info: " + slopeData);
}
);
} // runPeakTest()
*/
module.exports.newData = newData;
module.exports.activateChannel = activateChannel;
module.exports.resetPeakTools = resetPeakTools;