{"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":"code","source":"import numpy as np\nimport pandas as pd\nimport pydicom\nimport os\nimport collections\nimport sys\nimport glob\nimport random\nimport cv2\nimport tensorflow as tf\nimport multiprocessing\nfrom math import ceil, floor\nfrom copy import deepcopy\nfrom tqdm import tqdm_notebook as tqdm\nfrom imgaug import augmenters as iaa\nimport tensorflow.keras\nimport tensorflow.keras.backend as K\nfrom tensorflow.keras.callbacks import Callback, ModelCheckpoint\nfrom tensorflow.keras.layers import Dense, Flatten, Dropout\nfrom tensorflow.keras.models import Model, load_model\nfrom tensorflow.keras.utils import Sequence\nfrom tensorflow.keras.losses import binary_crossentropy\nfrom tensorflow.keras.optimizers import Adam","metadata":{"execution":{"iopub.status.busy":"2021-10-03T22:45:13.563813Z","iopub.execute_input":"2021-10-03T22:45:13.564122Z","iopub.status.idle":"2021-10-03T22:45:19.669015Z","shell.execute_reply.started":"2021-10-03T22:45:13.564043Z","shell.execute_reply":"2021-10-03T22:45:19.668269Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Install and import the efficientnet and iterative-stratification packages from the internet. The \npackage provides a very nice implementation of multi-label stratification. I've used it in a few competitions now with good results. There are offcourse more packages that provide implementations for it.","metadata":{}},{"cell_type":"code","source":"# Install Modules from internet\n!pip install efficientnet\n!pip install iterative-stratification","metadata":{"execution":{"iopub.status.busy":"2021-10-03T22:45:19.670719Z","iopub.execute_input":"2021-10-03T22:45:19.670954Z","iopub.status.idle":"2021-10-03T22:45:35.624719Z","shell.execute_reply.started":"2021-10-03T22:45:19.670922Z","shell.execute_reply":"2021-10-03T22:45:35.623874Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Import Custom Modules\nimport efficientnet.tfkeras as efn \nfrom iterstrat.ml_stratifiers import MultilabelStratifiedShuffleSplit","metadata":{"execution":{"iopub.status.busy":"2021-10-03T22:45:35.628008Z","iopub.execute_input":"2021-10-03T22:45:35.628267Z","iopub.status.idle":"2021-10-03T22:45:35.771732Z","shell.execute_reply.started":"2021-10-03T22:45:35.628239Z","shell.execute_reply":"2021-10-03T22:45:35.770987Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Next we will set the random_state, some constants and folders that will be used later on. I've specified a rather small test size as I want to maximize the training time available and minimize the time used for validation. I'am not using methods like early stopping...when the kernel time limit is approaching we could still increase the results on the LB if we were allowed to continue.","metadata":{}},{"cell_type":"code","source":"# Seed\nSEED = 12345\nnp.random.seed(SEED)\n# tf.set_random_seed(SEED)\n\n# Constants\nTEST_SIZE = 0.1\nHEIGHT = 256\nWIDTH = 256\nCHANNELS = 3\nTRAIN_BATCH_SIZE = 32\nVALID_BATCH_SIZE = 64\nSHAPE = (HEIGHT, WIDTH, CHANNELS)\n\n# Folders\nDATA_DIR = '/kaggle/input/rsna-intracranial-hemorrhage-detection/rsna-intracranial-hemorrhage-detection/'\nTEST_IMAGES_DIR = DATA_DIR + 'stage_2_test/'\nTRAIN_IMAGES_DIR = DATA_DIR + 'stage_2_train/'","metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","execution":{"iopub.status.busy":"2021-10-03T22:45:35.773788Z","iopub.execute_input":"2021-10-03T22:45:35.774048Z","iopub.status.idle":"2021-10-03T22:45:35.77925Z","shell.execute_reply.started":"2021-10-03T22:45:35.774015Z","shell.execute_reply":"2021-10-03T22:45:35.778638Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Next the code for the DICOM windowing and the Data Generators. After seeing the effect of different versions of windowing as presented in this very nice [kernel](https://www.kaggle.com/akensert/inceptionv3-prev-resnet50-keras-baseline-model) I decided to also update my kernel with it. Lets see what the effect will be.","metadata":{}},{"cell_type":"code","source":"def correct_dcm(dcm):\n    x = dcm.pixel_array + 1000\n    px_mode = 4096\n    x[x>=px_mode] = x[x>=px_mode] - px_mode\n    dcm.PixelData = x.tobytes()\n    dcm.RescaleIntercept = -1000\n\ndef window_image(dcm, window_center, window_width):    \n    if (dcm.BitsStored == 12) and (dcm.PixelRepresentation == 0) and (int(dcm.RescaleIntercept) > -100):\n        correct_dcm(dcm)\n    img = dcm.pixel_array * dcm.RescaleSlope + dcm.RescaleIntercept\n    \n    # Resize\n    img = cv2.resize(img, SHAPE[:2], interpolation = cv2.INTER_LINEAR)\n   \n    img_min = window_center - window_width // 2\n    img_max = window_center + window_width // 2\n    img = np.clip(img, img_min, img_max)\n    return img\n\ndef bsb_window(dcm):\n    brain_img = window_image(dcm, 40, 80)\n    subdural_img = window_image(dcm, 80, 200)\n    soft_img = window_image(dcm, 40, 380)\n    \n    brain_img = (brain_img - 0) / 80\n    subdural_img = (subdural_img - (-20)) / 200\n    soft_img = (soft_img - (-150)) / 380\n    bsb_img = np.array([brain_img, subdural_img, soft_img]).transpose(1,2,0)\n    return bsb_img\n\ndef _read(path, SHAPE):\n    if path.split('.')[-2]!='npy':\n        dcm = pydicom.dcmread(path)\n        try:\n            img = bsb_window(dcm)\n        except:\n            img = np.zeros(SHAPE)\n    else:\n        path = path[:-4]\n        path = path.split('/')[-1]\n        img = np.load(f'../input/isles-ct/pre_data/{path}')\n    return img","metadata":{"execution":{"iopub.status.busy":"2021-10-03T22:45:35.78059Z","iopub.execute_input":"2021-10-03T22:45:35.781108Z","iopub.status.idle":"2021-10-03T22:45:35.793897Z","shell.execute_reply.started":"2021-10-03T22:45:35.781059Z","shell.execute_reply":"2021-10-03T22:45:35.79304Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"I'll specify some light image augmentation. Some horizontal and vertical flipping and some cropping. I haven't yet tried out more augmentation but will do so in future versions of the kernel. Also the code for Data Generators for train and test data.","metadata":{}},{"cell_type":"code","source":"# Image Augmentation\nsometimes = lambda aug: iaa.Sometimes(0.25, aug)\naugmentation = iaa.Sequential([ iaa.Fliplr(0.25),\n                                iaa.Flipud(0.10),\n                                sometimes(iaa.Crop(px=(0, 25), keep_size = True, sample_independently = False))   \n                            ], random_order = True)       \n        \n# Generators\nclass TrainDataGenerator(tensorflow.keras.utils.Sequence):\n    def __init__(self, dataset, labels, batch_size = 16, img_size = SHAPE, img_dir = TRAIN_IMAGES_DIR, augment = False, *args, **kwargs):\n        self.dataset = dataset\n        self.ids = dataset.index\n        self.labels = labels\n        self.batch_size = batch_size\n        self.img_size = img_size\n        self.img_dir = img_dir\n        self.augment = augment\n        self.on_epoch_end()\n\n    def __len__(self):\n        return int(ceil(len(self.ids) / self.batch_size))\n\n    def __getitem__(self, index):\n        indices = self.indices[index*self.batch_size:(index+1)*self.batch_size]\n        X, Y = self.__data_generation(indices)\n        return X, Y\n\n    def augmentor(self, image):\n        augment_img = augmentation        \n        image_aug = augment_img.augment_image(image)\n        return image_aug\n\n    def on_epoch_end(self):\n        self.indices = np.arange(len(self.ids))\n        np.random.shuffle(self.indices)\n\n    def __data_generation(self, indices):\n        X = np.empty((self.batch_size, *self.img_size))\n        Y = np.empty((self.batch_size, 1), dtype=np.float32)\n        \n        for i, index in enumerate(indices):\n            ID = self.ids[index]\n            image = _read(self.img_dir+ID+\".dcm\", self.img_size)\n            if self.augment:\n                X[i,] = self.augmentor(image)\n            else:\n                X[i,] = image\n            Y[i,] = self.labels.iloc[index].values        \n        return X, Y\n \nclass ValDataGenerator(tensorflow.keras.utils.Sequence):\n    def __init__(self, dataset, labels, batch_size = 16, img_size = SHAPE, img_dir = TRAIN_IMAGES_DIR, augment = False, *args, **kwargs):\n        self.dataset = dataset\n        self.ids = dataset.index\n        self.labels = labels\n        self.batch_size = batch_size\n        self.img_size = img_size\n        self.img_dir = img_dir\n        self.augment = augment\n        self.on_epoch_end()\n\n    def __len__(self):\n        return int(ceil(len(self.ids) / self.batch_size))\n\n    def __getitem__(self, index):\n        indices = self.indices[index*self.batch_size:(index+1)*self.batch_size]\n        X, Y = self.__data_generation(indices)\n        return X, Y\n\n    def augmentor(self, image):\n        augment_img = augmentation        \n        image_aug = augment_img.augment_image(image)\n        return image_aug\n\n    def on_epoch_end(self):\n        self.indices = np.arange(len(self.ids))\n#         np.random.shuffle(self.indices)\n\n    def __data_generation(self, indices):\n        X = np.empty((self.batch_size, *self.img_size))\n        Y = np.empty((self.batch_size, 1), dtype=np.float32)\n        \n        for i, index in enumerate(indices):\n            ID = self.ids[index]\n            image = _read(self.img_dir+ID+\".dcm\", self.img_size)\n            \n            \n            \n            if self.augment:\n                X[i,] = self.augmentor(image)\n            else:\n                X[i,] = image\n            Y[i,] = self.labels.iloc[index].values        \n        return X, Y\n    \nclass TestDataGenerator(tensorflow.keras.utils.Sequence):\n    def __init__(self, dataset, labels, batch_size = 16, img_size = SHAPE, img_dir = TEST_IMAGES_DIR, *args, **kwargs):\n        self.dataset = dataset\n        self.ids = dataset.index\n        self.labels = labels\n        self.batch_size = batch_size\n        self.img_size = img_size\n        self.img_dir = img_dir\n        self.on_epoch_end()\n\n    def __len__(self):\n        return int(ceil(len(self.ids) / self.batch_size))\n\n    def __getitem__(self, index):\n        indices = self.indices[index*self.batch_size:(index+1)*self.batch_size]\n        X = self.__data_generation(indices)\n        return X\n\n    def on_epoch_end(self):\n        self.indices = np.arange(len(self.ids))\n    \n    def __data_generation(self, indices):\n        X = np.empty((self.batch_size, *self.img_size))\n        \n        for i, index in enumerate(indices):\n            ID = self.ids[index]\n            image = _read(self.img_dir+ID+\".dcm\", self.img_size)\n            X[i,] = image              \n        return X","metadata":{"execution":{"iopub.status.busy":"2021-10-03T22:45:40.382083Z","iopub.execute_input":"2021-10-03T22:45:40.382425Z","iopub.status.idle":"2021-10-03T22:45:40.41288Z","shell.execute_reply.started":"2021-10-03T22:45:40.38239Z","shell.execute_reply":"2021-10-03T22:45:40.411796Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Import the training and test datasets.","metadata":{"trusted":true}},{"cell_type":"code","source":"def read_testset(filename = DATA_DIR + \"stage_2_sample_submission.csv\"):\n    df = pd.read_csv(filename)\n    df[\"Image\"] = df[\"ID\"].str.slice(stop=12)\n    df[\"Diagnosis\"] = df[\"ID\"].str.slice(start=13)\n    df = df.loc[:, [\"Label\", \"Diagnosis\", \"Image\"]]\n    df = df.set_index(['Image', 'Diagnosis']).unstack(level=-1)\n    return df\n\ndef read_trainset(filename = DATA_DIR + \"stage_2_train.csv\"):\n    df = pd.read_csv(filename)\n    df[\"Image\"] = df[\"ID\"].str.slice(stop=12)\n    df[\"Diagnosis\"] = df[\"ID\"].str.slice(start=13)\n    duplicates_to_remove = [56346, 56347, 56348, 56349,\n                            56350, 56351, 1171830, 1171831,\n                            1171832, 1171833, 1171834, 1171835,\n                            3705312, 3705313, 3705314, 3705315,\n                            3705316, 3705317, 3842478, 3842479,\n                            3842480, 3842481, 3842482, 3842483 ]\n    df = df.drop(index = duplicates_to_remove)\n    df = df.reset_index(drop = True)    \n    df = df.loc[:, [\"Label\", \"Diagnosis\", \"Image\"]]\n    df = df.set_index(['Image', 'Diagnosis']).unstack(level=-1)\n    return df\n\n# Read Train and Test Datasets\ntest_df = read_testset()\ntrain_df = read_trainset()","metadata":{"execution":{"iopub.status.busy":"2021-10-03T22:45:42.719359Z","iopub.execute_input":"2021-10-03T22:45:42.719652Z","iopub.status.idle":"2021-10-03T22:46:00.061161Z","shell.execute_reply.started":"2021-10-03T22:45:42.719621Z","shell.execute_reply":"2021-10-03T22:46:00.060311Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_df = train_df.iloc[:]\ntrain_df","metadata":{"execution":{"iopub.status.busy":"2021-10-03T22:46:00.062663Z","iopub.execute_input":"2021-10-03T22:46:00.062923Z","iopub.status.idle":"2021-10-03T22:46:00.21775Z","shell.execute_reply.started":"2021-10-03T22:46:00.062889Z","shell.execute_reply":"2021-10-03T22:46:00.216817Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"The training data contains some class inbalance. Multiple kernels explored the use of undersampling..so let's try the opposite and oversample the minority class 'epidural' one additional time.","metadata":{}},{"cell_type":"code","source":"# Oversampling\nepidural_df = train_df[train_df.Label['epidural'] == 1]\ntrain_oversample_df = pd.concat([train_df, epidural_df])\ntrain_df = train_oversample_df\n\n# Summary\nprint('Train Shape: {}'.format(train_df.shape))\nprint('Test Shape: {}'.format(test_df.shape))","metadata":{"execution":{"iopub.status.busy":"2021-10-03T22:46:00.219195Z","iopub.execute_input":"2021-10-03T22:46:00.21946Z","iopub.status.idle":"2021-10-03T22:46:00.346087Z","shell.execute_reply.started":"2021-10-03T22:46:00.219427Z","shell.execute_reply":"2021-10-03T22:46:00.345268Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_df.index","metadata":{"execution":{"iopub.status.busy":"2021-10-03T22:46:00.347912Z","iopub.execute_input":"2021-10-03T22:46:00.348687Z","iopub.status.idle":"2021-10-03T22:46:00.418717Z","shell.execute_reply.started":"2021-10-03T22:46:00.348631Z","shell.execute_reply":"2021-10-03T22:46:00.417727Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def predictions(test_df, model):    \n    test_preds = model.predict_generator(TestDataGenerator(test_df, None, 8, SHAPE, TEST_IMAGES_DIR), verbose = 1)\n    return test_preds[:test_df.iloc[range(test_df.shape[0])].shape[0]]\n\ndef ModelCheckpointFull(model_name):\n    return ModelCheckpoint(model_name, \n                            monitor = 'val_AUC_full', \n                            verbose = 1, \n                            save_best_only = True, \n                            save_weights_only = True, \n                            mode = 'max', \n                            period = 1)\n\n# Create Model\ndef create_model():\n    K.clear_session()\n    \n    base_model =  efn.EfficientNetB2(weights = 'imagenet', include_top = False, pooling = 'avg', input_shape = SHAPE)\n    x = base_model.output\n    x = Dropout(0.15)(x)\n    y_pred = Dense(1, activation = 'sigmoid')(x)\n\n    return Model(inputs = base_model.input, outputs = y_pred)","metadata":{"execution":{"iopub.status.busy":"2021-10-03T22:46:00.42031Z","iopub.execute_input":"2021-10-03T22:46:00.420675Z","iopub.status.idle":"2021-10-03T22:46:00.43081Z","shell.execute_reply.started":"2021-10-03T22:46:00.42064Z","shell.execute_reply":"2021-10-03T22:46:00.429988Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Next we setup the multi label stratification. I've specified multiple splits but only using the first one for train data and validation data. Optionally you can also loop through the different splits and use a different train and validation set for each epoch. ","metadata":{}},{"cell_type":"code","source":"# Submission Placeholder\nsubmission_predictions = []\n# Multi Label Stratified Split stuff...\nmsss = MultilabelStratifiedShuffleSplit(n_splits = 10, test_size = TEST_SIZE, random_state = SEED)\nX = train_df.index\nY = train_df.Label.values","metadata":{"execution":{"iopub.status.busy":"2021-10-03T22:46:00.432008Z","iopub.execute_input":"2021-10-03T22:46:00.432627Z","iopub.status.idle":"2021-10-03T22:46:00.444066Z","shell.execute_reply.started":"2021-10-03T22:46:00.432576Z","shell.execute_reply":"2021-10-03T22:46:00.443263Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Get train and test index\nmsss_splits = next(msss.split(X, Y))\ntrain_idx = msss_splits[0]\nvalid_idx = msss_splits[1]","metadata":{"execution":{"iopub.status.busy":"2021-10-03T22:46:00.620766Z","iopub.execute_input":"2021-10-03T22:46:00.621337Z","iopub.status.idle":"2021-10-03T22:46:11.172212Z","shell.execute_reply.started":"2021-10-03T22:46:00.621301Z","shell.execute_reply":"2021-10-03T22:46:11.171453Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Now we can train the model for a number of epochs. All epochs we train the full model but each time on only 1/6 of the train data. With each epoch only a subset of the train data will allow us to make more epochs and allows todo averaging over more then just 1 or 2 epochs (compared to using all data every epoch).\n\nNote that I recreate the data generators and model on each epoch. This is only necessary when using the different Multi-label stratified splits since the data generators will get a totally different set of data on each epoch then. I left it in so that you can try it out.\n\nStarting with the 6th epoch a prediction for the test set is made on each epoch. In total predictions from the last 6 epochs will be averaged this way for the final submission.","metadata":{}},{"cell_type":"code","source":"test_BATCH_SIZE = 64\n\nrsna = train_df.iloc[valid_idx,[0]]\n\n# pred on 1000 sample slice\nrsna = rsna.iloc[:1000]\n\nval_generator_rsna = ValDataGenerator(rsna,\n                                        rsna,\n                                        test_BATCH_SIZE, \n                                        SHAPE,\n                                        augment = False)","metadata":{"execution":{"iopub.status.busy":"2021-10-03T22:46:30.928728Z","iopub.execute_input":"2021-10-03T22:46:30.929482Z","iopub.status.idle":"2021-10-03T22:46:30.947732Z","shell.execute_reply.started":"2021-10-03T22:46:30.929448Z","shell.execute_reply":"2021-10-03T22:46:30.946928Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"rsna.index","metadata":{"execution":{"iopub.status.busy":"2021-10-03T22:47:13.004159Z","iopub.execute_input":"2021-10-03T22:47:13.004414Z","iopub.status.idle":"2021-10-03T22:47:13.012491Z","shell.execute_reply.started":"2021-10-03T22:47:13.004387Z","shell.execute_reply":"2021-10-03T22:47:13.011539Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"model = create_model()   ","metadata":{"execution":{"iopub.status.busy":"2021-09-22T12:35:00.274663Z","iopub.execute_input":"2021-09-22T12:35:00.27506Z","iopub.status.idle":"2021-09-22T12:35:02.313897Z","shell.execute_reply.started":"2021-09-22T12:35:00.275025Z","shell.execute_reply":"2021-09-22T12:35:02.313134Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"model.load_weights('../input/usefhemorrhageeffnetb2abnorv1/model.h5')","metadata":{"execution":{"iopub.status.busy":"2021-09-22T12:35:11.853128Z","iopub.execute_input":"2021-09-22T12:35:11.853393Z","iopub.status.idle":"2021-09-22T12:35:13.086981Z","shell.execute_reply.started":"2021-09-22T12:35:11.853363Z","shell.execute_reply":"2021-09-22T12:35:13.086214Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# pred_isles = model.predict_generator(generator = val_generator_isles,verbose = 1)\npred_rsna = model.predict_generator(generator = val_generator_rsna,verbose = 1)","metadata":{"execution":{"iopub.status.busy":"2021-09-22T12:35:41.830212Z","iopub.execute_input":"2021-09-22T12:35:41.831039Z","iopub.status.idle":"2021-09-22T12:36:12.510443Z","shell.execute_reply.started":"2021-09-22T12:35:41.830988Z","shell.execute_reply":"2021-09-22T12:36:12.509637Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from sklearn.metrics import precision_recall_curve\nimport matplotlib.pyplot as plt\nfrom sklearn.metrics import roc_auc_score\nfrom sklearn.metrics import auc","metadata":{"execution":{"iopub.status.busy":"2021-09-22T12:36:12.512411Z","iopub.execute_input":"2021-09-22T12:36:12.512633Z","iopub.status.idle":"2021-09-22T12:36:12.516723Z","shell.execute_reply.started":"2021-09-22T12:36:12.512606Z","shell.execute_reply":"2021-09-22T12:36:12.516046Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print('roc:',roc_auc_score(rsna,pred_rsna[:1000]))#,\n\nprecision, recall, _ = precision_recall_curve(rsna,pred_rsna[:1000])\nplt.plot( recall, precision)\nplt.xlabel('recall')\nplt.ylabel('precision')\nplt.title('rsna(+isles)_v2')\nplt.savefig('rsna_v2.jpg',dpi=200,bbox_inches='tight')","metadata":{"execution":{"iopub.status.busy":"2021-09-22T12:36:41.747853Z","iopub.execute_input":"2021-09-22T12:36:41.748313Z","iopub.status.idle":"2021-09-22T12:36:42.319604Z","shell.execute_reply.started":"2021-09-22T12:36:41.748245Z","shell.execute_reply":"2021-09-22T12:36:42.318712Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{"execution":{"iopub.status.busy":"2021-09-22T01:12:44.949131Z","iopub.execute_input":"2021-09-22T01:12:44.949403Z","iopub.status.idle":"2021-09-22T01:12:46.520764Z","shell.execute_reply.started":"2021-09-22T01:12:44.949375Z","shell.execute_reply":"2021-09-22T01:12:46.520028Z"},"trusted":true},"execution_count":null,"outputs":[]}]}