{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","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":31234,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# ECG_Digitization_v4_Post-processing Pipeline (Centerline → Waveform)\n\nThis notebook focuses on post-processing improvements\nfor the v4 hybrid centerline extraction method.\n\nThe aim is to stabilize the waveform and improve\nsubmission score in the PhysioNet ECG digitization challenge.\n","metadata":{}},{"cell_type":"markdown","source":"## Overview\n\nThis notebook implements the **post-processing stage** for ECG image digitization,\nusing a **v4 centerline extraction pipeline**.\n\nFocus:\n- Convert v4 centerline output into a clean 1D waveform\n- Apply smoothing / interpolation\n- Prepare signals for downstream evaluation and submission\n\nThis notebook is intended as:\n- A clean, reproducible **post-processing reference**\n- A foundation for submission-ready waveform generation\n","metadata":{}},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"markdown","source":"## Sample Output (v4: hybrid + smoothing)\n\nBelow is an example of the extracted ECG waveform from a single input image\nafter centerline extraction, hybrid post-processing, and smoothing.\n\n- Input: PhysioNet ECG image\n- Output: 1D waveform (normalized amplitude)\n","metadata":{}},{"cell_type":"code","source":"plt.figure(figsize=(14,4))\nplt.plot(y_norm, color='red', linewidth=1)\nplt.title(\"v4: hybrid + smoothing (normalized)\")\nplt.xlabel(\"x (pixel)\")\nplt.ylabel(\"normalized amplitude\")\nplt.grid(True)\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-29T14:44:58.098892Z","iopub.execute_input":"2025-12-29T14:44:58.099957Z","iopub.status.idle":"2025-12-29T14:44:58.256177Z","shell.execute_reply.started":"2025-12-29T14:44:58.099915Z","shell.execute_reply":"2025-12-29T14:44:58.255304Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"###  1. Imports & Environment","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport os, cv2, numpy as np, pandas as pd, matplotlib.pyplot as plt\n\nfrom scipy.ndimage import median_filter, gaussian_filter1d\nfrom scipy.signal import savgol_filter\n\nplt.rcParams[\"figure.figsize\"] = (12,4)\nplt.rcParams[\"axes.grid\"] = True\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-29T14:43:28.238883Z","iopub.execute_input":"2025-12-29T14:43:28.239380Z","iopub.status.idle":"2025-12-29T14:43:29.759231Z","shell.execute_reply.started":"2025-12-29T14:43:28.239345Z","shell.execute_reply":"2025-12-29T14:43:29.758374Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 2. Load ECG Mask / Preprocessed Image","metadata":{}},{"cell_type":"markdown","source":"## Data\nThis notebook assumes that ECG waveform masks have already been extracted\nfrom the original images (e.g., via thresholding or segmentation).\n\nThe comparison focuses on **post-mask centerline extraction**.\n","metadata":{}},{"cell_type":"code","source":"#\n# Kaggle 上のデータ構造に合わせる\nBASE_PATH = \"//kaggle/input/physionet-ecg-image-digitization/\"\nTRAIN_DIR = f\"{BASE_PATH}/train\"\nTEST_DIR = f\"{BASE_PATH}/test\"\n\n# サンプルIDを選択\nrecord_id = \"1006427285\"\nimg_number = 1\nimg_name = f\"{record_id}-{str(img_number).zfill(4)}.png\"\nimg_path = f\"{TRAIN_DIR}/{record_id}/{img_name}\"\nprint(\"Image path:\", img_path)\n\nprint(\"Image path2:\",\"/kaggle/input/physionet-ecg-image-digitization/train/1006427285/1006427285-0001.png\")\n\n# Read Image\nimg = cv2.imread(img_path)\nassert img is not None, \"画像が読み込めていません\"\n##print(\"img:\",np.array(img))\ngray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)\n\nplt.figure(figsize=(12,6))\nplt.imshow(gray, cmap='gray')\nplt.title(f\"Sample ECG: {img_name}\")\nplt.axis('off')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-29T14:43:36.328975Z","iopub.execute_input":"2025-12-29T14:43:36.329919Z","iopub.status.idle":"2025-12-29T14:43:37.444415Z","shell.execute_reply.started":"2025-12-29T14:43:36.329884Z","shell.execute_reply":"2025-12-29T14:43:37.443433Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)\nmask_red = cv2.inRange(img_hsv, (0,50,50), (10,255,255)) | cv2.inRange(img_hsv, (160,50,50), (180,255,255))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-29T14:43:41.939443Z","iopub.execute_input":"2025-12-29T14:43:41.939816Z","iopub.status.idle":"2025-12-29T14:43:41.969166Z","shell.execute_reply.started":"2025-12-29T14:43:41.939788Z","shell.execute_reply":"2025-12-29T14:43:41.968049Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 3. Shared Preprocessing\nRed waveform extraction using HSV thresholding.\n","metadata":{}},{"cell_type":"code","source":"img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)\n\nmask_red = (\n    cv2.inRange(img_hsv, (0,50,50), (10,255,255)) |\n    cv2.inRange(img_hsv, (160,50,50), (180,255,255))\n)\n\nplt.imshow(mask_red, cmap=\"gray\")\nplt.title(\"Red Mask\")\nplt.axis(\"off\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-29T14:43:45.027861Z","iopub.execute_input":"2025-12-29T14:43:45.028826Z","iopub.status.idle":"2025-12-29T14:43:46.157323Z","shell.execute_reply.started":"2025-12-29T14:43:45.028791Z","shell.execute_reply":"2025-12-29T14:43:46.156316Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 4. Centerline Extraction Functions\nEach version differs mainly in how the centerline is estimated and smoothed.\n","metadata":{}},{"cell_type":"code","source":"\n\ndef extract_v3(mask):\n    # cog + peak hybrid\n    H, W = mask.shape\n    y_cog = np.full(W, np.nan)\n    y_peak = np.full(W, np.nan)\n\n    for x in range(W):\n        ys = np.where(mask[:,x]>0)[0]\n        if len(ys)>0:\n            y_cog[x] = ys.mean()\n            y_peak[x] = np.median(ys)\n\n    y = np.nanmean(np.vstack([y_cog, y_peak]), axis=0)\n    return pd.Series(y).interpolate().values\n\n\ndef extract_v4(mask):\n    # v4.1 style: hybrid + median + gaussian + savgol\n    y = extract_v3(mask)\n    y = median_filter(y, size=5)\n    y = gaussian_filter1d(y, sigma=4)\n    y = savgol_filter(y, 21, 3)\n    return y\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-29T14:44:15.544426Z","iopub.execute_input":"2025-12-29T14:44:15.545333Z","iopub.status.idle":"2025-12-29T14:44:15.552026Z","shell.execute_reply.started":"2025-12-29T14:44:15.545299Z","shell.execute_reply":"2025-12-29T14:44:15.551269Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 5. v4 Centerlines\n\nWe compare four versions:\n\n- **v4**: Robust hybrid + denoising + smoothing\n\n","metadata":{}},{"cell_type":"code","source":"\ny = extract_v4(mask_red)\n\nfig, ax = plt.subplots(1,1, sharex=True, figsize=(12,5))\n\nax.plot(y4, color=\"red\")\nax.set_title(\"v4: hybrid + smoothing\")\nax.invert_yaxis()\n\nplt.xlabel(\"x (pixel)\")\nplt.tight_layout()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-29T14:44:40.093542Z","iopub.execute_input":"2025-12-29T14:44:40.093852Z","iopub.status.idle":"2025-12-29T14:44:40.371563Z","shell.execute_reply.started":"2025-12-29T14:44:40.093829Z","shell.execute_reply":"2025-12-29T14:44:40.370602Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# y: extracted vertical pixel positions (numpy array)\ny_pixel = y.copy()\n\n# normalize to [0, 1]\ny_norm = (y_pixel - y_pixel.min()) / (y_pixel.max() - y_pixel.min())\n\n# invert y-axis (pixel origin is top-left)\ny_inv = -(y_pixel - y_pixel.mean())\n\n# normalize\ny_norm = (y_inv - y_inv.min()) / (y_inv.max() - y_inv.min())\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-29T14:44:43.454528Z","iopub.execute_input":"2025-12-29T14:44:43.454847Z","iopub.status.idle":"2025-12-29T14:44:43.461841Z","shell.execute_reply.started":"2025-12-29T14:44:43.454822Z","shell.execute_reply":"2025-12-29T14:44:43.460684Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.figure(figsize=(14,4))\nplt.plot(y_norm, color='red', linewidth=1)\nplt.title(\"v4: hybrid + smoothing (normalized)\")\nplt.xlabel(\"x (pixel)\")\nplt.ylabel(\"normalized amplitude\")\nplt.grid(True)\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-29T14:44:49.022239Z","iopub.execute_input":"2025-12-29T14:44:49.023124Z","iopub.status.idle":"2025-12-29T14:44:49.178509Z","shell.execute_reply.started":"2025-12-29T14:44:49.023090Z","shell.execute_reply":"2025-12-29T14:44:49.177718Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}