{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":16880,"databundleVersionId":858837,"sourceType":"competition"}],"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\n\"\"\"import 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\n\n\"\"\"","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-02-02T07:13:25.159796Z","iopub.execute_input":"2026-02-02T07:13:25.160154Z","iopub.status.idle":"2026-02-02T07:13:25.165339Z","shell.execute_reply.started":"2026-02-02T07:13:25.160130Z","shell.execute_reply":"2026-02-02T07:13:25.164689Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\ntrain_df = pd.read_csv(\"/kaggle/input/deepfake-detection-challenge/sample_submission.csv\")\nprint(train_df.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-02T07:13:25.166540Z","iopub.execute_input":"2026-02-02T07:13:25.166749Z","iopub.status.idle":"2026-02-02T07:13:25.459256Z","shell.execute_reply.started":"2026-02-02T07:13:25.166730Z","shell.execute_reply":"2026-02-02T07:13:25.458646Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df['label'].value_counts()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-02T07:13:25.460506Z","iopub.execute_input":"2026-02-02T07:13:25.460752Z","iopub.status.idle":"2026-02-02T07:13:25.471125Z","shell.execute_reply.started":"2026-02-02T07:13:25.460730Z","shell.execute_reply":"2026-02-02T07:13:25.470500Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n\nimport json\n\nmetadata_path = \"/kaggle/input/deepfake-detection-challenge/train_sample_videos/metadata.json\"\n\nwith open(metadata_path, \"r\") as f:\n    metadata = json.load(f)\n\n# show first 5 entries\nlist(metadata.items())[:5]\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-02T07:13:25.472583Z","iopub.execute_input":"2026-02-02T07:13:25.472847Z","iopub.status.idle":"2026-02-02T07:13:25.496818Z","shell.execute_reply.started":"2026-02-02T07:13:25.472825Z","shell.execute_reply":"2026-02-02T07:13:25.496331Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import json\nimport pandas as pd\n\nmetadata_path = \"/kaggle/input/deepfake-detection-challenge/train_sample_videos/metadata.json\"\n\nwith open(metadata_path, \"r\") as f:\n    metadata = json.load(f)\n\ndf = pd.DataFrame.from_dict(metadata, orient=\"index\")\ndf.reset_index(inplace=True)\ndf.rename(columns={\"index\": \"filename\"}, inplace=True)\n\ndf[\"label\"] = df[\"label\"].map({\"REAL\": 0, \"FAKE\": 1})\ndf.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-02T07:13:25.498103Z","iopub.execute_input":"2026-02-02T07:13:25.498305Z","iopub.status.idle":"2026-02-02T07:13:25.573100Z","shell.execute_reply.started":"2026-02-02T07:13:25.498285Z","shell.execute_reply":"2026-02-02T07:13:25.572428Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df[\"label\"].value_counts()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-02T07:13:25.573933Z","iopub.execute_input":"2026-02-02T07:13:25.574227Z","iopub.status.idle":"2026-02-02T07:13:25.583440Z","shell.execute_reply.started":"2026-02-02T07:13:25.574205Z","shell.execute_reply":"2026-02-02T07:13:25.582760Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport cv2\nimport matplotlib.pyplot as plt\n\ndef extract_frame(video_path, fps=1, size=(224, 224), output_dir=\"frames\"):\n    os.makedirs(output_dir, exist_ok=True)\n\n    vid = cv2.VideoCapture(video_path)\n    video_fps = int(vid.get(cv2.CAP_PROP_FPS))\n\n    frame_interval = video_fps // fps\n    count = 0\n    saved = 0\n\n    while True:\n        success, image = vid.read()\n        if not success:\n            break\n\n        if count % frame_interval == 0:\n            image = cv2.resize(image, size)\n            cv2.imwrite(f\"{output_dir}/frame_{saved}.jpg\", image)\n            saved += 1\n\n        count += 1\n\n    vid.release()\n    print(f\"Extracted {saved} frames\")\n\n    # 🔹 Show first 5 extracted frames\n    show_frames(output_dir, num_frames=5)\n\n\ndef show_frames(folder, num_frames=5):\n    images = sorted(os.listdir(folder))[:num_frames]\n\n    plt.figure(figsize=(15, 5))\n    for i, img_name in enumerate(images):\n        img_path = os.path.join(folder, img_name)\n        img = cv2.imread(img_path)\n        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n\n        plt.subplot(1, num_frames, i + 1)\n        plt.imshow(img)\n        plt.title(f\"Frame {i}\")\n        plt.axis(\"off\")\n\n    plt.show()\n\n\n# Call the function\nextract_frame(\n    video_path=\"/kaggle/input/deepfake-detection-challenge/train_sample_videos/aagfhgtpmv.mp4\",\n    fps=1\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-02T07:13:25.584405Z","iopub.execute_input":"2026-02-02T07:13:25.584670Z","iopub.status.idle":"2026-02-02T07:13:27.631025Z","shell.execute_reply.started":"2026-02-02T07:13:25.584649Z","shell.execute_reply":"2026-02-02T07:13:27.630263Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport cv2\nimport matplotlib.pyplot as plt\n\ndef extract_frames_from_videos(\n    video_dir,\n    fps=1,\n    size=(224, 224),\n    output_root=\"frames\"\n):\n    os.makedirs(output_root, exist_ok=True)\n\n    video_files = [v for v in os.listdir(video_dir) if v.endswith(\".mp4\")]\n\n    for idx, video_name in enumerate(video_files):\n        video_path = os.path.join(video_dir, video_name)\n        video_folder = os.path.join(output_root, video_name.split(\".\")[0])\n        os.makedirs(video_folder, exist_ok=True)\n\n        vid = cv2.VideoCapture(video_path)\n        video_fps = int(vid.get(cv2.CAP_PROP_FPS))\n        frame_interval = max(1, video_fps // fps)\n\n        count = 0\n        saved = 0\n\n        while True:\n            success, frame = vid.read()\n            if not success:\n                break\n\n            if count % frame_interval == 0:\n                frame = cv2.resize(frame, size)\n                cv2.imwrite(f\"{video_folder}/frame_{saved}.jpg\", frame)\n                saved += 1\n\n            count += 1\n\n        vid.release()\n        print(f\"✔ {video_name}: {saved} frames extracted\")\n\n        # 🔹 Show first 5 frames ONLY for first video\n        if idx == 0:\n            show_frames(video_folder, num_frames=5)\n\n\ndef show_frames(folder, num_frames=5):\n    images = sorted(os.listdir(folder))[:num_frames]\n\n    plt.figure(figsize=(15, 5))\n    for i, img_name in enumerate(images):\n        img_path = os.path.join(folder, img_name)\n        img = cv2.imread(img_path)\n        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n\n        plt.subplot(1, num_frames, i + 1)\n        plt.imshow(img)\n        plt.title(f\"Frame {i}\")\n        plt.axis(\"off\")\n\n    plt.show()\n\n\nextract_frames_from_videos(\n    video_dir=\"/kaggle/input/deepfake-detection-challenge/train_sample_videos\",\n    fps=1,\n    output_root=\"train_frames\"\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-02T07:13:27.632055Z","iopub.execute_input":"2026-02-02T07:13:27.632316Z","iopub.status.idle":"2026-02-02T07:22:26.387846Z","shell.execute_reply.started":"2026-02-02T07:13:27.632294Z","shell.execute_reply":"2026-02-02T07:22:26.387073Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport json\nimport shutil\n\ndef auto_label_frames(\n    frames_root,\n    metadata_path,\n    output_root=\"train_frames\"\n):\n    # Load metadata\n    with open(metadata_path, \"r\") as f:\n        metadata = json.load(f)\n\n    real_dir = os.path.join(output_root, \"real\")\n    fake_dir = os.path.join(output_root, \"fake\")\n    os.makedirs(real_dir, exist_ok=True)\n    os.makedirs(fake_dir, exist_ok=True)\n\n    video_folders = os.listdir(frames_root)\n\n    for video_name in video_folders:\n        video_mp4 = video_name + \".mp4\"\n\n        if video_mp4 not in metadata:\n            continue\n\n        label = metadata[video_mp4][\"label\"]\n        target_dir = real_dir if label == \"REAL\" else fake_dir\n\n        video_folder_path = os.path.join(frames_root, video_name)\n\n        for frame in os.listdir(video_folder_path):\n            src = os.path.join(video_folder_path, frame)\n            new_name = f\"{video_name}_{frame}\"\n            dst = os.path.join(target_dir, new_name)\n\n            shutil.copy(src, dst)\n\n        print(f\"✔ {video_name} → {label}\")\n\n    print(\"✅ Auto-labeling completed\")\n\n\nauto_label_frames(\n    frames_root=\"/kaggle/working/train_frames\",\n    metadata_path=\"/kaggle/input/deepfake-detection-challenge/train_sample_videos/metadata.json\",\n    output_root=\"/kaggle/working/labeled_frames\"\n)\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-02T07:22:26.388900Z","iopub.execute_input":"2026-02-02T07:22:26.389245Z","iopub.status.idle":"2026-02-02T07:22:26.835507Z","shell.execute_reply.started":"2026-02-02T07:22:26.389222Z","shell.execute_reply":"2026-02-02T07:22:26.834902Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport json\nfrom PIL import Image\nfrom torch.utils.data import Dataset\n\nclass DeepfakeDataset(Dataset):\n    def __init__(self, frames_root, metadata_path, transform=None):\n        self.samples = []\n        self.transform = transform\n\n        # Load metadata\n        with open(metadata_path, \"r\") as f:\n            metadata = json.load(f)\n\n        for video_name in os.listdir(frames_root):\n            video_folder = os.path.join(frames_root, video_name)\n            video_mp4 = video_name + \".mp4\"\n\n            if video_mp4 not in metadata:\n                continue\n\n            label = metadata[video_mp4][\"label\"]\n            label = 0 if label == \"REAL\" else 1\n\n            for frame_name in os.listdir(video_folder):\n                frame_path = os.path.join(video_folder, frame_name)\n                self.samples.append((frame_path, label))\n\n    def __len__(self):\n        return len(self.samples)\n\n    def __getitem__(self, idx):\n        img_path, label = self.samples[idx]\n\n        image = Image.open(img_path).convert(\"RGB\")\n\n        if self.transform:\n            image = self.transform(image)\n\n        return image, label\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-02T07:22:26.836472Z","iopub.execute_input":"2026-02-02T07:22:26.836773Z","iopub.status.idle":"2026-02-02T07:22:31.889641Z","shell.execute_reply.started":"2026-02-02T07:22:26.836747Z","shell.execute_reply":"2026-02-02T07:22:31.888997Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from torchvision import transforms\nfrom torch.utils.data import DataLoader\n\ntransform = transforms.Compose([\n    transforms.Resize((224,224)),\n    transforms.ToTensor(),\n    transforms.Normalize(\n        mean=[0.485, 0.456, 0.406],\n        std=[0.229, 0.224, 0.225]\n    )\n])\n\ndataset = DeepfakeDataset(\n    frames_root=\"/kaggle/working/train_frames\",\n    metadata_path=\"/kaggle/input/deepfake-detection-challenge/train_sample_videos/metadata.json\",\n    transform=transform\n)\n\nloader = DataLoader(dataset, batch_size=32, shuffle=True)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-02T07:22:31.891742Z","iopub.execute_input":"2026-02-02T07:22:31.892119Z","iopub.status.idle":"2026-02-02T07:22:35.584180Z","shell.execute_reply.started":"2026-02-02T07:22:31.892096Z","shell.execute_reply":"2026-02-02T07:22:35.583512Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torch.optim as optim\nimport torchvision.models as models\n\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(\"Using device:\", device)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-02T07:22:35.585111Z","iopub.execute_input":"2026-02-02T07:22:35.585477Z","iopub.status.idle":"2026-02-02T07:22:35.827710Z","shell.execute_reply.started":"2026-02-02T07:22:35.585429Z","shell.execute_reply":"2026-02-02T07:22:35.827077Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = models.resnet18(pretrained=True)\n\n# Replace final layer (2 classes: REAL / FAKE)\nmodel.fc = nn.Linear(model.fc.in_features, 2)\n\nmodel = model.to(device)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-02T07:22:35.828815Z","iopub.execute_input":"2026-02-02T07:22:35.829082Z","iopub.status.idle":"2026-02-02T07:22:36.715091Z","shell.execute_reply.started":"2026-02-02T07:22:35.829052Z","shell.execute_reply":"2026-02-02T07:22:36.714243Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"criterion = nn.CrossEntropyLoss()\noptimizer = optim.Adam(model.parameters(), lr=1e-4)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-02T07:22:36.715980Z","iopub.execute_input":"2026-02-02T07:22:36.716311Z","iopub.status.idle":"2026-02-02T07:22:36.720427Z","shell.execute_reply.started":"2026-02-02T07:22:36.716286Z","shell.execute_reply":"2026-02-02T07:22:36.719649Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from torch.utils.data import random_split, DataLoader\n\ntrain_size = int(0.8 * len(dataset))\nval_size = len(dataset) - train_size\n\ntrain_ds, val_ds = random_split(dataset, [train_size, val_size])\n\ntrain_loader = DataLoader(train_ds, batch_size=32, shuffle=True)\nval_loader = DataLoader(val_ds, batch_size=32, shuffle=False)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-02T07:22:36.721230Z","iopub.execute_input":"2026-02-02T07:22:36.721527Z","iopub.status.idle":"2026-02-02T07:22:36.735022Z","shell.execute_reply.started":"2026-02-02T07:22:36.721497Z","shell.execute_reply":"2026-02-02T07:22:36.734467Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train_model(model, train_loader, val_loader, epochs=5):\n    for epoch in range(epochs):\n        # ---- TRAIN ----\n        model.train()\n        train_loss = 0\n        correct = 0\n        total = 0\n\n        for images, labels in train_loader:\n            images, labels = images.to(device), labels.to(device)\n\n            optimizer.zero_grad()\n            outputs = model(images)\n            loss = criterion(outputs, labels)\n            loss.backward()\n            optimizer.step()\n\n            train_loss += loss.item()\n            _, preds = torch.max(outputs, 1)\n            correct += (preds == labels).sum().item()\n            total += labels.size(0)\n\n        train_acc = 100 * correct / total\n\n        # ---- VALIDATE ----\n        model.eval()\n        val_correct = 0\n        val_total = 0\n\n        with torch.no_grad():\n            for images, labels in val_loader:\n                images, labels = images.to(device), labels.to(device)\n                outputs = model(images)\n                _, preds = torch.max(outputs, 1)\n                val_correct += (preds == labels).sum().item()\n                val_total += labels.size(0)\n\n        val_acc = 100 * val_correct / val_total\n\n        print(\n            f\"Epoch [{epoch+1}/{epochs}] | \"\n            f\"Train Loss: {train_loss:.4f} | \"\n            f\"Train Acc: {train_acc:.2f}% | \"\n            f\"Val Acc: {val_acc:.2f}%\"\n        )\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-02T07:22:36.735794Z","iopub.execute_input":"2026-02-02T07:22:36.736311Z","iopub.status.idle":"2026-02-02T07:22:36.747634Z","shell.execute_reply.started":"2026-02-02T07:22:36.736289Z","shell.execute_reply":"2026-02-02T07:22:36.746958Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_model(model, train_loader, val_loader, epochs=5)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-02T07:22:36.748355Z","iopub.execute_input":"2026-02-02T07:22:36.748590Z","iopub.status.idle":"2026-02-02T07:23:57.369669Z","shell.execute_reply.started":"2026-02-02T07:22:36.748570Z","shell.execute_reply":"2026-02-02T07:23:57.368909Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"torch.save(model.state_dict(), \"deepfake_resnet18.pth\")\nprint(\"Model saved successfully\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-02T07:23:57.370704Z","iopub.execute_input":"2026-02-02T07:23:57.371188Z","iopub.status.idle":"2026-02-02T07:23:57.429610Z","shell.execute_reply.started":"2026-02-02T07:23:57.371164Z","shell.execute_reply":"2026-02-02T07:23:57.428962Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torchvision.models as models\n\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\nmodel = models.resnet18(pretrained=False)\nmodel.fc = nn.Linear(model.fc.in_features, 2)\nmodel.load_state_dict(torch.load(\"deepfake_resnet18.pth\", map_location=device))\nmodel.to(device)\nmodel.eval()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-02T07:25:37.609560Z","iopub.execute_input":"2026-02-02T07:25:37.610347Z","iopub.status.idle":"2026-02-02T07:25:37.829795Z","shell.execute_reply.started":"2026-02-02T07:25:37.610316Z","shell.execute_reply":"2026-02-02T07:25:37.829220Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import cv2\nimport numpy as np\n\nclass GradCAM:\n    def __init__(self, model, target_layer):\n        self.model = model\n        self.gradients = None\n        self.activations = None\n\n        target_layer.register_forward_hook(self.save_activation)\n        target_layer.register_backward_hook(self.save_gradient)\n\n    def save_activation(self, module, input, output):\n        self.activations = output\n\n    def save_gradient(self, module, grad_input, grad_output):\n        self.gradients = grad_output[0]\n\n    def generate(self, x, class_idx):\n        output = self.model(x)\n        self.model.zero_grad()\n        output[0, class_idx].backward()\n\n        weights = self.gradients.mean(dim=(2, 3), keepdim=True)\n        cam = (weights * self.activations).sum(dim=1)\n\n        cam = torch.relu(cam)\n        cam = cam.squeeze().cpu().detach().numpy()\n        cam = cv2.resize(cam, (224, 224))\n        cam = (cam - cam.min()) / (cam.max() - cam.min())\n\n        return cam\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-02T07:25:52.494666Z","iopub.execute_input":"2026-02-02T07:25:52.495307Z","iopub.status.idle":"2026-02-02T07:25:52.501513Z","shell.execute_reply.started":"2026-02-02T07:25:52.495276Z","shell.execute_reply":"2026-02-02T07:25:52.500649Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from PIL import Image\nfrom torchvision import transforms\nimport matplotlib.pyplot as plt\n\ntransform = transforms.Compose([\n    transforms.Resize((224,224)),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485,0.456,0.406],\n                         [0.229,0.224,0.225])\n])\n\n# Pick any extracted frame\nimg_path = \"/kaggle/working/train_frames/aagfhgtpmv/frame_0.jpg\"\nimg = Image.open(img_path).convert(\"RGB\")\n\ninput_tensor = transform(img).unsqueeze(0).to(device)\n\ngradcam = GradCAM(model, model.layer4)\ncam = gradcam.generate(input_tensor, class_idx=1)  # FAKE class\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-02T07:26:04.878808Z","iopub.execute_input":"2026-02-02T07:26:04.879441Z","iopub.status.idle":"2026-02-02T07:26:05.147779Z","shell.execute_reply.started":"2026-02-02T07:26:04.879410Z","shell.execute_reply":"2026-02-02T07:26:05.147095Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"img_np = np.array(img.resize((224,224)))\nheatmap = cv2.applyColorMap(np.uint8(255 * cam), cv2.COLORMAP_JET)\noverlay = cv2.addWeighted(img_np, 0.6, heatmap, 0.4, 0)\n\nplt.figure(figsize=(6,6))\nplt.imshow(overlay)\nplt.axis(\"off\")\nplt.title(\"Forged Artifact Localization\")\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-02T07:26:21.402022Z","iopub.execute_input":"2026-02-02T07:26:21.402699Z","iopub.status.idle":"2026-02-02T07:26:21.616220Z","shell.execute_reply.started":"2026-02-02T07:26:21.402671Z","shell.execute_reply":"2026-02-02T07:26:21.615163Z"}},"outputs":[],"execution_count":null}]}