{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Brain Tumor Classification with PyTorch⚡Lightning & EfficientNet 3D\n\nThe goal of this challenge is to Predict the status of a genetic biomarker important for brain cancer treatment.\n\nAll the code is refered from public repository: https://github.com/Borda/kaggle_brain-tumor-3D\nAny nice contribution is welcome!","metadata":{}},{"cell_type":"code","source":"! pip install -q https://github.com/Borda/kaggle_brain-tumor-3D/archive/refs/heads/main.zip\n! pip install -q https://github.com/shijianjian/EfficientNet-PyTorch-3D/archive/refs/heads/master.zip\n! pip uninstall -q -y wandb\n! ls -l /kaggle/input/rsna-miccai-brain-tumor-radiogenomic-classification\n! nvidia-smi\n! mkdir /kaggle/temp\n\n%matplotlib inline\n%load_ext autoreload\n%autoreload 2\n\nimport kaggle_brain3d\nprint(kaggle_brain3d.__version__)","metadata":{"tags":[],"execution":{"iopub.status.busy":"2021-08-25T13:52:18.041249Z","iopub.execute_input":"2021-08-25T13:52:18.041638Z","iopub.status.idle":"2021-08-25T13:52:52.142392Z","shell.execute_reply.started":"2021-08-25T13:52:18.041537Z","shell.execute_reply":"2021-08-25T13:52:52.141533Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Data exploration\n\nThese 3 cohorts are structured as follows: Each independent case has a dedicated folder identified by a five-digit number.\nWithin each of these “case” folders, there are four sub-folders, each of them corresponding to each of the structural multi-parametric MRI (mpMRI) scans, in DICOM format.\nThe exact mpMRI scans included are:\n\n- **FLAIR**: Fluid Attenuated Inversion Recovery\n- **T1w**: T1-weighted pre-contrast\n- **T1Gd**: T1-weighted post-contrast\n- **T2**: T2-weighted","metadata":{}},{"cell_type":"code","source":"import os\nimport glob\nimport pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\n\nPATH_DATASET = \"/kaggle/input/rsna-miccai-brain-tumor-radiogenomic-classification\"\nPATH_TEMP = \"/kaggle/temp\"\nSCAN_TYPES = (\"FLAIR\", \"T1w\", \"T1CE\", \"T2w\")\n\ndf_train = pd.read_csv(os.path.join(PATH_DATASET, \"train_labels.csv\"))\ndf_train[\"BraTS21ID\"] = df_train[\"BraTS21ID\"].apply(lambda i: \"%05d\" % i)\ndisplay(df_train.head())","metadata":{"tags":[],"execution":{"iopub.status.busy":"2021-08-25T13:52:52.144117Z","iopub.execute_input":"2021-08-25T13:52:52.14447Z","iopub.status.idle":"2021-08-25T13:52:52.304031Z","shell.execute_reply.started":"2021-08-25T13:52:52.144432Z","shell.execute_reply":"2021-08-25T13:52:52.303225Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"See the dataset label distribution","metadata":{}},{"cell_type":"code","source":"_= df_train[\"MGMT_value\"].value_counts().plot(kind=\"pie\", title=\"label distribution\", autopct=\"%.1f%%\")","metadata":{"tags":[],"execution":{"iopub.status.busy":"2021-08-25T13:52:52.307154Z","iopub.execute_input":"2021-08-25T13:52:52.307434Z","iopub.status.idle":"2021-08-25T13:52:52.485636Z","shell.execute_reply.started":"2021-08-25T13:52:52.307407Z","shell.execute_reply":"2021-08-25T13:52:52.484837Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"For almost all scans we have all four types","metadata":{}},{"cell_type":"code","source":"scans = [os.path.basename(p) for p in glob.glob(os.path.join(PATH_DATASET, \"train\", \"*\", \"*\"))]\n_= pd.Series(scans).value_counts().plot(kind=\"bar\", grid=True)","metadata":{"execution":{"iopub.status.busy":"2021-08-25T13:52:52.487217Z","iopub.execute_input":"2021-08-25T13:52:52.487567Z","iopub.status.idle":"2021-08-25T13:52:53.69215Z","shell.execute_reply.started":"2021-08-25T13:52:52.487527Z","shell.execute_reply":"2021-08-25T13:52:53.691341Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Interactive view\n\nshowing particular scan in XYZ dimension/slices","metadata":{}},{"cell_type":"code","source":"from ipywidgets import interact, IntSlider\n\nfrom kaggle_brain3d.utils import load_volume, interpolate_volume, show_volume\nfrom kaggle_brain3d.transforms import crop_volume\n\ndef interactive_show(volume_path: str, crop_thr: float):\n    print(f\"loading: {volume_path}\")\n    volume = load_volume(volume_path, percentile=0)\n    print(f\"sample shape: {volume.shape} >> {volume.dtype}\")\n    volume = interpolate_volume(volume)\n    print(f\"interp shape: {volume.shape} >> {volume.dtype}\")\n    volume = crop_volume(volume, crop_thr)\n    print(f\"crop shape: {volume.shape} >> {volume.dtype}\")\n    vol_shape = volume.shape\n    interact(\n        lambda x, y, z: plt.show(show_volume(volume, x, y, z)),\n        x=IntSlider(min=0, max=vol_shape[0], step=5, value=int(vol_shape[0] / 2)),\n        y=IntSlider(min=0, max=vol_shape[1], step=5, value=int(vol_shape[1] / 2)),\n        z=IntSlider(min=0, max=vol_shape[2], step=5, value=int(vol_shape[2] / 2)),\n    )\n\n\nPATH_SAMPLE_VOLUME = os.path.join(PATH_DATASET, \"train\", \"00005\", \"FLAIR\")\n\ninteractive_show(PATH_SAMPLE_VOLUME, crop_thr=1e-6)","metadata":{"tags":[],"execution":{"iopub.status.busy":"2021-08-25T13:52:53.693496Z","iopub.execute_input":"2021-08-25T13:52:53.693913Z","iopub.status.idle":"2021-08-25T13:53:31.045168Z","shell.execute_reply.started":"2021-08-25T13:52:53.693871Z","shell.execute_reply":"2021-08-25T13:53:31.044331Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Prepare dataset\n\n### Pytorch Dataset\n\nThe basic building block is traforming raw data to Torch Dataset.\nWe have here loading particular DICOM images into a volume and saving as temp/cacher, so we do not need to take the very time demanding loading do next time - this boost the IO from about 2h to 8min\n\nAt the end we show a few sample images from prepared dataset.","metadata":{}},{"cell_type":"code","source":"import os\nimport pandas as pd\nimport torch\nfrom tqdm.auto import tqdm\n\nfrom kaggle_brain3d.data import BrainScansDataset\nfrom kaggle_brain3d.transforms import resize_volume\n\n# ==============================\n\nds = BrainScansDataset(\n    image_dir=os.path.join(PATH_DATASET, \"train\"),\n    df_table=os.path.join(PATH_DATASET, \"train_labels.csv\"),\n    crop_thr=None, cache_dir=PATH_TEMP,\n)\nfor i in tqdm(range(2)):\n    img = ds[i * 10][\"data\"]\n    img = resize_volume(img[0])\n    show_volume(img, fig_size=(12, 8))","metadata":{"tags":[],"execution":{"iopub.status.busy":"2021-08-25T13:53:31.04651Z","iopub.execute_input":"2021-08-25T13:53:31.046905Z","iopub.status.idle":"2021-08-25T13:54:23.819824Z","shell.execute_reply.started":"2021-08-25T13:53:31.046866Z","shell.execute_reply":"2021-08-25T13:54:23.818954Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Lightning DataModule\n\nIt is constric to wrap all data-related peaces and define Pytoch dataloder for Training / Validation / Testing phase.\n\nAt the end we show a few sample images from the fost training batch.","metadata":{}},{"cell_type":"code","source":"from functools import partial\nimport rising.transforms as rtr\nfrom rising.loading import DataLoader, default_transform_call\nfrom rising.random import DiscreteParameter, UniformParameter\n\nfrom kaggle_brain3d.data import BrainScansDM  # , TRAIN_TRANSFORMS, VAL_TRANSFORMS\nfrom kaggle_brain3d.transforms import RandomAffine, rising_zero_mean\n\n# ==============================\n\n# Dataset >> mean: 0.13732214272022247 STD: 0.24326834082603455\nrising_norm = partial(rising_zero_mean, mean=0.137, std=0.243)\n\n# define transformations\nTRAIN_TRANSFORMS = [\n    rtr.Rot90((0, 1, 2), keys=[\"data\"], p=0.5),\n    rtr.Mirror(dims=DiscreteParameter([0, 1, 2]), keys=[\"data\"]),\n    RandomAffine(scale_range=(0.9, 1.1), rotation_range=(-10, 10), translation_range=(-0.1, 0.1)),\n    rising_norm,\n]\nVAL_TRANSFORMS = [\n    rising_norm,\n]\n\n# ==============================\n\ndm = BrainScansDM(\n    data_dir=PATH_DATASET,\n    scan_types=[\"FLAIR\"],\n    input_size=224,\n    crop_thr=1e-6,\n    batch_size=4,\n    cache_dir=PATH_TEMP,\n    # in_memory=True,\n    num_workers=2,\n    train_transforms=rtr.Compose(TRAIN_TRANSFORMS, transform_call=default_transform_call),\n    valid_transforms=rtr.Compose(VAL_TRANSFORMS, transform_call=default_transform_call),\n)\ndm.prepare_data(3)\ndm.setup()\nprint(f\"Training batches: {len(dm.train_dataloader())} and Validation {len(dm.val_dataloader())}\")\n\n# Quick view\nfor batch in dm.train_dataloader():\n    for i in range(2):\n        show_volume(batch[\"data\"][i][0], fig_size=(9, 6), v_min_max=(-1., 3.))\n    break","metadata":{"tags":[],"execution":{"iopub.status.busy":"2021-08-25T13:54:23.821197Z","iopub.execute_input":"2021-08-25T13:54:23.821564Z","iopub.status.idle":"2021-08-25T15:31:41.150155Z","shell.execute_reply.started":"2021-08-25T13:54:23.821526Z","shell.execute_reply":"2021-08-25T15:31:41.149256Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Prepare 3D model\n\nLightningModule is the core of PL, it wrappes all model related peaces, mainly:\n\n- the model/architecture/weights\n- evaluation metrics\n- configs for optimizer and LR cheduler","metadata":{}},{"cell_type":"code","source":"from torchsummary import summary\nfrom kaggle_brain3d.models import LitBrainMRI\n\n# ==============================\n\nmodel = LitBrainMRI(lr=5e-4)\n# summary(model, input_size=(1, 128, 128, 128))","metadata":{"tags":[],"execution":{"iopub.status.busy":"2021-08-25T15:31:41.153691Z","iopub.execute_input":"2021-08-25T15:31:41.154001Z","iopub.status.idle":"2021-08-25T15:31:44.365929Z","shell.execute_reply.started":"2021-08-25T15:31:41.153967Z","shell.execute_reply":"2021-08-25T15:31:44.364821Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Train a model\n\nLightning forces the following structure to your code which makes it reusable and shareable:\n\n- Research code (the LightningModule).\n- Engineering code (you delete, and is handled by the Trainer).\n- Non-essential research code (logging, etc... this goes in Callbacks).\n- Data (use PyTorch DataLoaders or organize them into a LightningDataModule).\n\nOnce you do this, you can train on multiple-GPUs, TPUs, CPUs and even in 16-bit precision without changing your code!","metadata":{}},{"cell_type":"code","source":"import pytorch_lightning as pl\n\nlogger = pl.loggers.CSVLogger(save_dir='logs/', name=model.name)\n# swa = pl.callbacks.StochasticWeightAveraging(swa_epoch_start=0.6)\nckpt = pl.callbacks.ModelCheckpoint(\n    monitor='valid/auroc',\n    save_top_k=1,\n    filename='checkpoint/{epoch:02d}-{valid_acc:.4f}-{valid_f1:.4f}',\n    mode='max',\n)\n\n# ==============================\n\ntrainer = pl.Trainer(\n    # overfit_batches=5,\n    # fast_dev_run=True,\n    gpus=1,\n    callbacks=[ckpt],  # , swa\n    logger=logger,\n    max_epochs=15,\n    precision=16,\n    accumulate_grad_batches=24,\n    # val_check_interval=0.5,\n    progress_bar_refresh_rate=1,\n    log_every_n_steps=5,\n    weights_summary='top',\n    auto_lr_find=True,\n#     auto_scale_batch_size='binsearch',\n)\n\n# ==============================\n\n# trainer.tune(\n#     model, \n#     datamodule=dm, \n#     lr_find_kwargs=dict(min_lr=2e-5, max_lr=1e-2, num_training=35),\n#     # scale_batch_size_kwargs=dict(max_trials=5),\n# )\n# print(f\"Batch size: {dm.batch_size}\")\n# print(f\"Learning Rate: {model.learning_rate}\")\n\n# ==============================\n\ntrainer.fit(model=model, datamodule=dm)","metadata":{"tags":[],"execution":{"iopub.status.busy":"2021-08-25T16:49:31.46637Z","iopub.execute_input":"2021-08-25T16:49:31.466749Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Training progress","metadata":{}},{"cell_type":"code","source":"metrics = pd.read_csv(f'{trainer.logger.log_dir}/metrics.csv')\ndisplay(metrics.head())\n\naggreg_metrics = []\nagg_col = \"epoch\"\nfor i, dfg in metrics.groupby(agg_col):\n    agg = dict(dfg.mean())\n    agg[agg_col] = i\n    aggreg_metrics.append(agg)\n\ndf_metrics = pd.DataFrame(aggreg_metrics)\ndf_metrics[['train/loss', 'valid/loss']].plot(grid=True, legend=True, xlabel=agg_col)\ndf_metrics[['train/f1', 'train/auroc', 'valid/f1', 'valid/auroc']].plot(grid=True, legend=True, xlabel=agg_col)","metadata":{"execution":{"iopub.status.busy":"2021-08-25T16:49:26.730745Z","iopub.execute_input":"2021-08-25T16:49:26.731308Z","iopub.status.idle":"2021-08-25T16:49:27.191755Z","shell.execute_reply.started":"2021-08-25T16:49:26.731258Z","shell.execute_reply":"2021-08-25T16:49:27.190603Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}