{"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":"# Necesaary Imports","metadata":{}},{"cell_type":"code","source":"pip install torchsummary # this module is not installed already","metadata":{"execution":{"iopub.status.busy":"2023-05-02T08:35:53.384080Z","iopub.execute_input":"2023-05-02T08:35:53.384494Z","iopub.status.idle":"2023-05-02T08:36:04.501714Z","shell.execute_reply.started":"2023-05-02T08:35:53.384446Z","shell.execute_reply":"2023-05-02T08:36:04.500449Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# necessary imports \nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nfrom time import time\nfrom matplotlib import style\n\n# imports for making models\nimport torch\nimport torch.nn as nn\nfrom torchsummary import summary  # for showing summary of the neural nets\nfrom tqdm import tqdm # for showing progress bars\n\n# taking images as input \nimport os  # for setting up paths for images\nfrom PIL import Image # reading images\nimport torchvision   # saving images\nimport albumentations as A  # for data augmentations\nfrom albumentations.pytorch import ToTensorV2 # for converting np array to tensors","metadata":{"execution":{"iopub.status.busy":"2023-05-02T08:36:15.521543Z","iopub.execute_input":"2023-05-02T08:36:15.522259Z","iopub.status.idle":"2023-05-02T08:36:15.528791Z","shell.execute_reply.started":"2023-05-02T08:36:15.522220Z","shell.execute_reply":"2023-05-02T08:36:15.527525Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"","metadata":{"jupyter":{"source_hidden":true}}},{"cell_type":"markdown","source":"# Unzipping the dataset","metadata":{}},{"cell_type":"code","source":"! unzip /kaggle/input/carvana-image-masking-challenge/train_masks.zip; # it is a bash command to unzip the file","metadata":{"execution":{"iopub.status.busy":"2023-05-02T08:36:23.485447Z","iopub.execute_input":"2023-05-02T08:36:23.486072Z","iopub.status.idle":"2023-05-02T08:36:25.191272Z","shell.execute_reply.started":"2023-05-02T08:36:23.486035Z","shell.execute_reply":"2023-05-02T08:36:25.190167Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"! unzip /kaggle/input/carvana-image-masking-challenge/train.zip;","metadata":{"execution":{"iopub.status.busy":"2023-05-02T08:36:44.017809Z","iopub.execute_input":"2023-05-02T08:36:44.018263Z","iopub.status.idle":"2023-05-02T08:36:52.453629Z","shell.execute_reply.started":"2023-05-02T08:36:44.018226Z","shell.execute_reply":"2023-05-02T08:36:52.452294Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Helper functions\n1. Forming the dataset.\n2. Plotting the images and masks.\n3. Making the dataloaders functions.","metadata":{}},{"cell_type":"code","source":"class dataset(torch.utils.data.Dataset):  # structure is inherited from pytorch Dataset class\n    def __init__(self, images_dir, masks_dir, transform=None):\n        super(dataset, self).__init__()\n        self.images_dir = images_dir\n        self.masks_dir = masks_dir\n        self.images_list = os.listdir(images_dir)\n        self.transform = transform\n    \n    def __len__(self):\n        return len(self.images_list)\n    \n    def __getitem__(self, index):\n        image_path = os.path.join(self.images_dir, self.images_list[index])   # setting up the path for image\n        mask_path = os.path.join(self.masks_dir, self.images_list[index].replace('.jpg', '_mask.gif'))  # setting up path for mask\n        image = np.array(Image.open(image_path).convert('RGB'))  # converting to default RGB format to which we are used to\n        mask = np.array(Image.open(mask_path).convert('L'), dtype=np.float32) # converting to gray scale image for mask\n        mask[mask==255.0] = 1.0  # mask will have values either equal to 0 or 255 so converting 255 to 1 for classification norms\n        \n        if self.transform is not None:  # data augmentation \n            augmentations = self.transform(image=image, mask=mask) # passing both images and mask to the same transform so that augmented images and masks still seem relevant\n            image = augmentations['image']\n            mask = augmentations['mask']\n            \n        return image, mask # dataset will be returned as tuple of lists first will be of images and second will be of masks\n        ","metadata":{"execution":{"iopub.status.busy":"2023-05-02T08:37:22.474321Z","iopub.execute_input":"2023-05-02T08:37:22.474873Z","iopub.status.idle":"2023-05-02T08:37:22.485663Z","shell.execute_reply.started":"2023-05-02T08:37:22.474824Z","shell.execute_reply":"2023-05-02T08:37:22.484530Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"'''\n# alternative method of generating the dataset, but it will be much slow\ndef dataset(images_dir, masks_dir, transform=None):\n    data = []\n    img_list = os.listdir(images_dir)\n    for i in range(len(img_list)): # len(img_list)\n        if i%100==0:\n            print(f\"{i} images processed\")\n        img_path = os.path.join(images_dir, img_list[i])\n        mask_path = os.path.join(mask_dir, img_list[i].replace(\".jpg\", \"_mask.jpg\"))\n        img = np.array(Image.open(img_path).convert('RGB'), dtype=np.float32)\n        mask = np.array(Image.open(mask_path).convert(\"L\"), dtype=np.float32)\n        mask[mask=255.0] = 1.0\n        \n        if transform is not None:\n            img = transform(img)\n            mask = transform(img)\n        \n        \n        #plt.subplot(1, 2, 1)\n        #plt.imshow(img)\n        #plt.axis('off')\n        #plt.subplot(1, 2, 2)\n        #plt.imshow(mask)\n        #plt.axis('off')\n        #plt.show()\n        \n        \n        data.append((img, mask))\n    \n    return data\n'''","metadata":{"execution":{"iopub.status.busy":"2023-01-02T12:37:40.959452Z","iopub.execute_input":"2023-01-02T12:37:40.960706Z","iopub.status.idle":"2023-01-02T12:37:40.973409Z","shell.execute_reply.started":"2023-01-02T12:37:40.960653Z","shell.execute_reply":"2023-01-02T12:37:40.972178Z"}}},{"cell_type":"markdown","source":"# Model Building ","metadata":{}},{"cell_type":"code","source":"class block(nn.Module):\n    # a building block of UNET representing the two convs on a step\n    def __init__(self, in_channels, out_channels):\n        super(block, self).__init__()\n        all_layers = []\n        all_layers.append(nn.Conv2d(in_channels=in_channels, out_channels=out_channels, kernel_size=3, padding=1))\n        all_layers.append(nn.BatchNorm2d(out_channels))\n        all_layers.append(nn.ReLU())\n        \n        all_layers.append(nn.Conv2d(in_channels=out_channels, out_channels=out_channels, kernel_size=3, padding=1))\n        all_layers.append(nn.BatchNorm2d(out_channels))\n        all_layers.append(nn.ReLU())\n\n        self.model = nn.Sequential(*all_layers)\n        \n        \n    def forward(self, x):\n        return self.model(x)","metadata":{"execution":{"iopub.status.busy":"2023-05-02T08:37:41.300254Z","iopub.execute_input":"2023-05-02T08:37:41.300642Z","iopub.status.idle":"2023-05-02T08:37:41.307855Z","shell.execute_reply.started":"2023-05-02T08:37:41.300611Z","shell.execute_reply":"2023-05-02T08:37:41.306903Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class UNET(nn.Module):\n    def __init__(self, in_channels=3, out_channels=1):\n        super(UNET, self).__init__()\n        self.pool = nn.MaxPool2d(kernel_size=2)\n        # downsampling part\n        self.block1 = block(in_channels=in_channels, out_channels=64)\n        self.block2 = block(in_channels=64, out_channels=128)\n        self.block3 = block(in_channels=128, out_channels=256)\n        self.block4 = block(in_channels=256, out_channels=512)\n        self.block5 = block(in_channels=512, out_channels=1024)\n        # upsampling part\n        self.convt1 = nn.Sequential(\n            nn.ConvTranspose2d(in_channels=1024, out_channels=512, kernel_size=2, stride=2),\n            nn.BatchNorm2d(512),\n            nn.ReLU()\n        )\n        self.block6 = block(in_channels=1024, out_channels=512)\n        \n        self.convt2 = nn.Sequential(\n            nn.ConvTranspose2d(in_channels=512, out_channels=256, kernel_size=2, stride=2),\n            nn.BatchNorm2d(256),\n            nn.ReLU()\n        )\n        self.block7 = block(in_channels=512, out_channels=256)\n        \n        self.convt3 = nn.Sequential(\n            nn.ConvTranspose2d(in_channels=256, out_channels=128, kernel_size=2, stride=2),\n            nn.BatchNorm2d(128),\n            nn.ReLU()\n        )\n        self.block8 = block(in_channels=256, out_channels=128)\n        \n        self.convt4 = nn.Sequential(\n            nn.ConvTranspose2d(in_channels=128, out_channels=64, kernel_size=2, stride=2),\n            nn.BatchNorm2d(64),\n            nn.ReLU()\n        )\n        self.block9 = block(in_channels=128, out_channels=64)\n        # final layer which maps the image into one channel\n        self.final_layer = nn.Conv2d(in_channels=64, out_channels=out_channels, kernel_size=1)\n        \n    \n    \n    def forward(self, x):\n        # x - (3, 256, 256)\n        # downsampling first\n        skip1 = self.block1(x) # (64, 256, 256)\n        skip2 = self.block2(self.pool(skip1)) # (128, 128, 128)\n        skip3 = self.block3(self.pool(skip2)) # (256, 64, 64)\n        skip4 = self.block4(self.pool(skip3)) # (512, 32, 32)\n        op = self.block5(self.pool(skip4)) # (1024, 16, 16)\n        # upsampling\n        op = self.convt1(op) # (512, 32, 32)\n        op = self.block6(torch.cat([skip4, op], 1)) # (512, 32, 32)\n        \n        op = self.convt2(op) # (256, 64, 64)\n        op = self.block7(torch.cat([skip3, op], 1)) # (256, 64, 64)\n        \n        op = self.convt3(op) # (128, 128, 128)\n        op = self.block8(torch.cat([skip2, op], 1)) # (128, 128, 128)\n        \n        op = self.convt4(op) # (64, 256, 256)\n        op = self.block9(torch.cat([skip1, op], 1)) # (64, 256, 256)\n        \n        return self.final_layer(op) # (1, 256, 256)","metadata":{"execution":{"iopub.status.busy":"2023-05-02T08:37:52.386732Z","iopub.execute_input":"2023-05-02T08:37:52.387086Z","iopub.status.idle":"2023-05-02T08:37:52.401971Z","shell.execute_reply.started":"2023-05-02T08:37:52.387057Z","shell.execute_reply":"2023-05-02T08:37:52.400807Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"device = torch.device('cuda' if torch.cuda.is_available()else 'cpu')\nmodel = UNET(in_channels=3, out_channels=1).to(device)\nsummary(model, (3, 256, 256))","metadata":{"execution":{"iopub.status.busy":"2023-05-02T08:38:00.649320Z","iopub.execute_input":"2023-05-02T08:38:00.650069Z","iopub.status.idle":"2023-05-02T08:38:10.028817Z","shell.execute_reply.started":"2023-05-02T08:38:00.650027Z","shell.execute_reply":"2023-05-02T08:38:10.027685Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# UTILS","metadata":{}},{"cell_type":"code","source":"def save_checkpoint(state, filename='my_checkpoint.pth.tar'):\n    # will save model and optimizer params at every epoch\n    print(\"-> Saving CheckPoint\")\n    torch.save(state, filename)","metadata":{"execution":{"iopub.status.busy":"2023-05-02T08:38:16.186605Z","iopub.execute_input":"2023-05-02T08:38:16.187735Z","iopub.status.idle":"2023-05-02T08:38:16.193672Z","shell.execute_reply.started":"2023-05-02T08:38:16.187693Z","shell.execute_reply":"2023-05-02T08:38:16.192561Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def load_checkpoint(checkpoint, model):\n    # it will just load, we can train it further, make changes to the architecture\n    # and simply use it to predict\n    print(\"-> Loading CheckPoint\")\n    model.load_state_dict(checkpoint[\"state_dict\"])\n    ","metadata":{"execution":{"iopub.status.busy":"2023-05-02T08:38:20.830930Z","iopub.execute_input":"2023-05-02T08:38:20.831287Z","iopub.status.idle":"2023-05-02T08:38:20.836407Z","shell.execute_reply.started":"2023-05-02T08:38:20.831259Z","shell.execute_reply":"2023-05-02T08:38:20.835172Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def accuracy(loader, model, device='cuda'):\n    # for an image accuracy can be number of correct pixels predicted, and for a batch it can be average over all the images\n    # but at the same time simple accuracy is not a good metric to have as simply outputting all black pixels will lead to have an 80% accuracy \n    # so a better metric is dice score (idk what it is but let's just implement)\n    correct_pixels = 0 \n    total_pixels = 0\n    dice_score = 0\n    model.eval()  # so that dropout layers and batch_norm layers don't work \n    \n    with torch.no_grad(): # so that no gradient calculation is done\n        for x, y in loader: \n            x = x.to(device)\n            y = y.to(device).unsqueeze(1) # so that both have a batch dimension \n            pred = torch.sigmoid(model(x)) # sigmoid wasn't in network, if multiple classes we would have implemented softmax and proceeded further\n            pred = (pred>0.5).float() # will return bool and have to convert all of them to float\n            correct_pixels+= (pred==y).sum()\n            total_pixels+= torch.numel(pred) # just multiply all the dimension of the tensor\n            dice_score+= (2*(pred*y).sum()/((pred+y).sum() + 1e-6)) # don't know what this means\n        \n    print(f\"Validation accuracy is {(correct_pixels/total_pixels)*100}\")\n    print(f\"Validation dice score is {dice_score/len(loader)}\")\n    # remember that both the metrics are for whole val_dataset\n    model.train() # reactivating the training phase\n                          ","metadata":{"execution":{"iopub.status.busy":"2023-05-02T08:38:24.960818Z","iopub.execute_input":"2023-05-02T08:38:24.961217Z","iopub.status.idle":"2023-05-02T08:38:24.970021Z","shell.execute_reply.started":"2023-05-02T08:38:24.961186Z","shell.execute_reply":"2023-05-02T08:38:24.968785Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def save_predictions_as_images(loader, model, folder=\"saved_images/\", device='cuda'):\n    model.eval()\n    with torch.no_grad():\n        for idx, (x, y) in enumerate(loader, 0): # this time i have enumerated the dataloader so that different batches get different names and names being differentiated by idx\n            x = x.to(device)\n            pred = torch.sigmoid(model(x))\n            pred = (pred>0.5).float() # will return bool and have to convert all of them to float and also squeeze the dimension by 1\n            torchvision.utils.save_image(pred, f\"{folder}/pred_{idx}.png\") # setting up the saving path\n            torchvision.utils.save_image(y.unsqueeze(1), f\"{folder}/correct_{idx}.png\") # setting up the saving path\n    '''\n    torchvision.utils.save_image() if given a batch of images will save images\n    by grid or saying simply it will embed all images in a batch into one image.\n    '''\n    model.train()\n            \n            ","metadata":{"execution":{"iopub.status.busy":"2023-05-02T08:38:35.654644Z","iopub.execute_input":"2023-05-02T08:38:35.655040Z","iopub.status.idle":"2023-05-02T08:38:35.662786Z","shell.execute_reply.started":"2023-05-02T08:38:35.655011Z","shell.execute_reply":"2023-05-02T08:38:35.661557Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Training Loop\n","metadata":{}},{"cell_type":"code","source":"def train(loader, model, optimizer, loss_fn, scaler):\n    '''\n    it is the training procedure for one epoch of the network\n    '''\n    batches = tqdm(loader) # tqdm will be used to generate progress bars\n    \n    for idx, (inp, target) in enumerate(batches, 0):\n        inp = inp.to(device)\n        target = target.float().unsqueeze(1).to(device) # unsqueeze this to match the dimensions of the network and output\n        \n        # forward\n        with torch.cuda.amp.autocast(): # for gradient underflowing and overflowing and it makes training faster by converting all floats to float16\n            pred = model(inp)\n            loss = loss_fn(pred, target) \n            \n        optimizer.zero_grad()  # making all the previous gradients zero \n        scaler.scale(loss).backward()\n        scaler.step(optimizer)\n        scaler.update()\n        \n        batches.set_postfix(loss = loss.item()) # loss of this current batch on current iteration \n        \n        ","metadata":{"execution":{"iopub.status.busy":"2023-05-02T08:38:43.647317Z","iopub.execute_input":"2023-05-02T08:38:43.648411Z","iopub.status.idle":"2023-05-02T08:38:43.657669Z","shell.execute_reply.started":"2023-05-02T08:38:43.648369Z","shell.execute_reply":"2023-05-02T08:38:43.656596Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Driver Code","metadata":{}},{"cell_type":"code","source":"# hyperparameters\nlr = 1e-4\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nbatch_size = 32\nnum_workers = 2\nnum_epochs = 3 # just for testing if loop works fine else i am gonna set it to 100\nin_channels = 3\nout_channels = 1 # it is two but for binary it is one\n\n# setting up parameters for the dataset creation \nimages_dir = \"/kaggle/working/train\"\nmasks_dir = \"/kaggle/working/train_masks\"\nif not os.path.exists(\"/kaggle/working/saved_images\"):\n    os.mkdir(path=\"/kaggle/working/saved_images\") # creating a directory for saving images and its predictions\ntrain_transform = A.Compose([\n    A.Resize(width=256, height=256),\n    A.Rotate(limit = 30, p=0.6),\n    A.HorizontalFlip(p=0.6),\n    A.VerticalFlip(p=0.6),\n    A.Normalize(\n    mean = [0, 0, 0],\n    std = [1, 1, 1], \n    max_pixel_value = 255.0),\n    ToTensorV2() \n])\n\n'''\n# don't need this now as i am splitting the train_dataset into train and val\ntest_transform = A.Compose([\n    A.Resize(width=256, height=256),\n    A.Normalize(\n    mean = [0, 0, 0],\n    std = [1, 1, 1], \n    max_pixel_value = 255.0),\n    ToTensorV2() \n])\n'''\n\n# setting up the model, loss_fn, optimizer, scaler\nmodel = UNET(in_channels=in_channels, out_channels=out_channels).to(device)\nloss_fn = nn.BCEWithLogitsLoss() # cross entropy loss if there are multiple classes\noptimizer = torch.optim.Adam(model.parameters(), lr=lr)\nscaler = torch.cuda.amp.GradScaler()\n\n# setting up the dataset and dataloaders and splitting the dataset into training and testing dataloaders\ndata = dataset(images_dir=images_dir, masks_dir=masks_dir, transform=train_transform) # dataset is ready \npercent = (len(data)*9)//10# let us take 90% data for training\ntrain_data, test_data = torch.utils.data.random_split(data, [percent, len(data)-percent])\ntrain_loader = torch.utils.data.DataLoader(train_data, batch_size=batch_size, shuffle=True, num_workers=2)\ntest_loader = torch.utils.data.DataLoader(test_data, batch_size=batch_size, shuffle=True, num_workers=2)\n","metadata":{"execution":{"iopub.status.busy":"2023-05-02T08:38:50.552120Z","iopub.execute_input":"2023-05-02T08:38:50.552502Z","iopub.status.idle":"2023-05-02T08:38:50.930328Z","shell.execute_reply.started":"2023-05-02T08:38:50.552455Z","shell.execute_reply":"2023-05-02T08:38:50.929278Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# now training \nfor epoch in range(num_epochs):\n    train(train_loader, model, optimizer, loss_fn, scaler)\n    \n    # save checkpoints \n    checkpoint = {\n        'state_dict': model.state_dict(),\n        'optimizer':optimizer.state_dict()\n    }\n    save_checkpoint(checkpoint)\n    # check accuracy \n    accuracy(test_loader, model)\n    # check some examples \n    save_predictions_as_images(test_loader, model)\n    \n    ","metadata":{"execution":{"iopub.status.busy":"2023-05-02T08:39:00.632963Z","iopub.execute_input":"2023-05-02T08:39:00.633341Z","iopub.status.idle":"2023-05-02T08:52:58.229469Z","shell.execute_reply.started":"2023-05-02T08:39:00.633309Z","shell.execute_reply":"2023-05-02T08:52:58.228117Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"load_checkpoint(torch.load('my_checkpoint.pth.tar'), model)\naccuracy(test_loader, model)","metadata":{"execution":{"iopub.status.busy":"2023-05-02T08:56:35.174434Z","iopub.execute_input":"2023-05-02T08:56:35.175264Z","iopub.status.idle":"2023-05-02T08:56:58.018088Z","shell.execute_reply.started":"2023-05-02T08:56:35.175220Z","shell.execute_reply":"2023-05-02T08:56:58.015882Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"load_checkpoint(torch.load('my_checkpoint.pth.tar'), model)\naccuracy(train_loader, model)","metadata":{"execution":{"iopub.status.busy":"2023-05-02T08:57:00.017434Z","iopub.execute_input":"2023-05-02T08:57:00.018542Z","iopub.status.idle":"2023-05-02T09:00:16.495578Z","shell.execute_reply.started":"2023-05-02T08:57:00.018469Z","shell.execute_reply":"2023-05-02T09:00:16.494229Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# let us view the predictions\nsaved_dir = \"/kaggle/working/saved_images\"\n\npred_imgs_path = os.path.join(saved_dir, \"pred_0.png\")\norig_imgs_path = os.path.join(saved_dir, \"correct_0.png\")\n\npred_imgs = np.array(Image.open(pred_imgs_path))\norig_imgs = np.array(Image.open(orig_imgs_path))\n\n","metadata":{"execution":{"iopub.status.busy":"2023-05-02T09:00:33.368198Z","iopub.execute_input":"2023-05-02T09:00:33.368639Z","iopub.status.idle":"2023-05-02T09:00:33.452690Z","shell.execute_reply.started":"2023-05-02T09:00:33.368602Z","shell.execute_reply":"2023-05-02T09:00:33.451711Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"figure = plt.figure(figsize=(30, 30))\nplt.subplot(1, 2, 1)\nplt.imshow(pred_imgs)\nplt.axis('off')\nplt.title(\"predicted masks\")\nplt.subplot(1, 2, 2)\nplt.imshow(orig_imgs)\nplt.axis('off')\nplt.title(\"orig masks\")\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2023-05-02T09:00:44.255446Z","iopub.execute_input":"2023-05-02T09:00:44.256183Z","iopub.status.idle":"2023-05-02T09:00:45.014390Z","shell.execute_reply.started":"2023-05-02T09:00:44.256143Z","shell.execute_reply":"2023-05-02T09:00:45.013433Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# it's all working fine","metadata":{"execution":{"iopub.status.busy":"2023-05-02T09:00:52.652861Z","iopub.execute_input":"2023-05-02T09:00:52.653240Z","iopub.status.idle":"2023-05-02T09:00:52.657783Z","shell.execute_reply.started":"2023-05-02T09:00:52.653208Z","shell.execute_reply":"2023-05-02T09:00:52.656796Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}