aufräumen und refactoren

This commit is contained in:
toni
2019-01-30 20:11:34 +01:00
parent 80e8fd499d
commit 9c187a3992
14 changed files with 361 additions and 24 deletions

View File

@@ -8,7 +8,6 @@ import java.util.TimerTask;
/** /**
* Created by toni on 20/12/17. * Created by toni on 20/12/17.
*/ */
public class Metronome extends TimerTask { public class Metronome extends TimerTask {
//private MediaPlayer mMediaPlayer; //private MediaPlayer mMediaPlayer;

View File

@@ -13,7 +13,7 @@ import android.widget.TextView;
import java.util.Timer; import java.util.Timer;
import java.util.Vector; import java.util.Vector;
import de.tonifetzer.conductorswatch.bpmEstimation.Estimator; import de.tonifetzer.conductorswatch.estimation.Estimator;
import de.tonifetzer.conductorswatch.ui.Croller; import de.tonifetzer.conductorswatch.ui.Croller;
import de.tonifetzer.conductorswatch.ui.Metronome; import de.tonifetzer.conductorswatch.ui.Metronome;

View File

@@ -1,4 +1,4 @@
package de.tonifetzer.conductorswatch.bpmEstimation; package de.tonifetzer.conductorswatch.estimation;
import android.content.Context; import android.content.Context;
import android.hardware.Sensor; import android.hardware.Sensor;
@@ -12,6 +12,8 @@ import java.util.Timer;
import java.util.TimerTask; import java.util.TimerTask;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import de.tonifetzer.conductorswatch.estimation.accelerometer.AccelerometerData;
import de.tonifetzer.conductorswatch.estimation.accelerometer.AccelerometerWindowBuffer;
import de.tonifetzer.conductorswatch.network.SensorDataFileStreamer; import de.tonifetzer.conductorswatch.network.SensorDataFileStreamer;
import de.tonifetzer.conductorswatch.utilities.ByteStreamWriter; import de.tonifetzer.conductorswatch.utilities.ByteStreamWriter;
import de.tonifetzer.conductorswatch.utilities.Utils; import de.tonifetzer.conductorswatch.utilities.Utils;

View File

@@ -1,12 +1,13 @@
package de.tonifetzer.conductorswatch.bpmEstimation; package de.tonifetzer.conductorswatch.estimation;
import de.tonifetzer.conductorswatch.estimation.accelerometer.AccelerometerWindowBuffer;
import de.tonifetzer.conductorswatch.estimation.accelerometer.AccelerometerInterpolator;
import de.tonifetzer.conductorswatch.estimation.dsp.AutoCorrelation;
import de.tonifetzer.conductorswatch.estimation.dsp.PeakDetector;
import de.tonifetzer.conductorswatch.utilities.MovingFilter; import de.tonifetzer.conductorswatch.utilities.MovingFilter;
import de.tonifetzer.conductorswatch.utilities.SimpleKalman;
import de.tonifetzer.conductorswatch.utilities.Utils; import de.tonifetzer.conductorswatch.utilities.Utils;
import java.util.ArrayList;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List;
/** /**
* Created by toni on 17/12/17. * Created by toni on 17/12/17.
@@ -28,7 +29,7 @@ public class EstimatorAutoCorr {
//private SimpleKalman mKalman; //private SimpleKalman mKalman;
public EstimatorAutoCorr(AccelerometerWindowBuffer windowBuffer, double sampleRate_ms, int resetAfter_ms){ EstimatorAutoCorr(AccelerometerWindowBuffer windowBuffer, double sampleRate_ms, int resetAfter_ms){
mBuffer = windowBuffer; mBuffer = windowBuffer;
mSampleRate_ms = sampleRate_ms; mSampleRate_ms = sampleRate_ms;
@@ -47,7 +48,7 @@ public class EstimatorAutoCorr {
//mKalman = new SimpleKalman(); //mKalman = new SimpleKalman();
} }
public double estimate(AccelerometerWindowBuffer fixedWindow){ double estimate(AccelerometerWindowBuffer fixedWindow){
double sampleRate = mSampleRate_ms; double sampleRate = mSampleRate_ms;
if(sampleRate <= 0){ if(sampleRate <= 0){
@@ -73,9 +74,9 @@ public class EstimatorAutoCorr {
//find a peak within range of 250 ms //find a peak within range of 250 ms
int peakWidth = (int) Math.round(250 / sampleRate); int peakWidth = (int) Math.round(250 / sampleRate);
Peaks pX = new Peaks(xAutoCorr, peakWidth, 0.1f, 0, false); PeakDetector pX = new PeakDetector(xAutoCorr, peakWidth, 0.1f, 0, false);
Peaks pY = new Peaks(yAutoCorr, peakWidth, 0.1f, 0, false); PeakDetector pY = new PeakDetector(yAutoCorr, peakWidth, 0.1f, 0, false);
Peaks pZ = new Peaks(zAutoCorr, peakWidth, 0.1f, 0, false); PeakDetector pZ = new PeakDetector(zAutoCorr, peakWidth, 0.1f, 0, false);
mBpmHistory_X.add(pX.getBPM(sampleRate)); mBpmHistory_X.add(pX.getBPM(sampleRate));
mBpmHistory_Y.add(pY.getBPM(sampleRate)); mBpmHistory_Y.add(pY.getBPM(sampleRate));
@@ -125,7 +126,7 @@ public class EstimatorAutoCorr {
return Utils.median(mBpmHistory); return Utils.median(mBpmHistory);
} }
private double getBestBpmEstimation(Peaks peaksX, Peaks peaksY, Peaks peaksZ) throws IllegalArgumentException { private double getBestBpmEstimation(PeakDetector peaksX, PeakDetector peaksY, PeakDetector peaksZ) throws IllegalArgumentException {
int cntNumAxis = 0; int cntNumAxis = 0;
double sumCorr = 0; //to prevent division by zero double sumCorr = 0; //to prevent division by zero
@@ -173,7 +174,7 @@ public class EstimatorAutoCorr {
//no peaks, reject //no peaks, reject
if(cntNumAxis == 0){ if(cntNumAxis == 0){
//throw new IllegalArgumentException("All Peaks are empty! -> Reject Estimation"); //throw new IllegalArgumentException("All PeakDetector are empty! -> Reject Estimation");
return -1; return -1;
} }

View File

@@ -0,0 +1,31 @@
package de.tonifetzer.conductorswatch.estimation;
import java.util.LinkedList;
import de.tonifetzer.conductorswatch.estimation.accelerometer.AccelerometerWindowBuffer;
import de.tonifetzer.conductorswatch.utilities.MovingFilter;
public class EstimatorDistCorr {
private AccelerometerWindowBuffer mBuffer;
private double mSampleRate_ms;
private LinkedList<Double> mBpmHistory;
private int mResetCounter;
private int mResetLimit_ms;
private MovingFilter mMvg;
EstimatorDistCorr(AccelerometerWindowBuffer windowBuffer, double sampleRate_ms, int resetAfter_ms){
mBuffer = windowBuffer;
mSampleRate_ms = sampleRate_ms;
mBpmHistory = new LinkedList<>();
mResetCounter = 0;
mResetLimit_ms = resetAfter_ms;
mMvg = new MovingFilter(2);
}
//hier gehts weiter :)
}

View File

@@ -1,4 +1,4 @@
package de.tonifetzer.conductorswatch.bpmEstimation; package de.tonifetzer.conductorswatch.estimation.accelerometer;
/** /**
* Created by toni on 15/12/17. * Created by toni on 15/12/17.

View File

@@ -1,7 +1,12 @@
package de.tonifetzer.conductorswatch.bpmEstimation; package de.tonifetzer.conductorswatch.estimation.accelerometer;
import java.util.Arrays; import java.util.Arrays;
import de.tonifetzer.conductorswatch.estimation.accelerometer.AccelerometerData;
import de.tonifetzer.conductorswatch.estimation.accelerometer.AccelerometerWindow;
import de.tonifetzer.conductorswatch.estimation.accelerometer.AccelerometerWindowBuffer;
import de.tonifetzer.conductorswatch.utilities.Utils;
/** /**
* Created by toni on 16/12/17. * Created by toni on 16/12/17.
*/ */
@@ -11,11 +16,12 @@ public class AccelerometerInterpolator {
private double[] mY; private double[] mY;
private double[] mZ; private double[] mZ;
private long[] mTsInterp; private long[] mTsInterp;
private int mSize;
public AccelerometerInterpolator(AccelerometerWindowBuffer ab, double sampleRate_ms){ public AccelerometerInterpolator(AccelerometerWindowBuffer ab, double sampleRate_ms){
long size = (ab.getYongest().ts - (ab.getOldest().ts - (long) sampleRate_ms)) / (long) sampleRate_ms; mSize = (int) ((ab.getYongest().ts - (ab.getOldest().ts - (long) sampleRate_ms)) / (long) sampleRate_ms);
mTsInterp = new long[(int)size]; mTsInterp = new long[mSize];
int j = 0; int j = 0;
for(long i = ab.getOldest().ts; i <= ab.getYongest().ts; i += sampleRate_ms){ for(long i = ab.getOldest().ts; i <= ab.getYongest().ts; i += sampleRate_ms){
mTsInterp[j++] = i; mTsInterp[j++] = i;
@@ -30,6 +36,18 @@ public class AccelerometerInterpolator {
return mTsInterp; return mTsInterp;
} }
public double getX(int idx){
return mX[idx];
}
public double getY(int idx){
return mY[idx];
}
public double getZ(int idx){
return mZ[idx];
}
public double[] getX(){ public double[] getX(){
return mX; return mX;
} }
@@ -42,6 +60,10 @@ public class AccelerometerInterpolator {
return mZ; return mZ;
} }
public int size(){
return mSize;
}
private static double[] interpLinear(double[] x, double[] y, double[] xi) throws IllegalArgumentException { private static double[] interpLinear(double[] x, double[] y, double[] xi) throws IllegalArgumentException {
if (x.length != y.length) { if (x.length != y.length) {
throw new IllegalArgumentException("X and Y must be the same length"); throw new IllegalArgumentException("X and Y must be the same length");
@@ -104,5 +126,34 @@ public class AccelerometerInterpolator {
return interpLinear(xd, y, xid); return interpLinear(xd, y, xid);
} }
public static AccelerometerWindow interpolate(AccelerometerWindow data){
//calculate samplerate
double[] rawTimestamps = data.getTs_D();
double sampleRate_ms = Math.round(Utils.mean(Utils.diff(rawTimestamps)));
//create interpolated timestamps
double[] interpolatedTimestamps;
int size = (int) ((data.getLast().ts - (data.getFirst().ts - (long) sampleRate_ms)) / (long) sampleRate_ms);
interpolatedTimestamps = new double[size];
int j = 0;
for(double i = data.getFirst().ts; i <= data.getLast().ts; i += sampleRate_ms){
interpolatedTimestamps[j++] = i;
}
//interpolate the single axis separately
double[] interpolatedX = interpLinear(rawTimestamps, data.getX(), interpolatedTimestamps);
double[] interpolatedY = interpLinear(rawTimestamps, data.getY(), interpolatedTimestamps);
double[] interpolatedZ = interpLinear(rawTimestamps, data.getZ(), interpolatedTimestamps);
//merge everything back into an ArrayList
AccelerometerWindow interpolatedData = new AccelerometerWindow(size);
for(int i = 0; i < size; ++i){
interpolatedData.add(new AccelerometerData((long) rawTimestamps[i], interpolatedX[i], interpolatedY[i], interpolatedZ[i]));
}
return interpolatedData;
}
} }

View File

@@ -0,0 +1,45 @@
package de.tonifetzer.conductorswatch.estimation.accelerometer;
import java.util.ArrayList;
import java.util.LinkedList;
public class AccelerometerWindow extends ArrayList<AccelerometerData> {
public AccelerometerWindow(int size){
super(size);
}
public AccelerometerWindow(AccelerometerWindow other){
super(other);
}
public AccelerometerWindow(LinkedList<AccelerometerData> other){
super(other);
}
public AccelerometerData getLast() {
//TODO: check if list is empty! this causes indexoutofbounce
synchronized (this){
return super.get(size() - 1);
}
}
public AccelerometerData getFirst() {
return super.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_L(){
return this.stream().mapToLong(d -> d.ts).toArray();
}
public double[] getTs_D(){
return this.stream().mapToDouble(d -> d.ts).toArray();
}
}

View File

@@ -1,4 +1,4 @@
package de.tonifetzer.conductorswatch.bpmEstimation; package de.tonifetzer.conductorswatch.estimation.accelerometer;
import java.util.ArrayList; import java.util.ArrayList;

View File

@@ -0,0 +1,110 @@
package de.tonifetzer.conductorswatch.estimation.accelerometer;
import java.util.ArrayList;
import java.util.LinkedList;
public class AccelerometerWindowContainer {
private int mWindowSize;
private int mOverlapSize;
private int mOverlapCounter;
//data arrays that provide fixed windows for calculation
private AccelerometerWindow mRawData;
private AccelerometerWindow mInterpolatedData;
//buffer that is permanently updated through incoming sensor data
private LinkedList<AccelerometerData> mBuffer;
public AccelerometerWindowContainer(int windowSize_ms, int overlap_ms){
mWindowSize = windowSize_ms;
mOverlapSize = overlap_ms;
mOverlapCounter = 0;
mBuffer = new LinkedList<>();
}
public boolean add(AccelerometerData ad){
//TODO: add exception handling. falseArgument if ad has no numeric x,y,z
synchronized (this){
//do not add duplicates!
if(!mBuffer.isEmpty() && mBuffer.getLast().equals(ad)){
return false;
}
// current - last to increment overlap time
if(!mBuffer.isEmpty()){
mOverlapCounter += ad.ts - mBuffer.getLast().ts;
}
//add element
boolean r = mBuffer.add(ad);
removeOldElements();
return r;
}
}
private void removeOldElements(){
synchronized (this) {
if (!mBuffer.isEmpty()) {
if ((mBuffer.getLast().ts - mBuffer.getFirst().ts) > mWindowSize) {
while(mBuffer.getFirst().ts < mBuffer.getLast().ts - mWindowSize){
mBuffer.remove(0);
}
}
}
}
}
public boolean isNextWindowReady(){
if(!mBuffer.isEmpty()){
if(((mBuffer.getFirst().ts - mBuffer.getLast().ts) > mWindowSize / 4) && mOverlapCounter > mOverlapSize){
mOverlapCounter = 0;
//fill the data arrays - we use a shallow copy, as the values are not touched.
mRawData = new AccelerometerWindow(mBuffer);
mInterpolatedData = AccelerometerInterpolator.interpolate(mRawData);
return true;
}
}
return false;
}
public ArrayList<AccelerometerData> getRawData(){
return mRawData;
}
public ArrayList<AccelerometerData> getInterpolatedData(){
return mInterpolatedData;
}
public int getOverlapSize(){
return mOverlapSize;
}
public void setWindowSize(int size_ms){
this.mWindowSize = size_ms;
removeOldElements(); // need to call this here, to remove too old elements, if the windowSize gets smaller.
}
public void setOverlapSize(int size_ms){
this.mOverlapSize = size_ms;
this.mOverlapCounter = 0;
}
public void clear(){
synchronized (this){
mBuffer.clear();
mRawData.clear();
mInterpolatedData.clear();
this.mOverlapCounter = 0;
}
}
}

View File

@@ -1,4 +1,4 @@
package de.tonifetzer.conductorswatch.bpmEstimation; package de.tonifetzer.conductorswatch.estimation.dsp;
import org.jtransforms.fft.DoubleFFT_1D; import org.jtransforms.fft.DoubleFFT_1D;
import de.tonifetzer.conductorswatch.utilities.Utils; import de.tonifetzer.conductorswatch.utilities.Utils;

View File

@@ -0,0 +1,67 @@
package de.tonifetzer.conductorswatch.estimation.dsp;
import de.tonifetzer.conductorswatch.estimation.accelerometer.AccelerometerWindow;
import de.tonifetzer.conductorswatch.utilities.Utils;
import java.util.Arrays;
import java.util.DoubleSummaryStatistics;
/**
* Created by toni on 06/12/18.
*/
public class DistanceCorrelation {
//TODO: remove bad peaks found at the very beginning and end of the signal
private static int mMaxLag;
private double[] mCorr;
public DistanceCorrelation(AccelerometerWindow data, int maxLag){
mMaxLag = maxLag;
mCorr = calc(data);
}
public double[] getCorr(){
return mCorr;
}
private double[] calc(AccelerometerWindow data){
if(mMaxLag < 1){
throw new RuntimeException("maxlag has to be greater 1");
}
//init
int n = data.size();
int lag_size = Math.min(mMaxLag, n - 1);
double[] corr = new double[lag_size + 1];
//do the math
for(int j = 0; j <= lag_size; ++j){
double[] dist = new double[n - Math.abs(j)];
int idx = 0;
for (int i = j; i < n; ++i){
dist[idx] = Utils.getDistance(data.get(i).x, data.get(i).y, data.get(i).z, data.get(i-j).x, data.get(i-j).y, data.get(i-j).z);
++idx;
}
corr[j] = Utils.geometricMeanLog(dist);
}
//to [0, 1]
DoubleSummaryStatistics corrStat = Arrays.stream(corr).summaryStatistics();
double corMaxVal = corrStat.getMax();
for(int k = 0; k < corr.length; ++k){
corr[k] = ((corr[k] * (-1)) / corMaxVal) + 1;
}
// mirror corr(2:512) and put it in front
double[] output = new double[(2 * lag_size) + 1];
System.arraycopy(corr, 0, output, lag_size, lag_size + 1); // +1 to place the 1.0 in the middle of correlation
Utils.reverse(corr);
System.arraycopy(corr, 0, output, 0, lag_size);
return output;
}
}

View File

@@ -1,4 +1,4 @@
package de.tonifetzer.conductorswatch.bpmEstimation; package de.tonifetzer.conductorswatch.estimation.dsp;
import de.tonifetzer.conductorswatch.utilities.Utils; import de.tonifetzer.conductorswatch.utilities.Utils;
@@ -7,7 +7,7 @@ import java.util.LinkedList;
/** /**
* Created by toni on 15/12/17. * Created by toni on 15/12/17.
*/ */
public class Peaks { public class PeakDetector {
private LinkedList<Integer> mPeaksIdx; //provide the idx within the given data array 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<Integer> mPeaksPos; //the real position within the data-rang e.g. lag -1024 to 1024
@@ -24,7 +24,7 @@ public class Peaks {
* @param isRelative minimum value of peaks is relative to local average * @param isRelative minimum value of peaks is relative to local average
* @return array of peaks * @return array of peaks
*/ */
public Peaks(double[] data, int width, double threshold, double decayRate, boolean isRelative){ public PeakDetector(double[] data, int width, double threshold, double decayRate, boolean isRelative){
this.mData = data; this.mData = data;
this.mPeaksIdx = new LinkedList<>(); this.mPeaksIdx = new LinkedList<>();

View File

@@ -12,7 +12,27 @@ import java.util.List;
//TODO: change from double to generic type //TODO: change from double to generic type
public class Utils { public class Utils {
public static double getDistance(double x1, double y1, double x2, double y2) { public static double getDistance(double x1, double y1, double x2, double y2) {
return (double) Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)); return Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
}
public static double getDistance(double x1, double y1, double z1, double x2, double y2, double z2) {
return Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) + (z1 - z2) * (z1 - z2));
}
public static void reverse(double[] array) {
if (array == null) {
return;
}
int i = 0;
int j = array.length - 1;
double tmp;
while (j > i) {
tmp = array[j];
array[j] = array[i];
array[i] = tmp;
j--;
i++;
}
} }
public static double sqr(double x) { public static double sqr(double x) {
@@ -120,6 +140,17 @@ public class Utils {
return Math.pow(sum, 1.0 / data.length); return Math.pow(sum, 1.0 / data.length);
} }
public static double geometricMeanLog(double[] data){
double GM_log = 0.0d;
for (int i = 0; i < data.length; ++i) {
if (data[i] == 0.0d) {
return 0.0d;
}
GM_log += Math.log(data[i]);
}
return Math.exp(GM_log / data.length);
}
public static int intersectionNumber(double[] signal, double border){ public static int intersectionNumber(double[] signal, double border){
int cnt = 0; int cnt = 0;
boolean isSmallerValue = false; boolean isSmallerValue = false;