{"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":[{"sourceId":29653,"databundleVersionId":2420395,"sourceType":"competition"},{"sourceId":2542390,"sourceType":"datasetVersion","datasetId":1541666}],"dockerImageVersionId":31154,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!nvidia-smi","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-10-12T03:16:16.359889Z","iopub.execute_input":"2025-10-12T03:16:16.360052Z","iopub.status.idle":"2025-10-12T03:16:23.467082Z","shell.execute_reply.started":"2025-10-12T03:16:16.360035Z","shell.execute_reply":"2025-10-12T03:16:23.466276Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!pip install monai\n!pip install medpy ","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport cv2\nimport glob\nimport PIL\nimport shutil\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\nimport matplotlib.pyplot as plt\nfrom skimage import data\nfrom skimage.util import montage \nimport skimage.transform as skTrans\nfrom skimage.transform import rotate\nfrom skimage.transform import resize\nfrom PIL import Image, ImageOps  \n\n# neural imaging\nimport nilearn as nl\nimport nibabel as nib\nimport nilearn.plotting as nlplt\n\n\n# ml libs\nimport keras\nimport keras.backend as K\nfrom keras.callbacks import CSVLogger\nimport tensorflow as tf\nfrom tensorflow.keras.utils import plot_model\nfrom sklearn.preprocessing import MinMaxScaler\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import classification_report\nfrom tensorflow.keras.models import *\nfrom tensorflow.keras.layers import *\nfrom tensorflow.keras.optimizers import *\nfrom tensorflow.keras.callbacks import ModelCheckpoint, ReduceLROnPlateau, EarlyStopping, TensorBoard\nfrom tensorflow.keras import layers","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Make numpy printouts easier to read.\nnp.set_printoptions(precision=3, suppress=True)\n# DEFINE seg-areas  \nSEGMENT_CLASSES = {\n    0 : 'NOT tumor',   \n    1 : 'NECROTIC/CORE', # or NON-ENHANCING tumor CORE - RED\n    2 : 'EDEMA',  # Green\n    3 : 'ENHANCING' # original 4 -> converted into 3 later, Yellow\n}\n\n# there are 155 slices per volume\n# to start at 5 and use 145 slices means we will skip the first 5 and last 5 \nVOLUME_SLICES = 100 \nVOLUME_START_AT = 22 # first slice of volume that we will include\n\nIMG_SIZE=128\nimport tarfile\nfile = tarfile.open('../input/brats-2021-task1/BraTS2021_Training_Data.tar')\n\nfile.extractall('./BraTS2021_Training_Data')\nfile.close()\nfile = tarfile.open('../input/brats-2021-task1/BraTS2021_00621.tar')\n\nfile.extractall('./sample_img')\nfile.close()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Make numpy printouts easier to read.\nnp.set_printoptions(precision=3, suppress=True)\n# DEFINE seg-areas  \nSEGMENT_CLASSES = {\n    0 : 'NOT tumor',   \n    1 : 'NECROTIC/CORE', # or NON-ENHANCING tumor CORE - RED\n    2 : 'EDEMA',  # Green\n    3 : 'ENHANCING' # original 4 -> converted into 3 later, Yellow\n}\n\n# there are 155 slices per volume\n# to start at 5 and use 145 slices means we will skip the first 5 and last 5 \nVOLUME_SLICES = 100 \nVOLUME_START_AT = 22 # first slice of volume that we will include\n\nIMG_SIZE=128\nimport tarfile\nfile = tarfile.open('../input/brats-2021-task1/BraTS2021_Training_Data.tar')\n\nfile.extractall('./BraTS2021_Training_Data')\nfile.close()\nfile = tarfile.open('../input/brats-2021-task1/BraTS2021_00621.tar')\n\nfile.extractall('./sample_img')\nfile.close()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nfrom glob import glob\n\n# Lokasi hasil ekstraksi\nDATA_PATH = \"./BraTS2021_Training_Data\"\n\n# Hitung jumlah folder pasien\npatient_folders = [f for f in os.listdir(DATA_PATH) if os.path.isdir(os.path.join(DATA_PATH, f))]\nnum_patients = len(patient_folders)\n\n# Hitung jumlah semua file .nii.gz di dalam folder\nnii_files = glob(os.path.join(DATA_PATH, \"**\", \"*.nii.gz\"), recursive=True)\nnum_files = len(nii_files)\n\nprint(f\"🧠 Jumlah pasien (volume MRI): {num_patients}\")\nprint(f\"📄 Total file NIfTI (.nii.gz): {num_files}\")\n\n# (Opsional) tampilkan beberapa contoh folder dan file\nprint(\"\\n📂 Contoh folder:\", patient_folders[:5])\nprint(\"🧩 Contoh file:\", nii_files[:5])","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"file = tarfile.open('../input/brats-2021-task1/BraTS2021_00621.tar')\n\nfile.extractall('./sample_img')\nfile.close()\n\nnSample = os.listdir('./sample_img')\nnSample","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"TRAIN_DATASET_PATH = './BraTS2021_Training_Data/'\nnSample = os.listdir(TRAIN_DATASET_PATH + 'BraTS2021_01261')\nnSample","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_image_flair=nib.load(TRAIN_DATASET_PATH + 'BraTS2021_01261/BraTS2021_01261_flair.nii.gz').get_fdata()\ntest_image_t1=nib.load(TRAIN_DATASET_PATH + 'BraTS2021_01261/BraTS2021_01261_t1.nii.gz').get_fdata()\ntest_image_t1ce=nib.load(TRAIN_DATASET_PATH + 'BraTS2021_01261/BraTS2021_01261_t1ce.nii.gz').get_fdata()\ntest_image_t2=nib.load(TRAIN_DATASET_PATH + 'BraTS2021_01261/BraTS2021_01261_t2.nii.gz').get_fdata()\ntest_mask=nib.load(TRAIN_DATASET_PATH + 'BraTS2021_01261/BraTS2021_01261_seg.nii.gz').get_fdata()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"fig, (ax1, ax2, ax3, ax4, ax5) = plt.subplots(1,5, figsize = (20, 10))\nslice_w = 25\nax1.imshow(test_image_flair[:,:,test_image_flair.shape[0]//2-slice_w], cmap = 'gray')\nax1.set_title('Image flair')\nax2.imshow(test_image_t1[:,:,test_image_t1.shape[0]//2-slice_w], cmap = 'gray')\nax2.set_title('Image t1')\nax3.imshow(test_image_t1ce[:,:,test_image_t1ce.shape[0]//2-slice_w], cmap = 'gray')\nax3.set_title('Image t1ce')\nax4.imshow(test_image_t2[:,:,test_image_t2.shape[0]//2-slice_w], cmap = 'gray')\nax4.set_title('Image t2')\nax5.imshow(test_mask[:,:,test_mask.shape[0]//2-slice_w])\nax5.set_title('Mask')","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"fig, ax1 = plt.subplots(1, 1, figsize = (15,15))\nax1.imshow(rotate(montage(test_image_t1[50:-50,:,:]), 90, resize=True), cmap ='gray')","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"fig, ax1 = plt.subplots(1, 1, figsize = (15,15))\nax1.imshow(rotate(montage(test_mask[60:-60,:,:]), 90, resize=True), cmap ='gray')","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"niimg = nl.image.load_img(TRAIN_DATASET_PATH + 'BraTS2021_01261/BraTS2021_01261_flair.nii.gz')\nnimask = nl.image.load_img(TRAIN_DATASET_PATH + 'BraTS2021_01261/BraTS2021_01261_seg.nii.gz')\n\nfig, axes = plt.subplots(nrows=4, figsize=(30, 40))\n\n\nnlplt.plot_anat(niimg,\n                title='BraTS18_Training_001_flair.nii plot_anat',\n                axes=axes[0])\n\nnlplt.plot_epi(niimg,\n               title='BraTS18_Training_001_flair.nii plot_epi',\n               axes=axes[1])\n\nnlplt.plot_img(niimg,\n               title='BraTS18_Training_001_flair.nii plot_img',\n               axes=axes[2])\n\nnlplt.plot_roi(nimask, \n               title='BraTS18_Training_001_flair.nii with mask plot_roi',\n               bg_img=niimg, \n               axes=axes[3], cmap='Paired')\n\nplt.show()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import tarfile\nimport plotly\nfrom plotly.offline import download_plotlyjs, init_notebook_mode, plot, iplot","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport albumentations as A\nimport nibabel as nib\nimport numpy as np\n\n\nclass ImageReader:\n    def __init__(self, root: str, img_size: int = 256, normalize: bool = False, single_class: bool = False):\n        # Ukuran minimal padding (agar dimensi seragam)\n        pad_size = 256 if img_size > 256 else 224\n\n        # Gunakan 'fill' (bukan 'value') untuk Albumentations >= 1.3\n        self.resize = A.Compose([\n            A.PadIfNeeded(min_height=128, min_width=128, fill=0),\n            A.Resize(img_size, img_size)\n        ])\n\n        self.normalize = normalize\n        self.single_class = single_class\n        self.root = root\n\n    def read_file(self, path: str) -> dict:\n        \"\"\"\n        Membaca file NIfTI (.nii.gz) dan mengembalikan hasil preprocess:\n        - 'scan' : volume MRI 3D (numpy array)\n        - 'segmentation' : mask tumor 3D (numpy array)\n        - 'orig_shape' : dimensi asli sebelum resize\n        \"\"\"\n        scan_type = path.split('_')[-1]\n        raw_image = nib.load(path).get_fdata()\n        raw_mask = nib.load(path.replace(scan_type, 'seg.nii.gz')).get_fdata()\n\n        processed_frames, processed_masks = [], []\n\n        for frame_idx in range(raw_image.shape[2]):\n            frame = raw_image[:, :, frame_idx]\n            mask = raw_mask[:, :, frame_idx]\n\n            # Normalisasi (opsional)\n            if self.normalize:\n                if frame.max() > 0:\n                    frame = frame / frame.max()\n                frame = frame.astype(np.float32)\n            else:\n                frame = frame.astype(np.uint8)\n\n            # Resize + Pad\n            resized = self.resize(image=frame, mask=mask)\n\n            # Pastikan mask tetap dalam tipe uint8\n            mask_resized = resized['mask'].astype(np.uint8)\n\n            # Jika single_class=True → ubah semua nilai >0 jadi 1\n            if self.single_class:\n                mask_resized = (mask_resized > 0).astype(np.uint8)\n\n            processed_frames.append(resized['image'])\n            processed_masks.append(mask_resized)\n\n        return {\n            'scan': np.stack(processed_frames, 0),\n            'segmentation': np.stack(processed_masks, 0),\n            'orig_shape': raw_image.shape\n        }\n\n    def load_patient_scan(self, idx: int, scan_type: str = 'flair') -> dict:\n        \"\"\"\n        Memuat scan MRI pasien berdasarkan indeks ID dan tipe scan.\n        Contoh path:\n        /kaggle/working/BraTS2021_Training_Data/BraTS2021_00095/BraTS2021_00095_flair.nii.gz\n        \"\"\"\n        patient_id = str(idx).zfill(5)\n        scan_filename = f'{self.root}/BraTS2021_{patient_id}/BraTS2021_{patient_id}_{scan_type}.nii.gz'\n        return self.read_file(scan_filename)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import plotly.graph_objects as go\nimport numpy as np\n\n# generate_3d_scatter(): \n# Fungsi ini membuat sebuah objek Scatter3d dari Plotly untuk menampilkan data tiga dimensi.\n# Fungsi menerima parameter seperti x, y, z (array koordinat), colors (array warna),\n# size (ukuran titik), opacity (tingkat transparansi), scale (skala warna),\n# hover (informasi yang muncul saat kursor diarahkan), dan name (nama objek Scatter3d).\ndef generate_3d_scatter(\n    x:np.array, y:np.array, z:np.array, colors:np.array,\n    size:int=3, opacity:float=0.2, scale:str='Teal',\n    hover:str='skip', name:str='MRI'\n) -> go.Scatter3d:\n    return go.Scatter3d(\n        x=x, y=y, z=z,\n        mode='markers', hoverinfo=hover,\n        marker = dict(\n            size=size, opacity=opacity,\n            color=colors, colorscale=scale\n        ),\n        name=name\n    )\n\n\nclass ImageViewer3d():\n    # Konstruktor (__init__) dari kelas ImageViewer3d menerima parameter seperti:\n    # reader (objek ImageReader), mri_downsample (tingkat downsampling citra MRI),\n    # mri_colorscale (skala warna untuk citra MRI), dan voxel_size (ukuran voxel).\n    def __init__(self, reader:ImageReader, mri_downsample:int=10, mri_colorscale:str='Ice', voxel_size:float=0.1) -> None:\n        self.reader = reader\n        self.mri_downsample = mri_downsample\n        self.mri_colorscale = mri_colorscale\n        self.voxel_size = voxel_size\n        \n    # Metode load_clean_mri() digunakan untuk memuat data citra MRI yang sudah dibersihkan.\n    # Metode ini menerima parameter image (array numpy dari citra MRI) dan orig_dim (dimensi asli citra).\n    # Ia menghitung koordinat x, y, z dari voxel yang bernilai > 0,\n    # kemudian mengambil sampel berdasarkan faktor downsampling (mri_downsample),\n    # dan mengembalikan dictionary berisi koordinat serta warna tiap titik.\n    def load_clean_mri(self, image:np.array, orig_dim:int) -> dict:\n        shape_offset = image.shape[1]/orig_dim\n        z, x, y = (image > 0).nonzero()\n        # hanya mengambil 1 dari setiap (1/mri_downsample) sampel\n        x, y, z = x[::self.mri_downsample], y[::self.mri_downsample], z[::self.mri_downsample]\n        colors = image[z, x, y]\n        return dict(x=x/shape_offset, y=y/shape_offset, z=z, colors=colors)\n    \n    # Metode load_tumor_segmentation() digunakan untuk memuat data segmentasi tumor.\n    # Metode ini menerima parameter image (array numpy segmentasi) dan orig_dim (dimensi asli citra).\n    # Ia menghitung koordinat x, y, z dari voxel yang sesuai dengan kelas tumor tertentu,\n    # menggunakan faktor sampling berbeda (1/1, 1/3, 1/5) untuk masing-masing kelas,\n    # kemudian mengembalikan dictionary berisi koordinat dan warna tiap kelas tumor.\n    def load_tumor_segmentation(self, image:np.array, orig_dim:int) -> dict:\n        tumors = {}\n        shape_offset = image.shape[1]/orig_dim\n        # sampling 1/1, 1/3, dan 1/5 untuk kelas jaringan tumor 1 (core), 2 (invaded), dan 4 (enhancing)\n        sampling = {\n            1: 1, 2: 3, 4: 5\n        }\n        for class_idx in sampling:\n            z, x, y = (image == class_idx).nonzero()\n            x, y, z = x[::sampling[class_idx]], y[::sampling[class_idx]], z[::sampling[class_idx]]\n            tumors[class_idx] = dict(\n                x=x/shape_offset, y=y/shape_offset, z=z,\n                colors=class_idx/4\n            )\n        return tumors\n\n    \n    # Fungsi collect_patient_data digunakan untuk mengumpulkan data pasien dan menghitung\n    # parameter terkait ukuran serta distribusi area dalam citra MRI.\n    # Pertama, fungsi memuat citra MRI bersih (clean_mri) dan segmentasi tumor (tumors)\n    # dari argumen scan. \n    # Kemudian, menghitung volume voxel berdasarkan kubik dari voxel_size.\n    # Jumlah total titik (markers_created) dihitung dari total titik di citra MRI bersih\n    # dan semua area tumor.\n    # Selanjutnya, menghitung jumlah titik per area, persentase masing-masing area terhadap total,\n    # serta volume (dalam cm³) tiap area.\n    # Hasil kemudian dicetak dan fungsi mengembalikan daftar objek Scatter3d\n    # untuk setiap area (MRI bersih dan tiga tipe jaringan tumor).\n    def collect_patient_data(self, scan:dict) -> tuple:\n        clean_mri = self.load_clean_mri(scan['scan'], scan['orig_shape'][0])\n        tumors = self.load_tumor_segmentation(scan['segmentation'], scan['orig_shape'][0])\n        \n        voxel_volume = self.voxel_size ** 3\n        markers_created = clean_mri['x'].shape[0] + sum(tumors[class_idx]['x'].shape[0] for class_idx in tumors)\n        \n        clean_mri_diem = clean_mri['x'].shape[0]\n        tumor1_diem = tumors[1]['x'].shape[0]\n        tumor2_diem = tumors[2]['x'].shape[0]\n        tumor4_diem = tumors[4]['x'].shape[0]\n        \n        clean_mri_tile = round(clean_mri_diem /markers_created*100, 2)\n        tumor1_tile = round(tumor1_diem /markers_created*100, 2)\n        tumor2_tile = round(tumor2_diem /markers_created*100, 2)\n        tumor4_tile = round(tumor4_diem /markers_created*100, 2)\n \n        clean_mri_kichthuoc = str(round(clean_mri_diem * voxel_volume, 2)) + ' cm^3'\n        tumor1_kichthuoc = str(round(tumor1_diem * voxel_volume, 2)) + ' cm^3'\n        tumor2_kichthuoc = str(round(tumor2_diem * voxel_volume, 2)) + ' cm^3'\n        tumor4_kichthuoc = str(round(tumor4_diem * voxel_volume, 2)) + ' cm^3'\n        \n        print('Citra MRI otak (bersih):', clean_mri_diem ,'titik,', clean_mri_tile ,'%',clean_mri_kichthuoc)\n        print('Inti tumor:', tumor1_diem , 'titik,', tumor1_tile ,'%,', tumor1_kichthuoc)\n        print('Jaringan di sekitar yang terinfiltrasi tumor:', tumor2_diem ,'titik,', tumor2_tile ,'%,', tumor2_kichthuoc)\n        print('Area tumor yang mengalami peningkatan kontras gadolinium:', tumor4_diem ,'titik,', tumor4_tile,'%,', tumor4_kichthuoc)\n        \n        return [\n            generate_3d_scatter(**clean_mri, scale=self.mri_colorscale, opacity=0.3, hover='skip', name='Citra MRI otak (bersih) ('+ clean_mri_kichthuoc +')'),\n            generate_3d_scatter(**tumors[1], opacity=0.8, hover='all', name='Inti tumor (' + tumor1_kichthuoc + ')'),\n            generate_3d_scatter(**tumors[2], opacity=0.4, hover='all', name='Jaringan sekitar terinfiltrasi (' + tumor2_kichthuoc + ')'),\n            generate_3d_scatter(**tumors[4], opacity=0.4, hover='all', name='Area peningkatan gadolinium (' + tumor4_kichthuoc + ')'),\n        ], markers_created\n \n\n    def get_3d_scan(self, patient_idx:int, scan_type:str='flair') -> go.Figure:\n        scan = self.reader.load_patient_scan(patient_idx, scan_type)\n        data, num_markers = self.collect_patient_data(scan)\n        fig = go.Figure(data=data)\n        fig.update_layout(\n            title=f\"[Pasien id:{patient_idx}] pemindaian MRI otak ({num_markers} titik)\",\n            legend_title=\"Kelas voxel (klik untuk menampilkan/sembunyikan)\",\n            font=dict(\n                family=\"Courier New, monospace\",\n                size=14,\n            ),\n            margin=dict(\n                l=0,r=0,b=0,t=30\n            ),\n            legend=dict(itemsizing='constant')\n        )\n        return fig\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"reader = ImageReader('/kaggle/working/BraTS2021_Training_Data', img_size=128, normalize=True, single_class=False)\nviewer = ImageViewer3d(reader, mri_downsample=25)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"fig = viewer.get_3d_scan(0, 't1')\nplotly.offline.iplot(fig)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"fig = viewer.get_3d_scan(9, 't1ce')\nplotly.offline.iplot(fig)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"fig = viewer.get_3d_scan(9, 'flair')\nplotly.offline.iplot(fig)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"img_id = \"00009\"\nimport matplotlib.pyplot as plt\nfor i, nii in enumerate([f'/kaggle/working/BraTS2021_Training_Data/BraTS2021_{img_id}/BraTS2021_{img_id}_{s_type}.nii.gz' for s_type in [\"flair\", \"t1\", \"t1ce\", \"t2\", \"seg\"]]):\n    # PLOTTING\n    image = nib.load(nii).get_fdata()\n    slices = image.shape[-1]\n    rows = int(np.ceil((slices/2)/10))\n    plt.figure(figsize=(20, rows*2))\n    plt.suptitle(f\"\\n\\n\\n{nii.rsplit('_', 1)[-1].split('.', 1)[0]} SCAN\\n\".upper(), fontsize=18, fontweight=\"bold\")\n    for j in range(0, slices, 2):\n        plt.subplot(rows, 10, 1+j//2)\n        plt.axis(False)\n        plt.imshow(image[:, :, j], cmap=\"bone\")\n    plt.tight_layout()\n    plt.show()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os, glob, time\nimport numpy as np\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\nfrom tqdm import tqdm\nimport nibabel as nib\nfrom skimage.transform import resize\nfrom monai.networks.nets import UNet\nfrom medpy.metric import binary\nfrom skimage.metrics import structural_similarity as ssim\nimport matplotlib.pyplot as plt","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"DATA_DIR = \"/kaggle/working/BraTS2021_Training_Data\"\nDEVICE = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nBATCH_SIZE = 2\nLR = 1e-4\nEPOCHS = 10\nPATIENCE = 5\nSCALE_FACTORS = [1.0, 0.75, 0.5, 0.25]","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"🧠 GPU yang tersedia:\", torch.cuda.device_count())\nfor i in range(torch.cuda.device_count()):\n    print(f\"  • {i}: {torch.cuda.get_device_name(i)}\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def downsample_volume(volume, scale_factor):\n    new_shape = np.round(np.array(volume.shape) * scale_factor).astype(int)\n    return resize(volume, new_shape, order=3, preserve_range=True, anti_aliasing=True)\n\ndef downsample_mask(mask, scale_factor):\n    new_shape = np.round(np.array(mask.shape) * scale_factor).astype(int)\n    return resize(mask, new_shape, order=0, preserve_range=True, anti_aliasing=False)\n\ndef pad_to_multiple(x, multiple=16):\n    shape = x.shape[-3:]\n    pad = [(multiple - s % multiple) % multiple for s in shape]\n    pad = [0, pad[2], 0, pad[1], 0, pad[0]]\n    return F.pad(x, pad, mode=\"constant\", value=0)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class BraTSDataset(Dataset):\n    def __init__(self, flair_list, scale_factor=1.0):\n        \"\"\"\n        flair_list: daftar path *_flair.nii.gz\n        \"\"\"\n        self.flair_list = flair_list\n        self.scale_factor = scale_factor\n\n    def __len__(self):\n        return len(self.flair_list)\n\n    def __getitem__(self, idx):\n        flair_path = self.flair_list[idx]\n        base = flair_path.replace(\"_flair.nii.gz\", \"\")\n        mods = [\"_flair.nii.gz\", \"_t1.nii.gz\", \"_t1ce.nii.gz\", \"_t2.nii.gz\"]\n        \n        vols = []\n        for m in mods:\n            vol = nib.load(base + m).get_fdata()\n            vol = downsample_volume(vol, self.scale_factor)\n            vol = (vol - np.mean(vol)) / (np.std(vol) + 1e-5)\n            vols.append(vol)\n        img = np.stack(vols, axis=0)  # shape (4, D, H, W)\n\n        mask = nib.load(base + \"_seg.nii.gz\").get_fdata()\n        mask = downsample_mask(mask, self.scale_factor)\n        mask = np.where(mask > 0, 1, 0)  # binerisasi\n        mask = np.expand_dims(mask, axis=0)\n\n        return torch.tensor(img, dtype=torch.float32), torch.tensor(mask, dtype=torch.float32)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def build_unet():\n    model = UNet(\n        spatial_dims=3,\n        in_channels=4,   # empat modalitas\n        out_channels=1,\n        channels=(16, 32, 64, 128, 256),\n        strides=(2, 2, 2, 2),\n        num_res_units=2,\n    )\n    if torch.cuda.device_count() > 1:\n        print(f\"🚀 Menggunakan {torch.cuda.device_count()} GPU (DataParallel aktif)\")\n        model = nn.DataParallel(model)\n    return model.to(DEVICE)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def dice_score(pred, gt): return binary.dc(pred, gt)\ndef jaccard_index(pred, gt): return binary.jc(pred, gt)\ndef hausdorff_distance(pred, gt): return binary.hd95(pred, gt)\n\ndef compute_ssim_3d(pred, gt):\n    ssim_scores = []\n    for z in range(pred.shape[2]):\n        try:\n            ssim_score = ssim(gt[:, :, z], pred[:, :, z], data_range=1.0)\n            ssim_scores.append(ssim_score)\n        except ValueError:\n            continue\n    return np.mean(ssim_scores) if len(ssim_scores) > 0 else 0","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train_model(model, loader, criterion, optimizer):\n    scaler = torch.cuda.amp.GradScaler()\n    best_loss = np.inf\n    patience_ct = 0\n    losses = []\n    best_state = None\n\n    for epoch in range(EPOCHS):\n        model.train()\n        total_loss = 0.0\n        for img, msk in tqdm(loader, desc=f\"Epoch {epoch+1}/{EPOCHS}\"):\n            img, msk = img.to(DEVICE, non_blocking=True), msk.to(DEVICE, non_blocking=True)\n            img, msk = pad_to_multiple(img), pad_to_multiple(msk)\n\n            optimizer.zero_grad(set_to_none=True)\n            with torch.cuda.amp.autocast():\n                out = model(img)\n                min_shape = [min(o, m) for o, m in zip(out.shape, msk.shape)]\n                out, msk = out[..., :min_shape[-3], :min_shape[-2], :min_shape[-1]], \\\n                           msk[..., :min_shape[-3], :min_shape[-2], :min_shape[-1]]\n                loss = criterion(out, msk)\n\n            scaler.scale(loss).backward()\n            scaler.step(optimizer)\n            scaler.update()\n            total_loss += loss.item()\n\n        avg_loss = total_loss / len(loader)\n        losses.append(avg_loss)\n        print(f\"✅ Epoch {epoch+1} | Avg Loss: {avg_loss:.4f}\")\n\n        # Early stopping (val loader belum digunakan di versi ini)\n        if avg_loss < best_loss:\n            best_loss = avg_loss\n            best_state = {k: v.cpu().clone() for k, v in model.state_dict().items()}\n            patience_ct = 0\n        else:\n            patience_ct += 1\n            if patience_ct >= PATIENCE:\n                print(f\"⏹ Early stop di epoch {epoch+1}\")\n                break\n\n    return best_state, losses, epoch + 1\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def evaluate_model(model, loader):\n    model.eval()\n    dice_list, jacc_list, haus_list, ssim_list = [], [], [], []\n    with torch.no_grad():\n        for img, msk in loader:\n            img = img.to(DEVICE)\n            out = torch.sigmoid(model(pad_to_multiple(img))).cpu().numpy()[0,0]\n            pred = (out > 0.5).astype(np.uint8)\n            gt = msk.numpy()[0,0]\n            min_shape = [min(a,b) for a,b in zip(pred.shape, gt.shape)]\n            pred, gt = pred[:min_shape[0], :min_shape[1], :min_shape[2]], gt[:min_shape[0], :min_shape[1], :min_shape[2]]\n            dice_list.append(dice_score(pred, gt))\n            jacc_list.append(jaccard_index(pred, gt))\n            haus_list.append(hausdorff_distance(pred, gt))\n            ssim_list.append(compute_ssim_3d(pred, gt))\n    return np.mean(dice_list), np.mean(jacc_list), np.mean(haus_list), np.mean(ssim_list)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train_and_evaluate(scale_factor):\n    print(f\"\\n🧩 Training skala {scale_factor}x (4 channel, GPU aktif)\\n\")\n\n    images = sorted(glob.glob(os.path.join(DATA_DIR, \"BraTS2021_*\", \"*_flair.nii.gz\")))[:100]\n    dataset = BraTSDataset(images, scale_factor)\n    loader  = DataLoader(dataset, batch_size=BATCH_SIZE, shuffle=True, num_workers=2, pin_memory=True)\n\n    model = build_unet()\n    optimizer = optim.Adam(model.parameters(), lr=LR)\n    criterion = nn.BCEWithLogitsLoss()\n\n    torch.cuda.synchronize()\n    start_train = time.time()\n    best_state, losses, last_epoch = train_model(model, loader, criterion, optimizer)\n    torch.cuda.synchronize()\n    train_time = time.time() - start_train\n\n    model.load_state_dict(best_state)\n    torch.cuda.synchronize()\n    start_eval = time.time()\n    dice, jacc, haus, ssim_score = evaluate_model(model, loader)\n    torch.cuda.synchronize()\n    eval_time = time.time() - start_eval\n\n    # Visualisasi overlay\n    plt.figure(figsize=(12,4))\n    img, msk = dataset[0]\n    mid = img.shape[-1]//2\n    plt.subplot(1,3,1); plt.imshow(msk[0,:,:,mid], cmap='gray'); plt.title(\"Ground Truth\")\n    plt.subplot(1,3,2); plt.imshow(img[0,:,:,mid], cmap='gray'); plt.title(\"FLAIR\")\n    plt.subplot(1,3,3)\n    plt.imshow(img[0,:,:,mid], cmap='gray')\n    plt.imshow(msk[0,:,:,mid], cmap='Reds', alpha=0.4)\n    plt.title(f\"Overlay (scale={scale_factor})\")\n    plt.tight_layout(); plt.show()\n\n    return {\n        \"scale\": scale_factor,\n        \"epochs_run\": last_epoch,\n        \"loss_final\": losses[-1],\n        \"dice\": dice,\n        \"jaccard\": jacc,\n        \"hausdorff\": haus,\n        \"ssim\": ssim_score,\n        \"train_time_sec\": round(train_time,2),\n        \"eval_time_sec\": round(eval_time,2)\n    }","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"images = sorted(glob.glob(os.path.join(DATA_DIR, \"BraTS2021_*\", \"*_flair.nii.gz\")))[:100]\n\ndataset = BraTSDataset(images, scale_factor=1.0)\nloader  = DataLoader(dataset, batch_size=BATCH_SIZE, shuffle=True)\n\nprint(\"Total data:\", len(dataset))\nprint(\"Batch size:\", BATCH_SIZE)\nprint(\"Total batch:\", len(loader))\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"results = []\nfor s in SCALE_FACTORS:\n    torch.cuda.empty_cache()\n    results.append(train_and_evaluate(s))\n\nimport pandas as pd\nresults_df = pd.DataFrame(results)\nprint(\"\\n📊 Hasil Evaluasi Multi-Modal:\")\nprint(results_df)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"fig, ax = plt.subplots(1, 2, figsize=(12,5))\n\nax[0].plot(results_df[\"scale\"], results_df[\"dice\"], 'o-', label=\"Dice\")\nax[0].plot(results_df[\"scale\"], results_df[\"jaccard\"], 's--', label=\"Jaccard\")\nax[0].plot(results_df[\"scale\"], results_df[\"ssim\"], 'd-.', label=\"SSIM\")\nax[0].set_title(\"Similarity Metrics vs Downsampling\")\nax[0].set_xlabel(\"Scale Factor (↓ resolusi)\")\nax[0].set_ylabel(\"Score\"); ax[0].legend(); ax[0].grid(True)\n\nax[1].plot(results_df[\"scale\"], results_df[\"train_time_sec\"], 'o-', label=\"Train Time\")\nax[1].plot(results_df[\"scale\"], results_df[\"eval_time_sec\"], 's--', label=\"Eval Time\")\nax[1].set_title(\"GPU Runtime vs Downsampling\")\nax[1].set_xlabel(\"Scale Factor\"); ax[1].set_ylabel(\"Seconds\")\nax[1].legend(); ax[1].grid(True)\n\nplt.tight_layout(); plt.show()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os, glob, time\nimport numpy as np\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader, random_split\nfrom tqdm import tqdm\nimport nibabel as nib\nfrom skimage.transform import resize\nimport matplotlib.pyplot as plt","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"DATA_DIR   = \"/kaggle/working/BraTS2021_Training_Data\"\nDEVICE     = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nBATCH_SIZE = 2\nLR         = 1e-4\nEPOCHS     = 10\nPATIENCE   = 5\n\nprint(\"🧠 GPU tersedia:\", torch.cuda.device_count())\nfor i in range(torch.cuda.device_count()):\n    print(f\"  • {i}: {torch.cuda.get_device_name(i)}\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def downsample_volume(vol, sf):\n    new_shape = np.round(np.array(vol.shape) * sf).astype(int)\n    return resize(vol, new_shape, order=3, preserve_range=True, anti_aliasing=True)\n\ndef downsample_mask(msk, sf):\n    new_shape = np.round(np.array(msk.shape) * sf).astype(int)\n    return resize(msk, new_shape, order=0, preserve_range=True, anti_aliasing=False)\n\ndef pad_to_multiple(x, m=16):\n    shp = x.shape[-3:]\n    pad = [(m - s % m) % m for s in shp]\n    pad = [0,pad[2],0,pad[1],0,pad[0]]\n    return F.pad(x, pad, mode=\"constant\", value=0)\n\nclass BraTSDataset(Dataset):\n    def __init__(self, flair_paths, scale_factor=1.0):\n        self.flair_paths  = flair_paths\n        self.scale_factor = scale_factor\n\n    def __len__(self): return len(self.flair_paths)\n\n    def __getitem__(self, idx):\n        base = self.flair_paths[idx].replace(\"_flair.nii.gz\",\"\")\n        mods = [\"_flair.nii.gz\",\"_t1.nii.gz\",\"_t1ce.nii.gz\",\"_t2.nii.gz\"]\n        vols = []\n        for m in mods:\n            v = nib.load(base+m).get_fdata()\n            v = downsample_volume(v, self.scale_factor)\n            v = (v - np.mean(v)) / (np.std(v)+1e-5)\n            vols.append(v)\n        img = np.stack(vols,axis=0)  # (4,D,H,W)\n        msk = nib.load(base+\"_seg.nii.gz\").get_fdata()\n        msk = downsample_mask(msk, self.scale_factor)\n        msk = np.where(msk>0,1,0)[None]  # binary mask\n        return torch.tensor(img,dtype=torch.float32), torch.tensor(msk,dtype=torch.float32)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class UNet3D(nn.Module):\n    def __init__(self,in_ch=4,out_ch=1):\n        super().__init__()\n        def block(ic,oc):\n            return nn.Sequential(\n                nn.Conv3d(ic,oc,3,padding=1), nn.BatchNorm3d(oc), nn.ReLU(inplace=True),\n                nn.Conv3d(oc,oc,3,padding=1), nn.BatchNorm3d(oc), nn.ReLU(inplace=True)\n            )\n        self.enc1 = block(in_ch,16)\n        self.enc2 = block(16,32)\n        self.enc3 = block(32,64)\n        self.pool = nn.MaxPool3d(2)\n        self.up1  = nn.ConvTranspose3d(64,32,2,stride=2)\n        self.dec1 = block(64,32)\n        self.up2  = nn.ConvTranspose3d(32,16,2,stride=2)\n        self.dec2 = block(32,16)\n        self.out  = nn.Conv3d(16,out_ch,1)\n    def forward(self,x):\n        e1=self.enc1(x)\n        e2=self.enc2(self.pool(e1))\n        e3=self.enc3(self.pool(e2))\n        d1=self.up1(e3); d1=torch.cat([d1,e2],1); d1=self.dec1(d1)\n        d2=self.up2(d1); d2=torch.cat([d2,e1],1); d2=self.dec2(d2)\n        return self.out(d2)\n\ndef build_unet():\n    model = UNet3D(in_ch=4,out_ch=1)\n    if torch.cuda.device_count()>1:\n        print(f\"⚡ Menggunakan {torch.cuda.device_count()} GPU (DataParallel aktif)\")\n        model = nn.DataParallel(model)\n    return model.to(DEVICE)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train_model_with_val(model,train_loader,val_loader,criterion,optimizer,epochs=100):\n    try:    scaler = torch.amp.GradScaler(\"cuda\")\n    except: scaler = torch.cuda.amp.GradScaler()\n    best_loss=np.inf; patience_ct=0; best_state=None\n    tr_losses, vl_losses=[],[]\n    for ep in range(epochs):\n        # --- Train ---\n        model.train(); tot=0\n        for img,msk in tqdm(train_loader,desc=f\"Epoch {ep+1}/{epochs} (Train)\"):\n            img,msk=img.to(DEVICE),msk.to(DEVICE)\n            img,msk=pad_to_multiple(img),pad_to_multiple(msk)\n            optimizer.zero_grad(set_to_none=True)\n            with torch.amp.autocast(\"cuda\"):\n                out=model(img)\n                out=out[...,:msk.shape[-3],:msk.shape[-2],:msk.shape[-1]]\n                loss=criterion(out,msk)\n            scaler.scale(loss).backward(); scaler.step(optimizer); scaler.update()\n            tot+=loss.item()\n        tr_losses.append(tot/len(train_loader))\n        # --- Val ---\n        model.eval(); val_tot=0\n        with torch.no_grad():\n            for img,msk in tqdm(val_loader,desc=f\"Epoch {ep+1}/{epochs} (Val)\"):\n                img,msk=img.to(DEVICE),msk.to(DEVICE)\n                img,msk=pad_to_multiple(img),pad_to_multiple(msk)\n                out=model(img)\n                out=out[...,:msk.shape[-3],:msk.shape[-2],:msk.shape[-1]]\n                loss=criterion(out,msk); val_tot+=loss.item()\n        vl_losses.append(val_tot/len(val_loader))\n        print(f\"✅ Epoch {ep+1:03d} | Train Loss: {tr_losses[-1]:.4f} | Val Loss: {vl_losses[-1]:.4f}\")\n        if vl_losses[-1]<best_loss:\n            best_loss=vl_losses[-1]; best_state={k:v.cpu().clone() for k,v in model.state_dict().items()}\n            patience_ct=0\n        else:\n            patience_ct+=1\n            if patience_ct>=PATIENCE:\n                print(f\"⏹ Early stop di epoch {ep+1}\"); break\n    return best_state,tr_losses,vl_losses","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"mages=sorted(glob.glob(os.path.join(DATA_DIR,\"BraTS2021_*\",\"*_flair.nii.gz\")))[:100]\ndataset=BraTSDataset(images,scale_factor=1.0)\nval_split=int(0.8*len(dataset))\ntrain_ds,val_ds=random_split(dataset,[val_split,len(dataset)-val_split])\ntrain_loader=DataLoader(train_ds,batch_size=BATCH_SIZE,shuffle=True,num_workers=2,pin_memory=True)\nval_loader=DataLoader(val_ds,batch_size=BATCH_SIZE,shuffle=False,num_workers=2,pin_memory=True)\n\nmodel=build_unet()\noptimizer=optim.Adam(model.parameters(),lr=LR)\ncriterion=nn.BCEWithLogitsLoss()\n\nbest_state,tr_losses,vl_losses=train_model_with_val(model,train_loader,val_loader,criterion,optimizer,epochs=EPOCHS)\nmodel.load_state_dict(best_state)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.figure(figsize=(8,5))\nplt.plot(tr_losses,label=\"Train Loss\",marker='o')\nplt.plot(vl_losses,label=\"Validation Loss\",marker='x')\nplt.xlabel(\"Epoch\"); plt.ylabel(\"Loss\")\nplt.title(\"Training & Validation Loss Curve (4-Channel U-Net 3D)\")\nplt.legend(); plt.grid(True); plt.show()","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}