{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":24800,"databundleVersionId":1831594,"isSourceIdPinned":false,"sourceType":"competition"},{"sourceId":282211068,"sourceType":"kernelVersion"},{"sourceId":663363,"sourceType":"modelInstanceVersion","isSourceIdPinned":false,"modelInstanceId":501969,"modelId":517132},{"sourceId":663373,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":501977,"modelId":517140}],"dockerImageVersionId":31192,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# import pandas as pd\n# import numpy as np\n# import torch\n# from torch.utils.data import Dataset, DataLoader\n# import os\n# import pydicom # Thư viện mới để đọc DICOM\n# from PIL import Image # Vẫn dùng để resize và chuyển đổi (tùy chọn)\n\n# # --- Thiết lập Hằng số ---\n# # Giả sử bạn có 14 lớp bệnh lý cho VINDR-CXR\n# NUM_CLASSES = 14\n# # Kích thước ảnh đầu vào cho mô hình\n# TARGET_SIZE = 512\n# # Kích thước của Attention Map (thường nhỏ hơn ảnh để giảm tải tính toán)\n# MAP_SIZE = 64\n# # Đường dẫn (giả định)\n# IMAGE_DIR = '/kaggle/input/vinbigdata-chest-xray-abnormalities-detection/train'\n# ANNOTATION_CSV = '/kaggle/input/vinbigdata-chest-xray-abnormalities-detection/train.csv'","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-27T06:52:38.957025Z","iopub.execute_input":"2025-11-27T06:52:38.95733Z","iopub.status.idle":"2025-11-27T06:52:38.963839Z","shell.execute_reply.started":"2025-11-27T06:52:38.95731Z","shell.execute_reply":"2025-11-27T06:52:38.962393Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# import pandas as pd\n# import numpy as np\n# import torch\n# from torch.utils.data import Dataset\n# import os\n# import pydicom\n# from PIL import Image\n\n# class VINDRCXRDataset(Dataset):\n#     def __init__(self, csv_file, image_dir, num_classes, target_size, map_size):\n#         \"\"\"\n#         Khởi tạo Dataset, sử dụng class_id gốc từ CSV để tạo mapping.\n#         \"\"\"\n#         self.data_df = pd.read_csv(csv_file)\n#         self.image_dir = image_dir\n#         self.target_size = target_size\n#         self.map_size = map_size\n\n#         # Tập hợp các ID ảnh duy nhất\n#         self.image_ids = self.data_df['image_id'].unique().tolist()\n        \n#         # Lấy tất cả các cặp (tên lớp, ID gốc) duy nhất\n#         class_mapping_data = self.data_df[['class_name', 'class_id']].drop_duplicates()\n        \n#         # ⚠️ PHẦN SỬA LỖI: Tạo mapping từ Tên Lớp -> ID Gốc (dựa vào cột class_id)\n#         self.class_to_id = {}\n#         pathology_class_count = 0\n        \n#         for _, row in class_mapping_data.iterrows():\n#             name = row['class_name']\n#             original_id = row['class_id']\n            \n#             # Chỉ ánh xạ 14 lớp bệnh lý (ID gốc 0 đến 13).\n#             # Lớp \"No finding\" thường có ID 14 và bị loại trừ.\n#             if name.lower() != 'no finding' and 0 <= original_id <= 13: \n#                  self.class_to_id[name] = int(original_id) # Đảm bảo ID là số nguyên\n#                  pathology_class_count += 1\n            \n#         # Thiết lập số lượng lớp chính xác\n#         self.num_classes = pathology_class_count\n#         if self.num_classes != 14:\n#              print(f\"Cảnh báo: Số lượng lớp bệnh lý sau ánh xạ là {self.num_classes} thay vì 14.\")\n             \n#     def __len__(self):\n#         return len(self.image_ids)\n    \n#     def __getitem__(self, idx):\n#         image_id = self.image_ids[idx]\n#         img_annotations = self.data_df[self.data_df['image_id'] == image_id]\n        \n#         # --- 1. Load và Tiền xử lý Ảnh (X) ---\n#         dicom_path = os.path.join(self.image_dir, f'{image_id}.dicom')\n        \n#         try:\n#             dicom = pydicom.dcmread(dicom_path)\n#             pixel_array = dicom.pixel_array.astype(np.float32)\n#         except Exception as e:\n#             return None, None, None, image_id \n\n#         # Lấy kích thước ảnh gốc và kiểm tra\n#         original_h, original_w = pixel_array.shape \n#         if original_w <= 0 or original_h <= 0:\n#             return None, None, None, image_id \n        \n#         # Chuẩn hóa, resize ảnh và chuyển sang Tensor\n#         pixel_array = (pixel_array - pixel_array.min()) / (pixel_array.max() - pixel_array.min())\n#         image = Image.fromarray(np.uint8(pixel_array * 255))\n#         image = image.resize((self.target_size, self.target_size))\n#         image = np.array(image, dtype=np.float32) / 255.0\n#         image = torch.from_numpy(image).unsqueeze(0) \n\n#         # --- 2. Tạo Classification Label (Y_cls) ---\n#         cls_label = torch.zeros(self.num_classes, dtype=torch.float32) \n#         present_classes = img_annotations['class_name'].unique()\n        \n#         for cls_name in present_classes:\n#             cls_id = self.class_to_id.get(cls_name)\n            \n#             if cls_id is not None: \n#                 cls_label[cls_id] = 1.0\n                \n#         # --- 3. Tạo Attention Maps (Y_map) cho từng lớp ---\n#         attn_maps = torch.zeros((self.num_classes, self.map_size, self.map_size), dtype=torch.float32)\n        \n#         # Duyệt qua TẤT CẢ các bbox \n#         for _, row in img_annotations.iterrows():\n#             cls_name = row['class_name']\n#             cls_id = self.class_to_id.get(cls_name)\n            \n#             # BỎ QUA 'No finding' VÀ DỮ LIỆU THIẾU\n#             if cls_id is None: \n#                 continue\n            \n#             if pd.isna(row[['x_min', 'y_min', 'x_max', 'y_max']]).any():\n#                 continue \n\n#             # Tọa độ bbox (chuẩn hóa về kích thước Map_Size)\n#             x_min = int(row['x_min'] * (self.map_size / original_w))\n#             y_min = int(row['y_min'] * (self.map_size / original_h))\n#             x_max = int(row['x_max'] * (self.map_size / original_w))\n#             y_max = int(row['y_max'] * (self.map_size / original_h))\n\n#             # Giới hạn tọa độ\n#             x_min = max(0, x_min)\n#             y_min = max(0, y_min)\n#             x_max = min(self.map_size, x_max)\n#             y_max = min(self.map_size, y_max)\n            \n#             # Vẽ bbox lên slice (Map) tương ứng. \n#             if x_max > x_min and y_max > y_min:\n#                 attn_maps[cls_id, y_min:y_max, x_min:x_max] = 1.0\n                \n#         return image, cls_label, attn_maps, image_id","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-27T06:52:40.954084Z","iopub.execute_input":"2025-11-27T06:52:40.955219Z","iopub.status.idle":"2025-11-27T06:52:40.972931Z","shell.execute_reply.started":"2025-11-27T06:52:40.955187Z","shell.execute_reply":"2025-11-27T06:52:40.971769Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# # --- Khởi tạo và Test ---\n# dataset = VINDRCXRDataset(\n#     csv_file=ANNOTATION_CSV, \n#     image_dir=IMAGE_DIR, \n#     num_classes=NUM_CLASSES, \n#     target_size=TARGET_SIZE, \n#     map_size=MAP_SIZE\n# )\n\n# dataloader = DataLoader(dataset, batch_size=1, shuffle=False)\n# print(dataloader)\n\n# # Lấy một batch (item) đầu tiên\n# image_batch, cls_label_batch, attn_maps_batch, image_id_batch = next(iter(dataloader))\n\n# print(\"--- KẾT QUẢ CỦA DATALOADER ---\")\n# print(f\"Ảnh ID: {image_id_batch[0]}\")\n# print(f\"1. Kích thước Ảnh (X): {image_batch.shape}\")\n# print(f\"2. Classification Label (Y_cls): {cls_label_batch.shape}\")\n# print(f\"   Các lớp có mặt (giá trị = 1): {torch.nonzero(cls_label_batch)}\")\n# print(f\"3. Attention Maps (Y_map): {attn_maps_batch.shape}\")\n# print(\"-\" * 30)\n\n# # Lấy kết quả item đầu tiên (Batch size = 1)\n# image = image_batch[0]\n# cls_label = cls_label_batch[0]\n# attn_maps = attn_maps_batch[0]\n\n# # --- PHÂN TÍCH VÀ TRỰC QUAN HÓA LOGIC ---\n\n# # 1. Classification Label (Y_cls)\n# print(\"\\n🔥 Y_cls (Nhãn Phân loại):\")\n# print(cls_label)\n# # Với img_001, giá trị 1.0 sẽ ở vị trí [0] (Class_0) và [1] (Class_1)\n\n# # 2. Attention Maps (Y_map) - Kiểm tra lớp 0 (trùng lặp) và lớp 1 (duy nhất)\n# print(\"\\n🔥 Y_map (Heatmaps):\")\n# # Kiểm tra Heatmap cho Class_0 (index 0)\n# attn_map_class_0 = attn_maps[0]\n# print(f\"Độ lớn trung bình của Map Class 0: {attn_map_class_0.mean():.4f}\")\n# print(f\"Giá trị max của Map Class 0: {attn_map_class_0.max():.4f}\")\n\n# # Kiểm tra Heatmap cho Class_1 (index 1)\n# attn_map_class_1 = attn_maps[1]\n# print(f\"Độ lớn trung bình của Map Class 1: {attn_map_class_1.mean():.4f}\")\n# print(f\"Giá trị max của Map Class 1: {attn_map_class_1.max():.4f}\")\n\n# # Kiểm tra một lớp không có mặt (ví dụ Class 5)\n# attn_map_class_5 = attn_maps[5]\n# print(f\"Độ lớn trung bình của Map Class 5: {attn_map_class_5.mean():.4f}\")\n# # Giá trị trung bình và max của map này phải là 0 vì nó không có bbox\n\n# # --- HƯỚNG DẪN TRỰC QUAN HÓA ---\n\n# # Bạn có thể dùng thư viện matplotlib để trực quan hóa các map này:\n# # \n# # import matplotlib.pyplot as plt\n# # \n# # def visualize_map(img_tensor, map_tensor, cls_id):\n# #     # Chuẩn bị ảnh và map\n# #     img_np = img_tensor.permute(1, 2, 0).cpu().numpy() # CWH -> HWC\n# #     map_np = map_tensor.cpu().numpy()\n# #     \n# #     fig, ax = plt.subplots(1, 1, figsize=(8, 8))\n# #     ax.imshow(img_np)\n# #     \n# #     # Resize map về kích thước ảnh để chồng lên (interpolation)\n# #     # Dùng mask để chỉ hiển thị vùng > 0\n# #     mask = np.where(map_np > 0, 1.0, np.nan)\n# #     ax.imshow(mask, cmap='jet', alpha=0.5, \n# #               extent=[0, TARGET_SIZE, TARGET_SIZE, 0], # Đảm bảo đúng tọa độ\n# #               interpolation='nearest')\n# #               \n# #     ax.set_title(f'Attention Map for Class {cls_id}')\n# #     plt.show()\n\n# # # Chạy trực quan hóa cho các lớp có mặt\n# # visualize_map(image, attn_maps[0], 0)\n# # visualize_map(image, attn_maps[1], 1)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-27T06:47:25.267788Z","iopub.execute_input":"2025-11-27T06:47:25.268699Z","iopub.status.idle":"2025-11-27T06:47:27.663068Z","shell.execute_reply.started":"2025-11-27T06:47:25.268667Z","shell.execute_reply":"2025-11-27T06:47:27.662137Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# # 1. Xác định image_id bạn muốn test\n# TEST_IMAGE_ID = '47ed17dcb2cbeec15182ed335a8b5a9e' \n\n# # 2. Tìm chỉ số (index) của ID đó trong dataset\n# try:\n#     test_idx = dataset.image_ids.index(TEST_IMAGE_ID)\n# except ValueError:\n#     print(f\"Lỗi: Không tìm thấy image_id '{TEST_IMAGE_ID}' trong dataset.\")\n#     test_idx = -1\n\n# if test_idx != -1:\n#     print(f\"Đã tìm thấy ID tại chỉ số: {test_idx}\")\n    \n#     # 3. Lấy dữ liệu trực tiếp từ dataset\n#     image, cls_label, attn_maps, image_id = dataset[test_idx]\n\n#     # Kiểm tra xem mẫu có bị lỗi (None) và đã được bỏ qua không\n#     if image is None:\n#         print(f\"\\n⚠️ Mẫu {image_id} bị lỗi (DICOM hoặc kích thước không hợp lệ) và đã trả về None.\")\n#     else:\n#         print(\"\\n--- KẾT QUẢ CỦA ITEM CỤ THỂ ---\")\n#         print(f\"Ảnh ID: {image_id}\")\n#         print(f\"1. Kích thước Ảnh (X): {image.shape}\")\n#         print(f\"2. Classification Label (Y_cls): {cls_label.shape}\")\n        \n#         # In ra các lớp có nhãn 1 (tức là có bệnh lý)\n#         active_indices = torch.nonzero(cls_label).squeeze(-1).tolist()\n#         if not active_indices:\n#             print(\"   => Nhãn bệnh lý: [No Finding]\")\n#         else:\n#             # Nếu cần, bạn có thể in ra tên lớp bằng cách đảo ngược self.class_to_id\n#             id_to_class = {v: k for k, v in dataset.class_to_id.items()}\n#             active_classes = [id_to_class[i] for i in active_indices]\n#             print(f\"   => Nhãn bệnh lý: {active_classes}\")\n\n#         print(f\"3. Attention Maps (Y_map): {attn_maps.shape}\")\n#         print(\"-\" * 30)\n\n#         # Trực quan hóa kết quả cho một số Map quan trọng\n#         print(\"\\n🔥 PHÂN TÍCH HEATMAPS:\")\n        \n#         # In ra thông số cho các lớp có nhãn 1.0\n#         if active_indices:\n#             for cls_id in active_indices:\n#                 map_i = attn_maps[cls_id]\n#                 cls_name = id_to_class[cls_id]\n#                 print(f\"   - Class ID {cls_id} ({cls_name}): Avg={map_i.mean():.6f}, Max={map_i.max():.6f}\")\n#         else:\n#             # Nếu là No Finding (tất cả các nhãn đều 0)\n#             map_0 = attn_maps[0]\n#             print(f\"   - Map Class 0 (Kiểm tra): Avg={map_0.mean():.6f}, Max={map_0.max():.6f} (Phải bằng 0)\")\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-27T06:53:17.484585Z","iopub.execute_input":"2025-11-27T06:53:17.485267Z","iopub.status.idle":"2025-11-27T06:53:17.934149Z","shell.execute_reply.started":"2025-11-27T06:53:17.485217Z","shell.execute_reply":"2025-11-27T06:53:17.932878Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# import matplotlib.pyplot as plt\n# import torch.nn.functional as F\n\n# def visualize_maps(image_tensor, attn_maps_tensor, active_indices, id_to_class, target_size):\n#     \"\"\"\n#     Trực quan hóa ảnh gốc và các Attention Map chồng lên nhau cho các lớp hoạt động.\n\n#     Args:\n#         image_tensor (torch.Tensor): Ảnh gốc (1 x 512 x 512).\n#         attn_maps_tensor (torch.Tensor): Các Heatmap (14 x 64 x 64).\n#         active_indices (list): Danh sách các Class ID có nhãn = 1.\n#         id_to_class (dict): Ánh xạ từ ID sang Tên lớp.\n#         target_size (int): Kích thước ảnh hiển thị (512).\n#     \"\"\"\n#     if not active_indices:\n#         print(\"Không có lớp bệnh lý nào để trực quan hóa (No Finding).\")\n#         # Chỉ hiển thị ảnh gốc nếu không có bệnh lý\n#         plt.figure(figsize=(6, 6))\n#         plt.imshow(image_tensor.squeeze(0).cpu().numpy(), cmap='gray')\n#         plt.title(\"Ảnh Gốc (No Finding)\")\n#         plt.axis('off')\n#         plt.show()\n#         return\n\n#     # Xác định số lượng biểu đồ cần vẽ\n#     num_maps = len(active_indices)\n    \n#     # Tính toán bố cục (ví dụ: 2x2, 2x3, 3x3...)\n#     cols = 3\n#     rows = (num_maps + cols - 1) // cols\n    \n#     fig, axes = plt.subplots(rows, cols, figsize=(cols * 5, rows * 5))\n#     axes = axes.flatten()\n\n#     for i, cls_id in enumerate(active_indices):\n#         ax = axes[i]\n#         cls_name = id_to_class[cls_id]\n#         map_i = attn_maps_tensor[cls_id].unsqueeze(0).unsqueeze(0) # Thêm chiều batch và channel\n\n#         # 1. Resize Heatmap về kích thước ảnh gốc (512x512)\n#         # Sử dụng Interpolation để làm mịn Heatmap\n#         # \n#         resized_map = F.interpolate(map_i, size=(target_size, target_size), mode='bilinear', align_corners=False).squeeze().cpu().numpy()\n        \n#         # 2. Hiển thị ảnh X-quang gốc (đơn kênh)\n#         # Bỏ đi chiều kênh 1 (1 x H x W -> H x W)\n#         ax.imshow(image_tensor.squeeze(0).cpu().numpy(), cmap='gray')\n        \n#         # 3. Chồng Heatmap lên ảnh\n#         # Sử dụng giá trị alpha để hiển thị độ trong suốt\n#         # Thiết lập màu sắc (ví dụ: 'jet' - nóng)\n#         ax.imshow(resized_map, cmap='jet', alpha=0.45, vmin=0, vmax=1)\n        \n#         ax.set_title(f\"ID {cls_id}: {cls_name}\", fontsize=12)\n#         ax.axis('off')\n\n#     # Ẩn các ô trống còn lại nếu số lượng map ít hơn tổng số ô\n#     for j in range(num_maps, len(axes)):\n#         fig.delaxes(axes[j])\n        \n#     plt.tight_layout()\n#     plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-27T06:52:52.706496Z","iopub.execute_input":"2025-11-27T06:52:52.706793Z","iopub.status.idle":"2025-11-27T06:52:52.718395Z","shell.execute_reply.started":"2025-11-27T06:52:52.706773Z","shell.execute_reply":"2025-11-27T06:52:52.716963Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# if image is not None:\n#     # Lấy thông tin cần thiết\n#     active_indices = torch.nonzero(cls_label).squeeze(-1).tolist()\n#     id_to_class = {v: k for k, v in dataset.class_to_id.items()}\n    \n#     # Gọi hàm để hiển thị\n#     visualize_maps(\n#         image_tensor=image, \n#         attn_maps_tensor=attn_maps, \n#         active_indices=active_indices, \n#         id_to_class=id_to_class, \n#         target_size=dataset.target_size # Kích thước 512\n#     )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-27T06:53:22.716425Z","iopub.execute_input":"2025-11-27T06:53:22.717206Z","iopub.status.idle":"2025-11-27T06:53:23.414444Z","shell.execute_reply.started":"2025-11-27T06:53:22.717168Z","shell.execute_reply":"2025-11-27T06:53:23.413353Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!python /kaggle/input/vin-train-v3/pytorch/default/1/src/train.py \\\n  --csv_path \"/kaggle/input/vinbigdata-chest-xray-abnormalities-detection/train.csv\" \\\n  --image_path \"/kaggle/input/vinbigdata-chest-xray-abnormalities-detection/train\" \\\n  --save_path \"./checkpoints\" \\\n  --epochs 30 \\\n  --batch_size 28 \\\n  --lr 1e-4","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-27T16:17:04.697646Z","iopub.execute_input":"2025-11-27T16:17:04.698167Z","iopub.status.idle":"2025-11-27T16:17:56.704777Z","shell.execute_reply.started":"2025-11-27T16:17:04.698136Z","shell.execute_reply":"2025-11-27T16:17:56.704075Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# !python /kaggle/input/vin-training/pytorch/default/1/inference.py \\\n#   --image_path \"/kaggle/input/vinbigdata-chest-xray-abnormalities-detection/train/1c32170b4af4ce1a3030eb8167753b06.dicom\" \\\n#   --checkpoint \"/kaggle/input/vindr-analysis/checkpoints_debug/best_model.pth\" \\\n#   --train_csv \"/kaggle/input/vinbigdata-chest-xray-abnormalities-detection/train.csv\" \\\n#   --train_dir \"/kaggle/input/vinbigdata-chest-xray-abnormalities-detection/train/\" --device cuda","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-27T15:51:34.124111Z","iopub.execute_input":"2025-11-27T15:51:34.124729Z","iopub.status.idle":"2025-11-27T15:51:42.301139Z","shell.execute_reply.started":"2025-11-27T15:51:34.124692Z","shell.execute_reply":"2025-11-27T15:51:42.300405Z"}},"outputs":[],"execution_count":null}]}