{"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}],"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-31T02:41:54.864909Z","iopub.execute_input":"2025-12-31T02:41:54.865753Z","iopub.status.idle":"2025-12-31T02:42:15.356748Z","shell.execute_reply.started":"2025-12-31T02:41:54.865724Z","shell.execute_reply":"2025-12-31T02:42:15.355960Z"}},"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-31T02:42:15.358399Z","iopub.execute_input":"2025-12-31T02:42:15.358679Z","iopub.status.idle":"2025-12-31T02:42:15.365151Z","shell.execute_reply.started":"2025-12-31T02:42:15.358649Z","shell.execute_reply":"2025-12-31T02:42:15.364295Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%writefile stage0.py\nfrom constant import *\nfrom stage0_model import Net as Stage0Net\nfrom stage0_common import *\n\ndef auto_clean_image(img):\n    # تحسين التباين وإزالة الظلال أوتوماتيكياً\n    gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)\n    clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8,8))\n    enhanced = clahe.apply(gray)\n    return cv2.cvtColor(enhanced, cv2.COLOR_GRAY2RGB)\n\nstage0_dir = Path(global_dict[\"stage0_dir\"])\nstage0_dir.mkdir(exist_ok=True, parents=True)\n\nstage0_net = Stage0Net(pretrained=False).to(CUDA0)\nstage0_net = load_net(stage0_net, '/kaggle/input/hengck23-submit-physionet/hengck23-submit-physionet/weight/stage0-last.checkpoint.pth')\nstage0_net.eval()\n\nfor sample_id in tqdm(valid_id):\n    path = test_dir / f'{sample_id}.png'\n    image = cv2.imread(str(path), cv2.IMREAD_COLOR)\n    if image is None: continue\n    image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n    \n    # تنظيف تلقائي قبل الكشف\n    cleaned = auto_clean_image(image)\n    batch = image_to_batch(cleaned)\n    batch = {k: v.to(CUDA0) if isinstance(v, torch.Tensor) else v for k, v in batch.items()}\n\n    try:\n        with torch.no_grad():\n            output = stage0_net(batch)\n        rotated, keypoint = output_to_predict(image, batch, output)\n        # إجبار الصورة على حجم ثابت (Rigid Dimensions) للحفاظ على الثوابت\n        normalised, _, _ = normalise_by_homography(rotated, keypoint)\n        # الحجم الذهبي للمسابقة\n        final = cv2.resize(normalised, (4352, 2176)) \n        cv2.imwrite(str(stage0_dir / f'{sample_id}.png'), cv2.cvtColor(final, cv2.COLOR_RGB2BGR))\n    except:\n        copyfile(path, stage0_dir / f'{sample_id}.png')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-31T02:42:15.366085Z","iopub.execute_input":"2025-12-31T02:42:15.366406Z","iopub.status.idle":"2025-12-31T02:42:15.400393Z","shell.execute_reply.started":"2025-12-31T02:42:15.366388Z","shell.execute_reply":"2025-12-31T02:42:15.399715Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%writefile stage1.py\nfrom constant import *\nfrom stage1_model import Net as Stage1Net\nfrom stage1_common import *\nimport torchvision.transforms.functional as TF\n\nstage0_dir = Path(global_dict[\"stage0_dir\"])\nstage1_dir = Path(global_dict[\"stage1_dir\"])\nstage1_dir.mkdir(exist_ok=True, parents=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).eval()\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    \n    image = cv2.imread(str(path), cv2.IMREAD_COLOR)\n    if image is None: image = cv2.imread(str(test_dir / f'{sample_id}.png'), cv2.IMREAD_COLOR)\n    \n    image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n    img_t = 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            # TTA للمرحلة الأولى: متوسط اكتشاف نقاط الشبكة\n            out1 = stage1_net({'image': img_t})\n            out2 = stage1_net({'image': TF.adjust_brightness(img_t, 1.2)}) # نسخة أفتح\n            \n            # دمج نقاط الشبكة (Grid Points)\n            avg_grid = (out1['gridpoint'] + out2['gridpoint']) / 2\n            output = {'gridpoint': avg_grid}\n\n        gridpoint_xy, _ = output_to_predict(image, {'image': img_t}, output)\n        rectified = rectify_image(image, gridpoint_xy)\n        cv2.imwrite(str(output_path), cv2.cvtColor(rectified, cv2.COLOR_RGB2BGR))\n    except:\n        copyfile(path, output_path)\n\nprint(\"Stage 1 Automatic finished.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-31T02:42:15.402142Z","iopub.execute_input":"2025-12-31T02:42:15.402559Z","iopub.status.idle":"2025-12-31T02:42:15.417030Z","shell.execute_reply.started":"2025-12-31T02:42:15.402538Z","shell.execute_reply":"2025-12-31T02:42:15.416437Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%writefile stage2.py\nfrom constant import *\nimport torchvision.transforms.functional as TF\nimport timm\nfrom stage2_model import *\nfrom stage2_common import *\nfrom scipy.signal import savgol_filter\n\nclass Net3(nn.Module):\n    def __init__(self, pretrained=False):\n        super(Net3, self).__init__()\n        self.encoder = timm.create_model('resnet34.a3_in1k', pretrained=pretrained, in_chans=3, num_classes=0, global_pool='')\n        self.decoder = MyCoordUnetDecoder(in_channel=512, skip_channel=[256, 128, 64, 0], out_channel=[128, 64, 32, 16], scale=[2, 2, 2, 2])\n        self.pixel = nn.Conv2d(16, 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        return self.pixel(last)\n\nstage0_dir = Path(global_dict[\"stage0_dir\"])\nstage2_dir = Path(global_dict[\"stage2_dir\"])\nstage2_dir.mkdir(exist_ok=True, parents=True)\n\nmodel = Net3().to(CUDA0)\nmodel.load_state_dict(torch.load(\"/kaggle/input/physio-seg-public/pytorch/net3_009_4200/1/iter_0004200.pt\"))\nmodel.eval()\n\n# الثوابت الفيزيائية (الأساس العلمي)\nmv_to_pixel = 78.5\nzero_mv = [703.5, 987.5, 1271.5, 1531.5]\nt0, t1 = 235, 4161\n\nprint(\"Automatic Digitization & Cleaning...\")\nfor sample_id in tqdm(valid_id):\n    path = stage0_dir / f'{sample_id}.png'\n    image = cv2.imread(str(path), cv2.IMREAD_COLOR)\n    if image is None: continue\n    \n    # --- تنظيف أوتوماتيكي متقدم ---\n    # 1. إزالة الشبكة الوردية/الرمادية عبر Thresholding ذكي\n    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)\n    # الاحتفاظ فقط بالخطوط السوداء (الحبر)\n    _, binary = cv2.threshold(gray, 165, 255, cv2.THRESH_BINARY_INV)\n    # إزالة النقاط الصغيرة (Noise)\n    binary = cv2.morphologyEx(binary, cv2.MORPH_OPEN, np.ones((2,2), np.uint8))\n    \n    # إعادتها لـ RGB ليدخل الموديل\n    cleaned_input = cv2.cvtColor(255 - binary, cv2.COLOR_GRAY2RGB)\n    \n    img_t = torch.from_numpy(cleaned_input.transpose(2, 0, 1)).unsqueeze(0).float().to(CUDA0) / 255.0\n    img_t = TF.resize(img_t, (1696, 4352))\n\n    try:\n        with torch.no_grad():\n            output = torch.sigmoid(model(img_t)).cpu().numpy()[0]\n        \n        # تصفير الأطراف أوتوماتيكياً (إزالة البراويز والقفزات)\n        output[:, :, :t0] = 0\n        output[:, :, t1:] = 0\n        output[output < 0.5] = 0\n        \n        # التحويل من بكسل لإشارة بناءً على الثوابت\n        length = valid_df[valid_df['id']==sample_id].iloc[0].number_of_rows\n        series_px = pixel_to_series(output[..., t0:t1], zero_mv, length)\n        series = (np.array(zero_mv).reshape(4, 1) - series_px) / mv_to_pixel\n        \n        # --- تنقية الإشارة أوتوماتيكياً ---\n        for i in range(4):\n            # 1. حذف القفزات غير المنطقية (أكثر من 4mV فجأة)\n            series[i] = np.clip(series[i], -4, 4)\n            # 2. تنعيم طبي فائق الجودة\n            series[i] = savgol_filter(series[i], window_length=25, polyorder=3)\n            # 3. تصفير خط الأساس (Median Centering)\n            series[i] -= np.median(series[i])\n            \n        np.save(stage2_dir / f'{sample_id}.npy', series)\n    except:\n        np.save(stage2_dir / f'{sample_id}.npy', np.zeros((4, 5000)))\n\nprint(\"Automatic Pipeline Finished Successfully.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-31T02:42:15.417805Z","iopub.execute_input":"2025-12-31T02:42:15.418093Z","iopub.status.idle":"2025-12-31T02:42:15.432470Z","shell.execute_reply.started":"2025-12-31T02:42:15.418075Z","shell.execute_reply":"2025-12-31T02:42:15.431910Z"}},"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-31T02:42:15.433178Z","iopub.execute_input":"2025-12-31T02:42:15.433343Z","iopub.status.idle":"2025-12-31T02:43:30.400812Z","shell.execute_reply.started":"2025-12-31T02:42:15.433330Z","shell.execute_reply":"2025-12-31T02:43:30.400024Z"}},"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-31T02:43:30.401865Z","iopub.execute_input":"2025-12-31T02:43:30.402114Z","iopub.status.idle":"2025-12-31T02:43:31.229401Z","shell.execute_reply.started":"2025-12-31T02:43:30.402087Z","shell.execute_reply":"2025-12-31T02:43:31.228643Z"}},"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-31T02:43:31.230290Z","iopub.execute_input":"2025-12-31T02:43:31.230800Z","iopub.status.idle":"2025-12-31T02:43:37.186578Z","shell.execute_reply.started":"2025-12-31T02:43:31.230777Z","shell.execute_reply":"2025-12-31T02:43:37.185930Z"}},"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-31T02:43:37.187346Z","iopub.execute_input":"2025-12-31T02:43:37.187585Z","iopub.status.idle":"2025-12-31T02:43:37.433096Z","shell.execute_reply.started":"2025-12-31T02:43:37.187558Z","shell.execute_reply":"2025-12-31T02:43:37.432287Z"}},"outputs":[],"execution_count":null}]}