| |
| """ |
| 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 |
|
|
| |
| 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 |
| |
| |
| 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.""" |
| |
| mv_signal = np.load(npy_path) |
| |
| |
| pixel_signal = convert_mv_to_pixels(mv_signal) |
| |
| |
| |
| df = pd.DataFrame(pixel_signal.T, columns=['row0', 'row1', 'row2', 'row3']) |
| |
| |
| 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) |
| |
| |
| npy_files = sorted(input_dir.glob('*.npy')) |
| print(f"Found {len(npy_files)} NPY files") |
| |
| if args.per_variant: |
| |
| |
| |
| |
| 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"): |
| |
| img_stem = png_path.stem |
| base_id = img_stem.rsplit('-', 1)[0] |
| |
| 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: |
| |
| 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") |
| |
| |
| 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() |
|
|