{"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":"nvidiaTeslaT4","dataSources":[{"sourceType":"competition","sourceId":24800,"datasetId":1042002,"databundleVersionId":1831594}],"dockerImageVersionId":31153,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# ===============================================================\n# 📦 VinBigData DICOM → JPG + YOLO Label Converter\n# Author: ChatGPT (GPT-5) | Verified for Kaggle 2025\n# ===============================================================\n!pip install -q pydicom tqdm pillow","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-10-15T19:40:31.318072Z","iopub.execute_input":"2025-10-15T19:40:31.318593Z","iopub.status.idle":"2025-10-15T19:40:34.457167Z","shell.execute_reply.started":"2025-10-15T19:40:31.318568Z","shell.execute_reply":"2025-10-15T19:40:34.456381Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os, random\nimport numpy as np\nimport pandas as pd\nfrom pathlib import Path\nfrom PIL import Image\nimport pydicom\nfrom tqdm import tqdm\n\n# =====================\n# CONFIG\n# =====================\nOUT_IMG_SIZE = (512, 512)   # Resize to 512x512\nVAL_FRAC = 0.1              # 10% validation split\nRANDOM_SEED = 42\nrandom.seed(RANDOM_SEED)\nnp.random.seed(RANDOM_SEED)\n\n# =====================\n# INPUT PATHS\n# =====================\nROOT_IN = Path(\"/kaggle/input/vinbigdata-chest-xray-abnormalities-detection\")\nTRAIN_CSV = ROOT_IN / \"train.csv\"\nSAMPLE_SUB = ROOT_IN / \"sample_submission.csv\"\nTRAIN_DICOM_DIR = ROOT_IN / \"train\"\nTEST_DICOM_DIR = ROOT_IN / \"test\"\n\n# =====================\n# OUTPUT PATHS\n# =====================\nWORK_DIR = Path(\"/kaggle/working/vindr\")\nIMG_DIR = WORK_DIR / \"images\"\nLAB_DIR = WORK_DIR / \"labels\"\nfor split in [\"train\", \"val\", \"test\"]:\n    (IMG_DIR / split).mkdir(parents=True, exist_ok=True)\n    (LAB_DIR / split).mkdir(parents=True, exist_ok=True)\n\n# =====================\n# LOAD TRAIN DATA\n# =====================\ndf = pd.read_csv(TRAIN_CSV)\nprint(\"📄 train.csv rows:\", len(df))\nprint(\"Unique images in CSV:\", df['image_id'].nunique())\n\n# Ensure class_id is int\ndf[\"class_id\"] = df[\"class_id\"].astype(int)\n\n# =====================\n# SPLIT IMAGES\n# =====================\nall_images = sorted([f.stem for f in TRAIN_DICOM_DIR.glob(\"*.dicom\")])\nrandom.shuffle(all_images)\nn_val = int(len(all_images) * VAL_FRAC)\nval_ids = set(all_images[:n_val])\ntrain_ids = set(all_images[n_val:])\nprint(f\"Train: {len(train_ids)} | Val: {len(val_ids)}\")\n\n# =====================\n# DICOM to JPEG helper\n# =====================\ndef dicom_to_pil(dicom_path):\n    ds = pydicom.dcmread(str(dicom_path))\n    img = ds.pixel_array.astype(np.float32)\n    lo, hi = np.percentile(img, (0.5, 99.5))\n    img = np.clip(img, lo, hi)\n    img = (img - img.min()) / (img.max() - img.min() + 1e-6)\n    img = (img * 255).astype(np.uint8)\n    return Image.fromarray(img).convert(\"RGB\")\n\n# =====================\n# YOLO label writer\n# =====================\ndef write_yolo_label(image_id, boxes_for_image, out_label_path, img_w, img_h):\n    lines = []\n    for _, row in boxes_for_image.iterrows():\n        cls = int(row[\"class_id\"])\n        # skip \"No finding\" rows — no box, but keep empty file\n        if cls == 14:\n            continue\n        # skip NaN coords\n        if any(pd.isna(row[c]) for c in [\"x_min\",\"y_min\",\"x_max\",\"y_max\"]):\n            continue\n        x_min, y_min, x_max, y_max = row[[\"x_min\",\"y_min\",\"x_max\",\"y_max\"]]\n        if x_max <= x_min or y_max <= y_min:\n            continue\n        # normalize\n        cx = (x_min + x_max) / 2 / img_w\n        cy = (y_min + y_max) / 2 / img_h\n        w = (x_max - x_min) / img_w\n        h = (y_max - y_min) / img_h\n        lines.append(f\"{cls} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}\")\n    # write label file\n    with open(out_label_path, \"w\") as f:\n        if lines:\n            f.write(\"\\n\".join(lines))\n        else:\n            f.write(\"\")  # empty = No Finding\n\n# =====================\n# CONVERSION LOOP\n# =====================\ndef convert_split(ids_set, split_name):\n    count, skipped = 0, 0\n    for img_id in tqdm(ids_set, desc=f\"Converting {split_name}\"):\n        dicom_path = TRAIN_DICOM_DIR / f\"{img_id}.dicom\"\n        if not dicom_path.exists():\n            skipped += 1\n            continue\n        try:\n            pil = dicom_to_pil(dicom_path)\n        except Exception as e:\n            print(f\"⚠️ Error reading {img_id}: {e}\")\n            skipped += 1\n            continue\n\n        if OUT_IMG_SIZE:\n            pil = pil.resize(OUT_IMG_SIZE)\n        out_img_path = IMG_DIR / split_name / f\"{img_id}.jpg\"\n        out_lbl_path = LAB_DIR / split_name / f\"{img_id}.txt\"\n        pil.save(out_img_path, quality=95)\n\n        boxes = df[df[\"image_id\"] == img_id]\n        write_yolo_label(img_id, boxes, out_lbl_path, pil.width, pil.height)\n        count += 1\n\n    print(f\"✅ {split_name} done: {count} converted, {skipped} skipped\")\n\ndef convert_test():\n    sample_sub = pd.read_csv(SAMPLE_SUB)\n    test_ids = sample_sub[\"image_id\"].tolist()\n    for img_id in tqdm(test_ids, desc=\"Converting test\"):\n        dicom_path = TEST_DICOM_DIR / f\"{img_id}.dicom\"\n        if not dicom_path.exists():\n            continue\n        try:\n            pil = dicom_to_pil(dicom_path)\n        except Exception:\n            continue\n        if OUT_IMG_SIZE:\n            pil = pil.resize(OUT_IMG_SIZE)\n        out_img_path = IMG_DIR / \"test\" / f\"{img_id}.jpg\"\n        out_lbl_path = LAB_DIR / \"test\" / f\"{img_id}.txt\"\n        pil.save(out_img_path, quality=95)\n        open(out_lbl_path, 'w').close()\n\n# =====================\n# RUN CONVERSIONS\n# =====================\nconvert_split(train_ids, \"train\")\nconvert_split(val_ids, \"val\")\nconvert_test()\n\n# =====================\n# YAML for YOLOv8\n# =====================\nyaml_path = WORK_DIR / \"vinbigdata_yolo.yaml\"\nclass_names = [\n    \"Aortic_enlargement\",\"Atelectasis\",\"Calcification\",\"Cardiomegaly\",\n    \"Consolidation\",\"ILD\",\"Infiltration\",\"Lung_Opacity\",\"Nodule_Mass\",\n    \"Other_lesion\",\"Pleural_effusion\",\"Pleural_thickening\",\n    \"Pneumothorax\",\"Pulmonary_fibrosis\",\"No_finding\"\n]\nwith open(yaml_path, \"w\") as f:\n    f.write(f\"path: {WORK_DIR}\\n\")\n    f.write(\"train: images/train\\n\")\n    f.write(\"val: images/val\\n\")\n    f.write(\"test: images/test\\n\")\n    f.write(f\"nc: {len(class_names)}\\n\")\n    f.write(\"names: \" + str(class_names) + \"\\n\")\n\nprint(f\"\\n✅ Conversion Completed!\")\nprint(f\"YAML file saved to: {yaml_path}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-14T13:26:40.592991Z","iopub.execute_input":"2025-10-14T13:26:40.593811Z","iopub.status.idle":"2025-10-14T18:42:52.859475Z","shell.execute_reply.started":"2025-10-14T13:26:40.593781Z","shell.execute_reply":"2025-10-14T18:42:52.858819Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os, numpy as np\n\nlabel_dir = \"/kaggle/working/vindr/labels/train\"\nids = set()\nfor f in os.listdir(label_dir):\n    with open(os.path.join(label_dir, f)) as fp:\n        for line in fp:\n            if line.strip():\n                cid = int(line.split()[0])\n                ids.add(cid)\nprint(\"Unique class IDs in labels:\", sorted(list(ids)))\nprint(\"Expected 0–13 for diseases, 14 empty = No finding handled via empty files.\")\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import shutil\nfrom IPython.display import FileLink, display\n\n# 1️⃣ Define the folder or files you want to zip\n# Example: 'output' is your folder with generated files\nfolder_to_zip = '/kaggle/working/'\nzip_filename = 'results.zip'\n\n# 2️⃣ Create the ZIP file\nshutil.make_archive('results', 'zip', folder_to_zip)\n\n# 3️⃣ Display download link inside notebook\ndisplay(FileLink(zip_filename))\n\n# ✅ Optional (for Google Colab-like behavior, may work in some browsers)\n# from IPython.display import Javascript\n# display(Javascript('window.open(\"/kaggle/working/results.zip\")'))\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}