Added error calculations for GesturePath :: MotionProcessor.averageError, MotionProcessor.getErrors, GesturePath::getError

This commit is contained in:
Luca Warmenhoven
2024-05-09 16:15:24 +02:00
parent ed49392210
commit 3e0e71489e
6 changed files with 180 additions and 58 deletions

View File

@@ -7,7 +7,7 @@ android {
defaultConfig {
applicationId "com.example.fitbot"
minSdk 23
minSdk 29
targetSdk 29
versionCode 1
versionName "1.0"

View File

@@ -36,14 +36,14 @@ public class MainScreen extends AppCompatActivity {
motionProcessor.parsePacket("sampleRate 1");
Vector3[] path = new Vector3[100];
GesturePath.Builder builder = new GesturePath.Builder();
for ( int i = 0; i < 100; i++ ) {
path[i] = new Vector3(0, Math.sin(i * Math.PI / 50), 0);
motionProcessor.parsePacket("data 0;" + Math.cos(i * Math.PI / 50) + ";0;0;0;0");
builder.addVector(new Vector3(0, Math.sin(i * Math.PI / 50), 0));
motionProcessor.parsePacket("data 0;" + (-Math.sin(i * Math.PI / 50)) + ";0;0;0;0");
}
motionProcessor.calculatePath(new GesturePath(path));
motionProcessor.calculatePath();
motionProcessor.printData();
/*---Tool Bar---*/

View File

@@ -1,28 +1,101 @@
package com.example.fitbot.util.processing;
import java.util.ArrayList;
import java.util.List;
public class GesturePath {
public Vector3[] vectors;
public GesturePath(Vector3[] vectors)
{
public GesturePath(Vector3[] vectors) {
this.vectors = vectors;
}
public Vector3 getError(Vector3 compare) {
Vector3 error = new Vector3(0, 0, 0);
double distance, previous = Double.MAX_VALUE;
/**
* Get the error between a line segment and the reference point.
*
* @param linePointA The first point of the line.
* @param linePointB The second point of the line.
* @param referencePoint The reference point to calculate the error of.
* @return The error offset between the line and the reference point.
*/
public static double getError(Vector3 linePointA, Vector3 linePointB, Vector3 referencePoint) {
double lineDistance = linePointA.distanceSq(linePointB);
if (lineDistance == 0)
return referencePoint.distanceSq(linePointA);
// Calculate the minimum distance between the vectors.
// This is used to determine the error.
for (int i = 0; i < vectors.length; i++) {
distance = vectors[i].distance(compare);
if ( distance < previous ) {
error = vectors[i];
previous = distance;
double t = ((referencePoint.x - linePointA.x) * (linePointB.x - linePointA.x) +
(referencePoint.y - linePointA.y) * (linePointB.y - linePointA.y) +
(referencePoint.z - linePointA.z) * (linePointB.z - linePointA.z)) / lineDistance;
t = Math.max(0, Math.min(1, t));
return referencePoint.distanceSq(new Vector3(
linePointA.x + t * (linePointB.x - linePointA.x),
linePointA.y + t * (linePointB.y - linePointA.y),
linePointA.z + t * (linePointB.z - linePointA.z))
);
}
/**
* Get the error between an arbitrary path segment and the reference point.
*
* @param referencePoint The reference point to calculate the error of.
* @return The error offset between the path and the reference point.
*/
public double getError(Vector3 referencePoint) {
double distance = Double.MAX_VALUE;
for (int i = 0; i < vectors.length - 1; i++) {
double error = getError(vectors[i], vectors[i + 1], referencePoint);
if (error < distance) {
distance = error;
}
}
return error;
return distance;
}
// Builder class for the GesturePath object.
public static class Builder {
// List of vectors to add to the GesturePath object.
private final List<Vector3> vectors;
/**
* Constructor for the Builder object.
*
* @param vectors The list of vectors to add.
*/
public Builder(List<Vector3> vectors) {
this.vectors = vectors;
}
/**
* Default constructor for the Builder object.
*/
public Builder() {
this.vectors = new ArrayList<>();
}
/**
* Adds a vector to the GesturePath object.
*
* @param vector The vector to add.
* @return The Builder object.
*/
public Builder addVector(Vector3 vector) {
vectors.add(vector);
return this;
}
/**
* Builds the GesturePath object.
*
* @return The GesturePath object.
*/
public GesturePath build() {
return new GesturePath(vectors.toArray(new Vector3[0]));
}
}
}

View File

@@ -60,22 +60,4 @@ public class MotionData {
Float.parseFloat(parts[5])
);
}
/**
* Class for representing parsed motion data.
* This data is parsed in the `MotionProcessor` class.
*/
public static class ParsedVector {
public Vector3 relativePosition;
public Vector3 relativeError;
public ParsedVector(Vector3 relativePosition, Vector3 relativeError) {
this.relativePosition = relativePosition;
this.relativeError = relativeError;
}
public String toString() {
return "position[" + relativePosition.toString() + "] error[" + relativeError.toString() + "]";
}
}
}

View File

@@ -9,15 +9,14 @@ import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class MotionProcessor {
private static final int MAX_PATH_LENGTH = 100;
public static final String DELIMITER = ";";
private final List<MotionData> motionData = new ArrayList<>();
private final List<MotionData.ParsedVector> parsedData = new ArrayList<>();
private final Vector3[] relativePath = new Vector3[MAX_PATH_LENGTH];
private final List<Vector3> relativePath = new ArrayList<>(); // Relative path of the motion data
private Vector3 ZERO = new Vector3(0, 0, 0);
private double sampleRate = 1.0D; // samples/second
@@ -76,33 +75,91 @@ public class MotionProcessor {
}
/**
* Function for calculating the path of the device.
* This only works when the ZERO point has been provided.
* Function for adding motion data to the processor.
*
* @param data The motion data to add.
*/
public void calculatePath(GesturePath path) {
public void addMotionData(MotionData data) {
this.motionData.add(data);
}
/**
* Function for calculating the 'absolute' path of the motion data.
* This converts the relative acceleration and rotation vectors and converts
* them to a position that is relative to the ZERO point.
*/
public void calculatePath() {
relativePath.clear(); // Clear previous path
int offset = Math.max(0, motionData.size() - MAX_PATH_LENGTH);
Vector3 previous = ZERO.copy();
for (int i = offset; i < motionData.size(); i++) {
for (int i = 0; i < motionData.size(); i++) {
MotionData data = motionData.get(i);
Vector3 relativePosition = data.acceleration
.rotate(data.rotation.negate()) // Rotate acceleration vector with it's negated angle to get the acceleration relative to the vector of gravity
.multiply(sampleRate * sampleRate / 6.0D) // Apply double integration to get the relative position
.add(previous); // Add the previous position to get the new position
Vector3 relativeVector = getRelativeVector(data)
.add(previous);
Vector3 relativeError = path.getError(relativePosition);
previous = relativePosition;
// Get relative vector from the ZERO point
parsedData.add(new MotionData.ParsedVector(relativePosition, relativeError));
relativePath.add(relativeVector);
previous = relativeVector;
}
}
/**
* Function for getting the relative vector of the motion data.
* This function will calculate the relative position of the motion data
* based on its acceleration and rotation vectors. This has to be done since
* the acceleration vector is relative to its own rotation vector.
*
* @param motionData The motion data to calculate the relative vector for.
* @return The relative vector of the motion data.
*/
public Vector3 getRelativeVector(MotionData motionData) {
// Rotate the acceleration vector back by the rotation vector to make it
// perpendicular to the gravity vector, then apply double integration to get the relative position.
// s = 1/2 * a * t^2
return motionData.acceleration
.rotate(motionData.rotation.negate())
.multiply(sampleRate * sampleRate / 6.0D);
}
/**
* Function for getting the error offsets of the provided path and the
* received motion data.
*
* @param referencePath The reference path to compare the motion data to.
* @return A list of error offsets of the motion data compared to the reference path.
*/
public List<Double> getErrors(GesturePath referencePath) {
// If the relative path is empty, calculate it.
if ( relativePath.isEmpty())
calculatePath();
// Return the errors of the relative path compared to the reference path.
return relativePath
.stream()
.map(referencePath::getError)
.collect(Collectors.toList());
}
/**
* Function for calculating the average error of the motion data
* compared to the reference path.
*
* @param referencePath The reference path to compare the motion data to.
* @return The average error of the motion data compared to the reference path.
*/
public double averageError(GesturePath referencePath) {
return getErrors(referencePath)
.stream()
.mapToDouble(Double::doubleValue)
.average()
.orElse(0.0D);
}
public void printData() {
for (MotionData.ParsedVector vector : parsedData) {
Log.i("MotionProcessor", vector.toString());
}
}
}

View File

@@ -175,6 +175,16 @@ public class Vector3 {
);
}
/**
* Get the squared distance between this vector and another vector.
*
* @param compare The other vector.
* @return The squared distance between the two vectors.
*/
public double distanceSq(Vector3 compare) {
return Math.pow(compare.x - x, 2) + Math.pow(compare.y - y, 2) + Math.pow(compare.z - z, 2);
}
/**
* Get the distance between this vector and another vector.
*
@@ -182,7 +192,7 @@ public class Vector3 {
* @return The distance between the two vectors.
*/
public double distance(Vector3 compare) {
return Math.sqrt(Math.pow(compare.x - x, 2) + Math.pow(compare.y - y, 2) + Math.pow(compare.z - z, 2));
return Math.sqrt(distanceSq(compare));
}
public Vector3 map(VectorMapFunction function) {