bowling-analysis-system
Version:
A comprehensive system for analyzing bowling techniques using video processing and metrics calculation
155 lines (136 loc) • 5.49 kB
JavaScript
/**
* @module bowling_analysis/pipeline
* @description Main pipeline for bowling metrics processing
*/
const path = require('path');
const fs = require('fs');
const { performance } = require('perf_hooks');
// Using the modern pipeline implementation
const { processBowlingMetrics: processMetrics } = require('./metrics/index');
const { FileLoader } = require('./stages/file_loader');
const { KeypointProcessor } = require('./stages/keypoint_processor');
const { FileSaver } = require('./stages/file_saver');
const { defaultLogger } = require('../utils/logger');
/**
* Process bowling metrics from keypoint data through the complete pipeline
* @param {Array} keypointData - Array of keypoint frames
* @param {Object} options - Pipeline options
* @param {string} [options.biasPath] - Path to existing bias file (optional)
* @param {boolean} [options.debug] - Enable debug logging
* @param {boolean} [options.preserveAllFrames=true] - Preserve all original frames
* @returns {Promise<Object>} - The complete metrics result
*/
async function processBowlingMetrics(keypointData, options = {}) {
const {
biasPath,
debug = false,
preserveAllFrames = true,
outputPath = path.join(process.cwd(), 'complete_metrics.json')
} = options;
const logger = defaultLogger.child('Pipeline');
if (debug) logger.setLevel('debug');
const startTime = performance.now();
const timings = {};
try {
logger.info('Starting multi-phase bowling metrics pipeline');
// Initialize pipeline stages
const keypointProcessor = new KeypointProcessor();
const fileSaver = new FileSaver();
// Stage 0: Keypoint processing
const t0 = performance.now();
logger.info('Stage 0: Keypoint processing');
const processedKeypoints = await keypointProcessor.execute({
keypointData: { frames: keypointData },
options: { preserveAllFrames, debug }
});
timings.keypointProcessing = performance.now() - t0;
// Stage 1: Phase One metrics
const { PhaseOneProcessor } = require('./metrics/PhaseOneProcessor');
const phaseOne = new PhaseOneProcessor({ debug });
const t1 = performance.now();
logger.info('Stage 1: Phase One metrics');
const phaseOneResult = await phaseOne.process({
keypointData: processedKeypoints.frames || processedKeypoints
});
timings.phaseOne = performance.now() - t1;
// Stage 2: Phase Two metrics
const { PhaseTwoProcessor } = require('./metrics/PhaseTwoProcessor');
const phaseTwo = new PhaseTwoProcessor({ debug, biasPath });
const t2 = performance.now();
logger.info('Stage 2: Phase Two metrics');
const phaseTwoResult = await phaseTwo.process(phaseOneResult);
timings.phaseTwo = performance.now() - t2;
// Stage 3: Phase Three metrics (composite)
const t3 = performance.now();
logger.info('Stage 3: Phase Three metrics');
const phaseThreeMetrics = {};
const phaseThreeTimeSeries = {};
// Example: combine phase one + phase two outputs
// (In practice, add more composite calculations here)
const calculations = require('./metrics/calculations');
// Placeholder: no-op, but you can add composite metrics here
// e.g., phaseThreeMetrics.someMetric = calculations.someFunction(phaseOneResult, phaseTwoResult);
timings.phaseThree = performance.now() - t3;
// Merge all outputs
const result = {
metrics: {
...phaseOneResult.metrics,
...phaseTwoResult.eventMetrics,
...phaseThreeMetrics
},
timeSeries: {
...phaseOneResult.timeSeries,
...phaseTwoResult.timeSeries,
...phaseThreeTimeSeries
},
events: phaseTwoResult.events,
bias: phaseTwoResult.bias,
metadata: {
totalFrames: keypointData.length,
timings
}
};
// Log time series data for debugging
logger.debug('Time series data in final result:');
if (result.timeSeries) {
Object.keys(result.timeSeries).forEach(category => {
if (result.timeSeries[category]) {
logger.debug(`Category: ${category}`);
Object.keys(result.timeSeries[category]).forEach(metric => {
const values = result.timeSeries[category][metric];
if (Array.isArray(values)) {
const nonNullCount = values.filter(v => v !== null).length;
logger.debug(` ${metric}: ${nonNullCount} non-null values`);
}
});
}
});
}
// Ensure balance metrics are included
if (!result.metrics.balance && phaseOneResult.metrics && phaseOneResult.metrics.balance) {
result.metrics.balance = phaseOneResult.metrics.balance;
logger.info(`Added ${Object.keys(phaseOneResult.metrics.balance).length} balance metrics to final output`);
} else if (phaseOneResult.metrics && phaseOneResult.metrics.balance) {
logger.info(`Including ${Object.keys(phaseOneResult.metrics.balance).length} balance metrics in final output`);
} else {
logger.warn('No balance metrics found in phase one result');
}
// Save results
if (outputPath) {
logger.info('Saving results to file...');
await fileSaver.execute({
context: result,
outputPath,
options: { debug }
});
}
logger.info('Pipeline completed successfully');
return result;
} catch (error) {
logger.error('Pipeline failed:', error);
throw error;
}
}
module.exports = {
processBowlingMetrics
};