aufräumen und refactoren
This commit is contained in:
@@ -8,7 +8,6 @@ import java.util.TimerTask;
|
||||
/**
|
||||
* Created by toni on 20/12/17.
|
||||
*/
|
||||
|
||||
public class Metronome extends TimerTask {
|
||||
|
||||
//private MediaPlayer mMediaPlayer;
|
||||
|
||||
@@ -13,7 +13,7 @@ import android.widget.TextView;
|
||||
import java.util.Timer;
|
||||
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.Metronome;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package de.tonifetzer.conductorswatch.bpmEstimation;
|
||||
package de.tonifetzer.conductorswatch.estimation;
|
||||
|
||||
import android.content.Context;
|
||||
import android.hardware.Sensor;
|
||||
@@ -12,6 +12,8 @@ import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
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.utilities.ByteStreamWriter;
|
||||
import de.tonifetzer.conductorswatch.utilities.Utils;
|
||||
@@ -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.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.
|
||||
@@ -28,7 +29,7 @@ public class EstimatorAutoCorr {
|
||||
//private SimpleKalman mKalman;
|
||||
|
||||
|
||||
public EstimatorAutoCorr(AccelerometerWindowBuffer windowBuffer, double sampleRate_ms, int resetAfter_ms){
|
||||
EstimatorAutoCorr(AccelerometerWindowBuffer windowBuffer, double sampleRate_ms, int resetAfter_ms){
|
||||
mBuffer = windowBuffer;
|
||||
mSampleRate_ms = sampleRate_ms;
|
||||
|
||||
@@ -47,7 +48,7 @@ public class EstimatorAutoCorr {
|
||||
//mKalman = new SimpleKalman();
|
||||
}
|
||||
|
||||
public double estimate(AccelerometerWindowBuffer fixedWindow){
|
||||
double estimate(AccelerometerWindowBuffer fixedWindow){
|
||||
|
||||
double sampleRate = mSampleRate_ms;
|
||||
if(sampleRate <= 0){
|
||||
@@ -73,9 +74,9 @@ public class EstimatorAutoCorr {
|
||||
|
||||
//find a peak within range of 250 ms
|
||||
int peakWidth = (int) Math.round(250 / sampleRate);
|
||||
Peaks pX = new Peaks(xAutoCorr, peakWidth, 0.1f, 0, false);
|
||||
Peaks pY = new Peaks(yAutoCorr, peakWidth, 0.1f, 0, false);
|
||||
Peaks pZ = new Peaks(zAutoCorr, peakWidth, 0.1f, 0, false);
|
||||
PeakDetector pX = new PeakDetector(xAutoCorr, peakWidth, 0.1f, 0, false);
|
||||
PeakDetector pY = new PeakDetector(yAutoCorr, peakWidth, 0.1f, 0, false);
|
||||
PeakDetector pZ = new PeakDetector(zAutoCorr, peakWidth, 0.1f, 0, false);
|
||||
|
||||
mBpmHistory_X.add(pX.getBPM(sampleRate));
|
||||
mBpmHistory_Y.add(pY.getBPM(sampleRate));
|
||||
@@ -125,7 +126,7 @@ public class EstimatorAutoCorr {
|
||||
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;
|
||||
double sumCorr = 0; //to prevent division by zero
|
||||
@@ -173,7 +174,7 @@ public class EstimatorAutoCorr {
|
||||
|
||||
//no peaks, reject
|
||||
if(cntNumAxis == 0){
|
||||
//throw new IllegalArgumentException("All Peaks are empty! -> Reject Estimation");
|
||||
//throw new IllegalArgumentException("All PeakDetector are empty! -> Reject Estimation");
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -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 :)
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package de.tonifetzer.conductorswatch.bpmEstimation;
|
||||
package de.tonifetzer.conductorswatch.estimation.accelerometer;
|
||||
|
||||
/**
|
||||
* Created by toni on 15/12/17.
|
||||
@@ -1,7 +1,12 @@
|
||||
package de.tonifetzer.conductorswatch.bpmEstimation;
|
||||
package de.tonifetzer.conductorswatch.estimation.accelerometer;
|
||||
|
||||
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.
|
||||
*/
|
||||
@@ -11,11 +16,12 @@ public class AccelerometerInterpolator {
|
||||
private double[] mY;
|
||||
private double[] mZ;
|
||||
private long[] mTsInterp;
|
||||
private int mSize;
|
||||
|
||||
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];
|
||||
mSize = (int) ((ab.getYongest().ts - (ab.getOldest().ts - (long) sampleRate_ms)) / (long) sampleRate_ms);
|
||||
mTsInterp = new long[mSize];
|
||||
int j = 0;
|
||||
for(long i = ab.getOldest().ts; i <= ab.getYongest().ts; i += sampleRate_ms){
|
||||
mTsInterp[j++] = i;
|
||||
@@ -30,6 +36,18 @@ public class AccelerometerInterpolator {
|
||||
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(){
|
||||
return mX;
|
||||
}
|
||||
@@ -42,6 +60,10 @@ public class AccelerometerInterpolator {
|
||||
return mZ;
|
||||
}
|
||||
|
||||
public int size(){
|
||||
return mSize;
|
||||
}
|
||||
|
||||
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");
|
||||
@@ -104,5 +126,34 @@ public class AccelerometerInterpolator {
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package de.tonifetzer.conductorswatch.bpmEstimation;
|
||||
package de.tonifetzer.conductorswatch.estimation.accelerometer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package de.tonifetzer.conductorswatch.bpmEstimation;
|
||||
package de.tonifetzer.conductorswatch.estimation.dsp;
|
||||
|
||||
import org.jtransforms.fft.DoubleFFT_1D;
|
||||
import de.tonifetzer.conductorswatch.utilities.Utils;
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package de.tonifetzer.conductorswatch.bpmEstimation;
|
||||
package de.tonifetzer.conductorswatch.estimation.dsp;
|
||||
|
||||
import de.tonifetzer.conductorswatch.utilities.Utils;
|
||||
|
||||
@@ -7,7 +7,7 @@ import java.util.LinkedList;
|
||||
/**
|
||||
* 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> 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
|
||||
* @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.mPeaksIdx = new LinkedList<>();
|
||||
@@ -12,7 +12,27 @@ import java.util.List;
|
||||
//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));
|
||||
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) {
|
||||
@@ -120,6 +140,17 @@ public class Utils {
|
||||
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){
|
||||
int cnt = 0;
|
||||
boolean isSmallerValue = false;
|
||||
|
||||
Reference in New Issue
Block a user