{"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":"# Intro\n\nThis notebook is an attempt to localize tumors areas on the FLAIR scans series and use only this areas to train the model. Allocation will be performed based on the pixel brightness level as the tumor areas are highlighted on the FLAIR scans. Anyway, the notebook should only be considered as just a way to play with data. <br>\n\nOne of the main tasks is to reduce the level of brightness of of the brain edges, since that bright pixels are affect the measurements. This can be done with some tools from `scipy.ndimage` library such as `gaussian_filter`, `convolve` or `grey_erosion`, which gives some results. But we will try to implement filter from scratch to make the brain boundaries less bright and highlight tumor area even more. The location of the brightest area will be located with the `center_of_mass` function from `scipy.ndimage`.\n\nSome code and ideas from this works are used:\n* [https://www.kaggle.com/sreevishnudamodaran/tpu-rsna-keras-3d-cnn-voxel-train](https://www.kaggle.com/sreevishnudamodaran/tpu-rsna-keras-3d-cnn-voxel-train)\n* [https://www.kaggle.com/ihelon/brain-tumor-eda-with-animations-and-modeling](https://www.kaggle.com/ihelon/brain-tumor-eda-with-animations-and-modeling)\n* [https://www.kaggle.com/smoschou55/dicom-to-2d-resized-axial-pngs-256x256-x36](https://www.kaggle.com/smoschou55/dicom-to-2d-resized-axial-pngs-256x256-x36)\n\n","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport os\nimport glob\nimport random\nimport gc\nfrom multiprocessing import Pool\nfrom tqdm.notebook import tqdm\nimport matplotlib.pyplot as plt\nfrom IPython.display import HTML, Image\nfrom plotly.subplots import make_subplots\nimport plotly.graph_objects as go\nimport plotly.express as px\nimport pydicom\nimport PIL \nfrom scipy import ndimage\nimport cv2\nfrom sklearn.model_selection import KFold\nimport tensorflow as tf\nfrom tensorflow import keras\nfrom tensorflow.keras import layers","metadata":{"execution":{"iopub.status.busy":"2021-09-28T08:29:42.502535Z","iopub.execute_input":"2021-09-28T08:29:42.502960Z","iopub.status.idle":"2021-09-28T08:29:51.907085Z","shell.execute_reply.started":"2021-09-28T08:29:42.502920Z","shell.execute_reply":"2021-09-28T08:29:51.906368Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Paths \nKAGGLE_DIR = '/kaggle/input/rsna-miccai-brain-tumor-radiogenomic-classification/'\nIMG_PATH_TRAIN = KAGGLE_DIR + 'train/'\nIMG_PATH_TEST = KAGGLE_DIR + 'test/'\nTRAIN_CSV_PATH = KAGGLE_DIR + 'train_labels.csv'\nTEST_CSV_PATH = KAGGLE_DIR + 'sample_submission.csv'\n\n# All filenames for train and test images\ntrain_images = os.listdir(IMG_PATH_TRAIN)\ntest_images = os.listdir(IMG_PATH_TEST)\n\ntrain=pd.read_csv(TRAIN_CSV_PATH)\ntest=pd.read_csv(TEST_CSV_PATH)\n\n#write patient id to df\ntrain['patient_id']=sorted(train_images)\ntest['patient_id']=sorted(test_images)\n\n#drop problem cases\ntrain = train[(train.patient_id != \"00109\") & \n                     (train.patient_id != \"00123\") &\n                    (train.patient_id != \"00709\")]\n","metadata":{"execution":{"iopub.status.busy":"2021-09-28T05:41:01.945777Z","iopub.execute_input":"2021-09-28T05:41:01.945974Z","iopub.status.idle":"2021-09-28T05:41:02.061156Z","shell.execute_reply.started":"2021-09-28T05:41:01.945951Z","shell.execute_reply":"2021-09-28T05:41:02.060483Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#obtaining file pathes and images characteristics\n\nmods = ['FLAIR', 'T1w', 'T1wCE', 'T2w']\nfor mod in mods:\n    train[mod+'_path'] = IMG_PATH_TRAIN + train['patient_id'] + \"/\" + mod\n    \ncount=[] \nsizes=[]\nfor path in train['FLAIR_path']:\n    files = glob.glob(os.path.join(path, \"*\"))\n    count.append(len(files))\n    dicom = pydicom.read_file(files[0])\n    sizes.append(dicom.pixel_array.shape)\n\ntrain['Flair_count']=count\ntrain['Resolution']=[str(x[0]) + ' x ' + str(x[1]) for x in sizes]\ntrain['Pixel_count']=[x[0]*x[1] for x in sizes]\ntrain['Tumor_type']=train['MGMT_value'].map(lambda x: 'MGMT: 1' if x == 1 else 'MGMT: 0' )\n\ntrain.head()","metadata":{"execution":{"iopub.status.busy":"2021-09-28T05:41:02.063214Z","iopub.execute_input":"2021-09-28T05:41:02.063497Z","iopub.status.idle":"2021-09-28T05:41:21.785863Z","shell.execute_reply.started":"2021-09-28T05:41:02.063465Z","shell.execute_reply":"2021-09-28T05:41:21.785235Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"for mod in mods:\n    test[mod+'_path'] = IMG_PATH_TEST + test['patient_id'] + \"/\" + mod\ntest.head()","metadata":{"execution":{"iopub.status.busy":"2021-09-28T05:41:21.787929Z","iopub.execute_input":"2021-09-28T05:41:21.78818Z","iopub.status.idle":"2021-09-28T05:41:21.80913Z","shell.execute_reply.started":"2021-09-28T05:41:21.788148Z","shell.execute_reply":"2021-09-28T05:41:21.808135Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"config = {\n    'depth': 24,\n    'img_size': 96,\n    'nfolds': 4, \n    'batch_size': 16,\n    'learning_rate': 0.0008,\n    'num_epochs': 10\n}\n\ndef load_dicom(path):\n    dicom = pydicom.read_file(path)\n    data = dicom.pixel_array\n    data = data - np.min(data)\n    if np.max(data) != 0:\n        data = data / np.max(data)\n    data = data.astype(np.float32)\n    return data\n\ndef load_dicom_line(path):\n    t_paths = sorted(\n        glob.glob(os.path.join(path, \"*\")), \n        key=lambda x: int(x[:-4].split(\"-\")[-1]),\n    )\n    images = []\n    for filename in t_paths:\n        data = load_dicom(filename)\n        images.append(data)\n    return np.array(images)\n\ndef crop_image(image):\n    keep = (image.mean(axis=(0, 1)) > 0)\n    image = image[:, :, keep]\n    keep = (image.mean(axis=(0, 2)) > 0)\n    image = image[:, keep, :]\n    keep = (image.mean(axis=(1, 2)) > 0)\n    image = image[keep, :, :]\n    return image\n\ndef zoom_img(image, height, width, depth):\n    current_height = image.shape[1] \n    current_width = image.shape[2]\n    current_depth = image.shape[0]\n    \n    height_factor = 1 / (current_height/height)\n    width_factor = 1 / (current_width/width)\n    depth_factor = 1 / (current_depth/depth)\n        \n    image = ndimage.zoom(image, (depth_factor, height_factor, width_factor), order=1)\n    return image\n","metadata":{"execution":{"iopub.status.busy":"2021-09-28T07:37:29.699889Z","iopub.execute_input":"2021-09-28T07:37:29.700568Z","iopub.status.idle":"2021-09-28T07:37:29.714487Z","shell.execute_reply.started":"2021-09-28T07:37:29.700524Z","shell.execute_reply":"2021-09-28T07:37:29.713344Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Exploratory Data Analysis\n\nThe graphs below shows some scans statistics: \n* The count of the pixels per one scan farame (resolution);\n* Total number of the images for patients;\n* Distribution of the resolutions in the train folder.","metadata":{}},{"cell_type":"code","source":"fig=px.bar(train, y='Pixel_count', color='Resolution', title=\"The count of pixels per frame (resolution)\")\nfig.show()\nfig=px.bar(train, y='Flair_count', color='Tumor_type', title=\"The number of scans by the case\", color_discrete_sequence=[\"crimson\", \"gray\"])\nfig.show()\n\nfig = px.histogram(train, y='Resolution', color='Tumor_type', \n                   title=\"The scans resolution counts by the tumor type\", color_discrete_sequence=[\"crimson\", \"gray\"]).update_yaxes(categoryorder='total ascending', title='Scan resolution')   \n    \nfig.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2021-09-28T05:41:21.825686Z","iopub.execute_input":"2021-09-28T05:41:21.826553Z","iopub.status.idle":"2021-09-28T05:41:23.034371Z","shell.execute_reply.started":"2021-09-28T05:41:21.826504Z","shell.execute_reply":"2021-09-28T05:41:23.033699Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Lets visualise some cases projections and look at the brightness statistics along axes. \nHere and below, the axes of the array and the dimensions are designated as follows: (0, 1, 2) or (z, y, x) or (depth, height, width). The stats includes:\n* Mean value of brightness along axes;\n* Third qurtile (percentile 0.75) of brightness along axes;\n* Third qurtile (percentile 0.75) of brightness along axes for non-zero pixels only.","metadata":{}},{"cell_type":"code","source":"def plot_case_stats(path):\n\n    images = load_dicom_line(path)\n    images=crop_image(images)\n    images = zoom_img(images, 150, 150, 150)\n    \n   \n    x_q75 = np.quantile(images,0.75,axis=(1, 0))\n    x_iqr = x_q75 - np.quantile(images,0.25,axis=(1, 0))\n    x_mean = np.mean(images,axis=(1, 0))   \n    non_zeros = [(np.trim_zeros(images[:,:,i].ravel())) for i in range(images.shape[2])]\n    nz_x_q75 = [np.quantile(x,0.75) for x in non_zeros]  \n        \n    y_q75 = np.quantile(images,0.75,axis=(2, 0))\n    y_iqr = y_q75 - np.quantile(images,0.25,axis=(2, 0))\n    y_mean = np.mean(images,axis=(2, 0)) \n    non_zeros = [(np.trim_zeros(images[:,i,:].ravel())) for i in range(images.shape[1])]\n    nz_y_q75 = [np.quantile(x,0.75) for x in non_zeros]  \n\n    z_q75 = np.quantile(images,0.75,axis=(1, 2))\n    z_iqr = z_q75 - np.quantile(images,0.25,axis=(1, 2))\n    z_mean = np.mean(images,axis=(1, 2)) \n    non_zeros = [(np.trim_zeros(x.ravel())) for x in images]\n    nz_z_q75 = [np.quantile(x,0.75) for x in non_zeros]  \n    #nz_q75 = [np.mean(x) for x in non_zeros]  \n    \n    # plot Width and Height projections\n    fig = make_subplots(rows=1, cols=3, subplot_titles=(\"Width (X-axis)\",\n                \"Axes (X,Y) projection\", \"Height (Y-axis)\"))\n\n         #fig.add_trace(go.Scatter(y=x_iqr, name='x_IQR'),row=1, col=1)\n    fig.add_trace(go.Scatter(y=x_mean, name='x_Mean'),row=1, col=1)\n    fig.add_trace(go.Scatter(y = x_q75, name='x_q75'),row=1, col=1)\n    fig.add_trace(go.Scatter(y = nz_x_q75, name='nonzero_x_q75'),row=1, col=1)     \n        \n    fig.add_trace(px.imshow(np.sum(images, axis=0), binary_string=True).data[0], row=1, col=2)\n    \n    #fig.add_trace(go.Scatter(x=y_iqr, name='y_IQR'),row=1, col=3)\n    fig.add_trace(go.Scatter(x=y_mean, name='y_Mean'),row=1, col=3)\n    fig.add_trace(go.Scatter(x = y_q75, name='y_q75'),row=1, col=3)\n    fig.add_trace(go.Scatter(x = nz_y_q75, name='nonzero_y_q75'),row=1, col=3)  \n    fig.update_yaxes(row=1, col=3, autorange='reversed')        \n        \n    fig.update_layout(height=300, margin=dict(l=5, r=5, t=70, b=25),\n                      title_text='Projections and pixel brightness statistics for the case: {}'.format(path.split(\"/\")[-2]))\n    fig.show()    \n        \n    # plot Depth and Height projections    \n    fig = make_subplots(rows=1, cols=3, subplot_titles=(\"Depth (Z-axis)\", \"Axes (Y,Z) projection\", \"Height (Y-axis)\"))\n\n    #fig.add_trace(go.Scatter(y=z_iqr, name='z_IQR'),row=1, col=1)\n    fig.add_trace(go.Scatter(y=z_mean, name='z_Mean'),row=1, col=1)\n    fig.add_trace(go.Scatter(y = z_q75, name='z_q75'),row=1, col=1)\n    fig.add_trace(go.Scatter(y = nz_z_q75, name='nonzero_z_q75'),row=1, col=1)\n    \n    fig.add_trace(px.imshow(np.transpose(np.sum(images, axis=2)), binary_string=True).data[0], row=1, col=2)\n\n    #fig.add_trace(go.Scatter(x=y_iqr, name='y_IQR'),row=1, col=3)\n    fig.add_trace(go.Scatter(x=y_mean, name='y_Mean'),row=1, col=3)\n    fig.add_trace(go.Scatter(x = y_q75, name='y_q75'),row=1, col=3)\n    fig.add_trace(go.Scatter(x = nz_y_q75, name='nonzero_y_q75'),row=1, col=3)  \n    fig.update_yaxes(row=1, col=3, autorange='reversed')    \n\n    fig.update_layout(height=300, margin=dict(l=5, r=5, t=25, b=50))\n    fig.show()\n    ","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2021-09-28T05:41:23.035866Z","iopub.execute_input":"2021-09-28T05:41:23.036299Z","iopub.status.idle":"2021-09-28T05:41:23.058832Z","shell.execute_reply.started":"2021-09-28T05:41:23.036263Z","shell.execute_reply":"2021-09-28T05:41:23.058121Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#plot statistics for 3 random cases \nsample=train[train.Flair_count>70].sample(3)\nsample=train.sample(3)\nfor path in sample['FLAIR_path']:\n    plot_case_stats(path)","metadata":{"execution":{"iopub.status.busy":"2021-09-28T05:41:23.061544Z","iopub.execute_input":"2021-09-28T05:41:23.061753Z","iopub.status.idle":"2021-09-28T05:41:47.351547Z","shell.execute_reply.started":"2021-09-28T05:41:23.061731Z","shell.execute_reply":"2021-09-28T05:41:47.350744Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Filtering steps\n\nThe goal is to make inner bright pixels of the tumor brighter and outer brain pixels darker. The operation includes following steps:\n* Leave only scans with th number of non-zero pixels more than given threshold;\n* Calculate the 3rd quartile of brightness along the width axis for the original image. Multiply each frame of the image along the width axis by its 3rd quartile value;\n* Calculate the 3rd quartile of brightness along the height axis for the original image. Multiply each frame of the image filtered in the previous step along the height axis by 3rd quartile values of the original image;\n* Calculate the interquartile range (IQR) of brightness along the depth axis of the image filtered in the previous step. Divide each frame of the filtered image along the depth axis by its IQR value.\n\nMultiplying by height and width reduces the brightness of the edges. You can see as the tumor location affects the value of the 3rd quartile. But here is the first con - small tumors at the edges can be darkened. For the brightness normalization the dividing by depth's brightness IQR is performed. As shown at the \"Step 2 projection\" below, it will reduce the brightness ifference between the areas as brighter frames will be divided by higher values than darker frames. And here is the second con - the threshold of at the first step should be picked carefully, since the edges has less pixels and IQR value tends to be zero. In the same time we may lose some valuable information like small tumors located in the beginning or the end of the MRI.","metadata":{}},{"cell_type":"code","source":"image = load_dicom_line(\"../input/rsna-miccai-brain-tumor-radiogenomic-classification/train/00777/FLAIR\")\nimage=crop_image(image)\nimage = zoom_img(image, 128, 128, 128)\n#keep only scans with 'min_pixels'*100 % non-zero pixels\n# scans with less then 40% of meaningful pixels will be dropped\nmin_pixels = 0.4\ndepth_quantile = np.quantile(image, 1-min_pixels ,axis=(1, 2))\nkeep = (depth_quantile > 0)\nimage = image[keep, :, :]\n\n#step 0 projection\nstep0=np.sum(image, axis=0)\n#filter for initial 2-axis\nqX = np.quantile(image, 0.75 ,axis=(1, 0))\nfiltered = np.array([image[:,:,i] * qX[i] for i in range(image.shape[2])])\n\n#step 1 projection\nstep1=np.sum(filtered, axis=0)\n#filter for initial 1-axis (array reshaped after 2-axis filtering)\nqY = np.quantile(image, 0.75, axis=(0, 2))\nfiltered = np.array([filtered[:,:,i] * qY[i] for i in range(filtered.shape[2])]) \n\n#step 2 projection\nstep2=np.sum(filtered, axis=0)\n#filter for initial 0-axis (array reshaped after 1-axis filtering)\nq75Z = np.quantile(filtered, 0.75, axis=(0, 1))\nq25Z = np.quantile(filtered, 0.25, axis=(0, 1))\niqrZ = q75Z - q25Z\nfiltered = np.array([filtered[:,:,i] / iqrZ[i] for i in range(filtered.shape[2])]) \nfiltered=filtered/np.max(filtered)\n\n#step 3 projection\nstep3=np.sum(filtered, axis=0)\n\nmean_z = np.mean(filtered ,axis=(1, 2))\nmean_y = np.mean(filtered ,axis=(0, 2))\nmean_x = np.mean(filtered ,axis=(1, 0))\n\n#vectors of non-zero pixels\nimage_flatten=np.trim_zeros(image.ravel())\nfiltered_flatten=np.trim_zeros(filtered.ravel())","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2021-09-28T05:41:47.352899Z","iopub.execute_input":"2021-09-28T05:41:47.353151Z","iopub.status.idle":"2021-09-28T05:41:51.84083Z","shell.execute_reply.started":"2021-09-28T05:41:47.353118Z","shell.execute_reply":"2021-09-28T05:41:51.840086Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"fig = make_subplots(rows=4, cols=2, column_widths=[0.4, 0.6], subplot_titles=(\"Original projection\",\"Brightness 0.75 quantile along X-axis\", \n                                                                              \"Step 1 projection\",\"Brightness 0.75 quantile along Y-axis\", \n                                                                              \"Step 2 projection\",\"Brightness IQR along Z-axis\", \n                                                                              \"Filtered projection\",\"Original and Filtered images histograms\",))             \n\nfig.add_trace(px.imshow(step0, binary_string=True).data[0], row=1, col=1)   \nfig.add_trace(go.Scatter(y=qX, name='X-axis brightness 0.75 quantile'),row=1, col=2)\n\nfig.add_trace(px.imshow(step1, binary_string=True).data[0], row=2, col=1)   \nfig.add_trace(go.Scatter(y=qY, name='Y-axis brightness 0.75 quantile'),row=2, col=2)\n\nfig.add_trace(px.imshow(step2, binary_string=True).data[0], row=3, col=1)   \nfig.add_trace(go.Scatter(y=iqrZ, name='IQR brightness'),row=3, col=2)\n\nfig.add_trace(px.imshow(step3, binary_string=True).data[0], row=4, col=1)\nfig.add_trace(go.Histogram(x=image_flatten,nbinsx=30, name = 'Original image hist'),row=4, col=2)\nfig.add_trace(go.Histogram(x=filtered_flatten,nbinsx=30, name = 'Filtered image hist'),row=4, col=2)\n\nfig.update_layout(height=1000, title_text='Filtering steps and values')\nfig.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2021-09-28T05:41:51.843964Z","iopub.execute_input":"2021-09-28T05:41:51.844212Z","iopub.status.idle":"2021-09-28T05:41:54.404298Z","shell.execute_reply.started":"2021-09-28T05:41:51.844186Z","shell.execute_reply":"2021-09-28T05:41:54.403215Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Define the filter function and get few cases data","metadata":{}},{"cell_type":"code","source":"def q_filter(image, q1=0.25, q2=0.75, min_pixels=0.4, filter_mode = True):\n    q1, q2 = sorted([q1, q2])\n    threshold = 1-min_pixels\n    if q1 < threshold <q2:\n        #keep only scans with 'min_pixels'*100 % non-zero pixels\n        depth_quantile = np.quantile(image, 1-min_pixels ,axis=(1, 2))\n        keep = (depth_quantile > 0)\n        image = image[keep, :, :]\n        \n        #cropped by depth and filtered\n        if filter_mode:\n            #filter for initial 2-axis\n            qX = np.quantile(image, q2 ,axis=(1, 0))\n            filtered = np.array([image[:,:,i] * qX[i] for i in range(image.shape[2])])\n            \n            #filter for initial 1-axis (array reshaped after 2-axis filtering)\n            qY = np.quantile(image, q2, axis=(0, 2))\n            filtered = np.array([filtered[:,:,i] * qY[i] for i in range(filtered.shape[2])]) \n            #filter for initial 0-axis (array reshaped after 1-axis filtering)\n            iqrZ = np.quantile(filtered, q2, axis=(0, 1)) - np.quantile(filtered, q1, axis=(0, 1))\n\n            filtered = np.array([filtered[:,:,i] / iqrZ[i] for i in range(filtered.shape[2])]) \n            filtered=filtered/np.max(filtered)\n            return filtered\n        #only cropped by depth\n        else:\n            return image\n    else:\n        print(\"Wrong 'min_pixels' value, should be: q1 < (1-min_pixels) <q2, and given q1=\", q1, \"q2=\", q2, '(1-min_pixels)=', 1-min_pixels)","metadata":{"execution":{"iopub.status.busy":"2021-09-28T05:41:54.405538Z","iopub.execute_input":"2021-09-28T05:41:54.405807Z","iopub.status.idle":"2021-09-28T05:41:54.422241Z","shell.execute_reply.started":"2021-09-28T05:41:54.405768Z","shell.execute_reply":"2021-09-28T05:41:54.421054Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#get 3D scans for random cases\nsample=train.sample(8)\nimages=[]\nfiltered=[]\nim_filenames=[]\nfilt_filenames=[]\n\nfor case in sample['FLAIR_path']:\n    image = load_dicom_line(case)\n    image = crop_image(image)\n    image_filt = q_filter(image, q1=0.25, q2=0.75, min_pixels=0.4)\n    image = zoom_img(image, 160, 160, 48)   \n    image_filt=zoom_img(image_filt, 160, 160, 48)\n    images.append(image)\n    filtered.append(image_filt)\n    \nfor i, img in enumerate(images):\n    filename = \"image\" + str(i) + \".gif\"\n    im_filenames.append('<img src='+filename+'>')\n    gif = [PIL.Image.fromarray(frame) for frame in img*255]\n    gif[0].save(filename, save_all=True, append_images=gif[1:], duration=150, loop=0)\n    \nfor i, img in enumerate(filtered):\n    filename = \"filt\" + str(i) + \".gif\"\n    filt_filenames.append('<img src='+filename+'>')\n    gif = [PIL.Image.fromarray(frame) for frame in img*255]\n    gif[0].save(filename, save_all=True, append_images=gif[1:], duration=150, loop=0)   \n\nnp.array(images).shape","metadata":{"execution":{"iopub.status.busy":"2021-09-28T05:41:54.423953Z","iopub.execute_input":"2021-09-28T05:41:54.424419Z","iopub.status.idle":"2021-09-28T05:42:12.833605Z","shell.execute_reply.started":"2021-09-28T05:41:54.424343Z","shell.execute_reply":"2021-09-28T05:42:12.832887Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Define the cropping function\n\nFor location of center we use `scipy.ndimage.center_of_mass` function with 3D mask as input. The mask consists only the pixels with brightness higher than 0.995 percentile of array values (brightness). Unfortunately the performed manipulation could not eliminate all highlighted edge pixels, but for the some cases the difference between masks are visible.\n\nAfter obtaining center of mass coordinates, we crop the area around it. ","metadata":{}},{"cell_type":"code","source":"original_masks=[]\nfiltered_masks=[]\n# plot masks for the samples\nfor i, img in enumerate(images):\n    filename = \"mask\" + str(i) + \".gif\"\n    mask = (img > np.quantile(img, 0.995)) * img  \n    original_masks.append('<img src='+filename+'>')\n    gif = [PIL.Image.fromarray(frame) for frame in mask*255]\n    gif[0].save(filename, save_all=True, append_images=gif[1:], duration=150, loop=0)\n\nfor i, img in enumerate(filtered):\n    filename = \"mask_f\" + str(i) + \".gif\"\n    mask = (img > np.quantile(img, 0.995)) * img  \n    filtered_masks.append('<img src='+filename+'>')\n    gif = [PIL.Image.fromarray(frame) for frame in mask*255]\n    gif[0].save(filename, save_all=True, append_images=gif[1:], duration=150, loop=0)\n\nmasks=pd.DataFrame.from_dict({'Original images masks': original_masks, \n                              'Filtered images masks':filtered_masks}, orient = 'index')\n\nHTML(masks.to_html(escape=False))","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2021-09-28T05:42:12.835014Z","iopub.execute_input":"2021-09-28T05:42:12.835282Z","iopub.status.idle":"2021-09-28T05:42:13.837076Z","shell.execute_reply.started":"2021-09-28T05:42:12.835249Z","shell.execute_reply":"2021-09-28T05:42:13.836274Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#function to locate center of mass and leave the area around it\ndef crop_3d(image, size_ratio, depth_ratio, filter_image = True):\n    \n            #crop scans with less than 40% non-zero pixels and return filtered voxel\n    if filter_image:\n        image =  q_filter(image, filter_mode = True)    \n        masked = (image > np.quantile(image, 0.995)) * image  \n        center= ndimage.center_of_mass(masked)\n        center=np.array(center).astype(int)\n    else:\n            #just crop scans with less than 40% non-zero pixels\n        image =  q_filter(image, filter_mode = False)   \n            #get filtered image for the center obtaining  \n        filtered = q_filter(image, filter_mode = True) \n        masked = (filtered > np.quantile(filtered, 0.995)) * filtered  \n        center= ndimage.center_of_mass(masked)\n        center=np.array(center).astype(int)\n    \n    current_height = image.shape[1] \n    current_width = image.shape[2]\n    current_depth = image.shape[0]\n    \n    #sizes of the crop: height and width  \n    size = int(max(image.shape[1], image.shape[2])*size_ratio)\n    #the depth of the crop\n    new_depth = int(image.shape[0]*depth_ratio)\n    \n    y1, y2 = max([center[1]-size//2, 0]), min([center[1]+size//2, current_height])\n    x1, x2 = max([center[2]-size//2, 0]), min([center[2]+size//2, current_width])\n    z1, z2 = max([center[0]-new_depth//2, 0]), min([center[0]+new_depth//2, current_depth])   \n    \n    if y1 == 0:\n        y2 = y1 + size\n    elif y2 == current_height:\n        y1 = y2 - size\n    \n    if x1 == 0:\n        x2 = x1 + size\n    elif x2 == current_width:\n        x1 = x2 - size\n        \n    if z1 == 0:\n        z2 = z1 + new_depth\n    elif z2 == current_depth:\n        z1 = z2 - new_depth\n \n    image = image[z1:z2, y1:y2, x1:x2]   \n\n    return image","metadata":{"execution":{"iopub.status.busy":"2021-09-28T05:42:13.838514Z","iopub.execute_input":"2021-09-28T05:42:13.83878Z","iopub.status.idle":"2021-09-28T05:42:13.850321Z","shell.execute_reply.started":"2021-09-28T05:42:13.838747Z","shell.execute_reply":"2021-09-28T05:42:13.849716Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#areas of the original voxels cropped around filtered center of mass\ncrop_75=[]\ncrop_66=[]\n\nfor i, img in enumerate(images):\n    cropped = crop_3d(img, size_ratio=0.75, depth_ratio=1, filter_image = False)\n    cropped = zoom_img(cropped, 160, 160, cropped.shape[0])\n    filename = \"crop75_\" + str(i) + \".gif\"\n    crop_75.append('<img src='+filename+'>')\n    \n    gif = [PIL.Image.fromarray(frame) for frame in cropped*255]\n    gif[0].save(filename, save_all=True, append_images=gif[1:], duration=200, loop=0)   \n\nfor i, img in enumerate(images):\n    cropped = crop_3d(img,  size_ratio=0.66, depth_ratio=1, filter_image = False)\n    cropped = zoom_img(cropped, 160, 160, cropped.shape[0])\n    filename = \"crop66_\" + str(i) + \".gif\"\n    crop_66.append('<img src='+filename+'>')\n    \n    gif = [PIL.Image.fromarray(frame) for frame in cropped*255]\n    gif[0].save(filename, save_all=True, append_images=gif[1:], duration=200, loop=0)       ","metadata":{"execution":{"iopub.status.busy":"2021-09-28T05:42:13.851534Z","iopub.execute_input":"2021-09-28T05:42:13.85195Z","iopub.status.idle":"2021-09-28T05:42:16.950834Z","shell.execute_reply.started":"2021-09-28T05:42:13.851912Z","shell.execute_reply":"2021-09-28T05:42:16.949929Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#areas of the filtered voxels cropped around the center of mass\ncrop_filt_75=[]\ncrop_filt_66=[]\n\nfor i, img in enumerate(images):\n    cropped = crop_3d(img,  size_ratio=0.75, depth_ratio=1, filter_image = True)\n    cropped = zoom_img(cropped, 160, 160, cropped.shape[0])\n    filename = \"crop_filt75_\" + str(i) + \".gif\"\n    crop_filt_75.append('<img src='+filename+'>')\n    \n    gif = [PIL.Image.fromarray(frame) for frame in cropped*255]\n    gif[0].save(filename, save_all=True, append_images=gif[1:], duration=150, loop=0)   \n\nfor i, img in enumerate(images):\n    cropped = crop_3d(img,  size_ratio=0.66, depth_ratio=1, filter_image = True)\n    cropped = zoom_img(cropped, 160, 160, cropped.shape[0])\n    filename = \"crop_filt66_\" + str(i) + \".gif\"\n    crop_filt_66.append('<img src='+filename+'>')\n    \n    gif = [PIL.Image.fromarray(frame) for frame in cropped*255]\n    gif[0].save(filename, save_all=True, append_images=gif[1:], duration=150, loop=0)       ","metadata":{"execution":{"iopub.status.busy":"2021-09-28T05:42:16.952057Z","iopub.execute_input":"2021-09-28T05:42:16.954102Z","iopub.status.idle":"2021-09-28T05:42:19.568635Z","shell.execute_reply.started":"2021-09-28T05:42:16.954042Z","shell.execute_reply":"2021-09-28T05:42:19.567888Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Comparison of original and filtered voxels","metadata":{}},{"cell_type":"code","source":"data=sample[['BraTS21ID', 'MGMT_value', 'Resolution', 'Flair_count']].copy()\ndata['Original']=im_filenames\ndata['Filtered']=filt_filenames\ndata['Cropped 75%']=crop_75\ndata['Filt. and Cropped 75%']=crop_filt_75\ndata['Cropped 66%']=crop_66\ndata['Filt. and Cropped 66%']=crop_filt_66\n\nHTML(data.to_html(escape=False))","metadata":{"execution":{"iopub.status.busy":"2021-09-28T05:42:19.569942Z","iopub.execute_input":"2021-09-28T05:42:19.570305Z","iopub.status.idle":"2021-09-28T05:42:19.586666Z","shell.execute_reply.started":"2021-09-28T05:42:19.570254Z","shell.execute_reply":"2021-09-28T05:42:19.585932Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"As shown above, the tumor localized right for the most of cases. However, unfortunate outcomes of cropping are possible. ","metadata":{}},{"cell_type":"markdown","source":"# Building the model\n\nNow lets try to use the FLAIR slices for the model training. Further steps:\n* Data loading and preparation;\n* Defining and training the model;\n* Results evaluation.\n\nThe `seresnet50` model from [https://github.com/ZFTurbo/classification_models_3D](https://github.com/ZFTurbo/classification_models_3D) will be used for training.","metadata":{}},{"cell_type":"code","source":"#function to load voxels\n#try unfiltered slices with square sizes equals to 66% of max(height, width)\ndef read_img(path):\n    image = load_dicom_line(path)\n    image = crop_image(image)\n    image = crop_3d(image, size_ratio=0.66, depth_ratio=1, filter_image = False)\n    #image = zoom_img(image, config['img_size'], config['img_size'], config['depth'])\n       #sampling frames instead of zooming by depth for less quality loss\n    image = zoom_img(image, config['img_size'], config['img_size'], image.shape[0])\n    ind = np.linspace(0, image.shape[0]-1, config['depth']).astype(int)   \n    image = image[tuple(ind),: ,:]\n            \n    image=np.stack(image, axis=-1)\n    return image\n\ndef read_modality(paths):\n    if __name__ == '__main__':\n        with Pool(8) as p:\n            images=list(tqdm(p.imap(read_img, paths), total=len(paths)))\n            \n    images = tf.expand_dims(images, -1)\n    return images","metadata":{"execution":{"iopub.status.busy":"2021-09-28T05:42:19.587829Z","iopub.execute_input":"2021-09-28T05:42:19.588417Z","iopub.status.idle":"2021-09-28T05:42:19.598395Z","shell.execute_reply.started":"2021-09-28T05:42:19.588368Z","shell.execute_reply":"2021-09-28T05:42:19.59773Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"flair_train=read_modality(train.FLAIR_path)\nflair_train.shape ","metadata":{"execution":{"iopub.status.busy":"2021-09-28T05:42:19.59955Z","iopub.execute_input":"2021-09-28T05:42:19.600047Z","iopub.status.idle":"2021-09-28T05:54:22.131421Z","shell.execute_reply.started":"2021-09-28T05:42:19.600012Z","shell.execute_reply":"2021-09-28T05:54:22.130437Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"flair_test=read_modality(test.FLAIR_path)\nflair_test.shape","metadata":{"execution":{"iopub.status.busy":"2021-09-28T05:54:22.133393Z","iopub.execute_input":"2021-09-28T05:54:22.133977Z","iopub.status.idle":"2021-09-28T05:56:09.028793Z","shell.execute_reply.started":"2021-09-28T05:54:22.133934Z","shell.execute_reply":"2021-09-28T05:56:09.027998Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"y=np.asarray(train['MGMT_value']).astype('float32').reshape((-1,1))","metadata":{"execution":{"iopub.status.busy":"2021-09-28T05:56:09.030822Z","iopub.execute_input":"2021-09-28T05:56:09.031996Z","iopub.status.idle":"2021-09-28T05:56:09.04301Z","shell.execute_reply.started":"2021-09-28T05:56:09.031955Z","shell.execute_reply":"2021-09-28T05:56:09.042367Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!pip install classification-models-3D\n!pip install keras_applications","metadata":{"execution":{"iopub.status.busy":"2021-09-28T05:56:09.044976Z","iopub.execute_input":"2021-09-28T05:56:09.045913Z","iopub.status.idle":"2021-09-28T05:56:25.588116Z","shell.execute_reply.started":"2021-09-28T05:56:09.045874Z","shell.execute_reply":"2021-09-28T05:56:25.587271Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#in case of TPU run\ntry:\n    tpu = tf.distribute.cluster_resolver.TPUClusterResolver()\n    print('Device:', tpu.master())\n    tf.config.experimental_connect_to_cluster(tpu)\n    tf.tpu.experimental.initialize_tpu_system(tpu)\n    strategy = tf.distribute.experimental.TPUStrategy(tpu)\nexcept:\n    strategy = tf.distribute.get_strategy()\nprint('Number of replicas:', strategy.num_replicas_in_sync)","metadata":{"execution":{"iopub.status.busy":"2021-09-28T05:56:25.589847Z","iopub.execute_input":"2021-09-28T05:56:25.590075Z","iopub.status.idle":"2021-09-28T05:56:25.605333Z","shell.execute_reply.started":"2021-09-28T05:56:25.590047Z","shell.execute_reply":"2021-09-28T05:56:25.604669Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#https://www.kaggle.com/sreevishnudamodaran/tpu-rsna-keras-3d-cnn-voxel-train\n\nfrom classification_models_3D.tfkeras import Classifiers\n\nmodel_arch = 'seresnet50'\n\ndef create_model(input_shape, num_classes):\n    inputs = tf.keras.layers.Input((*input_shape, 1), name='inputs')\n    x = tf.keras.layers.Conv3D(3, (3, 3, 3), strides=(1, 1, 1), \n                          padding='same', use_bias=True)(inputs)\n    \n    net, preprocess_input = Classifiers.get(model_arch)\n    x = net(input_shape=(*input_shape, 3), include_top=False,\n                   weights='imagenet')(x)\n    \n    x = tf.keras.layers.GlobalAveragePooling3D()(x)\n    x = tf.keras.layers.Dropout(rate=0.5)(x)\n    \n    # Cast output to float32 for numerical stability\n    outputs = tf.keras.layers.Dense(num_classes, activation='sigmoid',\n                                   dtype='float32')(x)\n    model  = tf.keras.Model(inputs, outputs)\n   \n    model.compile(loss='binary_crossentropy',\n                      optimizer=keras.optimizers.SGD(learning_rate=config['learning_rate']),\n                      metrics=['AUC'])\n    return model\n\ncreate_model((config['img_size'], config['img_size'], config['depth']), 1).summary()","metadata":{"execution":{"iopub.status.busy":"2021-09-28T07:37:46.351525Z","iopub.execute_input":"2021-09-28T07:37:46.352242Z","iopub.status.idle":"2021-09-28T07:37:49.268645Z","shell.execute_reply.started":"2021-09-28T07:37:46.352207Z","shell.execute_reply":"2021-09-28T07:37:49.263604Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#https://keras.io/examples/vision/3D_image_classification/\n\n#3d data augmentation\n@tf.function\ndef rotate_shift(voxel):\n    \"\"\"Rotate and shift the voxel\"\"\"\n    def scipy_rotate_shift(voxel):\n        \n        # define the values for np.power(array)\n        degrees=np.round([x for x in np.linspace(0.85, 1.15, 7)], 2)\n        degree=random.choice(degrees)\n        # power array \n        voxel = np.power(voxel, degree)\n        \n        # define some rotation angles\n        #angles = [x for x in np.linspace(-5, 5, 6)] + [0, 180]\n        angles = [0, 180]\n        angle = random.choice(angles)\n        # pick rotation axes\n        axes = [(0,1), (0,2), (1,2)]\n        ax = random.choice(axes)\n        # rotate volume \n        voxel = ndimage.rotate(voxel, angle, axes = ax, reshape=False)\n\n        voxel[voxel < 0] = 0\n        voxel[voxel > 1] = 1\n        return voxel\n\n    augmented = tf.numpy_function(scipy_rotate_shift, [voxel], tf.float32)\n    return augmented\n\ndef train_preprocessing(voxel, label):\n    \"\"\"Process training data by rotating and shifting\"\"\"\n    # Rotate volume\n    voxel = rotate_shift(voxel)\n    return voxel, label","metadata":{"execution":{"iopub.status.busy":"2021-09-28T08:32:02.761896Z","iopub.execute_input":"2021-09-28T08:32:02.762192Z","iopub.status.idle":"2021-09-28T08:32:02.776044Z","shell.execute_reply.started":"2021-09-28T08:32:02.762164Z","shell.execute_reply":"2021-09-28T08:32:02.774981Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plateau = tf.keras.callbacks.ReduceLROnPlateau(monitor='val_loss', factor=0.25,\n                                               patience=3, verbose=1, mode='min', min_lr=1e-07)\n\ndef evaluate_model(X, y, data_test=flair_test, n_folds=config['nfolds']):\n    \n    counter = 1\n    preds=pd.DataFrame(columns=list(range(n_folds)))\n    histories = list()\n    kfold = KFold(n_folds, shuffle=True, random_state=1)\n       \n    for ind_train, ind_test in kfold.split(X):\n        \n        print('CV {}/{}'.format(counter, n_folds))\n        \n        with strategy.scope():\n            model = create_model((config['img_size'], config['img_size'], config['depth']), 1)\n               \n        X_train = tf.stack([X[x] for x in ind_train], axis=0)\n        X_test = tf.stack([X[x] for x in ind_test], axis=0)\n        y_train, y_test = y[ind_train], y[ind_test]\n\n        print('Train shape:', X_train.shape, 'Test shape:', X_test.shape)\n        \n        train_loader = tf.data.Dataset.from_tensor_slices((X_train, y_train))\n        validation_loader = tf.data.Dataset.from_tensor_slices((X_test, y_test))\n        \n        train_dataset = (\n            train_loader.shuffle(len(X_train), \n                                 reshuffle_each_iteration=True).map(train_preprocessing).batch(config['batch_size']).prefetch(2))\n            \n        validation_dataset = (\n            validation_loader.shuffle(len(X_test), \n                                      reshuffle_each_iteration=True).batch(config['batch_size']).prefetch(2))\n        \n        history = model.fit(train_dataset, epochs=config['num_epochs'], \n                            validation_data=validation_dataset , verbose=1, callbacks=plateau)\n  \n        histories.append(history)\n        \n        pred=model.predict(data_test)\n        pred=pred.reshape(pred.shape[0])\n        preds[counter-1]=pred\n        \n        counter+=1 \n        \n    return histories, preds","metadata":{"execution":{"iopub.status.busy":"2021-09-28T07:17:14.550731Z","iopub.execute_input":"2021-09-28T07:17:14.551483Z","iopub.status.idle":"2021-09-28T07:17:14.568727Z","shell.execute_reply.started":"2021-09-28T07:17:14.551446Z","shell.execute_reply":"2021-09-28T07:17:14.567724Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"tf.random.set_seed(1)\nhistories, predictions = evaluate_model(flair_train, y)","metadata":{"execution":{"iopub.status.busy":"2021-09-28T07:37:49.668281Z","iopub.execute_input":"2021-09-28T07:37:49.669037Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"losses={}\nval_losses={}\naucs={}\nval_aucs={}\nn = 1\nfor history in histories:\n    df=pd.DataFrame(history.history)\n    val_losses[\"val_loss, CV{}\".format(n)] = df['val_loss']\n    losses[\"loss, CV{}\".format(n)] = df['loss']\n    val_aucs[\"val_auc, CV{}\".format(n)] = df['val_auc']\n    aucs[\"auc, CV{}\".format(n)] = df['auc']\n    n+=1\n    \nlosses=pd.DataFrame(losses)  \nval_losses=pd.DataFrame(val_losses)  \naucs=pd.DataFrame(aucs)\nval_aucs=pd.DataFrame(val_aucs)","metadata":{"execution":{"iopub.status.busy":"2021-09-28T07:34:38.390148Z","iopub.execute_input":"2021-09-28T07:34:38.390523Z","iopub.status.idle":"2021-09-28T07:34:38.432083Z","shell.execute_reply.started":"2021-09-28T07:34:38.390483Z","shell.execute_reply":"2021-09-28T07:34:38.431137Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"titles=[]\nfor i in range(config['nfolds']):\n    titles.append('Losses, CV{}'.format(i+1)+\"/{}\".format(config['nfolds']))\n\nfig = make_subplots(rows=config['nfolds'], cols=1, x_title = \"Epoch\", subplot_titles=tuple(titles))\nfor i, loss in enumerate(losses):\n    fig.add_trace(go.Scatter(y=losses[loss], name=loss),row=i+1, col=1)\nfor i, loss in enumerate(val_losses):\n    fig.add_trace(go.Scatter(y=val_losses[loss], name=loss),row=i+1, col=1)\n        \nfig.update_layout(title_text='Losses values')\nfig.update_layout(height=1000)\nfig.show()","metadata":{"execution":{"iopub.status.busy":"2021-09-28T07:34:40.093569Z","iopub.execute_input":"2021-09-28T07:34:40.093866Z","iopub.status.idle":"2021-09-28T07:34:40.186323Z","shell.execute_reply.started":"2021-09-28T07:34:40.093836Z","shell.execute_reply":"2021-09-28T07:34:40.185478Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"titles=[]\nfor i in range(config['nfolds']):\n    titles.append('AUC values, CV{}'.format(i+1)+\"/{}\".format(config['nfolds']))\n\nfig = make_subplots(rows=config['nfolds'], cols=1, x_title = \"Epoch\", subplot_titles=tuple(titles))\nfor i, auc in enumerate(aucs):\n    fig.add_trace(go.Scatter(y=aucs[auc], name=auc),row=i+1, col=1)\nfor i, auc in enumerate(val_aucs):\n    fig.add_trace(go.Scatter(y=val_aucs[auc], name=auc),row=i+1, col=1)\n        \nfig.update_layout(title_text='AUC values')\nfig.update_layout(height=1000)\nfig.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2021-09-28T07:35:16.205931Z","iopub.execute_input":"2021-09-28T07:35:16.206212Z","iopub.status.idle":"2021-09-28T07:35:16.305765Z","shell.execute_reply.started":"2021-09-28T07:35:16.206181Z","shell.execute_reply":"2021-09-28T07:35:16.304843Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"test['MGMT_value'] = predictions.mean(axis=1)\ntest.head(10)","metadata":{"execution":{"iopub.status.busy":"2021-09-28T07:35:26.386807Z","iopub.execute_input":"2021-09-28T07:35:26.387578Z","iopub.status.idle":"2021-09-28T07:35:26.410504Z","shell.execute_reply.started":"2021-09-28T07:35:26.387539Z","shell.execute_reply":"2021-09-28T07:35:26.40957Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"test[['BraTS21ID', 'MGMT_value']].to_csv('submission.csv',index = False)","metadata":{"execution":{"iopub.status.busy":"2021-09-28T06:07:46.69024Z","iopub.execute_input":"2021-09-28T06:07:46.690648Z","iopub.status.idle":"2021-09-28T06:07:46.705222Z","shell.execute_reply.started":"2021-09-28T06:07:46.690613Z","shell.execute_reply":"2021-09-28T06:07:46.704152Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"fig = make_subplots(rows=1, cols=2, column_widths=[0.7, 0.3])\n                    \nfig.add_trace(go.Histogram(x=test['MGMT_value'], name = 'Probability of MGMT'),row=1, col=1)\nfig.add_trace(go.Histogram(x=round(test['MGMT_value']), name = 'Predicted Labels'),row=1, col=2)\n\nfig.update_layout(title_text='Predicted probabilities and labels')\nfig.show()","metadata":{"execution":{"iopub.status.busy":"2021-09-28T07:35:30.475933Z","iopub.execute_input":"2021-09-28T07:35:30.476591Z","iopub.status.idle":"2021-09-28T07:35:30.524718Z","shell.execute_reply.started":"2021-09-28T07:35:30.47655Z","shell.execute_reply":"2021-09-28T07:35:30.523848Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}