{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","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},{"sourceType":"datasetVersion","sourceId":1810939,"datasetId":1075804,"databundleVersionId":1848423}],"dockerImageVersionId":31329,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!pip install ensemble-boxes\n!pip install 'git+https://github.com/facebookresearch/detectron2.git'","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-05-13T01:28:50.192969Z","iopub.execute_input":"2026-05-13T01:28:50.193594Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CẤU HÌNH ĐƯỜNG DẪN\n# ============================================================\nTRAIN_CSV   = '/kaggle/input/vinbigdata-chest-xray-abnormalities-detection/train.csv'\nMETA_CSV    = '/kaggle/input/vinbigdata-chest-xray-resized-png-256x256/train_meta.csv'\nIMG_DIR     = '/kaggle/input/vinbigdata-chest-xray-resized-png-256x256/train'\nTARGET_SIZE = 256\n\nprint('Đường dẫn đã cấu hình xong!')","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\n# ============================================================\n# TỰ ĐỘNG FIX ĐƯỜNG DẪN - QUÉT TOÀN BỘ INPUT\n# ============================================================\nTRAIN_CSV = \"\"\nMETA_CSV  = \"\"\nIMG_DIR   = \"\"\n\nfor root, dirs, files in os.walk('/kaggle/input'):\n    # 1. Tìm file train.csv (thường nằm trong folder cuộc thi)\n    if 'train.csv' in files and not TRAIN_CSV:\n        TRAIN_CSV = os.path.join(root, 'train.csv')\n    \n    # 2. Tìm file train_meta.csv (trong folder đã resize)\n    if 'train_meta.csv' in files:\n        META_CSV = os.path.join(root, 'train_meta.csv')\n        # Folder ảnh 'train' thường nằm ngay cạnh file meta này\n        if 'train' in dirs:\n            IMG_DIR = os.path.join(root, 'train')\n\nTARGET_SIZE = 256\n\nprint('--- Đã tìm thấy các đường dẫn chuẩn: ---')\nprint(f'TRAIN_CSV: {TRAIN_CSV if TRAIN_CSV else \"❌ Không thấy train.csv\"}')\nprint(f'META_CSV:  {META_CSV if META_CSV else \"❌ Không thấy train_meta.csv\"}')\nprint(f'IMG_DIR:   {IMG_DIR if IMG_DIR else \"❌ Không thấy folder ảnh\"}')","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\n\ndf_train = pd.read_csv(TRAIN_CSV)\ndf_meta  = pd.read_csv(META_CSV)\n\nprint(f'train.csv: {len(df_train)} dòng, {df_train[\"image_id\"].nunique()} ảnh')\n\ndf = pd.merge(df_train, df_meta, on='image_id', how='left')\n\ndf['x_min'] = df['x_min'] * (TARGET_SIZE / df['dim1'])\ndf['x_max'] = df['x_max'] * (TARGET_SIZE / df['dim1'])\ndf['y_min'] = df['y_min'] * (TARGET_SIZE / df['dim0'])\ndf['y_max'] = df['y_max'] * (TARGET_SIZE / df['dim0'])\n\ndf_updated = df.drop(columns=['dim0', 'dim1'])\ndf_updated.to_csv('train_256x256.csv', index=False)\n\nprint('Resize xong! Lưu tại train_256x256.csv')\nprint(df_updated[['image_id', 'class_id', 'x_min', 'y_min', 'x_max', 'y_max']].head())","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nfrom ensemble_boxes import weighted_boxes_fusion\n\ndef apply_wbf(df, target_size=256, iou_thr=0.45, skip_box_thr=0.0001):\n    new_data = []\n    image_ids = df['image_id'].unique()\n\n    for idx, img_id in enumerate(image_ids):\n        if idx % 2000 == 0:\n            print(f'  Đang xử lý {idx}/{len(image_ids)} ảnh...')\n\n        img_df = df[df['image_id'] == img_id]\n        actual_boxes = img_df[img_df['class_id'] != 14]\n\n        if len(actual_boxes) == 0:\n            new_data.append([img_id, 'No finding', 14, 'WBF', np.nan, np.nan, np.nan, np.nan])\n            continue\n\n        boxes_list  = []\n        scores_list = []\n        labels_list = []\n\n        for rad_id, rad_group in actual_boxes.groupby('rad_id'):\n            rad_boxes  = []\n            rad_scores = []\n            rad_labels = []\n\n            for _, row in rad_group.iterrows():\n                x_min = min(max(row['x_min'] / target_size, 0.0), 1.0)\n                y_min = min(max(row['y_min'] / target_size, 0.0), 1.0)\n                x_max = min(max(row['x_max'] / target_size, 0.0), 1.0)\n                y_max = min(max(row['y_max'] / target_size, 0.0), 1.0)\n\n                if x_min >= x_max or y_min >= y_max:\n                    continue\n\n                rad_boxes.append([x_min, y_min, x_max, y_max])\n                rad_scores.append(1.0)\n                rad_labels.append(int(row['class_id']))\n\n            if len(rad_boxes) > 0:\n                boxes_list.append(rad_boxes)\n                scores_list.append(rad_scores)\n                labels_list.append(rad_labels)\n\n        if len(boxes_list) == 0:\n            new_data.append([img_id, 'No finding', 14, 'WBF', np.nan, np.nan, np.nan, np.nan])\n            continue\n\n        boxes, scores, labels = weighted_boxes_fusion(\n            boxes_list, scores_list, labels_list,\n            iou_thr=iou_thr, skip_box_thr=skip_box_thr\n        )\n\n        for i in range(len(boxes)):\n            new_data.append([\n                img_id, '', int(labels[i]), 'WBF',\n                boxes[i][0] * target_size, boxes[i][1] * target_size,\n                boxes[i][2] * target_size, boxes[i][3] * target_size\n            ])\n\n    return pd.DataFrame(new_data, columns=['image_id', 'class_name', 'class_id', 'rad_id',\n                                            'x_min', 'y_min', 'x_max', 'y_max'])\n\nprint('Bắt đầu WBF...')\ndf_resized = pd.read_csv('train_256x256.csv')\ndf_wbf = apply_wbf(df_resized, target_size=TARGET_SIZE)\ndf_wbf.to_csv('train_wbf_256.csv', index=False)\nprint(f'Xong! Số ảnh: {df_wbf[\"image_id\"].nunique()}, Số box: {len(df_wbf)}')","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nfrom sklearn.model_selection import train_test_split\n\ndf_wbf = pd.read_csv('train_wbf_256.csv')\nall_ids = df_wbf['image_id'].unique()\ntrain_ids, val_ids = train_test_split(all_ids, test_size=0.1, random_state=42)\n\ndf_wbf[df_wbf['image_id'].isin(train_ids)].to_csv('train_final.csv', index=False)\ndf_wbf[df_wbf['image_id'].isin(val_ids)].to_csv('val_final.csv',   index=False)\n\nprint(f'Train: {len(train_ids)} ảnh')\nprint(f'Val  : {len(val_ids)} ảnh')","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport pandas as pd\nfrom detectron2.structures import BoxMode\nfrom detectron2.data import DatasetCatalog, MetadataCatalog\n\nCLASS_NAMES = [\n    'Aortic enlargement', 'Atelectasis', 'Calcification', 'Cardiomegaly',\n    'Consolidation', 'ILD', 'Infiltration', 'Lung Opacity',\n    'Nodule/Mass', 'Other lesion', 'Pleural effusion',\n    'Pleural thickening', 'Pneumothorax', 'Pulmonary fibrosis'\n]\n\ndef get_vinbigdata_dicts(imgdir, csvpath):\n    df = pd.read_csv(csvpath)\n    dataset_dicts = []\n\n    for img_id in df['image_id'].unique():\n        record = {\n            'file_name' : os.path.join(imgdir, img_id + '.png'),\n            'image_id'  : img_id,\n            'height'    : TARGET_SIZE,\n            'width'     : TARGET_SIZE,\n        }\n        objs = []\n        for _, row in df[df['image_id'] == img_id].iterrows():\n            if row['class_id'] == 14 or pd.isna(row['x_min']):\n                continue\n            objs.append({\n                'bbox'       : [float(row['x_min']), float(row['y_min']),\n                                float(row['x_max']), float(row['y_max'])],\n                'bbox_mode'  : BoxMode.XYXY_ABS,\n                'category_id': int(row['class_id']),\n            })\n        record['annotations'] = objs\n        dataset_dicts.append(record)\n\n    return dataset_dicts\n\nfor split, csv in [('vin_train', 'train_final.csv'), ('vin_val', 'val_final.csv')]:\n    if split in DatasetCatalog:\n        DatasetCatalog.remove(split)\n    DatasetCatalog.register(split, lambda imgdir=IMG_DIR, csv=csv: get_vinbigdata_dicts(imgdir, csv))\n    MetadataCatalog.get(split).set(thing_classes=CLASS_NAMES)\n\nprint('Đã đăng ký vin_train và vin_val!')","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%writefile train.py\nimport os\nimport pandas as pd\nfrom detectron2.engine import DefaultTrainer, launch\nfrom detectron2.config import get_cfg\nfrom detectron2 import model_zoo\nfrom detectron2.data import DatasetCatalog, MetadataCatalog\nfrom detectron2.structures import BoxMode\n\n# --- TỰ ĐỘNG TÌM ĐƯỜNG DẪN TRONG MÁY ẢO ---\nTRAIN_CSV = \"\"\nMETA_CSV  = \"\"\nIMG_DIR   = \"\"\nfor root, dirs, files in os.walk('/kaggle/input'):\n    if 'train.csv' in files and not TRAIN_CSV:\n        TRAIN_CSV = os.path.join(root, 'train.csv')\n    if 'train_meta.csv' in files:\n        META_CSV = os.path.join(root, 'train_meta.csv')\n        if 'train' in dirs:\n            IMG_DIR = os.path.join(root, 'train')\n\nTARGET_SIZE = 256\nCLASS_NAMES = [\n    'Aortic enlargement', 'Atelectasis', 'Calcification', 'Cardiomegaly',\n    'Consolidation', 'ILD', 'Infiltration', 'Lung Opacity',\n    'Nodule/Mass', 'Other lesion', 'Pleural effusion',\n    'Pleural thickening', 'Pneumothorax', 'Pulmonary fibrosis'\n]\n\ndef get_vinbigdata_dicts(imgdir, csvpath):\n    df = pd.read_csv(csvpath)\n    dataset_dicts = []\n    for img_id in df['image_id'].unique():\n        img_path = os.path.join(imgdir, img_id + '.png')\n        \n        # KIỂM TRA FILE CÓ THỰC SỰ TỒN TẠI KHÔNG TRƯỚC KHI ĐĂNG KÝ\n        if not os.path.exists(img_path):\n            continue\n            \n        record = {\n            'file_name' : img_path,\n            'image_id'  : img_id,\n            'height'    : TARGET_SIZE,\n            'width'     : TARGET_SIZE,\n        }\n        objs = []\n        for _, row in df[df['image_id'] == img_id].iterrows():\n            if row['class_id'] == 14 or pd.isna(row['x_min']):\n                continue\n            objs.append({\n                'bbox'       : [float(row['x_min']), float(row['y_min']),\n                                float(row['x_max']), float(row['y_max'])],\n                'bbox_mode'  : BoxMode.XYXY_ABS,\n                'category_id': int(row['class_id']),\n            })\n        record['annotations'] = objs\n        dataset_dicts.append(record)\n    return dataset_dicts\n\ndef setup_cfg():\n    cfg = get_cfg()\n    cfg.merge_from_file(model_zoo.get_config_file('COCO-Detection/faster_rcnn_R_101_FPN_3x.yaml'))\n    cfg.DATASETS.TRAIN = ('vin_train',)\n    cfg.DATASETS.TEST  = ('vin_val',)\n    cfg.MODEL.WEIGHTS  = model_zoo.get_checkpoint_url('COCO-Detection/faster_rcnn_R_101_FPN_3x.yaml')\n    cfg.SOLVER.IMS_PER_BATCH     = 8\n    cfg.SOLVER.BASE_LR           = 0.0005\n    cfg.SOLVER.MAX_ITER          = 20000\n    cfg.SOLVER.CHECKPOINT_PERIOD = 1000\n    cfg.MODEL.ROI_HEADS.NUM_CLASSES       = 14\n    cfg.MODEL.ROI_HEADS.SCORE_THRESH_TEST = 0.05\n    cfg.INPUT.MIN_SIZE_TRAIN = (256,)\n    cfg.INPUT.MAX_SIZE_TRAIN = 256\n    cfg.INPUT.MIN_SIZE_TEST  = 256\n    cfg.INPUT.MAX_SIZE_TEST  = 256\n    cfg.OUTPUT_DIR = './output'\n    return cfg\n\ndef main():\n    for split, csv in [('vin_train', 'train_final.csv'), ('vin_val', 'val_final.csv')]:\n        if split in DatasetCatalog:\n            DatasetCatalog.remove(split)\n        DatasetCatalog.register(split, lambda imgdir=IMG_DIR, csv=csv: get_vinbigdata_dicts(imgdir, csv))\n        MetadataCatalog.get(split).set(thing_classes=CLASS_NAMES)\n\n    cfg = setup_cfg()\n    os.makedirs(cfg.OUTPUT_DIR, exist_ok=True)\n    trainer = DefaultTrainer(cfg)\n    trainer.resume_or_load(resume=False)\n    trainer.train()\n\nif __name__ == '__main__':\n    launch(main, num_gpus_per_machine=2, num_machines=1, machine_rank=0, dist_url='auto')","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!python train.py","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from detectron2.evaluation import COCOEvaluator, inference_on_dataset\nfrom detectron2.data import build_detection_test_loader\nfrom detectron2.engine import DefaultPredictor\nfrom detectron2.config import get_cfg\nfrom detectron2 import model_zoo\n\ncfg = get_cfg()\ncfg.merge_from_file(model_zoo.get_config_file('COCO-Detection/faster_rcnn_R_101_FPN_3x.yaml'))\ncfg.MODEL.ROI_HEADS.NUM_CLASSES       = 14\ncfg.MODEL.ROI_HEADS.SCORE_THRESH_TEST = 0.05\ncfg.INPUT.MIN_SIZE_TEST = 256\ncfg.INPUT.MAX_SIZE_TEST = 256\ncfg.MODEL.WEIGHTS = '/kaggle/working/output/model_final.pth'\n\npredictor  = DefaultPredictor(cfg)\nevaluator  = COCOEvaluator('vin_val', output_dir='./output')\nval_loader = build_detection_test_loader(cfg, 'vin_val')\n\nresults = inference_on_dataset(predictor.model, val_loader, evaluator)\nprint('\\n📊 KẾT QUẢ mAP TRÊN VALIDATION SET:')\nprint(results)","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}