ref #9 - verbindung zwischen watch und app geht
- watch startet automatisch auch app - bpm kommen in hoher geschwindigkeit an ref #19 - rückweisunsklasse etwas verbessert - fehlnde files hochgeladen für die bpm estimation
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
package de.tonifetzer.conductorswatch.bpmEstimation;
|
||||
|
||||
/**
|
||||
* Created by toni on 15/12/17.
|
||||
*/
|
||||
public class AccelerometerData {
|
||||
|
||||
public double x,y,z;
|
||||
public long ts;
|
||||
|
||||
public AccelerometerData(long ts, double x, double y, double z){
|
||||
this.ts = ts;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other){
|
||||
|
||||
if (this == other)
|
||||
return true;
|
||||
|
||||
if (!(other instanceof AccelerometerData)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AccelerometerData ad = (AccelerometerData) other;
|
||||
return ts == ad.ts;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package de.tonifetzer.conductorswatch.bpmEstimation;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Created by toni on 16/12/17.
|
||||
*/
|
||||
public class AccelerometerInterpolator {
|
||||
|
||||
private double[] mX;
|
||||
private double[] mY;
|
||||
private double[] mZ;
|
||||
private long[] mTsInterp;
|
||||
|
||||
public AccelerometerInterpolator(AccelerometerWindowBuffer ab, double sampleRate_ms){
|
||||
|
||||
long size = (ab.getYongest().ts - (ab.getOldest().ts - (long) sampleRate_ms)) / (long) sampleRate_ms;
|
||||
mTsInterp = new long[(int)size];
|
||||
int j = 0;
|
||||
for(long i = ab.getOldest().ts; i <= ab.getYongest().ts; i += sampleRate_ms){
|
||||
mTsInterp[j++] = i;
|
||||
}
|
||||
|
||||
mX = interpLinear(ab.getTs(), ab.getX(), mTsInterp);
|
||||
mY = interpLinear(ab.getTs(), ab.getY(), mTsInterp);
|
||||
mZ = interpLinear(ab.getTs(), ab.getZ(), mTsInterp);
|
||||
}
|
||||
|
||||
public long[] getTs(){
|
||||
return mTsInterp;
|
||||
}
|
||||
|
||||
public double[] getX(){
|
||||
return mX;
|
||||
}
|
||||
|
||||
public double[] getY(){
|
||||
return mY;
|
||||
}
|
||||
|
||||
public double[] getZ(){
|
||||
return mZ;
|
||||
}
|
||||
|
||||
private static double[] interpLinear(double[] x, double[] y, double[] xi) throws IllegalArgumentException {
|
||||
if (x.length != y.length) {
|
||||
throw new IllegalArgumentException("X and Y must be the same length");
|
||||
}
|
||||
if (x.length == 1) {
|
||||
throw new IllegalArgumentException("X must contain more than one value");
|
||||
}
|
||||
double[] dx = new double[x.length - 1];
|
||||
double[] dy = new double[x.length - 1];
|
||||
double[] slope = new double[x.length - 1];
|
||||
double[] intercept = new double[x.length - 1];
|
||||
|
||||
// Calculate the line equation (i.e. slope and intercept) between each point
|
||||
for (int i = 0; i < x.length - 1; i++) {
|
||||
dx[i] = x[i + 1] - x[i];
|
||||
if (dx[i] == 0) {
|
||||
throw new IllegalArgumentException("X must be montotonic. A duplicate " + "x-value was found");
|
||||
}
|
||||
if (dx[i] < 0) {
|
||||
throw new IllegalArgumentException("X must be sorted");
|
||||
}
|
||||
dy[i] = y[i + 1] - y[i];
|
||||
slope[i] = dy[i] / dx[i];
|
||||
intercept[i] = y[i] - x[i] * slope[i];
|
||||
}
|
||||
|
||||
// Perform the interpolation here
|
||||
double[] yi = new double[xi.length];
|
||||
for (int i = 0; i < xi.length; i++) {
|
||||
if ((xi[i] > x[x.length - 1]) || (xi[i] < x[0])) {
|
||||
yi[i] = Double.NaN;
|
||||
}
|
||||
else {
|
||||
int loc = Arrays.binarySearch(x, xi[i]);
|
||||
if (loc < -1) {
|
||||
loc = -loc - 2;
|
||||
yi[i] = slope[loc] * xi[i] + intercept[loc];
|
||||
}
|
||||
else {
|
||||
yi[i] = y[loc];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return yi;
|
||||
}
|
||||
|
||||
private static double[] interpLinear(long[] x, double[] y, long[] xi) throws IllegalArgumentException {
|
||||
|
||||
double[] xd = new double[x.length];
|
||||
for (int i = 0; i < x.length; i++) {
|
||||
xd[i] = x[i];
|
||||
}
|
||||
|
||||
double[] xid = new double[xi.length];
|
||||
for (int i = 0; i < xi.length; i++) {
|
||||
xid[i] = xi[i];
|
||||
}
|
||||
|
||||
return interpLinear(xd, y, xid);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package de.tonifetzer.conductorswatch.bpmEstimation;
|
||||
|
||||
import de.tonifetzer.conductorswatch.utilities.Utils;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Created by toni on 15/12/17.
|
||||
*/
|
||||
public class AccelerometerWindowBuffer extends ArrayList<AccelerometerData> {
|
||||
|
||||
private int mWindowSize;
|
||||
private int mOverlapSize;
|
||||
private int mOverlapCounter;
|
||||
|
||||
public AccelerometerWindowBuffer(int windowSize, int overlap){
|
||||
mWindowSize = windowSize;
|
||||
mOverlapSize = overlap;
|
||||
mOverlapCounter = 1;
|
||||
}
|
||||
|
||||
//TODO: add exception handling. falseArgument if ad has no numeric x,y,z
|
||||
public boolean add(AccelerometerData ad){
|
||||
|
||||
//do not add duplicates!
|
||||
if(!isEmpty() && getYongest().equals(ad)){
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean r = super.add(ad);
|
||||
if (size() > mWindowSize){
|
||||
removeRange(0, size() - mWindowSize);
|
||||
}
|
||||
|
||||
++mOverlapCounter;
|
||||
return r;
|
||||
}
|
||||
|
||||
public boolean isNextWindowReady(){
|
||||
if((size() > mWindowSize / 2) && mOverlapCounter > mOverlapSize){
|
||||
mOverlapCounter = 1;
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public AccelerometerData getYongest() {
|
||||
return get(size() - 1);
|
||||
}
|
||||
|
||||
public AccelerometerData getOldest() {
|
||||
return get(0);
|
||||
}
|
||||
|
||||
public double[] getX(){
|
||||
return this.stream().mapToDouble(d -> d.x).toArray();
|
||||
}
|
||||
|
||||
public double[] getY(){
|
||||
return this.stream().mapToDouble(d -> d.y).toArray();
|
||||
}
|
||||
|
||||
public double[] getZ(){
|
||||
return this.stream().mapToDouble(d -> d.z).toArray();
|
||||
}
|
||||
|
||||
public long[] getTs(){
|
||||
return this.stream().mapToLong(d -> d.ts).toArray();
|
||||
}
|
||||
|
||||
public int getOverlapSize(){
|
||||
return mOverlapSize;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package de.tonifetzer.conductorswatch.bpmEstimation;
|
||||
|
||||
import org.jtransforms.fft.DoubleFFT_1D;
|
||||
import de.tonifetzer.conductorswatch.utilities.Utils;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Created by toni on 15/12/17.
|
||||
*/
|
||||
public class AutoCorrelation {
|
||||
|
||||
private int mMaxLag;
|
||||
private double[] mCorr;
|
||||
|
||||
public AutoCorrelation(double[] data, int maxLag){
|
||||
|
||||
mMaxLag = maxLag;
|
||||
mCorr = fft(data);
|
||||
}
|
||||
|
||||
public double[] getCorr(){
|
||||
return mCorr;
|
||||
}
|
||||
|
||||
private double[] fft(double[] data) {
|
||||
|
||||
if(mMaxLag < 1){
|
||||
throw new RuntimeException("maxlag has to be greater 1");
|
||||
}
|
||||
|
||||
int n = data.length;
|
||||
double[] x = Arrays.copyOf(data, n);
|
||||
int mxl = Math.min(mMaxLag, n - 1);
|
||||
int ceilLog2 = Utils.nextPow2(2*n -1);
|
||||
int n2 = (int) Math.pow(2,ceilLog2);
|
||||
|
||||
// x - mean(x) (pointwise)
|
||||
double x_mean = Utils.mean(x);
|
||||
for(int i = 0; i < x.length; ++i){
|
||||
x[i] -= x_mean;
|
||||
}
|
||||
|
||||
// double the size of x and fill up with zeros. if x is not even, add additional 0
|
||||
double[] x2 = new double[n2 * 2]; //need double the size for fft.realForwardFull (look into method description)
|
||||
Arrays.fill(x2, 0);
|
||||
System.arraycopy(x,0, x2, 0, x.length);
|
||||
|
||||
// x_fft calculate fft 1D
|
||||
DoubleFFT_1D fft = new DoubleFFT_1D(n2);
|
||||
fft.realForwardFull(x2);
|
||||
|
||||
// Cr = abs(x_fft).^2 (absolute with complex numbers is (r^2) + (i^2)
|
||||
double[] Cr = new double[n2 * 2];
|
||||
int j = 0;
|
||||
for(int i = 0; i < x2.length; ++i){
|
||||
Cr[j++] = Utils.sqr(x2[i]) + Utils.sqr(x2[i+1]);
|
||||
++i; //skip the complex part
|
||||
}
|
||||
|
||||
// ifft(Cr,[],1)
|
||||
DoubleFFT_1D ifft = new DoubleFFT_1D(n2);
|
||||
ifft.realInverseFull(Cr, true);
|
||||
|
||||
// remove complex part and scale/normalize
|
||||
double[] c1 = new double[n2];
|
||||
j = 0;
|
||||
for(int i = 0; i < Cr.length; ++i){
|
||||
c1[j++] = Cr[i] / Cr[0];
|
||||
++i; //skip the complex part
|
||||
}
|
||||
|
||||
// Keep only the lags we want and move negative lags before positive lags.
|
||||
double[] c = new double[(mxl * 2) + 1];
|
||||
System.arraycopy(c1, 0, c, mxl, mxl + 1); // +1 to place the 1.0 in the middle of correlation
|
||||
System.arraycopy(c1, n2 - mxl, c, 0, mxl);
|
||||
|
||||
return c;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
package de.tonifetzer.conductorswatch.bpmEstimation;
|
||||
|
||||
import de.tonifetzer.conductorswatch.utilities.MovingFilter;
|
||||
import de.tonifetzer.conductorswatch.utilities.SimpleKalman;
|
||||
import de.tonifetzer.conductorswatch.utilities.Utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Created by toni on 17/12/17.
|
||||
*/
|
||||
public class BpmEstimator {
|
||||
|
||||
private AccelerometerWindowBuffer mBuffer;
|
||||
private double mSampleRate_ms;
|
||||
|
||||
private LinkedList<Double> mBpmHistory_X;
|
||||
private LinkedList<Double> mBpmHistory_Y;
|
||||
private LinkedList<Double> mBpmHistory_Z;
|
||||
|
||||
private LinkedList<Double> mBpmHistory;
|
||||
private int mResetCounter;
|
||||
private int mResetLimit_ms;
|
||||
|
||||
private MovingFilter mMvg;
|
||||
//private SimpleKalman mKalman;
|
||||
|
||||
|
||||
public BpmEstimator(AccelerometerWindowBuffer windowBuffer, double sampleRate_ms, int resetAfter_ms){
|
||||
mBuffer = windowBuffer;
|
||||
mSampleRate_ms = sampleRate_ms;
|
||||
|
||||
mBpmHistory_X = new LinkedList<>();
|
||||
mBpmHistory_Y = new LinkedList<>();
|
||||
mBpmHistory_Z = new LinkedList<>();
|
||||
|
||||
mBpmHistory = new LinkedList<>();
|
||||
mResetCounter = 0;
|
||||
mResetLimit_ms = resetAfter_ms;
|
||||
|
||||
mMvg = new MovingFilter(2);
|
||||
//mKalman = new SimpleKalman();
|
||||
}
|
||||
|
||||
public double estimate(){
|
||||
|
||||
double sampleRate = mSampleRate_ms;
|
||||
if(sampleRate <= 0){
|
||||
sampleRate = Math.round(Utils.mean(Utils.diff(mBuffer.getTs())));
|
||||
}
|
||||
|
||||
AccelerometerInterpolator interp = new AccelerometerInterpolator(mBuffer, sampleRate);
|
||||
|
||||
//are we conducting?
|
||||
//just look at the newest 512 samples
|
||||
//List<AccelerometerData> subBuffer = mBuffer.subList(mBuffer.size() - 512, mBuffer.size());
|
||||
|
||||
double[] xAutoCorr = new AutoCorrelation(interp.getX(), 512).getCorr();
|
||||
double[] yAutoCorr = new AutoCorrelation(interp.getY(), 512).getCorr();
|
||||
double[] zAutoCorr = new AutoCorrelation(interp.getZ(), 512).getCorr();
|
||||
|
||||
Peaks pX = new Peaks(xAutoCorr, 50, 0.1f, 0, false);
|
||||
Peaks pY = new Peaks(yAutoCorr, 50, 0.1f, 0, false);
|
||||
Peaks pZ = new Peaks(zAutoCorr, 50, 0.1f, 0, false);
|
||||
|
||||
mBpmHistory_X.add(pX.getBPM(sampleRate));
|
||||
mBpmHistory_Y.add(pY.getBPM(sampleRate));
|
||||
mBpmHistory_Z.add(pZ.getBPM(sampleRate));
|
||||
|
||||
double estimatedBPM = getBestBpmEstimation(pX, pY, pZ);
|
||||
if(estimatedBPM != -1){
|
||||
|
||||
//moving avg (lohnt dann, wenn wir viele daten haben)
|
||||
mMvg.add(estimatedBPM);
|
||||
mBpmHistory.add(mMvg.getAverage());
|
||||
|
||||
//moving median (lohnt nur bei konstantem tempo, da wir nur ein tempo damit gut halten können.)
|
||||
//mMvg.add(estimatedBPM);
|
||||
//mBpmHistory.add(mMvg.getMedian());
|
||||
|
||||
//kalman filter (lohnt dann, wenn wir konstantes tempo haben, mit startangabe!)
|
||||
|
||||
//standard last element
|
||||
//mBpmHistory.add(estimatedBPM);
|
||||
//mResetCounter = 0;
|
||||
}
|
||||
else {
|
||||
int resetAfter = (int) Math.round(mResetLimit_ms / (mBuffer.getOverlapSize() * sampleRate));
|
||||
if(++mResetCounter > resetAfter){
|
||||
mBpmHistory.clear();
|
||||
mBuffer.clear();
|
||||
mMvg.clear();
|
||||
mResetCounter = 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
//last element
|
||||
return mBpmHistory.getLast();
|
||||
}
|
||||
|
||||
public double getMeanBpm(){
|
||||
return Utils.mean(mBpmHistory);
|
||||
}
|
||||
|
||||
public double getMedianBPM(){
|
||||
return Utils.median(mBpmHistory);
|
||||
}
|
||||
|
||||
private double getBestBpmEstimation(Peaks peaksX, Peaks peaksY, Peaks peaksZ) throws IllegalArgumentException {
|
||||
|
||||
int cntNumAxis = 0;
|
||||
double sumCorr = 0; //to prevent division by zero
|
||||
double sumRms = 0;
|
||||
double sumNumInter = 0;
|
||||
|
||||
double corrMeanX = 0, corrRmsX = 0;
|
||||
int corrNumInterX = 0;
|
||||
if(peaksX.hasPeaks()){
|
||||
corrMeanX = Utils.geometricMean(peaksX.getPeaksValueWithoutZeroIdxAndNegativeValues());
|
||||
corrRmsX = Utils.rms(peaksX.getPeaksValueWithoutZeroIdx());
|
||||
corrNumInterX = Utils.intersectionNumber(peaksX.getData(), 0.2f);
|
||||
|
||||
++cntNumAxis;
|
||||
sumCorr += corrMeanX;
|
||||
sumRms += corrRmsX;
|
||||
sumNumInter += corrNumInterX;
|
||||
}
|
||||
|
||||
double corrMeanY = 0, corrRmsY = 0;
|
||||
int corrNumInterY = 0;
|
||||
if(peaksY.hasPeaks()){
|
||||
corrMeanY = Utils.geometricMean(peaksY.getPeaksValueWithoutZeroIdxAndNegativeValues());
|
||||
corrRmsY = Utils.rms(peaksY.getPeaksValueWithoutZeroIdx());
|
||||
corrNumInterY = Utils.intersectionNumber(peaksY.getData(), 0.2f);
|
||||
|
||||
++cntNumAxis;
|
||||
sumCorr += corrMeanY;
|
||||
sumRms += corrRmsY;
|
||||
sumNumInter += corrNumInterY;
|
||||
}
|
||||
|
||||
double corrMeanZ = 0, corrRmsZ = 0;
|
||||
int corrNumInterZ = 0;
|
||||
if(peaksZ.hasPeaks()){
|
||||
corrMeanZ = Utils.geometricMean(peaksZ.getPeaksValueWithoutZeroIdxAndNegativeValues());
|
||||
corrRmsZ = Utils.rms(peaksZ.getPeaksValueWithoutZeroIdx());
|
||||
corrNumInterZ = Utils.intersectionNumber(peaksZ.getData(), 0.2f);
|
||||
|
||||
++cntNumAxis;
|
||||
sumCorr += corrMeanZ;
|
||||
sumRms += corrRmsZ;
|
||||
sumNumInter += corrNumInterZ;
|
||||
}
|
||||
|
||||
//no peaks, reject
|
||||
if(cntNumAxis == 0){
|
||||
//throw new IllegalArgumentException("All Peaks are empty! -> Reject Estimation");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
System.out.println("RMS-X: " + corrRmsX);
|
||||
System.out.println("GEO-X: " + corrMeanX);
|
||||
System.out.println("INTER-X: " + corrNumInterX);
|
||||
|
||||
System.out.println("RMS-Y: " + corrRmsY);
|
||||
System.out.println("GEO-Y: " + corrMeanY);
|
||||
System.out.println("INTER-Y: " + corrNumInterY);
|
||||
|
||||
System.out.println("RMS-Z: " + corrRmsZ);
|
||||
System.out.println("GEO-Z: " + corrMeanZ);
|
||||
System.out.println("INTER-Z: " + corrNumInterZ);
|
||||
*/
|
||||
|
||||
//values to low, reject
|
||||
//TODO: this is a pretty simple assumption. first shot!
|
||||
if(corrRmsX < 0.25 && corrRmsY < 0.25 && corrRmsZ < 0.25){
|
||||
return -1;
|
||||
}
|
||||
|
||||
double quantityX = ((corrMeanX / sumCorr) + (corrRmsX / sumRms) + (corrNumInterX / sumNumInter)) / cntNumAxis;
|
||||
double quantityY = ((corrMeanY / sumCorr) + (corrRmsY / sumRms) + (corrNumInterY / sumNumInter)) / cntNumAxis;
|
||||
double quantityZ = ((corrMeanZ / sumCorr) + (corrRmsZ / sumRms) + (corrNumInterZ / sumNumInter)) / cntNumAxis;
|
||||
|
||||
//get best axis by quantity and estimate bpm
|
||||
if(quantityX > quantityY && quantityX > quantityZ){
|
||||
return mBpmHistory_X.getLast();
|
||||
}
|
||||
else if(quantityY > quantityZ){
|
||||
return mBpmHistory_Y.getLast();
|
||||
}
|
||||
else {
|
||||
return mBpmHistory_Z.getLast();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
package de.tonifetzer.conductorswatch.bpmEstimation;
|
||||
|
||||
import de.tonifetzer.conductorswatch.utilities.Utils;
|
||||
|
||||
import java.util.LinkedList;
|
||||
|
||||
/**
|
||||
* Created by toni on 15/12/17.
|
||||
*/
|
||||
public class Peaks {
|
||||
|
||||
private LinkedList<Integer> mPeaksIdx; //provide the idx within the given data array
|
||||
private LinkedList<Integer> mPeaksPos; //the real position within the data-rang e.g. lag -1024 to 1024
|
||||
private LinkedList<Double> mPeaksValue; //the value at mPeaksPos
|
||||
|
||||
private double[] mData;
|
||||
|
||||
/**
|
||||
* Simple method for finding local maxima in an array
|
||||
* @param data input
|
||||
* @param width minimum distance between peaks
|
||||
* @param threshold minimum value of peaks
|
||||
* @param decayRate how quickly previous peaks are forgotten
|
||||
* @param isRelative minimum value of peaks is relative to local average
|
||||
* @return array of peaks
|
||||
*/
|
||||
public Peaks(double[] data, int width, double threshold, double decayRate, boolean isRelative){
|
||||
|
||||
this.mData = data;
|
||||
this.mPeaksIdx = new LinkedList<>();
|
||||
this.mPeaksPos = new LinkedList<>();
|
||||
this.mPeaksValue = new LinkedList<>();
|
||||
|
||||
//create the peaks
|
||||
simplePeakFinder(data, width, threshold, decayRate, isRelative);
|
||||
|
||||
updateLists();
|
||||
}
|
||||
|
||||
public LinkedList<Integer> getPeaksIdx() {
|
||||
return mPeaksIdx;
|
||||
}
|
||||
|
||||
public int[] getPeaksIdxAsArray() {
|
||||
return mPeaksIdx.stream().mapToInt(i->i).toArray();
|
||||
}
|
||||
|
||||
public LinkedList<Integer> getPeaksPos() {
|
||||
return mPeaksPos;
|
||||
}
|
||||
|
||||
public double[] getPeaksPosAsArray() {
|
||||
return mPeaksPos.stream().mapToDouble(i -> i).toArray();
|
||||
}
|
||||
|
||||
public LinkedList<Double> getPeaksValue() {
|
||||
return mPeaksValue;
|
||||
}
|
||||
|
||||
public double[] getPeaksValueAsArray() {
|
||||
return mPeaksValue.stream().mapToDouble(i -> i).toArray();
|
||||
}
|
||||
|
||||
public double[] getData(){
|
||||
return mData;
|
||||
}
|
||||
|
||||
//TODO: implement findFalseDetectedPeaks
|
||||
public void improveResults(double[] data){
|
||||
|
||||
updateLists();
|
||||
}
|
||||
|
||||
public double[] getPeaksValueWithoutZeroIdx() {
|
||||
|
||||
double[] values = new double[mPeaksIdx.size() - 1];
|
||||
int mid = (mData.length / 2);
|
||||
int j = 0;
|
||||
for(int i = 0; i < mPeaksIdx.size(); ++i){
|
||||
if(!(mPeaksIdx.get(i) == mid)){
|
||||
values[j] = mPeaksValue.get(i);
|
||||
++j;
|
||||
}
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
public double[] getPeaksValueWithoutNegativeValues() {
|
||||
|
||||
double[] values = new double[mPeaksIdx.size() - 1];
|
||||
int i = 0;
|
||||
for(Integer idx : mPeaksIdx) {
|
||||
double curVal = mPeaksValue.get(idx);
|
||||
if(curVal > 0){
|
||||
values[i] = curVal;
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
public double[] getPeaksValueWithoutZeroIdxAndNegativeValues(){
|
||||
double[] values = new double[mPeaksIdx.size() - 1];
|
||||
int mid = (mData.length / 2);
|
||||
int j = 0;
|
||||
for(int i = 0; i < mPeaksIdx.size(); ++i){
|
||||
double curVal = mPeaksValue.get(i);
|
||||
if(!(mPeaksIdx.get(i) == mid) && curVal > 0){
|
||||
values[j] = curVal;
|
||||
++j;
|
||||
}
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
public boolean hasPeaks() {
|
||||
return mPeaksIdx.size() > 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides an estimation of beats per minute given a samplerate in milliseconds
|
||||
* @param sampleRate_ms
|
||||
* @return bpm if peaks found and conducting activity recognized, else -1
|
||||
*/
|
||||
public double getBPM(double sampleRate_ms){
|
||||
|
||||
//todo: rückweisungsklasse kann auch hier mit rein.
|
||||
if(hasPeaks()){
|
||||
|
||||
//todo diff and mean method for linkedlists for speed
|
||||
//return 60000 / Utils.mean(Utils.diff(mPeaksPos.stream().mapToDouble(i -> i * sampleRate_ms).toArray()));
|
||||
return 60000 / (sampleRate_ms * Utils.mean(Utils.diff(mPeaksPos.stream().mapToDouble(i -> i).toArray())));
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* updates the position and values of the found peaks.
|
||||
* call this if peaks are somewhat changed.
|
||||
*/
|
||||
private void updateLists(){
|
||||
//fill the position and the value lists
|
||||
for(Integer idx : mPeaksIdx){
|
||||
int mid = (mData.length / 2);
|
||||
mPeaksPos.add(idx - mid);
|
||||
|
||||
mPeaksValue.add(mData[idx]);
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: findPeaks method identical to Matlab... with PeakProminence
|
||||
|
||||
private void simplePeakFinder(double[] data, int width, double threshold, double decayRate, boolean isRelative) {
|
||||
int maxp;
|
||||
int mid = 0;
|
||||
int end = data.length;
|
||||
double av = data[0];
|
||||
while (mid < end) {
|
||||
av = decayRate * av + (1 - decayRate) * data[mid];
|
||||
if (av < data[mid])
|
||||
av = data[mid];
|
||||
int i = mid - width;
|
||||
if (i < 0)
|
||||
i = 0;
|
||||
int stop = mid + width + 1;
|
||||
if (stop > data.length)
|
||||
stop = data.length;
|
||||
maxp = i;
|
||||
for (i++; i < stop; i++)
|
||||
if (data[i] > data[maxp])
|
||||
maxp = i;
|
||||
if (maxp == mid) {
|
||||
if (overThreshold(data, maxp, width, threshold, isRelative, av)){
|
||||
this.mPeaksIdx.add(new Integer(maxp));
|
||||
}
|
||||
}
|
||||
mid++;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean overThreshold(double[] data, int index, int width,
|
||||
double threshold, boolean isRelative,
|
||||
double av) {
|
||||
int pre = 3;
|
||||
int post = 1;
|
||||
|
||||
if (data[index] < av)
|
||||
return false;
|
||||
if (isRelative) {
|
||||
int iStart = index - pre * width;
|
||||
if (iStart < 0)
|
||||
iStart = 0;
|
||||
int iStop = index + post * width;
|
||||
if (iStop > data.length)
|
||||
iStop = data.length;
|
||||
double sum = 0;
|
||||
int count = iStop - iStart;
|
||||
while (iStart < iStop)
|
||||
sum += data[iStart++];
|
||||
return (data[index] > sum / count + threshold);
|
||||
} else
|
||||
return (data[index] > threshold);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package de.tonifetzer.conductorswatch.utilities;
|
||||
|
||||
import java.util.LinkedList;
|
||||
|
||||
/**
|
||||
* Created by toni on 18/12/17.
|
||||
*/
|
||||
public class MovingFilter {
|
||||
|
||||
private int mSize;
|
||||
private double mTotal = 0d;
|
||||
private LinkedList<Double> mSamples = new LinkedList<>();
|
||||
|
||||
public MovingFilter(int size) {
|
||||
this.mSize = size;
|
||||
}
|
||||
|
||||
public void add(double x) {
|
||||
mTotal += x;
|
||||
mSamples.add(x);
|
||||
if(mSamples.size() > mSize){
|
||||
mTotal -= mSamples.remove();
|
||||
}
|
||||
}
|
||||
|
||||
public double getAverage() {
|
||||
return mTotal / mSamples.size();
|
||||
}
|
||||
|
||||
public double getMedian() {
|
||||
return Utils.median(mSamples);
|
||||
}
|
||||
|
||||
public void clear(){
|
||||
mSamples.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package de.tonifetzer.conductorswatch.utilities;
|
||||
|
||||
/**
|
||||
* Created by toni on 18/12/17.
|
||||
*/
|
||||
public class SimpleKalman {
|
||||
|
||||
private double mSigmaUpdate;
|
||||
private double mSigmaPrediction;
|
||||
private double mMu;
|
||||
private double mSigma;
|
||||
|
||||
SimpleKalman(double initialMu, double initialSigma, double sigmaUpdate, double sigmaPrediction){
|
||||
mSigmaUpdate = sigmaUpdate;
|
||||
mSigmaPrediction = sigmaPrediction;
|
||||
mMu = initialMu;
|
||||
mSigma = initialSigma;
|
||||
}
|
||||
|
||||
public double update(double data){
|
||||
|
||||
//prediction
|
||||
mMu = mMu;
|
||||
mSigma += mSigmaPrediction;
|
||||
|
||||
//update
|
||||
double k = mSigma / (mSigma + mSigmaUpdate);
|
||||
mMu = mMu + k * (data - mMu);
|
||||
mSigma = (1 - k) * mSigma;
|
||||
|
||||
return mMu;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user