{"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-28T18:03:04.023286Z","iopub.execute_input":"2025-12-28T18:03:04.023780Z","iopub.status.idle":"2025-12-28T18:03:26.259176Z","shell.execute_reply.started":"2025-12-28T18:03:04.023755Z","shell.execute_reply":"2025-12-28T18:03:26.258209Z"}},"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-28T18:03:26.260779Z","iopub.execute_input":"2025-12-28T18:03:26.261340Z","iopub.status.idle":"2025-12-28T18:03:26.267386Z","shell.execute_reply.started":"2025-12-28T18:03:26.261313Z","shell.execute_reply":"2025-12-28T18:03:26.266629Z"}},"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 *\nimport torch.nn.functional as F\n\ndef apply_grayscale_guidance(image_rgb):\n    \"\"\"\n    تطوير متقدم لتحسين مدخلات الموديل:\n    1. تحويل للرمادي.\n    2. إزالة الضوضاء (Denoising) للحفاظ على الخطوط.\n    3. تحسين التباين التكيفي (CLAHE).\n    \"\"\"\n    # 1. التحويل إلى الرمادي\n    gray = cv2.cvtColor(image_rgb, cv2.COLOR_RGB2GRAY)\n    \n    # 2. إزالة الضوضاء (تساعد الموديل على تجاهل نسيج الورقة والتركيز على الحبر)\n    # ملاحظة: استخدمنا h=10 بناءً على النسخة الأعلى تقييماً\n    denoised = cv2.fastNlMeansDenoising(gray, h=10)\n    \n    # 3. تحسين التباين (CLAHE) لجعل الخطوط الباهتة حادة وواضحة\n    # clipLimit=3.0 أو 4.0 تعطي نتائج ممتازة في إظهار القنوات الضعيفة\n    clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8,8))\n    contrast_enhanced = clahe.apply(denoised)\n    \n    # 4. إعادة التحويل إلى RGB لأن الموديل (ResNet) يتوقع 3 قنوات مدخلة\n    guidance_img = cv2.cvtColor(contrast_enhanced, cv2.COLOR_GRAY2RGB)\n    \n    return guidance_img\n\ndef to_device(batch, device):\n    \"\"\"نقل القاموس للجهاز (GPU) بأمان\"\"\"\n    if isinstance(batch, dict):\n        return {k: v.to(device) if isinstance(v, torch.Tensor) else v for k, v in batch.items()}\n    return batch.to(device)\n\n# إعداد المسارات\nstage0_dir = Path(global_dict[\"stage0_dir\"])\nstage0_dir.mkdir(exist_ok=True, parents=True)\n\n# تحميل الموديل\nprint(\"Loading Stage 0 Model...\")\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)\nstage0_net.eval()\n\n# حلقة المعالجة\nprint(\"Starting Stage 0 Processing...\")\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    \n    # قراءة الصورة\n    image_original = cv2.imread(str(path), cv2.IMREAD_COLOR)\n    if image_original is None: \n        continue\n    image_original = cv2.cvtColor(image_original, cv2.COLOR_BGR2RGB)\n    \n    # تطبيق \"Grayscale Guidance\" المقترح\n    image_for_model = apply_grayscale_guidance(image_original)\n    \n    # تحويل الصورة إلى Batch جاهز للموديل\n    batch = image_to_batch(image_for_model)\n    batch = to_device(batch, CUDA0) \n\n    try:\n        with torch.no_grad(), torch.amp.autocast('cuda', dtype=FLOAT_TYPE):\n            output = stage0_net(batch)\n            \n        # استخراج نقاط المحاذاة (Keypoints)\n        # rotated: الصورة بعد تعديل الميل، keypoint: النقاط التي وجدها الموديل\n        rotated, keypoint = output_to_predict(image_original, batch, output)\n        \n        # تنفيذ عملية الـ Homography لجعل الورقة مستقيمة تماماً (Normalisation)\n        normalised, _, _ = normalise_by_homography(rotated, keypoint)\n        \n        # حفظ النتيجة للمرحلة القادمة\n        cv2.imwrite(str(output_path), cv2.cvtColor(normalised, cv2.COLOR_RGB2BGR))\n        \n    except Exception as e:\n        # في حالة الفشل، انسخ الصورة الأصلية لضمان عدم توقف الـ Pipeline\n        copyfile(path, output_path)\n\nprint(f\"Stage 0 finished. Images saved to {stage0_dir}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-28T18:03:26.268201Z","iopub.execute_input":"2025-12-28T18:03:26.268377Z","iopub.status.idle":"2025-12-28T18:03:26.611473Z","shell.execute_reply.started":"2025-12-28T18:03:26.268362Z","shell.execute_reply":"2025-12-28T18:03:26.610669Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%writefile stage1.py\n\nfrom constant import *\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, 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)\nstage1_net.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: \n        # إذا لم تجد الصورة من المرحلة 0، ابحث في الأصل\n        image = cv2.imread(str(test_dir / f'{sample_id}.png'), cv2.IMREAD_COLOR)\n    \n    image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n    batch = {'image': torch.from_numpy(np.ascontiguousarray(image.transpose(2, 0, 1))).unsqueeze(0).to(CUDA0)}\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(str(output_path), cv2.cvtColor(rectified, cv2.COLOR_RGB2BGR))\n    except Exception as e:\n        copyfile(path, output_path)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-28T18:03:26.613185Z","iopub.execute_input":"2025-12-28T18:03:26.613924Z","iopub.status.idle":"2025-12-28T18:03:26.628972Z","shell.execute_reply.started":"2025-12-28T18:03:26.613895Z","shell.execute_reply":"2025-12-28T18:03:26.628283Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%writefile stage2.py\nfrom constant import *\nimport torchvision.transforms as T\nfrom stage2_model import *\nfrom stage2_common import *\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        self.encoder = timm.create_model('resnet34.a3_in1k', pretrained=pretrained, in_chans=3, num_classes=0, global_pool='')\n        self.decoder = MyCoordUnetDecoder(in_channel=encoder_dim[-1], skip_channel=encoder_dim[:-1][::-1] + [0], out_channel=decoder_dim, scale=[2, 2, 2, 2])\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\n# --- المعاملات الفيزيائية ---\nVOLTAGE_RESOLUTION = 78.5  # px/mV (المعايرة الفيزيائية للورق)\nTIME_START, TIME_END = 235, 4161\nZERO_LEVELS = [703.5, 987.5, 1271.5, 1531.5] # خطوط الصفر (Isoelectric lines)\n\nstage1_dir = Path(global_dict[\"stage1_dir\"])\nstage2_dir = Path(global_dict[\"stage2_dir\"])\nstage2_dir.mkdir(exist_ok=True, parents=True)\n\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\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    \n    image = cv2.imread(str(path), cv2.IMREAD_COLOR)\n    if image is None: continue\n    \n    image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n    # الحصول على الطول الفيزيائي المطلوب (عدد العينات المخطط لها)\n    target_length = valid_df[(valid_df['id']==sample_id) & (valid_df['lead']=='II')].iloc[0].number_of_rows\n    \n    img_input = (image[:1696, :2176] / 255.0)\n    batch = resize(torch.from_numpy(np.ascontiguousarray(img_input.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        # تحويل المخرجات إلى احتمالات فيزيائية (Sigmoid)\n        probs = torch.sigmoid(output).float().data.cpu().numpy()[0]\n        \n        # --- استخراج الإشارة بمعاملات فيزيائية ---\n        # استخدام دالة تحول كثافة البكسلات إلى سلسلة زمنية (Pixel-to-Series)\n        series_in_pixel = pixel_to_series(probs[..., TIME_START:TIME_END], ZERO_LEVELS, target_length)\n        \n        series_mv = (np.array(ZERO_LEVELS).reshape(4, 1) - series_in_pixel) / VOLTAGE_RESOLUTION\n        \n        # --- الفلترة الفيزيائية (Signal Smoothing) ---\n        for i in range(series_mv.shape[0]):\n            # 1. Median Filter لإزالة \"القفزات\" الفيزيائية غير المنطقية (Spikes)\n            series_mv[i] = medfilt(series_mv[i], kernel_size=3)\n            # 2. Savitzky-Golay للحفاظ على قمم الموجات (P, QRS, T) مع تنعيم الضجيج\n            # window_length=7 تعادل تقريباً 14 مللي ثانية، وهي دقيقة طبياً\n            series_mv[i] = savgol_filter(series_mv[i], window_length=7, polyorder=2)\n            \n        np.save(output_path, series_mv)\n    except Exception as e:\n        np.save(output_path, np.zeros((4, target_length)))\n\nprint(\"Physical Signal Extraction Complete.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-28T18:03:26.629783Z","iopub.execute_input":"2025-12-28T18:03:26.630008Z","iopub.status.idle":"2025-12-28T18:03:26.647810Z","shell.execute_reply.started":"2025-12-28T18:03:26.629983Z","shell.execute_reply":"2025-12-28T18:03:26.647250Z"}},"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-28T18:03:26.648593Z","iopub.execute_input":"2025-12-28T18:03:26.649062Z","iopub.status.idle":"2025-12-28T18:05:20.183827Z","shell.execute_reply.started":"2025-12-28T18:03:26.649038Z","shell.execute_reply":"2025-12-28T18:05:20.182902Z"}},"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-28T18:05:20.185023Z","iopub.execute_input":"2025-12-28T18:05:20.185322Z","iopub.status.idle":"2025-12-28T18:05:20.997116Z","shell.execute_reply.started":"2025-12-28T18:05:20.185287Z","shell.execute_reply":"2025-12-28T18:05:20.996487Z"}},"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-28T18:05:20.997839Z","iopub.execute_input":"2025-12-28T18:05:20.998096Z","iopub.status.idle":"2025-12-28T18:05:26.929401Z","shell.execute_reply.started":"2025-12-28T18:05:20.998066Z","shell.execute_reply":"2025-12-28T18:05:26.928744Z"}},"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-28T18:05:26.930097Z","iopub.execute_input":"2025-12-28T18:05:26.930283Z","iopub.status.idle":"2025-12-28T18:05:27.206853Z","shell.execute_reply.started":"2025-12-28T18:05:26.930268Z","shell.execute_reply":"2025-12-28T18:05:27.206061Z"}},"outputs":[],"execution_count":null}]}