{"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":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport cv2\nimport numpy as np\nimport pandas as pd\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\nimport torchvision.models as models\nfrom sklearn.model_selection import train_test_split\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-09T23:19:43.431026Z","iopub.execute_input":"2026-01-09T23:19:43.432381Z","iopub.status.idle":"2026-01-09T23:19:43.441630Z","shell.execute_reply.started":"2026-01-09T23:19:43.432231Z","shell.execute_reply":"2026-01-09T23:19:43.440251Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"TRAIN_CSV = \"/kaggle/input/physionet-ecg-image-digitization/train.csv\"\nTRAIN_DIR = \"/kaggle/input/physionet-ecg-image-digitization/train\"\nTEST_CSV  = \"/kaggle/input/physionet-ecg-image-digitization/test.csv\"\nTEST_DIR  = \"/kaggle/input/physionet-ecg-image-digitization/test\"\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-09T23:19:43.443805Z","iopub.execute_input":"2026-01-09T23:19:43.444248Z","iopub.status.idle":"2026-01-09T23:19:43.462026Z","shell.execute_reply.started":"2026-01-09T23:19:43.444212Z","shell.execute_reply":"2026-01-09T23:19:43.461058Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df = pd.read_csv(TRAIN_CSV)\ndf = df[df[\"id\"].notnull()].reset_index(drop=True)\n\nprint(\"Total train samples:\", len(df))\ndf.head()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-09T23:19:43.463748Z","iopub.execute_input":"2026-01-09T23:19:43.464164Z","iopub.status.idle":"2026-01-09T23:19:43.499478Z","shell.execute_reply.started":"2026-01-09T23:19:43.464122Z","shell.execute_reply":"2026-01-09T23:19:43.498374Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def preprocess_image(img_path, target_width, target_height=256):\n    img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE)\n    if img is None:\n        raise ValueError(f\"Could not read image: {img_path}\")\n\n    img = cv2.resize(img, (target_width, target_height),\n                     interpolation=cv2.INTER_AREA)\n\n    img = img.astype(np.float32) / 255.0\n    img = np.expand_dims(img, axis=0)  # (1, H, W)\n    return img\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-09T23:19:43.500877Z","iopub.execute_input":"2026-01-09T23:19:43.501343Z","iopub.status.idle":"2026-01-09T23:19:43.507729Z","shell.execute_reply.started":"2026-01-09T23:19:43.501236Z","shell.execute_reply":"2026-01-09T23:19:43.506869Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def preprocess_waveform(csv_path, lead=\"II\"):\n    sig = pd.read_csv(csv_path)\n    y = sig[lead].values.astype(np.float32)\n\n    y = y - y.mean()\n    y = y / (y.std() + 1e-6)\n    return y\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-09T23:19:43.509816Z","iopub.execute_input":"2026-01-09T23:19:43.510099Z","iopub.status.idle":"2026-01-09T23:19:43.526523Z","shell.execute_reply.started":"2026-01-09T23:19:43.510068Z","shell.execute_reply":"2026-01-09T23:19:43.525597Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class ECGImageDataset(Dataset):\n    def __init__(self, df, train_dir):\n        self.df = df\n        self.train_dir = train_dir\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        ecg_id = str(row[\"id\"])\n\n        ecg_path = os.path.join(self.train_dir, ecg_id)\n\n        y = preprocess_waveform(\n            os.path.join(ecg_path, f\"{ecg_id}.csv\"),\n            lead=\"II\"\n        )\n\n        T = len(y)\n\n        x = preprocess_image(\n            os.path.join(ecg_path, f\"{ecg_id}-0003.png\"),\n            target_width=T\n        )\n\n        return (\n            torch.tensor(x, dtype=torch.float32),\n            torch.tensor(y, dtype=torch.float32)\n        )\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-09T23:19:43.528007Z","iopub.execute_input":"2026-01-09T23:19:43.528444Z","iopub.status.idle":"2026-01-09T23:19:43.551297Z","shell.execute_reply.started":"2026-01-09T23:19:43.528400Z","shell.execute_reply":"2026-01-09T23:19:43.550364Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df, val_df = train_test_split(df, test_size=0.2, random_state=42)\n\ntrain_ds = ECGImageDataset(train_df, TRAIN_DIR)\nval_ds   = ECGImageDataset(val_df, TRAIN_DIR)\n\ntrain_loader = DataLoader(train_ds, batch_size=1, shuffle=True)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-09T23:19:43.552808Z","iopub.execute_input":"2026-01-09T23:19:43.553194Z","iopub.status.idle":"2026-01-09T23:19:43.575184Z","shell.execute_reply.started":"2026-01-09T23:19:43.553154Z","shell.execute_reply":"2026-01-09T23:19:43.573959Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class ECGResNet(nn.Module):\n    def __init__(self):\n        super().__init__()\n\n        self.backbone = models.resnet34(weights=None)\n        self.backbone.conv1 = nn.Conv2d(\n            1, 64, kernel_size=7, stride=2, padding=3, bias=False\n        )\n        self.backbone.fc = nn.Identity()\n\n        self.pool = nn.AdaptiveAvgPool2d((1, None))\n        self.head = nn.Conv1d(512, 1, kernel_size=1)\n\n    def forward(self, x):\n        f = self.backbone.conv1(x)\n        f = self.backbone.bn1(f)\n        f = self.backbone.relu(f)\n        f = self.backbone.maxpool(f)\n\n        f = self.backbone.layer1(f)\n        f = self.backbone.layer2(f)\n        f = self.backbone.layer3(f)\n        f = self.backbone.layer4(f)\n\n        f = self.pool(f).squeeze(2)     # (B, C, W)\n        y = self.head(f).squeeze(1)     # (B, W)\n        return y\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-09T23:19:43.577729Z","iopub.execute_input":"2026-01-09T23:19:43.578016Z","iopub.status.idle":"2026-01-09T23:19:43.595044Z","shell.execute_reply.started":"2026-01-09T23:19:43.577990Z","shell.execute_reply":"2026-01-09T23:19:43.593780Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nmodel = ECGResNet().to(device)\n\noptimizer = torch.optim.Adam(model.parameters(), lr=1e-4)\ncriterion = nn.MSELoss()\n\nmodel.train()\nfor x, y in train_loader:\n    x, y = x.to(device), y.to(device)\n\n    optimizer.zero_grad()\n    y_hat = model(x)\n\n    if y_hat.shape[1] != y.shape[1]:\n        y_hat = nn.functional.interpolate(\n            y_hat.unsqueeze(1),\n            size=y.shape[1],\n            mode=\"linear\",\n            align_corners=False\n        ).squeeze(1)\n\n    loss = criterion(y_hat, y)\n    loss.backward()\n    optimizer.step()\n\n    print(\"Sanity batch loss:\", loss.item())\n    break\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-09T23:19:43.596229Z","iopub.execute_input":"2026-01-09T23:19:43.596580Z","iopub.status.idle":"2026-01-09T23:19:58.429762Z","shell.execute_reply.started":"2026-01-09T23:19:43.596552Z","shell.execute_reply":"2026-01-09T23:19:58.428716Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_df = pd.read_csv(TEST_CSV)\n\nclass ECGTestDataset(Dataset):\n    def __init__(self, df, test_dir):\n        self.df = df\n        self.test_dir = test_dir\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        base_id = str(row[\"id\"])\n        fs = row[\"fs\"]\n\n        n_rows = int(fs * 10)  # Lead II length\n        x = preprocess_image(\n            f\"{self.test_dir}/{base_id}.png\",\n            target_width=n_rows\n        )\n\n        return torch.tensor(x, dtype=torch.float32), base_id, fs\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-09T23:19:58.431016Z","iopub.execute_input":"2026-01-09T23:19:58.431409Z","iopub.status.idle":"2026-01-09T23:19:58.441734Z","shell.execute_reply.started":"2026-01-09T23:19:58.431370Z","shell.execute_reply":"2026-01-09T23:19:58.440806Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"LEADS = [\"I\",\"II\",\"III\",\"aVR\",\"aVL\",\"aVF\",\"V1\",\"V2\",\"V3\",\"V4\",\"V5\",\"V6\"]\n\ntest_loader = DataLoader(ECGTestDataset(test_df, TEST_DIR), batch_size=1)\n\nmodel.eval()\nsubmission_rows = []\n\nwith torch.no_grad():\n    for x, base_id, fs in test_loader:\n        base_id = base_id[0]\n        fs = fs.item()\n\n        x = x.to(device)\n        y_hat = model(x)\n\n        y_hat = nn.functional.interpolate(\n            y_hat.unsqueeze(1),\n            size=int(fs * 10),\n            mode=\"linear\",\n            align_corners=False\n        ).squeeze().cpu().numpy()\n\n        for lead in LEADS:\n            if lead == \"II\":\n                signal = y_hat\n            else:\n                signal = y_hat[:int(fs * 2.5)]\n\n            for i, val in enumerate(signal):\n                submission_rows.append([\n                    f\"{base_id}_{i}_{lead}\",\n                    float(val)\n                ])\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-09T23:19:58.442660Z","iopub.execute_input":"2026-01-09T23:19:58.442941Z","iopub.status.idle":"2026-01-09T23:21:37.431750Z","shell.execute_reply.started":"2026-01-09T23:19:58.442915Z","shell.execute_reply":"2026-01-09T23:21:37.430379Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submission = pd.DataFrame(submission_rows, columns=[\"id\", \"value\"])\nsubmission.to_csv(\"submission.csv\", index=False)\n\nprint(\"Submission saved!\")\nprint(\"Total rows:\", len(submission))\nsubmission.head()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-09T23:21:37.433366Z","iopub.execute_input":"2026-01-09T23:21:37.433817Z","iopub.status.idle":"2026-01-09T23:21:39.942157Z","shell.execute_reply.started":"2026-01-09T23:21:37.433784Z","shell.execute_reply":"2026-01-09T23:21:39.941111Z"}},"outputs":[],"execution_count":null}]}