{"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":31234,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"## uploaing modules","metadata":{}},{"cell_type":"code","source":"import os\nimport cv2\nimport numpy as np\nimport pandas as pd\nfrom tqdm import tqdm\nfrom scipy.signal import savgol_filter","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-09T00:03:02.242503Z","iopub.execute_input":"2026-01-09T00:03:02.242842Z","iopub.status.idle":"2026-01-09T00:03:03.252774Z","shell.execute_reply.started":"2026-01-09T00:03:02.242816Z","shell.execute_reply":"2026-01-09T00:03:03.251711Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"DATA_ROOT = \"/kaggle/input/physionet-ecg-image-digitization\"\nTEST_DIR = f\"{DATA_ROOT}/test\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-09T00:03:07.300959Z","iopub.execute_input":"2026-01-09T00:03:07.301468Z","iopub.status.idle":"2026-01-09T00:03:07.306534Z","shell.execute_reply.started":"2026-01-09T00:03:07.301441Z","shell.execute_reply":"2026-01-09T00:03:07.305510Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"LEADS = [\n    \"I\",\"II\",\"III\",\"aVR\",\"aVL\",\"aVF\",\n    \"V1\",\"V2\",\"V3\",\"V4\",\"V5\",\"V6\"\n]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-09T00:03:18.391606Z","iopub.execute_input":"2026-01-09T00:03:18.391971Z","iopub.status.idle":"2026-01-09T00:03:18.397201Z","shell.execute_reply.started":"2026-01-09T00:03:18.391944Z","shell.execute_reply":"2026-01-09T00:03:18.396012Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## processing images","metadata":{}},{"cell_type":"code","source":"def preprocess_image(img):\n    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)\n\n    # Remove grid (same as before)\n    vertical_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1, 25))\n    horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (25, 1))\n\n    no_vertical = cv2.morphologyEx(gray, cv2.MORPH_OPEN, vertical_kernel)\n    no_horizontal = cv2.morphologyEx(gray, cv2.MORPH_OPEN, horizontal_kernel)\n\n    grid_removed = cv2.subtract(gray, no_vertical)\n    grid_removed = cv2.subtract(grid_removed, no_horizontal)\n\n    # Binary\n    bw = cv2.adaptiveThreshold(\n        grid_removed, 255,\n        cv2.ADAPTIVE_THRESH_GAUSSIAN_C,\n        cv2.THRESH_BINARY_INV,\n        51, 5\n    )\n\n   \n    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3,3))\n    bw = cv2.erode(bw, kernel, iterations=1)\n\n    return bw","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-09T00:03:26.394627Z","iopub.execute_input":"2026-01-09T00:03:26.395004Z","iopub.status.idle":"2026-01-09T00:03:26.403706Z","shell.execute_reply.started":"2026-01-09T00:03:26.394972Z","shell.execute_reply":"2026-01-09T00:03:26.402599Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def extract_waveform(binary_img):\n    h, w = binary_img.shape\n\n    # Find connected components\n    num_labels, labels, stats, _ = cv2.connectedComponentsWithStats(\n        binary_img, connectivity=8\n    )\n\n    # Ignore background, keep largest component\n    if num_labels <= 1:\n        return np.zeros(w, dtype=np.float32)\n\n    largest = 1 + np.argmax(stats[1:, cv2.CC_STAT_AREA])\n    mask = (labels == largest).astype(np.uint8) * 255\n\n    signal = np.zeros(w, dtype=np.float32)\n\n    for x in range(w):\n        ys = np.where(mask[:, x] > 0)[0]\n        if len(ys) > 0:\n            # center of ridge\n            signal[x] = np.mean(ys)\n        else:\n            signal[x] = np.nan\n\n    # Interpolate gaps\n    nans = np.isnan(signal)\n    if np.any(~nans):\n        signal[nans] = np.interp(\n            np.flatnonzero(nans),\n            np.flatnonzero(~nans),\n            signal[~nans]\n        )\n    else:\n        signal[:] = 0.0\n\n    # Flip y-axis\n    signal = h - signal\n\n    # Normalize (critical for SNR)\n    signal -= np.mean(signal)\n    std = np.std(signal)\n    if std > 1e-6:\n        signal /= std\n\n    # Downsample → reduce jitter\n    target_len = max(500, len(signal) // 4)\n    signal = np.interp(\n        np.linspace(0, len(signal)-1, target_len),\n        np.arange(len(signal)),\n        signal\n    )\n\n    # Smooth AFTER downsampling\n    if len(signal) > 101:\n        signal = savgol_filter(signal, 101, 3)\n\n    return signal","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-09T00:03:34.694738Z","iopub.execute_input":"2026-01-09T00:03:34.695081Z","iopub.status.idle":"2026-01-09T00:03:34.705269Z","shell.execute_reply.started":"2026-01-09T00:03:34.695056Z","shell.execute_reply":"2026-01-09T00:03:34.704296Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def split_into_leads(img):\n    h, w, _ = img.shape\n    lead_h = h // 12\n    leads = []\n\n    for i in range(12):\n        y1 = int(i * lead_h + 0.1 * lead_h)\n        y2 = int((i + 1) * lead_h - 0.1 * lead_h)\n        leads.append(img[y1:y2, :])\n\n    return leads","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-09T00:03:40.125891Z","iopub.execute_input":"2026-01-09T00:03:40.126246Z","iopub.status.idle":"2026-01-09T00:03:40.132266Z","shell.execute_reply.started":"2026-01-09T00:03:40.126220Z","shell.execute_reply":"2026-01-09T00:03:40.131273Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def process_ecg_image(img):\n    lead_imgs = split_into_leads(img)\n    signals = []\n\n    for lead_img in lead_imgs:\n        bw = preprocess_image(lead_img)\n        sig = extract_waveform(bw)\n        signals.append(sig)\n\n    return signals","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-09T00:03:44.884095Z","iopub.execute_input":"2026-01-09T00:03:44.884449Z","iopub.status.idle":"2026-01-09T00:03:44.890099Z","shell.execute_reply.started":"2026-01-09T00:03:44.884425Z","shell.execute_reply":"2026-01-09T00:03:44.889003Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"sample_sub = pd.read_parquet(\n    f\"{DATA_ROOT}/sample_submission.parquet\"\n)\n\nids = sample_sub[\"id\"].str.split(\"_\", expand=True)\nsample_sub[\"record\"] = ids[0]\nsample_sub[\"time\"] = ids[1].astype(int)\nsample_sub[\"lead\"] = ids[2]\n\nprint(\"Expected rows:\", len(sample_sub))\nsample_sub.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-09T00:03:56.482727Z","iopub.execute_input":"2026-01-09T00:03:56.483413Z","iopub.status.idle":"2026-01-09T00:03:56.977757Z","shell.execute_reply.started":"2026-01-09T00:03:56.483382Z","shell.execute_reply":"2026-01-09T00:03:56.976733Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"record_to_image = {}\n\nfor fname in os.listdir(TEST_DIR):\n    rid = os.path.splitext(fname)[0]\n    record_to_image[rid] = os.path.join(TEST_DIR, fname)\n\nprint(\"Test images found:\", len(record_to_image))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-09T00:04:02.601243Z","iopub.execute_input":"2026-01-09T00:04:02.601919Z","iopub.status.idle":"2026-01-09T00:04:02.608597Z","shell.execute_reply.started":"2026-01-09T00:04:02.601862Z","shell.execute_reply":"2026-01-09T00:04:02.607450Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"rows = []\nlead_map = {name: i for i, name in enumerate(LEADS)}\n\nfor record_id, group in tqdm(sample_sub.groupby(\"record\")):\n    if record_id not in record_to_image:\n        # Defensive fallback\n        for _, row in group.iterrows():\n            rows.append({\"id\": row[\"id\"], \"value\": 0.0})\n        continue\n\n    img = cv2.imread(record_to_image[record_id])\n\n    if img is None:\n        # If image fails to load, output zeros\n        for _, row in group.iterrows():\n            rows.append({\"id\": row[\"id\"], \"value\": 0.0})\n        continue\n\n    signals = process_ecg_image(img)\n\n    for _, row in group.iterrows():\n        t = row[\"time\"]\n        lead_idx = lead_map[row[\"lead\"]]\n        sig = signals[lead_idx]\n\n        val = sig[t] if t < len(sig) else sig[-1]\n\n        rows.append({\n            \"id\": row[\"id\"],\n            \"value\": float(val)\n        })","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-09T00:04:09.309712Z","iopub.execute_input":"2026-01-09T00:04:09.310663Z","iopub.status.idle":"2026-01-09T00:04:14.236570Z","shell.execute_reply.started":"2026-01-09T00:04:09.310630Z","shell.execute_reply":"2026-01-09T00:04:14.235735Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submission = pd.DataFrame(rows)\n\n# Remove NaNs / infs (prevents scoring error)\nsubmission[\"value\"] = submission[\"value\"].replace([np.inf, -np.inf], 0.0)\nsubmission[\"value\"] = submission[\"value\"].fillna(0.0)\n\nsubmission.to_csv(\"submission.csv\", index=False)\n\nprint(\"Submission rows:\", len(submission))\nprint(\"Expected rows:\", len(sample_sub))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-09T00:04:16.098009Z","iopub.execute_input":"2026-01-09T00:04:16.098366Z","iopub.status.idle":"2026-01-09T00:04:16.298075Z","shell.execute_reply.started":"2026-01-09T00:04:16.098341Z","shell.execute_reply":"2026-01-09T00:04:16.297067Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"missing = set(sample_sub[\"id\"]) - set(submission[\"id\"])\nextra = set(submission[\"id\"]) - set(sample_sub[\"id\"])\n\nprint(\"Missing IDs:\", len(missing))\nprint(\"Extra IDs:\", len(extra))\nprint(\"NaNs:\", submission[\"value\"].isna().sum())\nprint(\"Infs:\", np.isinf(submission[\"value\"]).sum())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-09T00:04:23.996827Z","iopub.execute_input":"2026-01-09T00:04:23.997154Z","iopub.status.idle":"2026-01-09T00:04:24.058051Z","shell.execute_reply.started":"2026-01-09T00:04:23.997131Z","shell.execute_reply":"2026-01-09T00:04:24.056925Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## upvote","metadata":{}},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}