{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[],"dockerImageVersionId":28755,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# Cell 1: Import dependencies\nimport os\nimport pydicom # Untuk membaca file MRI mentah\nimport pandas as pd\nimport numpy as np\nimport cv2\nimport matplotlib.pyplot as plt\n\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\nimport torchvision.transforms as transforms\nimport torchvision.models as models\n\n# Setup device (GPU)\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nprint(f\"Menggunakan device: {device}\")","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-06-05T16:18:29.736358Z","iopub.execute_input":"2026-06-05T16:18:29.736620Z","iopub.status.idle":"2026-06-05T16:18:39.036304Z","shell.execute_reply.started":"2026-06-05T16:18:29.736596Z","shell.execute_reply":"2026-06-05T16:18:39.035492Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Cell 2: Cek Data Input & Custom Patient-Level Split\nimport pandas as pd\nfrom sklearn.model_selection import train_test_split\n\n# 1. Load Data CSV dengan path yang sudah dikoreksi\ntrain_csv_path = '/kaggle/input/competitions/rsna-2024-lumbar-spine-degenerative-classification/train.csv'\ncoords_csv_path = '/kaggle/input/competitions/rsna-2024-lumbar-spine-degenerative-classification/train_label_coordinates.csv'\n\ndf_train = pd.read_csv(train_csv_path)\ndf_coords = pd.read_csv(coords_csv_path)\n\nprint(\"=== 1. Cek Struktur Data Awal ===\")\nprint(f\"Total pasien unik (study_id) di train.csv: {df_train['study_id'].nunique()}\")\nprint(f\"Total titik koordinat anomali di train_label_coordinates.csv: {len(df_coords)} baris\\n\")\n\n# 2. Patient-Level Split (Mencegah Data Leakage)\nunique_patients = df_train['study_id'].unique()\n\n# Split tahap 1: Sisihkan 20% untuk pengujian, 80% untuk pelatihan\ntrain_patients, temp_patients = train_test_split(unique_patients, test_size=0.2, random_state=42)\n\n# Split tahap 2: Bagi yang 20% tadi menjadi 10% Validation dan 10% Test\nval_patients, test_patients = train_test_split(temp_patients, test_size=0.5, random_state=42)\n\n# 3. Filter dataframe berdasarkan hasil split\ndf_train_split = df_train[df_train['study_id'].isin(train_patients)]\ndf_val_split = df_train[df_train['study_id'].isin(val_patients)]\ndf_test_split = df_train[df_train['study_id'].isin(test_patients)]\n\nprint(\"=== 2. Hasil Custom Split (Berdasarkan ID Pasien) ===\")\nprint(f\"Data Latih (Train)\\t: {len(df_train_split['study_id'].unique())} pasien\")\nprint(f\"Data Validasi (Val)\\t: {len(df_val_split['study_id'].unique())} pasien\")\nprint(f\"Data Uji Internal (Test): {len(df_test_split['study_id'].unique())} pasien\")\n\nprint(\"\\n=== 3. Cuplikan Data Koordinat (5 Baris Pertama) ===\")\ndisplay(df_coords.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-05T16:25:11.017934Z","iopub.execute_input":"2026-06-05T16:25:11.018705Z","iopub.status.idle":"2026-06-05T16:25:13.156856Z","shell.execute_reply.started":"2026-06-05T16:25:11.018673Z","shell.execute_reply":"2026-06-05T16:25:13.156207Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Cell 3 (Revisi): Visualisasi Grid (Multiple Samples) Citra MRI & Titik Anomali\nimport pydicom\nimport matplotlib.pyplot as plt\nimport os\nimport random\n\nbase_image_dir = '/kaggle/input/competitions/rsna-2024-lumbar-spine-degenerative-classification/train_images'\n\n# Siapkan canvas Grid 2x2\nfig, axes = plt.subplots(2, 2, figsize=(15, 15))\naxes = axes.flatten()\n\n# Ambil 4 baris acak dari tabel koordinat agar variatif\nrandom.seed(42) # Set seed agar hasil acaknya tetap sama saat di-run ulang\nrandom_indices = random.sample(range(len(df_coords)), 4)\n\nfor i, idx in enumerate(random_indices):\n    sample = df_coords.iloc[idx] \n    study_id = str(sample['study_id'])\n    series_id = str(sample['series_id'])\n    instance_number = str(sample['instance_number'])\n    \n    condition = sample['condition']\n    level = sample['level']\n    x_coord = sample['x']\n    y_coord = sample['y']\n    \n    # Susun path file\n    dcm_path = os.path.join(base_image_dir, study_id, series_id, f\"{instance_number}.dcm\")\n    \n    if os.path.exists(dcm_path):\n        dicom_data = pydicom.dcmread(dcm_path)\n        image_array = dicom_data.pixel_array\n        \n        # Plot gambar di grid yang sesuai\n        axes[i].imshow(image_array, cmap='gray')\n        axes[i].scatter(x_coord, y_coord, color='red', s=150, marker='X')\n        \n        judul = f\"ID: {study_id}\\n{condition.replace('_', ' ').title()}\\nRuas: {level} | (X:{x_coord:.0f}, Y:{y_coord:.0f})\"\n        axes[i].set_title(judul, fontsize=12, pad=10)\n        axes[i].axis('off')\n    else:\n        axes[i].set_title(\"Gambar tidak ditemukan\", color='red')\n        axes[i].axis('off')\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-05T16:29:35.997572Z","iopub.execute_input":"2026-06-05T16:29:35.998144Z","iopub.status.idle":"2026-06-05T16:29:39.861214Z","shell.execute_reply.started":"2026-06-05T16:29:35.998101Z","shell.execute_reply":"2026-06-05T16:29:39.860018Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Cell 4: Fungsi Pembuat Spatial Attention Mask (Gaussian Heatmap)\nimport numpy as np\nimport cv2\n\ndef generate_spatial_mask(image_shape, x, y, sigma=15):\n    \"\"\"\n    Menghasilkan 2D Gaussian Heatmap berdasarkan koordinat x, y.\n    Ini adalah kunci dari mekanisme Spatial Attention di judul penelitian.\n    \"\"\"\n    mask = np.zeros(image_shape, dtype=np.float32)\n    \n    # Jika koordinat kosong (NaN), kembalikan mask hitam (tidak ada anomali)\n    if pd.isna(x) or pd.isna(y):\n        return mask\n        \n    x, y = int(x), int(y)\n    \n    # Buat grid koordinat\n    Y, X = np.ogrid[:image_shape[0], :image_shape[1]]\n    \n    # Rumus distribusi Gaussian 2D\n    dist_sq = (X - x)**2 + (Y - y)**2\n    mask = np.exp(-dist_sq / (2 * sigma**2))\n    \n    return mask\n\nprint(\"Fungsi generate_spatial_mask berhasil diinisialisasi!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-05T16:29:00.890646Z","iopub.execute_input":"2026-06-05T16:29:00.891442Z","iopub.status.idle":"2026-06-05T16:29:00.897536Z","shell.execute_reply.started":"2026-06-05T16:29:00.891409Z","shell.execute_reply":"2026-06-05T16:29:00.896643Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Cell 5: Custom Dataset untuk Model Spatial-Aware CNN\nfrom torch.utils.data import Dataset\nimport torch\n\nclass RSNAAttentionDataset(Dataset):\n    def __init__(self, df_labels, df_coords, base_dir, transform=None):\n        self.df_labels = df_labels.reset_index(drop=True)\n        self.df_coords = df_coords\n        self.base_dir = base_dir\n        self.transform = transform\n        \n        # Mapping label keparahan ke format numerik\n        self.label_map = {'Normal/Mild': 0, 'Moderate': 1, 'Severe': 2}\n\n    def __len__(self):\n        return len(self.df_labels)\n\n    def __getitem__(self, idx):\n        # 1. Ambil info dasar\n        row = self.df_labels.iloc[idx]\n        study_id = str(row['study_id'])\n        \n        # Untuk demonstrasi/baseline, kita cari 1 gambar dcm pertama dari study_id ini\n        # Pada riset sesungguhnya, ini akan di-loop untuk mencari dcm yang spesifik\n        patient_dir = os.path.join(self.base_dir, study_id)\n        dcm_files = glob.glob(os.path.join(patient_dir, \"*/*.dcm\"))\n        \n        image_array = np.zeros((512, 512), dtype=np.float32) # Gambar default jika gagal baca\n        spatial_mask = np.zeros((512, 512), dtype=np.float32)\n        \n        if dcm_files:\n            try:\n                # Baca MRI\n                dcm_data = pydicom.dcmread(dcm_files[0])\n                image_array = dcm_data.pixel_array.astype(np.float32)\n                \n                # Normalisasi piksel gambar ke 0-1\n                image_array = (image_array - np.min(image_array)) / (np.max(image_array) - np.min(image_array) + 1e-8)\n                \n                # Resize ke 512x512 agar ukurannya seragam untuk masuk ke CNN\n                image_array = cv2.resize(image_array, (512, 512))\n                \n                # 2. Cari koordinat yang sesuai dari df_coords (jika ada)\n                # Di sini kita ambil koordinat pertama yang cocok dengan study_id\n                coord_match = self.df_coords[self.df_coords['study_id'] == int(study_id)]\n                if not coord_match.empty:\n                    x_asli = coord_match.iloc[0]['x']\n                    y_asli = coord_match.iloc[0]['y']\n                    \n                    # Generate Spatial Mask (Heatmap)\n                    spatial_mask = generate_spatial_mask((512, 512), x_asli, y_asli)\n                    \n            except Exception as e:\n                pass # Lewati jika dcm rusak\n                \n        # 3. Ambil Label Klasifikasi (Ambil kondisi pertama sebagai contoh)\n        # Pada df_train ada kolom seperti 'spinal_canal_stenosis_l1_l2'\n        # Kita ambil satu kolom label valid untuk dilatih\n        label_text = 'Normal/Mild' # Default\n        valid_cols = [col for col in self.df_labels.columns if 'spinal_canal' in col]\n        if valid_cols and not pd.isna(row[valid_cols[0]]):\n            label_text = row[valid_cols[0]]\n            \n        label_idx = self.label_map.get(label_text, 0)\n        \n        # Ubah ke Tensor PyTorch\n        image_tensor = torch.tensor(image_array).unsqueeze(0) # Channel 1 (Grayscale)\n        mask_tensor = torch.tensor(spatial_mask).unsqueeze(0)\n        label_tensor = torch.tensor(label_idx, dtype=torch.long)\n        \n        # Return 3 elemen penting untuk judul penelitianmu!\n        return image_tensor, mask_tensor, label_tensor\n\nprint(\"Dataset Class siap digunakan!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-05T16:29:11.995863Z","iopub.execute_input":"2026-06-05T16:29:11.996807Z","iopub.status.idle":"2026-06-05T16:29:12.007929Z","shell.execute_reply.started":"2026-06-05T16:29:11.996753Z","shell.execute_reply":"2026-06-05T16:29:12.007280Z"}},"outputs":[],"execution_count":null}]}