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