{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":11848,"databundleVersionId":862157,"sourceType":"competition"}],"dockerImageVersionId":30919,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import tensorflow as tf\nimport numpy as np\nimport pandas as pd\nimport os\nfrom PIL import Image\nfrom tensorflow.keras.preprocessing.image import img_to_array\nfrom tensorflow.image import resize\nfrom tensorflow.keras.layers import Input, Conv2D, Dense, BatchNormalization, ReLU, GlobalAveragePooling2D, DepthwiseConv2D, Concatenate, Lambda\nfrom tensorflow.keras.models import Model\nfrom sklearn.model_selection import train_test_split\nfrom pathlib import Path","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-02T19:09:23.942569Z","iopub.execute_input":"2025-04-02T19:09:23.942876Z","iopub.status.idle":"2025-04-02T19:09:36.299566Z","shell.execute_reply.started":"2025-04-02T19:09:23.942847Z","shell.execute_reply":"2025-04-02T19:09:36.298880Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"base_path = Path(\"/kaggle/input/histopathologic-cancer-detection\")\ntrain_dir = base_path / \"train\"\nlabels_csv = base_path / \"train_labels.csv\"\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-02T19:09:58.624011Z","iopub.execute_input":"2025-04-02T19:09:58.624606Z","iopub.status.idle":"2025-04-02T19:09:58.628859Z","shell.execute_reply.started":"2025-04-02T19:09:58.624573Z","shell.execute_reply":"2025-04-02T19:09:58.627714Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"labels = pd.read_csv(labels_csv)\nlabels[\"id\"] = labels[\"id\"] + \".tif\"\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-02T19:10:17.506336Z","iopub.execute_input":"2025-04-02T19:10:17.506718Z","iopub.status.idle":"2025-04-02T19:10:17.895477Z","shell.execute_reply.started":"2025-04-02T19:10:17.506693Z","shell.execute_reply":"2025-04-02T19:10:17.894455Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"labels = pd.concat([\n    labels[labels.label == 0].sample(5000, random_state=42),\n    labels[labels.label == 1].sample(5000, random_state=42)\n]).reset_index(drop=True)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-02T19:10:30.836102Z","iopub.execute_input":"2025-04-02T19:10:30.836412Z","iopub.status.idle":"2025-04-02T19:10:30.870146Z","shell.execute_reply.started":"2025-04-02T19:10:30.836385Z","shell.execute_reply":"2025-04-02T19:10:30.868914Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def load_images(ids, directory, img_size=128):\n    images = []\n    for image_id in ids:\n        path = directory / image_id\n        with Image.open(path) as img:\n            img = img.resize((img_size, img_size))\n            img = img_to_array(img)\n            images.append(img)\n    return np.array(images)\n\nX = load_images(labels[\"id\"], train_dir)\ny = labels[\"label\"].values\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-02T19:10:51.562861Z","iopub.execute_input":"2025-04-02T19:10:51.563145Z","iopub.status.idle":"2025-04-02T19:11:52.864517Z","shell.execute_reply.started":"2025-04-02T19:10:51.563122Z","shell.execute_reply":"2025-04-02T19:11:52.863578Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --------------------- 🖼️ Better Visualization of Sample Images ---------------------\nimport matplotlib.pyplot as plt\n\n# Randomly sample 6 images from each class\ncancer_indices = np.where(y == 1)[0]\nnon_cancer_indices = np.where(y == 0)[0]\n\nsample_indices = np.concatenate([\n    np.random.choice(non_cancer_indices, 6, replace=False),\n    np.random.choice(cancer_indices, 6, replace=False)\n])\n\n# Show 12 images (6 Non-Cancer + 6 Cancer)\nplt.figure(figsize=(14, 7))\nfor i, idx in enumerate(sample_indices):\n    plt.subplot(3, 4, i + 1)\n    img = X[idx]\n    plt.imshow(img, cmap='gray', vmin=0, vmax=1)  # force full contrast\n    label = \"Cancer\" if y[idx] == 1 else \"Non-Cancer\"\n    plt.title(label, color=\"red\" if label == \"Cancer\" else \"green\")\n    plt.axis(\"off\")\n\nplt.suptitle(\"Sample Images - Cancer vs Non-Cancer\")\nplt.tight_layout()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-02T19:16:20.072743Z","iopub.execute_input":"2025-04-02T19:16:20.073091Z","iopub.status.idle":"2025-04-02T19:16:21.126532Z","shell.execute_reply.started":"2025-04-02T19:16:20.073064Z","shell.execute_reply":"2025-04-02T19:16:21.125659Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Normalize\nX = X / 255.0","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-02T19:17:09.106370Z","iopub.execute_input":"2025-04-02T19:17:09.106662Z","iopub.status.idle":"2025-04-02T19:17:09.667180Z","shell.execute_reply.started":"2025-04-02T19:17:09.106639Z","shell.execute_reply":"2025-04-02T19:17:09.666523Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --------------------- Train/Test Split ---------------------\nX_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2, stratify=y, random_state=42)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-02T19:17:22.378663Z","iopub.execute_input":"2025-04-02T19:17:22.378955Z","iopub.status.idle":"2025-04-02T19:17:22.948074Z","shell.execute_reply.started":"2025-04-02T19:17:22.378933Z","shell.execute_reply":"2025-04-02T19:17:22.947384Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# TensorFlow Datasets\ndef preprocess_tf(image, label):\n    return tf.convert_to_tensor(image, dtype=tf.float32), tf.convert_to_tensor(label)\n\ntrain_dataset = tf.data.Dataset.from_tensor_slices((X_train, y_train)).map(preprocess_tf).batch(32)\nval_dataset = tf.data.Dataset.from_tensor_slices((X_val, y_val)).map(preprocess_tf).batch(32)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-02T19:17:42.650209Z","iopub.execute_input":"2025-04-02T19:17:42.650522Z","iopub.status.idle":"2025-04-02T19:17:48.176771Z","shell.execute_reply.started":"2025-04-02T19:17:42.650497Z","shell.execute_reply":"2025-04-02T19:17:48.175898Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --------------------- Channel Shuffle ---------------------\nclass ChannelShuffle(tf.keras.layers.Layer):\n    def __init__(self, groups, **kwargs):\n        super(ChannelShuffle, self).__init__(**kwargs)\n        self.groups = groups\n\n    def call(self, inputs):\n        batch_size, height, width, channels = tf.unstack(tf.shape(inputs))\n        channels_per_group = channels // self.groups\n        x = tf.reshape(inputs, [batch_size, height, width, self.groups, channels_per_group])\n        x = tf.transpose(x, perm=[0, 1, 2, 4, 3])\n        x = tf.reshape(x, [batch_size, height, width, channels])\n        return x\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-02T19:18:03.787961Z","iopub.execute_input":"2025-04-02T19:18:03.788243Z","iopub.status.idle":"2025-04-02T19:18:03.793783Z","shell.execute_reply.started":"2025-04-02T19:18:03.788220Z","shell.execute_reply":"2025-04-02T19:18:03.792955Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --------------------- Shuffle Block ---------------------\ndef shuffle_block(x, out_channels, stride):\n    mid_channels = out_channels // 2\n    if stride == 2:\n        left = DepthwiseConv2D(kernel_size=3, strides=2, padding=\"same\", use_bias=False)(x)\n        left = BatchNormalization()(left)\n        left = Conv2D(mid_channels, kernel_size=1, padding=\"same\", use_bias=False)(left)\n        left = BatchNormalization()(left)\n        left = ReLU()(left)\n\n        right = Conv2D(mid_channels, kernel_size=1, padding=\"same\", use_bias=False)(x)\n        right = BatchNormalization()(right)\n        right = ReLU()(right)\n        right = DepthwiseConv2D(kernel_size=3, strides=2, padding=\"same\", use_bias=False)(right)\n        right = BatchNormalization()(right)\n        right = Conv2D(mid_channels, kernel_size=1, padding=\"same\", use_bias=False)(right)\n        right = BatchNormalization()(right)\n        right = ReLU()(right)\n\n        x = Concatenate()([left, right])\n    else:\n        left, right = Lambda(lambda x: tf.split(x, num_or_size_splits=2, axis=-1))(x)\n        right = Conv2D(mid_channels, kernel_size=1, padding=\"same\", use_bias=False)(right)\n        right = BatchNormalization()(right)\n        right = ReLU()(right)\n        right = DepthwiseConv2D(kernel_size=3, strides=1, padding=\"same\", use_bias=False)(right)\n        right = BatchNormalization()(right)\n        right = Conv2D(mid_channels, kernel_size=1, padding=\"same\", use_bias=False)(right)\n        right = BatchNormalization()(right)\n        right = ReLU()(right)\n        x = Concatenate()([left, right])\n\n    x = ChannelShuffle(groups=2)(x)\n    return x\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-02T19:18:29.049117Z","iopub.execute_input":"2025-04-02T19:18:29.049427Z","iopub.status.idle":"2025-04-02T19:18:29.056709Z","shell.execute_reply.started":"2025-04-02T19:18:29.049400Z","shell.execute_reply":"2025-04-02T19:18:29.055867Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --------------------- Build ShuffleNet V2 ---------------------\ndef build_shufflenet_v2(input_shape=(128, 128, 3), num_classes=2):\n    input_layer = Input(shape=input_shape)\n    x = Conv2D(24, kernel_size=3, strides=1, padding=\"same\", use_bias=False)(input_layer)\n    x = BatchNormalization()(x)\n    x = ReLU()(x)\n\n    for out_channels, num_blocks, stride in [(48, 2, 2), (96, 4, 2), (192, 8, 2)]:\n        x = shuffle_block(x, out_channels, stride)\n        for _ in range(num_blocks - 1):\n            x = shuffle_block(x, out_channels, 1)\n\n    x = GlobalAveragePooling2D()(x)\n    x = Dense(num_classes, activation=\"softmax\")(x)\n    return Model(inputs=input_layer, outputs=x)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-02T19:18:45.178420Z","iopub.execute_input":"2025-04-02T19:18:45.178695Z","iopub.status.idle":"2025-04-02T19:18:45.184032Z","shell.execute_reply.started":"2025-04-02T19:18:45.178675Z","shell.execute_reply":"2025-04-02T19:18:45.183178Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --------------------- Compile & Train ---------------------\nmodel = build_shufflenet_v2()\nmodel.compile(optimizer=\"adam\", loss=\"sparse_categorical_crossentropy\", metrics=[\"accuracy\"])\n\nhistory = model.fit(train_dataset, epochs=10, validation_data=val_dataset)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-02T19:19:08.883234Z","iopub.execute_input":"2025-04-02T19:19:08.883654Z","iopub.status.idle":"2025-04-02T19:21:54.059256Z","shell.execute_reply.started":"2025-04-02T19:19:08.883611Z","shell.execute_reply":"2025-04-02T19:21:54.058602Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --------------------- Plot Accuracy ---------------------\nimport matplotlib.pyplot as plt\nplt.plot(history.history[\"accuracy\"], label=\"Train Accuracy\")\nplt.plot(history.history[\"val_accuracy\"], label=\"Val Accuracy\")\nplt.legend()\nplt.title(\"Training Progress\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-02T19:23:55.158962Z","iopub.execute_input":"2025-04-02T19:23:55.159276Z","iopub.status.idle":"2025-04-02T19:23:55.319183Z","shell.execute_reply.started":"2025-04-02T19:23:55.159253Z","shell.execute_reply":"2025-04-02T19:23:55.318459Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import tensorflow.keras.backend as K\nimport cv2","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-02T19:24:40.973488Z","iopub.execute_input":"2025-04-02T19:24:40.973779Z","iopub.status.idle":"2025-04-02T19:24:41.262984Z","shell.execute_reply.started":"2025-04-02T19:24:40.973755Z","shell.execute_reply":"2025-04-02T19:24:41.262335Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def make_gradcam_heatmap(img_array, model, last_conv_layer_name):\n    grad_model = tf.keras.models.Model(\n        [model.inputs],\n        [model.get_layer(last_conv_layer_name).output, model.output]\n    )\n\n    with tf.GradientTape() as tape:\n        conv_outputs, predictions = grad_model(img_array)\n        class_idx = tf.argmax(predictions[0])\n        loss = predictions[:, class_idx]\n\n    \n    pooled_grads = tf.reduce_mean(tape.gradient(loss, conv_outputs), axis=(0, 1, 2))\n    conv_outputs = conv_outputs[0]\n\n    heatmap = tf.reduce_sum(tf.multiply(pooled_grads, conv_outputs), axis=-1)\n    heatmap = tf.maximum(heatmap, 0) / tf.math.reduce_max(heatmap)\n\n    return heatmap.numpy()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-02T19:27:14.900180Z","iopub.execute_input":"2025-04-02T19:27:14.900521Z","iopub.status.idle":"2025-04-02T19:27:14.905967Z","shell.execute_reply.started":"2025-04-02T19:27:14.900493Z","shell.execute_reply":"2025-04-02T19:27:14.904989Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"idx = 10  # change index to test different images\nsample_image = X_val[idx]\nsample_tensor = tf.expand_dims(sample_image, axis=0)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-02T19:30:22.013052Z","iopub.execute_input":"2025-04-02T19:30:22.013420Z","iopub.status.idle":"2025-04-02T19:30:22.019730Z","shell.execute_reply.started":"2025-04-02T19:30:22.013387Z","shell.execute_reply":"2025-04-02T19:30:22.018858Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Automatically find last Conv2D layer\nlast_conv_layer_name = [layer.name for layer in model.layers if isinstance(layer, tf.keras.layers.Conv2D)][-1]\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-02T19:30:36.836865Z","iopub.execute_input":"2025-04-02T19:30:36.837178Z","iopub.status.idle":"2025-04-02T19:30:36.841128Z","shell.execute_reply.started":"2025-04-02T19:30:36.837150Z","shell.execute_reply":"2025-04-02T19:30:36.840270Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"heatmap = make_gradcam_heatmap(sample_tensor, model, last_conv_layer_name)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-02T19:30:54.043766Z","iopub.execute_input":"2025-04-02T19:30:54.044092Z","iopub.status.idle":"2025-04-02T19:30:55.324594Z","shell.execute_reply.started":"2025-04-02T19:30:54.044061Z","shell.execute_reply":"2025-04-02T19:30:55.323647Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nimport cv2\nimport numpy as np\n\ndef display_gradcam(original_image, heatmap, alpha=0.4):\n    heatmap_resized = cv2.resize(heatmap, (original_image.shape[1], original_image.shape[0]))\n    heatmap_colored = cv2.applyColorMap(np.uint8(255 * heatmap_resized), cv2.COLORMAP_JET)\n\n    overlay = heatmap_colored * alpha + np.uint8(original_image * 255)\n    overlay = np.clip(overlay, 0, 255).astype(np.uint8)\n\n    plt.figure(figsize=(6, 6))\n    plt.imshow(overlay)\n    plt.axis('off')\n    plt.title(\"Grad-CAM Overlay\")\n    plt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-02T19:31:08.373757Z","iopub.execute_input":"2025-04-02T19:31:08.374040Z","iopub.status.idle":"2025-04-02T19:31:08.379157Z","shell.execute_reply.started":"2025-04-02T19:31:08.374016Z","shell.execute_reply":"2025-04-02T19:31:08.378389Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"display_gradcam(sample_image, heatmap)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-02T19:31:41.795133Z","iopub.execute_input":"2025-04-02T19:31:41.795493Z","iopub.status.idle":"2025-04-02T19:31:41.916081Z","shell.execute_reply.started":"2025-04-02T19:31:41.795462Z","shell.execute_reply":"2025-04-02T19:31:41.915232Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"prediction = model.predict(sample_tensor)[0]\npredicted_class = np.argmax(prediction)\nprint(f\"Predicted Class: {predicted_class} (Confidence: {prediction[predicted_class]:.4f})\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-02T19:32:02.976087Z","iopub.execute_input":"2025-04-02T19:32:02.976402Z","iopub.status.idle":"2025-04-02T19:32:05.568107Z","shell.execute_reply.started":"2025-04-02T19:32:02.976375Z","shell.execute_reply":"2025-04-02T19:32:05.567437Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --------------------- 🖼️ Better Visualization of Sample Images ---------------------\nimport matplotlib.pyplot as plt\n\n# Randomly sample 6 images from each class\ncancer_indices = np.where(y == 1)[0]\nnon_cancer_indices = np.where(y == 0)[0]\n\nsample_indices = np.concatenate([\n    np.random.choice(non_cancer_indices, 6, replace=False),\n    np.random.choice(cancer_indices, 6, replace=False)\n])\n\n# Show 12 images (6 Non-Cancer + 6 Cancer)\nplt.figure(figsize=(14, 7))\nfor i, idx in enumerate(sample_indices):\n    plt.subplot(3, 4, i + 1)\n    img = X[idx]\n    plt.imshow(img, cmap='gray', vmin=0, vmax=1)  # force full contrast\n    label = \"Cancer\" if y[idx] == 1 else \"Non-Cancer\"\n    plt.title(label, color=\"red\" if label == \"Cancer\" else \"green\")\n    plt.axis(\"off\")\n\nplt.suptitle(\"Sample Images - Cancer vs Non-Cancer\")\nplt.tight_layout()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-02T19:33:44.047536Z","iopub.execute_input":"2025-04-02T19:33:44.047845Z","iopub.status.idle":"2025-04-02T19:33:44.918831Z","shell.execute_reply.started":"2025-04-02T19:33:44.047812Z","shell.execute_reply":"2025-04-02T19:33:44.917701Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --------------------- 📈 Plot Accuracy & Loss ---------------------\nplt.figure(figsize=(12, 5))\nplt.subplot(1, 2, 1)\nplt.plot(history.history[\"accuracy\"], label=\"Train Accuracy\")\nplt.plot(history.history[\"val_accuracy\"], label=\"Val Accuracy\")\nplt.title(\"Accuracy over Epochs\")\nplt.legend()\n\nplt.subplot(1, 2, 2)\nplt.plot(history.history[\"loss\"], label=\"Train Loss\")\nplt.plot(history.history[\"val_loss\"], label=\"Val Loss\")\nplt.title(\"Loss over Epochs\")\nplt.legend()\n\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-02T19:36:41.268148Z","iopub.execute_input":"2025-04-02T19:36:41.268519Z","iopub.status.idle":"2025-04-02T19:36:41.570890Z","shell.execute_reply.started":"2025-04-02T19:36:41.268489Z","shell.execute_reply":"2025-04-02T19:36:41.570030Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --------------------- 🔮 Predictions & Confusion Matrix ---------------------\nfrom sklearn.metrics import classification_report, confusion_matrix, ConfusionMatrixDisplay\n\n# Predict on validation set\nval_preds = model.predict(X_val, batch_size=32)\nval_pred_labels = np.argmax(val_preds, axis=1)\n\n# Confusion Matrix\ncm = confusion_matrix(y_val, val_pred_labels)\ndisp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=[\"Non-Cancer\", \"Cancer\"])\ndisp.plot(cmap=plt.cm.Blues)\nplt.title(\"Confusion Matrix\")\nplt.show()\n\n# Classification Report\nprint(\"Classification Report:\")\nprint(classification_report(y_val, val_pred_labels, target_names=[\"Non-Cancer\", \"Cancer\"]))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-02T19:36:57.062779Z","iopub.execute_input":"2025-04-02T19:36:57.063085Z","iopub.status.idle":"2025-04-02T19:37:03.140553Z","shell.execute_reply.started":"2025-04-02T19:36:57.063059Z","shell.execute_reply":"2025-04-02T19:37:03.139653Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}