UNPKG

bowling-analysis-system

Version:

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

226 lines (188 loc) 7.5 kB
/** * Technique calculation methods for bowling metrics * Contains methods for calculating technique-related metrics from pose landmarks */ const { getSafeLandmark } = require('./MetricsUtilities'); const techniqueCalculations = { /** * Calculate follow through metric * @param {Array} landmarks - Pose landmarks array * @returns {number|null} Follow through metric value or null if calculation not possible */ calculateFollowThrough(landmarks) { try { const shoulder = getSafeLandmark(landmarks, 2); // Right shoulder const elbow = getSafeLandmark(landmarks, 3); // Right elbow const wrist = getSafeLandmark(landmarks, 4); // Right wrist if (!shoulder || !elbow || !wrist) { return null; } // Create vectors const upperArm = { x: elbow.x - shoulder.x, y: elbow.y - shoulder.y, z: elbow.z - shoulder.z }; const forearm = { x: wrist.x - elbow.x, y: wrist.y - elbow.y, z: wrist.z - elbow.z }; // Calculate magnitudes const upperArmMag = Math.sqrt( upperArm.x * upperArm.x + upperArm.y * upperArm.y + upperArm.z * upperArm.z ); const forearmMag = Math.sqrt( forearm.x * forearm.x + forearm.y * forearm.y + forearm.z * forearm.z ); // Calculate dot product const dotProduct = upperArm.x * forearm.x + upperArm.y * forearm.y + upperArm.z * forearm.z; // Calculate cosine of angle between vectors const cosAngle = dotProduct / (upperArmMag * forearmMag); // Clamp to valid range (-1 to 1) const clampedCosAngle = Math.max(-1, Math.min(1, cosAngle)); // Convert to angle in degrees const angleDegrees = Math.acos(clampedCosAngle) * (180 / Math.PI); // Return raw angle in degrees (lower is straighter arm) return angleDegrees; } catch (error) { console.warn('Error calculating follow through:', error.message); return null; } }, /** * Calculate approach consistency metric * @param {Array} landmarks - Pose landmarks array * @returns {number|null} Approach consistency metric value or null if calculation not possible */ calculateApproachConsistency(landmarks) { try { // In a real implementation, we would need multiple frames to measure consistency // This is a placeholder for now return 75; // Default value } catch (error) { console.warn('Error calculating approach consistency:', error.message); return null; } }, /** * Calculate release consistency metric * @param {Array} landmarks - Pose landmarks array * @returns {number|null} Release consistency metric value or null if calculation not possible */ calculateReleaseConsistency(landmarks) { try { // In a real implementation, we would need multiple frames to measure consistency // This is a placeholder for now return 80; // Default value } catch (error) { console.warn('Error calculating release consistency:', error.message); return null; } }, /** * Calculate arm alignment at release * @param {Array} landmarks - Pose landmarks array * @returns {number|null} Arm alignment metric or null if calculation not possible */ calculateArmAlignment(landmarks) { try { const wrist = getSafeLandmark(landmarks, 15) || getSafeLandmark(landmarks, 16); // Left or right wrist const shoulder = getSafeLandmark(landmarks, 11) || getSafeLandmark(landmarks, 12); // Left or right shoulder const hip = getSafeLandmark(landmarks, 23) || getSafeLandmark(landmarks, 24); // Left or right hip if (!wrist || !shoulder || !hip) { return null; } // Calculate vertical alignment // Ideal alignment would have the wrist directly above the shoulder at release const horizontalOffset = Math.abs(wrist.x - shoulder.x); const verticalDistance = Math.abs(wrist.y - shoulder.y); if (verticalDistance === 0) { return null; } // Calculate alignment score - lower horizontal offset relative to vertical distance is better const alignmentRatio = horizontalOffset / verticalDistance; // Convert to a 0-100 score (0 = worst, 100 = best) const alignmentScore = Math.max(0, 100 - (alignmentRatio * 100)); return alignmentScore; } catch (error) { console.warn('Error calculating arm alignment:', error.message); return null; } }, /** * Calculate release angle * @param {Array} landmarks - Pose landmarks array * @returns {number|null} Release angle in degrees or null if calculation not possible */ calculateReleaseAngle(landmarks) { try { const wrist = getSafeLandmark(landmarks, 15) || getSafeLandmark(landmarks, 16); // Left or right wrist const elbow = getSafeLandmark(landmarks, 13) || getSafeLandmark(landmarks, 14); // Left or right elbow if (!wrist || !elbow) { return null; } // Calculate angle with horizontal // Vector from elbow to wrist const elbowToWristX = wrist.x - elbow.x; const elbowToWristY = wrist.y - elbow.y; // Horizontal vector (1,0) const horizontalX = 1; const horizontalY = 0; // Calculate the angle const dotProduct = (elbowToWristX * horizontalX) + (elbowToWristY * horizontalY); const elbowToWristLength = Math.sqrt(elbowToWristX * elbowToWristX + elbowToWristY * elbowToWristY); if (elbowToWristLength === 0) { return null; } // Cosine of the angle const cosAngle = dotProduct / elbowToWristLength; // Clamp to valid range (-1 to 1) const clampedCosAngle = Math.max(-1, Math.min(1, cosAngle)); // Convert to angle in degrees let angleDegrees = Math.acos(clampedCosAngle) * (180 / Math.PI); // Adjust for quadrant (above/below horizontal) if (elbowToWristY > 0) { angleDegrees = 360 - angleDegrees; } return angleDegrees; } catch (error) { console.warn('Error calculating release angle:', error.message); return null; } }, /** * Calculate balance at release * @param {Array} landmarks - Pose landmarks array * @returns {number|null} Raw horizontal offset between hip and ankle centers */ calculateBalanceAtRelease(landmarks) { try { const leftAnkle = getSafeLandmark(landmarks, 27); const rightAnkle = getSafeLandmark(landmarks, 28); const leftHip = getSafeLandmark(landmarks, 23); const rightHip = getSafeLandmark(landmarks, 24); if (!leftAnkle || !rightAnkle || !leftHip || !rightHip) { return null; } // Calculate the center points const hipCenterX = (leftHip.x + rightHip.x) / 2; const ankleCenterX = (leftAnkle.x + rightAnkle.x) / 2; // Calculate the horizontal offset between hip center and ankle center const offset = Math.abs(hipCenterX - ankleCenterX); // Return raw offset value (lower is better balance) return offset; } catch (error) { console.warn('Error calculating balance at release:', error.message); return null; } } }; module.exports = techniqueCalculations;