UNPKG

bowling-analysis-system

Version:

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

201 lines (169 loc) 6.45 kB
/** * @module core/utils/FrameUtils * @description Centralized utilities for frame operations and event detection */ /** * Find the closest frame index to the given event index in an array of frame indices * @param {number} eventIndex - The frame index of the event * @param {Array<number>} validFrames - Array of valid frame indices * @returns {number} The closest valid frame index or the original event index if not found */ function findClosestFrameIndex(eventIndex, validFrames) { if (!validFrames || !validFrames.length) { return eventIndex; } // Check for direct match if (validFrames.includes(eventIndex)) { return eventIndex; } // Find the closest frame by index return validFrames.reduce((closest, current) => { return Math.abs(current - eventIndex) < Math.abs(closest - eventIndex) ? current : closest; }, validFrames[0]); } /** * Find the closest frame object to a given event index * @param {Array} validFrames - Array of frame objects * @param {number} eventIndex - The frame index to find * @param {string} [indexProperty='frameIndex'] - The property name for frame index * @returns {Object|null} The closest valid frame or null if not found */ function findClosestFrame(validFrames, eventIndex, indexProperty = 'frameIndex') { if (!validFrames || !validFrames.length || eventIndex === undefined || eventIndex === null) { return null; } // First try to find exact match for (const frame of validFrames) { if (frame && frame[indexProperty] === eventIndex) { return frame; } } // If not found, find closest frame let closestFrame = null; let minDistance = Number.MAX_VALUE; for (const frame of validFrames) { if (!frame || frame[indexProperty] === undefined) continue; const distance = Math.abs(frame[indexProperty] - eventIndex); if (distance < minDistance) { minDistance = distance; closestFrame = frame; } } return closestFrame; } /** * Find the index of the closest value in an array to a target value * @param {Array} array - Array of values * @param {number} target - Target value * @returns {number} Index of the closest value */ function findClosestIndex(array, target) { if (!array || !array.length) { return -1; } let closest = 0; let minDistance = Math.abs(array[0] - target); for (let i = 1; i < array.length; i++) { const distance = Math.abs(array[i] - target); if (distance < minDistance) { minDistance = distance; closest = i; } } return closest; } /** * Find frames within a specified range around a central frame * @param {number} centerFrame - The central frame index * @param {number} rangeSize - Number of frames to include before and after * @param {Array<number>} validFrames - Array of valid frame indices * @returns {Array<number>} Array of frame indices within the range */ function getFramesInRange(centerFrame, rangeSize, validFrames) { if (!validFrames || !validFrames.length) { return []; } const closestFrame = findClosestFrameIndex(centerFrame, validFrames); const rangeFrames = validFrames.filter( frame => Math.abs(frame - closestFrame) <= rangeSize ); return rangeFrames.sort((a, b) => a - b); } /** * Find frames between two event frames * @param {number} startEventIndex - Starting event frame index * @param {number} endEventIndex - Ending event frame index * @param {Array<number>} validFrames - Array of valid frame indices * @returns {Array<number>} Array of frame indices between the events */ function getFramesBetweenEvents(startEventIndex, endEventIndex, validFrames) { if (!validFrames || !validFrames.length) { return []; } const startFrame = findClosestFrameIndex(startEventIndex, validFrames); const endFrame = findClosestFrameIndex(endEventIndex, validFrames); const minFrame = Math.min(startFrame, endFrame); const maxFrame = Math.max(startFrame, endFrame); return validFrames.filter( frame => frame >= minFrame && frame <= maxFrame ).sort((a, b) => a - b); } /** * Calculate the percentage of the motion between two events * @param {number} currentFrame - Current frame index * @param {number} startEventIndex - Starting event frame index * @param {number} endEventIndex - Ending event frame index * @returns {number} Percentage of motion completion (0-1) */ function calculateMotionPercentage(currentFrame, startEventIndex, endEventIndex) { if (startEventIndex === endEventIndex) { return currentFrame >= startEventIndex ? 1 : 0; } const totalFrames = Math.abs(endEventIndex - startEventIndex); const framesPassed = Math.abs(currentFrame - startEventIndex); const percentage = framesPassed / totalFrames; return Math.max(0, Math.min(1, percentage)); } /** * Find the closest frame in an array by index or timestamp * @param {Array} frames - Array of frame objects * @param {Object} event - Event object with frameIndex or timestamp * @param {string} [indexProperty='index'] - Property name for frame index * @returns {number} Index in the frames array or -1 if not found */ function findFrameIndexByEvent(frames, event, indexProperty = 'index') { if (!frames || !frames.length || !event) { return -1; } // Try direct match by frameIndex or timestamp let frameIndex = frames.findIndex(frame => (event.frameIndex !== undefined && frame[indexProperty] === event.frameIndex) || (event.timestamp !== undefined && frame.timestamp === event.timestamp) ); // If not found, find the closest frame by index if (frameIndex < 0 && event.frameIndex !== undefined) { let minDiff = Infinity; let closestIndex = -1; for (let i = 0; i < frames.length; i++) { if (frames[i][indexProperty] === undefined) continue; const diff = Math.abs(frames[i][indexProperty] - event.frameIndex); if (diff < minDiff) { minDiff = diff; closestIndex = i; } } frameIndex = closestIndex; } return frameIndex; } module.exports = { findClosestFrameIndex, findClosestFrame, findClosestIndex, getFramesInRange, getFramesBetweenEvents, calculateMotionPercentage, findFrameIndexByEvent };