UNPKG

bowling-analysis-system

Version:

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

186 lines (159 loc) 4.9 kB
/** * @module bowling_analysis/utils/MetricsUtils * @description Centralized metrics processing utilities */ const { DEFAULT_THRESHOLDS } = require('./BiasConfigManager'); const { defaultLogger } = require('../../utils/logger'); const logger = defaultLogger.child('MetricsUtils'); /** * Get total frame count from metrics data * @param {Object} metrics - Metrics data * @returns {number} Total frame count */ function getTotalFrames(metrics) { if (!metrics) { logger.warn('No metrics provided for frame count determination'); return 0; } // Try metadata first if (metrics.metadata?.totalFrames) { return metrics.metadata.totalFrames; } if (metrics.metadata?.frameCount) { return metrics.metadata.frameCount; } // Check frames array if (Array.isArray(metrics.frames)) { return metrics.frames.length; } // Check time series lengths if (metrics.timeSeries) { const lengths = Object.values(metrics.timeSeries) .map(series => Array.isArray(series) ? series.length : 0); if (lengths.length > 0) { return Math.max(...lengths); } } logger.warn('Could not determine frame count, using default'); return 77; // Default fallback } /** * Filter relevant metrics for event detection * @param {Object} metrics - Metrics data * @param {string} eventType - Event type * @returns {Object} Filtered metrics */ function filterRelevantMetrics(metrics, eventType) { const relevantMetrics = {}; const metricPaths = getMetricPathsForEvent(eventType); for (const path of metricPaths) { const value = getMetricValueByPath(metrics, path); if (value !== undefined) { relevantMetrics[path] = value; } } return relevantMetrics; } /** * Calculate correlation between metrics * @param {Array} series1 - First time series * @param {Array} series2 - Second time series * @param {Object} options - Correlation options * @returns {number} Correlation value */ function calculateCorrelation(series1, series2, options = {}) { if (!Array.isArray(series1) || !Array.isArray(series2)) { return 0; } const { type = 'pearson', windowSize = 5, threshold = DEFAULT_THRESHOLDS.BIAS_CORRELATION_THRESHOLD } = options; switch (type) { case 'position': return calculatePositionCorrelation(series1, series2, windowSize); case 'angle': return calculateAngleCorrelation(series1, series2, windowSize); case 'peak': return calculatePeakCorrelation(series1, series2, threshold); default: return calculateDefaultCorrelation(series1, series2); } } /** * Get metric value by path * @private */ function getMetricValueByPath(metrics, path) { const parts = path.split('.'); let value = metrics; for (const part of parts) { if (value === undefined || value === null) return undefined; value = value[part]; } return value; } /** * Get relevant metric paths for event type * @private */ function getMetricPathsForEvent(eventType) { // Map of event types to relevant metric paths const eventMetricMap = { releasePoint: [ 'angles.wristAngles', 'angles.elbowFlexions', 'angles.shoulderRotations' ], frontFootLanding: [ 'angles.kneeFlexions', 'angles.ankleFlexions', 'balance.weightDistributions' ], backFootLanding: [ 'angles.hipRotations', 'angles.kneeFlexions', 'balance.posturalSways' ] }; return eventMetricMap[eventType] || []; } // Private correlation calculation methods function calculatePositionCorrelation(series1, series2, windowSize) { // Position-specific correlation logic return calculateDefaultCorrelation(series1, series2); } function calculateAngleCorrelation(series1, series2, windowSize) { // Angle-specific correlation logic return calculateDefaultCorrelation(series1, series2); } function calculatePeakCorrelation(series1, series2, threshold) { // Peak detection correlation logic return calculateDefaultCorrelation(series1, series2); } function calculateDefaultCorrelation(series1, series2) { const n = Math.min(series1.length, series2.length); if (n < 2) return 0; // Calculate means const mean1 = series1.reduce((a, b) => a + b, 0) / n; const mean2 = series2.reduce((a, b) => a + b, 0) / n; // Calculate correlation coefficient let num = 0; let den1 = 0; let den2 = 0; for (let i = 0; i < n; i++) { const x = series1[i] - mean1; const y = series2[i] - mean2; num += x * y; den1 += x * x; den2 += y * y; } if (den1 === 0 || den2 === 0) return 0; return num / Math.sqrt(den1 * den2); } module.exports = { getTotalFrames, filterRelevantMetrics, calculateCorrelation };