UNPKG

bowling-analysis-system

Version:

A comprehensive system for analyzing bowling techniques using video processing and metrics calculation

212 lines (179 loc) 6.38 kB
/** * @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 };