{"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":"# Introduction\nCompetition home page: https://www.kaggle.com/competitions/mayo-clinic-strip-ai\n\nReference: \n* https://github.com/Project-MONAI/monai-bootcamp/blob/main/MONAICore\n* https://github.com/Project-MONAI/tutorials\n* https://docs.monai.io","metadata":{}},{"cell_type":"markdown","source":"# Import Libraries","metadata":{}},{"cell_type":"code","source":"# Check GPU\n!nvidia-smi","metadata":{"execution":{"iopub.status.busy":"2022-09-09T00:23:14.887309Z","iopub.execute_input":"2022-09-09T00:23:14.888137Z","iopub.status.idle":"2022-09-09T00:23:15.93279Z","shell.execute_reply.started":"2022-09-09T00:23:14.888044Z","shell.execute_reply":"2022-09-09T00:23:15.93159Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"%%capture\n# Install MONAI\n!pip install -qU \"monai[ignite, nibabel, torchvision, tqdm]==0.9.0\"","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2022-09-09T00:23:15.935445Z","iopub.execute_input":"2022-09-09T00:23:15.936051Z","iopub.status.idle":"2022-09-09T00:23:29.22278Z","shell.execute_reply.started":"2022-09-09T00:23:15.936005Z","shell.execute_reply":"2022-09-09T00:23:29.221462Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import os\nimport shutil\nimport tempfile\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nimport PIL\n\nimport torch\nimport monai\n\nfrom monai.apps import download_and_extract\nfrom monai.config import print_config\nfrom monai.metrics import ROCAUCMetric\nfrom monai.data import decollate_batch, partition_dataset_classes\nfrom monai.data import PILReader\nfrom monai.networks.nets import DenseNet121\nfrom monai.transforms import (\n    AddChannel,\n    Compose,\n    LoadImage,\n    RandFlip,\n    RandRotate,\n    RandZoom,\n    ScaleIntensity,\n    ToTensor,\n    Activations,\n    AsDiscrete,\n    EnsureType,\n    Resize,\n    ResizeWithPadOrCrop,\n    EnsureChannelFirst,\n    CenterSpatialCrop\n)\nfrom monai.utils import set_determinism\n\nfrom ignite.engine import Events\nfrom ignite.handlers import ModelCheckpoint\nfrom ignite.metrics import Accuracy\nfrom monai.handlers import ROCAUC, ValidationHandler, CheckpointSaver\nfrom monai.engines import SupervisedTrainer, SupervisedEvaluator\nfrom ignite.utils import convert_tensor","metadata":{"execution":{"iopub.status.busy":"2022-09-09T01:15:28.901283Z","iopub.execute_input":"2022-09-09T01:15:28.902106Z","iopub.status.idle":"2022-09-09T01:15:28.911048Z","shell.execute_reply.started":"2022-09-09T01:15:28.902067Z","shell.execute_reply":"2022-09-09T01:15:28.909975Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Config","metadata":{}},{"cell_type":"code","source":"# Define the config for the pipeline\nclass cfg:\n    imgTrainDir = \"../input/mayo-clinic-strip-ai-jpg-dataset/data/train\"\n    imgTestDir = \"../input/mayo-clinic-strip-ai-jpg-dataset/data/test\"\n    imgotherDir = \"../input/mayo-clinic-strip-ai-jpg-dataset/data/other\"\n    imgTrainMetaFile = \"../input/mayo-clinic-strip-ai-jpg-dataset/data/train.csv\"\n    outDir = \"./\"\n    saveModelFilename = \"best_metric_model\"\n    device = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")\n    seed = 0\n    debug = True\n    sampleSize = 50\n    split = (8,1,1)\n    imgSize = [128,128]\n    classNum = 2\n    batchSize = 5\n    numWorkers = 2\n    learningRate = 1e-5\n    trainEpochs = 4\n    validEpochs = 1\n    saveEpochs = 4","metadata":{"execution":{"iopub.status.busy":"2022-09-09T01:15:28.974272Z","iopub.execute_input":"2022-09-09T01:15:28.974859Z","iopub.status.idle":"2022-09-09T01:15:28.981421Z","shell.execute_reply.started":"2022-09-09T01:15:28.974832Z","shell.execute_reply":"2022-09-09T01:15:28.980385Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Set the random seeds in both Numpy and PyTorch to ensure reproducibility\nset_determinism(seed=cfg.seed)","metadata":{"execution":{"iopub.status.busy":"2022-09-09T01:15:29.051968Z","iopub.execute_input":"2022-09-09T01:15:29.052248Z","iopub.status.idle":"2022-09-09T01:15:29.05704Z","shell.execute_reply.started":"2022-09-09T01:15:29.052222Z","shell.execute_reply":"2022-09-09T01:15:29.055882Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Prepare data\nWe will use this dataset: https://www.kaggle.com/datasets/dariussingh/mayo-clinic-strip-ai-jpg-dataset\n\nNote that there are other approaches to process WSI images.","metadata":{}},{"cell_type":"code","source":"# Read the image filenames and class names\n# We only use the images under the train folder as a POC. Consider all images in practice.\n\ndf = pd.read_csv(cfg.imgTrainMetaFile)\nif cfg.debug:\n    df = df.sample(cfg.sampleSize)\ndf.head()\nimageFiles = [os.path.join(cfg.imgTrainDir,x+\".jpg\") for x in df.image_id.values]\nimageClass = df.label.values.tolist()\nimageClass = [int(x==\"CE\") for x in imageClass] # convert categorical to int","metadata":{"execution":{"iopub.status.busy":"2022-09-09T01:15:29.129534Z","iopub.execute_input":"2022-09-09T01:15:29.130592Z","iopub.status.idle":"2022-09-09T01:15:29.145809Z","shell.execute_reply.started":"2022-09-09T01:15:29.130557Z","shell.execute_reply":"2022-09-09T01:15:29.144821Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Visualize\nim = PIL.Image.open(np.random.choice(imageFiles))\narr = np.array(im)\nplt.imshow(arr, cmap=\"gray\", vmin=0, vmax=255)\nplt.tight_layout()\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2022-09-09T01:18:35.831226Z","iopub.execute_input":"2022-09-09T01:18:35.832218Z","iopub.status.idle":"2022-09-09T01:18:36.582433Z","shell.execute_reply.started":"2022-09-09T01:18:35.832171Z","shell.execute_reply":"2022-09-09T01:18:36.581335Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Split training, validation, and test data\ntrainIdx, valIdx, testIdx = partition_dataset_classes(np.arange(len(imageFiles)), \n                                                      imageClass,cfg.split, \n                                                      shuffle=True)\n\ntrainX = [imageFiles[i] for i in trainIdx]\ntrainY = [imageClass[i] for i in trainIdx]\nvalX = [imageFiles[i] for i in valIdx]\nvalY = [imageClass[i] for i in valIdx]\ntestX = [imageFiles[i] for i in testIdx]\ntestY = [imageClass[i] for i in testIdx]","metadata":{"execution":{"iopub.status.busy":"2022-09-09T01:15:30.150346Z","iopub.execute_input":"2022-09-09T01:15:30.15097Z","iopub.status.idle":"2022-09-09T01:15:30.158689Z","shell.execute_reply.started":"2022-09-09T01:15:30.15093Z","shell.execute_reply":"2022-09-09T01:15:30.15776Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# MONAI transforms\ntrainTransforms = Compose([LoadImage(reader=PILReader(converter=lambda image: image.convert(\"L\")),image_only=True),\n                           AddChannel(),\n                           Resize(spatial_size=cfg.imgSize),\n                            ScaleIntensity(),\n                            RandRotate(range_x=15, prob=0.5, keep_size=True),\n                            RandFlip(spatial_axis=0, prob=0.5),\n                            RandZoom(min_zoom=0.9, max_zoom=1.1, prob=0.5),\n                            ToTensor()])\nvalTransforms = Compose([LoadImage(reader=PILReader(converter=lambda image: image.convert(\"L\")),image_only=True),  \n                         AddChannel(),\n                         Resize(spatial_size=cfg.imgSize),\n                         ScaleIntensity(), \n                         ToTensor()])\nact = Compose([EnsureType(),Activations(softmax=True)])\ntoOnehot = Compose([EnsureType(),AsDiscrete(to_onehot=cfg.classNum)])","metadata":{"execution":{"iopub.status.busy":"2022-09-09T01:15:30.160146Z","iopub.execute_input":"2022-09-09T01:15:30.160584Z","iopub.status.idle":"2022-09-09T01:15:30.176349Z","shell.execute_reply.started":"2022-09-09T01:15:30.160514Z","shell.execute_reply":"2022-09-09T01:15:30.175432Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# MONAI dataset\nclass MyDataset(torch.utils.data.Dataset):\n    def __init__(self, image_files, labels, transforms):\n        self.image_files = image_files\n        self.labels = labels\n        self.transforms = transforms\n\n    def __len__(self):\n        return len(self.image_files)\n\n    def __getitem__(self, index):\n        return self.transforms(self.image_files[index]), self.labels[index]\n\ntrainDs = MyDataset(trainX, trainY, trainTransforms)\nvalDs = MyDataset(valX, valY, valTransforms)\ntestDs = MyDataset(testX, testY, valTransforms)","metadata":{"execution":{"iopub.status.busy":"2022-09-09T01:15:30.180824Z","iopub.execute_input":"2022-09-09T01:15:30.181786Z","iopub.status.idle":"2022-09-09T01:15:30.196588Z","shell.execute_reply.started":"2022-09-09T01:15:30.181751Z","shell.execute_reply":"2022-09-09T01:15:30.195414Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# MONAI dataloader\ntrainLoader = torch.utils.data.DataLoader(trainDs,\n                                           batch_size=cfg.batchSize,\n                                           shuffle=True,\n                                           num_workers=cfg.numWorkers)\nvalLoader = torch.utils.data.DataLoader(valDs, \n                                         batch_size=cfg.batchSize, \n                                         num_workers=cfg.numWorkers)\ntestLoader = torch.utils.data.DataLoader(testDs, \n                                          batch_size=cfg.batchSize, \n                                          num_workers=cfg.numWorkers)","metadata":{"execution":{"iopub.status.busy":"2022-09-09T01:15:30.19793Z","iopub.execute_input":"2022-09-09T01:15:30.198795Z","iopub.status.idle":"2022-09-09T01:15:30.211431Z","shell.execute_reply.started":"2022-09-09T01:15:30.19876Z","shell.execute_reply":"2022-09-09T01:15:30.210484Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Prepare model","metadata":{}},{"cell_type":"code","source":"net = DenseNet121(spatial_dims=2, \n                  in_channels=1, \n                  out_channels=cfg.classNum).to(cfg.device)\nlossFunction = torch.nn.CrossEntropyLoss()\noptimizer = torch.optim.Adam(net.parameters(),cfg.learningRate)","metadata":{"execution":{"iopub.status.busy":"2022-09-09T01:15:30.212966Z","iopub.execute_input":"2022-09-09T01:15:30.213627Z","iopub.status.idle":"2022-09-09T01:15:30.436381Z","shell.execute_reply.started":"2022-09-09T01:15:30.213592Z","shell.execute_reply":"2022-09-09T01:15:30.435396Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Prepare training loop (Ignite) ","metadata":{}},{"cell_type":"code","source":"iterLosses = []\nbatchSizes = []\nepochLossValues = []\nmetricValues = []\n\nstepsPerEpoch = len(trainDs) // trainLoader.batch_size\nif len(trainDs) % trainLoader.batch_size != 0:\n    stepsPerEpoch += 1\n\n\ndef roc_auc_trans(x):\n    if isinstance(x, list):\n        pred = torch.cat([i[0][None, :] for i in x])\n        label = torch.cat([i[1][None, :] for i in x])\n        return pred, label\n\n    return act(x[\"pred\"]), toOnehot(x[\"label\"])\n\n\ndef prepare_batch(batchdata, device, non_blocking):\n    img, classes = batchdata\n    return convert_tensor(img, device, non_blocking),convert_tensor(classes, device, non_blocking)\n\n\nevaluator = SupervisedEvaluator(\n    device=cfg.device,\n    val_data_loader=valLoader,\n    network=net,\n    postprocessing=roc_auc_trans,\n    key_val_metric={\"rocauc\": ROCAUC(output_transform=roc_auc_trans)},\n    prepare_batch=prepare_batch,\n)\n\n# TODO: CheckpointSaver for saving model\ntrainer = SupervisedTrainer(\n    device=cfg.device,\n    max_epochs=cfg.trainEpochs,\n    train_data_loader=trainLoader,\n    network=net,\n    optimizer=optimizer,\n    loss_function=lossFunction,\n    train_handlers=[ValidationHandler(cfg.validEpochs, evaluator),\n                    CheckpointSaver(save_dir=cfg.outDir, \n                                    save_dict={'network': net},\n                                    save_interval=cfg.saveEpochs,\n                                    key_metric_filename=cfg.saveModelFilename)],\n    prepare_batch=prepare_batch,\n)\n\n\n@trainer.on(Events.ITERATION_COMPLETED)\ndef _end_iter(engine):\n    loss = np.average([o[\"loss\"] for o in engine.state.output])\n    batch_len = len(engine.state.batch[0])\n    epoch = engine.state.epoch\n    epochLen = engine.state.max_epochs\n    step = engine.state.iteration\n    iterLosses.append(loss)\n    batchSizes.append(batch_len)\n\n    print(f\"epoch {epoch}/{epochLen}, step {step}/{stepsPerEpoch}, training_loss = {loss:.4f}\")\n\n\n@trainer.on(Events.EPOCH_COMPLETED)\ndef run_validation(engine):\n    # the overall average loss must be weighted by batch size\n    overallAverageLoss = np.average(iterLosses, weights=batchSizes)\n    epochLossValues.append(overallAverageLoss)\n\n    # clear the contents of iter_losses and batch_sizes for the next epoch\n    del iterLosses[:]\n    del batchSizes[:]\n    \n    # reset iteration for next epoch\n    engine.state.iteration = 0\n\n    # fetch and report the validation metrics\n    roc = evaluator.state.metrics[\"rocauc\"]\n    metricValues.append(roc)\n    print(f\"evaluation for epoch {engine.state.epoch},  rocauc = {roc:.4f}\")","metadata":{"execution":{"iopub.status.busy":"2022-09-09T01:15:30.438017Z","iopub.execute_input":"2022-09-09T01:15:30.43843Z","iopub.status.idle":"2022-09-09T01:15:30.45743Z","shell.execute_reply.started":"2022-09-09T01:15:30.438391Z","shell.execute_reply":"2022-09-09T01:15:30.456417Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Train and save model","metadata":{}},{"cell_type":"code","source":"trainer.run()","metadata":{"execution":{"iopub.status.busy":"2022-09-09T01:15:30.459048Z","iopub.execute_input":"2022-09-09T01:15:30.459476Z","iopub.status.idle":"2022-09-09T01:15:57.782236Z","shell.execute_reply.started":"2022-09-09T01:15:30.459439Z","shell.execute_reply":"2022-09-09T01:15:57.781073Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Plot loss and metric","metadata":{}},{"cell_type":"code","source":"plt.figure(\"train\", (12, 6))\n\nplt.subplot(1, 2, 1)\nplt.title(\"Epoch Average Loss\")\nx = [i + 1 for i in range(len(epochLossValues))]\ny = epochLossValues\nplt.xlabel(\"epoch\")\nplt.plot(x, y)\n\nplt.subplot(1, 2, 2)\nplt.title(\"Val AUC\")\nx = [(i + 1) for i in range(len(metricValues))]\ny = metricValues\nplt.xlabel(\"epoch\")\nplt.plot(x, y)\n\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2022-09-09T01:15:57.784321Z","iopub.execute_input":"2022-09-09T01:15:57.784827Z","iopub.status.idle":"2022-09-09T01:15:58.153798Z","shell.execute_reply.started":"2022-09-09T01:15:57.784776Z","shell.execute_reply":"2022-09-09T01:15:58.152435Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Inference","metadata":{}},{"cell_type":"code","source":"net.load_state_dict(torch.load(os.path.join(\"./network_epoch=4.pt\")))\nnet.eval()\ny_true = list()\ny_pred = list()\n\nwith torch.no_grad():\n    for test_data in testLoader:\n        test_images, test_labels = (\n            test_data[0].to(cfg.device),\n            test_data[1].to(cfg.device),\n        )\n        pred = net(test_images).argmax(dim=1)\n        \n        for i in range(len(pred)):\n            y_true.append(test_labels[i].item())\n            y_pred.append(pred[i].item())\n        \n        idx = 1\n        arr = np.array(test_data[0][idx])\n        plt.imshow(arr[0,:,:], cmap=\"gray\", vmin=0, vmax=1)\n        plt.title(f\"Label: {test_labels[idx].item()}, Pred: {pred[idx].item()}\")\n        plt.tight_layout()\n        plt.show()\n\n        break","metadata":{"execution":{"iopub.status.busy":"2022-09-09T01:25:50.698477Z","iopub.execute_input":"2022-09-09T01:25:50.699107Z","iopub.status.idle":"2022-09-09T01:25:51.944846Z","shell.execute_reply.started":"2022-09-09T01:25:50.699061Z","shell.execute_reply":"2022-09-09T01:25:51.943705Z"},"trusted":true},"execution_count":null,"outputs":[]}]}