{"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":31154,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\n#for dirname, _, filenames in os.walk('/kaggle/input'):\n#    for filename in filenames:\n#        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-10-26T06:39:59.312384Z","iopub.execute_input":"2025-10-26T06:39:59.312699Z","iopub.status.idle":"2025-10-26T06:39:59.318720Z","shell.execute_reply.started":"2025-10-26T06:39:59.312676Z","shell.execute_reply":"2025-10-26T06:39:59.317497Z"},"_kg_hide-input":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Packages","metadata":{}},{"cell_type":"code","source":"%time\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport warnings\nwarnings.filterwarnings('ignore')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-26T06:39:59.320484Z","iopub.execute_input":"2025-10-26T06:39:59.320907Z","iopub.status.idle":"2025-10-26T06:39:59.342468Z","shell.execute_reply.started":"2025-10-26T06:39:59.320875Z","shell.execute_reply":"2025-10-26T06:39:59.340716Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Load Data","metadata":{}},{"cell_type":"code","source":"%%time\ntrain = pd.read_csv('/kaggle/input/physionet-ecg-image-digitization/train.csv')\ntest = pd.read_csv('/kaggle/input/physionet-ecg-image-digitization/test.csv')\nsubmission = pd.read_parquet(\"/kaggle/input/physionet-ecg-image-digitization/sample_submission.parquet\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-26T06:39:59.343630Z","iopub.execute_input":"2025-10-26T06:39:59.343990Z","iopub.status.idle":"2025-10-26T06:39:59.406553Z","shell.execute_reply.started":"2025-10-26T06:39:59.343960Z","shell.execute_reply":"2025-10-26T06:39:59.405398Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Load test train","metadata":{}},{"cell_type":"code","source":"idx = 0\nprint(train.id[idx])\n\nTRAIN_DIR = '/kaggle/input/physionet-ecg-image-digitization/train/'\nname = str(train.id[idx])\ndf_with_id0 = TRAIN_DIR + name + '/' + name + '.csv'\n\ndf = pd.read_csv(df_with_id0)\ndf.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-26T06:39:59.408521Z","iopub.execute_input":"2025-10-26T06:39:59.408925Z","iopub.status.idle":"2025-10-26T06:39:59.434398Z","shell.execute_reply.started":"2025-10-26T06:39:59.408901Z","shell.execute_reply":"2025-10-26T06:39:59.433340Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# EDA","metadata":{}},{"cell_type":"code","source":"%%time\ntrain_metadata = train[train['id'] == 7663343]\n\n# Check if signal length matches recording duration\nfs = train_metadata['fs'].values[0]\nsig_len = train_metadata['sig_len'].values[0]\nduration = sig_len / fs\n\nprint(f\"Signal duration: {duration} seconds\")\nprint(f\"Sampling frequency: {fs} Hz\")\nprint(f\"Number of samples: {sig_len}\")\n\n# Compare with what we see on the images\ndef analyze_ecg_image(image_path):\n    \"\"\"Analyze ECG image to determine characteristics\"\"\"\n    img = plt.imread(image_path)\n    print(f\"\\nAnalysis of {os.path.basename(image_path)}:\")\n    print(f\"Image size: {img.shape}\")\n    \n    # Can add analysis of grid, time markers, etc.\n    return img\n\n# Analyze the first image\nanalyze_ecg_image(TRAIN_DIR + '7663343/7663343-0001.png')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-26T06:39:59.435428Z","iopub.execute_input":"2025-10-26T06:39:59.435797Z","iopub.status.idle":"2025-10-26T06:39:59.576919Z","shell.execute_reply.started":"2025-10-26T06:39:59.435692Z","shell.execute_reply":"2025-10-26T06:39:59.575818Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Functions","metadata":{}},{"cell_type":"code","source":"%%time\ndef get_image_type(filename):\n    \"\"\"Determine image type based on filename\"\"\"\n    type_mapping = {\n        '0001': 'original_color',\n        '0003': 'printed_scanned_color', \n        '0004': 'printed_scanned_bw',\n        '0005': 'mobile_photo_color',\n        '0006': 'mobile_photo_screen',\n        '0009': 'stained_soaked',\n        '0010': 'extensive_damage',\n        '0011': 'mold_color',\n        '0012': 'mold_bw'\n    }\n    \n    image_id = filename.split('-')[1].split('.')[0]\n    return type_mapping.get(image_id, 'unknown')\n\ndef has_artifacts(filename):\n    \"\"\"Determine if the image has artifacts\"\"\"\n    artifact_types = ['0009', '0010', '0011', '0012']\n    image_id = filename.split('-')[1].split('.')[0]\n    return image_id in artifact_types","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-26T06:39:59.577886Z","iopub.execute_input":"2025-10-26T06:39:59.578166Z","iopub.status.idle":"2025-10-26T06:39:59.585768Z","shell.execute_reply.started":"2025-10-26T06:39:59.578136Z","shell.execute_reply":"2025-10-26T06:39:59.584635Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## ECG Signal","metadata":{}},{"cell_type":"code","source":"# Compare the original signal with different image versions\nfig, axes = plt.subplots(3, 3, figsize=(18, 12))\n\n# Plot original signal\ntime = np.arange(len(df['II'])) / train_metadata['fs'].values[0]\naxes[0,0].plot(time, df['II'], 'b-', linewidth=0.8)\naxes[0,0].set_title('Original ECG Signal (Lead II)')\naxes[0,0].set_xlabel('Time (s)')\naxes[0,0].set_ylabel('mV')\naxes[0,0].grid(True)\n\n# Display different image versions\nimage_files = [f for f in os.listdir(TRAIN_DIR + '7663343/') if f.endswith('.png')]\nfor i, img_file in enumerate(image_files[:8]):\n    row = (i + 1) // 3\n    col = (i + 1) % 3\n    \n    img_path = TRAIN_DIR + '7663343/' + img_file\n    img = plt.imread(img_path)\n    \n    axes[row, col].imshow(img)\n    axes[row, col].set_title(f'{get_image_type(img_file)}\\n{img_file}')\n    axes[row, col].axis('off')\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-26T06:39:59.588045Z","iopub.execute_input":"2025-10-26T06:39:59.588353Z","iopub.status.idle":"2025-10-26T06:40:15.477434Z","shell.execute_reply.started":"2025-10-26T06:39:59.588325Z","shell.execute_reply":"2025-10-26T06:40:15.476260Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Prediction and  Submision","metadata":{}},{"cell_type":"code","source":"%%time\nimport numpy as np\nimport pandas as pd\nimport os\nfrom scipy.signal import butter, filtfilt\nfrom tqdm import tqdm\n\n# ============================================================\n# Paths\n# ============================================================\nBASE_DIR = \"/kaggle/input/physionet-ecg-image-digitization\"\nTRAIN_DIR = os.path.join(BASE_DIR, \"train\")\nTEST_PATH = os.path.join(BASE_DIR, \"test.csv\")\nSAMPLE_SUB = os.path.join(BASE_DIR, \"sample_submission.parquet\")\n\n# ============================================================\n# Load data\n# ============================================================\ntrain = pd.read_csv(os.path.join(BASE_DIR, \"train.csv\"))\ntest = pd.read_csv(TEST_PATH)\nsubmission = pd.read_parquet(SAMPLE_SUB)\n\nprint(f\"✅ Train shape: {train.shape}\")\nprint(f\"✅ Test shape: {test.shape}\")\n\n# ============================================================\n# Create averaged ECG templates per lead\n# ============================================================\nleads = ['I','II','III','aVR','aVL','aVF','V1','V2','V3','V4','V5','V6']\ntemplate_len = 1000\nlead_templates = {}\n\nfor lead in leads:\n    signals = []\n    for _, row in train.sample(n=min(300, len(train)), random_state=42).iterrows():\n        csv_path = os.path.join(TRAIN_DIR, str(row[\"id\"]), f\"{row['id']}.csv\")\n        if not os.path.exists(csv_path):\n            continue\n        try:\n            df = pd.read_csv(csv_path)\n            if lead not in df.columns:\n                continue\n            s = df[lead].dropna().values.astype(np.float32)\n            if len(s) < 100: continue\n\n            s_norm = (s - s.mean()) / (s.std() + 1e-8)\n            s_resamp = np.interp(np.linspace(0, 1, template_len),\n                                 np.linspace(0, 1, len(s_norm)), s_norm)\n            signals.append(s_resamp)\n        except:\n            continue\n\n    if signals:\n        lead_templates[lead] = np.mean(signals, axis=0)\n    else:\n        t = np.linspace(0, 1, template_len)\n        lead_templates[lead] = np.sin(2 * np.pi * t)\n\nprint(f\"✅ Created templates for {len(lead_templates)} leads\")\n\n# ============================================================\n# Low-pass filter helper\n# ============================================================\ndef lowpass_filter(signal, fs=500, cutoff=15.0):\n    nyq = 0.5 * fs\n    normal_cutoff = min(cutoff / nyq, 0.99)\n    b, a = butter(2, normal_cutoff, btype='low')\n    return filtfilt(b, a, signal)\n\n# ============================================================\n# Predict test signals using templates + jitter ensemble\n# ============================================================\npredictions = {}\nmin_val, max_val = 0.0, 0.07\n\nfor _, row in tqdm(test.iterrows(), total=len(test)):\n    base_id, lead, n_rows = row[\"id\"], row[\"lead\"], row[\"number_of_rows\"]\n    fs = row.get(\"fs\", 500)\n    template = lead_templates.get(lead, lead_templates[\"II\"]).copy()\n\n    # Resize to match target length\n    signal = np.interp(np.linspace(0, 1, n_rows),\n                       np.linspace(0, 1, len(template)), template)\n\n    # Filter + jitter averaging\n    ensemble = []\n    for jitter in [0.98, 1.0, 1.02]:\n        s_jitter = np.interp(np.linspace(0, 1, n_rows),\n                             np.linspace(0, 1, len(template)),\n                             np.roll(template, int(len(template)*((jitter-1)/2))))\n        s_filtered = lowpass_filter(s_jitter, fs)\n        ensemble.append(s_filtered)\n    signal = np.mean(ensemble, axis=0)\n\n    # Normalize and scale\n    s_min, s_max = signal.min(), signal.max()\n    if s_max - s_min < 1e-8:\n        signal = np.full(n_rows, (min_val + max_val) / 2)\n    else:\n        signal = (signal - s_min) / (s_max - s_min)\n        signal = min_val + signal * (max_val - min_val)\n\n    predictions[(base_id, lead)] = signal.astype(np.float32)\n\n# ============================================================\n# Build submission\n# ============================================================\nsubmission_data = []\nfor _, row in test.iterrows():\n    base_id, lead, n_rows = row[\"id\"], row[\"lead\"], row[\"number_of_rows\"]\n    signal = predictions[(base_id, lead)]\n    for i in range(n_rows):\n        submission_data.append({\n            \"id\": f\"{base_id}_{i}_{lead}\",\n            \"value\": float(signal[i])\n        })\n\nsubmission_df = pd.DataFrame(submission_data)\nsubmission_df.to_csv(\"/kaggle/working/submission.csv\", index=False)\n\nprint(\"✅ Saved submission:\", submission_df.shape)\nsubmission_df.head(20)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-26T06:40:15.478545Z","iopub.execute_input":"2025-10-26T06:40:15.478902Z","iopub.status.idle":"2025-10-26T06:40:38.230723Z","shell.execute_reply.started":"2025-10-26T06:40:15.478875Z","shell.execute_reply":"2025-10-26T06:40:38.229832Z"},"_kg_hide-input":true},"outputs":[],"execution_count":null}]}