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