{"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":272137252,"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":"import os\n\nprint(\"--- Uninstalling TensorFlow to free GPU memory ---\")\n!pip uninstall -y tensorflow\n\nprint(\"--- Installing required medical imaging libraries ---\")\n!uv pip install --no-deps --system --no-index --find-links='/kaggle/input/hengck23-submit-physionet/hengck23-submit-physionet/setup' 'connected-components-3d'\n\nprint(\"--- Environment is ready! ---\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-13T11:15:43.328661Z","iopub.execute_input":"2026-01-13T11:15:43.328949Z","iopub.status.idle":"2026-01-13T11:16:05.656488Z","shell.execute_reply.started":"2026-01-13T11:15:43.328929Z","shell.execute_reply":"2026-01-13T11:16:05.655445Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%writefile constant.py\nimport os\nimport torch\nimport pandas as pd\nfrom pathlib import Path\n\n# الإعدادات العامة\nSEED = 42\nDEVICE = \"cuda:0\" if torch.cuda.is_available() else \"cpu\"\n\n# المسارات\nBASE_DIR = Path(\"/kaggle/input/physionet-ecg-image-digitization\")\nWORKING_DIR = Path(\"/kaggle/working\")\nHENGCK_DIR = Path(\"/kaggle/input/hengck23-submit-physionet/hengck23-submit-physionet\")\n\n# أوزان النماذج\nW_STAGE0 = HENGCK_DIR / \"weight/stage0-last.checkpoint.pth\"\nW_STAGE1 = HENGCK_DIR / \"weight/stage1-last.checkpoint.pth\"\nW_STAGE2 = Path(\"/kaggle/input/physio-seg-public/pytorch/net3_009_4200/1/iter_0004200.pt\")\nW_CLASSIFIER = Path(\"/kaggle/input/physionet-image-multi-class-train/efficientnet_b2_full_train.pth\")\n\n# مجلدات المخرجات\nDIRS = {\n    \"stage0\": WORKING_DIR / \"stage0\",\n    \"stage1\": WORKING_DIR / \"stage1\",\n    \"stage2\": WORKING_DIR / \"stage2\",\n    \"qa_vis\": WORKING_DIR / \"qa_visuals\", # ✅ جديد: لحفظ صور التحليل\n    \"logs\": WORKING_DIR / \"logs\"\n}\n\nfor d in DIRS.values():\n    d.mkdir(exist_ok=True, parents=True)\n\n# تحميل بيانات الاختبار\ntest_df = pd.read_csv(BASE_DIR / \"test.csv\")\ntest_df['id'] = test_df['id'].astype(str)\nsample_ids = test_df['id'].unique().tolist()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-13T11:16:05.658506Z","iopub.execute_input":"2026-01-13T11:16:05.658735Z","iopub.status.idle":"2026-01-13T11:16:05.665026Z","shell.execute_reply.started":"2026-01-13T11:16:05.658714Z","shell.execute_reply":"2026-01-13T11:16:05.664288Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%writefile qa_utils.py\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport cv2\nimport pandas as pd\nfrom constant import *\n\nLEADS_ORDER = [\"I\", \"II\", \"III\", \"aVR\", \"aVL\", \"aVF\", \"V1\", \"V2\", \"V3\", \"V4\", \"V5\", \"V6\"]\n\ndef normalize_heatmap(hm, p_low=1, p_high=99):\n    \"\"\"Normalize heatmap using percentiles for visibility (from Code A).\"\"\"\n    lo, hi = np.percentile(hm, [p_low, p_high])\n    hm = np.clip(hm, lo, hi)\n    return (hm - lo) / (hi - lo + 1e-9)\n\ndef plot_stage_gallery(sample_id, img_orig, img_s0, img_s1, pixel_map, series_dict):\n    \"\"\"\n    رسم مراحل المعالجة (أصلية -> تدوير -> تصحيح -> خريطة حرارية)\n    \"\"\"\n    fig, axes = plt.subplots(1, 4, figsize=(24, 6))\n    \n    # 1. Original\n    axes[0].imshow(cv2.cvtColor(img_orig, cv2.COLOR_BGR2RGB))\n    axes[0].set_title(f\"{sample_id} | Original\")\n    axes[0].axis(\"off\")\n\n    # 2. Stage 0 (Rotated)\n    if img_s0 is not None:\n        axes[1].imshow(cv2.cvtColor(img_s0, cv2.COLOR_BGR2RGB))\n        axes[1].set_title(\"Stage 0 (Rotated)\")\n    else:\n        axes[1].text(0.5, 0.5, \"N/A\", ha='center')\n    axes[1].axis(\"off\")\n\n    # 3. Stage 1 (Rectified)\n    if img_s1 is not None:\n        axes[2].imshow(cv2.cvtColor(img_s1, cv2.COLOR_BGR2RGB))\n        axes[2].set_title(\"Stage 1 (Rectified)\")\n    else:\n        axes[2].text(0.5, 0.5, \"N/A\", ha='center')\n    axes[2].axis(\"off\")\n\n    # 4. Stage 2 (Heatmap)\n    if pixel_map is not None:\n        # دمج القنوات الـ 4 لعرضها كصورة واحدة\n        hm_vis = np.max(pixel_map, axis=0) # Max projection\n        hm_vis = normalize_heatmap(hm_vis)\n        im = axes[3].imshow(hm_vis, cmap=\"magma\", vmin=0, vmax=1)\n        axes[3].set_title(\"Stage 2 (Segmentation Heatmap)\")\n        plt.colorbar(im, ax=axes[3], fraction=0.046, pad=0.04)\n    else:\n        axes[3].text(0.5, 0.5, \"Map Not Saved\", ha='center')\n    axes[3].axis(\"off\")\n\n    plt.tight_layout()\n    plt.savefig(DIRS[\"qa_vis\"] / f\"{sample_id}_gallery.png\")\n    plt.show()\n\ndef plot_signals_detailed(sample_id, d_series):\n    \"\"\"\n    رسم الإشارات الـ 12 بشكل مفصل (مثل Code A)\n    \"\"\"\n    ncols, nrows = 3, 4\n    fig, axes = plt.subplots(nrows, ncols, figsize=(20, 12))\n    axes = axes.flatten()\n\n    for i, lead in enumerate(LEADS_ORDER):\n        ax = axes[i]\n        \n        # استخراج الإشارة\n        y = d_series.get(lead, [])\n        if len(y) == 0 and lead == \"II\": y = d_series.get(\"II_Long\", []) # Fallback for Long Lead II\n\n        ax.plot(y, lw=1.0, color='#1f77b4', label=\"Pred\")\n        ax.set_title(lead, fontsize=11, fontweight='bold')\n        ax.grid(alpha=0.3)\n        \n        # إحصائيات بسيطة\n        std_val = np.std(y)\n        if std_val < 0.01:\n            ax.text(0.05, 0.9, \"⚠️ FLAT / DEAD\", transform=ax.transAxes, color=\"red\", fontsize=8, fontweight='bold')\n\n    plt.suptitle(f\"Reconstructed Signals for ID: {sample_id}\", fontsize=16)\n    plt.tight_layout(rect=[0, 0, 1, 0.96])\n    plt.savefig(DIRS[\"qa_vis\"] / f\"{sample_id}_signals.png\")\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-13T11:16:05.665966Z","iopub.execute_input":"2026-01-13T11:16:05.666235Z","iopub.status.idle":"2026-01-13T11:16:05.705763Z","shell.execute_reply.started":"2026-01-13T11:16:05.666211Z","shell.execute_reply":"2026-01-13T11:16:05.705118Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%writefile engine_utils.py\nimport torch\nimport torch.nn as nn\nimport timm\nimport cv2\nimport numpy as np\nfrom constant import DEVICE\n\ndef build_classifier(ckpt_path):\n    model = timm.create_model(\"efficientnet_b2\", pretrained=False, num_classes=12)\n    state = torch.load(ckpt_path, map_location=\"cpu\")\n    if isinstance(state, dict) and \"state_dict\" in state:\n        state = state[\"state_dict\"]\n    new_state = {k.replace(\"module.\", \"\"): v for k, v in state.items()}\n    model.load_state_dict(new_state, strict=False)\n    return model.to(DEVICE).eval()\n\ndef preprocess_by_source(img_bgr, source_suffix):\n    s = str(source_suffix)\n    if s == \"0003\":\n        img = img_bgr.astype(np.float32)\n        b, g, r = cv2.split(img)\n        m = (b.mean() + g.mean() + r.mean()) / 3.0\n        b *= (m / (b.mean() + 1e-6)); g *= (m / (g.mean() + 1e-6)); r *= (m / (r.mean() + 1e-6))\n        return np.clip(cv2.merge([b, g, r]), 0, 255).astype(np.uint8)\n    if s in [\"0005\", \"0009\", \"0010\"]:\n        lab = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2LAB)\n        l, a, b = cv2.split(lab)\n        bg = cv2.morphologyEx(l, cv2.MORPH_OPEN, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (81, 81)))\n        l_corr = cv2.normalize(cv2.subtract(l, bg), None, 0, 255, cv2.NORM_MINMAX)\n        return cv2.cvtColor(cv2.merge([l_corr, a, b]), cv2.COLOR_LAB2BGR)\n    return img_bgr\n\ndef get_image_quality(img_rgb):\n    gray = cv2.cvtColor(img_rgb.astype(np.uint8), cv2.COLOR_RGB2GRAY)\n    edges = cv2.Canny(gray, 50, 150)\n    density = edges.mean() / 255.0\n    gx = cv2.Sobel(gray, cv2.CV_32F, 1, 0, ksize=3)\n    gy = cv2.Sobel(gray, cv2.CV_32F, 0, 1, ksize=3)\n    anisotropy = max(np.mean(np.abs(gx)), np.mean(np.abs(gy))) / (min(np.mean(np.abs(gx)), np.mean(np.abs(gy))) + 1e-6)\n    return float(density * 0.7 + np.tanh(anisotropy - 1.0) * 0.3)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-13T11:16:05.706534Z","iopub.execute_input":"2026-01-13T11:16:05.706834Z","iopub.status.idle":"2026-01-13T11:16:05.721533Z","shell.execute_reply.started":"2026-01-13T11:16:05.706815Z","shell.execute_reply":"2026-01-13T11:16:05.720835Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%writefile stage0.py\nimport sys\nimport os\nimport cv2\nimport torch\nimport numpy as np\nfrom tqdm import tqdm\nimport json\nfrom constant import *\nfrom engine_utils import build_classifier, preprocess_by_source\n\n# إضافة المسار للمكتبات الخارجية\nsys.path.append(str(HENGCK_DIR))\nfrom stage0_model import Net as Stage0Net\nfrom stage0_common import *\n\n# دالة مساعدة لنقل البيانات للجهاز (GPU)\ndef to_device(batch, device):\n    if isinstance(batch, dict):\n        return {k: v.to(device).float() if torch.is_tensor(v) else v for k, v in batch.items()}\n    return batch.to(device).float()\n\n# تحميل النماذج\ncls_model = build_classifier(W_CLASSIFIER)\ns0_net = Stage0Net(pretrained=False)\ns0_net = load_net(s0_net, str(W_STAGE0))\ns0_net.to(DEVICE).eval()\n\nprint(\"Running Stage 0 & Classification...\")\nfor sample_id in tqdm(sample_ids):\n    img_path = BASE_DIR / \"test\" / f\"{sample_id}.png\"\n    img_bgr = cv2.imread(str(img_path))\n    if img_bgr is None: continue\n    \n    # 1. التنبؤ بالمصدر (Classification)\n    img_cls = cv2.resize(cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB), (256, 256))\n    img_cls = (img_cls.astype(np.float32) / 255.0 - [0.485, 0.456, 0.406]) / [0.229, 0.224, 0.225]\n    x = torch.from_numpy(img_cls.astype(np.float32)).permute(2, 0, 1).unsqueeze(0).to(DEVICE)\n    \n    with torch.no_grad():\n        output_cls = cls_model(x)\n        pred_src = f\"{int(torch.argmax(output_cls).item()) + 1:04d}\"\n    \n    # 2. تحسين الصورة بناءً على المصدر\n    img_pp = preprocess_by_source(img_bgr, pred_src)\n    img_rgb = cv2.cvtColor(img_pp, cv2.COLOR_BGR2RGB)\n    \n    # 3. تشغيل المرحلة 0 (تصحيح الدوران)\n    batch = image_to_batch(img_rgb)\n    batch = to_device(batch, DEVICE) # الحل الصحيح لنقل القاموس للـ GPU\n    \n    with torch.no_grad():\n        output = s0_net(batch)\n    \n    try:\n        rotated, keypoint = output_to_predict(img_rgb, batch, output)\n        normalised, _, _ = normalise_by_homography(rotated, keypoint)\n        # حفظ الصورة المصححة\n        cv2.imwrite(str(DIRS[\"stage0\"] / f\"{sample_id}.png\"), cv2.cvtColor(normalised, cv2.COLOR_RGB2BGR))\n    except Exception as e:\n        # في حالة فشل الـ Homography، احفظ الصورة الأصلية المنظفة\n        cv2.imwrite(str(DIRS[\"stage0\"] / f\"{sample_id}.png\"), cv2.cvtColor(img_rgb, cv2.COLOR_RGB2BGR))\n\n    with open(DIRS[\"logs\"] / f\"{sample_id}_src.json\", 'w') as f:\n        json.dump({\"source\": pred_src}, f)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-13T11:16:05.723210Z","iopub.execute_input":"2026-01-13T11:16:05.723462Z","iopub.status.idle":"2026-01-13T11:16:05.736298Z","shell.execute_reply.started":"2026-01-13T11:16:05.723447Z","shell.execute_reply":"2026-01-13T11:16:05.735729Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%writefile stage1.py\nimport sys\nimport cv2\nimport torch\nimport numpy as np\nfrom tqdm import tqdm\nfrom shutil import copyfile\nfrom constant import *\n\nsys.path.append(str(HENGCK_DIR))\nfrom stage1_model import Net as Stage1Net\nfrom stage1_common import *\n\ns1_net = Stage1Net(pretrained=False)\ns1_net = load_net(s1_net, str(W_STAGE1))\ns1_net.to(DEVICE).eval()\n\nprint(\"Running Stage 1 (Smart Rectification)...\")\nfor sample_id in tqdm(sample_ids):\n    path_s0 = DIRS[\"stage0\"] / f\"{sample_id}.png\"\n    img = cv2.imread(str(path_s0))\n    if img is None: \n        # إذا فشلت المرحلة 0، نحاول استخدام الأصل كحل أخير\n        img = cv2.imread(str(BASE_DIR / \"test\" / f\"{sample_id}.png\"))\n        if img is None: continue\n\n    img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n    batch = {'image': torch.from_numpy(np.ascontiguousarray(img_rgb.transpose(2, 0, 1))).unsqueeze(0).to(DEVICE).float()}\n    \n    try:\n        with torch.no_grad():\n            output = s1_net(batch)\n        gridpoint_xy, _ = output_to_predict(img_rgb, batch, output)\n        rectified = rectify_image(img_rgb, gridpoint_xy)\n        cv2.imwrite(str(DIRS[\"stage1\"] / f\"{sample_id}.png\"), cv2.cvtColor(rectified, cv2.COLOR_RGB2BGR))\n    except Exception as e:\n        cv2.imwrite(str(DIRS[\"stage1\"] / f\"{sample_id}.png\"), cv2.cvtColor(img_rgb, cv2.COLOR_RGB2BGR))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-13T11:16:05.736997Z","iopub.execute_input":"2026-01-13T11:16:05.737220Z","iopub.status.idle":"2026-01-13T11:16:05.751591Z","shell.execute_reply.started":"2026-01-13T11:16:05.737199Z","shell.execute_reply":"2026-01-13T11:16:05.750851Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%writefile stage2.py\nimport sys\nimport torch\nimport torch.nn as nn\nimport torchvision.transforms as T\nimport cv2\nimport numpy as np\nfrom tqdm import tqdm\nimport timm\nimport random\nfrom constant import *\n\nsys.path.append(str(HENGCK_DIR))\nfrom stage2_model import *\nfrom stage2_common import *\nfrom scipy.signal import savgol_filter\n\n# --- تعريف الموديل (كما هو في Code B) ---\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    def forward(self, x):\n        e = encode_with_resnet(self.encoder, x)\n        last, _ = self.decoder(feature=e[-1], skip=e[:-1][::-1] + [None])\n        return self.pixel(last)\n\ndef series_to_dict(s_4row):\n    names = [['I', 'aVR', 'V1', 'V4'], ['II', 'aVL', 'V2', 'V5'], ['III', 'aVF', 'V3', 'V6']]\n    d = {}\n    for i in range(3):\n        splits = np.array_split(s_4row[i], 4)\n        for name, data in zip(names[i], splits):\n            d[name] = data\n    d['II_Long'] = s_4row[3]\n    return d\n\ndef dw_dynamic_medical(series_dict):\n    # (نفس دالة Code B)\n    if all(k in series_dict for k in ['I', 'II', 'III']):\n        L1, L2, L3 = series_dict['I'], series_dict['II'], series_dict['III']\n        error = L2 - (L1 + L3)\n        n1 = np.std(np.diff(L1, n=2)) + 1e-6\n        n2 = np.std(np.diff(L2, n=2)) + 1e-6\n        n3 = np.std(np.diff(L3, n=2)) + 1e-6\n        total = n1 + n2 + n3\n        series_dict['I'] += (n1/total) * error\n        series_dict['III'] += (n3/total) * error\n        series_dict['II'] -= (n2/total) * error\n    return series_dict\n\n# إعداد الموديل\ns2_net = Net3().to(DEVICE).eval()\ns2_net.load_state_dict(torch.load(W_STAGE2))\n\nresize = T.Resize((1696, 4352))\nmv_to_pixel = 78.8\nzero_points = [703.5, 987.5, 1271.5, 1531.5]\n\n# ✅ اختيار عينات عشوائية لحفظ الـ Heatmap الخاصة بها (لغرض الـ Visual QA)\nQA_SAMPLES = random.sample(sample_ids, min(len(sample_ids), 5)) \n\nprint(\"Running Stage 2 with QA capability...\")\nfor sample_id in tqdm(sample_ids):\n    img_path = DIRS[\"stage1\"] / f\"{sample_id}.png\"\n    if not img_path.exists(): continue\n    \n    img = cv2.imread(str(img_path))\n    if img is None: continue\n    \n    img_rgb = cv2.cvtColor(img[:1696, :2176], cv2.COLOR_BGR2RGB) / 255.0\n    batch = resize(torch.from_numpy(np.ascontiguousarray(img_rgb.transpose(2, 0, 1))).unsqueeze(0)).to(DEVICE).float()\n    \n    with torch.no_grad():\n        # الحصول على الـ Raw Output\n        raw_output = s2_net(batch)\n        pixel_output = torch.sigmoid(raw_output).cpu().numpy()[0]\n    \n    # ✅ حفظ الـ Heatmap للعينات المختارة فقط\n    if sample_id in QA_SAMPLES:\n        np.save(DIRS[\"qa_vis\"] / f\"{sample_id}_heatmap.npy\", pixel_output.astype(np.float16))\n\n    # استكمال المعالجة لاستخراج الإشارة\n    lead_ii_meta = test_df[(test_df['id']==sample_id) & (test_df['lead']=='II')]\n    if len(lead_ii_meta) == 0: continue\n    length = lead_ii_meta.iloc[0].number_of_rows\n    \n    px_series = pixel_to_series(pixel_output[..., 235:4161], zero_points, length)\n    dyn_zero = np.median(px_series, axis=1).reshape(4, 1)\n    series = (dyn_zero - px_series) / mv_to_pixel\n    \n    for i in range(4): series[i] = savgol_filter(series[i], 7, 2)\n    s_dict = dw_dynamic_medical(series_to_dict(series))\n    \n    final_dict = {k: v - np.median(v) for k, v in s_dict.items()}\n    np.save(DIRS[\"stage2\"] / f\"{sample_id}.npy\", final_dict)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-13T11:16:05.752419Z","iopub.execute_input":"2026-01-13T11:16:05.752670Z","iopub.status.idle":"2026-01-13T11:16:05.764446Z","shell.execute_reply.started":"2026-01-13T11:16:05.752653Z","shell.execute_reply":"2026-01-13T11:16:05.763703Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%writefile run_visual_qa.py\nimport cv2\nimport numpy as np\nimport matplotlib.pyplot as plt\nfrom pathlib import Path\nfrom constant import *\nfrom qa_utils import plot_stage_gallery, plot_signals_detailed\n\nprint(\"--- Running Visual QA Analysis (Like Code A) ---\")\n\n# البحث عن الملفات التي تم حفظ الـ Heatmap لها\nqa_files = list(DIRS[\"qa_vis\"].glob(\"*_heatmap.npy\"))\n\nif not qa_files:\n    print(\"⚠️ No QA heatmaps found. Make sure Stage 2 processed the QA samples.\")\nelse:\n    for f in qa_files:\n        sample_id = f.stem.replace(\"_heatmap\", \"\")\n        print(f\"Generating Report for: {sample_id}\")\n        \n        # 1. تحميل الصور والمخرجات\n        # Original\n        img_orig = cv2.imread(str(BASE_DIR / \"test\" / f\"{sample_id}.png\"))\n        \n        # Stage 0\n        img_s0 = cv2.imread(str(DIRS[\"stage0\"] / f\"{sample_id}.png\"))\n        \n        # Stage 1\n        img_s1 = cv2.imread(str(DIRS[\"stage1\"] / f\"{sample_id}.png\"))\n        \n        # Heatmap\n        heatmap = np.load(f)\n        \n        # Signals\n        series_path = DIRS[\"stage2\"] / f\"{sample_id}.npy\"\n        if series_path.exists():\n            d_series = np.load(series_path, allow_pickle=True).item()\n        else:\n            d_series = {}\n\n        # 2. رسم المعرض (Gallery)\n        plot_stage_gallery(sample_id, img_orig, img_s0, img_s1, heatmap, d_series)\n        \n        # 3. رسم الإشارات التفصيلية (12 Leads)\n        plot_signals_detailed(sample_id, d_series)\n\nprint(\"✅ Visual QA Complete. Images saved in 'qa_visuals' folder.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-13T11:16:05.765329Z","iopub.execute_input":"2026-01-13T11:16:05.765592Z","iopub.status.idle":"2026-01-13T11:16:05.777776Z","shell.execute_reply.started":"2026-01-13T11:16:05.765567Z","shell.execute_reply":"2026-01-13T11:16:05.776973Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%writefile submission.py\nfrom constant import *\nimport numpy as np\nimport pandas as pd\nfrom tqdm import tqdm\n\nres = []\nprint(\"Generating Final Submission...\")\nfor sample_id in tqdm(sample_ids):\n    path_npy = DIRS[\"stage2\"] / f\"{sample_id}.npy\"\n    if not path_npy.exists(): continue\n    \n    d_series = np.load(path_npy, allow_pickle=True).item()\n    rows = test_df[test_df['id'] == sample_id]\n    \n    for _, r in rows.iterrows():\n        s = d_series.get(r.lead if r.lead != 'II' else 'II_Long', np.zeros(r.number_of_rows))\n        if len(s) != r.number_of_rows:\n            s = np.interp(np.linspace(0, 1, r.number_of_rows), np.linspace(0, 1, len(s)), s)\n        \n        row_ids = [f\"{sample_id}_{i}_{r.lead}\" for i in range(r.number_of_rows)]\n        res.append(pd.DataFrame({'id': row_ids, 'value': s}))\n\nif res:\n    pd.concat(res).to_csv(\"submission.csv\", index=False)\n    print(\"✅ Submission.csv created successfully!\")\nelse:\n    print(\"❌ Critical Error: No data processed!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-13T11:16:05.778654Z","iopub.execute_input":"2026-01-13T11:16:05.778896Z","iopub.status.idle":"2026-01-13T11:16:05.789615Z","shell.execute_reply.started":"2026-01-13T11:16:05.778861Z","shell.execute_reply":"2026-01-13T11:16:05.788899Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!python stage0.py\n!python stage1.py\n!python stage2.py \n!python run_visual_qa.py\n!python submission.py","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-13T11:16:05.790423Z","iopub.execute_input":"2026-01-13T11:16:05.790670Z","iopub.status.idle":"2026-01-13T11:17:08.397844Z","shell.execute_reply.started":"2026-01-13T11:16:05.790654Z","shell.execute_reply":"2026-01-13T11:17:08.397116Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nimport numpy as np\nfrom pathlib import Path\nimport random\n\n# دالة لحساب جودة الإشارة (Pseudo-SNR)\ndef calculate_quality_db(signal):\n    # نستخدم نعومة الإشارة كمقياس للجودة\n    # Signal Power / Noise Power (approx by diff)\n    sig_std = np.std(signal)\n    noise_std = np.std(np.diff(signal)) + 1e-6\n    snr = sig_std / noise_std\n    return 20 * np.log10(snr)\n\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    sample_id = sample_file.stem\n    \n    # تحميل البيانات (مع السماح بالـ pickle)\n    data = np.load(sample_file, allow_pickle=True).item()\n    \n    # --- إعادة تجميع الصفوف (Reconstructing Rows) ---\n    # القاموس يحتوي على القنوات منفصلة، سنقوم بدمجها لنحاكي شكل \"الشريط\" في الصورة\n    try:\n        row1 = np.concatenate([data['I'], data['aVR'], data['V1'], data['V4']])\n        row2 = np.concatenate([data['II'], data['aVL'], data['V2'], data['V5']])\n        row3 = np.concatenate([data['III'], data['aVF'], data['V3'], data['V6']])\n        row4 = data['II_Long'] # القناة الطويلة\n        \n        rows_data = [row1, row2, row3, row4]\n    except KeyError as e:\n        print(f\"❌ Error: Missing key {e} in the data file.\")\n        rows_data = []\n\n    if rows_data:\n        # إعدادات الرسم\n        fig, axes = plt.subplots(4, 1, figsize=(20, 14), sharex=True)\n        colors = ['#1f77b4', '#2ca02c', '#9467bd', '#d62728'] # أزرق، أخضر، بنفسجي، أحمر\n        row_names = [\n            \"Row 1: Leads (I, aVR, V1, V4)\", \n            \"Row 2: Leads (II, aVL, V2, V5)\", \n            \"Row 3: Leads (III, aVF, V3, V6)\", \n            \"Row 4: Long Lead II (Reference)\"\n        ]\n        \n        # حساب الجودة لكل صف لتحديد الأفضل والأسوأ\n        qualities = [calculate_quality_db(r) for r in rows_data]\n        max_q = max(qualities)\n        min_q = min(qualities)\n        avg_q = np.mean(qualities)\n        \n        # العنوان الرئيسي (Global Stats)\n        plt.suptitle(f\"Stage 2 Signal Quality Analysis: {sample_id}\\n\"\n                     f\"Global Stats -> Max Quality: {max_q:.2f} dB | Min Quality: {min_q:.2f} dB | Avg Quality: {avg_q:.2f} dB\", \n                     fontsize=16, fontweight='bold', y=0.98)\n\n        for i in range(4):\n            ax = axes[i]\n            signal = rows_data[i]\n            quality = qualities[i]\n            color = colors[i]\n            \n            # تحديد العلامة (Best/Worst)\n            tag = \"\"\n            if quality == max_q: tag = \"⭐ (BEST)\"\n            elif quality == min_q: tag = \"⚠️ (WORST)\"\n            \n            # رسم الإشارة\n            ax.plot(signal, color=color, linewidth=1.2)\n            \n            # تنسيق العنوان الفرعي بنفس نمط الصورة\n            title_text = f\"{row_names[i]} | {quality:.2f} dB {tag} | Range: [{signal.min():.2f}, {signal.max():.2f}] mV\"\n            \n            # تلوين العنوان ليتناسب مع الإشارة (اختياري لجمال الشكل) أو تركه موحد\n            ax.set_title(title_text, fontsize=12, color=color if i==3 else 'darkblue', fontweight='bold')\n            \n            ax.grid(True, alpha=0.3, linestyle='--')\n            ax.set_ylabel(\"Voltage (mV)\")\n            \n            # خط الصفر\n            ax.axhline(0, color='gray', linestyle=':', alpha=0.7)\n\n        plt.xlabel(\"Time (Samples)\", fontsize=12)\n        plt.tight_layout(rect=[0, 0, 1, 0.94]) # ترك مسافة للعنوان الرئيسي\n        plt.show()\n        \n        print(f\"✅ Visualization generated for {sample_id}\")\n    \nelse:\n    print(\"❌ No output files found!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-13T11:17:08.398929Z","iopub.execute_input":"2026-01-13T11:17:08.399132Z","iopub.status.idle":"2026-01-13T11:17:09.481297Z","shell.execute_reply.started":"2026-01-13T11:17:08.399111Z","shell.execute_reply":"2026-01-13T11:17:09.480513Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nfrom pathlib import Path\nfrom tqdm.auto import tqdm\nimport gc\n\n# 1. تعريف المسارات الضرورية يدوياً لحل مشكلة NameError\nBASE_DIR = Path(\"/kaggle/input/physionet-ecg-image-digitization\")\nSTAGE2_DIR = Path(\"/kaggle/working/stage2\")\n\n# 2. تحميل ملف الـ Test Metadata\n# (نستخدم نفس الاسم في الكود الخاص بك valid_df لكي لا نغير المنطق)\nvalid_df = pd.read_csv(BASE_DIR / \"test.csv\")\nvalid_df['id'] = valid_df['id'].astype(str)\n\nsubmit_df = []\ngb = valid_df.groupby('id')\n\nprint(\"🚀 Generating submission file from Dictionary format...\")\n\nfor rec_idx, (sample_id, df) in enumerate(tqdm(gb)):\n    try:\n        path = STAGE2_DIR / f'{sample_id}.npy'\n        if not path.exists():\n            continue\n            \n        # ✅ التعديل الجوهري: تحميل البيانات كقاموس مباشرة\n        # (حذفنا دالة series_dict لأن البيانات أصبحت جاهزة)\n        series_by_lead = np.load(path, allow_pickle=True).item()\n\n        for _, d in df.iterrows():\n            # البحث عن القناة، وإذا كانت Lead II نستخدم الاسم الطويل المخصص لها\n            lead_key = d.lead\n            if lead_key == 'II' and 'II_Long' in series_by_lead:\n                lead_key = 'II_Long'\n            \n            s = series_by_lead.get(lead_key, np.zeros(d.number_of_rows))\n            \n            # ضبط الطول (Interpolation) ليتطابق مع المطلوب في المسابقة\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        print(f\"Error in {sample_id}: {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":"2026-01-13T11:17:09.482027Z","iopub.execute_input":"2026-01-13T11:17:09.482283Z","iopub.status.idle":"2026-01-13T11:17:10.502231Z","shell.execute_reply.started":"2026-01-13T11:17:09.482256Z","shell.execute_reply":"2026-01-13T11:17:10.501459Z"}},"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":"2026-01-13T11:17:10.502938Z","iopub.execute_input":"2026-01-13T11:17:10.503138Z","iopub.status.idle":"2026-01-13T11:17:10.565897Z","shell.execute_reply.started":"2026-01-13T11:17:10.503122Z","shell.execute_reply":"2026-01-13T11:17:10.565293Z"}},"outputs":[],"execution_count":null}]}