UNPKG

bowling-analysis-system

Version:

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

293 lines (255 loc) 10.5 kB
/** * @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 };