pose-deep-learning / A15_Data /augment_cut_data.py
Bachstelze
init data preprocessing
b3e0245
Raw
History Blame Contribute Delete
7.76 kB
#!/usr/bin/env python3
"""
Dataset Augmentation Script for A15 Cut Skeleton Sequences
Applies the following augmentations to each cut sequence CSV in a15_cut/:
1. Mirror on y-axis (flip x-coordinates left ↔ right)
2. Rotate on y-axis by ±10 degrees
3. Stretch/compress a few % in x, y, z axes
Each augmented exercise retains the same quality score as the original.
The cut CSVs have columns: FrameNo, head_x, head_y, head_z, ... (13 joints × 3 coords)
Each row is one frame; the full file is one exercise sequence.
Output:
- a15_cut_augmented/ directory with all original + augmented CSV files
- a15_augmented_data.csv combined CSV with clip names, scores, probabilities
Usage:
python3 augment_cut_data.py
"""
import os
import glob
import pandas as pd
import numpy as np
from typing import List, Tuple
# ── coordinate helpers ──────────────────────────────────────────────────────────
def get_coordinate_columns(df: pd.DataFrame) -> Tuple[List[int], List[int], List[int]]:
"""
After FrameNo (column 0), the columns follow the pattern
joint1_x, joint1_y, joint1_z, joint2_x, joint2_y, joint2_z, …
so (col_index - 1) % 3 tells us the axis.
Returns:
(x_indices, y_indices, z_indices) — column indices (0-based) into the DataFrame.
"""
coord_cols = list(range(1, len(df.columns))) # skip FrameNo
x_idx = [c for c in coord_cols if (c - 1) % 3 == 0]
y_idx = [c for c in coord_cols if (c - 1) % 3 == 1]
z_idx = [c for c in coord_cols if (c - 1) % 3 == 2]
return x_idx, y_idx, z_idx
# ── augmentation primitives ────────────────────────────────────────────────────
def mirror_on_y_axis(df: pd.DataFrame, x_idx: List[int]) -> pd.DataFrame:
"""Mirror left ↔ right by negating all x-coordinates."""
df_aug = df.copy()
for idx in x_idx:
df_aug.iloc[:, idx] = -df.iloc[:, idx]
return df_aug
def rotate_on_y_axis(df: pd.DataFrame, x_idx: List[int], z_idx: List[int],
angle_deg: float) -> pd.DataFrame:
"""
Rotate every joint around the y-axis.
x' = x·cos(θ) + z·sin(θ)
y' = y
z' = –x·sin(θ) + z·cos(θ)
"""
df_aug = df.copy()
angle_rad = np.radians(angle_deg)
cos_a = np.cos(angle_rad)
sin_a = np.sin(angle_rad)
for xi, zi in zip(x_idx, z_idx):
x_orig = df.iloc[:, xi].values
z_orig = df.iloc[:, zi].values
df_aug.iloc[:, xi] = x_orig * cos_a + z_orig * sin_a
df_aug.iloc[:, zi] = -x_orig * sin_a + z_orig * cos_a
return df_aug
def stretch_compress(df: pd.DataFrame, x_idx: List[int], y_idx: List[int],
z_idx: List[int], scale_x: float, scale_y: float,
scale_z: float) -> pd.DataFrame:
"""Scale coordinates independently per axis."""
df_aug = df.copy()
for idx in x_idx:
df_aug.iloc[:, idx] *= scale_x
for idx in y_idx:
df_aug.iloc[:, idx] *= scale_y
for idx in z_idx:
df_aug.iloc[:, idx] *= scale_z
return df_aug
# ── I/O helpers ─────────────────────────────────────────────────────────────────
def load_score_map(score_file: str) -> dict:
"""Return {clip_name: (score_rescaled, good_probability)} from a15_good_rescaled.csv."""
df_scores = pd.read_csv(score_file)
return {
row['clip']: (row['score_rescaled'], row['good_probability'])
for _, row in df_scores.iterrows()
}
def augment_all_sequences(input_dir: str, score_file: str,
output_dir: str, output_csv: str) -> None:
# Load scores
print(f"Loading scores from {score_file} …")
score_map = load_score_map(score_file)
print(f" → {len(score_map)} clips with scores")
# Discover cut sequence CSVs
csv_pattern = os.path.join(input_dir, '*_kinect.csv')
csv_files = sorted(glob.glob(csv_pattern))
n_original = len(csv_files)
if n_original == 0:
print(f"ERROR: No *_kinect.csv files found in {input_dir}.")
return
print(f"Found {n_original} original sequence files")
# Create output directory
os.makedirs(output_dir, exist_ok=True)
# Augmentation recipes → (suffix, callable)
augmentations = [
('mirror', lambda df, x, y, z: mirror_on_y_axis(df, x)),
('rotate_pos', lambda df, x, y, z: rotate_on_y_axis(df, x, z, 10)),
('rotate_neg', lambda df, x, y, z: rotate_on_y_axis(df, x, z, -10)),
('stretch', lambda df, x, y, z: stretch_compress(df, x, y, z, 1.05, 0.95, 1.02)),
]
all_entries: List[Tuple[str, float, float]] = [] # (clip, score, prob)
total_augmented = 0
for csv_path in csv_files:
basename = os.path.basename(csv_path) # e.g. "A100_kinect.csv"
clip_name = basename.replace('.csv', '') # e.g. "A100_kinect"
# Skip if no score is known (should not happen for the good list)
if clip_name not in score_map:
print(f" ⚠ WARNING: no score for {clip_name}, skipping")
continue
score_val, prob_val = score_map[clip_name]
# Read the frame-by-frame skeleton data
df = pd.read_csv(csv_path)
x_idx, y_idx, z_idx = get_coordinate_columns(df)
# ── 1. Keep original ──
orig_path = os.path.join(output_dir, basename)
df.to_csv(orig_path, index=False)
all_entries.append((clip_name, score_val, prob_val))
# ── 2–5. Augmented variants ──
for suffix, aug_fn in augmentations:
df_aug = aug_fn(df.copy(), x_idx, y_idx, z_idx)
aug_clip_name = f"{clip_name}_{suffix}"
aug_filename = f"{aug_clip_name}.csv"
df_aug.to_csv(os.path.join(output_dir, aug_filename), index=False)
all_entries.append((aug_clip_name, score_val, prob_val))
total_augmented += 1
if (len(csv_files) <= 10 or csv_files.index(csv_path) % 20 == 0):
print(f" ✓ {clip_name} → original + 4 augmented variants")
# ── Write combined metadata CSV ──
df_out = pd.DataFrame(all_entries, columns=['clip', 'score_rescaled', 'good_probability'])
df_out.to_csv(output_csv, index=False)
print(f"\n{'=' * 50}")
print(f" Augmentation complete")
print(f" Original clips : {n_original}")
print(f" Augmented variants : {total_augmented}")
print(f" Total (orig + augment) : {len(df_out)}")
print(f" Augmented CSV directory : {output_dir}/")
print(f" Combined metadata CSV : {output_csv}")
print(f"{'=' * 50}")
# ── entry point ─────────────────────────────────────────────────────────────────
def main():
# Paths relative to this script's directory
script_dir = os.path.dirname(os.path.abspath(__file__))
input_dir = os.path.join(script_dir, 'a15_cut')
score_file = os.path.join(script_dir, 'a15_good_rescaled.csv')
output_dir = os.path.join(script_dir, 'a15_cut_augmented')
output_csv = os.path.join(script_dir, 'a15_augmented_data.csv')
augment_all_sequences(
input_dir=input_dir,
score_file=score_file,
output_dir=output_dir,
output_csv=output_csv,
)
if __name__ == '__main__':
main()