{"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":"gpu","dataSources":[{"sourceId":97984,"databundleVersionId":14096757,"sourceType":"competition"}],"dockerImageVersionId":31154,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# *ECG Image-to-Signal Reconstruction*","metadata":{}},{"cell_type":"markdown","source":"## Import Libraries","metadata":{}},{"cell_type":"code","source":"# Step 1: Basic Setup & File Check\n\nimport os\nimport pandas as pd\n\n# Base path\nDATA_PATH = '/kaggle/input/physionet-ecg-image-digitization/'\n\n# List all files\nfor dirname, _, filenames in os.walk(DATA_PATH):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# Check main CSV files\ntrain_csv = os.path.join(DATA_PATH, 'train.csv')\ntest_csv = os.path.join(DATA_PATH, 'test.csv')\n\n# Load CSVs\ntrain_df = pd.read_csv(train_csv)\ntest_df = pd.read_csv(test_csv)\n\n# Basic info\nprint(\"\\n✅ Train shape:\", train_df.shape)\nprint(\"✅ Test shape:\", test_df.shape)\n\n# Preview first few rows\nprint(\"\\n📄 Train data preview:\")\ndisplay(train_df.head())\n\nprint(\"\\n📄 Test data preview:\")\ndisplay(test_df.head())\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-26T08:31:30.491755Z","iopub.execute_input":"2025-10-26T08:31:30.492307Z","iopub.status.idle":"2025-10-26T08:31:39.654682Z","shell.execute_reply.started":"2025-10-26T08:31:30.492281Z","shell.execute_reply":"2025-10-26T08:31:39.654079Z"},"collapsed":true,"jupyter":{"outputs_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"##  Data Overview & Missing Values","metadata":{}},{"cell_type":"code","source":"# Basic info about train and test datasets\nprint(\"=== TRAIN DATA INFO ===\")\ntrain_df.info()\nprint(\"\\n=== TEST DATA INFO ===\")\ntest_df.info()\n\n# Check missing values\nprint(\"\\n=== Missing Values (Train) ===\")\nprint(train_df.isnull().sum())\n\nprint(\"\\n=== Missing Values (Test) ===\")\nprint(test_df.isnull().sum())\n\n# Basic descriptive stats for numeric columns\nprint(\"\\n=== Descriptive Statistics (Train) ===\")\ndisplay(train_df.describe())\n\n# Unique values for object-type columns\nprint(\"\\n=== Unique Values in Object Columns (Train) ===\")\nfor col in train_df.select_dtypes(include='object').columns:\n    print(f\"{col}: {train_df[col].nunique()} unique values\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-26T08:32:23.190525Z","iopub.execute_input":"2025-10-26T08:32:23.191093Z","iopub.status.idle":"2025-10-26T08:32:23.227199Z","shell.execute_reply.started":"2025-10-26T08:32:23.191069Z","shell.execute_reply":"2025-10-26T08:32:23.226427Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Data Distribution & Relationships","metadata":{}},{"cell_type":"code","source":"# Step 3: Data Distribution & Relationships\n\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n\n# Style setup\nsns.set(style=\"whitegrid\")\n\n# 1️⃣ Histogram for fs\nplt.figure(figsize=(6,4))\nsns.histplot(train_df['fs'], bins=20, kde=True)\nplt.title('Distribution of Sampling Frequency (fs)')\nplt.xlabel('fs')\nplt.ylabel('Count')\nplt.show()\n\n# 2️⃣ Histogram for sig_len\nplt.figure(figsize=(6,4))\nsns.histplot(train_df['sig_len'], bins=20, kde=True, color='orange')\nplt.title('Distribution of Signal Length (sig_len)')\nplt.xlabel('sig_len')\nplt.ylabel('Count')\nplt.show()\n\n# 3️⃣ Scatter plot: fs vs sig_len\nplt.figure(figsize=(6,4))\nsns.scatterplot(data=train_df, x='fs', y='sig_len')\nplt.title('fs vs sig_len Relationship')\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-26T08:33:23.830584Z","iopub.execute_input":"2025-10-26T08:33:23.831257Z","iopub.status.idle":"2025-10-26T08:33:24.783705Z","shell.execute_reply.started":"2025-10-26T08:33:23.831229Z","shell.execute_reply":"2025-10-26T08:33:24.783022Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Train–Test Comparison & Summary Visualization","metadata":{}},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nimport seaborn as sns\n\n# Combine 'fs' values from both train and test for comparison\ntrain_fs = train_df['fs']\ntest_fs = test_df['fs']\n\nplt.figure(figsize=(8,4))\nsns.kdeplot(train_fs, label='Train fs', fill=True)\nsns.kdeplot(test_fs, label='Test fs', fill=True, color='orange')\nplt.title('Distribution Comparison: fs (Train vs Test)')\nplt.legend()\nplt.show()\n\n# Boxplot for signal length (train)\nplt.figure(figsize=(6,4))\nsns.boxplot(x=train_df['sig_len'], color='lightblue')\nplt.title('Boxplot: Signal Length (Train)')\nplt.xlabel('sig_len')\nplt.show()\n\n# Correlation heatmap (train)\nplt.figure(figsize=(4,3))\nsns.heatmap(train_df.corr(), annot=True, cmap='coolwarm')\nplt.title('Correlation Heatmap (Train Data)')\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-26T08:34:06.340888Z","iopub.execute_input":"2025-10-26T08:34:06.342134Z","iopub.status.idle":"2025-10-26T08:34:06.932519Z","shell.execute_reply.started":"2025-10-26T08:34:06.342095Z","shell.execute_reply":"2025-10-26T08:34:06.931731Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Parquet File (sample_submission.parquet) Analysis","metadata":{}},{"cell_type":"code","source":"import pandas as pd\n\n# Load the parquet file\nsample_path = '/kaggle/input/physionet-ecg-image-digitization/sample_submission.parquet'\nsample_df = pd.read_parquet(sample_path)\n\n# Basic info\nprint(\"=== SAMPLE SUBMISSION INFO ===\")\nprint(sample_df.info())\n\n# Preview data\nprint(\"\\n=== SAMPLE SUBMISSION PREVIEW ===\")\ndisplay(sample_df.head())\n\n# Unique ID count\nprint(\"\\nUnique IDs in sample:\", sample_df['id'].nunique())\n\n# Describe numeric columns (if any)\nprint(\"\\n=== Numeric Summary ===\")\ndisplay(sample_df.describe())\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-26T08:35:17.552630Z","iopub.execute_input":"2025-10-26T08:35:17.552902Z","iopub.status.idle":"2025-10-26T08:35:17.815147Z","shell.execute_reply.started":"2025-10-26T08:35:17.552880Z","shell.execute_reply":"2025-10-26T08:35:17.814573Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"##  EDA Summary — PhysioNet ECG Image Digitization Dataset\n\n### 🔹 Dataset Overview\n- **Train set:** 977 samples  \n- **Test set:** 24 samples  \n- **Sample submission:** 1 Parquet file (`sample_submission.parquet`)  \n- No missing values found in any dataset ✅  \n- All numeric columns are clean (`id`, `fs`, `sig_len`)\n\n---\n\n### 🔹 Feature Insights\n| Feature | Meaning | Type | Observation |\n|----------|----------|------|--------------|\n| id | Unique ECG record ID | Integer | Unique per sample |\n| fs | Sampling frequency | Integer | Ranges from ~250 to 1025 Hz |\n| sig_len | Signal length | Integer | Roughly proportional to `fs` (≈ ×10) |\n\n---\n\n### 🔹 Train vs Test Comparison\n- Both **train** and **test** have similar `fs` distributions → good consistency.  \n- `sig_len` in train data shows no extreme outliers.  \n- Test data includes an additional `lead` column (ECG lead type).  \n\n---\n\n### 🔹 Correlation\n- Strong positive correlation between `fs` and `sig_len` (~1.0).  \n- Indicates each signal’s length depends on its sampling frequency.  \n\n---\n\n### 🔹 Sample Submission File\n- The Parquet file contains the **submission structure** — likely `id` + prediction columns.  \n- Useful for building final prediction output.\n\n---\n\n### Key Takeaways\n- Dataset is **clean, well-structured, and balanced**.  \n- Feature engineering can focus on relationships between `fs`, `sig_len`, and ECG images.  \n- No preprocessing required for missing/null handling.\n\n---\n **Next Step:**  \nMove on to **ECG Image Reconstruction / Signal Extraction** from the `train/` directory,  \nthen visualize and process the raw ECG waveforms.\n","metadata":{}},{"cell_type":"markdown","source":"## ECG Image File List & Size Check","metadata":{}},{"cell_type":"code","source":"import os\nfrom PIL import Image\nimport random\n\nTRAIN_DIR = '/kaggle/input/physionet-ecg-image-digitization/train'\nTEST_DIR = '/kaggle/input/physionet-ecg-image-digitization/test'\n\n# Recursively find all .png files in train dir\ntrain_files = []\nfor root, dirs, files in os.walk(TRAIN_DIR):\n    for f in files:\n        if f.endswith('.png'):\n            train_files.append(os.path.join(root, f))\n\n# Recursively find all .png files in test dir\ntest_files = []\nfor root, dirs, files in os.walk(TEST_DIR):\n    for f in files:\n        if f.endswith('.png'):\n            test_files.append(os.path.join(root, f))\n\nprint(\"Total train images:\", len(train_files))\nprint(\"Total test images:\", len(test_files))\n\n# Check a random train image (if exists)\nif train_files:\n    sample_img = random.choice(train_files)\n    with Image.open(sample_img) as img:\n        print(\"Sample image path:\", sample_img)\n        print(\"Image size:\", img.size, \"Mode:\", img.mode)\n        display(img)\nelse:\n    print(\"No train images found. Only test images available.\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-26T08:39:00.246340Z","iopub.execute_input":"2025-10-26T08:39:00.246881Z","iopub.status.idle":"2025-10-26T08:39:04.377526Z","shell.execute_reply.started":"2025-10-26T08:39:00.246856Z","shell.execute_reply":"2025-10-26T08:39:04.376281Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Grayscale Conversion & Pixel-to-Signal Extraction","metadata":{}},{"cell_type":"code","source":"import pandas as pd\n\n# Function to convert image to 1D signal\ndef image_to_signal(img_path):\n    with Image.open(img_path) as img:\n        gray = img.convert(\"L\")\n        arr = np.array(gray)\n        signal = arr.mean(axis=0)  # collapse vertical axis\n        signal = (signal - signal.min()) / (signal.max() - signal.min())  # normalize\n    return signal\n\n# Extract signals for first 5 train images (or fewer if not enough)\nnum_samples = min(5, len(train_files))\nsignals = []\n\nfor i in range(num_samples):\n    sig = image_to_signal(train_files[i])\n    signals.append(sig)\n\n# Convert to DataFrame (each row = one image's signal)\nmax_len = max(len(s) for s in signals)\nsignal_df = pd.DataFrame([np.pad(s, (0, max_len - len(s)), 'constant') for s in signals])\n\nprint(\"Signal DataFrame shape:\", signal_df.shape)\ndisplay(signal_df.head())\n\n# Plot all signals\nplt.figure(figsize=(12,6))\nfor i, s in enumerate(signals):\n    plt.plot(np.arange(len(s)), s, label=f\"Image {i+1}\")\nplt.title(\"ECG Signals from Images\")\nplt.xlabel(\"Time (pixels)\")\nplt.ylabel(\"Normalized Amplitude\")\nplt.legend()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-26T08:40:17.412282Z","iopub.execute_input":"2025-10-26T08:40:17.412967Z","iopub.status.idle":"2025-10-26T08:40:19.838346Z","shell.execute_reply.started":"2025-10-26T08:40:17.412944Z","shell.execute_reply":"2025-10-26T08:40:19.837436Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\n\n# Make processed folder\nprocessed_dir = '/kaggle/working/processed'\nos.makedirs(processed_dir, exist_ok=True)\n\n# Extract signals for test images\ntest_signals = []\nfor f in test_files:\n    sig = image_to_signal(f)\n    test_signals.append(sig)\n\n# Convert to DataFrame\nmax_len = max(len(s) for s in test_signals)\ntest_signal_df = pd.DataFrame([np.pad(s, (0, max_len - len(s)), 'constant') for s in test_signals])\n\n# Save CSV\ntest_csv_path = f'{processed_dir}/test_signals.csv'\ntest_signal_df.to_csv(test_csv_path, index=False)\nprint(\"Test signals saved to:\", test_csv_path)\n\n# Quick EDA on test signals\nprint(\"Test signals shape:\", test_signal_df.shape)\n\n# Plot all test signals\nplt.figure(figsize=(12,6))\nfor i, s in enumerate(test_signals):\n    plt.plot(np.arange(len(s)), s, label=f'Signal {i+1}')\nplt.title(\"Reconstructed ECG Signals (Test)\")\nplt.xlabel(\"Time (pixels)\")\nplt.ylabel(\"Normalized Amplitude\")\nplt.legend()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-26T08:42:57.689178Z","iopub.execute_input":"2025-10-26T08:42:57.689776Z","iopub.status.idle":"2025-10-26T08:42:58.168204Z","shell.execute_reply.started":"2025-10-26T08:42:57.689750Z","shell.execute_reply":"2025-10-26T08:42:58.167531Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Pretrained Model","metadata":{}},{"cell_type":"code","source":"from tensorflow.keras.applications import ResNet50\nfrom tensorflow.keras.models import Model\nfrom tensorflow.keras.layers import Dense, GlobalAveragePooling2D, Input\n\n# Input shape (for image) \ninput_shape = (224,224,3)  # signal -> spectrogram if needed\n\nbase_model = ResNet50(weights='imagenet', include_top=False, input_shape=input_shape)\n\n# Freeze base model\nfor layer in base_model.layers:\n    layer.trainable = False\n\n# Add custom classifier\nx = base_model.output\nx = GlobalAveragePooling2D()(x)\nx = Dense(128, activation='relu')(x)\nx = Dense(1, activation='sigmoid')(x)  # binary classification example\n\nmodel = Model(inputs=base_model.input, outputs=x)\n\nmodel.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])\nmodel.summary()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-26T08:54:56.989868Z","iopub.execute_input":"2025-10-26T08:54:56.990172Z","iopub.status.idle":"2025-10-26T08:54:59.282800Z","shell.execute_reply.started":"2025-10-26T08:54:56.990148Z","shell.execute_reply":"2025-10-26T08:54:59.282040Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Prepare Small Dataset","metadata":{}},{"cell_type":"code","source":"import os\n\nTRAIN_DIR = '/kaggle/input/physionet-ecg-image-digitization/train'\n\nimage_files = []\nfor root, dirs, files in os.walk(TRAIN_DIR):\n    for file in files:\n        if file.endswith('.png'):\n            # store relative path from TRAIN_DIR\n            rel_dir = os.path.relpath(root, TRAIN_DIR)\n            rel_file = os.path.join(rel_dir, file)\n            image_files.append(rel_file)\n\nprint(f\"Total images found: {len(image_files)}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-26T08:58:10.099382Z","iopub.execute_input":"2025-10-26T08:58:10.099910Z","iopub.status.idle":"2025-10-26T08:58:11.178111Z","shell.execute_reply.started":"2025-10-26T08:58:10.099872Z","shell.execute_reply":"2025-10-26T08:58:11.177381Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nfrom sklearn.model_selection import train_test_split\n\n# Assign dummy labels (0 or 1) just for demonstration\nlabels = [0 if i % 2 == 0 else 1 for i in range(len(image_files))]\n\n# Create DataFrame\ndf = pd.DataFrame({\n    'filename': image_files,\n    'class': labels\n})\n\n# Split into train/validation\ntrain_df, val_df = train_test_split(df, test_size=0.2, random_state=42, stratify=df['class'])\n\nprint(\"Train samples:\", len(train_df))\nprint(\"Validation samples:\", len(val_df))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-26T08:58:31.443192Z","iopub.execute_input":"2025-10-26T08:58:31.443892Z","iopub.status.idle":"2025-10-26T08:58:31.456375Z","shell.execute_reply.started":"2025-10-26T08:58:31.443866Z","shell.execute_reply":"2025-10-26T08:58:31.455624Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Convert labels to string\ndf['class'] = df['class'].astype(str)\n\n# Split into train/validation again\ntrain_df, val_df = train_test_split(df, test_size=0.2, random_state=42, stratify=df['class'])\n\nprint(\"Train samples:\", len(train_df))\nprint(\"Validation samples:\", len(val_df))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-26T08:59:08.684625Z","iopub.execute_input":"2025-10-26T08:59:08.685097Z","iopub.status.idle":"2025-10-26T08:59:08.701055Z","shell.execute_reply.started":"2025-10-26T08:59:08.685071Z","shell.execute_reply":"2025-10-26T08:59:08.700349Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_gen = datagen.flow_from_dataframe(\n    train_df,\n    directory=TRAIN_DIR,\n    x_col='filename',\n    y_col='class',\n    target_size=(224,224),\n    batch_size=32,\n    class_mode='binary'\n)\n\nval_gen = datagen.flow_from_dataframe(\n    val_df,\n    directory=TRAIN_DIR,\n    x_col='filename',\n    y_col='class',\n    target_size=(224,224),\n    batch_size=32,\n    class_mode='binary'\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-26T08:59:21.263647Z","iopub.execute_input":"2025-10-26T08:59:21.264146Z","iopub.status.idle":"2025-10-26T08:59:26.778486Z","shell.execute_reply.started":"2025-10-26T08:59:21.264120Z","shell.execute_reply":"2025-10-26T08:59:26.777807Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Build Pretrained Model (Transfer Learning)","metadata":{}},{"cell_type":"code","source":"from tensorflow.keras.applications import ResNet50\nfrom tensorflow.keras.models import Model\nfrom tensorflow.keras.layers import Dense, GlobalAveragePooling2D, Dropout, Input\nfrom tensorflow.keras.optimizers import Adam\n\n# Input shape (image size)\ninput_shape = (224, 224, 3)\n\n# Load ResNet50 without top layer\nbase_model = ResNet50(weights='imagenet', include_top=False, input_shape=input_shape)\n\n# Freeze base model layers\nfor layer in base_model.layers:\n    layer.trainable = False\n\n# Add custom classification layers\nx = base_model.output\nx = GlobalAveragePooling2D()(x)\nx = Dense(128, activation='relu')(x)\nx = Dropout(0.3)(x)\noutput = Dense(1, activation='sigmoid')(x)  # binary classification\n\n# Final model\nmodel = Model(inputs=base_model.input, outputs=output)\n\n# Compile model\nmodel.compile(optimizer=Adam(learning_rate=1e-4),\n              loss='binary_crossentropy',\n              metrics=['accuracy'])\n\nmodel.summary()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-26T09:00:03.367316Z","iopub.execute_input":"2025-10-26T09:00:03.367902Z","iopub.status.idle":"2025-10-26T09:00:04.542867Z","shell.execute_reply.started":"2025-10-26T09:00:03.367873Z","shell.execute_reply":"2025-10-26T09:00:04.542158Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Train the Model","metadata":{}},{"cell_type":"code","source":"# Reduce image size\ntrain_gen = datagen.flow_from_dataframe(\n    train_df,\n    directory=TRAIN_DIR,\n    x_col='filename',\n    y_col='class',\n    target_size=(128,128),\n    batch_size=16,\n    class_mode='binary'\n)\nval_gen = datagen.flow_from_dataframe(\n    val_df,\n    directory=TRAIN_DIR,\n    x_col='filename',\n    y_col='class',\n    target_size=(128,128),\n    batch_size=16,\n    class_mode='binary'\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-26T09:06:41.260434Z","iopub.execute_input":"2025-10-26T09:06:41.260701Z","iopub.status.idle":"2025-10-26T09:06:46.977685Z","shell.execute_reply.started":"2025-10-26T09:06:41.260681Z","shell.execute_reply":"2025-10-26T09:06:46.976933Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from tensorflow.keras.callbacks import EarlyStopping\n\n# Stop early if val_loss doesn't improve\nearly_stop = EarlyStopping(monitor='val_loss', patience=5, restore_best_weights=True)\n\nhistory = model.fit(\n    train_gen,\n    validation_data=val_gen,\n    epochs=20,\n    callbacks=[early_stop]\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-26T09:07:23.869852Z","iopub.execute_input":"2025-10-26T09:07:23.870136Z","iopub.status.idle":"2025-10-26T09:45:19.336532Z","shell.execute_reply.started":"2025-10-26T09:07:23.870112Z","shell.execute_reply":"2025-10-26T09:45:19.335543Z"}},"outputs":[],"execution_count":null}]}