UNPKG

bowling-analysis-system

Version:

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

777 lines (636 loc) 22.9 kB
/** * @module metrics/calculations/AngleCalculations * @description Functions for calculating joint angles and alignments */ const { calculateAngleBetweenVectors, getSafeLandmark, createVector, standardizePointFormat } = require('./MetricsUtilities'); /** * Calculate arm angles for both left and right sides * @param {Array} landmarks - Array of pose landmarks * @returns {Object} Object containing left and right arm angles in degrees */ function calculateArmAngles(landmarks) { return { left: calculateLeftArmAngle(landmarks), right: calculateRightArmAngle(landmarks) }; } /** * Calculate left arm angle * @param {Array} landmarks - Array of pose landmarks * @returns {number} Left arm angle in degrees */ function calculateLeftArmAngle(landmarks) { const shoulder = getSafeLandmark(landmarks, 5); // Left shoulder const elbow = getSafeLandmark(landmarks, 7); // Left elbow const wrist = getSafeLandmark(landmarks, 9); // Left wrist if (!shoulder || !elbow || !wrist) return null; const upperArm = createVector(shoulder, elbow); const forearm = createVector(elbow, wrist); return calculateAngleBetweenVectors(upperArm, forearm); } /** * Calculate right arm angle * @param {Array} landmarks - Array of pose landmarks * @returns {number} Right arm angle in degrees */ function calculateRightArmAngle(landmarks) { const shoulder = getSafeLandmark(landmarks, 6); // Right shoulder const elbow = getSafeLandmark(landmarks, 8); // Right elbow const wrist = getSafeLandmark(landmarks, 10); // Right wrist if (!shoulder || !elbow || !wrist) return null; const upperArm = createVector(shoulder, elbow); const forearm = createVector(elbow, wrist); return calculateAngleBetweenVectors(upperArm, forearm); } /** * Calculate elbow flexions for both left and right sides * @param {Array} landmarks - Array of pose landmarks * @returns {Object} Object containing left and right elbow flexion angles in degrees */ function calculateElbowFlexions(landmarks) { return { left: calculateLeftElbowFlexion(landmarks), right: calculateRightElbowFlexion(landmarks) }; } /** * Calculate left elbow flexion * @param {Array} landmarks - Array of pose landmarks * @returns {number} Left elbow flexion angle in degrees */ function calculateLeftElbowFlexion(landmarks) { return calculateLeftArmAngle(landmarks); } /** * Calculate right elbow flexion * @param {Array} landmarks - Array of pose landmarks * @returns {number} Right elbow flexion angle in degrees */ function calculateRightElbowFlexion(landmarks) { return calculateRightArmAngle(landmarks); } /** * Calculate shoulder rotations for both left and right sides * @param {Array} landmarks - Array of pose landmarks * @returns {Object} Object containing left and right shoulder rotation angles in degrees */ function calculateShoulderRotations(landmarks) { return { left: calculateLeftShoulderRotation(landmarks), right: calculateRightShoulderRotation(landmarks) }; } /** * Calculate left shoulder rotation * @param {Array} landmarks - Array of pose landmarks * @returns {number} Left shoulder rotation angle in degrees */ function calculateLeftShoulderRotation(landmarks) { const leftShoulder = getSafeLandmark(landmarks, 5); const leftElbow = getSafeLandmark(landmarks, 7); const rightShoulder = getSafeLandmark(landmarks, 6); if (!leftShoulder || !leftElbow || !rightShoulder) return null; // Create vectors const shoulderLine = createVector(rightShoulder, leftShoulder); const upperArm = createVector(leftShoulder, leftElbow); // Project upper arm onto horizontal plane const upperArmHorizontal = { x: upperArm.x, y: 0, z: upperArm.z || 0 }; return calculateAngleBetweenVectors(shoulderLine, upperArmHorizontal); } /** * Calculate right shoulder rotation * @param {Array} landmarks - Array of pose landmarks * @returns {number} Right shoulder rotation angle in degrees */ function calculateRightShoulderRotation(landmarks) { const leftShoulder = getSafeLandmark(landmarks, 5); const rightShoulder = getSafeLandmark(landmarks, 6); const rightElbow = getSafeLandmark(landmarks, 8); if (!leftShoulder || !rightShoulder || !rightElbow) return null; // Create vectors const shoulderLine = createVector(leftShoulder, rightShoulder); const upperArm = createVector(rightShoulder, rightElbow); // Project upper arm onto horizontal plane const upperArmHorizontal = { x: upperArm.x, y: 0, z: upperArm.z || 0 }; return calculateAngleBetweenVectors(shoulderLine, upperArmHorizontal); } /** * Calculate shoulder tilts for both left and right sides * @param {Array} landmarks - Array of pose landmarks * @returns {Object} Object containing left and right shoulder tilt angles in degrees */ function calculateShoulderTilts(landmarks) { return { left: calculateLeftShoulderTilt(landmarks), right: calculateRightShoulderTilt(landmarks) }; } /** * Calculate left shoulder tilt (up/down angle) * @param {Array} landmarks - Array of pose landmarks * @returns {number} Left shoulder tilt angle in degrees */ function calculateLeftShoulderTilt(landmarks) { const leftShoulder = getSafeLandmark(landmarks, 5); const leftElbow = getSafeLandmark(landmarks, 7); if (!leftShoulder || !leftElbow) return null; // Create vectors const upperArm = createVector(leftShoulder, leftElbow); // Calculate angle with vertical const vertical = { x: 0, y: 1, z: 0 }; return calculateAngleBetweenVectors(upperArm, vertical); } /** * Calculate right shoulder tilt (up/down angle) * @param {Array} landmarks - Array of pose landmarks * @returns {number} Right shoulder tilt angle in degrees */ function calculateRightShoulderTilt(landmarks) { const rightShoulder = getSafeLandmark(landmarks, 6); const rightElbow = getSafeLandmark(landmarks, 8); if (!rightShoulder || !rightElbow) return null; // Create vectors const upperArm = createVector(rightShoulder, rightElbow); // Calculate angle with vertical const vertical = { x: 0, y: 1, z: 0 }; return calculateAngleBetweenVectors(upperArm, vertical); } /** * Calculate shoulder flexions for both left and right sides * @param {Array} landmarks - Array of pose landmarks * @returns {Object} Object containing left and right shoulder flexion angles in degrees */ function calculateShoulderFlexions(landmarks) { return { left: calculateLeftShoulderFlexion(landmarks), right: calculateRightShoulderFlexion(landmarks) }; } /** * Calculate left shoulder flexion * @param {Array} landmarks - Array of pose landmarks * @returns {number} Left shoulder flexion angle in degrees */ function calculateLeftShoulderFlexion(landmarks) { const leftShoulder = getSafeLandmark(landmarks, 5); const leftElbow = getSafeLandmark(landmarks, 7); if (!leftShoulder || !leftElbow) return null; // Create vectors const upperArm = createVector(leftShoulder, leftElbow); // Calculate angle with vertical from top (180 - angle with vertical from bottom) const vertical = { x: 0, y: -1, z: 0 }; return calculateAngleBetweenVectors(upperArm, vertical); } /** * Calculate right shoulder flexion * @param {Array} landmarks - Array of pose landmarks * @returns {number} Right shoulder flexion angle in degrees */ function calculateRightShoulderFlexion(landmarks) { const rightShoulder = getSafeLandmark(landmarks, 6); const rightElbow = getSafeLandmark(landmarks, 8); if (!rightShoulder || !rightElbow) return null; // Create vectors const upperArm = createVector(rightShoulder, rightElbow); // Calculate angle with vertical from top (180 - angle with vertical from bottom) const vertical = { x: 0, y: -1, z: 0 }; return calculateAngleBetweenVectors(upperArm, vertical); } /** * Calculate hip rotations for both left and right sides * @param {Array} landmarks - Array of pose landmarks * @returns {Object} Object containing left and right hip rotation angles in degrees */ function calculateHipRotations(landmarks) { return { left: calculateLeftHipRotation(landmarks), right: calculateRightHipRotation(landmarks) }; } /** * Calculate left hip rotation * @param {Array} landmarks - Array of pose landmarks * @returns {number} Left hip rotation angle in degrees */ function calculateLeftHipRotation(landmarks) { const leftHip = getSafeLandmark(landmarks, 11); const leftKnee = getSafeLandmark(landmarks, 13); const rightHip = getSafeLandmark(landmarks, 12); if (!leftHip || !rightHip || !leftKnee) return null; // Create vectors const hipLine = createVector(rightHip, leftHip); const thigh = createVector(leftHip, leftKnee); // Project thigh onto horizontal plane const thighHorizontal = { x: thigh.x, y: 0, z: thigh.z || 0 }; return calculateAngleBetweenVectors(hipLine, thighHorizontal); } /** * Calculate right hip rotation * @param {Array} landmarks - Array of pose landmarks * @returns {number} Right hip rotation angle in degrees */ function calculateRightHipRotation(landmarks) { const rightHip = getSafeLandmark(landmarks, 12); const rightKnee = getSafeLandmark(landmarks, 14); const leftHip = getSafeLandmark(landmarks, 11); if (!rightHip || !leftHip || !rightKnee) return null; // Create vectors const hipLine = createVector(leftHip, rightHip); const thigh = createVector(rightHip, rightKnee); // Project thigh onto horizontal plane const thighHorizontal = { x: thigh.x, y: 0, z: thigh.z || 0 }; return calculateAngleBetweenVectors(hipLine, thighHorizontal); } /** * Calculate knee flexions for both left and right sides * @param {Array} landmarks - Array of pose landmarks * @returns {Object} Object containing left and right knee flexion angles in degrees */ function calculateKneeFlexions(landmarks) { return { left: calculateLeftKneeFlexion(landmarks), right: calculateRightKneeFlexion(landmarks) }; } /** * Calculate left knee flexion * @param {Array} landmarks - Array of pose landmarks * @returns {number} Left knee flexion angle in degrees */ function calculateLeftKneeFlexion(landmarks) { const hip = getSafeLandmark(landmarks, 11); // Left hip const knee = getSafeLandmark(landmarks, 13); // Left knee const ankle = getSafeLandmark(landmarks, 15); // Left ankle if (!hip || !knee || !ankle) return null; const thigh = createVector(hip, knee); const shin = createVector(knee, ankle); return calculateAngleBetweenVectors(thigh, shin); } /** * Calculate right knee flexion * @param {Array} landmarks - Array of pose landmarks * @returns {number} Right knee flexion angle in degrees */ function calculateRightKneeFlexion(landmarks) { const hip = getSafeLandmark(landmarks, 12); // Right hip const knee = getSafeLandmark(landmarks, 14); // Right knee const ankle = getSafeLandmark(landmarks, 16); // Right ankle if (!hip || !knee || !ankle) return null; const thigh = createVector(hip, knee); const shin = createVector(knee, ankle); return calculateAngleBetweenVectors(thigh, shin); } /** * Calculate ankle flexions for both left and right sides * @param {Array} landmarks - Array of pose landmarks * @returns {Object} Object containing left and right ankle flexion angles in degrees */ function calculateAnkleFlexions(landmarks) { return { left: calculateLeftAnkleFlexion(landmarks), right: calculateRightAnkleFlexion(landmarks) }; } /** * Calculate left ankle flexion * @param {Array} landmarks - Array of pose landmarks * @returns {number} Left ankle flexion angle in degrees */ function calculateLeftAnkleFlexion(landmarks) { const knee = getSafeLandmark(landmarks, 13); // Left knee const ankle = getSafeLandmark(landmarks, 15); // Left ankle if (!knee || !ankle) return null; // Create vectors const shin = createVector(knee, ankle); // Calculate angle with horizontal const horizontal = { x: 1, y: 0, z: 0 }; return calculateAngleBetweenVectors(shin, horizontal); } /** * Calculate right ankle flexion * @param {Array} landmarks - Array of pose landmarks * @returns {number} Right ankle flexion angle in degrees */ function calculateRightAnkleFlexion(landmarks) { const knee = getSafeLandmark(landmarks, 14); // Right knee const ankle = getSafeLandmark(landmarks, 16); // Right ankle if (!knee || !ankle) return null; // Create vectors const shin = createVector(knee, ankle); // Calculate angle with horizontal const horizontal = { x: 1, y: 0, z: 0 }; return calculateAngleBetweenVectors(shin, horizontal); } /** * Calculate wrist angles for both left and right sides * @param {Array} landmarks - Array of pose landmarks * @returns {Object} Object containing left and right wrist angles in degrees */ function calculateWristAngles(landmarks) { return { left: calculateLeftWristAngle(landmarks), right: calculateRightWristAngle(landmarks) }; } /** * Calculate left wrist angle * @param {Array} landmarks - Array of pose landmarks * @returns {number} Left wrist angle in degrees */ function calculateLeftWristAngle(landmarks) { const elbow = getSafeLandmark(landmarks, 7); // Left elbow const wrist = getSafeLandmark(landmarks, 9); // Left wrist if (!elbow || !wrist) return null; // Create vectors const forearm = createVector(elbow, wrist); // Calculate angle with horizontal const horizontal = { x: 1, y: 0, z: 0 }; return calculateAngleBetweenVectors(forearm, horizontal); } /** * Calculate right wrist angle * @param {Array} landmarks - Array of pose landmarks * @returns {number} Right wrist angle in degrees */ function calculateRightWristAngle(landmarks) { const elbow = getSafeLandmark(landmarks, 8); // Right elbow const wrist = getSafeLandmark(landmarks, 10); // Right wrist if (!elbow || !wrist) return null; // Create vectors const forearm = createVector(elbow, wrist); // Calculate angle with horizontal const horizontal = { x: 1, y: 0, z: 0 }; return calculateAngleBetweenVectors(forearm, horizontal); } /** * Calculate hip flexions for both left and right sides * @param {Array} landmarks - Array of pose landmarks * @returns {Object} Object containing left and right hip flexion angles in degrees */ function calculateHipFlexions(landmarks) { return { left: calculateLeftHipFlexion(landmarks), right: calculateRightHipFlexion(landmarks) }; } /** * Calculate left hip flexion * @param {Array} landmarks - Array of pose landmarks * @returns {number} Left hip flexion angle in degrees */ function calculateLeftHipFlexion(landmarks) { const shoulder = getSafeLandmark(landmarks, 5); // Left shoulder const hip = getSafeLandmark(landmarks, 11); // Left hip const knee = getSafeLandmark(landmarks, 13); // Left knee if (!shoulder || !hip || !knee) return null; // Create vectors const trunk = createVector(hip, shoulder); const thigh = createVector(hip, knee); // Calculate angle return calculateAngleBetweenVectors(trunk, thigh); } /** * Calculate right hip flexion * @param {Array} landmarks - Array of pose landmarks * @returns {number} Right hip flexion angle in degrees */ function calculateRightHipFlexion(landmarks) { const shoulder = getSafeLandmark(landmarks, 6); // Right shoulder const hip = getSafeLandmark(landmarks, 12); // Right hip const knee = getSafeLandmark(landmarks, 14); // Right knee if (!shoulder || !hip || !knee) return null; // Create vectors const trunk = createVector(hip, shoulder); const thigh = createVector(hip, knee); // Calculate angle return calculateAngleBetweenVectors(trunk, thigh); } /** * Calculate spine twist angle * @param {Array} landmarks - Array of pose landmarks * @returns {number} Spine twist angle in degrees */ function calculateSpineTwist(landmarks) { const leftShoulder = getSafeLandmark(landmarks, 5); const rightShoulder = getSafeLandmark(landmarks, 6); const leftHip = getSafeLandmark(landmarks, 11); const rightHip = getSafeLandmark(landmarks, 12); if (!leftShoulder || !rightShoulder || !leftHip || !rightHip) return null; // Create vectors const shoulderLine = createVector(rightShoulder, leftShoulder); const hipLine = createVector(rightHip, leftHip); // Project onto horizontal plane const shoulderHorizontal = { x: shoulderLine.x, y: 0, z: shoulderLine.z || 0 }; const hipHorizontal = { x: hipLine.x, y: 0, z: hipLine.z || 0 }; return calculateAngleBetweenVectors(shoulderHorizontal, hipHorizontal); } /** * Calculate trunk flexion angle * @param {Array} landmarks - Array of pose landmarks * @returns {number} Trunk flexion angle in degrees */ function calculateTrunkFlexion(landmarks) { const midShoulder = calculateMidpoint( getSafeLandmark(landmarks, 5), // Left shoulder getSafeLandmark(landmarks, 6) // Right shoulder ); const midHip = calculateMidpoint( getSafeLandmark(landmarks, 11), // Left hip getSafeLandmark(landmarks, 12) // Right hip ); if (!midShoulder || !midHip) return null; // Create vectors const trunk = createVector(midHip, midShoulder); // Calculate angle with vertical const vertical = { x: 0, y: 1, z: 0 }; return calculateAngleBetweenVectors(trunk, vertical); } /** * Calculate trunk lean angle * @param {Array} landmarks - Array of pose landmarks * @returns {number} Trunk lean angle in degrees */ function calculateTrunkLean(landmarks) { const midShoulder = calculateMidpoint( getSafeLandmark(landmarks, 5), // Left shoulder getSafeLandmark(landmarks, 6) // Right shoulder ); const midHip = calculateMidpoint( getSafeLandmark(landmarks, 11), // Left hip getSafeLandmark(landmarks, 12) // Right hip ); if (!midShoulder || !midHip) return null; // Create vectors const trunk = createVector(midHip, midShoulder); // Calculate angle with vertical on frontal plane const vertical = { x: 0, y: 1, z: 0 }; const trunkFrontal = { x: trunk.x, y: trunk.y, z: 0 }; return calculateAngleBetweenVectors(trunkFrontal, vertical); } /** * Calculate spine angle (curvature) * @param {Array} landmarks - Array of pose landmarks * @returns {number} Spine angle in degrees */ function calculateSpineAngle(landmarks) { // For simplicity, use trunk flexion as an approximation return calculateTrunkFlexion(landmarks); } /** * Calculate neck angle * @param {Array} landmarks - Array of pose landmarks * @returns {number} Neck angle in degrees */ function calculateNeckAngle(landmarks) { const nose = getSafeLandmark(landmarks, 0); const midShoulder = calculateMidpoint( getSafeLandmark(landmarks, 5), // Left shoulder getSafeLandmark(landmarks, 6) // Right shoulder ); if (!nose || !midShoulder) return null; // Create vectors const neck = createVector(midShoulder, nose); // Calculate angle with vertical const vertical = { x: 0, y: 1, z: 0 }; return calculateAngleBetweenVectors(neck, vertical); } /** * Calculate shoulder alignments for both left and right sides * @param {Array} landmarks - Array of pose landmarks * @returns {Object} Object containing left and right shoulder alignment values */ function calculateShoulderAlignments(landmarks) { return { left: calculateLeftShoulderAlignment(landmarks), right: calculateRightShoulderAlignment(landmarks) }; } /** * Calculate left shoulder alignment * @param {Array} landmarks - Array of pose landmarks * @returns {number} Left shoulder alignment value */ function calculateLeftShoulderAlignment(landmarks) { const leftShoulder = getSafeLandmark(landmarks, 5); const rightShoulder = getSafeLandmark(landmarks, 6); if (!leftShoulder || !rightShoulder) return null; // Calculate height difference return leftShoulder.y - rightShoulder.y; } /** * Calculate right shoulder alignment * @param {Array} landmarks - Array of pose landmarks * @returns {number} Right shoulder alignment value */ function calculateRightShoulderAlignment(landmarks) { const leftShoulder = getSafeLandmark(landmarks, 5); const rightShoulder = getSafeLandmark(landmarks, 6); if (!leftShoulder || !rightShoulder) return null; // Calculate height difference return rightShoulder.y - leftShoulder.y; } /** * Calculate hip alignments for both left and right sides * @param {Array} landmarks - Array of pose landmarks * @returns {Object} Object containing left and right hip alignment values */ function calculateHipAlignments(landmarks) { return { left: calculateLeftHipAlignment(landmarks), right: calculateRightHipAlignment(landmarks) }; } /** * Calculate left hip alignment * @param {Array} landmarks - Array of pose landmarks * @returns {number} Left hip alignment value */ function calculateLeftHipAlignment(landmarks) { const leftHip = getSafeLandmark(landmarks, 11); const rightHip = getSafeLandmark(landmarks, 12); if (!leftHip || !rightHip) return null; // Calculate height difference return leftHip.y - rightHip.y; } /** * Calculate right hip alignment * @param {Array} landmarks - Array of pose landmarks * @returns {number} Right hip alignment value */ function calculateRightHipAlignment(landmarks) { const leftHip = getSafeLandmark(landmarks, 11); const rightHip = getSafeLandmark(landmarks, 12); if (!leftHip || !rightHip) return null; // Calculate height difference return rightHip.y - leftHip.y; } /** * Calculate torso to ground angle * @param {Array} landmarks - Array of pose landmarks * @returns {number} Torso to ground angle in degrees */ function calculateTorsoToGround(landmarks) { // Same as trunk flexion return calculateTrunkFlexion(landmarks); } /** * Calculate midpoint between two landmarks * @param {Object} point1 - First landmark * @param {Object} point2 - Second landmark * @returns {Object} Midpoint as {x,y,z} */ function calculateMidpoint(point1, point2) { if (!point1 || !point2) return null; const p1 = standardizePointFormat(point1); const p2 = standardizePointFormat(point2); if (!p1 || !p2) return null; return { x: (p1.x + p2.x) / 2, y: (p1.y + p2.y) / 2, z: (p1.z + p2.z) / 2 }; } module.exports = { calculateArmAngles, calculateElbowFlexions, calculateShoulderRotations, calculateShoulderTilts, calculateShoulderFlexions, calculateWristAngles, calculateHipRotations, calculateKneeFlexions, calculateAnkleFlexions, calculateHipFlexions, calculateSpineTwist, calculateTrunkFlexion, calculateTrunkLean, calculateSpineAngle, calculateNeckAngle, calculateShoulderAlignments, calculateHipAlignments, calculateTorsoToGround };