bowling-analysis-system
Version:
A comprehensive system for analyzing bowling techniques using video processing and metrics calculation
777 lines (636 loc) • 22.9 kB
JavaScript
/**
* @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
};