{"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":"gpu","dataSources":[{"sourceId":117682,"databundleVersionId":15062069,"sourceType":"competition"}],"dockerImageVersionId":31234,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# 📜 Project: ScrollVision-AIStrategic Lead: Mahmoud Saad ElgharebTechnical Focus: Deep Learning for Ancient Document Recovery1.\nExecutive Summary: ScrollVision-AI is a high-performance computer vision pipeline designed to solve the \"Vesuvius Challenge.\" The system utilizes 3D X-ray Computed Tomography (CT) data to detect non-destructive ink patterns buried within carbonized papyrus layers. By leveraging PyTorch Lightning and GPU-accelerated decompression, this project transforms raw volumetric data into readable 2D probability maps.2. Detailed Technical Architecture (How the Code Works)A. The Data Engine (High-Resolution Processing)The core challenge is processing the 786-layer TIF volumes. Standard loading methods fail due to LZW compression.The Solution: We implemented a custom Dataset that uses the imagecodecs library. This allows the GPU to handle decompression in real-time, preventing CPU bottlenecks.Volumetric Patching: The code slices the large scroll into small $64 \\times 64 \\times 8$ voxels. This \"Body Recomposition\" of data ensures the model focuses on the surface depth where ink resides.B. The Modular Pipeline (PyTorch Lightning)Instead of messy scripts, we used a LightningDataModule to manage the lifecycle of the data:Initial Setup: Scans directories for training and test images.Validation Logic: Automatically reserves 20% of data for \"blind testing\" during training to prevent overfitting.Test Branch: A dedicated path for the Inference Phase, where the model predicts ink on fragments it has never seen before.3. Operational Guide: Training & Result ExtractionStep 1: Initiating the Learning PhaseThe training is executed via the Trainer class. It uses 16-bit Mixed Precision, allowing the model to train faster and use less VRAM.Python\n## Launching the training engine\n","metadata":{}},{"cell_type":"markdown","source":"## trainer.fit(model, data_module)","metadata":{}},{"cell_type":"markdown","source":"# Step 2: The Inference & Visualization Phase (Test)Once training is complete, the predict_and_plot_and_save function is triggered. \nThis is where the \"magic\" happens:Logic: The model processes the Test Images, applies a Sigmoid activation, and thresholds the results at $0.5$.Output: It generates a comparative plot showing the Original Slice vs. the Predicted Ink Map.4. How to Retrieve Your Project Files (The Output)After running the code, your results are organized into three main categories in the /kaggle/working/ directory:\n## 🛠️ The Model (Your Intellectual Property)vesuvius_final_model.pth: This is the \"Weights\" file. \nIt contains the learned patterns. You can load this file later to run the model without retraining.full_project_backup.pt: A complete snapshot of the model architecture and parameters.\n## 📊 The Performance Data (Analytics)metrics.csv: Every epoch's loss and accuracy is recorded here. \nYou can use this to prove that your model actually learned and didn't just guess.\n## 🖼️ The Visual Proof (Evidence)test_result_plot.png: A high-resolution image of the model's predictions. \nPerfect for presentations or your portfolio.test_predictions.npy: The raw numerical data of the predicted ink, which can be used for further statistical analysis.5. Technical Stack SummaryCore: PyTorch / PyTorch LightningData Science: NumPy, Pandas, MatplotlibAdvanced Image Ops: Imagecodecs, TifffileHardware: NVIDIA Tesla GPU (for LZW Decompression)","metadata":{}},{"cell_type":"code","source":"# Install specialized codec to decode LZW-compressed 3D TIF files\n!pip install -q imagecodecs","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-24T19:25:50.045106Z","iopub.execute_input":"2026-01-24T19:25:50.045343Z","iopub.status.idle":"2026-01-24T19:25:56.874818Z","shell.execute_reply.started":"2026-01-24T19:25:50.045319Z","shell.execute_reply":"2026-01-24T19:25:56.873908Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Part 1: Libraries & Setup 🛠️\nImporting core frameworks and configuring the GPU environment.\n\n## 📦 Core: \ntorch, pytorch_lightning.\n\n## 🧠 Model Hub:\nsegmentation_models_pytorch.\n\n## 📊 Logging:\nCSVLogger for performance tracking.","metadata":{}},{"cell_type":"code","source":"# --- Visualization and System Utilities ---\nimport matplotlib.pyplot as plt  # For plotting ink detection maps and training curves\nimport sys                       # To access system-specific parameters and functions\nimport tqdm                      # To display progress bars during data loading and training\nimport glob                      # To retrieve files/pathnames matching a specified pattern\nimport numpy as np               # Core library for high-performance numerical array operations\nimport os                        # To interact with the operating system and manage file paths\nimport cv2                       # Open Source Computer Vision Library for advanced image processing\n\n# --- Deep Learning Framework (PyTorch Lightning) ---\nimport pytorch_lightning as pl   # High-level wrapper for PyTorch to organize training pipelines\n\n# --- Image Handling and Data Pipelining ---\nimport matplotlib.image as mpimg         # For basic image reading and display operations\nfrom torchvision.datasets import ImageFolder # Tool for loading organized image datasets\nfrom torch.utils.data import Dataset, DataLoader # Base classes for custom data engines\nfrom typing import Tuple, Optional, Dict, List, Callable # For professional Type Hinting\n\n# --- Computer Vision and Tensor Operations ---\nimport torchvision.transforms as transforms # To apply augmentations and data preprocessing\nimport torch                               # The main Deep Learning engine\nimport torchvision                          # Utilities for Computer Vision tasks\nfrom PIL import Image                       # Python Imaging Library for opening/manipulating images\n\n# --- Specialized 3D/Scientific Image Decoding ---\nimport tifffile as tiff          # Specifically for reading multi-layer TIF/TIFF stacks\nimport imagecodecs               # CRITICAL: Handles GPU-accelerated LZW decompression for 3D data\nfrom pathlib import Path         # Modern, object-oriented file path management\n\n# --- System Logging and Warning Management ---\nimport warnings                  # To control warning messages during execution\nimport logging                   # To record events and errors for debugging\nimport os\n\n# --- Environment Optimization ---\n# Set TensorFlow log level to suppress unnecessary background noise\nos.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' \n# Disable python warnings to keep the console output clean for metrics\nos.environ['PYTHONWARNINGS'] = 'ignore'\n# Filter out non-critical warnings that clutter the training logs\nwarnings.filterwarnings('ignore')\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-01-24T19:25:56.877067Z","iopub.execute_input":"2026-01-24T19:25:56.877343Z","iopub.status.idle":"2026-01-24T19:26:16.827714Z","shell.execute_reply.started":"2026-01-24T19:25:56.877312Z","shell.execute_reply":"2026-01-24T19:26:16.827117Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Hardware acceleration: Use CUDA GPU if available, otherwise fallback to CPU\nDEVICE = 'cuda' if torch.cuda.is_available() else 'cpu'\n\n# Display the active compute device for verification\nprint(DEVICE)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-24T19:26:16.828584Z","iopub.execute_input":"2026-01-24T19:26:16.829066Z","iopub.status.idle":"2026-01-24T19:26:16.861801Z","shell.execute_reply.started":"2026-01-24T19:26:16.829044Z","shell.execute_reply":"2026-01-24T19:26:16.861162Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Set seed for Python's built-in random number generator\nimport random\nseed = 42\n\n# Ensure PyTorch operations produce identical results across runs\ntorch.manual_seed(seed)\n\n# Fix the seed for NumPy's mathematical and random operations\nnp.random.seed(seed)\n\n# Standardize randomness for all basic Python operations\nrandom.seed(seed)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-24T19:26:16.862709Z","iopub.execute_input":"2026-01-24T19:26:16.862939Z","iopub.status.idle":"2026-01-24T19:26:16.875827Z","shell.execute_reply.started":"2026-01-24T19:26:16.862909Z","shell.execute_reply":"2026-01-24T19:26:16.875156Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Set the directory path for the complete training image dataset\ntrainset = '/kaggle/input/vesuvius-challenge-surface-detection/train_images'\n\n# Retrieve a list of all file names present in the training images folder\ntrain_images = [f for f in os.listdir(trainset)]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-24T19:26:16.876789Z","iopub.execute_input":"2026-01-24T19:26:16.877033Z","iopub.status.idle":"2026-01-24T19:26:16.926345Z","shell.execute_reply.started":"2026-01-24T19:26:16.877013Z","shell.execute_reply":"2026-01-24T19:26:16.925749Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Define the directory path for the ground-truth ink labels\ntrain_labels = '/kaggle/input/vesuvius-challenge-surface-detection/train_labels'\n\n# List all TIF files containing binary masks for the training samples\nlabels_files = [f for f in os.listdir(train_labels) if f.endswith(('.tif'))]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-24T19:26:16.927242Z","iopub.execute_input":"2026-01-24T19:26:16.927660Z","iopub.status.idle":"2026-01-24T19:26:17.010946Z","shell.execute_reply.started":"2026-01-24T19:26:16.927638Z","shell.execute_reply":"2026-01-24T19:26:17.010077Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Initialize a visualization grid with 6 subplots for side-by-side comparison\nfig, axes = plt.subplots(1, 6, figsize=(30, 10)) \n\n# Iterate through the first 6 label files to display their ink patterns\nfor i, image_file in enumerate(labels_files[:6]):\n    # Construct the full file path for each specific ink label\n    image_path = os.path.join(train_labels, image_file) \n    \n    # Load the image data into memory using the Matplotlib image engine\n    img = mpimg.imread(image_path) \n    \n    # Render the label image on its corresponding subplot in the grid\n    axes[i].imshow(img)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-24T19:26:17.013017Z","iopub.execute_input":"2026-01-24T19:26:17.013261Z","iopub.status.idle":"2026-01-24T19:26:18.266998Z","shell.execute_reply.started":"2026-01-24T19:26:17.013239Z","shell.execute_reply":"2026-01-24T19:26:18.266128Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Create a layout of 6 subplots to visualize the raw 3D X-ray slices\nfig, axes = plt.subplots(1, 6, figsize=(30, 10))\n\n# Loop through the first 6 volumetric scan files for initial inspection\nfor i, image_file in enumerate(train_images[:6]):\n    # Generate the absolute path for each individual TIF image slice\n    image_path = os.path.join(trainset, image_file)\n    \n    # Read the raw image data using the Matplotlib image processing tool\n    img = mpimg.imread(image_path)\n    \n    # Display each slice in the grid to check for data quality and contrast\n    axes[i].imshow(img)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-24T19:26:18.267994Z","iopub.execute_input":"2026-01-24T19:26:18.268231Z","iopub.status.idle":"2026-01-24T19:26:19.470181Z","shell.execute_reply.started":"2026-01-24T19:26:18.268210Z","shell.execute_reply":"2026-01-24T19:26:19.469219Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- Step 1: Directory Setup using Kaggle Paths ---\n# Standard Kaggle input directory for the Vesuvius Challenge\nBASE_PATH = Path('/kaggle/input/vesuvius-challenge-surface-detection')\n\nTRAIN_IMAGES_DIR = BASE_PATH / 'train_images'\nTRAIN_LABELS_DIR = BASE_PATH / 'train_labels' \nTEST_IMAGES_DIR  = BASE_PATH / 'test_labels'\n\n# Fetching all .tif slices and sorting them for sequential depth\nall_files = sorted([f.name for f in TRAIN_IMAGES_DIR.glob('*.tif')])\n\nsplit_idx = int(len(all_files)*0.85)\ntrain_files_list = all_files[:split_idx]\nval_files_list = all_files[split_idx:]\n\n\nprint(f\"kaggle Data Ready: {len(train_files_list)} train slice, {len(val_files_list)} val slices.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-24T19:26:19.471726Z","iopub.execute_input":"2026-01-24T19:26:19.472026Z","iopub.status.idle":"2026-01-24T19:26:19.483050Z","shell.execute_reply.started":"2026-01-24T19:26:19.471994Z","shell.execute_reply":"2026-01-24T19:26:19.482349Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Import functional transformations for precise, manual control over image processing\nimport torchvision.transforms.functional as F","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-24T19:26:19.483959Z","iopub.execute_input":"2026-01-24T19:26:19.484186Z","iopub.status.idle":"2026-01-24T19:26:19.493758Z","shell.execute_reply.started":"2026-01-24T19:26:19.484168Z","shell.execute_reply":"2026-01-24T19:26:19.493262Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-24T19:26:19.494617Z","iopub.execute_input":"2026-01-24T19:26:19.494826Z","iopub.status.idle":"2026-01-24T19:26:23.143369Z","shell.execute_reply.started":"2026-01-24T19:26:19.494797Z","shell.execute_reply":"2026-01-24T19:26:23.142628Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Install the library for high-performance semantic segmentation architectures and encoders\n!pip install segmentation-models-pytorch","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-24T19:26:19.494617Z","iopub.execute_input":"2026-01-24T19:26:19.494826Z","iopub.status.idle":"2026-01-24T19:26:23.143369Z","shell.execute_reply.started":"2026-01-24T19:26:19.494797Z","shell.execute_reply":"2026-01-24T19:26:23.142628Z"},"collapsed":true,"jupyter":{"outputs_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Part 2: Exploratory Data Analysis (EDA) 🔍\nVisualizing the 3D volume to understand the hidden layers.\n\n## 🌊 Surface Mapping:\nanalyze_surface_curves.\n\n## 📡 Z-Axis Profiling: \nIdentifying the \"Active Ink Zone\".\n\n## 🖼️ Image Inspection:\nViewing raw CT scans.","metadata":{}},{"cell_type":"code","source":"# --- Neural Network Components and Training Callbacks ---\nimport torch.nn as nn # Base class for all neural network modules in PyTorch\nfrom pytorch_lightning.callbacks import ModelCheckpoint, EarlyStopping, LearningRateMonitor # Tools to automate saving, stopping, and LR adjustments\n# --- Part 1: Custom Dataset Class for 3D Volumetric Data ---\nclass VesuviusIntegratedDataset(Dataset):\n    def __init__(self , images_dir , labels_dir = None , volume_files = None):\n        \n    #\"Initializes the dataset by setting up paths for 3D image slices and their 2D labels.\"\n    #\"- images_dir: Path to the directory containing CT scan .tif files.\"\n    #\"- labels_dir: Optional path to the binary ink masks.\"\n        self.images_dir = Path(images_dir)\n        self.labels_dir = Path(labels_dir) if labels_dir else None\n        self.volume_files = volume_files if volume_files else sorted([f.name for f in self.images_dir.glob('*.tif')])\n    def __len__(self):\n        # Returns the total number of patches/slices available in the dataset\n        return len(self.volume_files)\n\n    def _smart_load(self, path:Path)  -> np.ndarray:\n        \"Helper to load files regardless of format (.tif or .npy) and convert to float32.\"\n        if path.suffix == '.tif':\n            return tiff.imread(str(path)).astype(np.float32)\n        return np.load(str(path)).astype(np.float32)\n    def __getitem__(self , idx):\n        # 1. Loading the raw volumetric slice\n        filename = self.volume_files[idx]\n        image = self._smart_load(self.images_dir / filename)\n        # 2. Loading the corresponding ground-truth mask (if available)\n        if self.labels_dir and (self.labels_dir / filename).exists():\n            mask = self._smart_load(self.labels_dir / filename)\n        else:\n            # Create a blank mask for inference when labels are missing\n            mask = np.zeros((image.shape[-2], image.shape[-1]), dtype=np.float32)\n        \n        # 3. Converting NumPy arrays to PyTorch Tensors\n        image_t  = torch.from_numpy(image).float()\n        mask_t = torch.from_numpy(mask).float()\n\n        # 4. Dimensionality Handling & Channel Selection\n        # We limit the input to 8 slices to capture sufficient vertical depth while saving memory.\n        num_slices = 8\n        if image_t.ndim == 3:\n            image_t = image_t[:num_slices, :, :]\n            # Target alignment: Extracting a single representative 2D slice for the mask\n            if mask_t.ndim == 3:\n                mask_t = mask_t[0, :, :]\n\n        else:\n                \n            image_t = image_t.unsqueeze(0).repeat(num_slices, 1, 1)\n                \n\n            # 5. Image Pre-processing: Standardization & Normalization\n            # Standardizing resolution to 256x256 for consistent model input size\n        size = [256 , 256]\n        image_t = F.resize(image_t , size)\n        mask_t = F.resize(mask_t.unsqueeze(0) ,size).squeeze(0)\n\n        # Scaling pixel values to [0, 1] range to improve convergence and feature detection.\n        if image_t.max() > 1.0:\n            image_t /= 255.0\n\n        return image_t , mask_t","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-24T19:26:23.144649Z","iopub.execute_input":"2026-01-24T19:26:23.144951Z","iopub.status.idle":"2026-01-24T19:26:23.155345Z","shell.execute_reply.started":"2026-01-24T19:26:23.144914Z","shell.execute_reply":"2026-01-24T19:26:23.154615Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- Part 2: LightningDataModule for Pipeline Automation ---\nclass VesuviusDataModule(pl.LightningDataModule):\n    def __init__(self, train_images, train_labels, test_images, val_images=None, val_labels=None, batch_size=4, num_workers=0):\n        super().__init__()\n        self.train_images, self.train_labels = train_images, train_labels\n        self.val_images, self.val_labels = val_images, val_labels\n        self.test_images = test_images\n        self.batch_size, self.num_workers = batch_size, num_workers\n\n    def setup(self, stage=None):\n        \"\"\"Setting up datasets based on the current execution stage.\"\"\"\n        if stage == 'fit' or stage is None:\n            self.train_ds = VesuviusIntegratedDataset(self.train_images, self.train_labels)\n            if self.val_images and self.val_labels:\n                self.val_ds = VesuviusIntegratedDataset(self.val_images, self.val_labels)\n\n        if stage == 'test' or stage is None:\n            # Passing arguments positionally to prevent TypeErrors\n            self.test_ds = VesuviusIntegratedDataset(self.test_images, None)\n\n    def train_dataloader(self):\n        return DataLoader(self.train_ds, batch_size=self.batch_size, shuffle=True, num_workers=self.num_workers, pin_memory=True)\n\n    def val_dataloader(self):\n        if self.val_ds:\n            return DataLoader(self.val_ds, batch_size=self.batch_size, shuffle=False, num_workers=self.num_workers, pin_memory=True)\n        return None\n\n    def test_dataloader(self):\n        # Sequential loading (shuffle=False) is vital for spatial reconstruction\n        return DataLoader(self.test_ds, batch_size=self.batch_size, shuffle=False, num_workers=0, pin_memory=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-24T19:26:23.156214Z","iopub.execute_input":"2026-01-24T19:26:23.156410Z","iopub.status.idle":"2026-01-24T19:26:23.177412Z","shell.execute_reply.started":"2026-01-24T19:26:23.156394Z","shell.execute_reply":"2026-01-24T19:26:23.176606Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Initialize the DataModule with pre-defined paths and hardware settings\ndata_module = VesuviusDataModule(\n    train_images=TRAIN_IMAGES_DIR,  # Path to the directory containing .tif training slices\n    train_labels=TRAIN_LABELS_DIR,  # Path to the binary ink mask (.png or .tif)\n    test_images=TEST_IMAGES_DIR,    # Path to the test segment directory\n    val_images=TRAIN_IMAGES_DIR,    # Reusing the training folder for our validation split\n    val_labels=TRAIN_LABELS_DIR,    # Reusing the training labels for our validation split\n    batch_size=4,                   # Number of samples per training step (Optimal for VRAM)\n    num_workers=2                   # Set to 2 or 4 in Kaggle for faster data fetching\n)\n# Execute the setup phase to build the internal dataset instances\ndata_module.setup()          \n\n# Extract a single batch from the training loader to verify data integrity\nimages, masks = next(iter(data_module.train_dataloader())) \n\n# Confirm the final tensor dimensions: Expected [Batch, Slices, Height, Width]\nprint(f\"Final Image Shape: {images.shape}\") \n\n# Confirm the mask dimensions: Expected [Batch, Height, Width]\nprint(f\"Final Mask Shape: {masks.shape}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-24T19:26:23.178587Z","iopub.execute_input":"2026-01-24T19:26:23.178816Z","iopub.status.idle":"2026-01-24T19:26:29.950663Z","shell.execute_reply.started":"2026-01-24T19:26:23.178795Z","shell.execute_reply":"2026-01-24T19:26:29.949853Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Initialize the training data stream for the model execution phase\ntrain_loader = data_module.train_dataloader()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-24T19:26:29.952090Z","iopub.execute_input":"2026-01-24T19:26:29.952454Z","iopub.status.idle":"2026-01-24T19:26:29.957398Z","shell.execute_reply.started":"2026-01-24T19:26:29.952425Z","shell.execute_reply":"2026-01-24T19:26:29.956718Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Function to perform statistical analysis on the 3D surface topography\ndef analyze_surface_curves(volume_tensor: torch.Tensor):\n    # Convert the GPU tensor to a NumPy array for visualization and analysis\n    data = volume_tensor.squeeze(0).cpu().numpy()\n    depth, hight, width = data.shape\n\n    # Calculate the average intensity across all layers to reveal hidden patterns\n    mean_projection = np.mean(data, axis=0)\n\n    # Measure signal activity along the Z-axis to locate the papyrus depth\n    z_activity = np.mean(data, axis=(1, 2))\n\n    # Initialize a wide figure for multi-panel data visualization\n    fig = plt.figure(figsize=(20, 10))\n\n    # Panel 1: Display the integrated surface map showing curve intensity\n    plt.subplot(1, 3, 1)\n    plt.imshow(mean_projection)\n    plt.title('Surface Topography Map (Curves)')\n    plt.colorbar(label='Signal Intensity')\n\n    # Panel 2: Plot the Z-axis signal to identify the exact depth of the scroll\n    plt.subplot(1, 3, 2)\n    plt.plot(z_activity, range(depth), 'r-o')\n    plt.title('Z-Axis Curve Detection (Where is the Papyrus?)')\n    plt.ylabel('Layer Index (0-36)')\n    plt.xlabel('Average Signal')\n\n    # Panel 3: Show a raw horizontal cross-section at the center of the volume\n    plt.subplot(1, 3, 3)\n    middle_layer = depth // 2\n    plt.imshow(data[middle_layer], cmap='gray')\n    plt.title(f'Cross-section at Layer {middle_layer}')\n\n    # Finalize the layout and render the analysis plots\n    plt.tight_layout()\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-24T19:26:29.958583Z","iopub.execute_input":"2026-01-24T19:26:29.958870Z","iopub.status.idle":"2026-01-24T19:26:30.231554Z","shell.execute_reply.started":"2026-01-24T19:26:29.958848Z","shell.execute_reply":"2026-01-24T19:26:30.230955Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Fetch a single batch of images from the training pipeline for inspection\nimg_batch, _ = next(iter(train_loader))\n\n# Execute the deep topography analysis on the first sample of the batch\nanalyze_surface_curves(img_batch[0])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-24T19:26:30.232576Z","iopub.execute_input":"2026-01-24T19:26:30.232824Z","iopub.status.idle":"2026-01-24T19:26:38.111036Z","shell.execute_reply.started":"2026-01-24T19:26:30.232803Z","shell.execute_reply":"2026-01-24T19:26:38.110066Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Pull a second, different batch to ensure data consistency across the set\nimg_batch2, _ = next(iter(train_loader))\n\n# Run the topography analysis again to verify the stability of the 3D signal\nanalyze_surface_curves(img_batch2[0])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-24T19:26:38.112168Z","iopub.execute_input":"2026-01-24T19:26:38.112420Z","iopub.status.idle":"2026-01-24T19:26:45.664723Z","shell.execute_reply.started":"2026-01-24T19:26:38.112397Z","shell.execute_reply":"2026-01-24T19:26:45.663844Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Extract a third unique batch to finalize the data quality assurance process\nimg_batch3, _ = next(iter(train_loader))\n\n# Perform a final topography analysis to confirm the 3D signal remains robust\nanalyze_surface_curves(img_batch3[0])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-24T19:26:45.666337Z","iopub.execute_input":"2026-01-24T19:26:45.666716Z","iopub.status.idle":"2026-01-24T19:26:53.036940Z","shell.execute_reply.started":"2026-01-24T19:26:45.666688Z","shell.execute_reply":"2026-01-24T19:26:53.036129Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Part 4: Model Architecture & Training 🧠🔥\nDesigning the brain and starting the learning process.\n\n## 🏗️ Architecture:\nU-Net + ResNet34 (Transfer Learning).\n\n## 📉 Loss Function:\nBCEWithLogitsLoss.\n\n## 🛡️ Callbacks: \nEarlyStopping & ModelCheckpoint for safety.\n\n## 📈 Metrics:\nMonitoring Pixel-level Accuracy.","metadata":{}},{"cell_type":"code","source":"# Import high-level semantic segmentation library for state-of-the-art model architectures\nimport segmentation_models_pytorch as smp","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-24T19:26:53.038578Z","iopub.execute_input":"2026-01-24T19:26:53.039399Z","iopub.status.idle":"2026-01-24T19:26:57.600533Z","shell.execute_reply.started":"2026-01-24T19:26:53.039368Z","shell.execute_reply":"2026-01-24T19:26:57.599961Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- Part 1: Architecture Definition ---\nclass VesuviusVolumeModel(pl.LightningModule):\n    def __init__(self, in_channels=8, lr=1e-4):\n    \n        \"\"\"\n        Constructor to initialize the model layers and parameters.\n        - in_channels: Number of CT scan layers (depth).\n        - lr: Learning rate for the optimizer.\n        \"\"\"\n        super().__init__()\n        # Save hyperparameters to allow loading and logging later\n        self.save_hyperparameters()\n        \n        # Load a pre-trained Unet with a ResNet34 backbone for high-performance segmentation\n        self.model = smp.Unet(encoder_name='resnet34', in_channels=in_channels, classes=1)\n        \n        \n        # BCEWithLogitsLoss combines Sigmoid + Binary Cross Entropy for better numerical stability\n        self.loss_fn = nn.BCEWithLogitsLoss()\n\n    def forward(self, x):\n        \"\"\"Standard forward pass through the U-Net architecture.\"\"\"\n        return self.model(x)\n\n    # --- Part 2: Training Logic ---\n    def training_step(self, batch, batch_idx):\n        \"\"\"Operations performed during each training iteration.\"\"\"\n        x , y = batch\n        # Ensure labels are binary (0 or 1) for the loss function\n        y = (y > 0.5).float()\n        \n        # Predict mask and remove the channel dimension to match 'y' shape\n        y_hat = self(x).squeeze(1)\n        loss = self.loss_fn(y_hat , y)\n\n        # Convert raw output (logits) to probabilities and then to binary predictions\n        preds = (torch.sigmoid(y_hat) > 0.5).float()        \n        # Calculate Pixel-level Accuracy (Metric to monitor training quality)\n        acc = (preds == y).float().mean()\n\n        # Log metrics for real-time monitoring in the progress bar\n        self.log('train_loss' , loss , prog_bar = True , on_step = True , on_epoch  = True)\n        self.log('train_acc' , acc , prog_bar = True , on_step = True , on_epoch = True)\n    # --- Part 3: Testing Logic ---\n    def test_step(self, batch, batch_idx):\n        \"\"\"Final evaluation on unseen data to assess real-world performance.\"\"\"\n        x , y = batch\n        y = (y > 0.5).float()\n        y_hat = self(x).squeeze(1)\n        loss = self.loss_fn(y_hat , y)\n        # Final predictions for accuracy reporting\n        preds = (torch.sigmoid(y_hat) > 0.5).float()\n        \n        acc = (preds == y).float().mean() # Comparing predictions to actual labels\n        \n\n        # Log test results for the final summary report\n        self.log('test_loss' , loss , prog_bar = True)\n        self.log('test_acc' , acc  , prog_bar = True)\n        return loss\n\n\n    def validation_step(self , batch , batch_idx):\n        \"\"\"Operations performed during each validation iteration.\"\"\"\n        x , y = batch\n        y  = (y >0.5).float()\n\n        # Forward pass and squeeze to match 'y' shape\n        y_hat = self(x).squeeze(1)\n        val_loss = self.loss_fn(y_hat , y)\n\n        # Log val_loss - this is the specific metric the EarlyStopping looks for\n        self.log('val_loss'  , val_loss , prog_bar = True , on_epoch  = True)\n        return val_loss\n        \n\n    # --- Part 4: Optimizer Configuration ---\n    def configure_optimizers(self):\n        \"\"\" Defining the optimizer (AdamW) to update model weights.\"\"\"\n        optimizer = torch.optim.AdamW(self.parameters() , lr=self.hparams.lr)\n        scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(\n            optimizer,mode='min', factor=0.5,patience =2\n       )\n        return {\n                \"optimizer\": optimizer,\n                \"lr_scheduler\": {\"scheduler\": scheduler, \"monitor\": \"val_loss\"}\n            }","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-24T19:26:57.601337Z","iopub.execute_input":"2026-01-24T19:26:57.601554Z","iopub.status.idle":"2026-01-24T19:26:57.612958Z","shell.execute_reply.started":"2026-01-24T19:26:57.601534Z","shell.execute_reply":"2026-01-24T19:26:57.612280Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- Training Automation and Performance Logging ---\nfrom pytorch_lightning.callbacks import EarlyStopping, ModelCheckpoint\nfrom pytorch_lightning.loggers import CSVLogger\n\n# --- Part 5: Experiment Logging and Automation Callbacks ---\n\n# Log metrics (Loss/Accuracy) to a CSV file for offline performance analysis\ncsv_logger = CSVLogger('lightning_logs', name='vesuvius_experiment')\n\n# Configure ModelCheckpoint to preserve the state of the best model\ncheckpoint_callback = ModelCheckpoint(\n    monitor='val_loss',        # Primary metric to track\n    dirpath='checkpoints/',    # Local directory to store weight files (.ckpt)\n    filename='vesuvius-best',  # Identifier for the best performing weights\n    save_top_k=1,              # Overwrite to keep only the absolute best version\n    mode='min'                 # Optimization goal: minimize the validation loss\n)\n\n# Configure EarlyStopping to prevent overfitting and save compute time\nearly_stop_callback = EarlyStopping(\n    monitor='val_loss',        # Watch for plateaus in validation performance\n    patience=3,                # Number of epochs to wait for improvement before halting\n    mode='min'                 # Stop when the loss stops decreasing\n)\n# --- Part 6: Execution Setup ---\n\n# Initialize the Vesuvius model with the specified 8-channel depth\nmodel = VesuviusVolumeModel(in_channels=8)\n\n# Set up the high-level Trainer to manage the training loop\ntrainer = pl.Trainer(\n    max_epochs=10,             # Total training iterations\n    accelerator='auto',        # Automatically detect and use available GPU (CUDA)\n    devices=1,                 # Utilize a single GPU unit\n    logger=csv_logger,         # Record metrics for later visualization\n    callbacks=[checkpoint_callback, early_stop_callback], # Apply the automation tools\n    precision='16-mixed',      # Enable Mixed Precision (FP16) to double the training speed and save VRAM\n    enable_progress_bar=True   # Provide visual feedback on training status\n)\n\n# Initiate the training and validation cycles using the DataModule pipeline\ntrainer.fit(model, datamodule=data_module)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-24T19:26:57.616332Z","iopub.execute_input":"2026-01-24T19:26:57.616587Z","iopub.status.idle":"2026-01-24T19:55:47.393193Z","shell.execute_reply.started":"2026-01-24T19:26:57.616567Z","shell.execute_reply":"2026-01-24T19:55:47.392383Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Part 5: \nResults & Ink Reconstruction 🎨🖋️\nFinal evaluation and revealing the ancient text.\n\n## 📊 Learning Curves: Plotting Loss vs. Accuracy.\n\n## 🔮 Inference:\nPredicting ink on unseen data.\n\n## 💾 Model Export: \nSaving weights (.pth) and final results.","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nimport matplotlib.pyplot as plt\n\n# --- Part 1: Data Preparation ---\n# Loading the training logs to extract step-by-step performance metrics\nmetrics_df = pd.read_csv('/kaggle/working/lightning_logs/vesuvius_experiment/version_0/metrics.csv')\n\n\n# --- Part 2: Visualizing Training Loss ---\n# Creating a separate plot to monitor the error reduction (Convergence)\n\nplt.figure(figsize = (10 , 5))\nplt.plot(metrics_df['step'] , metrics_df['train_loss_step'] , color = 'red' , label = 'Training Loss')\nplt.title('Vesuvius Project: Training Loss Over Steps')\nplt.xlabel('Steps')\nplt.ylabel('Loss Value')\nplt.grid(True , linestyle = '--' , alpha = 0.6)\n\nplt.legend()\nplt.show()\n\n# --- Part 3: Visualizing Training Accuracy ---\n# Creating a dedicated plot for ink detection precision (Performance)\nplt.figure(figsize = (10 , 5))\nplt.plot(metrics_df['step']  , metrics_df['train_acc_step'] , color = 'green' , label = 'Training Accuracy')\nplt.title('Vesuvius Project: Ink Detection Accuracy Over Steps')\nplt.xlabel('Steps')\nplt.ylabel('Accuracy (%)')\nplt.grid(True  , linestyle = '--' , alpha  = 0.6)\nplt.legend()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-24T19:55:47.394460Z","iopub.execute_input":"2026-01-24T19:55:47.394722Z","iopub.status.idle":"2026-01-24T19:55:47.746540Z","shell.execute_reply.started":"2026-01-24T19:55:47.394696Z","shell.execute_reply":"2026-01-24T19:55:47.745904Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Execute the testing phase to evaluate model performance on unseen data\ntrainer.test(model, datamodule=data_module)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-24T19:55:47.747477Z","iopub.execute_input":"2026-01-24T19:55:47.747715Z","iopub.status.idle":"2026-01-24T19:55:47.833680Z","shell.execute_reply.started":"2026-01-24T19:55:47.747695Z","shell.execute_reply":"2026-01-24T19:55:47.832954Z"},"collapsed":true,"jupyter":{"outputs_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def predict_and_plot_and_save(model, data_module):\n    # Set the model to evaluation mode to disable dropout and batch norm updates\n    model.eval()\n\n    # Access the test data stream and pull the first batch for visualization\n    test_loader = data_module.train_dataloader() # Using train_loader for labels, or val_dataloader\n    batch = next(iter(test_loader))\n    images, masks = batch\n    # Perform inference without calculating gradients to save memory and time\n    with torch.no_grad():\n        logits = model(images.to(model.device))\n        # Convert raw network outputs into probability maps using sigmoid\n        preds_probs = torch.sigmoid(logits).squeeze(1).cpu().numpy()\n        # Create a hard binary mask where 1 represents detected ink\n        preds_binary = (preds_probs > 0.5).astype(np.uint8)\n\n    # Initialize a large plotting area to compare input, ground truth, and prediction\n    fig = plt.figure(figsize=(20, 10))\n    \n    # Panel 1: Display the original X-ray slice with the true ink label overlaid\n    plt.subplot(1, 3, 1)\n    middle_idx = images.shape[1] // 2\n    plt.imshow(images[0, middle_idx].cpu(), cmap='gray')\n    plt.imshow(masks[0].cpu(), cmap='jet', alpha=0.5)\n    plt.title('Original Slice + Ground Truth')\n\n    # Panel 2: Show the raw probability map generated by the model\n    plt.subplot(1, 3, 2)\n    plt.imshow(preds_probs[0], cmap='magma')\n    plt.title('Model Prediction (Probability)')\n    plt.colorbar(label='Confidence')\n\n\n    # Panel 3: Show the final binary detection (Ink vs Paper)\n    plt.subplot(1, 3, 3)\n    plt.imshow(preds_binary[0], cmap='gray')\n    plt.title('Final Binary Detection')\n\n    plt.tight_layout()\n    plt.show()\n\n\n    # Save the numerical results to disk for later submission or analysis\n    np.save('test_predictions.npy', preds_binary)\n\n\n# Execute the visualization function to inspect your model's intelligence\npredict_and_plot_and_save(model, data_module)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-24T19:55:47.834726Z","iopub.execute_input":"2026-01-24T19:55:47.835119Z","iopub.status.idle":"2026-01-24T19:55:53.763010Z","shell.execute_reply.started":"2026-01-24T19:55:47.835090Z","shell.execute_reply":"2026-01-24T19:55:53.762100Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Save only the learned weights (parameters) - Best for production and flexibility\ntorch.save(model.state_dict(), 'vesuvius_model_weights.pth')\n\n# Save the entire model object (including architecture and hyperparameters)\ntorch.save(model, 'complete_vesuvius_project.pt')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-24T19:55:53.764421Z","iopub.execute_input":"2026-01-24T19:55:53.764701Z","iopub.status.idle":"2026-01-24T19:55:53.972291Z","shell.execute_reply.started":"2026-01-24T19:55:53.764674Z","shell.execute_reply":"2026-01-24T19:55:53.971288Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}