{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"}},"cells":[{"cell_type":"markdown","source":"# Competition: vesuvius-challenge-ink-detection\n\n**Generated by Alexandria Research Assistant**\n\n**Dataset:** vesuvius-challenge-ink-detection\n\n**Task:** competition - kaggle-competition\n\n---\n\n⚠️ **Note:** This notebook contains Alexandria markers (lines starting with `# ⚠️ ALEXANDRIA MARKER`) at the top of each code cell. These markers enable the 'Sync from Kaggle' feature to track cell outputs. Please do not delete them.","metadata":{}},{"cell_type":"markdown","source":"## Setup & Imports\n\nImport required libraries","metadata":{}},{"cell_type":"code","source":"# ⚠️ ALEXANDRIA MARKER - DO NOT DELETE (used for syncing outputs from Kaggle)\nprint(\"===ALEXANDRIA_CELL_1_START===\")\n\n# Setup & Imports\n# Import required libraries and configure environment\n\nimport os\nimport random\nimport numpy as np\nimport pandas as pd\n\n# Try to import torch, else fallback to tensorflow\ntry:\n    import torch\n    torch_available = True\n    print(\"PyTorch imported successfully.\")\nexcept ImportError:\n    torch_available = False\n    print(\"PyTorch not found, trying TensorFlow...\")\n    try:\n        import tensorflow as tf\n        tf_available = True\n        print(\"TensorFlow imported successfully.\")\n    except ImportError:\n        tf_available = False\n        print(\"TensorFlow not found. Please install either PyTorch or TensorFlow.\")\n\n# Set random seeds for reproducibility\nSEED = 42\nrandom.seed(SEED)\nnp.random.seed(SEED)\nif torch_available:\n    torch.manual_seed(SEED)\n    torch.cuda.manual_seed_all(SEED)\n    torch.backends.cudnn.deterministic = True\n    torch.backends.cudnn.benchmark = False\n    print(\"Random seeds set for PyTorch.\")\nelif 'tf_available' in locals() and tf_available:\n    tf.random.set_seed(SEED)\n    print(\"Random seeds set for TensorFlow.\")\n\n# Configure device (GPU/CPU)\nif torch_available:\n    DEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n    print(f\"Using device: {DEVICE}\")\nelif 'tf_available' in locals() and tf_available:\n    DEVICE = \"/GPU:0\" if tf.config.list_physical_devices('GPU') else \"/CPU:0\"\n    print(f\"Using device: {DEVICE}\")\nelse:\n    DEVICE = \"cpu\"\n    print(\"No deep learning framework found. Defaulting to CPU.\")\n\n# Define dataset path\nDATA_PATH = \"/kaggle/input/vesuvius-challenge-ink-detection\"\nprint(f\"Dataset path set to: {DATA_PATH}\")\n\n# List files in dataset directory for verification\ntry:\n    for root, dirs, files in os.walk(DATA_PATH):\n        for name in files:\n            print(os.path.join(root, name))\n    print(\"Dataset files listed successfully.\")\nexcept Exception as e:\n    print(f\"Error listing dataset files: {e}\")","outputs":[],"metadata":{},"execution_count":null},{"cell_type":"markdown","source":"## Load Dataset\n\nLoad vesuvius-challenge-ink-detection files","metadata":{}},{"cell_type":"code","source":"# ⚠️ ALEXANDRIA MARKER - DO NOT DELETE (used for syncing outputs from Kaggle)\nprint(\"===ALEXANDRIA_CELL_2_START===\")\n\n# ===ALEXANDRIA_CELL_2_START===\n# Title: Load Dataset\n# Description: Load vesuvius-challenge-ink-detection files\n\nimport os\nimport pandas as pd\nfrom PIL import Image\n\n# Define dataset path (from prior cell)\n# DATA_PATH is already set\n\nprint(\"\\n--- Loading Dataset ---\")\n\n# Inspect top-level files\ntry:\n    top_files = os.listdir(DATA_PATH)\n    print(f\"Top-level files in {DATA_PATH}: {top_files}\")\nexcept Exception as e:\n    print(f\"Error listing top-level files: {e}\")\n\n# Load sample_submission.csv\nsample_submission_path = os.path.join(DATA_PATH, \"sample_submission.csv\")\ntry:\n    sample_submission_df = pd.read_csv(sample_submission_path)\n    print(f\"Loaded sample_submission.csv. Shape: {sample_submission_df.shape}\")\n    print(sample_submission_df.head())\nexcept Exception as e:\n    print(f\"Error loading sample_submission.csv: {e}\")\n\n# Inspect test/a directory\ntest_a_path = os.path.join(DATA_PATH, \"test\", \"a\")\ntry:\n    test_a_files = os.listdir(test_a_path)\n    print(f\"Files in test/a/: {test_a_files}\")\nexcept Exception as e:\n    print(f\"Error listing test/a/: {e}\")\n\n# Load mask.png\nmask_path = os.path.join(test_a_path, \"mask.png\")\ntry:\n    mask_img = Image.open(mask_path)\n    print(f\"Loaded mask.png. Size: {mask_img.size}, Mode: {mask_img.mode}\")\nexcept Exception as e:\n    print(f\"Error loading mask.png: {e}\")\n\n# Inspect surface_volume directory\nsurface_volume_path = os.path.join(test_a_path, \"surface_volume\")\ntry:\n    surface_volume_files = sorted([f for f in os.listdir(surface_volume_path) if f.endswith('.tif')])\n    print(f\"TIFF files in test/a/surface_volume/: {surface_volume_files}\")\nexcept Exception as e:\n    print(f\"Error listing surface_volume files: {e}\")\n\n# Load and inspect one TIFF file (e.g., 00.tif)\ntiff_sample_path = os.path.join(surface_volume_path, \"00.tif\")\ntry:\n    tiff_img = Image.open(tiff_sample_path)\n    print(f\"Loaded 00.tif. Size: {tiff_img.size}, Mode: {tiff_img.mode}\")\nexcept Exception as e:\n    print(f\"Error loading 00.tif: {e}\")\n\nprint(\"--- Dataset loading and inspection complete ---\")","outputs":[],"metadata":{},"execution_count":null},{"cell_type":"markdown","source":"## EDA\n\nExploratory data analysis","metadata":{}},{"cell_type":"code","source":"# ⚠️ ALEXANDRIA MARKER - DO NOT DELETE (used for syncing outputs from Kaggle)\nprint(\"===ALEXANDRIA_CELL_3_START===\")\n\n# ===ALEXANDRIA_CELL_3_START===\n# Title: EDA\n# Description: Exploratory data analysis\n\nimport os\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom PIL import Image\n\nprint(\"\\n--- Exploratory Data Analysis ---\")\n\n# 1. Check data shape and structure for sample_submission.csv\ntry:\n    print(\"\\nSample Submission CSV:\")\n    print(f\"Shape: {sample_submission_df.shape}\")\n    print(\"Columns:\", sample_submission_df.columns.tolist())\n    print(\"Info:\")\n    sample_submission_df.info()\n    print(\"\\nSummary statistics:\")\n    print(sample_submission_df.describe(include='all').T)\n    print(\"\\nMissing values per column:\")\n    print(sample_submission_df.isnull().sum())\n    print(\"\\nUnique values per column:\")\n    print(sample_submission_df.nunique())\nexcept Exception as e:\n    print(f\"Error analyzing sample_submission.csv: {e}\")\n\n# 2. Visualize distributions for sample_submission.csv (if numeric columns exist)\nnumeric_cols = sample_submission_df.select_dtypes(include=np.number).columns.tolist()\nif numeric_cols:\n    try:\n        plt.figure(figsize=(12, 6))\n        sample_submission_df[numeric_cols].hist(bins=30, figsize=(12, 6), layout=(1, len(numeric_cols)))\n        plt.suptitle(\"Numeric Column Distributions - sample_submission.csv\")\n        plt.tight_layout()\n        plt.show()\n    except Exception as e:\n        print(f\"Error visualizing numeric distributions: {e}\")\nelse:\n    print(\"No numeric columns found in sample_submission.csv for distribution plots.\")\n\n# 3. Analyze mask.png\ntry:\n    print(\"\\nMask Image Analysis:\")\n    print(f\"Size: {mask_img.size}, Mode: {mask_img.mode}\")\n    mask_arr = np.array(mask_img)\n    print(f\"Mask array shape: {mask_arr.shape}, dtype: {mask_arr.dtype}\")\n    print(f\"Mask unique values: {np.unique(mask_arr)}\")\n    plt.figure(figsize=(6, 6))\n    plt.imshow(mask_arr, cmap='gray')\n    plt.title(\"Mask Image Visualization\")\n    plt.axis('off')\n    plt.show()\nexcept Exception as e:\n    print(f\"Error analyzing mask.png: {e}\")\n\n# 4. Analyze TIFF stack in surface_volume\ntry:\n    print(\"\\nSurface Volume TIFF Stack Analysis:\")\n    tiff_stats = []\n    for fname in surface_volume_files:\n        fpath = os.path.join(surface_volume_path, fname)\n        img = Image.open(fpath)\n        arr = np.array(img)\n        tiff_stats.append({\n            \"file\": fname,\n            \"shape\": arr.shape,\n            \"dtype\": arr.dtype,\n            \"min\": np.min(arr),\n            \"max\": np.max(arr),\n            \"mean\": np.mean(arr),\n            \"std\": np.std(arr)\n        })\n    tiff_stats_df = pd.DataFrame(tiff_stats)\n    print(tiff_stats_df)\n    # Visualize distribution for first TIFF file\n    first_tiff_arr = np.array(Image.open(os.path.join(surface_volume_path, surface_volume_files)))\n    plt.figure(figsize=(8, 6))\n    sns.histplot(first_tiff_arr.flatten(), bins=50, kde=True)\n    plt.title(f\"Pixel Value Distribution: {surface_volume_files}\")\n    plt.xlabel(\"Pixel Value\")\n    plt.ylabel(\"Frequency\")\n    plt.show()\nexcept Exception as e:\n    print(f\"Error analyzing surface_volume TIFF files: {e}\")\n\n# 5. Identify patterns: Correlation between TIFF slices (mean pixel value trend)\ntry:\n    print(\"\\nMean Pixel Value Trend Across TIFF Slices:\")\n    plt.figure(figsize=(10, 5))\n    plt.plot(tiff_stats_df[\"file\"], tiff_stats_df[\"mean\"], marker='o')\n    plt.xticks(rotation=90)\n    plt.xlabel(\"TIFF Slice\")\n    plt.ylabel(\"Mean Pixel Value\")\n    plt.title(\"Mean Pixel Value Across Surface Volume Slices\")\n    plt.tight_layout()\n    plt.show()\nexcept Exception as e:\n    print(f\"Error plotting mean pixel value trend: {e}\")\n\nprint(\"--- EDA complete ---\")","outputs":[],"metadata":{},"execution_count":null},{"cell_type":"markdown","source":"## Preprocessing\n\nClean and prepare data","metadata":{}},{"cell_type":"code","source":"# ⚠️ ALEXANDRIA MARKER - DO NOT DELETE (used for syncing outputs from Kaggle)\nprint(\"===ALEXANDRIA_CELL_4_START===\")\n\n# ⚠️ ALEXANDRIA MARKER - DO NOT DELETE (used for syncing outputs from Kaggle)\nprint(\"===ALEXANDRIA_CELL_4_START===\")\n\n# Title: Preprocessing\n# Description: Clean and prepare data (handle missing values, normalize/scale, train/val split)\n\nimport os\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.preprocessing import MinMaxScaler\n\nprint(\"\\n--- Preprocessing: Clean and Prepare Data ---\")\n\n# 1. Handle missing values in sample_submission.csv\ntry:\n    print(\"Checking missing values in sample_submission.csv...\")\n    missing_vals = sample_submission_df.isnull().sum()\n    print(\"Missing values per column:\\n\", missing_vals)\n    if missing_vals.any():\n        print(\"Imputing missing values with column mean...\")\n        sample_submission_df.fillna(sample_submission_df.mean(numeric_only=True), inplace=True)\n        print(\"Missing values after imputation:\\n\", sample_submission_df.isnull().sum())\n    else:\n        print(\"No missing values found in sample_submission.csv.\")\nexcept Exception as e:\n    print(f\"Error handling missing values in sample_submission.csv: {e}\")\n\n# 2. Normalize/scale TIFF stack (surface_volume)\ntry:\n    print(\"\\nNormalizing TIFF stack in surface_volume...\")\n    # Load all TIFF slices into a 3D numpy array\n    tiff_arrays = []\n    for fname in surface_volume_files:\n        fpath = os.path.join(surface_volume_path, fname)\n        img = Image.open(fpath)\n        arr = np.array(img, dtype=np.float32)\n        tiff_arrays.append(arr)\n    tiff_stack = np.stack(tiff_arrays, axis=0)  # Shape: (num_slices, H, W)\n    print(f\"TIFF stack shape: {tiff_stack.shape}, dtype: {tiff_stack.dtype}\")\n\n    # Min-Max scaling to [0, 1] across the entire stack\n    tiff_min = np.min(tiff_stack)\n    tiff_max = np.max(tiff_stack)\n    print(f\"TIFF stack min: {tiff_min}, max: {tiff_max}\")\n    if tiff_max > tiff_min:\n        tiff_stack_norm = (tiff_stack - tiff_min) / (tiff_max - tiff_min)\n        print(\"TIFF stack normalized to [0, 1].\")\n    else:\n        tiff_stack_norm = tiff_stack\n        print(\"TIFF stack has constant value; skipping normalization.\")\nexcept Exception as e:\n    print(f\"Error normalizing TIFF stack: {e}\")\n\n# 3. Prepare mask and flatten data for splitting\ntry:\n    print(\"\\nPreparing mask and flattening data for train/val split...\")\n    mask_arr = np.array(mask_img)\n    # Only consider pixels where mask is nonzero (valid region)\n    valid_idx = np.where(mask_arr.flatten() > 0)\n    print(f\"Number of valid pixels in mask: {len(valid_idx)}\")\n\n    # Flatten TIFF stack to (num_pixels, num_slices)\n    H, W = mask_arr.shape\n    num_slices = tiff_stack_norm.shape\n    tiff_flat = tiff_stack_norm.reshape(num_slices, -1).T  # Shape: (H*W, num_slices)\n    tiff_flat_valid = tiff_flat[valid_idx]\n\n    # Prepare dummy targets (since ground truth is not provided in test set)\n    # For demonstration, use zeros\n    y_dummy = np.zeros(len(valid_idx), dtype=np.uint8)\nexcept Exception as e:\n    print(f\"Error preparing mask and flattening data: {e}\")\n\n# 4. Train/validation split\ntry:\n    print(\"\\nSplitting data into train and validation sets...\")\n    X_train, X_val, y_train, y_val = train_test_split(\n        tiff_flat_valid, y_dummy, test_size=0.2, random_state=SEED, stratify=y_dummy\n    )\n    print(f\"Train shape: {X_train.shape}, Validation shape: {X_val.shape}\")\nexcept Exception as e:\n    print(f\"Error splitting data: {e}\")\n\nprint(\"--- Preprocessing complete ---\")","outputs":[],"metadata":{},"execution_count":null},{"cell_type":"markdown","source":"## Feature Engineering\n\nCreate task-specific features","metadata":{}},{"cell_type":"code","source":"# ⚠️ ALEXANDRIA MARKER - DO NOT DELETE (used for syncing outputs from Kaggle)\nprint(\"===ALEXANDRIA_CELL_5_START===\")\n\n# ⚠️ ALEXANDRIA MARKER - DO NOT DELETE (used for syncing outputs from Kaggle)\nprint(\"===ALEXANDRIA_CELL_5_START===\")\n\n# Title: Feature Engineering\n# Description: Create task-specific features\n\nimport numpy as np\nimport pandas as pd\nfrom skimage.feature import canny\nfrom skimage.filters import sobel, gaussian\nfrom skimage.segmentation import slic\nfrom skimage.measure import label, regionprops\nfrom skimage import exposure\nimport warnings\n\nprint(\"\\n--- Feature Engineering: Extracting and Augmenting Features ---\")\n\n# 1. Extract pixel-level features from normalized TIFF stack\ntry:\n    print(\"Extracting pixel-level features...\")\n    # Use tiff_stack_norm, mask_arr, valid_idx from previous cells\n    # Features: original intensities, mean, std, min, max across slices for each pixel\n    pixel_features = {\n        \"mean_intensity\": np.mean(tiff_flat_valid, axis=1),\n        \"std_intensity\": np.std(tiff_flat_valid, axis=1),\n        \"min_intensity\": np.min(tiff_flat_valid, axis=1),\n        \"max_intensity\": np.max(tiff_flat_valid, axis=1),\n    }\n    # Stack features into a DataFrame\n    pixel_features_df = pd.DataFrame(pixel_features)\n    print(f\"Pixel-level features shape: {pixel_features_df.shape}\")\nexcept Exception as e:\n    print(f\"Error extracting pixel-level features: {e}\")\n\n# 2. Augment with edge and texture features from central slice\ntry:\n    print(\"Extracting edge and texture features from central TIFF slice...\")\n    central_idx = tiff_stack_norm.shape // 2\n    central_slice = tiff_stack_norm[central_idx]\n    # Apply mask\n    central_slice_masked = np.zeros_like(central_slice)\n    central_slice_masked[mask_arr > 0] = central_slice[mask_arr > 0]\n    # Edge features: Canny edge detector\n    edges = canny(central_slice_masked, sigma=1)\n    # Texture: Sobel filter (gradient magnitude)\n    sobel_edges = sobel(central_slice_masked)\n    # Local contrast (CLAHE)\n    with warnings.catch_warnings():\n        warnings.simplefilter(\"ignore\")\n        clahe_img = exposure.equalize_adapthist(central_slice_masked, clip_limit=0.03)\n    # Flatten and select valid pixels\n    edge_feat = edges.flatten()[valid_idx]\n    sobel_feat = sobel_edges.flatten()[valid_idx]\n    clahe_feat = clahe_img.flatten()[valid_idx]\n    pixel_features_df[\"canny_edge\"] = edge_feat.astype(np.uint8)\n    pixel_features_df[\"sobel_grad\"] = sobel_feat\n    pixel_features_df[\"clahe\"] = clahe_feat\n    print(\"Edge and texture features added.\")\nexcept Exception as e:\n    print(f\"Error extracting edge/texture features: {e}\")\n\n# 3. Augment with superpixel (SLIC) region features\ntry:\n    print(\"Extracting superpixel (SLIC) region features...\")\n    # SLIC segmentation on central slice\n    slic_labels = slic(central_slice_masked, n_segments=100, compactness=1, start_label=0)\n    # For each valid pixel, assign its superpixel label\n    slic_labels_flat = slic_labels.flatten()[valid_idx]\n    pixel_features_df[\"slic_label\"] = slic_labels_flat\n    # Compute region properties (mean intensity per superpixel)\n    region_means = {}\n    for region in regionprops(label(slic_labels)):\n        region_means[region.label] = np.mean(central_slice[slic_labels == region.label])\n    pixel_features_df[\"slic_region_mean\"] = [region_means.get(lbl, 0) for lbl in slic_labels_flat]\n    print(\"Superpixel features added.\")\nexcept Exception as e:\n    print(f\"Error extracting superpixel features: {e}\")\n\n# 4. Create final dataset for modeling\ntry:\n    print(\"Creating final feature dataset for modeling...\")\n    # X_feat: feature matrix, y_feat: dummy targets (from preprocessing)\n    X_feat = pixel_features_df.values\n    y_feat = y_dummy\n    print(f\"Final feature matrix shape: {X_feat.shape}, Target shape: {y_feat.shape}\")\nexcept Exception as e:\n    print(f\"Error creating final feature dataset: {e}\")\n\nprint(\"--- Feature Engineering complete ---\")","outputs":[],"metadata":{},"execution_count":null},{"cell_type":"markdown","source":"## Model Training\n\nTrain kaggle-competition model","metadata":{}},{"cell_type":"code","source":"# ⚠️ ALEXANDRIA MARKER - DO NOT DELETE (used for syncing outputs from Kaggle)\nprint(\"===ALEXANDRIA_CELL_6_START===\")\n\n# ⚠️ ALEXANDRIA MARKER - DO NOT DELETE (used for syncing outputs from Kaggle)\nprint(\"===ALEXANDRIA_CELL_6_START===\")\n\n# Title: Model Training\n# Description: Train kaggle-competition model\n\nimport numpy as np\nimport pandas as pd\nimport traceback\n\n# Check for torch availability from previous cells\nif 'torch_available' in globals() and torch_available:\n    import torch\n    import torch.nn as nn\n    import torch.optim as optim\n    from torch.utils.data import TensorDataset, DataLoader\n    print(\"Using PyTorch for model training.\")\nelse:\n    from sklearn.ensemble import RandomForestClassifier\n    print(\"Using scikit-learn RandomForest for model training.\")\n\nprint(\"\\n--- Model Training ---\")\n\ntry:\n    # Use features and targets from previous cell\n    # X_feat: feature matrix, y_feat: dummy targets\n\n    # Define model architecture and training loop\n    if 'torch_available' in globals() and torch_available:\n        # Convert features and targets to torch tensors\n        X_tensor = torch.tensor(X_feat, dtype=torch.float32)\n        y_tensor = torch.tensor(y_feat, dtype=torch.long)\n\n        # Train/val split (already done, but reusing for torch)\n        X_train_tensor = torch.tensor(X_train, dtype=torch.float32)\n        y_train_tensor = torch.tensor(y_train, dtype=torch.long)\n        X_val_tensor = torch.tensor(X_val, dtype=torch.float32)\n        y_val_tensor = torch.tensor(y_val, dtype=torch.long)\n\n        # Define simple feedforward neural network\n        class SimpleFFNN(nn.Module):\n            def __init__(self, input_dim, hidden_dim=64, output_dim=2):\n                super(SimpleFFNN, self).__init__()\n                self.net = nn.Sequential(\n                    nn.Linear(input_dim, hidden_dim),\n                    nn.ReLU(),\n                    nn.Linear(hidden_dim, hidden_dim),\n                    nn.ReLU(),\n                    nn.Linear(hidden_dim, output_dim)\n                )\n            def forward(self, x):\n                return self.net(x)\n\n        input_dim = X_train_tensor.shape[1]\n        output_dim = len(np.unique(y_train_tensor.numpy()))\n        model = SimpleFFNN(input_dim=input_dim, hidden_dim=64, output_dim=output_dim).to(DEVICE)\n        print(f\"Model architecture:\\n{model}\")\n\n        # Loss and optimizer\n        criterion = nn.CrossEntropyLoss()\n        optimizer = optim.Adam(model.parameters(), lr=1e-3)\n\n        # DataLoader\n        train_dataset = TensorDataset(X_train_tensor, y_train_tensor)\n        val_dataset = TensorDataset(X_val_tensor, y_val_tensor)\n        train_loader = DataLoader(train_dataset, batch_size=2048, shuffle=True)\n        val_loader = DataLoader(val_dataset, batch_size=2048, shuffle=False)\n\n        # Training loop\n        n_epochs = 5\n        print(\"Starting training loop...\")\n        for epoch in range(n_epochs):\n            model.train()\n            train_loss = 0.0\n            for xb, yb in train_loader:\n                xb = xb.to(DEVICE)\n                yb = yb.to(DEVICE)\n                optimizer.zero_grad()\n                outputs = model(xb)\n                loss = criterion(outputs, yb)\n                loss.backward()\n                optimizer.step()\n                train_loss += loss.item() * xb.size(0)\n            train_loss /= len(train_loader.dataset)\n\n            # Validation\n            model.eval()\n            val_loss = 0.0\n            correct = 0\n            total = 0\n            with torch.no_grad():\n                for xb, yb in val_loader:\n                    xb = xb.to(DEVICE)\n                    yb = yb.to(DEVICE)\n                    outputs = model(xb)\n                    loss = criterion(outputs, yb)\n                    val_loss += loss.item() * xb.size(0)\n                    _, predicted = torch.max(outputs, 1)\n                    correct += (predicted == yb).sum().item()\n                    total += yb.size(0)\n            val_loss /= len(val_loader.dataset)\n            val_acc = correct / total if total > 0 else 0\n            print(f\"Epoch {epoch+1}/{n_epochs} - Train Loss: {train_loss:.4f} - Val Loss: {val_loss:.4f} - Val Acc: {val_acc:.4f}\")\n\n        # Save model\n        model_save_path = \"vesuvius_ffnn_model.pth\"\n        torch.save(model.state_dict(), model_save_path)\n        print(f\"Model saved to {model_save_path}\")\n\n    else:\n        # Fallback: scikit-learn RandomForest\n        print(\"Training RandomForestClassifier...\")\n        rf = RandomForestClassifier(n_estimators=50, max_depth=10, random_state=SEED, n_jobs=-1)\n        rf.fit(X_train, y_train)\n        val_acc = rf.score(X_val, y_val)\n        print(f\"Validation Accuracy: {val_acc:.4f}\")\n\n        # Save model\n        import joblib\n        model_save_path = \"vesuvius_rf_model.pkl\"\n        joblib.dump(rf, model_save_path)\n        print(f\"Model saved to {model_save_path}\")\n\nexcept Exception as e:\n    print(\"Error during model training:\")\n    traceback.print_exc()\n\nprint(\"--- Model Training complete ---\")","outputs":[],"metadata":{},"execution_count":null},{"cell_type":"markdown","source":"## Evaluation\n\nEvaluate model performance","metadata":{}},{"cell_type":"code","source":"# ⚠️ ALEXANDRIA MARKER - DO NOT DELETE (used for syncing outputs from Kaggle)\nprint(\"===ALEXANDRIA_CELL_7_START===\")\n\n# ⚠️ ALEXANDRIA MARKER - DO NOT DELETE (used for syncing outputs from Kaggle)\nprint(\"===ALEXANDRIA_CELL_7_START===\")\n\n# Title: Evaluation\n# Description: Evaluate model performance (generate predictions, calculate metrics, visualize results)\n\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport traceback\n\nprint(\"\\n--- Evaluation: Model Performance ---\")\n\ntry:\n    # 1. Generate predictions\n    print(\"Generating predictions on validation set...\")\n    if 'torch_available' in globals() and torch_available:\n        model.eval()\n        X_val_tensor = torch.tensor(X_val, dtype=torch.float32).to(DEVICE)\n        with torch.no_grad():\n            outputs = model(X_val_tensor)\n            _, val_preds = torch.max(outputs, 1)\n        val_preds_np = val_preds.cpu().numpy()\n    else:\n        val_preds_np = rf.predict(X_val)\n\n    print(f\"Predictions generated. Shape: {val_preds_np.shape}\")\n\n    # 2. Calculate metrics\n    print(\"Calculating evaluation metrics...\")\n    from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, confusion_matrix\n\n    val_acc = accuracy_score(y_val, val_preds_np)\n    val_precision = precision_score(y_val, val_preds_np, average='binary', zero_division=0)\n    val_recall = recall_score(y_val, val_preds_np, average='binary', zero_division=0)\n    val_f1 = f1_score(y_val, val_preds_np, average='binary', zero_division=0)\n    val_cm = confusion_matrix(y_val, val_preds_np)\n\n    print(f\"Validation Accuracy: {val_acc:.4f}\")\n    print(f\"Validation Precision: {val_precision:.4f}\")\n    print(f\"Validation Recall: {val_recall:.4f}\")\n    print(f\"Validation F1 Score: {val_f1:.4f}\")\n    print(\"Confusion Matrix:\")\n    print(val_cm)\n\n    # 3. Visualize results\n    print(\"Visualizing results...\")\n\n    # Bar plot for metrics\n    metrics_names = ['Accuracy', 'Precision', 'Recall', 'F1 Score']\n    metrics_values = [val_acc, val_precision, val_recall, val_f1]\n    plt.figure(figsize=(8, 5))\n    plt.bar(metrics_names, metrics_values, color='skyblue')\n    plt.ylim(0, 1)\n    plt.title(\"Validation Metrics\")\n    plt.ylabel(\"Score\")\n    plt.show()\n\n    # Confusion matrix heatmap\n    plt.figure(figsize=(6, 5))\n    sns.heatmap(val_cm, annot=True, fmt='d', cmap='Blues')\n    plt.title(\"Confusion Matrix\")\n    plt.xlabel(\"Predicted Label\")\n    plt.ylabel(\"True Label\")\n    plt.show()\n\n    # Visualize prediction mask for a subset (reshape to image if possible)\n    print(\"Visualizing prediction mask for a sample region...\")\n    # Attempt to reconstruct a mask from validation indices (if possible)\n    try:\n        # Get indices of validation pixels in the original mask\n        val_indices = valid_idx[X_train.shape:X_train.shape+X_val.shape]\n        mask_pred = np.zeros_like(mask_arr, dtype=np.uint8)\n        mask_pred_flat = mask_pred.flatten()\n        mask_pred_flat[val_indices] = val_preds_np\n        mask_pred = mask_pred_flat.reshape(mask_arr.shape)\n        plt.figure(figsize=(6, 6))\n        plt.imshow(mask_pred, cmap='gray')\n        plt.title(\"Validation Prediction Mask\")\n        plt.axis('off')\n        plt.show()\n    except Exception as e:\n        print(f\"Error visualizing prediction mask: {e}\")\n\nexcept Exception as e:\n    print(\"Error during evaluation:\")\n    traceback.print_exc()\n\nprint(\"--- Evaluation complete ---\")","outputs":[],"metadata":{},"execution_count":null},{"cell_type":"markdown","source":"## Results\n\nFinal results and submission","metadata":{}},{"cell_type":"code","source":"# ⚠️ ALEXANDRIA MARKER - DO NOT DELETE (used for syncing outputs from Kaggle)\nprint(\"===ALEXANDRIA_CELL_8_START===\")\n\n# ⚠️ ALEXANDRIA MARKER - DO NOT DELETE (used for syncing outputs from Kaggle)\nprint(\"===ALEXANDRIA_CELL_8_START===\")\n\n# Title: Results\n# Description: Final results and submission\n\nimport os\nimport numpy as np\nimport pandas as pd\nimport traceback\n\nprint(\"\\n--- Results: Formatting Output, Creating Submission, Summary ---\")\n\ntry:\n    # 1. Prepare prediction mask for test set\n    print(\"Preparing prediction mask for test set...\")\n\n    # Reload mask and TIFF stack for test/a\n    from PIL import Image\n\n    test_a_path = os.path.join(DATA_PATH, \"test\", \"a\")\n    mask_path = os.path.join(test_a_path, \"mask.png\")\n    surface_volume_path = os.path.join(test_a_path, \"surface_volume\")\n    surface_volume_files = sorted([f for f in os.listdir(surface_volume_path) if f.endswith('.tif')])\n\n    mask_img = Image.open(mask_path)\n    mask_arr = np.array(mask_img)\n    H, W = mask_arr.shape\n\n    # Load and normalize TIFF stack\n    tiff_arrays = []\n    for fname in surface_volume_files:\n        fpath = os.path.join(surface_volume_path, fname)\n        img = Image.open(fpath)\n        arr = np.array(img, dtype=np.float32)\n        tiff_arrays.append(arr)\n    tiff_stack = np.stack(tiff_arrays, axis=0)  # (num_slices, H, W)\n    tiff_min = np.min(tiff_stack)\n    tiff_max = np.max(tiff_stack)\n    if tiff_max > tiff_min:\n        tiff_stack_norm = (tiff_stack - tiff_min) / (tiff_max - tiff_min)\n    else:\n        tiff_stack_norm = tiff_stack\n\n    # Flatten for valid pixels\n    valid_idx = np.where(mask_arr.flatten() > 0)\n    tiff_flat = tiff_stack_norm.reshape(tiff_stack_norm.shape, -1).T  # (H*W, num_slices)\n    tiff_flat_valid = tiff_flat[valid_idx]\n\n    # Feature extraction (same as training)\n    pixel_features = {\n        \"mean_intensity\": np.mean(tiff_flat_valid, axis=1),\n        \"std_intensity\": np.std(tiff_flat_valid, axis=1),\n        \"min_intensity\": np.min(tiff_flat_valid, axis=1),\n        \"max_intensity\": np.max(tiff_flat_valid, axis=1),\n    }\n\n    # Central slice features\n    from skimage.feature import canny\n    from skimage.filters import sobel\n    from skimage import exposure\n    from skimage.segmentation import slic\n    from skimage.measure import label, regionprops\n    import warnings\n\n    central_idx = tiff_stack_norm.shape // 2\n    central_slice = tiff_stack_norm[central_idx]\n    central_slice_masked = np.zeros_like(central_slice)\n    central_slice_masked[mask_arr > 0] = central_slice[mask_arr > 0]\n    edges = canny(central_slice_masked, sigma=1)\n    sobel_edges = sobel(central_slice_masked)\n    with warnings.catch_warnings():\n        warnings.simplefilter(\"ignore\")\n        clahe_img = exposure.equalize_adapthist(central_slice_masked, clip_limit=0.03)\n    edge_feat = edges.flatten()[valid_idx]\n    sobel_feat = sobel_edges.flatten()[valid_idx]\n    clahe_feat = clahe_img.flatten()[valid_idx]\n\n    # SLIC superpixels\n    slic_labels = slic(central_slice_masked, n_segments=100, compactness=1, start_label=0)\n    slic_labels_flat = slic_labels.flatten()[valid_idx]\n    region_means = {}\n    for region in regionprops(label(slic_labels)):\n        region_means[region.label] = np.mean(central_slice[slic_labels == region.label])\n    slic_region_mean = [region_means.get(lbl, 0) for lbl in slic_labels_flat]\n\n    # Assemble features\n    test_pixel_features_df = pd.DataFrame(pixel_features)\n    test_pixel_features_df[\"canny_edge\"] = edge_feat.astype(np.uint8)\n    test_pixel_features_df[\"sobel_grad\"] = sobel_feat\n    test_pixel_features_df[\"clahe\"] = clahe_feat\n    test_pixel_features_df[\"slic_label\"] = slic_labels_flat\n    test_pixel_features_df[\"slic_region_mean\"] = slic_region_mean\n    X_test_feat = test_pixel_features_df.values\n\n    # 2. Predict ink mask\n    print(\"Generating predictions for test set...\")\n    if 'torch_available' in globals() and torch_available:\n        import torch\n        model.eval()\n        X_test_tensor = torch.tensor(X_test_feat, dtype=torch.float32).to(DEVICE)\n        with torch.no_grad():\n            outputs = model(X_test_tensor)\n            probs = torch.softmax(outputs, dim=1)[:, 1].cpu().numpy()\n        # Threshold (use 0.5 for demonstration; tune as needed)\n        test_preds = (probs > 0.5).astype(np.uint8)\n    else:\n        probs = rf.predict_proba(X_test_feat)[:, 1]\n        test_preds = (probs > 0.5).astype(np.uint8)\n\n    print(f\"Test predictions generated. Positive pixels: {test_preds.sum()}\")\n\n    # 3. Create full-size prediction mask\n    pred_mask_flat = np.zeros(H * W, dtype=np.uint8)\n    pred_mask_flat[valid_idx] = test_preds\n    pred_mask = pred_mask_flat.reshape(H, W)\n\n    # 4. Run-Length Encoding (RLE) for submission\n    def rle_encode(mask):\n        # Flatten in row-major order\n        pixels = mask.flatten(order='C')\n        # 1-based indexing for Kaggle\n        pixels = np.concatenate([, pixels, ])\n        runs = np.where(pixels[1:] != pixels[:-1]) + 1\n        runs[1::2] -= runs[::2]\n        rle = ' '.join(str(x) for x in runs)\n        return rle\n\n    rle_string = rle_encode(pred_mask)\n\n    # 5. Create submission DataFrame\n    submission = pd.DataFrame({\n        \"Id\": [\"a\"],\n        \"Predicted\": [rle_string]\n    })\n\n    # 6. Save submission\n    submission_path = \"submission.csv\"\n    submission.to_csv(submission_path, index=False)\n    print(f\"Submission file saved to {submission_path}\")\n\n    # 7. Print summary\n    print(\"\\n--- Submission Summary ---\")\n    print(f\"Submission shape: {submission.shape}\")\n    print(submission.head())\n    print(f\"Total predicted ink pixels: {test_preds.sum()}\")\n    print(f\"RLE string (first 100 chars): {rle_string[:100]}...\")\n\nexcept Exception as e:\n    print(\"Error in results/submission cell:\")\n    traceback.print_exc()\n\nprint(\"--- Results and submission complete ---\")","outputs":[],"metadata":{},"execution_count":null}],"nbformat":4,"nbformat_minor":4}