307 lines
9.3 KiB
TypeScript
307 lines
9.3 KiB
TypeScript
import { useEffect, useRef, useState, useCallback } from 'react';
|
|
import type { DetectionConfig, DetectionResult, DetectionMetrics } from './types';
|
|
import { DetectionEngine } from './detection-engine';
|
|
|
|
interface UseDetectionOptions {
|
|
modelVariant?: 'quantized' | 'standard' | 'full';
|
|
enableContinuous?: boolean;
|
|
enableTrigger?: boolean;
|
|
onDetection?: (detection: DetectionResult | null) => void;
|
|
onError?: (error: Error) => void;
|
|
}
|
|
|
|
interface UseDetectionReturn {
|
|
// State
|
|
isLoading: boolean;
|
|
isDetecting: boolean;
|
|
currentDetection: DetectionResult | null;
|
|
metrics: DetectionMetrics | null;
|
|
error: string | null;
|
|
|
|
// Actions
|
|
initialize: (videoElement: HTMLVideoElement) => Promise<DetectionEngine>;
|
|
startContinuous: () => void;
|
|
stopContinuous: () => void;
|
|
triggerDetection: () => Promise<DetectionResult | null>;
|
|
updateConfig: (config: Partial<DetectionConfig>) => Promise<void>;
|
|
setDetectionCallback: (callback: (detection: DetectionResult | null) => void) => void;
|
|
|
|
// Config
|
|
config: DetectionConfig | null;
|
|
|
|
// Engine reference
|
|
detectionEngine: DetectionEngine | null;
|
|
}
|
|
|
|
/**
|
|
* React hook for shoe detection functionality
|
|
*/
|
|
export function useDetection(options: UseDetectionOptions = {}): UseDetectionReturn {
|
|
const {
|
|
modelVariant = 'standard',
|
|
enableContinuous = true,
|
|
enableTrigger = true,
|
|
onDetection,
|
|
onError
|
|
} = options;
|
|
|
|
// Store the callback in a ref so it can be updated
|
|
const detectionCallbackRef = useRef<((detection: DetectionResult | null) => void) | undefined>(onDetection);
|
|
|
|
// State
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const [isDetecting, setIsDetecting] = useState(false);
|
|
const [currentDetection, setCurrentDetection] = useState<DetectionResult | null>(null);
|
|
const [metrics, setMetrics] = useState<DetectionMetrics | null>(null);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [config, setConfig] = useState<DetectionConfig | null>(null);
|
|
|
|
// Refs
|
|
const detectionEngineRef = useRef<DetectionEngine | null>(null);
|
|
const videoElementRef = useRef<HTMLVideoElement | null>(null);
|
|
const initializationPromiseRef = useRef<Promise<void> | null>(null);
|
|
|
|
// Initialize detection engine
|
|
const initialize = useCallback(async (videoElement: HTMLVideoElement): Promise<DetectionEngine> => {
|
|
console.log('🚀 useDetection.initialize called:', { videoElement: !!videoElement });
|
|
|
|
// Prevent multiple initializations
|
|
if (initializationPromiseRef.current) {
|
|
console.log('⚠️ Initialization already in progress, returning existing promise');
|
|
return initializationPromiseRef.current;
|
|
}
|
|
|
|
setIsLoading(true);
|
|
setError(null);
|
|
|
|
const initPromise = (async () => {
|
|
try {
|
|
console.log('🏗️ Creating detection engine...');
|
|
// Create detection engine
|
|
const engine = new DetectionEngine();
|
|
detectionEngineRef.current = engine;
|
|
videoElementRef.current = videoElement;
|
|
|
|
// Set up event listeners
|
|
engine.onDetection((detection) => {
|
|
setCurrentDetection(detection);
|
|
detectionCallbackRef.current?.(detection);
|
|
});
|
|
|
|
engine.onMetrics((newMetrics) => {
|
|
setMetrics(newMetrics);
|
|
});
|
|
|
|
// Initialize with progress tracking
|
|
await engine.initialize(modelVariant, (progress) => {
|
|
// You could add progress state here if needed
|
|
console.log(`Model loading: ${progress.toFixed(1)}%`);
|
|
});
|
|
|
|
// Get initial configuration
|
|
const initialConfig = engine.getConfig();
|
|
setConfig(initialConfig);
|
|
|
|
console.log('Detection hook initialized successfully');
|
|
return engine; // Return engine instance
|
|
|
|
} catch (err) {
|
|
const error = err instanceof Error ? err : new Error('Unknown initialization error');
|
|
console.error('Detection initialization failed:', error);
|
|
setError(error.message);
|
|
onError?.(error);
|
|
throw error;
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
})();
|
|
|
|
initializationPromiseRef.current = initPromise;
|
|
return initPromise;
|
|
}, [modelVariant, onDetection, onError]);
|
|
|
|
// Start continuous detection
|
|
const startContinuous = useCallback(() => {
|
|
console.log('🔄 useDetection.startContinuous called:', {
|
|
hasEngine: !!detectionEngineRef.current,
|
|
hasVideo: !!videoElementRef.current,
|
|
enableContinuous
|
|
});
|
|
|
|
if (!detectionEngineRef.current || !videoElementRef.current) {
|
|
console.warn('Detection engine or video element not available');
|
|
return;
|
|
}
|
|
|
|
if (!enableContinuous) {
|
|
console.warn('Continuous detection is disabled');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
console.log('🚀 Starting continuous detection...');
|
|
detectionEngineRef.current.startContinuousDetection(videoElementRef.current);
|
|
setIsDetecting(true);
|
|
setError(null);
|
|
console.log('✅ Continuous detection started successfully');
|
|
} catch (err) {
|
|
const error = err instanceof Error ? err : new Error('Failed to start continuous detection');
|
|
console.error('❌ Start continuous detection failed:', error);
|
|
setError(error.message);
|
|
onError?.(error);
|
|
}
|
|
}, [enableContinuous, onError]);
|
|
|
|
// Stop continuous detection
|
|
const stopContinuous = useCallback(() => {
|
|
if (!detectionEngineRef.current) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
detectionEngineRef.current.stopContinuousDetection();
|
|
setIsDetecting(false);
|
|
setCurrentDetection(null);
|
|
} catch (err) {
|
|
console.error('Stop continuous detection failed:', err);
|
|
}
|
|
}, []);
|
|
|
|
// Trigger single detection
|
|
const triggerDetection = useCallback(async (): Promise<DetectionResult | null> => {
|
|
if (!detectionEngineRef.current || !videoElementRef.current) {
|
|
throw new Error('Detection engine or video element not available');
|
|
}
|
|
|
|
if (!enableTrigger) {
|
|
throw new Error('Trigger detection is disabled');
|
|
}
|
|
|
|
try {
|
|
setError(null);
|
|
const detection = await detectionEngineRef.current.triggerDetection(videoElementRef.current);
|
|
|
|
// Update current detection state
|
|
setCurrentDetection(detection);
|
|
detectionCallbackRef.current?.(detection);
|
|
|
|
return detection;
|
|
} catch (err) {
|
|
const error = err instanceof Error ? err : new Error('Trigger detection failed');
|
|
console.error('Trigger detection failed:', error);
|
|
setError(error.message);
|
|
onError?.(error);
|
|
throw error;
|
|
}
|
|
}, [enableTrigger, onError]);
|
|
|
|
// Update configuration
|
|
const updateConfig = useCallback(async (newConfig: Partial<DetectionConfig>): Promise<void> => {
|
|
if (!detectionEngineRef.current) {
|
|
throw new Error('Detection engine not available');
|
|
}
|
|
|
|
try {
|
|
await detectionEngineRef.current.updateConfig(newConfig);
|
|
const updatedConfig = detectionEngineRef.current.getConfig();
|
|
setConfig(updatedConfig);
|
|
setError(null);
|
|
} catch (err) {
|
|
const error = err instanceof Error ? err : new Error('Failed to update configuration');
|
|
console.error('Update config failed:', error);
|
|
setError(error.message);
|
|
onError?.(error);
|
|
throw error;
|
|
}
|
|
}, [onError]);
|
|
|
|
// Cleanup on unmount
|
|
useEffect(() => {
|
|
return () => {
|
|
if (detectionEngineRef.current) {
|
|
detectionEngineRef.current.destroy();
|
|
detectionEngineRef.current = null;
|
|
}
|
|
initializationPromiseRef.current = null;
|
|
videoElementRef.current = null;
|
|
};
|
|
}, []);
|
|
|
|
return {
|
|
// State
|
|
isLoading,
|
|
isDetecting,
|
|
currentDetection,
|
|
metrics,
|
|
error,
|
|
|
|
// Actions
|
|
initialize,
|
|
startContinuous,
|
|
stopContinuous,
|
|
triggerDetection,
|
|
updateConfig,
|
|
setDetectionCallback: (callback: (detection: DetectionResult | null) => void) => {
|
|
detectionCallbackRef.current = callback;
|
|
},
|
|
|
|
// Config
|
|
config,
|
|
|
|
// Engine reference
|
|
detectionEngine: detectionEngineRef.current
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Hook for detection metrics monitoring
|
|
*/
|
|
export function useDetectionMetrics(detectionEngine: DetectionEngine | null) {
|
|
const [metrics, setMetrics] = useState<DetectionMetrics | null>(null);
|
|
|
|
useEffect(() => {
|
|
if (!detectionEngine) return;
|
|
|
|
detectionEngine.onMetrics(setMetrics);
|
|
|
|
// Get initial metrics
|
|
const initialMetrics = detectionEngine.getMetrics();
|
|
setMetrics(initialMetrics);
|
|
}, [detectionEngine]);
|
|
|
|
return metrics;
|
|
}
|
|
|
|
/**
|
|
* Hook for performance monitoring and adjustments
|
|
*/
|
|
export function usePerformanceOptimization(detectionEngine: DetectionEngine | null) {
|
|
const [recommendations, setRecommendations] = useState<string[]>([]);
|
|
|
|
useEffect(() => {
|
|
if (!detectionEngine) return;
|
|
|
|
const interval = setInterval(() => {
|
|
const metrics = detectionEngine.getMetrics();
|
|
const newRecommendations: string[] = [];
|
|
|
|
if (metrics.fps < 15) {
|
|
newRecommendations.push('Consider increasing frame skip or switching to a lighter model');
|
|
}
|
|
|
|
if (metrics.inferenceTime > 100) {
|
|
newRecommendations.push('Inference time is high, consider switching to quantized model');
|
|
}
|
|
|
|
if (metrics.memoryUsage > 100) {
|
|
newRecommendations.push('High memory usage detected');
|
|
}
|
|
|
|
setRecommendations(newRecommendations);
|
|
}, 5000); // Check every 5 seconds
|
|
|
|
return () => clearInterval(interval);
|
|
}, [detectionEngine]);
|
|
|
|
return recommendations;
|
|
} |