| """ |
| Reference Video Processor |
| Processes reference videos once and saves noisy samples for scoring |
| """ |
|
|
| import os |
| import sys |
| import numpy as np |
| import json |
| from pathlib import Path |
|
|
| |
| project_root = Path(__file__).parent.parent |
| sys.path.insert(0, str(project_root)) |
| sys.path.insert(0, str(project_root / 'demo')) |
|
|
| from fitness_coach.noise_scoring import create_noisy_samples, calculate_statistical_bounds |
| from fitness_coach.body_parts import calculate_body_scale, get_joints_for_exercise |
|
|
|
|
| def process_reference_video(video_path, exercise_type='pushup', output_dir=None, n_samples=100): |
| """ |
| Process a reference video and generate noisy samples for scoring |
| |
| Args: |
| video_path: Path to reference video file |
| exercise_type: Type of exercise (e.g., 'pushup', 'squat') |
| output_dir: Directory to save processed data (default: references/{exercise_type}/) |
| n_samples: Number of noisy samples to generate |
| |
| Returns: |
| Dictionary with paths to saved files and metadata |
| """ |
| |
| original_cwd = os.getcwd() |
| os.chdir(project_root) |
| |
| try: |
| |
| from demo.vis import get_pose2D, get_pose3D |
| finally: |
| os.chdir(original_cwd) |
| |
| video_path = Path(video_path) |
| if not video_path.exists(): |
| raise FileNotFoundError(f"Video not found: {video_path}") |
| |
| |
| if output_dir is None: |
| output_dir = Path('references') / exercise_type |
| else: |
| output_dir = Path(output_dir) |
| |
| output_dir.mkdir(parents=True, exist_ok=True) |
| |
| print(f"Processing reference video: {video_path.name}") |
| print(f"Exercise type: {exercise_type}") |
| print(f"Output directory: {output_dir}") |
| |
| |
| temp_output = output_dir / 'temp_processing' |
| temp_output.mkdir(exist_ok=True) |
| |
| |
| |
| temp_output_abs = temp_output.resolve() |
| output_dir_str = str(temp_output_abs).replace('\\', '/') |
| if not output_dir_str.endswith('/'): |
| output_dir_str += '/' |
| |
| video_path_abs = video_path.resolve() |
| |
| |
| os.chdir(project_root) |
| |
| |
| original_argv = sys.argv.copy() |
| sys.argv = [sys.argv[0]] |
| |
| try: |
| |
| print("\n[1/4] Extracting 2D poses...") |
| try: |
| |
| |
| get_pose2D(str(video_path_abs), output_dir_str) |
| except Exception as e: |
| print(f"Error in 2D pose extraction: {e}") |
| raise |
| |
| |
| print("\n[2/4] Extracting 3D poses...") |
| try: |
| |
| |
| get_pose3D(str(video_path_abs), output_dir_str) |
| except Exception as e: |
| print(f"Error in 3D pose extraction: {e}") |
| raise |
| finally: |
| sys.argv = original_argv |
| os.chdir(original_cwd) |
| |
| |
| |
| keypoints_3d_path = temp_output_abs / 'keypoints_3D.npz' |
| |
| if not keypoints_3d_path.exists(): |
| |
| alt_paths = [ |
| temp_output_abs / 'keypoints_3D.npz', |
| temp_output_abs.parent / 'keypoints_3D.npz', |
| ] |
| for alt_path in alt_paths: |
| if alt_path.exists(): |
| keypoints_3d_path = alt_path |
| break |
| else: |
| |
| print(f"\nDebug: Looking for keypoints_3D.npz") |
| print(f"Expected location: {keypoints_3d_path}") |
| print(f"Files in temp_processing:") |
| if temp_output_abs.exists(): |
| for item in temp_output_abs.rglob('*'): |
| if item.is_file(): |
| print(f" {item}") |
| raise FileNotFoundError(f"3D keypoints not found: {keypoints_3d_path}") |
| |
| keypoints_3d = np.load(str(keypoints_3d_path), allow_pickle=True)['reconstruction'] |
| print(f"Loaded {len(keypoints_3d)} frames of 3D poses") |
| |
| |
| if isinstance(keypoints_3d, list): |
| keypoints_3d = np.array(keypoints_3d) |
| |
| |
| print(f"\n[3/4] Generating {n_samples} noisy samples...") |
| noisy_samples = create_noisy_samples(keypoints_3d, n_samples=n_samples, per_joint_noise=True) |
| print(f"Generated noisy samples shape: {noisy_samples.shape}") |
| |
| |
| print("\n[4/4] Calculating metadata...") |
| body_scale = calculate_body_scale(keypoints_3d) |
| relevant_body_parts = get_joints_for_exercise(exercise_type) |
| |
| |
| mean_poses, lower_bound, upper_bound, tolerance = calculate_statistical_bounds( |
| keypoints_3d, noise_std=0.05 |
| ) |
| |
| metadata = { |
| 'exercise_type': exercise_type, |
| 'video_path': str(video_path), |
| 'video_name': video_path.stem, |
| 'num_frames': len(keypoints_3d), |
| 'body_scale': float(body_scale), |
| 'relevant_body_parts': relevant_body_parts, |
| 'n_samples': n_samples, |
| 'timestamp': str(Path(video_path).stat().st_mtime) if video_path.exists() else None |
| } |
| |
| |
| print("\nSaving processed data...") |
| |
| |
| poses_3d_path = output_dir / 'keypoints_3D.npz' |
| np.savez_compressed(str(poses_3d_path), reconstruction=keypoints_3d) |
| print(f" Saved 3D poses: {poses_3d_path}") |
| |
| |
| noisy_samples_path = output_dir / 'noisy_samples.npz' |
| np.savez_compressed(str(noisy_samples_path), samples=noisy_samples) |
| print(f" Saved noisy samples: {noisy_samples_path}") |
| |
| |
| bounds_path = output_dir / 'statistical_bounds.npz' |
| np.savez_compressed( |
| str(bounds_path), |
| mean=mean_poses, |
| lower_bound=lower_bound, |
| upper_bound=upper_bound, |
| tolerance=tolerance |
| ) |
| print(f" Saved statistical bounds: {bounds_path}") |
| |
| |
| metadata_path = output_dir / 'metadata.json' |
| with open(metadata_path, 'w') as f: |
| json.dump(metadata, f, indent=2) |
| print(f" Saved metadata: {metadata_path}") |
| |
| |
| |
| |
| |
| print(f"\n✓ Reference video processed successfully!") |
| print(f" Output directory: {output_dir}") |
| |
| return { |
| 'output_dir': str(output_dir), |
| 'poses_3d_path': str(poses_3d_path), |
| 'noisy_samples_path': str(noisy_samples_path), |
| 'bounds_path': str(bounds_path), |
| 'metadata_path': str(metadata_path), |
| 'metadata': metadata |
| } |
|
|
|
|
| def load_reference(exercise_type, references_dir='references'): |
| """ |
| Load a processed reference |
| |
| Args: |
| exercise_type: Type of exercise (e.g., 'pushup') |
| references_dir: Directory containing references |
| |
| Returns: |
| Dictionary with loaded data |
| """ |
| ref_dir = Path(references_dir) / exercise_type |
| |
| if not ref_dir.exists(): |
| raise FileNotFoundError(f"Reference not found: {ref_dir}") |
| |
| |
| metadata_path = ref_dir / 'metadata.json' |
| if not metadata_path.exists(): |
| raise FileNotFoundError(f"Metadata not found: {metadata_path}") |
| |
| with open(metadata_path, 'r') as f: |
| metadata = json.load(f) |
| |
| |
| poses_3d_path = ref_dir / 'keypoints_3D.npz' |
| if not poses_3d_path.exists(): |
| raise FileNotFoundError(f"3D poses not found: {poses_3d_path}") |
| |
| poses_3d = np.load(str(poses_3d_path), allow_pickle=True)['reconstruction'] |
| if isinstance(poses_3d, list): |
| poses_3d = np.array(poses_3d) |
| |
| |
| noisy_samples_path = ref_dir / 'noisy_samples.npz' |
| noisy_samples = None |
| if noisy_samples_path.exists(): |
| noisy_samples = np.load(str(noisy_samples_path), allow_pickle=True)['samples'] |
| |
| |
| bounds_path = ref_dir / 'statistical_bounds.npz' |
| bounds = None |
| if bounds_path.exists(): |
| bounds_data = np.load(str(bounds_path), allow_pickle=True) |
| bounds = { |
| 'mean': bounds_data['mean'], |
| 'lower_bound': bounds_data['lower_bound'], |
| 'upper_bound': bounds_data['upper_bound'], |
| 'tolerance': bounds_data['tolerance'] |
| } |
| |
| return { |
| 'poses_3d': poses_3d, |
| 'noisy_samples': noisy_samples, |
| 'bounds': bounds, |
| 'metadata': metadata, |
| 'ref_dir': str(ref_dir) |
| } |
|
|
|
|
| if __name__ == "__main__": |
| import argparse |
| |
| parser = argparse.ArgumentParser(description='Process reference video for scoring') |
| parser.add_argument('--video', type=str, required=True, help='Path to reference video') |
| parser.add_argument('--exercise', type=str, default='pushup', help='Exercise type') |
| parser.add_argument('--output', type=str, default=None, help='Output directory') |
| parser.add_argument('--samples', type=int, default=100, help='Number of noisy samples') |
| |
| args = parser.parse_args() |
| |
| try: |
| result = process_reference_video( |
| args.video, |
| exercise_type=args.exercise, |
| output_dir=args.output, |
| n_samples=args.samples |
| ) |
| print("\n" + "="*50) |
| print("SUCCESS!") |
| print("="*50) |
| print(f"Reference saved to: {result['output_dir']}") |
| except Exception as e: |
| print(f"\nERROR: {e}") |
| import traceback |
| traceback.print_exc() |
| sys.exit(1) |
|
|
|
|