{"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":16202081,"datasetId":10387933,"databundleVersionId":17181346},{"sourceType":"datasetVersion","sourceId":16192991,"datasetId":10382390,"databundleVersionId":17171506},{"sourceType":"datasetVersion","sourceId":16236510,"datasetId":10410805,"databundleVersionId":17218803}],"dockerImageVersionId":31328,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!pip install -q ultralytics\n!pip install pydicom","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-13T03:27:08.465624Z","iopub.execute_input":"2026-05-13T03:27:08.466095Z","iopub.status.idle":"2026-05-13T03:27:18.164573Z","shell.execute_reply.started":"2026-05-13T03:27:08.466032Z","shell.execute_reply":"2026-05-13T03:27:18.16369Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport pydicom\nfrom pydicom.pixel_data_handlers.util import apply_voi_lut\nimport os, glob, cv2\nfrom ultralytics import YOLO\nfrom tqdm import tqdm\n\n# --- CẤU HÌNH ---\nMODEL_PATH = '/kaggle/input/datasets/nhthongnguyn17/model/best.pt'\nTEST_DIR = '/kaggle/input/competitions/vinbigdata-chest-xray-abnormalities-detection/test'\nBATCH_SIZE = 16  # Tăng/giảm tùy thuộc vào dung lượng RAM\nCONF_THRESHOLD = 0.01 # Kaggle thường để thấp để lấy nhiều box\nIMG_SIZE = 512\n\n# --- HÀM XỬ LÝ ---\ndef read_xray(path):\n    dicom = pydicom.dcmread(path)\n    try:\n        data = apply_voi_lut(dicom.pixel_array, dicom)\n    except:\n        data = dicom.pixel_array\n    \n    if dicom.PhotometricInterpretation == \"MONOCHROME1\":\n        data = np.amax(data) - data\n        \n    data = data - np.min(data)\n    data = data / np.max(data)\n    data = (data * 255).astype(np.uint8)\n    \n    # Resize về 512 ngay từ đầu để tiết kiệm RAM khi lưu vào list\n    # Nhưng phải lưu lại kích thước gốc để scale tọa độ sau này\n    orig_h, orig_w = dicom.Rows, dicom.Columns\n    return cv2.cvtColor(data, cv2.COLOR_GRAY2RGB), orig_h, orig_w\n\n# --- THỰC THI ---\nmodel = YOLO(MODEL_PATH)\ntest_files = glob.glob(os.path.join(TEST_DIR, \"**/*.dicom\"), recursive=True)\nresults_list = []\n\n# Chia danh sách file thành từng batch\nfor i in tqdm(range(0, len(test_files), BATCH_SIZE)):\n    batch_paths = test_files[i : i + BATCH_SIZE]\n    batch_imgs = []\n    batch_info = []\n\n    # 1. Tiền xử lý theo batch (Có thể dùng ThreadPoolExecutor ở đây để nhanh hơn)\n    for path in batch_paths:\n        img, h, w = read_xray(path)\n        batch_imgs.append(img)\n        batch_info.append((os.path.basename(path).replace('.dicom', ''), h, w))\n\n    # 2. Batch Inference (Sử dụng half=True để tăng tốc)\n    # YOLO sẽ tự động resize về 512, nhưng vì ta đã resize sẵn nên sẽ nhanh hơn\n    preds = model.predict(batch_imgs, imgsz=IMG_SIZE, conf=CONF_THRESHOLD, half=True, verbose=False)\n\n    # 3. Hậu xử lý kết quả\n    for idx, res in enumerate(preds):\n        image_id, orig_h, orig_w = batch_info[idx]\n        prediction_string = \"\"\n        \n        boxes = res.boxes\n        if len(boxes) == 0:\n            prediction_string = \"14 1 0 0 1 1\"\n        else:\n            for box in boxes:\n                c = int(box.cls[0])\n                conf = float(box.conf[0])\n                # Tính toán tỉ lệ để scale tọa độ ngược về ảnh gốc\n                # Formula: x_orig = x_pred * (W_orig / W_input)\n                coords = box.xyxy[0].cpu().numpy()\n                \n                # YOLOv8 trả về tọa độ đã được scale theo ảnh đưa vào (ở đây là ảnh gốc hoặc 512)\n                # Nếu bạn đưa mảng đã resize vào, hãy nhân tỉ lệ tại đây.\n                # Do YOLOv8 tự động handle scale nếu pass mảng vào, ta lấy trực tiếp:\n                x1, y1, x2, y2 = coords\n                \n                prediction_string += f\"{c} {conf:.4f} {int(x1)} {int(y1)} {int(x2)} {int(y2)} \"\n\n        results_list.append({\"image_id\": image_id, \"PredictionString\": prediction_string.strip()})\n\n# 4. Xuất file\nsubmission_df = pd.DataFrame(results_list)\nsubmission_df.to_csv('submission.csv', index=False)","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-05-13T03:27:32.111136Z","iopub.execute_input":"2026-05-13T03:27:32.111932Z"}},"outputs":[],"execution_count":null}]}