convitom Claude Opus 4.7 commited on
Commit ·
21fa652
1
Parent(s): 47a30f7
chore(data): WIP EDA notebooks + labeler comparison tooling
Browse filesPre-existing in-progress work (not part of the resize tooling): expanded
EDA notebooks, eval_labelers tweak, new img_stat.py and
labeler_comparison.csv. Committed to clean the working tree.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- data/build_subset_colab.ipynb +323 -33
- data/build_subset_local.ipynb +0 -0
- data/eda_full.ipynb +0 -0
- data/eda_p18.ipynb +176 -35
- data/eda_reports.ipynb +0 -0
- data/img_stat.py +82 -0
- dev/eval_labelers.py +5 -5
- dev/labeler_comparison.csv +14 -0
data/build_subset_colab.ipynb
CHANGED
|
@@ -3,7 +3,27 @@
|
|
| 3 |
{
|
| 4 |
"cell_type": "markdown",
|
| 5 |
"metadata": {},
|
| 6 |
-
"source":
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
},
|
| 8 |
{
|
| 9 |
"cell_type": "markdown",
|
|
@@ -16,6 +36,7 @@
|
|
| 16 |
"cell_type": "code",
|
| 17 |
"execution_count": null,
|
| 18 |
"metadata": {},
|
|
|
|
| 19 |
"source": [
|
| 20 |
"import sys, os\n",
|
| 21 |
"IN_COLAB = \"google.colab\" in sys.modules\n",
|
|
@@ -24,15 +45,63 @@
|
|
| 24 |
" drive.mount(\"/content/drive\")\n",
|
| 25 |
" !pip -q install huggingface_hub tqdm\n",
|
| 26 |
"print(\"IN_COLAB =\", IN_COLAB)"
|
| 27 |
-
]
|
| 28 |
-
"outputs": []
|
| 29 |
},
|
| 30 |
{
|
| 31 |
"cell_type": "code",
|
| 32 |
"execution_count": null,
|
|
|
|
| 33 |
"metadata": {},
|
| 34 |
-
"
|
| 35 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
},
|
| 37 |
{
|
| 38 |
"cell_type": "markdown",
|
|
@@ -45,6 +114,7 @@
|
|
| 45 |
"cell_type": "code",
|
| 46 |
"execution_count": null,
|
| 47 |
"metadata": {},
|
|
|
|
| 48 |
"source": [
|
| 49 |
"BUNDLE_DIR.mkdir(parents=True, exist_ok=True)\n",
|
| 50 |
"with zipfile.ZipFile(BUNDLE_ZIP) as z:\n",
|
|
@@ -56,46 +126,196 @@
|
|
| 56 |
" print(f\"{sp}: {len(r):,} studies\")\n",
|
| 57 |
"all_rows = manifests[\"train\"]+manifests[\"val\"]+manifests[\"test\"]\n",
|
| 58 |
"print(\"TOTAL ảnh cần tải:\", len(all_rows))"
|
| 59 |
-
]
|
| 60 |
-
"outputs": []
|
| 61 |
},
|
| 62 |
{
|
| 63 |
"cell_type": "markdown",
|
| 64 |
"id": "918e0272",
|
| 65 |
-
"source": "## 2. Tải ảnh JPG từ PhysioNet (qua `wget`) → thẳng vào package trên Drive\n\nPhysioNet từ chối `requests` basic-auth nhưng chấp nhận `wget` → dùng `wget` per-file, 12 luồng song song.\n\nĐứt giữa chừng → reconnect → chạy lại cell này, file đã tải (>10KB) được bỏ qua.",
|
| 66 |
"metadata": {},
|
| 67 |
-
"
|
| 68 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
},
|
| 70 |
{
|
| 71 |
"cell_type": "code",
|
| 72 |
-
"metadata": {},
|
| 73 |
-
"source": "import subprocess, threading, shutil\nfrom pathlib import Path as _P\nfrom concurrent.futures import ThreadPoolExecutor, as_completed\nfrom collections import Counter\nfrom tqdm.auto import tqdm\n\n# Log per-file ghi vào /content (SSD, ~µs, không bóp tải).\n# Cứ mỗi CHECKPOINT_EVERY ảnh -> copy log sang Drive (1 thao tác, rẻ).\n# Session mới: đọc log Drive (tức thì) thay vì os.walk (chậm).\nDL_LOG_LOCAL = _P(\"/content/downloaded.txt\")\nDL_LOG_DRIVE = OUT / \"downloaded.txt\"\nCHECKPOINT_EVERY = 500\n\n_log_lk = threading.Lock()\n_log_cnt = 0\n\ndef mark_done(relpath):\n global _log_cnt\n with _log_lk:\n with open(DL_LOG_LOCAL, \"a\") as f:\n f.write(relpath + \"\\n\")\n _log_cnt += 1\n if _log_cnt % CHECKPOINT_EVERY == 0:\n try:\n shutil.copy(DL_LOG_LOCAL, DL_LOG_DRIVE) # checkpoint -> Drive\n except Exception as e:\n print(\" [warn] copy log -> Drive lỗi:\", e)\n\ndef flush_log_to_drive():\n with _log_lk:\n if DL_LOG_LOCAL.exists():\n shutil.copy(DL_LOG_LOCAL, DL_LOG_DRIVE)\n\n# PhysioNet chặn requests-basic-auth nhưng OK với wget. Cell này CHỈ định nghĩa dl().\ndef dl(row):\n rp = row[\"image_relpath\"]\n out = OUT / rp # files/pXX/pSUBJ/sSTUDY/<dicom>.jpg\n if out.exists() and out.stat().st_size > 10_000:\n mark_done(rp)\n return \"skip\"\n out.parent.mkdir(parents=True, exist_ok=True)\n tmp = out.with_suffix(\".part\")\n cmd = [\"wget\", \"-q\", \"-T\", \"60\", \"-t\", \"3\", \"-O\", str(tmp),\n \"--user\", PHYSIONET_USER, \"--password\", PHYSIONET_PASS, row[\"jpg_url\"]]\n rc = subprocess.run(cmd).returncode\n if rc == 0 and tmp.exists() and tmp.stat().st_size > 10_000:\n tmp.replace(out)\n mark_done(rp)\n return \"ok\"\n if tmp.exists():\n tmp.unlink()\n return f\"fail(rc={rc})\"\n\nprint(f\"dl() sẵn sàng. Log local={DL_LOG_LOCAL}, checkpoint -> {DL_LOG_DRIVE} mỗi {CHECKPOINT_EVERY} ảnh.\")",
|
| 74 |
"execution_count": null,
|
| 75 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
},
|
| 77 |
{
|
| 78 |
"cell_type": "code",
|
|
|
|
| 79 |
"id": "a78d23a9",
|
| 80 |
-
"source": "# ── TEST NHANH + ĐO TỐC ĐỘ trước khi tải 50k ─────────────────────────────────\n# Lần đầu: tải thật 10 ảnh để đo tốc độ + xác nhận auth.\n# Khi RESUME (Run all lại): ảnh đã có -> bỏ qua, không phí thời gian.\nimport time as _t\nsample = all_rows[:10]\nneed = [r for r in sample\n if not ((OUT/r[\"image_relpath\"]).exists()\n and (OUT/r[\"image_relpath\"]).stat().st_size > 10_000)]\n\nif not need:\n print(f\"{len(sample)}/{len(sample)} ảnh test đã có sẵn (resume) — bỏ qua test.\")\n print(\"✓ Chạy cell TẢI 50k bên dưới.\")\nelse:\n print(f\"Test tải {len(need)} ảnh (đo tốc độ)...\")\n t0 = _t.time(); tot = 0; ok = 0\n for row in need:\n st = dl(row)\n p = OUT/row[\"image_relpath\"]\n if p.exists() and p.stat().st_size > 10_000:\n tot += p.stat().st_size; ok += 1\n print(f\" {row['study_name']:>10s} -> {st}\")\n dt = _t.time() - t0\n kbs = (tot/1024)/dt if dt > 0 else 0\n avg = (tot/1e6)/ok if ok else 0\n print(f\"\\n{ok}/{len(need)} OK | {tot/1e6:.1f} MB / {dt:.1f}s \"\n f\"= {kbs:,.0f} KB/s ({kbs/1024:.2f} MB/s) [1 luồng]\")\n if ok:\n n = len(all_rows)\n h = (n*avg)/(kbs/1024)/3600\n print(f\"Ảnh ~{avg:.2f} MB | {n:,} ảnh ≈ {n*avg/1000:.0f} GB\")\n print(f\"ETA: ~{h:.1f}h (1 luồng) → ~{h/12*1.6:.1f}-{h/12*3:.1f}h (12 luồng)\")\n print(\"\\n\" + (\"✓ OK — chạy cell TẢI 50k bên dưới.\" if ok == len(need)\n else \"✗ Lỗi — kiểm tra user/pass, ĐỪNG chạy tải 50k.\"))",
|
| 81 |
"metadata": {},
|
| 82 |
-
"
|
| 83 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
},
|
| 85 |
{
|
| 86 |
"cell_type": "code",
|
|
|
|
| 87 |
"id": "0f8b3dec",
|
| 88 |
-
"source": "# ── TẢI ẢNH (chỉ chạy khi cell TEST báo ✓ OK) ────────────────────────────────\n# Resume ưu tiên: log local -> log Drive (copy về local) -> os.walk (fallback).\nif DL_LOG_LOCAL.exists():\n done_set = set(l.strip() for l in open(DL_LOG_LOCAL) if l.strip())\n print(f\"[log local] {len(done_set):,} ảnh đã tải (tức thì).\")\nelif DL_LOG_DRIVE.exists():\n shutil.copy(DL_LOG_DRIVE, DL_LOG_LOCAL) # session mới: lấy checkpoint từ Drive\n done_set = set(l.strip() for l in open(DL_LOG_LOCAL) if l.strip())\n print(f\"[log Drive] session mới, đọc checkpoint: {len(done_set):,} ảnh (tức thì).\")\nelse:\n print(\"Chưa có log -> quét Drive 1 lần để dựng log...\")\n done_set = set()\n froot = OUT / \"files\"\n if froot.exists():\n for dp, _, fns in os.walk(froot):\n for fn in fns:\n if fn.endswith(\".jpg\"):\n done_set.add(os.path.relpath(os.path.join(dp, fn), OUT).replace(\"\\\\\", \"/\"))\n with open(DL_LOG_LOCAL, \"w\") as f:\n f.write(\"\\n\".join(sorted(done_set)) + (\"\\n\" if done_set else \"\"))\n if done_set:\n shutil.copy(DL_LOG_LOCAL, DL_LOG_DRIVE)\n print(f\"Dựng log xong: {len(done_set):,} ảnh.\")\n\ntodo = [r for r in all_rows if r[\"image_relpath\"] not in done_set]\nn_done = len(all_rows) - len(todo)\nprint(f\"Đã có : {n_done:,} / {len(all_rows):,} ({n_done/len(all_rows)*100:.1f}%)\")\nprint(f\"Cần tải: {len(todo):,}\")\n\nif not todo:\n print(\"\\n✓ Đã tải đủ toàn bộ.\")\n flush_log_to_drive()\nelse:\n res = Counter({\"skip\": n_done})\n try:\n with ThreadPoolExecutor(max_workers=12) as ex:\n futs = [ex.submit(dl, r) for r in todo]\n for f in tqdm(as_completed(futs), total=len(todo), desc=\"downloading\"):\n res[f.result().split(\"(\")[0]] += 1\n finally:\n flush_log_to_drive() # luôn lưu log Drive khi kết thúc/lỗi\n print(dict(res))\n if any(k.startswith(\"fail\") for k in res):\n print(\"Còn fail -> chạy lại cell này (chỉ tải phần thiếu).\")",
|
| 89 |
"metadata": {},
|
| 90 |
-
"
|
| 91 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
},
|
| 93 |
{
|
| 94 |
"cell_type": "code",
|
| 95 |
"execution_count": null,
|
| 96 |
"metadata": {},
|
| 97 |
-
"
|
| 98 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
},
|
| 100 |
{
|
| 101 |
"cell_type": "markdown",
|
|
@@ -108,27 +328,76 @@
|
|
| 108 |
"cell_type": "code",
|
| 109 |
"execution_count": null,
|
| 110 |
"metadata": {},
|
| 111 |
-
"
|
| 112 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 113 |
},
|
| 114 |
{
|
| 115 |
"cell_type": "code",
|
| 116 |
"execution_count": null,
|
| 117 |
"metadata": {},
|
| 118 |
-
"
|
| 119 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
},
|
| 121 |
{
|
| 122 |
"cell_type": "markdown",
|
| 123 |
"metadata": {},
|
| 124 |
-
"source":
|
|
|
|
|
|
|
|
|
|
|
|
|
| 125 |
},
|
| 126 |
{
|
| 127 |
"cell_type": "code",
|
| 128 |
"execution_count": null,
|
| 129 |
"metadata": {},
|
| 130 |
-
"
|
| 131 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
},
|
| 133 |
{
|
| 134 |
"cell_type": "markdown",
|
|
@@ -143,15 +412,36 @@
|
|
| 143 |
"cell_type": "code",
|
| 144 |
"execution_count": null,
|
| 145 |
"metadata": {},
|
| 146 |
-
"
|
| 147 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 148 |
},
|
| 149 |
{
|
| 150 |
"cell_type": "code",
|
| 151 |
"execution_count": null,
|
| 152 |
"metadata": {},
|
| 153 |
-
"
|
| 154 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 155 |
}
|
| 156 |
],
|
| 157 |
"metadata": {
|
|
@@ -167,4 +457,4 @@
|
|
| 167 |
},
|
| 168 |
"nbformat": 4,
|
| 169 |
"nbformat_minor": 5
|
| 170 |
-
}
|
|
|
|
| 3 |
{
|
| 4 |
"cell_type": "markdown",
|
| 5 |
"metadata": {},
|
| 6 |
+
"source": [
|
| 7 |
+
"# Build Subset — PHASE 2 (Google Colab)\n",
|
| 8 |
+
"\n",
|
| 9 |
+
"Chạy **sau** `build_subset_local.ipynb`. Input = `subset_bundle.zip` đã upload lên Drive.\n",
|
| 10 |
+
"\n",
|
| 11 |
+
"**Việc:**\n",
|
| 12 |
+
"1. Giải nén bundle (manifest + reports + vqa)\n",
|
| 13 |
+
"2. Tải ~50k ảnh JPG từ PhysioNet vào **đúng path gốc** `files/pXX/pSUBJ/sSTUDY/<dicom>.jpg` (resume — đứt thì chạy lại tiếp)\n",
|
| 14 |
+
"3. Copy reports (giữ path gốc) + vqa.json vào package\n",
|
| 15 |
+
"4. Push Hugging Face\n",
|
| 16 |
+
"\n",
|
| 17 |
+
"**Cấu trúc kết quả** (`MIMIC-CXR_processed/`):\n",
|
| 18 |
+
"```\n",
|
| 19 |
+
"files/pXX/pSUBJ/sSTUDY/<dicom>.jpg ← ảnh (giữ tên gốc)\n",
|
| 20 |
+
"files/pXX/pSUBJ/sSTUDY.txt ← report\n",
|
| 21 |
+
"manifest_{train,val,test}.json/.csv ← split + nhãn (đối chiếu khi tải đứt)\n",
|
| 22 |
+
"vqa.json / vqa_val.json / vqa_test.json\n",
|
| 23 |
+
"```\n",
|
| 24 |
+
"\n",
|
| 25 |
+
"> Không cần GPU. Ảnh tải thẳng vào Drive → resume an toàn khi Colab ngắt; đối chiếu manifest để biết còn thiếu study/ảnh nào."
|
| 26 |
+
]
|
| 27 |
},
|
| 28 |
{
|
| 29 |
"cell_type": "markdown",
|
|
|
|
| 36 |
"cell_type": "code",
|
| 37 |
"execution_count": null,
|
| 38 |
"metadata": {},
|
| 39 |
+
"outputs": [],
|
| 40 |
"source": [
|
| 41 |
"import sys, os\n",
|
| 42 |
"IN_COLAB = \"google.colab\" in sys.modules\n",
|
|
|
|
| 45 |
" drive.mount(\"/content/drive\")\n",
|
| 46 |
" !pip -q install huggingface_hub tqdm\n",
|
| 47 |
"print(\"IN_COLAB =\", IN_COLAB)"
|
| 48 |
+
]
|
|
|
|
| 49 |
},
|
| 50 |
{
|
| 51 |
"cell_type": "code",
|
| 52 |
"execution_count": null,
|
| 53 |
+
"id": "08b13eef",
|
| 54 |
"metadata": {},
|
| 55 |
+
"outputs": [],
|
| 56 |
+
"source": [
|
| 57 |
+
"from pathlib import Path\n",
|
| 58 |
+
"import os, getpass, zipfile, json, time, shutil\n",
|
| 59 |
+
"\n",
|
| 60 |
+
"DRIVE = Path(\"/content/drive/MyDrive\")\n",
|
| 61 |
+
"BUNDLE_ZIP = DRIVE / \"subset_bundle.zip\"\n",
|
| 62 |
+
"BUNDLE_DIR = Path(\"/content/subset_bundle\")\n",
|
| 63 |
+
"OUT = DRIVE / \"MIMIC-CXR_processed\"\n",
|
| 64 |
+
"\n",
|
| 65 |
+
"HF_REPO_ID = \"hieu3636/cxr-vlm-data\"\n",
|
| 66 |
+
"HF_REPO_TYPE = \"dataset\"\n",
|
| 67 |
+
"HF_PATH_IN_REPO = \"MIMIC-CXR_processed\"\n",
|
| 68 |
+
"\n",
|
| 69 |
+
"# ── CREDENTIALS ──────────────────────────────────────────────────────────────\n",
|
| 70 |
+
"# Cách 1 (KHUYẾN NGHỊ, an toàn + không hỏi lại): Colab Secrets.\n",
|
| 71 |
+
"# Bấm icon CHÌA KHOÁ 🔑 cột trái -> Add new secret, tạo 3 secret:\n",
|
| 72 |
+
"# PHYSIONET_USER , PHYSIONET_PASS , HF_TOKEN (bật \"Notebook access\")\n",
|
| 73 |
+
"# -> set 1 lần, dùng mãi mọi session, KHÔNG nằm trong code.\n",
|
| 74 |
+
"#\n",
|
| 75 |
+
"# Cách 2 (bạn muốn): gõ thẳng vào đây. Nhanh nhưng LỘ nếu push/chia sẻ notebook.\n",
|
| 76 |
+
"# -> điền vào 3 dòng _HARDCODE_* bên dưới.\n",
|
| 77 |
+
"#\n",
|
| 78 |
+
"# Cách 3: để trống tất cả -> nó hỏi nhập tay khi chạy (như cũ).\n",
|
| 79 |
+
"\n",
|
| 80 |
+
"_HARDCODE_USER = \"\" # <- điền PhysioNet username (vd \"convitom\")\n",
|
| 81 |
+
"_HARDCODE_PASS = \"\" # <- điền PhysioNet password\n",
|
| 82 |
+
"_HARDCODE_HFTOK = \"\" # <- điền HF write token\n",
|
| 83 |
+
"\n",
|
| 84 |
+
"def _get(name, hard):\n",
|
| 85 |
+
" if hard: # ưu tiên giá trị gõ thẳng\n",
|
| 86 |
+
" return hard\n",
|
| 87 |
+
" try: # rồi tới Colab Secrets\n",
|
| 88 |
+
" from google.colab import userdata\n",
|
| 89 |
+
" v = userdata.get(name)\n",
|
| 90 |
+
" if v:\n",
|
| 91 |
+
" return v\n",
|
| 92 |
+
" except Exception:\n",
|
| 93 |
+
" pass\n",
|
| 94 |
+
" return os.environ.get(name) # rồi tới biến môi trường\n",
|
| 95 |
+
"\n",
|
| 96 |
+
"PHYSIONET_USER = _get(\"PHYSIONET_USER\", _HARDCODE_USER) or input(\"PhysioNet username: \")\n",
|
| 97 |
+
"PHYSIONET_PASS = _get(\"PHYSIONET_PASS\", _HARDCODE_PASS) or getpass.getpass(\"PhysioNet password: \")\n",
|
| 98 |
+
"HF_TOKEN = _get(\"HF_TOKEN\", _HARDCODE_HFTOK) or getpass.getpass(\"HF write token: \")\n",
|
| 99 |
+
"\n",
|
| 100 |
+
"VQA_OUT = {\"train\":\"vqa.json\",\"val\":\"vqa_val.json\",\"test\":\"vqa_test.json\"}\n",
|
| 101 |
+
"OUT.mkdir(parents=True, exist_ok=True)\n",
|
| 102 |
+
"print(\"Credentials OK |\", \"bundle zip exists:\", BUNDLE_ZIP.exists())\n",
|
| 103 |
+
"print(\"⚠️ Nếu gõ thẳng pass/token vào code: ĐỪNG commit/push notebook này lên git/HF.\")"
|
| 104 |
+
]
|
| 105 |
},
|
| 106 |
{
|
| 107 |
"cell_type": "markdown",
|
|
|
|
| 114 |
"cell_type": "code",
|
| 115 |
"execution_count": null,
|
| 116 |
"metadata": {},
|
| 117 |
+
"outputs": [],
|
| 118 |
"source": [
|
| 119 |
"BUNDLE_DIR.mkdir(parents=True, exist_ok=True)\n",
|
| 120 |
"with zipfile.ZipFile(BUNDLE_ZIP) as z:\n",
|
|
|
|
| 126 |
" print(f\"{sp}: {len(r):,} studies\")\n",
|
| 127 |
"all_rows = manifests[\"train\"]+manifests[\"val\"]+manifests[\"test\"]\n",
|
| 128 |
"print(\"TOTAL ảnh cần tải:\", len(all_rows))"
|
| 129 |
+
]
|
|
|
|
| 130 |
},
|
| 131 |
{
|
| 132 |
"cell_type": "markdown",
|
| 133 |
"id": "918e0272",
|
|
|
|
| 134 |
"metadata": {},
|
| 135 |
+
"source": [
|
| 136 |
+
"## 2. Tải ảnh JPG từ PhysioNet (qua `wget`) → thẳng vào package trên Drive\n",
|
| 137 |
+
"\n",
|
| 138 |
+
"PhysioNet từ chối `requests` basic-auth nhưng chấp nhận `wget` → dùng `wget` per-file, 12 luồng song song.\n",
|
| 139 |
+
"\n",
|
| 140 |
+
"Đứt giữa chừng → reconnect → chạy lại cell này, file đã tải (>10KB) được bỏ qua."
|
| 141 |
+
]
|
| 142 |
},
|
| 143 |
{
|
| 144 |
"cell_type": "code",
|
|
|
|
|
|
|
| 145 |
"execution_count": null,
|
| 146 |
+
"metadata": {},
|
| 147 |
+
"outputs": [],
|
| 148 |
+
"source": [
|
| 149 |
+
"import subprocess, threading, shutil\n",
|
| 150 |
+
"from pathlib import Path as _P\n",
|
| 151 |
+
"from concurrent.futures import ThreadPoolExecutor, as_completed\n",
|
| 152 |
+
"from collections import Counter\n",
|
| 153 |
+
"from tqdm.auto import tqdm\n",
|
| 154 |
+
"\n",
|
| 155 |
+
"# Log per-file ghi vào /content (SSD, ~µs, không bóp tải).\n",
|
| 156 |
+
"# Cứ mỗi CHECKPOINT_EVERY ảnh -> copy log sang Drive (1 thao tác, rẻ).\n",
|
| 157 |
+
"# Session mới: đọc log Drive (tức thì) thay vì os.walk (chậm).\n",
|
| 158 |
+
"DL_LOG_LOCAL = _P(\"/content/downloaded.txt\")\n",
|
| 159 |
+
"DL_LOG_DRIVE = OUT / \"downloaded.txt\"\n",
|
| 160 |
+
"CHECKPOINT_EVERY = 500\n",
|
| 161 |
+
"\n",
|
| 162 |
+
"_log_lk = threading.Lock()\n",
|
| 163 |
+
"_log_cnt = 0\n",
|
| 164 |
+
"\n",
|
| 165 |
+
"def mark_done(relpath):\n",
|
| 166 |
+
" global _log_cnt\n",
|
| 167 |
+
" with _log_lk:\n",
|
| 168 |
+
" with open(DL_LOG_LOCAL, \"a\") as f:\n",
|
| 169 |
+
" f.write(relpath + \"\\n\")\n",
|
| 170 |
+
" _log_cnt += 1\n",
|
| 171 |
+
" if _log_cnt % CHECKPOINT_EVERY == 0:\n",
|
| 172 |
+
" try:\n",
|
| 173 |
+
" shutil.copy(DL_LOG_LOCAL, DL_LOG_DRIVE) # checkpoint -> Drive\n",
|
| 174 |
+
" except Exception as e:\n",
|
| 175 |
+
" print(\" [warn] copy log -> Drive lỗi:\", e)\n",
|
| 176 |
+
"\n",
|
| 177 |
+
"def flush_log_to_drive():\n",
|
| 178 |
+
" with _log_lk:\n",
|
| 179 |
+
" if DL_LOG_LOCAL.exists():\n",
|
| 180 |
+
" shutil.copy(DL_LOG_LOCAL, DL_LOG_DRIVE)\n",
|
| 181 |
+
"\n",
|
| 182 |
+
"# PhysioNet chặn requests-basic-auth nhưng OK với wget. Cell này CHỈ định nghĩa dl().\n",
|
| 183 |
+
"def dl(row):\n",
|
| 184 |
+
" rp = row[\"image_relpath\"]\n",
|
| 185 |
+
" out = OUT / rp # files/pXX/pSUBJ/sSTUDY/<dicom>.jpg\n",
|
| 186 |
+
" if out.exists() and out.stat().st_size > 10_000:\n",
|
| 187 |
+
" mark_done(rp)\n",
|
| 188 |
+
" return \"skip\"\n",
|
| 189 |
+
" out.parent.mkdir(parents=True, exist_ok=True)\n",
|
| 190 |
+
" tmp = out.with_suffix(\".part\")\n",
|
| 191 |
+
" cmd = [\"wget\", \"-q\", \"-T\", \"60\", \"-t\", \"3\", \"-O\", str(tmp),\n",
|
| 192 |
+
" \"--user\", PHYSIONET_USER, \"--password\", PHYSIONET_PASS, row[\"jpg_url\"]]\n",
|
| 193 |
+
" rc = subprocess.run(cmd).returncode\n",
|
| 194 |
+
" if rc == 0 and tmp.exists() and tmp.stat().st_size > 10_000:\n",
|
| 195 |
+
" tmp.replace(out)\n",
|
| 196 |
+
" mark_done(rp)\n",
|
| 197 |
+
" return \"ok\"\n",
|
| 198 |
+
" if tmp.exists():\n",
|
| 199 |
+
" tmp.unlink()\n",
|
| 200 |
+
" return f\"fail(rc={rc})\"\n",
|
| 201 |
+
"\n",
|
| 202 |
+
"print(f\"dl() sẵn sàng. Log local={DL_LOG_LOCAL}, checkpoint -> {DL_LOG_DRIVE} mỗi {CHECKPOINT_EVERY} ảnh.\")"
|
| 203 |
+
]
|
| 204 |
},
|
| 205 |
{
|
| 206 |
"cell_type": "code",
|
| 207 |
+
"execution_count": null,
|
| 208 |
"id": "a78d23a9",
|
|
|
|
| 209 |
"metadata": {},
|
| 210 |
+
"outputs": [],
|
| 211 |
+
"source": [
|
| 212 |
+
"# ── TEST NHANH + ĐO TỐC ĐỘ trước khi tải 50k ─────────────────────────────────\n",
|
| 213 |
+
"# Lần đầu: tải thật 10 ảnh để đo tốc độ + xác nhận auth.\n",
|
| 214 |
+
"# Khi RESUME (Run all lại): ảnh đã có -> bỏ qua, không phí thời gian.\n",
|
| 215 |
+
"import time as _t\n",
|
| 216 |
+
"sample = all_rows[:10]\n",
|
| 217 |
+
"need = [r for r in sample\n",
|
| 218 |
+
" if not ((OUT/r[\"image_relpath\"]).exists()\n",
|
| 219 |
+
" and (OUT/r[\"image_relpath\"]).stat().st_size > 10_000)]\n",
|
| 220 |
+
"\n",
|
| 221 |
+
"if not need:\n",
|
| 222 |
+
" print(f\"{len(sample)}/{len(sample)} ảnh test đã có sẵn (resume) — bỏ qua test.\")\n",
|
| 223 |
+
" print(\"✓ Chạy cell TẢI 50k bên dưới.\")\n",
|
| 224 |
+
"else:\n",
|
| 225 |
+
" print(f\"Test tải {len(need)} ảnh (đo tốc độ)...\")\n",
|
| 226 |
+
" t0 = _t.time(); tot = 0; ok = 0\n",
|
| 227 |
+
" for row in need:\n",
|
| 228 |
+
" st = dl(row)\n",
|
| 229 |
+
" p = OUT/row[\"image_relpath\"]\n",
|
| 230 |
+
" if p.exists() and p.stat().st_size > 10_000:\n",
|
| 231 |
+
" tot += p.stat().st_size; ok += 1\n",
|
| 232 |
+
" print(f\" {row['study_name']:>10s} -> {st}\")\n",
|
| 233 |
+
" dt = _t.time() - t0\n",
|
| 234 |
+
" kbs = (tot/1024)/dt if dt > 0 else 0\n",
|
| 235 |
+
" avg = (tot/1e6)/ok if ok else 0\n",
|
| 236 |
+
" print(f\"\\n{ok}/{len(need)} OK | {tot/1e6:.1f} MB / {dt:.1f}s \"\n",
|
| 237 |
+
" f\"= {kbs:,.0f} KB/s ({kbs/1024:.2f} MB/s) [1 luồng]\")\n",
|
| 238 |
+
" if ok:\n",
|
| 239 |
+
" n = len(all_rows)\n",
|
| 240 |
+
" h = (n*avg)/(kbs/1024)/3600\n",
|
| 241 |
+
" print(f\"Ảnh ~{avg:.2f} MB | {n:,} ảnh ≈ {n*avg/1000:.0f} GB\")\n",
|
| 242 |
+
" print(f\"ETA: ~{h:.1f}h (1 luồng) → ~{h/12*1.6:.1f}-{h/12*3:.1f}h (12 luồng)\")\n",
|
| 243 |
+
" print(\"\\n\" + (\"✓ OK — chạy cell TẢI 50k bên dưới.\" if ok == len(need)\n",
|
| 244 |
+
" else \"✗ Lỗi — kiểm tra user/pass, ĐỪNG chạy tải 50k.\"))"
|
| 245 |
+
]
|
| 246 |
},
|
| 247 |
{
|
| 248 |
"cell_type": "code",
|
| 249 |
+
"execution_count": null,
|
| 250 |
"id": "0f8b3dec",
|
|
|
|
| 251 |
"metadata": {},
|
| 252 |
+
"outputs": [],
|
| 253 |
+
"source": [
|
| 254 |
+
"# ── TẢI ẢNH (chỉ chạy khi cell TEST báo ✓ OK) ────────────────────────────────\n",
|
| 255 |
+
"# Resume ưu tiên: log local -> log Drive (copy về local) -> os.walk (fallback).\n",
|
| 256 |
+
"if DL_LOG_LOCAL.exists():\n",
|
| 257 |
+
" done_set = set(l.strip() for l in open(DL_LOG_LOCAL) if l.strip())\n",
|
| 258 |
+
" print(f\"[log local] {len(done_set):,} ảnh đã tải (tức thì).\")\n",
|
| 259 |
+
"elif DL_LOG_DRIVE.exists():\n",
|
| 260 |
+
" shutil.copy(DL_LOG_DRIVE, DL_LOG_LOCAL) # session mới: lấy checkpoint từ Drive\n",
|
| 261 |
+
" done_set = set(l.strip() for l in open(DL_LOG_LOCAL) if l.strip())\n",
|
| 262 |
+
" print(f\"[log Drive] session mới, đọc checkpoint: {len(done_set):,} ảnh (tức thì).\")\n",
|
| 263 |
+
"else:\n",
|
| 264 |
+
" print(\"Chưa có log -> quét Drive 1 lần để dựng log...\")\n",
|
| 265 |
+
" done_set = set()\n",
|
| 266 |
+
" froot = OUT / \"files\"\n",
|
| 267 |
+
" if froot.exists():\n",
|
| 268 |
+
" for dp, _, fns in os.walk(froot):\n",
|
| 269 |
+
" for fn in fns:\n",
|
| 270 |
+
" if fn.endswith(\".jpg\"):\n",
|
| 271 |
+
" done_set.add(os.path.relpath(os.path.join(dp, fn), OUT).replace(\"\\\\\", \"/\"))\n",
|
| 272 |
+
" with open(DL_LOG_LOCAL, \"w\") as f:\n",
|
| 273 |
+
" f.write(\"\\n\".join(sorted(done_set)) + (\"\\n\" if done_set else \"\"))\n",
|
| 274 |
+
" if done_set:\n",
|
| 275 |
+
" shutil.copy(DL_LOG_LOCAL, DL_LOG_DRIVE)\n",
|
| 276 |
+
" print(f\"Dựng log xong: {len(done_set):,} ảnh.\")\n",
|
| 277 |
+
"\n",
|
| 278 |
+
"todo = [r for r in all_rows if r[\"image_relpath\"] not in done_set]\n",
|
| 279 |
+
"n_done = len(all_rows) - len(todo)\n",
|
| 280 |
+
"print(f\"Đã có : {n_done:,} / {len(all_rows):,} ({n_done/len(all_rows)*100:.1f}%)\")\n",
|
| 281 |
+
"print(f\"Cần tải: {len(todo):,}\")\n",
|
| 282 |
+
"\n",
|
| 283 |
+
"if not todo:\n",
|
| 284 |
+
" print(\"\\n✓ Đã tải đủ toàn bộ.\")\n",
|
| 285 |
+
" flush_log_to_drive()\n",
|
| 286 |
+
"else:\n",
|
| 287 |
+
" res = Counter({\"skip\": n_done})\n",
|
| 288 |
+
" try:\n",
|
| 289 |
+
" with ThreadPoolExecutor(max_workers=12) as ex:\n",
|
| 290 |
+
" futs = [ex.submit(dl, r) for r in todo]\n",
|
| 291 |
+
" for f in tqdm(as_completed(futs), total=len(todo), desc=\"downloading\"):\n",
|
| 292 |
+
" res[f.result().split(\"(\")[0]] += 1\n",
|
| 293 |
+
" finally:\n",
|
| 294 |
+
" flush_log_to_drive() # luôn lưu log Drive khi kết thúc/lỗi\n",
|
| 295 |
+
" print(dict(res))\n",
|
| 296 |
+
" if any(k.startswith(\"fail\") for k in res):\n",
|
| 297 |
+
" print(\"Còn fail -> chạy lại cell này (chỉ tải phần thiếu).\")"
|
| 298 |
+
]
|
| 299 |
},
|
| 300 |
{
|
| 301 |
"cell_type": "code",
|
| 302 |
"execution_count": null,
|
| 303 |
"metadata": {},
|
| 304 |
+
"outputs": [],
|
| 305 |
+
"source": [
|
| 306 |
+
"# Kiểm tra còn thiếu ảnh nào không (đối chiếu manifest). Còn thì chạy lại cell tải.\n",
|
| 307 |
+
"miss = {sp: [] for sp in (\"train\",\"val\",\"test\")}\n",
|
| 308 |
+
"for row in all_rows:\n",
|
| 309 |
+
" p = OUT / row[\"image_relpath\"]\n",
|
| 310 |
+
" if not (p.exists() and p.stat().st_size > 0):\n",
|
| 311 |
+
" miss[row[\"split\"]].append(row[\"study_name\"])\n",
|
| 312 |
+
"print(\"ảnh còn thiếu:\", {k: len(v) for k, v in miss.items()},\n",
|
| 313 |
+
" \"| tổng:\", sum(len(v) for v in miss.values()))\n",
|
| 314 |
+
"# In vài study còn thiếu để đối chiếu\n",
|
| 315 |
+
"for sp, names in miss.items():\n",
|
| 316 |
+
" if names:\n",
|
| 317 |
+
" print(f\" [{sp}] thiếu {len(names)}, vd: {names[:5]}\")"
|
| 318 |
+
]
|
| 319 |
},
|
| 320 |
{
|
| 321 |
"cell_type": "markdown",
|
|
|
|
| 328 |
"cell_type": "code",
|
| 329 |
"execution_count": null,
|
| 330 |
"metadata": {},
|
| 331 |
+
"outputs": [],
|
| 332 |
+
"source": [
|
| 333 |
+
"# Copy reports (giữ path gốc) + vqa + manifest vào package\n",
|
| 334 |
+
"# reports: bundle/reports/files/pXX/.../sSTUDY.txt -> OUT/files/pXX/.../sSTUDY.txt\n",
|
| 335 |
+
"shutil.copytree(BUNDLE_DIR/\"reports\", OUT, dirs_exist_ok=True)\n",
|
| 336 |
+
"\n",
|
| 337 |
+
"for sp in (\"train\",\"val\",\"test\"):\n",
|
| 338 |
+
" shutil.copy(BUNDLE_DIR/\"vqa\"/VQA_OUT[sp], OUT/VQA_OUT[sp])\n",
|
| 339 |
+
" shutil.copy(BUNDLE_DIR/f\"manifest_{sp}.json\", OUT/f\"manifest_{sp}.json\")\n",
|
| 340 |
+
" src_csv = BUNDLE_DIR/f\"manifest_{sp}.csv\"\n",
|
| 341 |
+
" if src_csv.exists():\n",
|
| 342 |
+
" shutil.copy(src_csv, OUT/f\"manifest_{sp}.csv\")\n",
|
| 343 |
+
" nv = len(json.load(open(OUT/VQA_OUT[sp], encoding=\"utf-8\")))\n",
|
| 344 |
+
" print(f\"{sp}: vqa={nv:,} manifest copied\")\n",
|
| 345 |
+
"\n",
|
| 346 |
+
"n_rep = sum(1 for _ in (OUT/'files').rglob('s*.txt'))\n",
|
| 347 |
+
"print(f\"reports trong package: {n_rep:,}\")"
|
| 348 |
+
]
|
| 349 |
},
|
| 350 |
{
|
| 351 |
"cell_type": "code",
|
| 352 |
"execution_count": null,
|
| 353 |
"metadata": {},
|
| 354 |
+
"outputs": [],
|
| 355 |
+
"source": [
|
| 356 |
+
"# Sanity check cuối — đếm theo manifest (image + report tồn tại thực sự)\n",
|
| 357 |
+
"for sp in (\"train\",\"val\",\"test\"):\n",
|
| 358 |
+
" rows = manifests[sp]\n",
|
| 359 |
+
" ni = sum(1 for x in rows if (OUT/x[\"image_relpath\"]).exists())\n",
|
| 360 |
+
" nr = sum(1 for x in rows if (OUT/x[\"report_relpath\"]).exists())\n",
|
| 361 |
+
" print(f\"{sp:5s} manifest={len(rows):,} images_ok={ni:,} reports_ok={nr:,}\")\n",
|
| 362 |
+
"print(\"\\nPackage:\", OUT)"
|
| 363 |
+
]
|
| 364 |
},
|
| 365 |
{
|
| 366 |
"cell_type": "markdown",
|
| 367 |
"metadata": {},
|
| 368 |
+
"source": [
|
| 369 |
+
"## 4. Upload lên Hugging Face\n",
|
| 370 |
+
"\n",
|
| 371 |
+
"Push vào repo **có sẵn** `hieu3636/cxr-vlm-data`, nằm trong thư mục con `MIMIC-CXR_processed/` (dùng `path_in_repo`, không tạo repo mới)."
|
| 372 |
+
]
|
| 373 |
},
|
| 374 |
{
|
| 375 |
"cell_type": "code",
|
| 376 |
"execution_count": null,
|
| 377 |
"metadata": {},
|
| 378 |
+
"outputs": [],
|
| 379 |
+
"source": [
|
| 380 |
+
"RUN_HF = False # ← bật khi sẵn sàng push\n",
|
| 381 |
+
"if RUN_HF:\n",
|
| 382 |
+
" from huggingface_hub import HfApi\n",
|
| 383 |
+
" api = HfApi(token=HF_TOKEN)\n",
|
| 384 |
+
" # repo đã tồn tại sẵn -> exist_ok=True chỉ no-op, không tạo mới\n",
|
| 385 |
+
" api.create_repo(HF_REPO_ID, repo_type=HF_REPO_TYPE, exist_ok=True)\n",
|
| 386 |
+
" # upload_folder hỗ trợ path_in_repo -> đẩy vào thư mục con MIMIC-CXR_processed\n",
|
| 387 |
+
" # (chạy lại nếu đứt: file đã có trên repo được bỏ qua theo hash)\n",
|
| 388 |
+
" api.upload_folder(\n",
|
| 389 |
+
" repo_id = HF_REPO_ID,\n",
|
| 390 |
+
" repo_type = HF_REPO_TYPE,\n",
|
| 391 |
+
" folder_path = str(OUT),\n",
|
| 392 |
+
" path_in_repo = HF_PATH_IN_REPO,\n",
|
| 393 |
+
" commit_message = \"Add MIMIC-CXR_processed subset\",\n",
|
| 394 |
+
" )\n",
|
| 395 |
+
" print(\"pushed →\",\n",
|
| 396 |
+
" f\"https://huggingface.co/{HF_REPO_TYPE}s/{HF_REPO_ID}/tree/main/{HF_PATH_IN_REPO}\")\n",
|
| 397 |
+
"else:\n",
|
| 398 |
+
" print(\"RUN_HF=False — bật True để push vào\",\n",
|
| 399 |
+
" f\"{HF_REPO_ID}/{HF_PATH_IN_REPO}\")"
|
| 400 |
+
]
|
| 401 |
},
|
| 402 |
{
|
| 403 |
"cell_type": "markdown",
|
|
|
|
| 412 |
"cell_type": "code",
|
| 413 |
"execution_count": null,
|
| 414 |
"metadata": {},
|
| 415 |
+
"outputs": [],
|
| 416 |
+
"source": [
|
| 417 |
+
"RUN_ZIP = False\n",
|
| 418 |
+
"if RUN_ZIP:\n",
|
| 419 |
+
" # zip cả package (giữ cấu trúc gốc) thành 1 file\n",
|
| 420 |
+
" shutil.make_archive(\"/content/MIMIC-CXR_processed\", \"zip\", OUT)\n",
|
| 421 |
+
" shutil.copy(\"/content/MIMIC-CXR_processed.zip\", DRIVE/\"MIMIC-CXR_processed.zip\")\n",
|
| 422 |
+
" print(\"zipped -> Drive/MIMIC-CXR_processed.zip\")\n",
|
| 423 |
+
"else:\n",
|
| 424 |
+
" print(\"RUN_ZIP=False\")"
|
| 425 |
+
]
|
| 426 |
},
|
| 427 |
{
|
| 428 |
"cell_type": "code",
|
| 429 |
"execution_count": null,
|
| 430 |
"metadata": {},
|
| 431 |
+
"outputs": [],
|
| 432 |
+
"source": [
|
| 433 |
+
"print(\"=\"*54)\n",
|
| 434 |
+
"print(\" PHASE 2 (COLAB) DONE\")\n",
|
| 435 |
+
"print(\"=\"*54)\n",
|
| 436 |
+
"for sp in (\"train\",\"val\",\"test\"):\n",
|
| 437 |
+
" rows=manifests[sp]\n",
|
| 438 |
+
" ni=sum(1 for x in rows if (OUT/x[\"image_relpath\"]).exists())\n",
|
| 439 |
+
" print(f\" {sp:5s} images={ni:,}/{len(rows):,}\")\n",
|
| 440 |
+
"print(f\" package: {OUT}\")\n",
|
| 441 |
+
"print(\" cấu trúc gốc: files/pXX/pSUBJ/sSTUDY/<dicom>.jpg + .txt\")\n",
|
| 442 |
+
"print(\" Flags: RUN_HF / RUN_ZIP (mặc định False)\")\n",
|
| 443 |
+
"print(\"=\"*54)"
|
| 444 |
+
]
|
| 445 |
}
|
| 446 |
],
|
| 447 |
"metadata": {
|
|
|
|
| 457 |
},
|
| 458 |
"nbformat": 4,
|
| 459 |
"nbformat_minor": 5
|
| 460 |
+
}
|
data/build_subset_local.ipynb
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
data/eda_full.ipynb
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
data/eda_p18.ipynb
CHANGED
|
@@ -26,13 +26,43 @@
|
|
| 26 |
{
|
| 27 |
"cell_type": "code",
|
| 28 |
"execution_count": null,
|
|
|
|
| 29 |
"metadata": {},
|
| 30 |
"outputs": [],
|
| 31 |
-
"source":
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
},
|
| 33 |
{
|
| 34 |
"cell_type": "code",
|
| 35 |
"execution_count": null,
|
|
|
|
| 36 |
"metadata": {},
|
| 37 |
"outputs": [],
|
| 38 |
"source": [
|
|
@@ -61,6 +91,7 @@
|
|
| 61 |
},
|
| 62 |
{
|
| 63 |
"cell_type": "markdown",
|
|
|
|
| 64 |
"metadata": {},
|
| 65 |
"source": [
|
| 66 |
"## 1. Load & lọc subset p18"
|
|
@@ -69,6 +100,7 @@
|
|
| 69 |
{
|
| 70 |
"cell_type": "code",
|
| 71 |
"execution_count": null,
|
|
|
|
| 72 |
"metadata": {},
|
| 73 |
"outputs": [],
|
| 74 |
"source": [
|
|
@@ -89,6 +121,7 @@
|
|
| 89 |
{
|
| 90 |
"cell_type": "code",
|
| 91 |
"execution_count": null,
|
|
|
|
| 92 |
"metadata": {},
|
| 93 |
"outputs": [],
|
| 94 |
"source": [
|
|
@@ -104,6 +137,7 @@
|
|
| 104 |
},
|
| 105 |
{
|
| 106 |
"cell_type": "markdown",
|
|
|
|
| 107 |
"metadata": {},
|
| 108 |
"source": [
|
| 109 |
"## 2. Tổng quan số lượng ảnh & report theo split"
|
|
@@ -112,6 +146,7 @@
|
|
| 112 |
{
|
| 113 |
"cell_type": "code",
|
| 114 |
"execution_count": null,
|
|
|
|
| 115 |
"metadata": {},
|
| 116 |
"outputs": [],
|
| 117 |
"source": [
|
|
@@ -136,6 +171,7 @@
|
|
| 136 |
{
|
| 137 |
"cell_type": "code",
|
| 138 |
"execution_count": null,
|
|
|
|
| 139 |
"metadata": {},
|
| 140 |
"outputs": [],
|
| 141 |
"source": [
|
|
@@ -153,6 +189,7 @@
|
|
| 153 |
},
|
| 154 |
{
|
| 155 |
"cell_type": "markdown",
|
|
|
|
| 156 |
"metadata": {},
|
| 157 |
"source": [
|
| 158 |
"## 3. Số ảnh mỗi study (1 study → bao nhiêu ảnh?)"
|
|
@@ -161,6 +198,7 @@
|
|
| 161 |
{
|
| 162 |
"cell_type": "code",
|
| 163 |
"execution_count": null,
|
|
|
|
| 164 |
"metadata": {},
|
| 165 |
"outputs": [],
|
| 166 |
"source": [
|
|
@@ -176,6 +214,7 @@
|
|
| 176 |
{
|
| 177 |
"cell_type": "code",
|
| 178 |
"execution_count": null,
|
|
|
|
| 179 |
"metadata": {},
|
| 180 |
"outputs": [],
|
| 181 |
"source": [
|
|
@@ -192,6 +231,7 @@
|
|
| 192 |
},
|
| 193 |
{
|
| 194 |
"cell_type": "markdown",
|
|
|
|
| 195 |
"metadata": {},
|
| 196 |
"source": [
|
| 197 |
"## 4. Phân bố View Position (AP, PA, Lateral, ...)"
|
|
@@ -200,6 +240,7 @@
|
|
| 200 |
{
|
| 201 |
"cell_type": "code",
|
| 202 |
"execution_count": null,
|
|
|
|
| 203 |
"metadata": {},
|
| 204 |
"outputs": [],
|
| 205 |
"source": [
|
|
@@ -212,6 +253,7 @@
|
|
| 212 |
{
|
| 213 |
"cell_type": "code",
|
| 214 |
"execution_count": null,
|
|
|
|
| 215 |
"metadata": {},
|
| 216 |
"outputs": [],
|
| 217 |
"source": [
|
|
@@ -237,6 +279,7 @@
|
|
| 237 |
{
|
| 238 |
"cell_type": "code",
|
| 239 |
"execution_count": null,
|
|
|
|
| 240 |
"metadata": {},
|
| 241 |
"outputs": [],
|
| 242 |
"source": [
|
|
@@ -256,27 +299,104 @@
|
|
| 256 |
{
|
| 257 |
"cell_type": "markdown",
|
| 258 |
"id": "ae9f3d3c",
|
| 259 |
-
"
|
| 260 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 261 |
},
|
| 262 |
{
|
| 263 |
"cell_type": "code",
|
|
|
|
| 264 |
"id": "d2ce6beb",
|
| 265 |
-
"source": "frontal = df[df[\"ViewPosition\"].isin([\"AP\", \"PA\"])].copy()\n\n# Với mỗi study: chọn AP trước, nếu không có thì chọn PA (lấy 1 ảnh duy nhất)\ndef pick_frontal_view(group):\n ap = group[group[\"ViewPosition\"] == \"AP\"]\n if len(ap) > 0:\n return ap.iloc[[0]]\n return group[group[\"ViewPosition\"] == \"PA\"].iloc[[0]]\n\nfrontal_1img = (\n frontal.groupby(\"study_id\", group_keys=False)\n .apply(pick_frontal_view)\n .reset_index(drop=True)\n)\n\n# Thống kê tổng quan\nn_study_total = df[\"study_id\"].nunique()\nn_study_frontal = frontal_1img[\"study_id\"].nunique()\nn_study_no_front = n_study_total - n_study_frontal\n\nprint(\"=== Frontal-Only Sampling (p18) ===\")\nprint(f\"Tổng số study : {n_study_total:,}\")\nprint(f\"Study có ảnh frontal (AP/PA) : {n_study_frontal:,} ({n_study_frontal/n_study_total*100:.1f}%)\")\nprint(f\"Study bị loại (không có frontal): {n_study_no_front:,} ({n_study_no_front/n_study_total*100:.1f}%)\")\nprint()\nprint(f\"Ảnh được chọn theo view:\")\nprint(frontal_1img[\"ViewPosition\"].value_counts().to_string())\nprint()\nprint(\"=== Mẫu train sau khi filter (split) ===\")\nsplit_frontal = frontal_1img[\"split\"].value_counts().reindex([\"train\", \"validate\", \"test\"])\nsplit_all = df.drop_duplicates(\"study_id\")[\"split\"].value_counts().reindex([\"train\", \"validate\", \"test\"])\ncompare = pd.DataFrame({\n \"All studies\": split_all,\n \"Frontal-only\": split_frontal,\n \"Giảm (%)\": ((split_all - split_frontal) / split_all * 100).round(1)\n})\nprint(compare.to_string())",
|
| 266 |
"metadata": {},
|
| 267 |
-
"
|
| 268 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 269 |
},
|
| 270 |
{
|
| 271 |
"cell_type": "code",
|
|
|
|
| 272 |
"id": "9d4aaf5c",
|
| 273 |
-
"source": "fig, axes = plt.subplots(1, 3, figsize=(14, 4))\n\n# 1. All vs Frontal-only (study count)\ncats = [\"All studies\", \"Frontal-only\"]\nvals = [n_study_total, n_study_frontal]\nbars = axes[0].bar(cats, vals, color=[\"#4C72B0\", \"#55A868\"], width=0.5)\naxes[0].bar_label(bars, fmt=\"%d\")\naxes[0].set_title(\"Study count: All vs Frontal-only\")\naxes[0].set_ylabel(\"Số study\")\n\n# 2. View breakdown của ảnh được chọn\nvc = frontal_1img[\"ViewPosition\"].value_counts()\naxes[1].pie(vc.values, labels=vc.index, autopct=\"%1.1f%%\",\n colors=[\"#4C72B0\", \"#DD8452\"])\naxes[1].set_title(\"View được chọn (AP ưu tiên)\")\n\n# 3. So sánh train/val/test\nx = np.arange(3)\nw = 0.35\nsplits = [\"train\", \"validate\", \"test\"]\naxes[2].bar(x - w/2, split_all.values, w, label=\"All\", color=\"#4C72B0\", alpha=0.85)\naxes[2].bar(x + w/2, split_frontal.values, w, label=\"Frontal-only\", color=\"#55A868\", alpha=0.85)\naxes[2].set_xticks(x)\naxes[2].set_xticklabels(splits)\naxes[2].set_title(\"Frontal-only vs All (per split)\")\naxes[2].set_ylabel(\"Số study\")\naxes[2].legend()\n\nplt.suptitle(\"Frontal-Only Sampling Strategy — p18\", fontsize=13)\nplt.tight_layout()\nplt.show()",
|
| 274 |
"metadata": {},
|
| 275 |
-
"
|
| 276 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 277 |
},
|
| 278 |
{
|
| 279 |
"cell_type": "markdown",
|
|
|
|
| 280 |
"metadata": {},
|
| 281 |
"source": [
|
| 282 |
"## 5. CheXpert Labels — 14 nhãn bệnh lý"
|
|
@@ -285,6 +405,7 @@
|
|
| 285 |
{
|
| 286 |
"cell_type": "code",
|
| 287 |
"execution_count": null,
|
|
|
|
| 288 |
"metadata": {},
|
| 289 |
"outputs": [],
|
| 290 |
"source": [
|
|
@@ -311,13 +432,33 @@
|
|
| 311 |
{
|
| 312 |
"cell_type": "code",
|
| 313 |
"execution_count": null,
|
|
|
|
| 314 |
"metadata": {},
|
| 315 |
"outputs": [],
|
| 316 |
-
"source":
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 317 |
},
|
| 318 |
{
|
| 319 |
"cell_type": "code",
|
| 320 |
"execution_count": null,
|
|
|
|
| 321 |
"metadata": {},
|
| 322 |
"outputs": [],
|
| 323 |
"source": [
|
|
@@ -338,6 +479,7 @@
|
|
| 338 |
},
|
| 339 |
{
|
| 340 |
"cell_type": "markdown",
|
|
|
|
| 341 |
"metadata": {},
|
| 342 |
"source": [
|
| 343 |
"## 6. Phân tích Report — Findings & Impression"
|
|
@@ -346,6 +488,7 @@
|
|
| 346 |
{
|
| 347 |
"cell_type": "code",
|
| 348 |
"execution_count": null,
|
|
|
|
| 349 |
"metadata": {},
|
| 350 |
"outputs": [],
|
| 351 |
"source": [
|
|
@@ -405,6 +548,7 @@
|
|
| 405 |
{
|
| 406 |
"cell_type": "code",
|
| 407 |
"execution_count": null,
|
|
|
|
| 408 |
"metadata": {},
|
| 409 |
"outputs": [],
|
| 410 |
"source": [
|
|
@@ -418,6 +562,7 @@
|
|
| 418 |
{
|
| 419 |
"cell_type": "code",
|
| 420 |
"execution_count": null,
|
|
|
|
| 421 |
"metadata": {},
|
| 422 |
"outputs": [],
|
| 423 |
"source": [
|
|
@@ -451,6 +596,7 @@
|
|
| 451 |
{
|
| 452 |
"cell_type": "code",
|
| 453 |
"execution_count": null,
|
|
|
|
| 454 |
"metadata": {},
|
| 455 |
"outputs": [],
|
| 456 |
"source": [
|
|
@@ -471,6 +617,7 @@
|
|
| 471 |
},
|
| 472 |
{
|
| 473 |
"cell_type": "markdown",
|
|
|
|
| 474 |
"metadata": {},
|
| 475 |
"source": [
|
| 476 |
"## 7. VQA — phân tích câu hỏi & đáp"
|
|
@@ -479,6 +626,7 @@
|
|
| 479 |
{
|
| 480 |
"cell_type": "code",
|
| 481 |
"execution_count": null,
|
|
|
|
| 482 |
"metadata": {},
|
| 483 |
"outputs": [],
|
| 484 |
"source": [
|
|
@@ -506,6 +654,7 @@
|
|
| 506 |
{
|
| 507 |
"cell_type": "code",
|
| 508 |
"execution_count": null,
|
|
|
|
| 509 |
"metadata": {},
|
| 510 |
"outputs": [],
|
| 511 |
"source": [
|
|
@@ -517,6 +666,7 @@
|
|
| 517 |
{
|
| 518 |
"cell_type": "code",
|
| 519 |
"execution_count": null,
|
|
|
|
| 520 |
"metadata": {},
|
| 521 |
"outputs": [],
|
| 522 |
"source": [
|
|
@@ -534,6 +684,7 @@
|
|
| 534 |
{
|
| 535 |
"cell_type": "code",
|
| 536 |
"execution_count": null,
|
|
|
|
| 537 |
"metadata": {},
|
| 538 |
"outputs": [],
|
| 539 |
"source": [
|
|
@@ -559,31 +710,10 @@
|
|
| 559 |
"plt.show()"
|
| 560 |
]
|
| 561 |
},
|
| 562 |
-
{
|
| 563 |
-
"cell_type": "markdown",
|
| 564 |
-
"id": "c313b9c3",
|
| 565 |
-
"source": "### VQA × View Position — mẫu hỏi đáp thuộc ảnh view nào",
|
| 566 |
-
"metadata": {}
|
| 567 |
-
},
|
| 568 |
-
{
|
| 569 |
-
"cell_type": "code",
|
| 570 |
-
"id": "0791482f",
|
| 571 |
-
"source": "# image_id trong VQA = dicom_id trong metadata\nvqa_view = vqa_p18.merge(\n p18_meta[[\"dicom_id\", \"ViewPosition\"]],\n left_on=\"image_id\", right_on=\"dicom_id\",\n how=\"left\"\n)\n\nmissing_view_vqa = vqa_view[\"ViewPosition\"].isna().sum()\nvqa_view[\"ViewPosition\"] = vqa_view[\"ViewPosition\"].fillna(\"Unknown\")\n\nview_vqa_counts = vqa_view[\"ViewPosition\"].value_counts()\nprint(\"=== VQA samples theo View Position (p18) ===\")\nprint(view_vqa_counts.to_string())\nprint(f\"\\nKhông map được ViewPosition: {missing_view_vqa:,} ({missing_view_vqa/len(vqa_view)*100:.1f}%)\")",
|
| 572 |
-
"metadata": {},
|
| 573 |
-
"execution_count": null,
|
| 574 |
-
"outputs": []
|
| 575 |
-
},
|
| 576 |
-
{
|
| 577 |
-
"cell_type": "code",
|
| 578 |
-
"id": "049baaef",
|
| 579 |
-
"source": "fig, axes = plt.subplots(1, 3, figsize=(15, 4))\n\n# 1. Bar: số mẫu VQA theo view\nbars = axes[0].bar(view_vqa_counts.index, view_vqa_counts.values,\n color=sns.color_palette(\"Set2\", len(view_vqa_counts)))\naxes[0].bar_label(bars, fmt=\"%d\")\naxes[0].set_title(\"Số mẫu VQA theo View Position\")\naxes[0].set_ylabel(\"Số mẫu\")\n\n# 2. Pie\naxes[1].pie(view_vqa_counts.values, labels=view_vqa_counts.index,\n autopct=\"%1.1f%%\", colors=sns.color_palette(\"Set2\", len(view_vqa_counts)))\naxes[1].set_title(\"Tỉ lệ VQA theo View Position\")\n\n# 3. Semantic type × View (stacked bar)\nsem_view = vqa_view.groupby([\"ViewPosition\", \"semantic_type\"]).size().unstack(fill_value=0)\nsem_view.plot(kind=\"bar\", ax=axes[2], color=sns.color_palette(\"Set1\", sem_view.shape[1]),\n width=0.7, stacked=True)\naxes[2].set_title(\"Semantic Type × View Position\")\naxes[2].set_xlabel(\"View Position\")\naxes[2].set_ylabel(\"Số mẫu\")\naxes[2].tick_params(axis=\"x\", rotation=30)\naxes[2].legend(title=\"Semantic Type\", fontsize=8)\n\nplt.suptitle(\"VQA × View Position — p18\", fontsize=13)\nplt.tight_layout()\nplt.show()\n\n# Content type × View\nprint(\"\\nContent type theo View Position:\")\nprint(vqa_view.groupby([\"ViewPosition\", \"content_type\"]).size()\n .unstack(fill_value=0).to_string())",
|
| 580 |
-
"metadata": {},
|
| 581 |
-
"execution_count": null,
|
| 582 |
-
"outputs": []
|
| 583 |
-
},
|
| 584 |
{
|
| 585 |
"cell_type": "code",
|
| 586 |
"execution_count": null,
|
|
|
|
| 587 |
"metadata": {},
|
| 588 |
"outputs": [],
|
| 589 |
"source": [
|
|
@@ -602,6 +732,7 @@
|
|
| 602 |
{
|
| 603 |
"cell_type": "code",
|
| 604 |
"execution_count": null,
|
|
|
|
| 605 |
"metadata": {},
|
| 606 |
"outputs": [],
|
| 607 |
"source": [
|
|
@@ -627,6 +758,7 @@
|
|
| 627 |
{
|
| 628 |
"cell_type": "code",
|
| 629 |
"execution_count": null,
|
|
|
|
| 630 |
"metadata": {},
|
| 631 |
"outputs": [],
|
| 632 |
"source": [
|
|
@@ -657,6 +789,7 @@
|
|
| 657 |
},
|
| 658 |
{
|
| 659 |
"cell_type": "markdown",
|
|
|
|
| 660 |
"metadata": {},
|
| 661 |
"source": [
|
| 662 |
"## 8. Gợi ý thêm — Missing data & Data Quality"
|
|
@@ -665,6 +798,7 @@
|
|
| 665 |
{
|
| 666 |
"cell_type": "code",
|
| 667 |
"execution_count": null,
|
|
|
|
| 668 |
"metadata": {},
|
| 669 |
"outputs": [],
|
| 670 |
"source": [
|
|
@@ -682,6 +816,7 @@
|
|
| 682 |
{
|
| 683 |
"cell_type": "code",
|
| 684 |
"execution_count": null,
|
|
|
|
| 685 |
"metadata": {},
|
| 686 |
"outputs": [],
|
| 687 |
"source": [
|
|
@@ -693,6 +828,7 @@
|
|
| 693 |
{
|
| 694 |
"cell_type": "code",
|
| 695 |
"execution_count": null,
|
|
|
|
| 696 |
"metadata": {},
|
| 697 |
"outputs": [],
|
| 698 |
"source": [
|
|
@@ -711,6 +847,7 @@
|
|
| 711 |
{
|
| 712 |
"cell_type": "code",
|
| 713 |
"execution_count": null,
|
|
|
|
| 714 |
"metadata": {},
|
| 715 |
"outputs": [],
|
| 716 |
"source": [
|
|
@@ -733,6 +870,7 @@
|
|
| 733 |
{
|
| 734 |
"cell_type": "code",
|
| 735 |
"execution_count": null,
|
|
|
|
| 736 |
"metadata": {},
|
| 737 |
"outputs": [],
|
| 738 |
"source": [
|
|
@@ -743,12 +881,14 @@
|
|
| 743 |
"\n",
|
| 744 |
" res_counts = df.groupby([\"Rows\", \"Columns\"]).size().sort_values(ascending=False).head(15)\n",
|
| 745 |
" print(\"\\nTop-15 resolutions:\")\n",
|
| 746 |
-
" print(res_counts.to_string())\
|
|
|
|
| 747 |
" print(\"Cột Rows/Columns không có trong metadata.\")"
|
| 748 |
]
|
| 749 |
},
|
| 750 |
{
|
| 751 |
"cell_type": "markdown",
|
|
|
|
| 752 |
"metadata": {},
|
| 753 |
"source": [
|
| 754 |
"## 9. Tóm tắt (Summary)"
|
|
@@ -757,6 +897,7 @@
|
|
| 757 |
{
|
| 758 |
"cell_type": "code",
|
| 759 |
"execution_count": null,
|
|
|
|
| 760 |
"metadata": {},
|
| 761 |
"outputs": [],
|
| 762 |
"source": [
|
|
@@ -789,9 +930,9 @@
|
|
| 789 |
},
|
| 790 |
"language_info": {
|
| 791 |
"name": "python",
|
| 792 |
-
"version": "3.
|
| 793 |
}
|
| 794 |
},
|
| 795 |
"nbformat": 4,
|
| 796 |
"nbformat_minor": 5
|
| 797 |
-
}
|
|
|
|
| 26 |
{
|
| 27 |
"cell_type": "code",
|
| 28 |
"execution_count": null,
|
| 29 |
+
"id": "a4238924",
|
| 30 |
"metadata": {},
|
| 31 |
"outputs": [],
|
| 32 |
+
"source": [
|
| 33 |
+
"from pathlib import Path\n",
|
| 34 |
+
"\n",
|
| 35 |
+
"DATA_DIR = Path(r\"D:\\USTH\\KLTN\\cxr-vlm-data\")\n",
|
| 36 |
+
"CXR_ROOT = DATA_DIR / \"mimic-cxr-reports\" # files/p10…p19/pXXXXXX/sYYYYYY.txt\n",
|
| 37 |
+
"\n",
|
| 38 |
+
"SPLIT_CSV = DATA_DIR / \"mimic-cxr-2.0.0-split.csv\"\n",
|
| 39 |
+
"META_CSV = DATA_DIR / \"mimic-cxr-2.0.0-metadata.csv\"\n",
|
| 40 |
+
"CHEXPERT_CSV = DATA_DIR / \"mimic-cxr-2.0.0-chexpert.csv\"\n",
|
| 41 |
+
"\n",
|
| 42 |
+
"_VQA_DIR = (DATA_DIR\n",
|
| 43 |
+
" / \"mimic-ext-mimic-cxr-vqa-a-complex-diverse-and-large-scale-visual-question-answering-dataset-for-chest-x-ray-images-1.0.0\"\n",
|
| 44 |
+
" / \"MIMIC-Ext-MIMIC-CXR-VQA\"\n",
|
| 45 |
+
" / \"dataset\")\n",
|
| 46 |
+
"VQA_TRAIN = _VQA_DIR / \"train.json\"\n",
|
| 47 |
+
"VQA_VALID = _VQA_DIR / \"valid.json\"\n",
|
| 48 |
+
"VQA_TEST = _VQA_DIR / \"test.json\"\n",
|
| 49 |
+
"\n",
|
| 50 |
+
"# Kiểm tra nhanh\n",
|
| 51 |
+
"for name, p in [(\"SPLIT_CSV\", SPLIT_CSV),\n",
|
| 52 |
+
" (\"META_CSV\", META_CSV),\n",
|
| 53 |
+
" (\"CHEXPERT_CSV\", CHEXPERT_CSV),\n",
|
| 54 |
+
" (\"CXR_ROOT\", CXR_ROOT),\n",
|
| 55 |
+
" (\"VQA_TRAIN\", VQA_TRAIN)]:\n",
|
| 56 |
+
" status = \"✓\" if p.exists() else \"✗ NOT FOUND\"\n",
|
| 57 |
+
" print(f\" {status} {name}: {p}\")\n",
|
| 58 |
+
"\n",
|
| 59 |
+
"print(\"\\nPaths configured.\")"
|
| 60 |
+
]
|
| 61 |
},
|
| 62 |
{
|
| 63 |
"cell_type": "code",
|
| 64 |
"execution_count": null,
|
| 65 |
+
"id": "99828a70",
|
| 66 |
"metadata": {},
|
| 67 |
"outputs": [],
|
| 68 |
"source": [
|
|
|
|
| 91 |
},
|
| 92 |
{
|
| 93 |
"cell_type": "markdown",
|
| 94 |
+
"id": "4674dd4f",
|
| 95 |
"metadata": {},
|
| 96 |
"source": [
|
| 97 |
"## 1. Load & lọc subset p18"
|
|
|
|
| 100 |
{
|
| 101 |
"cell_type": "code",
|
| 102 |
"execution_count": null,
|
| 103 |
+
"id": "9f1d59fe",
|
| 104 |
"metadata": {},
|
| 105 |
"outputs": [],
|
| 106 |
"source": [
|
|
|
|
| 121 |
{
|
| 122 |
"cell_type": "code",
|
| 123 |
"execution_count": null,
|
| 124 |
+
"id": "6657d6ec",
|
| 125 |
"metadata": {},
|
| 126 |
"outputs": [],
|
| 127 |
"source": [
|
|
|
|
| 137 |
},
|
| 138 |
{
|
| 139 |
"cell_type": "markdown",
|
| 140 |
+
"id": "5a7bff47",
|
| 141 |
"metadata": {},
|
| 142 |
"source": [
|
| 143 |
"## 2. Tổng quan số lượng ảnh & report theo split"
|
|
|
|
| 146 |
{
|
| 147 |
"cell_type": "code",
|
| 148 |
"execution_count": null,
|
| 149 |
+
"id": "81be327d",
|
| 150 |
"metadata": {},
|
| 151 |
"outputs": [],
|
| 152 |
"source": [
|
|
|
|
| 171 |
{
|
| 172 |
"cell_type": "code",
|
| 173 |
"execution_count": null,
|
| 174 |
+
"id": "80fa39e7",
|
| 175 |
"metadata": {},
|
| 176 |
"outputs": [],
|
| 177 |
"source": [
|
|
|
|
| 189 |
},
|
| 190 |
{
|
| 191 |
"cell_type": "markdown",
|
| 192 |
+
"id": "4fed2aa0",
|
| 193 |
"metadata": {},
|
| 194 |
"source": [
|
| 195 |
"## 3. Số ảnh mỗi study (1 study → bao nhiêu ảnh?)"
|
|
|
|
| 198 |
{
|
| 199 |
"cell_type": "code",
|
| 200 |
"execution_count": null,
|
| 201 |
+
"id": "39b23ccb",
|
| 202 |
"metadata": {},
|
| 203 |
"outputs": [],
|
| 204 |
"source": [
|
|
|
|
| 214 |
{
|
| 215 |
"cell_type": "code",
|
| 216 |
"execution_count": null,
|
| 217 |
+
"id": "b8c6560b",
|
| 218 |
"metadata": {},
|
| 219 |
"outputs": [],
|
| 220 |
"source": [
|
|
|
|
| 231 |
},
|
| 232 |
{
|
| 233 |
"cell_type": "markdown",
|
| 234 |
+
"id": "0262e14a",
|
| 235 |
"metadata": {},
|
| 236 |
"source": [
|
| 237 |
"## 4. Phân bố View Position (AP, PA, Lateral, ...)"
|
|
|
|
| 240 |
{
|
| 241 |
"cell_type": "code",
|
| 242 |
"execution_count": null,
|
| 243 |
+
"id": "cad06cc2",
|
| 244 |
"metadata": {},
|
| 245 |
"outputs": [],
|
| 246 |
"source": [
|
|
|
|
| 253 |
{
|
| 254 |
"cell_type": "code",
|
| 255 |
"execution_count": null,
|
| 256 |
+
"id": "d86b2102",
|
| 257 |
"metadata": {},
|
| 258 |
"outputs": [],
|
| 259 |
"source": [
|
|
|
|
| 279 |
{
|
| 280 |
"cell_type": "code",
|
| 281 |
"execution_count": null,
|
| 282 |
+
"id": "d8f24892",
|
| 283 |
"metadata": {},
|
| 284 |
"outputs": [],
|
| 285 |
"source": [
|
|
|
|
| 299 |
{
|
| 300 |
"cell_type": "markdown",
|
| 301 |
"id": "ae9f3d3c",
|
| 302 |
+
"metadata": {},
|
| 303 |
+
"source": [
|
| 304 |
+
"## 4b. Frontal-Only Sampling Strategy (AP > PA)\n",
|
| 305 |
+
"\n",
|
| 306 |
+
"Chiến lược train: **1 report + 1 ảnh frontal** mỗi study.\n",
|
| 307 |
+
"- Chỉ giữ AP hoặc PA; nếu study có cả hai thì **ưu tiên AP**.\n",
|
| 308 |
+
"- Study không có ảnh frontal nào → loại khỏi tập train."
|
| 309 |
+
]
|
| 310 |
},
|
| 311 |
{
|
| 312 |
"cell_type": "code",
|
| 313 |
+
"execution_count": null,
|
| 314 |
"id": "d2ce6beb",
|
|
|
|
| 315 |
"metadata": {},
|
| 316 |
+
"outputs": [],
|
| 317 |
+
"source": [
|
| 318 |
+
"frontal = df[df[\"ViewPosition\"].isin([\"AP\", \"PA\"])].copy()\n",
|
| 319 |
+
"\n",
|
| 320 |
+
"# Với mỗi study: chọn AP trước, nếu không có thì chọn PA (lấy 1 ảnh duy nhất)\n",
|
| 321 |
+
"def pick_frontal_view(group):\n",
|
| 322 |
+
" ap = group[group[\"ViewPosition\"] == \"AP\"]\n",
|
| 323 |
+
" if len(ap) > 0:\n",
|
| 324 |
+
" return ap.iloc[[0]]\n",
|
| 325 |
+
" return group[group[\"ViewPosition\"] == \"PA\"].iloc[[0]]\n",
|
| 326 |
+
"\n",
|
| 327 |
+
"frontal_1img = (\n",
|
| 328 |
+
" frontal.groupby(\"study_id\", group_keys=False)\n",
|
| 329 |
+
" .apply(pick_frontal_view)\n",
|
| 330 |
+
" .reset_index(drop=True)\n",
|
| 331 |
+
")\n",
|
| 332 |
+
"\n",
|
| 333 |
+
"# Thống kê tổng quan\n",
|
| 334 |
+
"n_study_total = df[\"study_id\"].nunique()\n",
|
| 335 |
+
"n_study_frontal = frontal_1img[\"study_id\"].nunique()\n",
|
| 336 |
+
"n_study_no_front = n_study_total - n_study_frontal\n",
|
| 337 |
+
"\n",
|
| 338 |
+
"print(\"=== Frontal-Only Sampling (p18) ===\")\n",
|
| 339 |
+
"print(f\"Tổng số study : {n_study_total:,}\")\n",
|
| 340 |
+
"print(f\"Study có ảnh frontal (AP/PA) : {n_study_frontal:,} ({n_study_frontal/n_study_total*100:.1f}%)\")\n",
|
| 341 |
+
"print(f\"Study bị loại (không có frontal): {n_study_no_front:,} ({n_study_no_front/n_study_total*100:.1f}%)\")\n",
|
| 342 |
+
"print()\n",
|
| 343 |
+
"print(f\"Ảnh được chọn theo view:\")\n",
|
| 344 |
+
"print(frontal_1img[\"ViewPosition\"].value_counts().to_string())\n",
|
| 345 |
+
"print()\n",
|
| 346 |
+
"print(\"=== Mẫu train sau khi filter (split) ===\")\n",
|
| 347 |
+
"split_frontal = frontal_1img[\"split\"].value_counts().reindex([\"train\", \"validate\", \"test\"])\n",
|
| 348 |
+
"split_all = df.drop_duplicates(\"study_id\")[\"split\"].value_counts().reindex([\"train\", \"validate\", \"test\"])\n",
|
| 349 |
+
"compare = pd.DataFrame({\n",
|
| 350 |
+
" \"All studies\": split_all,\n",
|
| 351 |
+
" \"Frontal-only\": split_frontal,\n",
|
| 352 |
+
" \"Giảm (%)\": ((split_all - split_frontal) / split_all * 100).round(1)\n",
|
| 353 |
+
"})\n",
|
| 354 |
+
"print(compare.to_string())"
|
| 355 |
+
]
|
| 356 |
},
|
| 357 |
{
|
| 358 |
"cell_type": "code",
|
| 359 |
+
"execution_count": null,
|
| 360 |
"id": "9d4aaf5c",
|
|
|
|
| 361 |
"metadata": {},
|
| 362 |
+
"outputs": [],
|
| 363 |
+
"source": [
|
| 364 |
+
"fig, axes = plt.subplots(1, 3, figsize=(14, 4))\n",
|
| 365 |
+
"\n",
|
| 366 |
+
"# 1. All vs Frontal-only (study count)\n",
|
| 367 |
+
"cats = [\"All studies\", \"Frontal-only\"]\n",
|
| 368 |
+
"vals = [n_study_total, n_study_frontal]\n",
|
| 369 |
+
"bars = axes[0].bar(cats, vals, color=[\"#4C72B0\", \"#55A868\"], width=0.5)\n",
|
| 370 |
+
"axes[0].bar_label(bars, fmt=\"%d\")\n",
|
| 371 |
+
"axes[0].set_title(\"Study count: All vs Frontal-only\")\n",
|
| 372 |
+
"axes[0].set_ylabel(\"Số study\")\n",
|
| 373 |
+
"\n",
|
| 374 |
+
"# 2. View breakdown của ảnh được chọn\n",
|
| 375 |
+
"vc = frontal_1img[\"ViewPosition\"].value_counts()\n",
|
| 376 |
+
"axes[1].pie(vc.values, labels=vc.index, autopct=\"%1.1f%%\",\n",
|
| 377 |
+
" colors=[\"#4C72B0\", \"#DD8452\"])\n",
|
| 378 |
+
"axes[1].set_title(\"View được chọn (AP ưu tiên)\")\n",
|
| 379 |
+
"\n",
|
| 380 |
+
"# 3. So sánh train/val/test\n",
|
| 381 |
+
"x = np.arange(3)\n",
|
| 382 |
+
"w = 0.35\n",
|
| 383 |
+
"splits = [\"train\", \"validate\", \"test\"]\n",
|
| 384 |
+
"axes[2].bar(x - w/2, split_all.values, w, label=\"All\", color=\"#4C72B0\", alpha=0.85)\n",
|
| 385 |
+
"axes[2].bar(x + w/2, split_frontal.values, w, label=\"Frontal-only\", color=\"#55A868\", alpha=0.85)\n",
|
| 386 |
+
"axes[2].set_xticks(x)\n",
|
| 387 |
+
"axes[2].set_xticklabels(splits)\n",
|
| 388 |
+
"axes[2].set_title(\"Frontal-only vs All (per split)\")\n",
|
| 389 |
+
"axes[2].set_ylabel(\"Số study\")\n",
|
| 390 |
+
"axes[2].legend()\n",
|
| 391 |
+
"\n",
|
| 392 |
+
"plt.suptitle(\"Frontal-Only Sampling Strategy — p18\", fontsize=13)\n",
|
| 393 |
+
"plt.tight_layout()\n",
|
| 394 |
+
"plt.show()"
|
| 395 |
+
]
|
| 396 |
},
|
| 397 |
{
|
| 398 |
"cell_type": "markdown",
|
| 399 |
+
"id": "28847d0b",
|
| 400 |
"metadata": {},
|
| 401 |
"source": [
|
| 402 |
"## 5. CheXpert Labels — 14 nhãn bệnh lý"
|
|
|
|
| 405 |
{
|
| 406 |
"cell_type": "code",
|
| 407 |
"execution_count": null,
|
| 408 |
+
"id": "410fbdbe",
|
| 409 |
"metadata": {},
|
| 410 |
"outputs": [],
|
| 411 |
"source": [
|
|
|
|
| 432 |
{
|
| 433 |
"cell_type": "code",
|
| 434 |
"execution_count": null,
|
| 435 |
+
"id": "50c9a91d",
|
| 436 |
"metadata": {},
|
| 437 |
"outputs": [],
|
| 438 |
+
"source": [
|
| 439 |
+
"fig, ax = plt.subplots(figsize=(12, 5))\n",
|
| 440 |
+
"x = np.arange(len(label_cols))\n",
|
| 441 |
+
"w = 0.25\n",
|
| 442 |
+
"\n",
|
| 443 |
+
"ordered_labels = label_summary.sort_values(\"Positive\", ascending=False).index.tolist()\n",
|
| 444 |
+
"\n",
|
| 445 |
+
"ax.bar(x - w, label_summary.loc[ordered_labels, \"Positive\"], width=w, label=\"Positive\", color=\"#e74c3c\")\n",
|
| 446 |
+
"ax.bar(x, label_summary.loc[ordered_labels, \"Uncertain\"], width=w, label=\"Uncertain\", color=\"#f39c12\")\n",
|
| 447 |
+
"ax.bar(x + w, label_summary.loc[ordered_labels, \"Negative\"], width=w, label=\"Negative\", color=\"#2ecc71\")\n",
|
| 448 |
+
"\n",
|
| 449 |
+
"ax.set_xticks(x)\n",
|
| 450 |
+
"ax.set_xticklabels(ordered_labels, rotation=40, ha=\"right\", fontsize=9)\n",
|
| 451 |
+
"ax.set_ylabel(\"Số study\")\n",
|
| 452 |
+
"ax.set_title(\"CheXpert Labels — Positive / Uncertain / Negative (p18)\")\n",
|
| 453 |
+
"ax.legend()\n",
|
| 454 |
+
"plt.tight_layout()\n",
|
| 455 |
+
"plt.show()"
|
| 456 |
+
]
|
| 457 |
},
|
| 458 |
{
|
| 459 |
"cell_type": "code",
|
| 460 |
"execution_count": null,
|
| 461 |
+
"id": "1e1209c5",
|
| 462 |
"metadata": {},
|
| 463 |
"outputs": [],
|
| 464 |
"source": [
|
|
|
|
| 479 |
},
|
| 480 |
{
|
| 481 |
"cell_type": "markdown",
|
| 482 |
+
"id": "f0aa1ba8",
|
| 483 |
"metadata": {},
|
| 484 |
"source": [
|
| 485 |
"## 6. Phân tích Report — Findings & Impression"
|
|
|
|
| 488 |
{
|
| 489 |
"cell_type": "code",
|
| 490 |
"execution_count": null,
|
| 491 |
+
"id": "8b1e562e",
|
| 492 |
"metadata": {},
|
| 493 |
"outputs": [],
|
| 494 |
"source": [
|
|
|
|
| 548 |
{
|
| 549 |
"cell_type": "code",
|
| 550 |
"execution_count": null,
|
| 551 |
+
"id": "c49a401a",
|
| 552 |
"metadata": {},
|
| 553 |
"outputs": [],
|
| 554 |
"source": [
|
|
|
|
| 562 |
{
|
| 563 |
"cell_type": "code",
|
| 564 |
"execution_count": null,
|
| 565 |
+
"id": "942959d1",
|
| 566 |
"metadata": {},
|
| 567 |
"outputs": [],
|
| 568 |
"source": [
|
|
|
|
| 596 |
{
|
| 597 |
"cell_type": "code",
|
| 598 |
"execution_count": null,
|
| 599 |
+
"id": "170b0971",
|
| 600 |
"metadata": {},
|
| 601 |
"outputs": [],
|
| 602 |
"source": [
|
|
|
|
| 617 |
},
|
| 618 |
{
|
| 619 |
"cell_type": "markdown",
|
| 620 |
+
"id": "5512e3aa",
|
| 621 |
"metadata": {},
|
| 622 |
"source": [
|
| 623 |
"## 7. VQA — phân tích câu hỏi & đáp"
|
|
|
|
| 626 |
{
|
| 627 |
"cell_type": "code",
|
| 628 |
"execution_count": null,
|
| 629 |
+
"id": "7caa394c",
|
| 630 |
"metadata": {},
|
| 631 |
"outputs": [],
|
| 632 |
"source": [
|
|
|
|
| 654 |
{
|
| 655 |
"cell_type": "code",
|
| 656 |
"execution_count": null,
|
| 657 |
+
"id": "ddb012a8",
|
| 658 |
"metadata": {},
|
| 659 |
"outputs": [],
|
| 660 |
"source": [
|
|
|
|
| 666 |
{
|
| 667 |
"cell_type": "code",
|
| 668 |
"execution_count": null,
|
| 669 |
+
"id": "86eec60e",
|
| 670 |
"metadata": {},
|
| 671 |
"outputs": [],
|
| 672 |
"source": [
|
|
|
|
| 684 |
{
|
| 685 |
"cell_type": "code",
|
| 686 |
"execution_count": null,
|
| 687 |
+
"id": "4567a110",
|
| 688 |
"metadata": {},
|
| 689 |
"outputs": [],
|
| 690 |
"source": [
|
|
|
|
| 710 |
"plt.show()"
|
| 711 |
]
|
| 712 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 713 |
{
|
| 714 |
"cell_type": "code",
|
| 715 |
"execution_count": null,
|
| 716 |
+
"id": "f968f772",
|
| 717 |
"metadata": {},
|
| 718 |
"outputs": [],
|
| 719 |
"source": [
|
|
|
|
| 732 |
{
|
| 733 |
"cell_type": "code",
|
| 734 |
"execution_count": null,
|
| 735 |
+
"id": "97179573",
|
| 736 |
"metadata": {},
|
| 737 |
"outputs": [],
|
| 738 |
"source": [
|
|
|
|
| 758 |
{
|
| 759 |
"cell_type": "code",
|
| 760 |
"execution_count": null,
|
| 761 |
+
"id": "9ffe116e",
|
| 762 |
"metadata": {},
|
| 763 |
"outputs": [],
|
| 764 |
"source": [
|
|
|
|
| 789 |
},
|
| 790 |
{
|
| 791 |
"cell_type": "markdown",
|
| 792 |
+
"id": "37f8ee29",
|
| 793 |
"metadata": {},
|
| 794 |
"source": [
|
| 795 |
"## 8. Gợi ý thêm — Missing data & Data Quality"
|
|
|
|
| 798 |
{
|
| 799 |
"cell_type": "code",
|
| 800 |
"execution_count": null,
|
| 801 |
+
"id": "c0a10b57",
|
| 802 |
"metadata": {},
|
| 803 |
"outputs": [],
|
| 804 |
"source": [
|
|
|
|
| 816 |
{
|
| 817 |
"cell_type": "code",
|
| 818 |
"execution_count": null,
|
| 819 |
+
"id": "f2fe0d2e",
|
| 820 |
"metadata": {},
|
| 821 |
"outputs": [],
|
| 822 |
"source": [
|
|
|
|
| 828 |
{
|
| 829 |
"cell_type": "code",
|
| 830 |
"execution_count": null,
|
| 831 |
+
"id": "4b3a9176",
|
| 832 |
"metadata": {},
|
| 833 |
"outputs": [],
|
| 834 |
"source": [
|
|
|
|
| 847 |
{
|
| 848 |
"cell_type": "code",
|
| 849 |
"execution_count": null,
|
| 850 |
+
"id": "ea4da928",
|
| 851 |
"metadata": {},
|
| 852 |
"outputs": [],
|
| 853 |
"source": [
|
|
|
|
| 870 |
{
|
| 871 |
"cell_type": "code",
|
| 872 |
"execution_count": null,
|
| 873 |
+
"id": "9b990ae5",
|
| 874 |
"metadata": {},
|
| 875 |
"outputs": [],
|
| 876 |
"source": [
|
|
|
|
| 881 |
"\n",
|
| 882 |
" res_counts = df.groupby([\"Rows\", \"Columns\"]).size().sort_values(ascending=False).head(15)\n",
|
| 883 |
" print(\"\\nTop-15 resolutions:\")\n",
|
| 884 |
+
" print(res_counts.to_string())\n",
|
| 885 |
+
"else:\n",
|
| 886 |
" print(\"Cột Rows/Columns không có trong metadata.\")"
|
| 887 |
]
|
| 888 |
},
|
| 889 |
{
|
| 890 |
"cell_type": "markdown",
|
| 891 |
+
"id": "a03900eb",
|
| 892 |
"metadata": {},
|
| 893 |
"source": [
|
| 894 |
"## 9. Tóm tắt (Summary)"
|
|
|
|
| 897 |
{
|
| 898 |
"cell_type": "code",
|
| 899 |
"execution_count": null,
|
| 900 |
+
"id": "f8cc6c50",
|
| 901 |
"metadata": {},
|
| 902 |
"outputs": [],
|
| 903 |
"source": [
|
|
|
|
| 930 |
},
|
| 931 |
"language_info": {
|
| 932 |
"name": "python",
|
| 933 |
+
"version": "3.11.7"
|
| 934 |
}
|
| 935 |
},
|
| 936 |
"nbformat": 4,
|
| 937 |
"nbformat_minor": 5
|
| 938 |
+
}
|
data/eda_reports.ipynb
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
data/img_stat.py
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pandas as pd
|
| 2 |
+
import matplotlib.pyplot as plt
|
| 3 |
+
|
| 4 |
+
# Đọc file CSV
|
| 5 |
+
# Thay "images.csv" bằng đường dẫn file của bạn
|
| 6 |
+
df = pd.read_csv(r"D:\USTH\KLTN\cxr-vlm-data\mimic-cxr-2.0.0-metadata.csv")
|
| 7 |
+
|
| 8 |
+
# Kiểm tra các cột cần thiết
|
| 9 |
+
required_cols = ["Rows", "Columns"]
|
| 10 |
+
for col in required_cols:
|
| 11 |
+
if col not in df.columns:
|
| 12 |
+
raise ValueError(f"Thiếu cột: {col}")
|
| 13 |
+
|
| 14 |
+
# Tạo thêm cột diện tích ảnh
|
| 15 |
+
df["TotalPixels"] = df["Rows"] * df["Columns"]
|
| 16 |
+
|
| 17 |
+
# Thống kê cơ bản
|
| 18 |
+
print("===== THỐNG KÊ KÍCH THƯỚC ẢNH =====")
|
| 19 |
+
print(df[["Rows", "Columns", "TotalPixels"]].describe())
|
| 20 |
+
|
| 21 |
+
# Tỉ lệ khung hình
|
| 22 |
+
df["AspectRatio"] = df["Columns"] / df["Rows"]
|
| 23 |
+
|
| 24 |
+
print("\n===== THỐNG KÊ TỈ LỆ KHUNG HÌNH =====")
|
| 25 |
+
print(df["AspectRatio"].describe())
|
| 26 |
+
|
| 27 |
+
# -------------------------
|
| 28 |
+
# Histogram chiều cao
|
| 29 |
+
# -------------------------
|
| 30 |
+
plt.figure(figsize=(8, 5))
|
| 31 |
+
plt.hist(df["Rows"], bins=30)
|
| 32 |
+
plt.xlabel("Rows (Height)")
|
| 33 |
+
plt.ylabel("Number of Images")
|
| 34 |
+
plt.title("Distribution of Image Heights")
|
| 35 |
+
plt.grid(True)
|
| 36 |
+
plt.show()
|
| 37 |
+
|
| 38 |
+
# -------------------------
|
| 39 |
+
# Histogram chiều rộng
|
| 40 |
+
# -------------------------
|
| 41 |
+
plt.figure(figsize=(8, 5))
|
| 42 |
+
plt.hist(df["Columns"], bins=30)
|
| 43 |
+
plt.xlabel("Columns (Width)")
|
| 44 |
+
plt.ylabel("Number of Images")
|
| 45 |
+
plt.title("Distribution of Image Widths")
|
| 46 |
+
plt.grid(True)
|
| 47 |
+
plt.show()
|
| 48 |
+
|
| 49 |
+
# -------------------------
|
| 50 |
+
# Histogram tổng pixel
|
| 51 |
+
# -------------------------
|
| 52 |
+
plt.figure(figsize=(8, 5))
|
| 53 |
+
plt.hist(df["TotalPixels"], bins=30)
|
| 54 |
+
plt.xlabel("Total Pixels")
|
| 55 |
+
plt.ylabel("Number of Images")
|
| 56 |
+
plt.title("Distribution of Image Sizes")
|
| 57 |
+
plt.grid(True)
|
| 58 |
+
plt.show()
|
| 59 |
+
|
| 60 |
+
# -------------------------
|
| 61 |
+
# Scatter plot Width vs Height
|
| 62 |
+
# -------------------------
|
| 63 |
+
plt.figure(figsize=(7, 7))
|
| 64 |
+
plt.scatter(df["Columns"], df["Rows"], alpha=0.5)
|
| 65 |
+
plt.xlabel("Width (Columns)")
|
| 66 |
+
plt.ylabel("Height (Rows)")
|
| 67 |
+
plt.title("Image Resolution Distribution")
|
| 68 |
+
plt.grid(True)
|
| 69 |
+
plt.show()
|
| 70 |
+
|
| 71 |
+
# -------------------------
|
| 72 |
+
# Top resolution phổ biến nhất
|
| 73 |
+
# -------------------------
|
| 74 |
+
resolution_counts = (
|
| 75 |
+
df.groupby(["Rows", "Columns"])
|
| 76 |
+
.size()
|
| 77 |
+
.reset_index(name="Count")
|
| 78 |
+
.sort_values("Count", ascending=False)
|
| 79 |
+
)
|
| 80 |
+
|
| 81 |
+
print("\n===== TOP RESOLUTION PHỔ BIẾN =====")
|
| 82 |
+
print(resolution_counts.head(10))
|
dev/eval_labelers.py
CHANGED
|
@@ -11,15 +11,15 @@ from sklearn.metrics import (
|
|
| 11 |
)
|
| 12 |
|
| 13 |
# ── Cấu hình — chỉnh 4 dòng này ──────────────────────────────────────────────
|
| 14 |
-
CHEXPERT_PATH = r"mimic-cxr-2.0.0-chexpert.csv
|
| 15 |
-
NEGBIO_PATH = r"mimic-cxr-2.0.0-negbio.csv
|
| 16 |
-
GT_PATH = r"mimic-cxr-2.1.0-test-set-labeled.csv"
|
| 17 |
|
| 18 |
# Cách xử lý nhãn uncertain (-1):
|
| 19 |
# "positive" → coi là có bệnh (mặc định, conservative)
|
| 20 |
# "negative" → coi là không có bệnh
|
| 21 |
# "drop" → bỏ hẳn các study có uncertain
|
| 22 |
-
UNCERTAIN_STRATEGY = "
|
| 23 |
# ─────────────────────────────────────────────────────────────────────────────
|
| 24 |
|
| 25 |
PATHOLOGIES = [
|
|
@@ -133,7 +133,7 @@ def main():
|
|
| 133 |
|
| 134 |
summary = pd.DataFrame([res_chx, res_neg]).set_index("tool")
|
| 135 |
print("=" * 60)
|
| 136 |
-
print("OVERALL METRICS (uncertain strategy: '{}')".format(
|
| 137 |
print("=" * 60)
|
| 138 |
print(summary.to_string(float_format="{:.4f}".format))
|
| 139 |
|
|
|
|
| 11 |
)
|
| 12 |
|
| 13 |
# ── Cấu hình — chỉnh 4 dòng này ──────────────────────────────────────────────
|
| 14 |
+
CHEXPERT_PATH = r"D:\USTH\KLTN\cxr-vlm-data\mimic-cxr-2.0.0-chexpert.csv"
|
| 15 |
+
NEGBIO_PATH = r"D:\USTH\KLTN\cxr-vlm-data\mimic-cxr-2.0.0-negbio.csv"
|
| 16 |
+
GT_PATH = r"D:\USTH\KLTN\cxr-vlm-data\mimic-cxr-2.1.0-test-set-labeled.csv"
|
| 17 |
|
| 18 |
# Cách xử lý nhãn uncertain (-1):
|
| 19 |
# "positive" → coi là có bệnh (mặc định, conservative)
|
| 20 |
# "negative" → coi là không có bệnh
|
| 21 |
# "drop" → bỏ hẳn các study có uncertain
|
| 22 |
+
UNCERTAIN_STRATEGY = "negative"
|
| 23 |
# ─────────────────────────────────────────────────────────────────────────────
|
| 24 |
|
| 25 |
PATHOLOGIES = [
|
|
|
|
| 133 |
|
| 134 |
summary = pd.DataFrame([res_chx, res_neg]).set_index("tool")
|
| 135 |
print("=" * 60)
|
| 136 |
+
print("OVERALL METRICS (uncertain strategy: '{}')".format(UNCERTAIN_STRATEGY))
|
| 137 |
print("=" * 60)
|
| 138 |
print(summary.to_string(float_format="{:.4f}".format))
|
| 139 |
|
dev/labeler_comparison.csv
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
,CheXpert_F1,NegBio_F1,Winner,Δ
|
| 2 |
+
Atelectasis,0.9465648854961832,0.9518987341772152,NegBio,-0.0053
|
| 3 |
+
Cardiomegaly,0.8363636363636363,0.8466666666666667,NegBio,-0.0103
|
| 4 |
+
Consolidation,0.832,0.907563025210084,NegBio,-0.0756
|
| 5 |
+
Edema,0.8897058823529411,0.8805970149253731,CheXpert,0.0091
|
| 6 |
+
Enlarged Cardiomediastinum,0.5,0.49019607843137253,CheXpert,0.0098
|
| 7 |
+
Fracture,0.8131868131868132,0.8470588235294118,NegBio,-0.0339
|
| 8 |
+
Lung Lesion,0.7889908256880734,0.7850467289719626,CheXpert,0.0039
|
| 9 |
+
No Finding,0.5376344086021505,0.5252525252525253,CheXpert,0.0124
|
| 10 |
+
Pleural Effusion,0.9265536723163842,0.9274809160305344,NegBio,-0.0009
|
| 11 |
+
Pleural Other,0.4918032786885246,0.5,NegBio,-0.0082
|
| 12 |
+
Pneumonia,0.5362318840579711,0.5801526717557252,NegBio,-0.0439
|
| 13 |
+
Pneumothorax,0.7083333333333334,0.7560975609756098,NegBio,-0.0478
|
| 14 |
+
Support Devices,0.8830645161290323,0.8806584362139918,CheXpert,0.0024
|