{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":97984,"databundleVersionId":14096757,"sourceType":"competition"}],"dockerImageVersionId":31153,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"_kg_hide-output":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom pathlib import Path\nimport cv2\nfrom PIL import Image\nimport warnings\nwarnings.filterwarnings('ignore')\n\n# Configuración de visualización\nplt.style.use('seaborn-v0_8-darkgrid')\nsns.set_palette(\"husl\")\nplt.rcParams['figure.figsize'] = (15, 8)\nplt.rcParams['font.size'] = 10\n\n# Paths de Kaggle\nDATA_PATH = Path('/kaggle/input/physionet-ecg-image-digitization')\nTRAIN_PATH = DATA_PATH / 'train'\nTEST_PATH = DATA_PATH / 'test'\n\nprint(\"🚀 PhysioNet ECG Digitization - EDA\")\nprint(\"=\" * 60)\nprint(f\"📁 Data path exists: {DATA_PATH.exists()}\")\nprint(f\"📁 Train path exists: {TRAIN_PATH.exists()}\")\nprint(f\"📁 Test path exists: {TEST_PATH.exists()}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-27T12:08:02.480202Z","iopub.execute_input":"2025-10-27T12:08:02.480490Z","iopub.status.idle":"2025-10-27T12:08:03.833874Z","shell.execute_reply.started":"2025-10-27T12:08:02.480469Z","shell.execute_reply":"2025-10-27T12:08:03.832824Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"\\n\" + \"=\"*60)\nprint(\"📊 SECTION 1: DATA OVERVIEW\")\nprint(\"=\"*60)\n\n# Cargar metadata\ntrain_meta = pd.read_csv(DATA_PATH / 'train.csv')\ntest_meta = pd.read_csv(DATA_PATH / 'test.csv')\nsample_submission = pd.read_parquet(DATA_PATH / 'sample_submission.parquet')\n\nprint(\"\\n📋 Train Metadata:\")\nprint(f\"  - Shape: {train_meta.shape}\")\nprint(f\"  - Columns: {list(train_meta.columns)}\")\nprint(f\"\\n{train_meta.head()}\")\n\nprint(\"\\n📋 Test Metadata:\")\nprint(f\"  - Shape: {test_meta.shape}\")\nprint(f\"  - Columns: {list(test_meta.columns)}\")\nprint(f\"\\n{test_meta.head()}\")\n\nprint(\"\\n📋 Sample Submission:\")\nprint(f\"  - Shape: {sample_submission.shape}\")\nprint(f\"  - Columns: {list(sample_submission.columns)}\")\nprint(f\"\\n{sample_submission.head()}\")\n\n# Análisis de distribuciones\nprint(\"\\n📊 Sampling Frequencies Distribution (Train):\")\nprint(train_meta['fs'].value_counts().sort_index())\n\nprint(\"\\n📊 Signal Lengths Distribution (Train):\")\nprint(train_meta['sig_len'].value_counts().sort_index())\n\n# Visualizar distribuciones\nfig, axes = plt.subplots(1, 2, figsize=(15, 5))\n\n# Sampling frequencies\ntrain_meta['fs'].value_counts().sort_index().plot(kind='bar', ax=axes[0], color='skyblue')\naxes[0].set_title('Sampling Frequencies in Train Set', fontsize=14, fontweight='bold')\naxes[0].set_xlabel('Sampling Frequency (Hz)')\naxes[0].set_ylabel('Count')\naxes[0].grid(axis='y', alpha=0.3)\n\n# Signal lengths\ntrain_meta['sig_len'].value_counts().sort_index().plot(kind='bar', ax=axes[1], color='coral')\naxes[1].set_title('Signal Lengths in Train Set', fontsize=14, fontweight='bold')\naxes[1].set_xlabel('Signal Length (samples)')\naxes[1].set_ylabel('Count')\naxes[1].grid(axis='y', alpha=0.3)\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-27T12:08:20.483244Z","iopub.execute_input":"2025-10-27T12:08:20.483803Z","iopub.status.idle":"2025-10-27T12:08:21.473597Z","shell.execute_reply.started":"2025-10-27T12:08:20.483775Z","shell.execute_reply":"2025-10-27T12:08:21.472611Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"\\n\" + \"=\"*60)\nprint(\"🖼️  SECTION 2: IMAGE ANALYSIS - ALL VARIANTS\")\nprint(\"=\"*60)\n\n# Tipos de imágenes según la documentación\nIMAGE_VARIANTS = {\n    '0001': 'Original digital (clean)',\n    '0003': 'Printed + scanned (color)',\n    '0004': 'Printed + scanned (B&W)',\n    '0005': 'Mobile photo of print',\n    '0006': 'Mobile photo of screen',\n    '0009': 'Stained/soaked print',\n    '0010': 'Extensively damaged',\n    '0011': 'Scanned with mold (color)',\n    '0012': 'Scanned with mold (B&W)'\n}\n\n# Seleccionar un ECG de ejemplo\nsample_id = train_meta['id'].iloc[0]\nprint(f\"\\n🔍 Analyzing sample ECG: {sample_id}\")\n\n# Verificar qué variantes existen para este sample\nsample_dir = TRAIN_PATH / str(sample_id)\nif sample_dir.exists():\n    available_images = sorted(sample_dir.glob(f\"{sample_id}-*.png\"))\n    print(f\"  - Found {len(available_images)} image variants\")\n    \n    # Visualizar todas las variantes disponibles\n    n_images = len(available_images)\n    n_cols = 3\n    n_rows = (n_images + n_cols - 1) // n_cols\n    \n    fig, axes = plt.subplots(n_rows, n_cols, figsize=(18, 6*n_rows))\n    axes = axes.flatten() if n_images > 1 else [axes]\n    \n    for idx, img_path in enumerate(available_images):\n        img = Image.open(img_path)\n        img_array = np.array(img)\n        \n        # Extraer el código de variante\n        variant_code = img_path.stem.split('-')[-1]\n        variant_desc = IMAGE_VARIANTS.get(variant_code, 'Unknown')\n        \n        axes[idx].imshow(img_array)\n        axes[idx].set_title(f'{variant_code}: {variant_desc}\\nSize: {img_array.shape}', \n                           fontsize=11, fontweight='bold')\n        axes[idx].axis('off')\n        \n        print(f\"  - {variant_code}: {variant_desc}\")\n        print(f\"    Shape: {img_array.shape}, Dtype: {img_array.dtype}\")\n    \n    # Ocultar ejes sobrantes\n    for idx in range(n_images, len(axes)):\n        axes[idx].axis('off')\n    \n    plt.tight_layout()\n    plt.show()\nelse:\n    print(f\"  ⚠️  Sample directory not found: {sample_dir}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-27T12:08:26.815354Z","iopub.execute_input":"2025-10-27T12:08:26.815689Z","iopub.status.idle":"2025-10-27T12:08:41.726665Z","shell.execute_reply.started":"2025-10-27T12:08:26.815666Z","shell.execute_reply":"2025-10-27T12:08:41.723975Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"\\n\" + \"=\"*60)\nprint(\"📈 SECTION 3: ECG SIGNAL ANALYSIS\")\nprint(\"=\"*60)\n\n# Cargar señal ground truth del mismo sample\nsignal_path = sample_dir / f\"{sample_id}.csv\"\nif signal_path.exists():\n    signal_df = pd.read_csv(signal_path)\n    \n    print(f\"\\n📊 Signal DataFrame:\")\n    print(f\"  - Shape: {signal_df.shape}\")\n    print(f\"  - Columns (12 leads): {list(signal_df.columns)}\")\n    print(f\"\\n{signal_df.head()}\")\n    \n    # Estadísticas por lead\n    print(\"\\n📊 Signal Statistics by Lead:\")\n    stats_df = signal_df.describe().T\n    print(stats_df)\n    \n    # Visualizar las 12 derivaciones\n    leads = signal_df.columns\n    n_leads = len(leads)\n    \n    fig, axes = plt.subplots(n_leads, 1, figsize=(16, 2*n_leads))\n    \n    for idx, lead in enumerate(leads):\n        signal = signal_df[lead].values\n        time = np.arange(len(signal)) / train_meta[train_meta['id']==sample_id]['fs'].values[0]\n        \n        axes[idx].plot(time, signal, linewidth=0.8, color='darkblue')\n        axes[idx].set_ylabel(f'{lead}\\n(mV)', fontweight='bold')\n        axes[idx].grid(True, alpha=0.3)\n        axes[idx].set_xlim(0, time[-1])\n        \n        # Estadísticas en el título\n        axes[idx].set_title(\n            f'Mean: {signal.mean():.3f} mV | Std: {signal.std():.3f} mV | Range: [{signal.min():.3f}, {signal.max():.3f}]',\n            fontsize=9\n        )\n        \n        if idx == n_leads - 1:\n            axes[idx].set_xlabel('Time (seconds)', fontweight='bold')\n    \n    plt.suptitle(f'ECG Signal - All 12 Leads (ID: {sample_id})', \n                 fontsize=14, fontweight='bold', y=1.001)\n    plt.tight_layout()\n    plt.show()\n    \n    # Análisis de relaciones matemáticas entre leads\n    print(\"\\n🔬 Mathematical Relationships Between Leads:\")\n    print(\"  (Einthoven's Triangle and Goldberger's equations)\")\n    \n    # Lead III debería ser aproximadamente II - I\n    lead_III_calculated = signal_df['II'] - signal_df['I']\n    lead_III_actual = signal_df['III']\n    diff_III = np.abs(lead_III_calculated - lead_III_actual).mean()\n    print(f\"  - Lead III vs (II - I): Mean absolute difference = {diff_III:.6f} mV\")\n    \n    # aVR debería ser aproximadamente -(I + II)/2\n    aVR_calculated = -(signal_df['I'] + signal_df['II']) / 2\n    aVR_actual = signal_df['aVR']\n    diff_aVR = np.abs(aVR_calculated - aVR_actual).mean()\n    print(f\"  - Lead aVR vs -(I + II)/2: Mean absolute difference = {diff_aVR:.6f} mV\")\n    \n    print(\"\\n💡 Insight: Estas relaciones pueden usarse para validación y corrección!\")\n\nelse:\n    print(f\"  ⚠️  Signal file not found: {signal_path}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-27T12:08:41.728920Z","iopub.execute_input":"2025-10-27T12:08:41.729407Z","iopub.status.idle":"2025-10-27T12:08:44.528575Z","shell.execute_reply.started":"2025-10-27T12:08:41.729352Z","shell.execute_reply":"2025-10-27T12:08:44.526972Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"\\n\" + \"=\"*60)\nprint(\"🔗 SECTION 4: IMAGE-SIGNAL RELATIONSHIP\")\nprint(\"=\"*60)\n\n# Cargar imagen limpia (-0001) y señal para comparar\nclean_img_path = sample_dir / f\"{sample_id}-0001.png\"\nif clean_img_path.exists() and signal_path.exists():\n    \n    clean_img = np.array(Image.open(clean_img_path))\n    \n    print(f\"\\n🖼️  Clean Image Properties:\")\n    print(f\"  - Shape: {clean_img.shape}\")\n    print(f\"  - Dtype: {clean_img.dtype}\")\n    print(f\"  - Value range: [{clean_img.min()}, {clean_img.max()}]\")\n    \n    # Visualización lado a lado\n    fig = plt.figure(figsize=(18, 10))\n    \n    # Imagen completa\n    ax1 = plt.subplot(2, 1, 1)\n    ax1.imshow(clean_img)\n    ax1.set_title('Complete ECG Image (Clean Digital Version)', \n                  fontsize=14, fontweight='bold')\n    ax1.axis('off')\n    \n    # Zoom a una región específica para ver detalles de la cuadrícula\n    ax2 = plt.subplot(2, 2, 3)\n    h, w = clean_img.shape[:2]\n    crop = clean_img[int(h*0.3):int(h*0.5), int(w*0.1):int(w*0.3)]\n    ax2.imshow(crop)\n    ax2.set_title('Zoomed Region: Grid Detail', fontsize=12, fontweight='bold')\n    ax2.axis('off')\n    \n    # Señal correspondiente (ejemplo: Lead II)\n    ax3 = plt.subplot(2, 2, 4)\n    signal_sample = signal_df['II'].values[:1000]  # Primeros 1000 puntos\n    time_sample = np.arange(len(signal_sample)) / train_meta[train_meta['id']==sample_id]['fs'].values[0]\n    ax3.plot(time_sample, signal_sample, linewidth=1.5, color='darkred')\n    ax3.set_title('Corresponding Signal (Lead II, first 1000 samples)', \n                  fontsize=12, fontweight='bold')\n    ax3.set_xlabel('Time (s)')\n    ax3.set_ylabel('Amplitude (mV)')\n    ax3.grid(True, alpha=0.3)\n    \n    plt.tight_layout()\n    plt.show()\n    \n    print(\"\\n💡 Key Observations:\")\n    print(\"  - ECG images contain a grid (typically 1mm squares)\")\n    print(\"  - Standard calibration: 25 mm/s horizontal, 10 mm/mV vertical\")\n    print(\"  - Need to detect grid and calibration marks\")\n    print(\"  - Lead II is longer (10s) vs other leads (2.5s)\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-27T12:08:50.303365Z","iopub.execute_input":"2025-10-27T12:08:50.304444Z","iopub.status.idle":"2025-10-27T12:08:51.395455Z","shell.execute_reply.started":"2025-10-27T12:08:50.304409Z","shell.execute_reply":"2025-10-27T12:08:51.394213Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"\\n\" + \"=\"*60)\nprint(\"🎯 SECTION 5: CHALLENGE IDENTIFICATION\")\nprint(\"=\"*60)\n\nprint(\"\\n🔴 Expected Challenges:\")\nchallenges = [\n    \"1. Geometric distortions: rotation, perspective, scaling\",\n    \"2. Noise and artifacts: stains, mold, damage, reflections\",\n    \"3. Variable layouts: different manufacturers, lead positions\",\n    \"4. Grid detection: crucial for calibration\",\n    \"5. Lead identification: which signal is which in the image\",\n    \"6. Different durations: Lead II (10s) vs others (2.5s)\",\n    \"7. Overlapping signals: some layouts have overlaid leads\",\n    \"8. Quality variation: pristine to barely readable\",\n    \"9. Color vs B&W: loss of information in B&W scans\",\n    \"10. Photo artifacts: moiré, blur, uneven lighting\"\n]\n\nfor challenge in challenges:\n    print(f\"  {challenge}\")\n\n# Comparar variantes extremas\nprint(\"\\n🔬 Comparing Clean vs Damaged Images:\")\n\nclean_variant = sample_dir / f\"{sample_id}-0001.png\"\ndamaged_variants = [\n    sample_dir / f\"{sample_id}-0009.png\",  # Stained\n    sample_dir / f\"{sample_id}-0010.png\",  # Damaged\n]\n\nfig, axes = plt.subplots(1, 3, figsize=(18, 6))\n\n# Clean\nif clean_variant.exists():\n    img = np.array(Image.open(clean_variant))\n    axes[0].imshow(img)\n    axes[0].set_title('0001: Clean Digital\\n(Baseline)', fontsize=12, fontweight='bold')\n    axes[0].axis('off')\n\n# Damaged variants\nfor idx, dmg_path in enumerate(damaged_variants):\n    if dmg_path.exists():\n        img = np.array(Image.open(dmg_path))\n        variant_code = dmg_path.stem.split('-')[-1]\n        axes[idx+1].imshow(img)\n        axes[idx+1].set_title(f'{variant_code}: {IMAGE_VARIANTS.get(variant_code, \"\")}\\n(Challenge!)', \n                             fontsize=12, fontweight='bold')\n        axes[idx+1].axis('off')\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-27T12:08:55.470481Z","iopub.execute_input":"2025-10-27T12:08:55.470829Z","iopub.status.idle":"2025-10-27T12:09:00.828763Z","shell.execute_reply.started":"2025-10-27T12:08:55.470804Z","shell.execute_reply":"2025-10-27T12:09:00.827664Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"\\n\" + \"=\"*60)\nprint(\"📝 SECTION 6: KEY TAKEAWAYS\")\nprint(\"=\"*60)\n\ntakeaways = \"\"\"\n✅ DATA STRUCTURE:\n  - Train: {n_train} ECGs with multiple image variants each\n  - Test: {n_test} images (one per ECG)\n  - 12 leads per ECG (11 short + 1 long)\n  \n✅ TECHNICAL SPECS:\n  - Sampling frequencies: {fs_range}\n  - Signal duration: 2.5s (most leads) and 10s (Lead II)\n  - Standard grid: 1mm squares\n  - Standard scale: 25 mm/s, 10 mm/mV\n  \n✅ MAIN CHALLENGES:\n  1. Robust preprocessing (rotation, perspective correction)\n  2. Grid detection and calibration\n  3. Lead segmentation (identify which is which)\n  4. Handle extreme artifacts (stains, mold, damage)\n  5. Maintain mathematical relationships between leads\n  \n✅ STRATEGY INSIGHTS:\n  - Use clean variants (-0001) for initial model development\n  - Gradually train on degraded variants\n  - Implement physics-based constraints (lead relationships)\n  - Consider ensemble: CV + Deep Learning\n  \n🎯 NEXT STEPS (HITO 1):\n  1. Build basic preprocessing pipeline (CV)\n  2. Implement simple signal extraction (baseline)\n  3. Create local evaluation metric (SNR)\n  4. Make first submission\n\"\"\"\n\nprint(takeaways.format(\n    n_train=len(train_meta),\n    n_test=len(test_meta),\n    fs_range=f\"{train_meta['fs'].min()}-{train_meta['fs'].max()} Hz\"\n))\n\nprint(\"\\n\" + \"=\"*60)\nprint(\"✨ EDA COMPLETE - Ready for modeling!\")\nprint(\"=\"*60)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-27T12:09:00.830569Z","iopub.execute_input":"2025-10-27T12:09:00.831031Z","iopub.status.idle":"2025-10-27T12:09:00.842627Z","shell.execute_reply.started":"2025-10-27T12:09:00.830999Z","shell.execute_reply":"2025-10-27T12:09:00.841412Z"}},"outputs":[],"execution_count":null}]}