ref #6 -refactoring
- added interp1 - added peak finder - added bpm estimation - added findbestaxis
This commit is contained in:
@@ -34,6 +34,8 @@ public class BpmEstimator implements SensorEventListener {
|
|||||||
|
|
||||||
if(mAccelerometerWindowBuffer.isNextWindowReady()){
|
if(mAccelerometerWindowBuffer.isNextWindowReady()){
|
||||||
|
|
||||||
|
//TODO: calculate average samplerate using the window - interpolation of sensordata would be better i think
|
||||||
|
|
||||||
//int randomNum = ThreadLocalRandom.current().nextInt(0, 240 + 1);
|
//int randomNum = ThreadLocalRandom.current().nextInt(0, 240 + 1);
|
||||||
long diff = mAccelerometerWindowBuffer.getYongest().ts - mAccelerometerWindowBuffer.getOldest().ts;
|
long diff = mAccelerometerWindowBuffer.getYongest().ts - mAccelerometerWindowBuffer.getOldest().ts;
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
import bpmEstimation.*;
|
||||||
|
import utilities.Utils;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@@ -24,8 +27,7 @@ public class Main {
|
|||||||
for (File file : listOfFiles) {
|
for (File file : listOfFiles) {
|
||||||
if (file.isFile() && file.getName().contains(".csv")) {
|
if (file.isFile() && file.getName().contains(".csv")) {
|
||||||
|
|
||||||
Utils.AccelerometerWindowBuffer accWindowBuffer = new Utils.AccelerometerWindowBuffer(4096, 256);
|
AccelerometerWindowBuffer accWindowBuffer = new AccelerometerWindowBuffer(4096, 256);
|
||||||
|
|
||||||
|
|
||||||
//read the file line by line
|
//read the file line by line
|
||||||
try (BufferedReader br = new BufferedReader(new FileReader(file))) {
|
try (BufferedReader br = new BufferedReader(new FileReader(file))) {
|
||||||
@@ -36,86 +38,83 @@ public class Main {
|
|||||||
//if linear acc
|
//if linear acc
|
||||||
if(measurement[1].equals("2")){
|
if(measurement[1].equals("2")){
|
||||||
long ts = Long.parseLong(measurement[0]);
|
long ts = Long.parseLong(measurement[0]);
|
||||||
float x = Float.parseFloat(measurement[2]);
|
double x = Double.parseDouble(measurement[2]);
|
||||||
float y = Float.parseFloat(measurement[3]);
|
double y = Double.parseDouble(measurement[3]);
|
||||||
float z = Float.parseFloat(measurement[4]);
|
double z = Double.parseDouble(measurement[4]);
|
||||||
accWindowBuffer.add(new Utils.AccelerometerData(ts, x, y, z));
|
accWindowBuffer.add(new AccelerometerData(ts, x, y, z));
|
||||||
}
|
}
|
||||||
|
|
||||||
//do calculation stuff
|
//do calculation stuff
|
||||||
if(accWindowBuffer.isNextWindowReady()){
|
if(accWindowBuffer.isNextWindowReady()){
|
||||||
|
|
||||||
|
AccelerometerInterpolator acInterp = new AccelerometerInterpolator(accWindowBuffer, 6);
|
||||||
|
|
||||||
//print raw x,y,z
|
//print raw x,y,z
|
||||||
double[] dTs = IntStream.range(0, accWindowBuffer.getTs().length).mapToDouble(i -> accWindowBuffer.getTs()[i]).toArray();
|
double[] dTs = IntStream.range(0, accWindowBuffer.getTs().length).mapToDouble(i -> accWindowBuffer.getTs()[i]).toArray();
|
||||||
double[] dX = IntStream.range(0, accWindowBuffer.getX().length).mapToDouble(i -> accWindowBuffer.getX()[i]).toArray();
|
double[] dTsInterp = IntStream.range(0, acInterp.getTs().length).mapToDouble(i -> acInterp.getTs()[i]).toArray();
|
||||||
double[] dY = IntStream.range(0, accWindowBuffer.getY().length).mapToDouble(i -> accWindowBuffer.getY()[i]).toArray();
|
|
||||||
double[] dZ = IntStream.range(0, accWindowBuffer.getZ().length).mapToDouble(i -> accWindowBuffer.getZ()[i]).toArray();
|
|
||||||
|
|
||||||
Plot plotRaw = Plot.plot(Plot.plotOpts().
|
Plot plotRaw = Plot.plot(Plot.plotOpts().
|
||||||
title("Raw Acc Data").
|
title("Raw Acc Data").
|
||||||
legend(Plot.LegendFormat.BOTTOM)).
|
legend(Plot.LegendFormat.BOTTOM)).
|
||||||
series("x", Plot.data().xy(dTs, dX), Plot.seriesOpts().color(Color.RED)).
|
series("x", Plot.data().xy(dTsInterp, acInterp.getX()), Plot.seriesOpts().color(Color.RED)).
|
||||||
series("y", Plot.data().xy(dTs, dY), Plot.seriesOpts().color(Color.BLUE)).
|
series("y", Plot.data().xy(dTsInterp, acInterp.getY()), Plot.seriesOpts().color(Color.BLUE)).
|
||||||
series("z", Plot.data().xy(dTs, dZ), Plot.seriesOpts().color(Color.GREEN));
|
series("z", Plot.data().xy(dTsInterp, acInterp.getZ()), Plot.seriesOpts().color(Color.GREEN));
|
||||||
|
|
||||||
windowRaw.set(plotRaw.draw());
|
windowRaw.set(plotRaw.draw());
|
||||||
|
|
||||||
//auto corr
|
//auto corr
|
||||||
float[] xAutoCorr = Utils.fftAutoCorrelation(accWindowBuffer.getX(), 1024);
|
double[] xAutoCorr = new AutoCorrelation(acInterp.getX(), 1024).getCorr();
|
||||||
float[] yAutoCorr = Utils.fftAutoCorrelation(accWindowBuffer.getY(), 1024);
|
double[] yAutoCorr = new AutoCorrelation(acInterp.getY(), 1024).getCorr();
|
||||||
float[] zAutoCorr = Utils.fftAutoCorrelation(accWindowBuffer.getZ(), 1024);
|
double[] zAutoCorr = new AutoCorrelation(acInterp.getZ(), 1024).getCorr();
|
||||||
|
|
||||||
//print autocorr
|
//print autocorr
|
||||||
int[] tmp = IntStream.rangeClosed(-((xAutoCorr.length - 1)/2), ((xAutoCorr.length - 1)/2)).toArray();
|
int[] tmp = IntStream.rangeClosed(-((xAutoCorr.length - 1)/2), ((xAutoCorr.length - 1)/2)).toArray();
|
||||||
double[] rangeAuto = IntStream.range(0, tmp.length).mapToDouble(i -> tmp[i]).toArray();
|
double[] rangeAuto = IntStream.range(0, tmp.length).mapToDouble(i -> tmp[i]).toArray();
|
||||||
double[] dXAuto = IntStream.range(0, xAutoCorr.length).mapToDouble(i -> xAutoCorr[i]).toArray();
|
|
||||||
double[] dYAuto = IntStream.range(0, yAutoCorr.length).mapToDouble(i -> yAutoCorr[i]).toArray();
|
|
||||||
double[] dZAuto = IntStream.range(0, zAutoCorr.length).mapToDouble(i -> zAutoCorr[i]).toArray();
|
|
||||||
|
|
||||||
Plot plotCorr = Plot.plot(Plot.plotOpts().
|
Plot plotCorr = Plot.plot(Plot.plotOpts().
|
||||||
title("Auto Correlation").
|
title("Auto Correlation").
|
||||||
legend(Plot.LegendFormat.BOTTOM)).
|
legend(Plot.LegendFormat.BOTTOM)).
|
||||||
series("x", Plot.data().xy(rangeAuto, dXAuto), Plot.seriesOpts().color(Color.RED)).
|
series("x", Plot.data().xy(rangeAuto, xAutoCorr), Plot.seriesOpts().color(Color.RED)).
|
||||||
series("y", Plot.data().xy(rangeAuto, dYAuto), Plot.seriesOpts().color(Color.BLUE)).
|
series("y", Plot.data().xy(rangeAuto, yAutoCorr), Plot.seriesOpts().color(Color.BLUE)).
|
||||||
series("z", Plot.data().xy(rangeAuto, dZAuto), Plot.seriesOpts().color(Color.GREEN));
|
series("z", Plot.data().xy(rangeAuto, zAutoCorr), Plot.seriesOpts().color(Color.GREEN));
|
||||||
|
|
||||||
windowAuto.set(plotCorr.draw());
|
windowAuto.set(plotCorr.draw());
|
||||||
|
|
||||||
//find peaks
|
Peaks pX = new Peaks(xAutoCorr, 50, 0.1f, 0, false);
|
||||||
LinkedList<Integer> peaksX = Utils.findPeaks(xAutoCorr, 50, 0.1f, 0, false);
|
LinkedList<Integer> peaksX = pX.getPeaksIdx();
|
||||||
|
|
||||||
double[] dPeaksXX = IntStream.range(0, peaksX.size()).mapToDouble(i -> (peaksX.get(i) - 1024)).toArray();//peaks.stream().mapToDouble(i->i).toArray();
|
double[] dPeaksXX = IntStream.range(0, peaksX.size()).mapToDouble(i -> (peaksX.get(i) - 1024)).toArray();//peaks.stream().mapToDouble(i->i).toArray();
|
||||||
double[] dPeaksXY = IntStream.range(0, peaksX.size()).mapToDouble(i -> (dXAuto[peaksX.get(i)])).toArray();
|
double[] dPeaksXY = IntStream.range(0, peaksX.size()).mapToDouble(i -> (xAutoCorr[peaksX.get(i)])).toArray();
|
||||||
Plot plotPeaksX = Plot.plot(Plot.plotOpts().
|
Plot plotPeaksX = Plot.plot(Plot.plotOpts().
|
||||||
title("Peak Detection on X").
|
title("Peak Detection on X").
|
||||||
legend(Plot.LegendFormat.BOTTOM)).
|
legend(Plot.LegendFormat.BOTTOM)).
|
||||||
series("x", Plot.data().xy(rangeAuto, dXAuto), Plot.seriesOpts().color(Color.RED)).
|
series("x", Plot.data().xy(rangeAuto, xAutoCorr), Plot.seriesOpts().color(Color.RED)).
|
||||||
series("Peaks", Plot.data().xy(dPeaksXX, dPeaksXY), Plot.seriesOpts().color(Color.CYAN).
|
series("Peaks", Plot.data().xy(dPeaksXX, dPeaksXY), Plot.seriesOpts().color(Color.CYAN).
|
||||||
marker(Plot.Marker.DIAMOND).line(Plot.Line.NONE));
|
marker(Plot.Marker.DIAMOND).line(Plot.Line.NONE));
|
||||||
|
|
||||||
windowPeaksX.set(plotPeaksX.draw());
|
windowPeaksX.set(plotPeaksX.draw());
|
||||||
|
|
||||||
LinkedList<Integer> peaksY = Utils.findPeaks(yAutoCorr, 50, 0.1f, 0, false);
|
Peaks pY = new Peaks(yAutoCorr, 50, 0.1f, 0, false);
|
||||||
|
LinkedList<Integer> peaksY = pY.getPeaksIdx();
|
||||||
|
|
||||||
double[] dPeaksYX = IntStream.range(0, peaksY.size()).mapToDouble(i -> (peaksY.get(i) - 1024)).toArray();//peaks.stream().mapToDouble(i->i).toArray();
|
double[] dPeaksYX = IntStream.range(0, peaksY.size()).mapToDouble(i -> (peaksY.get(i) - 1024)).toArray();//peaks.stream().mapToDouble(i->i).toArray();
|
||||||
double[] dPeaksYY = IntStream.range(0, peaksY.size()).mapToDouble(i -> (dYAuto[peaksY.get(i)])).toArray();
|
double[] dPeaksYY = IntStream.range(0, peaksY.size()).mapToDouble(i -> (yAutoCorr[peaksY.get(i)])).toArray();
|
||||||
Plot plotPeaksY = Plot.plot(Plot.plotOpts().
|
Plot plotPeaksY = Plot.plot(Plot.plotOpts().
|
||||||
title("Peak Detection on Y").
|
title("Peak Detection on Y").
|
||||||
legend(Plot.LegendFormat.BOTTOM)).
|
legend(Plot.LegendFormat.BOTTOM)).
|
||||||
series("x", Plot.data().xy(rangeAuto, dYAuto), Plot.seriesOpts().color(Color.RED)).
|
series("x", Plot.data().xy(rangeAuto, yAutoCorr), Plot.seriesOpts().color(Color.RED)).
|
||||||
series("Peaks", Plot.data().xy(dPeaksYX, dPeaksYY), Plot.seriesOpts().color(Color.CYAN).
|
series("Peaks", Plot.data().xy(dPeaksYX, dPeaksYY), Plot.seriesOpts().color(Color.CYAN).
|
||||||
marker(Plot.Marker.DIAMOND).line(Plot.Line.NONE));
|
marker(Plot.Marker.DIAMOND).line(Plot.Line.NONE));
|
||||||
|
|
||||||
windowPeaksY.set(plotPeaksY.draw());
|
windowPeaksY.set(plotPeaksY.draw());
|
||||||
|
|
||||||
LinkedList<Integer> peaksZ = Utils.findPeaks(zAutoCorr, 50, 0.1f, 0, false);
|
Peaks pZ = new Peaks(zAutoCorr, 50, 0.1f, 0, false);
|
||||||
|
LinkedList<Integer> peaksZ = pZ.getPeaksIdx();
|
||||||
|
|
||||||
double[] dPeaksZX = IntStream.range(0, peaksZ.size()).mapToDouble(i -> (peaksZ.get(i) - 1024)).toArray();//peaks.stream().mapToDouble(i->i).toArray();
|
double[] dPeaksZX = IntStream.range(0, peaksZ.size()).mapToDouble(i -> (peaksZ.get(i) - 1024)).toArray();//peaks.stream().mapToDouble(i->i).toArray();
|
||||||
double[] dPeaksZY = IntStream.range(0, peaksZ.size()).mapToDouble(i -> (dZAuto[peaksZ.get(i)])).toArray();
|
double[] dPeaksZY = IntStream.range(0, peaksZ.size()).mapToDouble(i -> (zAutoCorr[peaksZ.get(i)])).toArray();
|
||||||
Plot plotPeaksZ = Plot.plot(Plot.plotOpts().
|
Plot plotPeaksZ = Plot.plot(Plot.plotOpts().
|
||||||
title("Peak Detection on Z").
|
title("Peak Detection on Z").
|
||||||
legend(Plot.LegendFormat.BOTTOM)).
|
legend(Plot.LegendFormat.BOTTOM)).
|
||||||
series("x", Plot.data().xy(rangeAuto, dZAuto), Plot.seriesOpts().color(Color.RED)).
|
series("x", Plot.data().xy(rangeAuto, zAutoCorr), Plot.seriesOpts().color(Color.RED)).
|
||||||
series("Peaks", Plot.data().xy(dPeaksZX, dPeaksZY), Plot.seriesOpts().color(Color.CYAN).
|
series("Peaks", Plot.data().xy(dPeaksZX, dPeaksZY), Plot.seriesOpts().color(Color.CYAN).
|
||||||
marker(Plot.Marker.DIAMOND).line(Plot.Line.NONE));
|
marker(Plot.Marker.DIAMOND).line(Plot.Line.NONE));
|
||||||
|
|
||||||
@@ -124,6 +123,13 @@ public class Main {
|
|||||||
//fill hols improve peaks
|
//fill hols improve peaks
|
||||||
|
|
||||||
//estimate bpm between detected peaks
|
//estimate bpm between detected peaks
|
||||||
|
System.out.println("BPM-X: " + pX.getBPM(6));
|
||||||
|
System.out.println("BPM-Y: " + pY.getBPM(6));
|
||||||
|
System.out.println("BPM-Z: " + pZ.getBPM(6));
|
||||||
|
|
||||||
|
//todo: statistikzeuch und mit matlab vergleichen.
|
||||||
|
//todo: todos machen. lol
|
||||||
|
//todo: kleiner fenstergrößen testen. so ist doch etwas langsam auf der Uhr.
|
||||||
|
|
||||||
int dummyForBreakpoint = 0;
|
int dummyForBreakpoint = 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,272 +0,0 @@
|
|||||||
import org.jtransforms.fft.FloatFFT_1D;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
|
||||||
import java.awt.*;
|
|
||||||
import java.awt.image.BufferedImage;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
|
|
||||||
|
|
||||||
public class Utils {
|
|
||||||
public static float getDistance(float x1, float y1, float x2, float y2) {
|
|
||||||
return (float) Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class AccelerometerData {
|
|
||||||
|
|
||||||
public float x,y,z;
|
|
||||||
public long ts;
|
|
||||||
|
|
||||||
public AccelerometerData(long ts, float x, float y, float z){
|
|
||||||
this.ts = ts;
|
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
this.z = z;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class AccelerometerWindowBuffer extends ArrayList<AccelerometerData> {
|
|
||||||
|
|
||||||
private int mWindowSize;
|
|
||||||
private int mOverlapSize;
|
|
||||||
private int mOverlapCounter;
|
|
||||||
private float[] mX;
|
|
||||||
private float[] mY;
|
|
||||||
private float[] mZ;
|
|
||||||
private long[] mTs;
|
|
||||||
|
|
||||||
public AccelerometerWindowBuffer(int windowSize, int overlap){
|
|
||||||
this.mWindowSize = windowSize;
|
|
||||||
this.mOverlapSize = overlap;
|
|
||||||
mOverlapCounter = 1;
|
|
||||||
|
|
||||||
mX = new float[this.mWindowSize];
|
|
||||||
mY = new float[this.mWindowSize];
|
|
||||||
mZ = new float[this.mWindowSize];
|
|
||||||
mTs = new long[this.mWindowSize];
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean add(AccelerometerData ad){
|
|
||||||
boolean r = super.add(ad);
|
|
||||||
if (size() > mWindowSize){
|
|
||||||
removeRange(0, size() - mWindowSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
//update the double arrays.
|
|
||||||
for (int i = 0; i < size(); ++i) {
|
|
||||||
mX[i] = get(i).x;
|
|
||||||
mY[i] = get(i).y;
|
|
||||||
mZ[i] = get(i).z;
|
|
||||||
mTs[i] = get(i).ts;
|
|
||||||
}
|
|
||||||
|
|
||||||
++mOverlapCounter;
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isNextWindowReady(){
|
|
||||||
if(size() == mWindowSize && mOverlapCounter > mOverlapSize){
|
|
||||||
mOverlapCounter = 1;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AccelerometerData getYongest() {
|
|
||||||
return get(size() - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AccelerometerData getOldest() {
|
|
||||||
return get(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public float[] getX(){
|
|
||||||
return mX;
|
|
||||||
}
|
|
||||||
|
|
||||||
public float[] getY(){
|
|
||||||
return mY;
|
|
||||||
}
|
|
||||||
|
|
||||||
public float[] getZ(){
|
|
||||||
return mZ;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long[] getTs(){
|
|
||||||
return mTs;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static float sqr(float x) {
|
|
||||||
return x * x;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int nextPow2(int a){
|
|
||||||
return a == 0 ? 0 : 32 - Integer.numberOfLeadingZeros(a - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static float mean(float[] data){
|
|
||||||
float sum = 0;
|
|
||||||
for (int i = 0; i < data.length; i++) {
|
|
||||||
sum += data[i];
|
|
||||||
}
|
|
||||||
return sum / data.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static float[] removeZero(float[] array){
|
|
||||||
int j = 0;
|
|
||||||
for( int i=0; i<array.length; i++ )
|
|
||||||
{
|
|
||||||
if (array[i] != 0)
|
|
||||||
array[j++] = array[i];
|
|
||||||
}
|
|
||||||
float[] newArray = new float[j];
|
|
||||||
System.arraycopy( array, 0, newArray, 0, j );
|
|
||||||
|
|
||||||
return newArray;
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO: errorhandling maxLag = 0;
|
|
||||||
//TODO: größeren Testcase schreiben
|
|
||||||
public static float[] fftAutoCorrelation(float[] data, int maxLag) {
|
|
||||||
|
|
||||||
int n = data.length;
|
|
||||||
float[] x = Arrays.copyOf(data, n);
|
|
||||||
int mxl = Math.min(maxLag, n - 1);
|
|
||||||
int ceilLog2 = nextPow2(2*n -1);
|
|
||||||
int n2 = (int) Math.pow(2,ceilLog2);
|
|
||||||
|
|
||||||
// x - mean(x) (pointwise)
|
|
||||||
float x_mean = 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
|
|
||||||
float[] x2 = new float[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
|
|
||||||
FloatFFT_1D fft = new FloatFFT_1D(n2);
|
|
||||||
fft.realForwardFull(x2);
|
|
||||||
|
|
||||||
// Cr = abs(x_fft).^2 (absolute with complex numbers is (r^2) + (i^2)
|
|
||||||
float[] Cr = new float[n2 * 2];
|
|
||||||
int j = 0;
|
|
||||||
for(int i = 0; i < x2.length; ++i){
|
|
||||||
Cr[j++] = sqr(x2[i]) + sqr(x2[i+1]);
|
|
||||||
++i; //skip the complex part
|
|
||||||
}
|
|
||||||
|
|
||||||
// ifft(Cr,[],1)
|
|
||||||
FloatFFT_1D ifft = new FloatFFT_1D(n2);
|
|
||||||
ifft.realInverseFull(Cr, true);
|
|
||||||
|
|
||||||
// remove complex part and scale/normalize
|
|
||||||
float[] c1 = new float[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.
|
|
||||||
float[] c = new float[(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;
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO: findPeaks method identical to Matlab... with PeakProminence
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 static LinkedList<Integer> findPeaks(float[] data, int width, float threshold, float decayRate, boolean isRelative) {
|
|
||||||
LinkedList<Integer> peaks = new LinkedList<>();
|
|
||||||
int maxp = 0;
|
|
||||||
int mid = 0;
|
|
||||||
int end = data.length;
|
|
||||||
float 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)){
|
|
||||||
peaks.add(new Integer(maxp));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mid++;
|
|
||||||
}
|
|
||||||
return peaks;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean overThreshold(float[] data, int index, int width,
|
|
||||||
float threshold, boolean isRelative,
|
|
||||||
float 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;
|
|
||||||
float sum = 0;
|
|
||||||
int count = iStop - iStart;
|
|
||||||
while (iStart < iStop)
|
|
||||||
sum += data[iStart++];
|
|
||||||
return (data[index] > sum / count + threshold);
|
|
||||||
} else
|
|
||||||
return (data[index] > threshold);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings("serial")
|
|
||||||
public static class ShowPNG extends JFrame
|
|
||||||
{
|
|
||||||
JLabel mLabel;
|
|
||||||
ImageIcon mIcon;
|
|
||||||
|
|
||||||
public ShowPNG(){
|
|
||||||
mLabel = new JLabel();
|
|
||||||
this.setLayout(new GridLayout(1,1));
|
|
||||||
this.setSize(800, 640);
|
|
||||||
this.add(mLabel);
|
|
||||||
this.setVisible(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void set(BufferedImage bi){
|
|
||||||
|
|
||||||
mIcon = new ImageIcon(bi);
|
|
||||||
mLabel.setVisible(false);
|
|
||||||
mLabel.setIcon(mIcon);
|
|
||||||
mLabel.setVisible(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
31
java/src/main/java/bpmEstimation/AccelerometerData.java
Normal file
31
java/src/main/java/bpmEstimation/AccelerometerData.java
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
114
java/src/main/java/bpmEstimation/AccelerometerInterpolator.java
Normal file
114
java/src/main/java/bpmEstimation/AccelerometerInterpolator.java
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
package bpmEstimation;
|
||||||
|
|
||||||
|
import utilities.Utils;
|
||||||
|
|
||||||
|
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){
|
||||||
|
|
||||||
|
if(sampleRate_ms <= 0){
|
||||||
|
sampleRate_ms = Utils.mean(Utils.diff(ab.getTs()));
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public 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,89 @@
|
|||||||
|
package bpmEstimation;
|
||||||
|
|
||||||
|
import 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;
|
||||||
|
private double[] mX;
|
||||||
|
private double[] mY;
|
||||||
|
private double[] mZ;
|
||||||
|
private long[] mTs;
|
||||||
|
|
||||||
|
public AccelerometerWindowBuffer(int windowSize, int overlap){
|
||||||
|
this.mWindowSize = windowSize;
|
||||||
|
this.mOverlapSize = overlap;
|
||||||
|
mOverlapCounter = 1;
|
||||||
|
|
||||||
|
mX = new double[this.mWindowSize];
|
||||||
|
mY = new double[this.mWindowSize];
|
||||||
|
mZ = new double[this.mWindowSize];
|
||||||
|
mTs = new long[this.mWindowSize];
|
||||||
|
}
|
||||||
|
|
||||||
|
//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);
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: bullshit... jedes mal wieder alle neue anlegen ist ja nur langsam
|
||||||
|
//update the double arrays.
|
||||||
|
for (int i = 0; i < size(); ++i) {
|
||||||
|
mX[i] = get(i).x;
|
||||||
|
mY[i] = get(i).y;
|
||||||
|
mZ[i] = get(i).z;
|
||||||
|
mTs[i] = get(i).ts;
|
||||||
|
}
|
||||||
|
|
||||||
|
++mOverlapCounter;
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isNextWindowReady(){
|
||||||
|
if(size() == mWindowSize && 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 mX;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double[] getY(){
|
||||||
|
return mY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double[] getZ(){
|
||||||
|
return mZ;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long[] getTs(){
|
||||||
|
return mTs;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
79
java/src/main/java/bpmEstimation/AutoCorrelation.java
Normal file
79
java/src/main/java/bpmEstimation/AutoCorrelation.java
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
package bpmEstimation;
|
||||||
|
|
||||||
|
import org.jtransforms.fft.DoubleFFT_1D;
|
||||||
|
import utilities.Utils;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by toni on 15/12/17.
|
||||||
|
*/
|
||||||
|
public class AutoCorrelation {
|
||||||
|
|
||||||
|
int mMaxLag;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
207
java/src/main/java/bpmEstimation/Peaks.java
Normal file
207
java/src/main/java/bpmEstimation/Peaks.java
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
package bpmEstimation;
|
||||||
|
|
||||||
|
import 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 i = 0;
|
||||||
|
int mid = (mData.length / 2);
|
||||||
|
for(Integer idx : mPeaksIdx) {
|
||||||
|
if(!idx.equals(mid)){
|
||||||
|
values[i] = mPeaksValue.get(idx);
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 i = 0;
|
||||||
|
int mid = (mData.length / 2);
|
||||||
|
for(Integer idx : mPeaksIdx) {
|
||||||
|
double curVal = mPeaksValue.get(idx);
|
||||||
|
if(!idx.equals(mid) && curVal> 0){
|
||||||
|
values[i] = curVal;
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 -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 = 0;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
232
java/src/main/java/utilities/Utils.java
Normal file
232
java/src/main/java/utilities/Utils.java
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
package utilities;
|
||||||
|
|
||||||
|
import bpmEstimation.Peaks;
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
//TODO: change from double to generic type
|
||||||
|
public class Utils {
|
||||||
|
public static double getDistance(double x1, double y1, double x2, double y2) {
|
||||||
|
return (double) Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double sqr(double x) {
|
||||||
|
return x * x;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int nextPow2(int a){
|
||||||
|
return a == 0 ? 0 : 32 - Integer.numberOfLeadingZeros(a - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double sum(double[] data){
|
||||||
|
double sum = 0;
|
||||||
|
for (int i = 0; i < data.length; i++) {
|
||||||
|
sum += data[i];
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long sum(long[] data){
|
||||||
|
long sum = 0;
|
||||||
|
for (int i = 0; i < data.length; i++) {
|
||||||
|
sum += data[i];
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: Could be slow.. faster method?
|
||||||
|
public static double rms(double[] nums) {
|
||||||
|
double sum = 0.0f;
|
||||||
|
for (double num : nums)
|
||||||
|
sum += num * num;
|
||||||
|
return (double) Math.sqrt(sum / nums.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double[] diff(double[] data){
|
||||||
|
double[] diff = new double[data.length - 1];
|
||||||
|
int i=0;
|
||||||
|
for(int j = 1; j < data.length; ++j){
|
||||||
|
diff[i] = data[j] - data[i];
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
return diff;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long[] diff(long[] data){
|
||||||
|
long[] diff = new long[data.length - 1];
|
||||||
|
int i=0;
|
||||||
|
for(int j = 1; j < data.length; ++j){
|
||||||
|
diff[i] = data[j] - data[i];
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
return diff;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double mean(double[] data){
|
||||||
|
return sum(data) / data.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long mean(long[] data){
|
||||||
|
return sum(data) / data.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: Could be slow.. faster method?
|
||||||
|
public static double geometricMean(double[] data) {
|
||||||
|
double sum = data[0];
|
||||||
|
for (int i = 1; i < data.length; i++) {
|
||||||
|
sum *= data[i];
|
||||||
|
}
|
||||||
|
return (double) Math.pow(sum, 1.0 / data.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int intersectionNumber(double[] signal, double border){
|
||||||
|
int cnt = 0;
|
||||||
|
boolean isSmallerValue = false;
|
||||||
|
boolean isBiggerValue = false;
|
||||||
|
|
||||||
|
for(double value : signal){
|
||||||
|
if(value < border){
|
||||||
|
if(isBiggerValue){
|
||||||
|
cnt++;
|
||||||
|
}
|
||||||
|
|
||||||
|
isSmallerValue = true;
|
||||||
|
isBiggerValue = false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if(isSmallerValue){
|
||||||
|
cnt++;
|
||||||
|
}
|
||||||
|
|
||||||
|
isSmallerValue = false;
|
||||||
|
isBiggerValue = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cnt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double getBestBpmEstimation(Peaks peaksX, Peaks peaksY, Peaks peaksZ) throws IllegalArgumentException {
|
||||||
|
|
||||||
|
int cntNumAxis = 0;
|
||||||
|
double sumCorr = 1; //to prevent division by zero
|
||||||
|
double sumRms = 1;
|
||||||
|
double sumNumInter = 1;
|
||||||
|
|
||||||
|
double corrMeanX = 0, corrRmsX = 0;
|
||||||
|
int corrNumInterX = 0;
|
||||||
|
if(peaksX.hasPeaks()){
|
||||||
|
corrMeanX = geometricMean(peaksX.getPeaksValueWithoutZeroIdxAndNegativeValues());
|
||||||
|
corrRmsX = rms(peaksX.getPeaksValueWithoutZeroIdx());
|
||||||
|
corrNumInterX = intersectionNumber(peaksX.getData(), 0.2f);
|
||||||
|
|
||||||
|
++cntNumAxis;
|
||||||
|
sumCorr += corrMeanX;
|
||||||
|
sumRms += corrRmsX;
|
||||||
|
sumNumInter += corrNumInterX;
|
||||||
|
}
|
||||||
|
|
||||||
|
double corrMeanY = 0, corrRmsY = 0;
|
||||||
|
int corrNumInterY = 0;
|
||||||
|
if(peaksY.hasPeaks()){
|
||||||
|
corrMeanY = geometricMean(peaksY.getPeaksValueWithoutZeroIdxAndNegativeValues());
|
||||||
|
corrRmsY = rms(peaksY.getPeaksValueWithoutZeroIdx());
|
||||||
|
corrNumInterY = intersectionNumber(peaksY.getData(), 0.2f);
|
||||||
|
|
||||||
|
++cntNumAxis;
|
||||||
|
sumCorr += corrMeanY;
|
||||||
|
sumRms += corrRmsY;
|
||||||
|
sumNumInter += corrNumInterY;
|
||||||
|
}
|
||||||
|
|
||||||
|
double corrMeanZ = 0, corrRmsZ = 0;
|
||||||
|
int corrNumInterZ = 0;
|
||||||
|
if(peaksZ.hasPeaks()){
|
||||||
|
corrMeanZ = geometricMean(peaksZ.getPeaksValueWithoutZeroIdxAndNegativeValues());
|
||||||
|
corrRmsZ = rms(peaksZ.getPeaksValueWithoutZeroIdx());
|
||||||
|
corrNumInterZ = intersectionNumber(peaksZ.getData(), 0.2f);
|
||||||
|
|
||||||
|
++cntNumAxis;
|
||||||
|
sumCorr += corrMeanZ;
|
||||||
|
sumRms += corrRmsZ;
|
||||||
|
sumNumInter += corrNumInterZ;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(cntNumAxis == 0){
|
||||||
|
throw new IllegalArgumentException("All Peaks are empty! -> Reject Estimation");
|
||||||
|
}
|
||||||
|
|
||||||
|
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){
|
||||||
|
|
||||||
|
}
|
||||||
|
else if(quantityY > quantityZ){
|
||||||
|
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//check if values are empty.
|
||||||
|
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double[] removeZero(double[] array){
|
||||||
|
int j = 0;
|
||||||
|
for( int i=0; i<array.length; i++ )
|
||||||
|
{
|
||||||
|
if (array[i] != 0){
|
||||||
|
array[j++] = array[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
double[] newArray = new double[j];
|
||||||
|
System.arraycopy( array, 0, newArray, 0, j );
|
||||||
|
|
||||||
|
return newArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double[] greaterZero(double[] array){
|
||||||
|
int j = 0;
|
||||||
|
for( int i=0; i < array.length; i++ )
|
||||||
|
{
|
||||||
|
if (array[i] > 0){
|
||||||
|
array[j++] = array[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
double[] newArray = new double[j];
|
||||||
|
System.arraycopy( array, 0, newArray, 0, j );
|
||||||
|
|
||||||
|
return newArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
public static class ShowPNG extends JFrame
|
||||||
|
{
|
||||||
|
JLabel mLabel;
|
||||||
|
ImageIcon mIcon;
|
||||||
|
|
||||||
|
public ShowPNG(){
|
||||||
|
mLabel = new JLabel();
|
||||||
|
this.setLayout(new GridLayout(1,1));
|
||||||
|
this.setSize(800, 640);
|
||||||
|
this.add(mLabel);
|
||||||
|
this.setVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set(BufferedImage bi){
|
||||||
|
|
||||||
|
mIcon = new ImageIcon(bi);
|
||||||
|
mLabel.setVisible(false);
|
||||||
|
mLabel.setIcon(mIcon);
|
||||||
|
mLabel.setVisible(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user