{"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":"gpu","dataSources":[{"sourceId":97984,"databundleVersionId":14096757,"sourceType":"competition"},{"sourceId":13746387,"sourceType":"datasetVersion","datasetId":8747012},{"sourceId":13816899,"sourceType":"datasetVersion","datasetId":8620533},{"sourceId":271051632,"sourceType":"kernelVersion"},{"sourceId":677607,"sourceType":"modelInstanceVersion","modelInstanceId":513841,"modelId":528480}],"dockerImageVersionId":31193,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!pip uninstall -y tensorflow\n!uv pip install --no-deps --system --no-index --find-links='/kaggle/input/hengck23-submit-physionet/hengck23-submit-physionet/setup' 'connected-components-3d'","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-12-20T14:44:13.382964Z","iopub.execute_input":"2025-12-20T14:44:13.383666Z","iopub.status.idle":"2025-12-20T14:44:34.761114Z","shell.execute_reply.started":"2025-12-20T14:44:13.383639Z","shell.execute_reply":"2025-12-20T14:44:34.760225Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%writefile constant.py\n\nimport warnings\nwarnings.filterwarnings(\"ignore\", category=UserWarning, module=\"pydantic\")\n\nimport kagglehub\nseed = 0\nCUDA0 = \"cuda:0\"\ndeterministic = kagglehub.package_import('wasupandceacar/deterministic').deterministic\ndeterministic.init_all(seed, disable_list=['cuda_block'])\n\nimport sys\nsys.path.append('/kaggle/input/hengck23-submit-physionet/hengck23-submit-physionet')\n\nimport os\nimport traceback\nfrom pathlib import Path\nfrom shutil import copyfile\nimport torch\nimport cv2\nimport pandas as pd\nimport numpy as np\nfrom tqdm.auto import tqdm\n\nif_submit = os.getenv('KAGGLE_IS_COMPETITION_RERUN')\n\nif if_submit:\n    test_meta = Path(\"/kaggle/input/physionet-ecg-image-digitization/test.csv\")\n    test_dir = Path(\"/kaggle/input/physionet-ecg-image-digitization/test\")\nelse:\n    test_meta = Path(\"/kaggle/input/physio-test-fake-dataset/test_fake/test.csv\")\n    test_dir = Path(\"/kaggle/input/physio-test-fake-dataset/test_fake\")\n\nvalid_df = pd.read_csv(test_meta)\nvalid_df['id'] = valid_df['id'].astype(str) \nvalid_id = valid_df['id'].unique().tolist()\n\nFLOAT_TYPE = torch.float32\n\nglobal_dict = {\n    \"stage0_dir\": \"/kaggle/working/stage0\",\n    \"stage1_dir\": \"/kaggle/working/stage1\",\n    \"stage2_dir\": \"/kaggle/working/stage2\",\n}","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-20T14:44:34.762901Z","iopub.execute_input":"2025-12-20T14:44:34.763114Z","iopub.status.idle":"2025-12-20T14:44:34.769039Z","shell.execute_reply.started":"2025-12-20T14:44:34.763093Z","shell.execute_reply":"2025-12-20T14:44:34.768341Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%writefile stage0.py\n\nfrom constant import *\n\nfrom stage0_model import Net as Stage0Net\nfrom stage0_common import *\n\nstage0_dir = Path(global_dict[\"stage0_dir\"])\nstage0_dir.mkdir(exist_ok=True)\n\nstage0_net = Stage0Net(pretrained=False)\nstage0_net = load_net(stage0_net, '/kaggle/input/hengck23-submit-physionet/hengck23-submit-physionet/weight/stage0-last.checkpoint.pth')\nstage0_net.to(CUDA0)\n\nfor n, sample_id in enumerate(tqdm(valid_id)):\n    path = test_dir / f'{sample_id}.png'\n    output_path = stage0_dir / f'{sample_id}.png'\n    image = cv2.imread(path, cv2.IMREAD_COLOR_RGB)\n    batch = image_to_batch(image)\n\n    try:\n        with torch.no_grad(), torch.amp.autocast('cuda', dtype=FLOAT_TYPE):\n            output = stage0_net(batch)\n        rotated, keypoint = output_to_predict(image, batch, output)\n        normalised, _, _ = normalise_by_homography(rotated, keypoint)\n        cv2.imwrite(output_path, cv2.cvtColor(normalised, cv2.COLOR_RGB2BGR))\n    except:\n        traceback.print_exc()\n        copyfile(path, output_path)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-20T14:44:34.769831Z","iopub.execute_input":"2025-12-20T14:44:34.770072Z","iopub.status.idle":"2025-12-20T14:44:34.814025Z","shell.execute_reply.started":"2025-12-20T14:44:34.770052Z","shell.execute_reply":"2025-12-20T14:44:34.813262Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%writefile stage1.py\n\nfrom constant import *\n\nfrom stage1_model import Net as Stage1Net\nfrom stage1_common import *\n\nstage0_dir = Path(global_dict[\"stage0_dir\"])\nstage1_dir = Path(global_dict[\"stage1_dir\"])\nstage1_dir.mkdir(exist_ok=True)\n\nstage1_net = Stage1Net(pretrained=False)\nstage1_net = load_net(stage1_net, '/kaggle/input/hengck23-submit-physionet/hengck23-submit-physionet/weight/stage1-last.checkpoint.pth')\nstage1_net.to(CUDA0)\n\nfor n, sample_id in enumerate(tqdm(valid_id)):\n    path = stage0_dir / f'{sample_id}.png'\n    output_path = stage1_dir / f'{sample_id}.png'\n    image = cv2.imread(path, cv2.IMREAD_COLOR_RGB)\n    batch = {'image': torch.from_numpy(np.ascontiguousarray(image.transpose(2, 0, 1))).unsqueeze(0)}\n\n    try:\n        with torch.no_grad(), torch.amp.autocast('cuda', dtype=FLOAT_TYPE):\n            output = stage1_net(batch)\n        gridpoint_xy, _ = output_to_predict(image, batch, output)\n        rectified = rectify_image(image, gridpoint_xy)\n        cv2.imwrite(output_path, cv2.cvtColor(rectified, cv2.COLOR_RGB2BGR))\n    except:\n        traceback.print_exc()\n        copyfile(path, output_path)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-20T14:44:34.815702Z","iopub.execute_input":"2025-12-20T14:44:34.816004Z","iopub.status.idle":"2025-12-20T14:44:34.829485Z","shell.execute_reply.started":"2025-12-20T14:44:34.815985Z","shell.execute_reply":"2025-12-20T14:44:34.828838Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%writefile stage2.py\n\nfrom constant import *\nimport torchvision.transforms as T\nfrom stage2_model import *\nfrom stage2_common import *\n# --- NEW: Import Strategy Tool ---\nfrom scipy.signal import savgol_filter, medfilt\n# ---------------------------------\n\nclass Net3(nn.Module):\n    def __init__(self, pretrained=True):\n        super(Net3, self).__init__()\n        encoder_dim = [64, 128, 256, 512]\n        decoder_dim = [128, 64, 32, 16]\n\n        self.encoder = timm.create_model(\n            model_name='resnet34.a3_in1k', pretrained=pretrained, in_chans=3, num_classes=0, global_pool=''\n        )\n\n        self.decoder = MyCoordUnetDecoder(\n            in_channel=encoder_dim[-1],\n            skip_channel=encoder_dim[:-1][::-1] + [0],\n            out_channel=decoder_dim,\n            scale=[2, 2, 2, 2]\n        )\n        self.pixel = nn.Conv2d(decoder_dim[-1], 4, 1)\n\n    def forward(self, image):\n        encode = encode_with_resnet(self.encoder, image)\n        last, _ = self.decoder(feature=encode[-1], skip=encode[:-1][::-1] + [None])\n        pixel = self.pixel(last)\n        return pixel\n\nstage1_dir = Path(global_dict[\"stage1_dir\"])\nstage2_dir = Path(global_dict[\"stage2_dir\"])\nstage2_dir.mkdir(exist_ok=True)\n\nstage2_net = Net3(pretrained=False).to(CUDA0)\nmodel_path = \"/kaggle/input/physio-seg-public/pytorch/net3_009_4200/1/iter_0004200.pt\"\nstage2_net.load_state_dict(torch.load(model_path))\nstage2_net.eval()\n\nx0, x1 = 0, 2176\ny0, y1 = 0, 1696\nzero_mv = [703.2, 987.2, 1271.2, 1531.2]\nmv_to_pixel = 78.3\nt0, t1 = 235, 4161\n\nresize = T.Resize((1696, 4352), interpolation=T.InterpolationMode.BILINEAR)\n\nfor n, sample_id in enumerate(tqdm(valid_id)):\n    path = stage1_dir / f'{sample_id}.png'\n    output_path = stage2_dir / f'{sample_id}.npy'\n    image = cv2.imread(path, cv2.IMREAD_COLOR_RGB)\n    \n    # Get original length for later, but extract full resolution first\n    length = valid_df[(valid_df['id']==sample_id) & (valid_df['lead']=='II')].iloc[0].number_of_rows\n    \n    image = image[y0:y1, x0:x1] / 255\n    batch = resize(torch.from_numpy(np.ascontiguousarray(image.transpose(2, 0, 1))).unsqueeze(0)).float().to(CUDA0)\n    \n    try:\n        with torch.no_grad(), torch.amp.autocast('cuda', dtype=FLOAT_TYPE):\n            output = stage2_net(batch)\n        \n        pixel = torch.sigmoid(output).float().data.cpu().numpy()[0]\n        \n        series_in_pixel = pixel_to_series(pixel[..., t0:t1], zero_mv, length)\n        \n        series = (np.array(zero_mv).reshape(4, 1) - series_in_pixel) / mv_to_pixel\n        \n       \n        for i in range(series.shape[0]): \n           \n            series[i] = savgol_filter(series[i], window_length=7, polyorder=2)\n\n        np.save(output_path, series)\n    except:\n        traceback.print_exc()\n        series = np.zeros((4, length)) \n        np.save(output_path, series)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-20T14:44:34.830064Z","iopub.execute_input":"2025-12-20T14:44:34.830219Z","iopub.status.idle":"2025-12-20T14:44:34.845354Z","shell.execute_reply.started":"2025-12-20T14:44:34.830206Z","shell.execute_reply":"2025-12-20T14:44:34.844608Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!python stage0.py\n!python stage1.py\n!python stage2.py","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-20T14:44:34.846118Z","iopub.execute_input":"2025-12-20T14:44:34.846858Z","iopub.status.idle":"2025-12-20T14:45:51.802999Z","shell.execute_reply.started":"2025-12-20T14:44:34.846835Z","shell.execute_reply":"2025-12-20T14:45:51.802217Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nimport numpy as np\nfrom pathlib import Path\nimport random\n\nstage2_dir = Path(\"/kaggle/working/stage2\")\nfiles = list(stage2_dir.glob(\"*.npy\"))\n\nif len(files) > 0:\n    \n    sample_file = random.choice(files)\n    \n    print(f\"📊 Analyzing file: {sample_file.name}\")\n    \n    \n    series = np.load(sample_file)\n    \n    fig, axes = plt.subplots(4, 1, figsize=(18, 12), sharex=True)\n    lead_names = [\"Leads Group 1 (I, II, III)\", \"Leads Group 2 (aVR, aVL, aVF)\", \"Leads Group 3 (V1-V6)\", \"Long Lead II\"]\n    \n    for i in range(4):\n        ax = axes[i]\n        signal = series[i, :]\n        \n        ax.plot(signal, color='#1f77b4', linewidth=1.2)\n        ax.set_title(f\"{lead_names[i]} | Min: {signal.min():.2f} | Max: {signal.max():.2f}\", fontsize=12)\n        ax.grid(True, alpha=0.3)\n        ax.set_ylabel(\"Voltage (mV)\")\n        \n        ax.axhline(0, color='red', linestyle='--', alpha=0.5, linewidth=0.8)\n\n    plt.xlabel(\"Time (Samples)\")\n    plt.tight_layout()\n    plt.show()\n    \n    print(\"✅ Done plotting.\")\nelse:\n    print(\"❌ No output files found in stage2!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-20T14:45:51.804032Z","iopub.execute_input":"2025-12-20T14:45:51.804265Z","iopub.status.idle":"2025-12-20T14:45:52.652411Z","shell.execute_reply.started":"2025-12-20T14:45:51.804239Z","shell.execute_reply":"2025-12-20T14:45:52.651641Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from constant import *\nimport gc\nfrom scipy.signal import resample\nimport pandas as pd\nimport numpy as np\nfrom tqdm import tqdm\nfrom pathlib import Path\n\ndef expand_4_to_12(pred4):\n    pred12 = np.zeros((pred4.shape[0], 12))\n    quat = pred4.shape[0] // 4\n\n    pred12[:quat, 0] = pred4[:quat, 0]\n    pred12[:, 1] = pred4[:, 3]\n    pred12[:quat, 2] = pred4[:quat, 2]\n    pred12[quat:2*quat, 3] = pred4[quat:2*quat, 0]\n    pred12[quat:2*quat, 4] = pred4[quat:2*quat, 1]\n    pred12[quat:2*quat, 5] = pred4[quat:2*quat, 2]\n    pred12[2*quat:3*quat, 6] = pred4[2*quat:3*quat, 0]\n    pred12[2*quat:3*quat, 7] = pred4[2*quat:3*quat, 1]\n    pred12[2*quat:3*quat, 8] = pred4[2*quat:3*quat, 2]\n    pred12[3*quat:4*quat, 9] = pred4[3*quat:4*quat, 0]\n    pred12[3*quat:4*quat, 10] = pred4[3*quat:4*quat, 1]\n    pred12[3*quat:4*quat, 11] = pred4[3*quat:4*quat, 2]\n    \n    return pred12\n\ndef series_dict(series):\n    series_by_lead = dict()\n    for l in range(3):\n        lead_names = [\n            ['I',   'aVR', 'V1', 'V4'],\n            ['II',  'aVL', 'V2', 'V5'],\n            ['III', 'aVF', 'V3', 'V6'],\n        ][l]\n        split = np.array_split(series[l], 4)\n        for (k, s) in zip(lead_names, split):\n            series_by_lead[k] = s\n    series_by_lead['II'] = series[3]\n    return series_by_lead\n\n\nstage2_dir = Path(global_dict[\"stage2_dir\"])\n\nsubmit_df = list()\ngb = valid_df.groupby('id')\n\nshow = True\n\nprint(\"Generating submission file...\")\n\nfor rec_idx, (sample_id, df) in enumerate(tqdm(gb)):\n    try:\n        \n        series = np.load(stage2_dir / f'{sample_id}.npy')\n        \n        series_by_lead = series_dict(series)\n\n        for _, d in df.iterrows():\n            s = series_by_lead.get(d.lead, np.zeros(d.number_of_rows))\n            \n            if len(s) != d.number_of_rows:\n                x_old = np.linspace(0, 1, len(s))\n                x_new = np.linspace(0, 1, d.number_of_rows)\n                s = np.interp(x_new, x_old, s)\n            \n            row_id = [f'{sample_id}_{t}_{d.lead}' for t in range(d.number_of_rows)]\n            submit_df.append(pd.DataFrame({'id': row_id, 'value': s}))\n            \n    except Exception as e:\n        pass\n\n    if rec_idx % 100 == 0:\n        gc.collect()\n\nif submit_df:\n    final_df = pd.concat(submit_df, axis=0, ignore_index=True)\n    final_df.to_csv('submission.csv', index=False)\n    print(f\"✅ Done! Saved submission.csv with shape: {final_df.shape}\")\n    print(final_df.head())\nelse:\n    print(\"❌ Error: No predictions were generated!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-20T14:45:52.653358Z","iopub.execute_input":"2025-12-20T14:45:52.653934Z","iopub.status.idle":"2025-12-20T14:45:58.816026Z","shell.execute_reply.started":"2025-12-20T14:45:52.653914Z","shell.execute_reply":"2025-12-20T14:45:58.815216Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"sub = pd.read_csv('submission.csv')\nprint(len(sub))\nsub.head(30)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-20T14:45:58.816922Z","iopub.execute_input":"2025-12-20T14:45:58.817194Z","iopub.status.idle":"2025-12-20T14:45:59.092409Z","shell.execute_reply.started":"2025-12-20T14:45:58.817169Z","shell.execute_reply":"2025-12-20T14:45:59.091555Z"}},"outputs":[],"execution_count":null}]}