bowling-analysis-system
Version:
A comprehensive system for analyzing bowling techniques using video processing and metrics calculation
293 lines (255 loc) • 10.5 kB
JavaScript
/**
* @module bowling_analysis/metrics/calculators/VelocityCalculator
* @description Calculator for velocity-related metrics
*/
const { calculateVelocity, calculateJointVelocity } = require('../calculations/MetricsUtilities');
const velocityCalculations = require('../calculations/VelocityCalculations');
const {
calculateWristVelocities,
calculateElbowVelocities,
calculateShoulderVelocities,
calculateHipVelocities,
calculateKneeVelocities,
calculateAnkleVelocities,
calculateArmVelocities,
calculateLegVelocities,
calculateTorsoVelocity,
calculateApproachVelocity,
calculateBallVelocity,
calculateHeadVelocity,
calculateSpineVelocity,
calculateAngularVelocity,
calculateRotationalVelocity
} = require('../calculations/VelocityCalculations');
/**
* Calculate velocity-related metrics
* @param {Array} keypointData - Array of keypoint frames
* @param {Array} validFrames - Array of valid keypoint frames
* @param {Object} options - Calculator options
* @returns {Promise<Object>} Velocity metrics
*/
async function calculate(keypointData, validFrames, options = {}) {
try {
const { debug, includeTimeSeries } = options;
// Initialize result
const result = {};
// Initialize time series
const timeSeries = {};
// Process joint velocities (with left/right variants)
const jointVelocities = [
'armVelocities',
'shoulderVelocities',
'elbowVelocities',
'wristVelocities',
'hipVelocities',
'kneeVelocities',
'legVelocities',
'ankleVelocities',
'spineVelocities',
'headVelocities',
'footVelocities',
'handVelocities'
];
// Process individual velocities (no left/right)
const individualVelocities = [
'torsoVelocity',
'approachVelocity',
'ballVelocity',
'angularVelocity',
'rotationalVelocity'
];
// Process joint velocities
for (const velocityName of jointVelocities) {
// Calculate average metrics for each joint
const leftSum = [];
const rightSum = [];
// Create time series data if enabled
const leftValues = Array(keypointData.length).fill(null);
const rightValues = Array(keypointData.length).fill(null);
// Process each valid frame
for (let i = 1; i < validFrames.length; i++) {
const currentFrameIndex = validFrames[i].index || i;
const prevFrameIndex = validFrames[i-1].index || (i-1);
if (currentFrameIndex >= keypointData.length || prevFrameIndex >= keypointData.length) {
continue;
}
const currentFrame = keypointData[currentFrameIndex];
const prevFrame = keypointData[prevFrameIndex];
if (!currentFrame || !prevFrame) {
continue;
}
// Get timestamps if available
const currTimestamp = currentFrame.timestamp;
const prevTimestamp = prevFrame.timestamp;
// Calculate time delta in ms
const timeDelta = (currTimestamp && prevTimestamp) ?
(currTimestamp - prevTimestamp) : 33.33; // Default to 30fps
// Calculate velocity based on velocity type
let velocity = null;
switch(velocityName) {
case 'wristVelocities':
velocity = calculateWristVelocities(currentFrame, prevFrame, timeDelta);
break;
case 'elbowVelocities':
velocity = calculateElbowVelocities(currentFrame, prevFrame, timeDelta);
break;
case 'shoulderVelocities':
velocity = calculateShoulderVelocities(currentFrame, prevFrame, timeDelta);
break;
case 'hipVelocities':
velocity = calculateHipVelocities(currentFrame, prevFrame, timeDelta);
break;
case 'kneeVelocities':
velocity = calculateKneeVelocities(currentFrame, prevFrame, timeDelta);
break;
case 'ankleVelocities':
velocity = calculateAnkleVelocities(currentFrame, prevFrame, timeDelta);
break;
case 'armVelocities':
velocity = calculateArmVelocities(currentFrame, prevFrame, timeDelta);
break;
case 'legVelocities':
velocity = calculateLegVelocities(currentFrame, prevFrame, timeDelta);
break;
case 'spineVelocities':
// Use spine velocities from velocityCalculations
const leftSpineVelocity = velocityCalculations.calculateLeftSpineVelocity(currentFrame, prevFrame, timeDelta);
const rightSpineVelocity = velocityCalculations.calculateRightSpineVelocity(currentFrame, prevFrame, timeDelta);
velocity = {
left: leftSpineVelocity,
right: rightSpineVelocity
};
break;
case 'headVelocities':
// Use head velocity from velocityCalculations
const headVelocity = velocityCalculations.calculateHeadVelocity(currentFrame, prevFrame, timeDelta);
velocity = {
left: headVelocity,
right: headVelocity
};
break;
case 'footVelocities':
// Use ankle velocities from velocityCalculations
const leftAnkleVelocity = velocityCalculations.calculateLeftAnkleVelocity(currentFrame, prevFrame, timeDelta);
const rightAnkleVelocity = velocityCalculations.calculateRightAnkleVelocity(currentFrame, prevFrame, timeDelta);
velocity = {
left: leftAnkleVelocity,
right: rightAnkleVelocity
};
break;
case 'handVelocities':
// Use wrist velocities from velocityCalculations
const leftWristVelocity = velocityCalculations.calculateLeftWristVelocity(currentFrame, prevFrame, timeDelta);
const rightWristVelocity = velocityCalculations.calculateRightWristVelocity(currentFrame, prevFrame, timeDelta);
velocity = {
left: leftWristVelocity,
right: rightWristVelocity
};
break;
default:
// For other velocities, use placeholder until implemented
velocity = { left: null, right: null };
}
// Store calculated values
if (velocity) {
if (velocity.left !== null) {
leftValues[currentFrameIndex] = velocity.left;
leftSum.push(velocity.left);
}
if (velocity.right !== null) {
rightValues[currentFrameIndex] = velocity.right;
rightSum.push(velocity.right);
}
}
}
// Calculate average metrics
const leftAvg = leftSum.length > 0 ? leftSum.reduce((a, b) => a + b, 0) / leftSum.length : 0;
const rightAvg = rightSum.length > 0 ? rightSum.reduce((a, b) => a + b, 0) / rightSum.length : 0;
// Calculate asymmetry
const asymmetry = (leftAvg + rightAvg > 0) ?
Math.abs(leftAvg - rightAvg) / Math.max(leftAvg, rightAvg) : 0;
// Store results
result[velocityName] = {
left: leftAvg,
right: rightAvg,
asymmetry: asymmetry
};
// Add time series data if enabled
if (includeTimeSeries) {
timeSeries[`${velocityName}.left`] = leftValues;
timeSeries[`${velocityName}.right`] = rightValues;
}
}
// Process individual velocities
for (const velocityName of individualVelocities) {
// Calculate average metrics for each velocity
const values = Array(keypointData.length).fill(null);
const valueSum = [];
// Process each valid frame
for (let i = 1; i < validFrames.length; i++) {
const currentFrameIndex = validFrames[i].index || i;
const prevFrameIndex = validFrames[i-1].index || (i-1);
if (currentFrameIndex >= keypointData.length || prevFrameIndex >= keypointData.length) {
continue;
}
const currentFrame = keypointData[currentFrameIndex];
const prevFrame = keypointData[prevFrameIndex];
if (!currentFrame || !prevFrame) {
continue;
}
// Get timestamps if available
const currTimestamp = currentFrame.timestamp;
const prevTimestamp = prevFrame.timestamp;
// Calculate time delta in ms
const timeDelta = (currTimestamp && prevTimestamp) ?
(currTimestamp - prevTimestamp) : 33.33; // Default to 30fps
// Calculate velocity based on velocity type
let velocity = null;
switch(velocityName) {
case 'ballVelocity':
velocity = calculateBallVelocity(currentFrame, prevFrame, timeDelta);
break;
case 'angularVelocity':
velocity = calculateAngularVelocity(currentFrame, prevFrame, timeDelta);
break;
case 'rotationalVelocity':
velocity = calculateRotationalVelocity(currentFrame, prevFrame, timeDelta);
break;
case 'torsoVelocity':
velocity = velocityCalculations.calculateTorsoVelocity(currentFrame, prevFrame, timeDelta);
break;
case 'approachVelocity':
velocity = velocityCalculations.calculateApproachVelocity(currentFrame, prevFrame, timeDelta);
break;
default:
// For other velocities, use null until implemented
velocity = null;
}
// Store calculated values
if (velocity !== null) {
values[currentFrameIndex] = velocity;
valueSum.push(velocity);
}
}
// Calculate average value
const average = valueSum.length > 0 ? valueSum.reduce((a, b) => a + b, 0) / valueSum.length : 0;
// Store result
result[velocityName] = average;
// Add time series data if enabled
if (includeTimeSeries) {
timeSeries[velocityName] = values;
}
}
// Add time series data if enabled
if (includeTimeSeries) {
result.timeSeries = timeSeries;
}
return result;
} catch (error) {
console.error(`Error calculating velocity metrics: ${error.message}`);
return {};
}
}
module.exports = {
calculate
};