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