From a16bd22e99e7ea15df25208210bcd4e0ee3cd29f Mon Sep 17 00:00:00 2001 From: Luca Warmenhoven Date: Thu, 9 May 2024 17:12:59 +0200 Subject: [PATCH] Updated error function calculation to be more efficient --- .../fitbot/util/processing/GesturePath.java | 38 ++++++++++++++++--- .../fitbot/util/processing/MotionData.java | 14 +++---- .../util/processing/MotionProcessor.java | 26 +++++++++++-- .../example/fitbot/util/server/WebSocket.java | 8 ++-- .../server/WebSocketConnectionHandler.java | 10 +---- .../fitbot/WebSocketMessageParsingTest.java | 11 +++--- 6 files changed, 73 insertions(+), 34 deletions(-) diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/GesturePath.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/GesturePath.java index a68d35b..ed761f8 100644 --- a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/GesturePath.java +++ b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/GesturePath.java @@ -44,14 +44,42 @@ public class GesturePath { * @return The error offset between the path and the reference point. */ public double getError(Vector3 referencePoint) { + // If there are no vectors, return 0. + if ( vectors.length == 0) + return 0; + + // If there's only one vector, return the distance to that vector. + if ( vectors.length == 1) + return vectors[0].distance(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; + double currentDistSq, nextDistSq; + int closestVectorIdx = 0; + + // Acquire two closest points to the reference point. + for ( int i = 0; i < vectors.length - 1; i++) { + currentDistSq = vectors[i].distanceSq(referencePoint); + nextDistSq = vectors[i + 1].distanceSq(referencePoint); + + if ( currentDistSq < distance) { + distance = currentDistSq; + closestVectorIdx = i; + } else if ( nextDistSq < distance) { + distance = nextDistSq; + closestVectorIdx = i + 1; + i++; // Skip the next iteration; this point is already closer. } } - return distance; + + // Calculate the error between the two closest points. + Vector3 pointB = (closestVectorIdx == vectors.length - 1) ? + vectors[closestVectorIdx - 1] : // If the closest point is the last point, use the 1 to last one + (closestVectorIdx > 0 && // Find the closer point between the surrounding points. + (vectors[closestVectorIdx - 1].distanceSq(referencePoint) < vectors[closestVectorIdx + 1].distanceSq(referencePoint))) ? + vectors[closestVectorIdx - 1] : + vectors[closestVectorIdx + 1]; + + return getError(vectors[closestVectorIdx], pointB, referencePoint); } // Builder class for the GesturePath object. diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/MotionData.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/MotionData.java index edd037b..2c10030 100644 --- a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/MotionData.java +++ b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/MotionData.java @@ -20,7 +20,7 @@ public class MotionData { * @param rotationY The rotation in the Y axis in degrees. * @param rotationZ The rotation in the Z axis in degrees. */ - public MotionData(float accelerationX, float accelerationY, float accelerationZ, float rotationX, float rotationY, float rotationZ) { + public MotionData(double accelerationX, double accelerationY, double accelerationZ, double rotationX, double rotationY, double rotationZ) { this.acceleration = new Vector3(accelerationX, accelerationY, accelerationZ); this.rotation = new Vector3(rotationX, rotationY, rotationZ); } @@ -52,12 +52,12 @@ public class MotionData { return null; return new MotionData( - Float.parseFloat(parts[0]), - Float.parseFloat(parts[1]), - Float.parseFloat(parts[2]), - Float.parseFloat(parts[3]), - Float.parseFloat(parts[4]), - Float.parseFloat(parts[5]) + Double.parseDouble(parts[0]), + Double.parseDouble(parts[1]), + Double.parseDouble(parts[2]), + Double.parseDouble(parts[3]), + Double.parseDouble(parts[4]), + Double.parseDouble(parts[5]) ); } } diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/MotionProcessor.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/MotionProcessor.java index ecf5303..4fac2c0 100644 --- a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/MotionProcessor.java +++ b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/MotionProcessor.java @@ -83,12 +83,22 @@ public class MotionProcessor { this.motionData.add(data); } + /** + * Function for updating the relative path. + * + * @param relativePath The new relative path. + */ + public void setRelativePath(List relativePath) { + this.relativePath.clear(); + this.relativePath.addAll(relativePath); + } + /** * 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() { + public void calculateRelativePath() { relativePath.clear(); // Clear previous path @@ -121,7 +131,8 @@ public class MotionProcessor { // s = 1/2 * a * t^2 return motionData.acceleration .rotate(motionData.rotation.negate()) - .multiply(sampleRate * sampleRate / 6.0D); + .divide(2) + .multiply(sampleRate * sampleRate); } /** @@ -135,7 +146,7 @@ public class MotionProcessor { // If the relative path is empty, calculate it. if ( relativePath.isEmpty()) - calculatePath(); + calculateRelativePath(); // Return the errors of the relative path compared to the reference path. return relativePath @@ -159,7 +170,14 @@ public class MotionProcessor { .orElse(0.0D); } - public void printData() { + public void logStatistics(GesturePath referencePath) { + Log.i("MotionProcessor", "Average path error: " + averageError(referencePath)); + Log.i("MotionProcessor", "Path length: " + relativePath.size()); + Log.i("MotionProcessor", "Sample rate: " + sampleRate); + Log.i("MotionProcessor", "Calibration point: " + ZERO.toString()); + Log.i("MotionProcessor", "Path comparison:\n" + + relativePath.stream().map(Vector3::toString).collect(Collectors.joining(", \t")) + + "\n" + getErrors(referencePath).stream().map(Object::toString).collect(Collectors.joining(", \t"))); } } diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/server/WebSocket.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/server/WebSocket.java index 0cc2957..837da04 100644 --- a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/server/WebSocket.java +++ b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/server/WebSocket.java @@ -84,7 +84,7 @@ public class WebSocket { public static class Message { // Enumerable representing message type (opcode). - public enum Type { + public enum Opcode { CONTINUING((byte) 0x0), TEXT((byte) 0x1), @@ -96,7 +96,7 @@ public class WebSocket { RES5((byte) 0xB), RES6((byte) 0xC), RES7((byte) 0xD), RES8((byte) 0xE), RES9((byte) 0xF); byte opcode; - Type(final byte opcode) { + Opcode(final byte opcode) { this.opcode = opcode; } @@ -105,8 +105,8 @@ public class WebSocket { * @param opcode The opcode to decode. * @return The message type. */ - public Type decode(byte opcode) { - return Type.values()[opcode & 0xF]; + public static Opcode decode(byte opcode) { + return Opcode.values()[opcode & 0xF]; } // Returns the opcode of this message type. public byte getOpcode() { return this.opcode; } diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/server/WebSocketConnectionHandler.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/server/WebSocketConnectionHandler.java index 4018ce4..f1748ce 100644 --- a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/server/WebSocketConnectionHandler.java +++ b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/server/WebSocketConnectionHandler.java @@ -1,7 +1,5 @@ package com.example.fitbot.util.server; -import android.os.Build; -import android.support.annotation.RequiresApi; import android.util.Log; import java.io.IOException; @@ -31,7 +29,6 @@ public class WebSocketConnectionHandler implements Runnable { this.theSocket = webSocket; } - @RequiresApi(api = Build.VERSION_CODES.O) @Override public void run() { // Listen for new connections until the socket @@ -65,7 +62,6 @@ public class WebSocketConnectionHandler implements Runnable { * @param streamOut The OutputStream of the client socket connection. * @return Whether or not the connection was successfully upgraded. */ - @RequiresApi(api = Build.VERSION_CODES.O) private boolean upgradeConnection(InputStream streamIn, OutputStream streamOut) { Scanner scanner = new Scanner(streamIn, "UTF-8"); String data = scanner.useDelimiter("\\r\\n\\r\\n").next(); @@ -105,7 +101,7 @@ public class WebSocketConnectionHandler implements Runnable { * @param streamIn The message stream to decode. */ private void applyMessageDecoder(InputStream streamIn) { - + // TODO: Implement } /** @@ -119,13 +115,11 @@ public class WebSocketConnectionHandler implements Runnable { if (bytes.length <= 2 || (bytes[0] & 0b1110) != 0) throw new IllegalArgumentException("Attempted to decode corrupted WebSocket frame data"); - byte opcode = (byte) (bytes[0] & 0b11110000); // Opcode (4 bits) + WebSocket.Message.Opcode opcode = WebSocket.Message.Opcode.decode((byte) (bytes[0] & 0b11110000)); byte payloadLength = (byte) (bytes[1] & 0b01111111); // Payload size (7 bits) boolean fin = (bytes[0] & 0b1) != 0; // Whether this is the whole message boolean masked = (bytes[1] & 0b10000000) != 0; // Whether the 9th bit is masked - System.out.printf("Fin: %b, Opcode: %d, masked: %b\n", fin, opcode, masked); - long extendedPayloadLength = 0; int byteOffset = 2; diff --git a/code/src/Fitbot/app/src/test/java/com/example/fitbot/WebSocketMessageParsingTest.java b/code/src/Fitbot/app/src/test/java/com/example/fitbot/WebSocketMessageParsingTest.java index 3dd4107..87f6beb 100644 --- a/code/src/Fitbot/app/src/test/java/com/example/fitbot/WebSocketMessageParsingTest.java +++ b/code/src/Fitbot/app/src/test/java/com/example/fitbot/WebSocketMessageParsingTest.java @@ -1,6 +1,8 @@ package com.example.fitbot; +import static org.junit.Assert.assertEquals; + import com.example.fitbot.util.server.WebSocketConnectionHandler; import org.junit.Test; @@ -11,6 +13,8 @@ import org.junit.Test; */ public class WebSocketMessageParsingTest { + + @Test public void parseWebSocketMessage() { @@ -27,11 +31,6 @@ public class WebSocketMessageParsingTest { } catch (Exception e) { System.err.println("Error occurred whilst attempting to parse input" + e.getMessage()); } - System.out.println("Parsed output: '" + decoded + "'"); - if (decoded.equals(reference)) { - System.out.println("Test passed"); - } else { - System.out.println("Test failed"); - } + assertEquals(reference, decoded); } }