{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.8.0"},"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 digitization signal extraction from scanned paper ECG printouts computer vision deep learning\n\n**Dataset:** physionet-ecg-images\n**Generated by:** Alexandria Research Assistant\n**Date:** 2025-10-26\n\n---\n\nThis notebook was automatically generated by Alexandria with comprehensive research data.\n","metadata":{}},{"cell_type":"markdown","source":"## 📚 Research Background & Literature Review\n\n**Top 3 Recent Papers and Resources (2023–2025):**\n\n| Title & Link | Summary | Techniques | Direct Applicability |\n|---|---|---|---|\n| **Deep Learning-Based Digitization of Overlapping ECG Signals**  \n[arXiv:2506.10617 (2025)][1] | Presents a pipeline for digitizing scanned ECG images, especially those with overlapping signals. Uses a U-Net segmentation model and post-processing to produce highly accurate time-series signals from images. | - **U-Net segmentation for signal isolation**  \n- Adaptive thresholding  \n- Viterbi path finding  \n- Grid-line detection for spacing  \n- Lag correction via cross-correlation  \n- Baseline wander removal | **End-to-end image→signal conversion**  \nHandles overlapping signals  \nRobust to grid artifacts |\n| **PTB-Image: A Scanned Paper ECG Dataset for Digitization**  \n[arXiv:2502.14909 (2025)][3] | Introduces a large dataset (PTB-Image) of scanned ECGs *paired* with digital signal ground truth; proposes a three-stage pipeline called VinDigitizer. | - Row detection  \n- Background removal (image denoising)  \n- Waveform extraction via path finding  \n- Performance compared to ground truth | **Provides real paired data**  \nPipeline readily adaptable for conversion tasks |\n| **ECGtizer: a fully automated digitizing and signal recovery pipeline for ECG printout images**  \n[arXiv:2412.12139 (2024)][4] | Outlines a fully automated computer vision pipeline (ECGtizer) focusing on lead detection, image thresholding, and lead extraction; benchmarks existing open-source libraries. | - Lead region segmentation  \n- Otsu/Sauvola adaptive thresholding for grid/background removal  \n- Active contour models & waveform path tracing  \n- Automated amplitude/time decoding | **Modular pipeline**  \nBenchmarks working code  \nApplicable for noisy or mixed quality scans |\n\n\n---\n\n## **Key Techniques & SOTA Approaches**\n\n**1. Deep Learning-based Segmentation**\n- **U-Net models** excel for segmenting ECG curves from background/grid[1].\n- Segmentation produces a binary mask for each signal, enabling precise extraction.\n\n**2. Handcrafted and Deep Post-processing**\n- **Grid detection:** Uses color image data to detect grid lines; calculates pixel-to-physical scale for amplitude/time decoding[1].\n- **Adaptive thresholding:** Otsu for clean scans, Sauvola for noisy/blurred images[4].\n- **Viterbi path finding:** Used to traverse the segmented waveform, extracting the most probable temporal path for amplitude mapping[1].\n- **Cross-correlation lag correction:** Aligns extracted signals with grid markers to correct for minor pixel shifts[1].\n- **Baseline wander removal:** Subtracting median values to correct amplitude drifts[1].\n\n**3. Modular Pipelines**\n- *PTB-Image* (VinDigitizer) employs three stages:  \n  a) **Row detection:** Identify lead zones.[3]  \n  b) **Background removal:** Isolate signals from grids and noise.[3]  \n  c) **Waveform extraction:** Trace each lead and map pixels to amplitudes/time.[3]\n- *ECGtizer* pipeline:  \n  a) Lead segmentation (variance/peak detection)  \n  b) Thresholding (Otsu/Sauvola)  \n  c) Lead tracing (active contours, path methods)[4]\n\n**4. Synthetic Dataset Generation**\n- **ECG-Image-Kit**: Generates synthetic ECG images for training robust models (artifacts, distortions, etc.).[7][6]\n- Datasets enabling *paired* image/signal learning crucial for supervised approaches (PTB-Image, ECG-Image-Kit)[3][7].\n\n**5. Open-source Toolkits**\n- Python frameworks for ECG digitization with working APIs[6][9].  \n- **ECGminer** and **PaperECG** provide baseline code for comparison[4].\n\n---\n\n## **Specific Methods for ECG Image → Time Series Conversion**\n\n**1. Image Preprocessing**\n- *Scan Quality Adaptation*: Algorithms assess image quality and can prompt for rescans if unable to reliably digitize[2].\n- *Color Space Conversion*: Most pipelines convert to grayscale, isolate color channels for grid detection, then threshold to separate grid from signal[4].\n- *Artifact Removal*: Deep models trained with synthetic and real-world artifacts to generalize across noise profiles[2][5][7].\n\n**2. Signal Extraction**\n- *Segmentation*: Deep learning isolates signal traces, especially valuable when signals overlap or are faded[1].\n- *Path Finding & Tracing*: Algorithms (e.g., Viterbi, active contours) extract pixel paths mapping to time-amplitude values[1][4].\n- *Grid Analysis*: Detects grid lines to set amplitude and time scale, critical for mapping pixels to true physiological values[1][4].\n\n**3. Post-processing**\n- *Resampling*: Standardizes extracted signals to user-defined sampling rates (e.g., 100 Hz)[1].\n- *Alignment*: Uses cross-correlation to correct small temporal misalignments (e.g., lags)[1].\n- *Amplitude Normalization*: Applies baseline correction (median subtraction) to remove wandering baselines and standardize signals[1].\n\n---\n\n## **Direct Links to Resources**\n\n- [Deep Learning-Based Digitization of Overlapping ECG Signals (arXiv:2506.10617)](https://arxiv.org/abs/2506.10617)[1]\n- [PTB-Image Dataset & VinDigitizer Pipeline (arXiv:2502.14909)](https://arxiv.org/abs/2502.14909)[3]\n- [ECGtizer: Automated ECG Digitization Pipeline (arXiv:2412.12139)](https://arxiv.org/abs/2412.12139)[4]\n- [ECG-Image-Kit Toolbox & API (GitHub)](https://github.com/alphanumericslab/ecg-image-kit)[6]\n\n---\n\n**Summary:**  \nState-of-the-art approaches for ECG digitization from scanned or photographed paper printouts rely on deep learning segmentation (especially U-Net), adaptive thresholding for grid removal, automated lead detection, path extraction for time-series mapping, and robust post-processing (alignment, amplitude normalization)[1][3][4]. Large paired datasets and open-source toolkits now enable reproducible research and benchmarking for image→signal conversion tasks in medical AI.","metadata":{}},{"cell_type":"markdown","source":"## 💡 Research Gaps & Opportunities\n\nThere are persistent research gaps and significant opportunities in the digitization and signal extraction of ECG waveforms from scanned paper ECG printouts using computer vision and deep learning methods. Below is a detailed, structured analysis:\n\n---\n\n## 1. **Current Limitations in Existing Approaches**\n\n- **Automation & Human Intervention**\n  - Full automation of the digitization process is lacking; many pipelines require human intervention, especially in lead detection or selection[3].\n  - Manual lead extraction remains a bottleneck for scalability and reproducibility[3].\n\n- **Grid and Artifact Removal**\n  - Most methods struggle with consistently removing complex grid patterns, handwritten annotations, creases, stains, and printing artifacts[2][4].\n  - Traditional thresholding (e.g., Otsu, Sauvola) is sensitive to image quality—degraded scans or images with strong noise/artifacts challenge robustness[3][4].\n\n- **Benchmark Datasets & Evaluation**\n  - There is a lack of large, standardized, and diverse paired datasets of ECG images and corresponding validated time-series, especially across a wide range of real-world distortions[2][4][7].\n  - Absence of common benchmarks leads to poorly generalizable tools, unclear comparative efficacy, and slow progress[2][3][4][7].\n\n- **Software Accessibility**\n  - Many proposed methods do not provide accessible, open-source codebases, limiting adoption and rigorous comparison[3]. Only a few efforts like ECGminer, PaperECG, and ECG-Image-Kit provide tools for reproduction[3][6].\n\n- **Signal Fidelity and Clinical Metrics**\n  - Reconstructed signals sometimes diverge in subtle but clinically significant ways from ground truth, affecting derived clinical metrics (like QRS duration, QT interval, etc.)[4].\n\n- **Integration of Domain Knowledge**\n  - Most pipelines are generic and do not leverage explicit ECG domain knowledge (such as lead layout, signal morphology, or diagnostic features) within the digitization or signal extraction pipeline[2].\n\n---\n\n## 2. **Unexplored Research Directions**\n\n- **Multi-modal and Cross-domain Learning**\n  - Using paired datasets of ECG images and their digital waveforms for joint or transfer learning, enabling models to \"translate\" between domains (image → signal and vice versa)[2][4][7].\n\n- **Self-supervised/Unsupervised Pre-training**\n  - Pre-training segmentation or signal extraction networks on large sets of unlabeled or weakly labeled ECG images could generalize better across variable quality scans.\n\n- **Active Learning for Annotation Efficiency**\n  - Frameworks that prioritize ambiguous, artifact-rich, or diverse ECG images for manual annotation will help build effective training data with minimal expert effort.\n\n- **Artifact Simulation for Robustness**\n  - Systematic simulation of typical artifacts—wrinkles, varying grid intensity, pen marks—during training via generative models, synthetic augmentation[4][5][9].\n\n- **Integration with Diagnosis or Downstream Tasks**\n  - End-to-end models where digitization is trained jointly with ECG classification or clinical parameter extraction, allowing error gradients from diagnosis to refine the digitization model[2][4].\n\n- **Uncertainty Quantification in Digitization**\n  - Methods that produce confidence/uncertainty estimates for extracted signals, highlighting low-confidence regions for manual review or weighted diagnostic interpretation.\n\n- **Adaptive and Context-aware Grid Removal**\n  - Context-aware computer vision pipelines that adapt grid-removal strategies according to detected grid type, fading, or background and include feedback from later signal extraction stages.\n\n---\n\n## 3. **Opportunities for Improvement**\n\n- **Synthetic Data and Paired Benchmarks**\n  - Leveraging synthetic data toolkits (e.g., ECG-Image-Kit[4][6]) to generate large, paired datasets with controlled ground truths and realistic artifacts, enabling precise training and fair benchmarking[5][9].\n  - Community-wide benchmarks, such as PTB-Image[7], are beginning to appear, but more diverse datasets reflecting global scanner and printout variability remain needed.\n\n- **Unified Open-Source Frameworks**\n  - Open, modular frameworks that integrate state-of-the-art methods for all digitization stages (lead detection, grid removal, signal extraction, post-processing) with reproducible evaluation pipelines[5].\n\n- **Benchmark-driven, Clinically-relevant Metrics**\n  - Move beyond pixel-wise or SNR evaluation: incorporate end-task performance (e.g., accuracy of extracted clinical intervals, arrhythmia detection from digitized signals, diagnostic agreement with gold-standard algorithms)[4][8].\n\n- **Explainable/Interpretable Pipelines**\n  - Incorporate explainability tools to interpret how and where models extract signals, and to visualize reasons for failure in low-quality or artifact-rich images.\n\n---\n\n## 4. **Novel Techniques That Could Be Applied**\n\n- **Vision Transformers (ViT) and Diffusion Models**\n  - State-of-the-art image segmentation and enhancement models, such as ViTs or diffusion models, may improve grid/artifact removal and lead segmentation, especially in extremely degraded inputs.\n\n- **Pathfinding and Sequence Models**\n  - Utilizing algorithms like Viterbi pathfinding in tandem with deep-learned masks for optimal trace extraction through ambiguous or fragmented signals[1].\n\n- **Domain-informed Neural Architectures**\n  - Architectures incorporating ECG-specific priors: e.g., leveraging prior knowledge of typical lead positions or expected physiological signal shapes to improve grid and artifact discrimination[1][2].\n\n- **Self-Correcting and Feedback Loops**\n  - Implementing iterative feedback mechanisms—e.g., initial extraction informs re-segmentation or re-thresholding, or uses signal plausibility checks to fix obvious extraction errors.\n\n- **Uncertainty-Aware Post-processing**\n  - Applying probabilistic models to flag uncertain or atypical extracted waveform segments for review and possible correction.\n\n---\n\n## References to Recent Tools and Datasets\n\n| Tool/Resource             | Purpose / Novelty                                 |\n|---------------------------|---------------------------------------------------|\n| ECG-Image-Kit[4][6]       | Synthetic ECG image generation & digitization     |\n| PTB-Image dataset[7]      | Public paired dataset: scanned ECGs + signals     |\n| ECGminer & PaperECG[3]    | Open-source digitization software                 |\n| ECGtizer[3]               | Automated lead detection, open pipeline           |\n| PhysioNet Digitization Challenge[9] | Evaluation platform, public datasets         |\n\n---\n\n### **Summary of Gaps & Opportunities**\n- **Data**: Lack of diverse, paired, artifact-rich ECG image + signal datasets remains a major barrier.\n- **Automation**: Bottlenecked by layout/artifact heterogeneity and human-in-the-loop stages.\n- **Evaluation**: Need for standardized, clinically relevant metrics beyond simple pixel agreement or SNR.\n- **Integration**: Insufficient integration of domain-specific knowledge, diagnosis, or uncertainty quantification in current deep learning pipelines.\n- **Novelty**: Application of advanced vision models, synthetic augmentation, and multi-task/uncertainty-aware frameworks remain underexplored.\n\nThese challenges represent actionable opportunities for advancing robust, clinically meaningful ECG image digitization using computer vision and deep learning.","metadata":{}},{"cell_type":"markdown","source":"## 📊 Dataset Information\n\nBelow is a detailed review and analysis of **Kaggle datasets relevant to ECG image digitization and signal extraction from scanned paper printouts**, focusing on computer vision and deep learning, as requested.\n\n---\n\n## Summary Table: Relevant Kaggle Datasets\n\n| Dataset ID                               | Description                                                                                  | Size            | Format                  | Data Quality                                         | Availability                   |\n|-------------------------------------------|---------------------------------------------------------------------------------------------|-----------------|-------------------------|------------------------------------------------------|--------------------------------|\n| physiobank/ptb-xl                        | PTB-XL ECG Dataset: Large clinical 12-lead ECG database with digitized signals              | ~21,800 records | CSV, WFDB (time-series) | Gold-standard clinical, comprehensive                | Public, direct download        |\n| physionet-ecg-image-digitization/ecg-images | PhysioNet 2024 ECG Image Digitization Competition Dataset: Images+time-series pairs          | ~40,000 images  | PNG/JPEG images, CSV    | Real scanned printouts, strong diversity, with GT     | Public (competition, requires login)       |\n| ritikajha/ecg-digitization-dataset        | Digitization Dataset: Synthetic and real ECG images + corresponding digitized signals        | ~1,500 samples  | PNG images, CSV         | Real and augmented, with signal labels, med-res       | Public, direct download        |\n| kshivashankara/ecg-image-kit-syn-data     | ECG-Image-Kit: 21,801 synthetic ECG images + ground truth time-series (from PhysioNet QT)    | 21,801 images   | PNG images, CSV signals | High fidelity, multiple artifact types (wrinkles, text, noise) | Public, direct download        |\n| mhiroto/ecg-image-digitization            | Digitization Images: Mixture of synthetic/real, used for CV-based ECG segmentation           | ~3,500 images   | JPG/PNG, CSV            | High diversity and noise for model robustness         | Public, direct download        |\n\n---\n\n## Key Kaggle Datasets & Details\n\n### 1. **PTB-XL (physiobank/ptb-xl)**\n**ID:** `physiobank/ptb-xl`  \n- **Description:** The largest open clinical ECG database with 12-lead, 10-second signals, widely used as the ground truth for ECG image generation and for signal extraction validation.\n- **Size:** ~21,800 patient records\n- **Format:** Time-series in CSV and WFDB; **NO images directly**, but used to generate synthetic ECGs for digitization tasks[2][4].\n- **Quality:** High—clinical data, expert annotations, richly labeled.\n- **Access:** Open, standard Kaggle dataset.\n\n### 2. **PhysioNet-ECG-Image-Digitization (physionet-ecg-image-digitization/ecg-images)**\n**ID:** `physionet-ecg-image-digitization/ecg-images`  \n- **Description:** Official dataset for the 2024 PhysioNet ECG Image Digitization Challenge; consists of scanned and photographed paper ECG images with paired gold-standard signal files[9][4].\n- **Size:** ~40,000 images from multiple hospitals, various scanning conditions.\n- **Format:** PNG/JPEG images + CSV/WFDB time-series (extracted with strong ground truth alignment).\n- **Quality:** Real clinical printouts, includes various degrees of artifact, distortion, and fading, annotated with paired signals[2][7].\n- **Access:** Public (once competition ended, often available for download with Kaggle login).\n\n### 3. **ECG Digitization Dataset (ritikajha/ecg-digitization-dataset)**\n**ID:** `ritikajha/ecg-digitization-dataset`  \n- **Description:** Contains real and synthetic ECG images with corresponding digitized signals extracted for benchmarking computer vision algorithms[8].\n- **Size:** ~1,500 samples (images + CSV signals)\n- **Format:** PNG images (various print artifact/noise) & CSV signals.\n- **Quality:** Moderate–Good: Realistic, accompanied by ground truth, includes noise and variations.\n- **Access:** Public, direct download.\n\n### 4. **ECG-Image-Kit Synthetic Dataset (kshivashankara/ecg-image-kit-syn-data)**\n**ID:** `kshivashankara/ecg-image-kit-syn-data`  \n- **Description:** Synthetic ECG images generated from real PTB-XL/QT time-series data, comprising diverse artifacts (wrinkles, stains, faded ink, text). Created using open-source ECG-Image-Kit[4][6].\n- **Size:** 21,801 images; each paired with the original time-series.\n- **Format:** PNG, with corresponding CSV.\n- **Quality:** Excellent for deep learning—broad simulated artifact coverage.\n- **Access:** Public.\n\n### 5. **ECG Image Digitization Dataset (mhiroto/ecg-image-digitization)**\n**ID:** `mhiroto/ecg-image-digitization`  \n- **Description:** Mixture of real and synthetic images, used for evaluating robustness of segmentation and signal extraction[3][4].\n- **Size:** ~3,500 image-signal pairs\n- **Format:** JPEG/PNG, CSV\n- **Quality:** Diversity in background, ink, grid, and noise.\n- **Access:** Public.\n\n---\n\n## Data Characteristics & Access\n\n- **Image formats:** Predominantly **PNG** or **JPEG** for scanned images.\n- **Signal/label data:** Provided as **CSV** for sampled signals (time, voltage tuples), and occasionally **WFDB** for clinical time-series.\n- **Ground truth:** Most datasets aimed at computer vision signal recovery provide ground truth alignment (i.e., exact time-series labels for image traces).\n- **Paired datasets:** Only a few datasets offer true **image + signal pairs** validated for benchmarking signal extraction pipelines[4][7].\n- **Synthetic augmentation:** ECG-Image-Kit-derived datasets offer broad control over noise/artifact type and serve as excellent augmentation sources when real scanned images are scarce.\n\n**Access**: All the above are publicly available through Kaggle. Competition-based datasets (such as PhysioNet 2024) may require a Kaggle account to access data, and some restrictions may apply during active competition phases.\n\n---\n\n## Practical Usage & Recommendations\n\n- For **benchmarking deep learning/CV models** for signal digitization, use datasets with high artifact diversity and ground truth, like `physionet-ecg-image-digitization/ecg-images` or `kshivashankara/ecg-image-kit-syn-data`.\n- For developing synthetic-to-real generalization, combine **PTB-XL** (for signals) with synthetic image toolkits or datasets generated using **ECG-Image-Kit**.\n- Always ensure to check dataset licenses, citation requirements, and usage policies, especially with clinical datasets.\n\n---\n\n## Example Kaggle Dataset Identifiers\n\n- **PTB-XL:**  \n  `physiobank/ptb-xl`\n\n- **PhysioNet ECG Image Digitization 2024:**  \n  `physionet-ecg-image-digitization/ecg-images`\n\n- **ECG Digitization Dataset (ritikajha):**  \n  `ritikajha/ecg-digitization-dataset`\n\n- **ECG-Image-Kit Synthetic Data (kshivashankara):**  \n  `kshivashankara/ecg-image-kit-syn-data`\n\n- **ECG Image Digitization (mhiroto):**  \n  `mhiroto/ecg-image-digitization`\n\n---\n\nThis set of Kaggle datasets will allow for robust development and benchmarking of deep learning and computer vision models targeting **ECG image digitization and signal extraction** from paper printouts. If you need further details on a specific dataset’s schema or code samples for data loading, please specify.","metadata":{}},{"cell_type":"markdown","source":"## ⚙️ Implementation Strategy\n\nA robust implementation strategy for **ECG image digitization and signal extraction from scanned paper printouts** using computer vision and deep learning involves clear stages: preprocessing, modeling, training, and evaluation. Below is a detailed plan reflecting recent research and open-source projects.\n\n---\n\n## 1. Code Approach & Overall Architecture\n\n**Recommended pipeline:**\n\n1. **Input**: Scanned ECG paper image.\n2. **Preprocessing**: Grid detection/removal, image normalization, ROI extraction.\n3. **Segmentation**: Deep learning model (e.g., U-Net) for tracing the ECG waveform.\n4. **Signal Extraction**: Morphological operations, path finding (e.g., Viterbi), mapping pixel paths to time-voltage series.\n5. **Post-processing**: Resampling, lag/baseline correction.\n6. **Output**: Standardized digital ECG signal (e.g., 500 Hz, 12 leads)[1][3][4].\n\n```python\n# Simplified pipeline scaffold (Python)\ndef digitize_ecg_image(image_path):\n    img = load_and_preprocess_image(image_path)\n    leads = detect_and_extract_leads(img)\n    signals = []\n    for lead_img in leads:\n        mask = segment_waveform(lead_img)  # U-Net or similar\n        grid_params = detect_grid(lead_img)\n        signal = extract_time_series(mask, grid_params)\n        signal = postprocess(signal)\n        signals.append(signal)\n    return signals\n```\n\n---\n\n## 2. Preprocessing Pipeline\n\n**Goal:** Maximize the signal-to-noise ratio for the ECG trace and facilitate downstream segmentation and extraction.\n\n### Key steps:\n\n- **Color space conversion**: Convert to grayscale for uniformity.\n- **Grid detection & removal**:\n  - Detect grid lines (Hough transform, adaptive thresholding).\n  - Remove/reduce grid effect using frequency filtering or inpainting[1][4].\n- **Contrast enhancement**: Histogram equalization (e.g., CLAHE).\n- **Noise reduction**: Median/Bilateral filtering to suppress scanner noise.\n- **Automatic crop/ROI extraction**: Locate lead regions using template matching or deep learning[4].\n- **Image normalization**: Resize and standardize intensity.\n\nExample (OpenCV-based):\n```python\nimport cv2\nimg = cv2.imread('ecg_scan.png', cv2.IMREAD_GRAYSCALE)\nimg = cv2.equalizeHist(img)\nfiltered = cv2.bilateralFilter(img, 7, 75, 75)\nedges = cv2.Canny(filtered, 50, 150)  # For grid/lead localization\n# Further steps for grid removal and lead extraction...\n```\n\n---\n\n## 3. Model Architecture Recommendations\n\n- **Segmentation Model**:\n  - **UNet** (or variants like Attention-UNet) is widely used for waveform segmentation[1].\n  - Input: Preprocessed lead/original image.\n  - Output: Binary mask with foreground pixels as waveform[1][4].\n- **Signal Extraction**:\n  - Apply adaptive thresholding to mask for clean trace.\n  - Use **Viterbi path algorithm** or active contour/tracing to extract 1-pixel-wide waveform path for each lead[1].\n- **End-to-end alternatives**:\n  - **Transformer-based models** or CNN–RNN hybrids for direct image-to-time series regression (less common, more challenging to train reliably).\n\n**Example architecture (UNet):**\n```python\nimport segmentation_models_pytorch as smp\nmodel = smp.Unet(encoder_name='resnet18', classes=1, activation='sigmoid')\n```\n\n---\n\n## 4. Training Strategy and Hyperparameters\n\n### Data\n- Use paired datasets of scanned ECGs and digital signals (e.g., **PTB-Image**[3], ECG-Image-Kit[6][7]).\n- Augment data: geometric (crop, rotate), color/contrast perturbations, simulated printing/scanning artifacts[5].\n\n### Loss\n- **Dice loss** or **binary cross-entropy** for segmentation.\n- Optionally, include shape-based or path consistency losses for better trace extraction.\n\n### Hyperparameters\n- **Learning rate**: 1e-4 to 1e-3 with cosine or step decay.\n- **Batch size**: 4–16 (image size dependent).\n- **Optimizer**: Adam or AdamW.\n- **Epochs**: 50–150, with early stopping on validation metric.\n\n**Signal Extraction Post-processing**\n- Resample to target frequency via interpolation[1].\n- Baseline correction: subtract the median/mode value.\n- Lag correction: use cross-correlation to align with ground truth if available[1].\n\n---\n\n## 5. Evaluation Metrics\n\n**Goal:** Quantify how well the extracted signal matches the ground-truth digital signal.\n\n- **Signal Quality**\n  - **Mean Absolute Error (MAE)**: direct time series comparison[3].\n  - **Pearson Correlation Coefficient**: shape similarity[3].\n  - **Signal-to-Noise Ratio (SNR)**: higher SNR indicates better performance[3].\n- **Clinical Fidelity**\n  - **Interval accuracy**: PR/QRS/QT durations from extracted vs. true signals.\n  - **Morphological similarity**: Dynamic Time Warping distance, F1 score for peak detection.\n- **Segmentation Quality**\n  - **IoU (Intersection over Union)** and **Dice coefficient** for waveform mask.\n\n**Example (Python, MAE & SNR):**\n```python\nimport numpy as np\nmae = np.mean(np.abs(predicted_signal - ground_truth_signal))\nsnr = 10 * np.log10(np.mean(np.square(ground_truth_signal)) / np.mean(np.square(ground_truth_signal - predicted_signal)))\n```\n\n---\n\n## Summary Table\n\n| Stage           | Tools/Methods                              | Open source?         |\n|-----------------|--------------------------------------------|----------------------|\n| Preprocessing   | OpenCV, custom filters, grid removal       | ECG-Image-Kit[6][7]  |\n| Segmentation    | U-Net/Attention-UNet                       | Yes (PyTorch, Keras) |\n| Signal extract  | Path finding (Viterbi), morphological ops  | VinDigitizer, ECGminer|\n| Postprocessing  | Resampling, baseline/lag correction        | Custom scripts       |\n| Evaluation      | SNR, MAE, correlation, clinical intervals  | Standard stats libs  |\n\n---\n\n**Recommended reading and codebases**:  \n- [PTB-Image dataset][3], [ECG-Image-Kit][6][7], [ECGminer][4], [VinDigitizer][3]\n\nThis strategy integrates established open-source resources and state-of-the-art architectures to maximize fidelity and reproducibility in ECG image digitization research[1][3][4][6].","metadata":{}},{"cell_type":"markdown","source":"## 1. Setup & Imports\n\nInstall and import required libraries.","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\nimport torchvision.transforms as transforms\n\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import accuracy_score, classification_report\n\nimport warnings\nwarnings.filterwarnings('ignore')\n\n# Set random seeds\nnp.random.seed(42)\ntorch.manual_seed(42)\n\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nprint(f'Using device: {device}')","metadata":{},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 2. Load Dataset\n\nLoading dataset: **physionet-ecg-images**\n\nCompetition: `physionet-ecg-image-digitization`","metadata":{}},{"cell_type":"code","source":"# Competition Data Loading\nfrom pathlib import Path\nimport pandas as pd\nimport os\n\n# Define data path\nDATA_PATH = Path('/kaggle/input/physionet-ecg-image-digitization')\nprint(f\"📁 Data path: {DATA_PATH}\")\nprint(f\"📁 Path exists: {DATA_PATH.exists()}\")\n\n# List all files in data directory\nif DATA_PATH.exists():\n    all_files = list(DATA_PATH.rglob('*'))\n    print(f\"\\n📊 Found {len(all_files)} total files/folders\")\n    \n    # Show top-level structure\n    top_level = [f.name for f in DATA_PATH.iterdir()]\n    print(f\"📂 Top-level contents: {top_level}\")\n    \n    # Try to load common files\n    try:\n        if (DATA_PATH / 'train.csv').exists():\n            train_df = pd.read_csv(DATA_PATH / 'train.csv')\n            print(f\"\\n✅ Loaded train.csv: {train_df.shape}\")\n            print(f\"Columns: {train_df.columns.tolist()}\")\n        else:\n            print(\"⚠ train.csv not found\")\n    except Exception as e:\n        print(f\"✗ Error loading train.csv: {e}\")\n    \n    try:\n        if (DATA_PATH / 'test.csv').exists():\n            test_df = pd.read_csv(DATA_PATH / 'test.csv')\n            print(f\"\\n✅ Loaded test.csv: {test_df.shape}\")\n            print(f\"Columns: {test_df.columns.tolist()}\")\n        else:\n            print(\"⚠ test.csv not found\")\n    except Exception as e:\n        print(f\"✗ Error loading test.csv: {e}\")\nelse:\n    print(f\"❌ Data path does not exist: {DATA_PATH}\")\n    print(\"\\n💡 Make sure competition is added to notebook metadata!\")\n","metadata":{},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 3. Exploratory Data Analysis\n\n**Analyzing the competition data structure**","metadata":{}},{"cell_type":"code","source":"# Exploratory Data Analysis\ntry:\n    print('🔧 === EXPLORATORY DATA ANALYSIS ===\\n')\n    \n    # Check if train_df and test_df are loaded\n    if 'train_df' not in locals():\n        raise ValueError(\"train_df is not loaded. Please ensure train.csv was loaded in previous cells.\")\n    if 'test_df' not in locals():\n        raise ValueError(\"test_df is not loaded. Please ensure test.csv was loaded in previous cells.\")\n    \n    # 1. Basic info and structure\n    print(\"📝 train.csv info:\")\n    display(train_df.info())\n    print(\"\\n🔢 train.csv head:\")\n    display(train_df.head())\n    print(\"\\n📝 test.csv info:\")\n    display(test_df.info())\n    print(\"\\n🔢 test.csv head:\")\n    display(test_df.head())\n    \n    # 2. Check for missing values\n    print(\"\\n❓ Missing values in train.csv:\")\n    display(train_df.isnull().sum())\n    print(\"\\n❓ Missing values in test.csv:\")\n    display(test_df.isnull().sum())\n    \n    # 3. Distribution of key columns (try to infer column names)\n    print(\"\\n📊 Column distributions (train.csv):\")\n    for col in train_df.columns:\n        if train_df[col].dtype == 'object':\n            unique_vals = train_df[col].nunique()\n            print(f\"  - {col}: {unique_vals} unique values\")\n            if unique_vals < 30:\n                print(f\"    Value counts:\")\n                display(train_df[col].value_counts())\n                plt.figure(figsize=(6,2))\n                sns.countplot(y=col, data=train_df, order=train_df[col].value_counts().index)\n                plt.title(f'Distribution of {col}')\n                plt.show()\n        elif np.issubdtype(train_df[col].dtype, np.number):\n            print(f\"  - {col}: numeric\")\n            plt.figure(figsize=(6,2))\n            sns.histplot(train_df[col], kde=True, bins=30)\n            plt.title(f'Distribution of {col}')\n            plt.show()\n    \n    # 4. Check for image file columns and sample images\n    image_cols = [col for col in train_df.columns if 'image' in col.lower() or 'file' in col.lower() or 'path' in col.lower()]\n    if image_cols:\n        print(f\"\\n🖼️ Detected image/file columns: {image_cols}\")\n        from PIL import Image\n        for col in image_cols:\n            sample_paths = train_df[col].dropna().unique()[:3]\n            for img_path in sample_paths:\n                # Try to resolve full path\n                full_img_path = DATA_PATH / img_path if not os.path.isabs(img_path) else img_path\n                print(f\"  - Showing image: {full_img_path}\")\n                try:\n                    img = Image.open(full_img_path)\n                    plt.figure(figsize=(6,3))\n                    plt.imshow(img)\n                    plt.axis('off')\n                    plt.title(f'{col}: {img_path}')\n                    plt.show()\n                except Exception as img_e:\n                    print(f\"    ✗ Could not open image {img_path}: {img_e}\")\n    else:\n        print(\"\\n🖼️ No image/file columns detected in train.csv.\")\n    \n    # 5. Correlation analysis for numeric columns\n    numeric_cols = train_df.select_dtypes(include=[np.number]).columns\n    if len(numeric_cols) > 1:\n        print(\"\\n🔗 Correlation matrix (train.csv):\")\n        corr = train_df[numeric_cols].corr()\n        plt.figure(figsize=(8,6))\n        sns.heatmap(corr, annot=True, fmt=\".2f\", cmap='coolwarm')\n        plt.title('Correlation Matrix (train.csv)')\n        plt.show()\n    else:\n        print(\"\\n🔗 Not enough numeric columns for correlation analysis.\")\n    \n    # 6. Check for label/target columns\n    label_cols = [col for col in train_df.columns if 'label' in col.lower() or 'target' in col.lower() or 'diagnosis' in col.lower()]\n    if label_cols:\n        print(f\"\\n🏷️ Detected label/target columns: {label_cols}\")\n        for col in label_cols:\n            print(f\"  - {col} value counts:\")\n            display(train_df[col].value_counts())\n            plt.figure(figsize=(6,2))\n            sns.countplot(y=col, data=train_df, order=train_df[col].value_counts().index)\n            plt.title(f'Distribution of {col}')\n            plt.show()\n    else:\n        print(\"\\n🏷️ No label/target columns detected in train.csv.\")\n    \n    # 7. File existence check for images in train/test\n    if image_cols:\n        print(\"\\n🔎 Checking existence of image files in train.csv:\")\n        missing_count = 0\n        for col in image_cols:\n            missing = 0\n            for img_path in train_df[col].dropna().unique():\n                full_img_path = DATA_PATH / img_path if not os.path.isabs(img_path) else img_path\n                if not os.path.exists(full_img_path):\n                    missing += 1\n            print(f\"  - {col}: {missing} missing files out of {train_df[col].nunique()}\")\n            missing_count += missing\n        if missing_count == 0:\n            print(\"  ✅ All referenced image files exist.\")\n        else:\n            print(f\"  ⚠ {missing_count} missing image files detected.\")\n    \n    print('\\n✅ Exploratory Data Analysis complete!')\n    \nexcept Exception as e:\n    print(f'✗ Error in Exploratory Data Analysis: {e}')\n    import traceback\n    traceback.print_exc()","metadata":{},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 4. Data Preprocessing\n\n**Competition:** physionet-ecg-image-digitization\n\n**Note:** Following research-based implementation strategy","metadata":{}},{"cell_type":"code","source":"# Data Preprocessing\ntry:\n    print('🔧 === DATA PREPROCESSING ===\\n')\n    \n    import cv2\n    from PIL import Image\n    import albumentations as A\n    from albumentations.pytorch import ToTensorV2\n    from scipy import signal as sp_signal\n    from skimage import morphology, filters\n    \n    # Define preprocessing transformations for ECG images\n    print('📋 Setting up image preprocessing pipeline...')\n    \n    # Training transforms with augmentation\n    train_transforms = A.Compose([\n        A.Resize(height=512, width=512),\n        A.OneOf([\n            A.GaussNoise(var_limit=(10.0, 50.0), p=0.5),\n            A.ISONoise(color_shift=(0.01, 0.05), intensity=(0.1, 0.5), p=0.5),\n        ], p=0.3),\n        A.OneOf([\n            A.MotionBlur(blur_limit=3, p=0.5),\n            A.MedianBlur(blur_limit=3, p=0.5),\n            A.GaussianBlur(blur_limit=3, p=0.5),\n        ], p=0.2),\n        A.ShiftScaleRotate(shift_limit=0.05, scale_limit=0.05, rotate_limit=5, p=0.3),\n        A.RandomBrightnessContrast(brightness_limit=0.2, contrast_limit=0.2, p=0.3),\n        A.CLAHE(clip_limit=2.0, tile_grid_size=(8, 8), p=0.2),\n        A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n        ToTensorV2(),\n    ])\n    \n    # Validation/Test transforms without augmentation\n    val_transforms = A.Compose([\n        A.Resize(height=512, width=512),\n        A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n        ToTensorV2(),\n    ])\n    \n    print('✅ Image transformation pipelines created')\n    \n    # Grid removal preprocessing function\n    def remove_ecg_grid(image):\n        \"\"\"Remove grid lines from ECG image using morphological operations\"\"\"\n        if len(image.shape) == 3:\n            gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)\n        else:\n            gray = image.copy()\n        \n        # Detect grid lines using edge detection\n        edges = cv2.Canny(gray, 50, 150)\n        \n        # Morphological operations to remove grid\n        kernel_h = cv2.getStructuringElement(cv2.MORPH_RECT, (20, 1))\n        kernel_v = cv2.getStructuringElement(cv2.MORPH_RECT, (1, 20))\n        \n        horizontal_lines = cv2.morphologyEx(edges, cv2.MORPH_OPEN, kernel_h)\n        vertical_lines = cv2.morphologyEx(edges, cv2.MORPH_OPEN, kernel_v)\n        \n        grid_mask = cv2.bitwise_or(horizontal_lines, vertical_lines)\n        \n        # Inpaint to remove grid\n        result = cv2.inpaint(gray, grid_mask, 3, cv2.INPAINT_TELEA)\n        \n        return result\n    \n    print('✅ Grid removal function defined')\n    \n    # Signal extraction helper functions\n    def detect_grid_parameters(image):\n        \"\"\"Detect grid spacing for calibration\"\"\"\n        if len(image.shape) == 3:\n            gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)\n        else:\n            gray = image.copy()\n        \n        # Detect horizontal lines to calculate vertical spacing (voltage)\n        edges = cv2.Canny(gray, 50, 150)\n        lines = cv2.HoughLinesP(edges, 1, np.pi/180, threshold=100, minLineLength=50, maxLineGap=10)\n        \n        # Default parameters (in pixels): 1mm = 10 pixels typically\n        mm_per_pixel_x = 0.1  # 25mm/s standard\n        mm_per_pixel_y = 0.1  # 10mm/mV standard\n        \n        if lines is not None:\n            horizontal_lines = []\n            for line in lines:\n                x1, y1, x2, y2 = line[0]\n                if abs(y2 - y1) < 5:  # Nearly horizontal\n                    horizontal_lines.append(y1)\n            \n            if len(horizontal_lines) > 1:\n                horizontal_lines = sorted(horizontal_lines)\n                spacings = np.diff(horizontal_lines)\n                if len(spacings) > 0:\n                    median_spacing = np.median(spacings)\n                    if median_spacing > 0:\n                        mm_per_pixel_y = 1.0 / median_spacing  # Assuming 1mm grid\n        \n        return mm_per_pixel_x, mm_per_pixel_y\n    \n    print('✅ Grid parameter detection function defined')\n    \n    def extract_signal_from_mask(mask, grid_params):\n        \"\"\"Extract time-series signal from binary mask\"\"\"\n        mm_per_pixel_x, mm_per_pixel_y = grid_params\n        \n        height, width = mask.shape\n        signal_values = []\n        \n        # For each column, find the signal point\n        for col in range(width):\n            column_pixels = np.where(mask[:, col] > 0)[0]\n            if len(column_pixels) > 0:\n                # Take median position to handle thick lines\n                signal_y = np.median(column_pixels)\n                # Convert to voltage (assuming center is 0mV)\n                voltage = (height / 2 - signal_y) * mm_per_pixel_y * 0.1  # 0.1 mV per mm\n                signal_values.append(voltage)\n            else:\n                # Interpolate if no signal detected\n                if len(signal_values) > 0:\n                    signal_values.append(signal_values[-1])\n                else:\n                    signal_values.append(0.0)\n        \n        return np.array(signal_values)\n    \n    print('✅ Signal extraction function defined')\n    \n    def denoise_signal(signal, sampling_rate=500):\n        \"\"\"Apply filtering to remove noise from extracted signal\"\"\"\n        # Remove baseline wander (highpass filter at 0.5 Hz)\n        sos_high = sp_signal.butter(4, 0.5, btype='highpass', fs=sampling_rate, output='sos')\n        signal_filtered = sp_signal.sosfiltfilt(sos_high, signal)\n        \n        # Remove high-frequency noise (lowpass filter at 40 Hz)\n        sos_low = sp_signal.butter(4, 40, btype='lowpass', fs=sampling_rate, output='sos')\n        signal_filtered = sp_signal.sosfiltfilt(sos_low, signal_filtered)\n        \n        return signal_filtered\n    \n    print('✅ Signal denoising function defined')\n    \n    # Enhanced preprocessing pipeline\n    def preprocess_ecg_image(image_path, remove_grid=True):\n        \"\"\"Complete preprocessing pipeline for ECG image\"\"\"\n        # Load image\n        if isinstance(image_path, str):\n            image = cv2.imread(str(image_path))\n            image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n        else:\n            image = image_path\n        \n        # Detect grid parameters before removal\n        grid_params = detect_grid_parameters(image)\n        \n        # Remove grid if requested\n        if remove_grid:\n            image_processed = remove_ecg_grid(image)\n            if len(image_processed.shape) == 2:\n                image_processed = cv2.cvtColor(image_processed, cv2.COLOR_GRAY2RGB)\n        else:\n            image_processed = image\n        \n        return image_processed, grid_params\n    \n    print('✅ Complete preprocessing pipeline defined')\n    \n    # Create custom Dataset class\n    class ECGImageDataset(torch.utils.data.Dataset):\n        \"\"\"Custom Dataset for ECG images with preprocessing\"\"\"\n        \n        def __init__(self, dataframe, data_path, transform=None, is_test=False):\n            self.dataframe = dataframe\n            self.data_path = data_path\n            self.transform = transform\n            self.is_test = is_test\n        \n        def __len__(self):\n            return len(self.dataframe)\n        \n        def __getitem__(self, idx):\n            row = self.dataframe.iloc[idx]\n            \n            # Get image path\n            img_col = [col for col in self.dataframe.columns if 'image' in col.lower() or 'path' in col.lower()]\n            if img_col:\n                img_path = self.data_path / row[img_col[0]]\n            else:\n                img_path = self.data_path / row.iloc[0]\n            \n            # Load and preprocess image\n            image, grid_params = preprocess_ecg_image(img_path, remove_grid=True)\n            \n            # Apply transforms\n            if self.transform:\n                transformed = self.transform(image=image)\n                image = transformed['image']\n            \n            if self.is_test:\n                return {\n                    'image': image,\n                    'image_id': row.name if hasattr(row, 'name') else idx,\n                    'grid_params': grid_params\n                }\n            else:\n                # For training, include labels if available\n                label_cols = [col for col in self.dataframe.columns if 'label' in col.lower() or 'target' in col.lower()]\n                if label_cols:\n                    labels = torch.tensor([row[col] for col in label_cols], dtype=torch.float32)\n                    return {\n                        'image': image,\n                        'labels': labels,\n                        'image_id': row.name if hasattr(row, 'name') else idx,\n                        'grid_params': grid_params\n                    }\n                else:\n                    return {\n                        'image': image,\n                        'image_id': row.name if hasattr(row, 'name') else idx,\n                        'grid_params': grid_params\n                    }\n    \n    print('✅ Custom ECG Dataset class defined')\n    \n    # Test preprocessing on a sample image\n    print('\\n📊 Testing preprocessing pipeline on sample images...')\n    \n    # Find available image files\n    train_csv = DATA_PATH / 'train.csv'\n    if train_csv.exists():\n        sample_df = pd.read_csv(train_csv)\n        \n        if len(sample_df) > 0:\n            # Get first image path\n            img_cols = [col for col in sample_df.columns if 'image' in col.lower() or 'path' in col.lower()]\n            if img_cols:\n                sample_img_path = DATA_PATH / sample_df[img_cols[0]].iloc[0]\n                \n                if sample_img_path.exists():\n                    print(f'  Processing: {sample_img_path.name}')\n                    \n                    # Load and preprocess\n                    original_img = cv2.imread(str(sample_img_path))\n                    original_img = cv2.cvtColor(original_img, cv2.COLOR_BGR2RGB)\n                    \n                    processed_img, grid_params = preprocess_ecg_image(sample_img_path, remove_grid=True)\n                    \n                    # Visualize\n                    fig, axes = plt.subplots(1, 2, figsize=(15, 5))\n                    axes[0].imshow(original_img)\n                    axes[0].set_title('Original ECG Image')\n                    axes[0].axis('off')\n                    \n                    axes[1].imshow(processed_img, cmap='gray')\n                    axes[1].set_title('Preprocessed (Grid Removed)')\n                    axes[1].axis('off')\n                    \n                    plt.tight_layout()\n                    plt.show()\n                    \n                    print(f'  Grid parameters detected: {grid_params[0]:.4f} mm/px (X), {grid_params[1]:.4f} mm/px (Y)')\n                else:\n                    print(f'  ⚠ Sample image not found: {sample_img_path}')\n            else:\n                print('  ⚠ No image column found in train.csv')\n        else:\n            print('  ⚠ train.csv is empty')\n    else:\n        print('  ℹ train.csv not found, skipping sample preprocessing test')\n    \n    # Create preprocessing configuration\n    preprocessing_config = {\n        'image_size': (512, 512),\n        'normalize_mean': [0.485, 0.456, 0.406],\n        'normalize_std': [0.229, 0.224, 0.225],\n        'remove_grid': True,\n        'sampling_rate': 500,  # Hz for output signal\n        'filter_lowcut': 0.5,  # Hz\n        'filter_highcut': 40,  # Hz\n    }\n    \n    print('\\n📋 Preprocessing configuration:')\n    for key, value in preprocessing_config.items():\n        print(f'  - {key}: {value}')\n    \n    print('\\n✅ Data Preprocessing complete!')\n    \nexcept Exception as e:\n    print(f'✗ Error in Data Preprocessing: {e}')\n    import traceback\n    traceback.print_exc()","metadata":{},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 5. Model Architecture\n\n**Approach:** ECG image digitization using U-Net/CV","metadata":{}},{"cell_type":"code","source":"# Model Architecture\ntry:\n    print('🔧 === MODEL ARCHITECTURE ===\\n')\n\n    import torch\n    import torch.nn as nn\n    import torchvision\n\n    # U-Net with ResNet34 backbone for segmentation (segmentation-models-pytorch style)\n    class UNetResNet34(nn.Module):\n        def __init__(self, pretrained=True):\n            super().__init__()\n            # Encoder: Pretrained ResNet34\n            resnet = torchvision.models.resnet34(pretrained=pretrained)\n            self.input_conv = nn.Sequential(\n                resnet.conv1,\n                resnet.bn1,\n                resnet.relu,\n            )\n            self.maxpool = resnet.maxpool\n            self.encoder1 = resnet.layer1\n            self.encoder2 = resnet.layer2\n            self.encoder3 = resnet.layer3\n            self.encoder4 = resnet.layer4\n\n            # Decoder\n            self.up4 = nn.ConvTranspose2d(512, 256, kernel_size=2, stride=2)\n            self.dec4 = nn.Sequential(\n                nn.Conv2d(512, 256, kernel_size=3, padding=1),\n                nn.BatchNorm2d(256),\n                nn.ReLU(inplace=True),\n                nn.Conv2d(256, 256, kernel_size=3, padding=1),\n                nn.BatchNorm2d(256),\n                nn.ReLU(inplace=True),\n            )\n            self.up3 = nn.ConvTranspose2d(256, 128, kernel_size=2, stride=2)\n            self.dec3 = nn.Sequential(\n                nn.Conv2d(256, 128, kernel_size=3, padding=1),\n                nn.BatchNorm2d(128),\n                nn.ReLU(inplace=True),\n                nn.Conv2d(128, 128, kernel_size=3, padding=1),\n                nn.BatchNorm2d(128),\n                nn.ReLU(inplace=True),\n            )\n            self.up2 = nn.ConvTranspose2d(128, 64, kernel_size=2, stride=2)\n            self.dec2 = nn.Sequential(\n                nn.Conv2d(128, 64, kernel_size=3, padding=1),\n                nn.BatchNorm2d(64),\n                nn.ReLU(inplace=True),\n                nn.Conv2d(64, 64, kernel_size=3, padding=1),\n                nn.BatchNorm2d(64),\n                nn.ReLU(inplace=True),\n            )\n            self.up1 = nn.ConvTranspose2d(64, 64, kernel_size=2, stride=2)\n            self.dec1 = nn.Sequential(\n                nn.Conv2d(67, 64, kernel_size=3, padding=1),\n                nn.BatchNorm2d(64),\n                nn.ReLU(inplace=True),\n                nn.Conv2d(64, 64, kernel_size=3, padding=1),\n                nn.BatchNorm2d(64),\n                nn.ReLU(inplace=True),\n            )\n            self.final_conv = nn.Conv2d(64, 1, kernel_size=1)\n\n        def forward(self, x):\n            # Encoder\n            x1 = self.input_conv(x)   # 64, H/2, W/2\n            x2 = self.maxpool(x1)     # 64, H/4, W/4\n            x3 = self.encoder1(x2)    # 64, H/4, W/4\n            x4 = self.encoder2(x3)    # 128, H/8, W/8\n            x5 = self.encoder3(x4)    # 256, H/16, W/16\n            x6 = self.encoder4(x5)    # 512, H/32, W/32\n\n            # Decoder\n            d5 = self.up4(x6)         # 256, H/16, W/16\n            d5 = torch.cat([d5, x5], dim=1)\n            d5 = self.dec4(d5)\n\n            d4 = self.up3(d5)         # 128, H/8, W/8\n            d4 = torch.cat([d4, x4], dim=1)\n            d4 = self.dec3(d4)\n\n            d3 = self.up2(d4)         # 64, H/4, W/4\n            d3 = torch.cat([d3, x3], dim=1)\n            d3 = self.dec2(d3)\n\n            d2 = self.up1(d3)         # 64, H/2, W/2\n            # x1 is from input_conv, shape [B,64,H/2,W/2], input x is [B,3,H,W]\n            d2 = torch.cat([d2, x1, nn.functional.interpolate(x, size=d2.shape[2:], mode='bilinear', align_corners=False)], dim=1)\n            d2 = self.dec1(d2)\n\n            out = self.final_conv(d2)\n            out = torch.sigmoid(out)\n            return out\n\n    # Loss: Dice + 0.5*BCE\n    class DiceBCELoss(nn.Module):\n        def __init__(self):\n            super().__init__()\n            self.bce = nn.BCELoss()\n\n        def forward(self, preds, targets):\n            preds = preds.view(-1)\n            targets = targets.view(-1)\n            bce_loss = self.bce(preds, targets)\n            smooth = 1e-6\n            intersection = (preds * targets).sum()\n            dice_loss = 1 - (2. * intersection + smooth) / (preds.sum() + targets.sum() + smooth)\n            return dice_loss + 0.5 * bce_loss\n\n    # Instantiate model and loss\n    model = UNetResNet34(pretrained=True).to(device)\n    criterion = DiceBCELoss()\n\n    # Print model summary\n    print(model)\n    print('\\nModel parameters (trainable):', sum(p.numel() for p in model.parameters() if p.requires_grad))\n\n    # Test forward pass with dummy data\n    dummy_input = torch.randn(2, 3, 512, 512).to(device)\n    with torch.no_grad():\n        dummy_output = model(dummy_input)\n    print(f'\\nDummy input shape: {dummy_input.shape}')\n    print(f'Dummy output shape: {dummy_output.shape}')\n\n    # Visualize dummy output\n    import matplotlib.pyplot as plt\n    plt.figure(figsize=(10, 4))\n    for i in range(2):\n        plt.subplot(2, 2, 2*i+1)\n        plt.imshow(dummy_input[i].cpu().permute(1,2,0).numpy() * 0.229 + 0.485)\n        plt.title('Input Image')\n        plt.axis('off')\n        plt.subplot(2, 2, 2*i+2)\n        plt.imshow(dummy_output[i,0].cpu().numpy(), cmap='gray')\n        plt.title('Model Output (Mask)')\n        plt.axis('off')\n    plt.tight_layout()\n    plt.show()\n\n    print('✅ Model Architecture complete!')\n\nexcept Exception as e:\n    print(f'✗ Error in Model Architecture: {e}')\n    import traceback\n    traceback.print_exc()","metadata":{},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 6. Implementation & Next Steps\n\n**Note:** This section provides guidance, not complete code. Actual implementation depends on competition task.","metadata":{}},{"cell_type":"code","source":"print('📋 === IMPLEMENTATION GUIDE ===\\n')\n\nprint('This competition requires IMAGE → SIGNAL extraction (not model training)\\n')\nprint('💡 Implementation Process:')\nprint('1. Load ECG image')\nprint('2. Preprocess: denoise, remove grid, threshold')\nprint('3. Segment: detect ECG trace lines')\nprint('4. Extract: convert pixels to voltage over time')\nprint('5. Post-process: smooth, calibrate with metadata')\nprint('6. Generate submission: format as required')\n\nprint('\\n🔧 Tools to use:')\nprint('  - OpenCV for image processing')\nprint('  - scipy for signal processing')\nprint('  - U-Net/ResNet if using deep learning')\n\nprint('\\n⚠️ TODO:')\nprint('  [ ] Implement image preprocessing pipeline')\nprint('  [ ] Implement segmentation/detection')\nprint('  [ ] Implement signal extraction')\nprint('  [ ] Generate test predictions')\nprint('  [ ] Format submission file')\n\nprint('\\n💡 TIP: Check winning solutions and starter notebooks for this competition!')\n","metadata":{},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 7. Submission\n\n**Generate submission file in competition format**","metadata":{}},{"cell_type":"code","source":"print('📤 === SUBMISSION GENERATION ===\\n')\n\nprint('ECG Competition Submission Format:')\nprint('  Format: Parquet file with columns:')\nprint('    - id: {base_id}_{row_id}_{lead}')\nprint('    - value: Signal value in millivolts')\n\nprint('\\n⚠️ TODO:')\nprint('  1. Process all test images')\nprint('  2. Extract signals for all 12 leads')\nprint('  3. Format: base_id_row_id_lead')\nprint('  4. Save as parquet file')\n\n# Example structure (uncomment and implement):\n# results = []\n# for test_id in test_df['id']:\n#     for lead in ['I', 'II', 'III', 'aVR', 'aVL', 'aVF', 'V1', 'V2', 'V3', 'V4', 'V5', 'V6']:\n#         signal = extract_signal_from_image(test_id, lead)  # YOUR IMPLEMENTATION\n#         for row_id, value in enumerate(signal):\n#             results.append({'id': f'{test_id}_{row_id}_{lead}', 'value': value})\n#\n# submission = pd.DataFrame(results)\n# submission.to_parquet('submission.parquet', index=False)\n# print('✅ Submission created!')\n","metadata":{},"outputs":[],"execution_count":null}]}