ecg-digitization-experiments / code /scripts /convert_npy_to_csv.py
Ubuntu
Add training scripts and notebooks
b69e447
Raw
History Blame Contribute Delete
4.78 kB
#!/usr/bin/env python3
"""
Convert NPY ground truth files to CSV format for training.
NPY files contain mV values with shape [4, N].
CSV files should contain pixel Y-coordinates with N rows and 4 columns.
Conversion: pixel_y = ZERO_MV[row] - mv_value * MV_TO_PIXEL
"""
import os
import sys
import numpy as np
import pandas as pd
from pathlib import Path
from tqdm import tqdm
import argparse
# Constants from training script
ZERO_MV = np.array([703.5, 987.5, 1271.5, 1531.5])
MV_TO_PIXEL = 78.5
TARGET_HEIGHT = 1696
def convert_mv_to_pixels(mv_signal):
"""Convert mV values to pixel Y-coordinates.
Args:
mv_signal: Array of shape [4, N] with mV values
Returns:
pixel_signal: Array of shape [4, N] with pixel Y-coordinates
"""
pixel_signal = np.zeros_like(mv_signal)
for row in range(4):
pixel_signal[row] = ZERO_MV[row] - mv_signal[row] * MV_TO_PIXEL
# Clip to valid range
pixel_signal = np.clip(pixel_signal, 0, TARGET_HEIGHT - 1)
return pixel_signal
def convert_npy_to_csv(npy_path, csv_path):
"""Convert a single NPY file to CSV format."""
# Load NPY file [4, N] in mV
mv_signal = np.load(npy_path)
# Convert to pixel coordinates
pixel_signal = convert_mv_to_pixels(mv_signal)
# Transpose to [N, 4] for CSV format
# When training loads: gt_df.values.T gives [4, N]
df = pd.DataFrame(pixel_signal.T, columns=['row0', 'row1', 'row2', 'row3'])
# Save CSV
df.to_csv(csv_path, index=False)
return True
def main():
parser = argparse.ArgumentParser(description='Convert NPY ground truth to CSV format')
parser.add_argument('--input_dir', type=str,
default='/data/ecg-digitization/synthetic_ecgkit/gt',
help='Directory containing NPY files')
parser.add_argument('--output_dir', type=str, default=None,
help='Output directory for CSV files (default: same as input)')
parser.add_argument('--stage1_dir', type=str,
default='/data/ecg-digitization/synthetic_ecgkit/stage1',
help='Stage1 images directory (for creating per-variant CSVs)')
parser.add_argument('--per_variant', action='store_true',
help='Create separate CSV for each image variant')
args = parser.parse_args()
input_dir = Path(args.input_dir)
output_dir = Path(args.output_dir) if args.output_dir else input_dir
stage1_dir = Path(args.stage1_dir)
output_dir.mkdir(parents=True, exist_ok=True)
# Find all NPY files
npy_files = sorted(input_dir.glob('*.npy'))
print(f"Found {len(npy_files)} NPY files")
if args.per_variant:
# Create CSV for each image variant
# Image names: syn_00000000-0001.png -> need syn_00000000-0001.csv
# NPY names: syn_00000000.npy
png_files = sorted(stage1_dir.glob('*.png'))
print(f"Found {len(png_files)} PNG files")
converted = 0
for png_path in tqdm(png_files, desc="Converting"):
# Extract base ID from image name (e.g., syn_00000000-0001 -> syn_00000000)
img_stem = png_path.stem # syn_00000000-0001
base_id = img_stem.rsplit('-', 1)[0] # syn_00000000
npy_path = input_dir / f"{base_id}.npy"
if not npy_path.exists():
continue
csv_path = output_dir / f"{img_stem}.csv"
if convert_npy_to_csv(npy_path, csv_path):
converted += 1
print(f"Converted {converted} files")
else:
# Simple 1:1 conversion
converted = 0
for npy_path in tqdm(npy_files, desc="Converting"):
csv_path = output_dir / f"{npy_path.stem}.csv"
if convert_npy_to_csv(npy_path, csv_path):
converted += 1
print(f"Converted {converted} files")
# Verify a sample
if npy_files:
sample_npy = npy_files[0]
sample_csv = output_dir / f"{sample_npy.stem}.csv"
print(f"\nSample verification:")
print(f" NPY: {sample_npy}")
mv_data = np.load(sample_npy)
print(f" NPY shape: {mv_data.shape}")
print(f" NPY (mV) range: [{mv_data.min():.2f}, {mv_data.max():.2f}]")
if sample_csv.exists():
csv_data = pd.read_csv(sample_csv)
print(f" CSV shape: {csv_data.shape}")
print(f" CSV (pixels) range: [{csv_data.values.min():.1f}, {csv_data.values.max():.1f}]")
print(f" Expected pixel range: ~[{ZERO_MV.min() - 2*MV_TO_PIXEL:.0f}, {ZERO_MV.max() + 2*MV_TO_PIXEL:.0f}]")
if __name__ == '__main__':
main()