bowling-analysis-system
Version:
A comprehensive system for analyzing bowling techniques using video processing and metrics calculation
212 lines (179 loc) • 6.38 kB
JavaScript
/**
* @module metrics/calculations/MetricsUtilities
* @description Utility functions for biomechanical metrics calculations
*/
/**
* Utility function to calculate the angle between two vectors
* @param {Object} v1 - First vector with x,y components
* @param {Object} v2 - Second vector with x,y components
* @returns {number} Angle in degrees
*/
function calculateAngleBetweenVectors(v1, v2) {
// Calculate dot product
const dotProduct = v1.x * v2.x + v1.y * v2.y;
// Calculate magnitudes
const mag1 = Math.sqrt(v1.x * v1.x + v1.y * v1.y);
const mag2 = Math.sqrt(v2.x * v2.x + v2.y * v2.y);
// Avoid division by zero
if (mag1 === 0 || mag2 === 0) return 0;
// Calculate angle in radians and convert to degrees
const angleRad = Math.acos(Math.max(-1, Math.min(1, dotProduct / (mag1 * mag2))));
return angleRad * (180 / Math.PI);
}
/**
* Get landmark safely from the array
* @param {Array|Object[]} landmarks - Pose landmarks array or array of objects with x,y,z properties
* @param {number} index - Index of the landmark
* @param {number} minConfidence - Minimum confidence threshold (0-1)
* @returns {Array|Object|null} Landmark coordinates [x,y,z] or {x,y,z} or null
*/
function getSafeLandmark(landmarks, index, minConfidence = 0) {
if (!landmarks) {
return null;
}
// Handle case where landmarks is an array of objects with x,y,z properties
if (Array.isArray(landmarks) && landmarks.length > index &&
typeof landmarks[index] === 'object' &&
'x' in landmarks[index] && 'y' in landmarks[index] && 'z' in landmarks[index]) {
return landmarks[index];
}
// Handle case where landmarks is an array of arrays [x,y,z]
if (Array.isArray(landmarks) && landmarks.length > index &&
Array.isArray(landmarks[index]) && landmarks[index].length >= 3) {
return landmarks[index];
}
// Handle case where landmarks is a flat array [x,y,z]
if (Array.isArray(landmarks) && landmarks.length >= 3 &&
typeof landmarks[0] === 'number' &&
typeof landmarks[1] === 'number' &&
typeof landmarks[2] === 'number') {
return landmarks;
}
return null;
}
/**
* Calculate distance between two points
* @param {Array|Object} point1 - First point [x,y,z] or {x,y,z}
* @param {Array|Object} point2 - Second point [x,y,z] or {x,y,z}
* @returns {number} Distance between points
*/
function calculateDistance(point1, point2) {
// Check if points are valid
if (!point1 || !point2) return 0;
let x1, y1, z1, x2, y2, z2;
// Handle array format [x,y,z]
if (Array.isArray(point1) && Array.isArray(point2)) {
if (point1.length < 3 || point2.length < 3) return 0;
x1 = point1[0];
y1 = point1[1];
z1 = point1[2];
x2 = point2[0];
y2 = point2[1];
z2 = point2[2];
}
// Handle object format {x,y,z}
else if (typeof point1 === 'object' && typeof point2 === 'object') {
if (!('x' in point1) || !('y' in point1) || !('z' in point1) ||
!('x' in point2) || !('y' in point2) || !('z' in point2)) {
return 0;
}
x1 = point1.x;
y1 = point1.y;
z1 = point1.z;
x2 = point2.x;
y2 = point2.y;
z2 = point2.z;
} else {
return 0; // Invalid input format
}
// Check for NaN values
if (isNaN(x1) || isNaN(y1) || isNaN(z1) || isNaN(x2) || isNaN(y2) || isNaN(z2)) {
return 0;
}
// Calculate Euclidean distance
return Math.sqrt(
Math.pow(x2 - x1, 2) +
Math.pow(y2 - y1, 2) +
Math.pow(z2 - z1, 2)
);
}
/**
* Calculate velocity between two positions
* @param {Array|Object} currentPos - Current position [x,y,z] or {x,y,z}
* @param {Array|Object} prevPos - Previous position [x,y,z] or {x,y,z}
* @param {number} timeDelta - Time difference between frames in seconds
* @returns {number} Velocity in units per second
*/
function calculateVelocity(currentPos, prevPos, timeDelta) {
// Check if any of the inputs are null or undefined
if (!currentPos || !prevPos) {
return 0;
}
// Check if timeDelta is valid
if (!timeDelta || timeDelta === 0) {
return 0;
}
// Extract coordinates based on format
let x1, y1, z1, x2, y2, z2;
// Handle array format [x,y,z]
if (Array.isArray(currentPos) && Array.isArray(prevPos)) {
if (currentPos.length < 3 || prevPos.length < 3) {
return 0;
}
x1 = currentPos[0];
y1 = currentPos[1];
z1 = currentPos[2];
x2 = prevPos[0];
y2 = prevPos[1];
z2 = prevPos[2];
}
// Handle object format {x,y,z}
else if (typeof currentPos === 'object' && typeof prevPos === 'object') {
if (!('x' in currentPos) || !('y' in currentPos) || !('z' in currentPos) ||
!('x' in prevPos) || !('y' in prevPos) || !('z' in prevPos)) {
return 0;
}
x1 = currentPos.x;
y1 = currentPos.y;
z1 = currentPos.z;
x2 = prevPos.x;
y2 = prevPos.y;
z2 = prevPos.z;
}
else {
return 0;
}
// Check for NaN values
if (isNaN(x1) || isNaN(y1) || isNaN(z1) || isNaN(x2) || isNaN(y2) || isNaN(z2)) {
return 0;
}
// Calculate Euclidean distance
const distance = Math.sqrt(
Math.pow(x2 - x1, 2) +
Math.pow(y2 - y1, 2) +
Math.pow(z2 - z1, 2)
);
return distance / timeDelta; // Units per second
}
/**
* Calculate acceleration between velocity measurements
* @param {number} currentVel - Current velocity
* @param {number} prevVel - Previous velocity
* @param {number} timeDelta - Time difference between measurements in seconds
* @returns {number} Acceleration in units per second squared
*/
function calculateAcceleration(currentVel, prevVel, timeDelta) {
// Check if velocities are valid numbers
if (typeof currentVel !== 'number' || typeof prevVel !== 'number') return 0;
if (isNaN(currentVel) || isNaN(prevVel)) return 0;
// Check if timeDelta is valid
if (!timeDelta || timeDelta === 0 || isNaN(timeDelta)) return 0;
return (currentVel - prevVel) / timeDelta; // Units per second squared
}
module.exports = {
calculateAngleBetweenVectors,
getSafeLandmark,
calculateDistance,
calculateVelocity,
calculateAcceleration
};