{"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":"none","dataSources":[{"sourceId":126118,"databundleVersionId":14933142,"sourceType":"competition"}],"dockerImageVersionId":31259,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-01-21T13:00:00.893433Z","iopub.execute_input":"2026-01-21T13:00:00.894555Z","execution_failed":"2026-01-21T13:12:31.089Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"pip install torch torchvision rasterio pandas numpy tqdm","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-21T13:06:52.311642Z","iopub.execute_input":"2026-01-21T13:06:52.312001Z","iopub.status.idle":"2026-01-21T13:07:01.004545Z","shell.execute_reply.started":"2026-01-21T13:06:52.311965Z","shell.execute_reply":"2026-01-21T13:07:01.003271Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport glob\nimport numpy as np\nimport pandas as pd\nimport rasterio\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import models\nfrom tqdm import tqdm\n\n# ==========================================\n# 1. CONFIGURATION (Updated for your Path)\n# ==========================================\n# Based on the paths you pasted:\n# /kaggle/input/beyond-visible-spectrum-ai-for-agriculture-2026p2/archive/share/val/1215/B7.tif\nDATA_ROOT = \"/kaggle/input/beyond-visible-spectrum-ai-for-agriculture-2026p2/archive/share\"\n\n# Configuration\nIMG_SIZE = 64          # Keep small for faster testing. Increase to 128 or 224 for best results.\nBATCH_SIZE = 16\nNUM_CLASSES = 4\nCLASS_MAP = {0: 'Aphid', 1: 'Rust', 2: 'RPH', 3: 'Blast'}\nBAND_NAMES = ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'B8', 'B8A', 'B9', 'B11', 'B12']\n\n# ==========================================\n# 2. DATASET LOADER\n# ==========================================\nclass SentinelDataset(Dataset):\n    def __init__(self, root_dir):\n        self.root_dir = root_dir\n        self.samples = []\n        \n        # Check for 'val' folder based on your file dump\n        val_dir = os.path.join(root_dir, 'val')\n        test_dir = os.path.join(root_dir, 'test')\n        \n        # Determine which folder to use\n        if os.path.exists(val_dir):\n            target_dir = val_dir\n            print(f\"✅ Found data in: {target_dir}\")\n        elif os.path.exists(test_dir):\n            target_dir = test_dir\n            print(f\"✅ Found data in: {target_dir}\")\n        else:\n            print(f\"❌ Error: Could not find 'val' or 'test' inside {root_dir}\")\n            target_dir = None\n\n        if target_dir:\n            # Get all subfolders (these are the Sample IDs, e.g., '1215', '1518')\n            self.samples = [f.path for f in os.scandir(target_dir) if f.is_dir()]\n\n    def __len__(self):\n        return len(self.samples)\n\n    def __getitem__(self, idx):\n        sample_path = self.samples[idx]\n        sample_id = os.path.basename(sample_path)\n        bands_data = []\n\n        # Load all 12 Bands\n        for b_name in BAND_NAMES:\n            # Construct path: .../val/1215/B1.tif\n            file_path = os.path.join(sample_path, f\"{b_name}.tif\")\n            \n            # Robust Check: Sometimes files might be nested. \n            # If not found directly, look one level deeper (based on your dump showing 20210511...)\n            if not os.path.exists(file_path):\n                # Try finding it recursively if strictly necessary, \n                # but for now we fallback to zeros to prevent crashing.\n                possible_files = glob.glob(os.path.join(sample_path, \"**\", f\"{b_name}.tif\"), recursive=True)\n                if possible_files:\n                    file_path = possible_files[0]\n                else:\n                    # If absolutely cannot find band, return black image\n                    band = np.zeros((IMG_SIZE, IMG_SIZE), dtype=np.float32)\n                    bands_data.append(band)\n                    continue\n\n            with rasterio.open(file_path) as src:\n                # Read and resize\n                # rasterio reads as (bands, height, width)\n                band = src.read(1, out_shape=(1, IMG_SIZE, IMG_SIZE),\n                              resampling=rasterio.enums.Resampling.bilinear)\n                \n                # Handle NaNs or infinite values\n                band = np.nan_to_num(band)\n                bands_data.append(band)\n\n        # Stack bands -> Shape (12, H, W)\n        img = np.stack(bands_data, axis=0).astype(np.float32)\n        \n        # Normalize (Sentinel-2 reflectance is usually 0-10000)\n        img = img / 10000.0\n        img = np.clip(img, 0, 1)\n        \n        return torch.from_numpy(img), sample_id\n\n# ==========================================\n# 3. MODEL (12-Channel ResNet)\n# ==========================================\ndef get_model(num_classes):\n    # Using ResNet18 for speed\n    model = models.resnet18(weights=None)\n    \n    # Modify first layer to accept 12 channels instead of 3\n    original_layer = model.conv1\n    model.conv1 = nn.Conv2d(12, 64, kernel_size=7, stride=2, padding=3, bias=False)\n    \n    # Initialize weights by averaging the original RGB weights\n    with torch.no_grad():\n        model.conv1.weight[:] = torch.mean(original_layer.weight, dim=1, keepdim=True).repeat(1, 12, 1, 1)\n    \n    # Modify output layer\n    num_ftrs = model.fc.in_features\n    model.fc = nn.Linear(num_ftrs, num_classes)\n    \n    return model\n\n# ==========================================\n# 4. MAIN EXECUTION\n# ==========================================\nif __name__ == \"__main__\":\n    device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n    print(f\"Running on: {device}\")\n\n    # A. Load Data\n    test_dataset = SentinelDataset(DATA_ROOT)\n    \n    if len(test_dataset) == 0:\n        print(\"❌ No samples found! Please verify the path.\")\n    else:\n        print(f\"✅ Loaded {len(test_dataset)} samples for inference.\")\n        test_loader = DataLoader(test_dataset, batch_size=BATCH_SIZE, shuffle=False)\n\n        # B. Load Model\n        model = get_model(NUM_CLASSES).to(device)\n        model.eval()\n\n        # C. Inference Loop\n        all_predictions = []\n        print(\"Starting inference (this may take a moment)...\")\n        \n        with torch.no_grad():\n            for images, ids in tqdm(test_loader):\n                images = images.to(device)\n                \n                # Forward pass\n                outputs = model(images)\n                _, predicted = torch.max(outputs, 1)\n                \n                # Map predictions to Class Names\n                for id_str, pred_idx in zip(ids, predicted):\n                    pred_label = CLASS_MAP[pred_idx.item()]\n                    all_predictions.append({'Id': id_str, 'Category': pred_label})\n\n        # D. Save Submission File\n        df = pd.DataFrame(all_predictions)\n        \n        # Ensure 'Id' is clean (sometimes folder names have extra paths)\n        df['Id'] = df['Id'].apply(lambda x: str(x).split('/')[-1])\n        \n        df.to_csv(\"submission.csv\", index=False)\n        \n        print(\"\\n\" + \"=\"*40)\n        print(\"🎉 SUCCESS! 'submission.csv' generated.\")\n        print(\"=\"*40)\n        print(df.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-21T13:10:49.217697Z","iopub.execute_input":"2026-01-21T13:10:49.218101Z","iopub.status.idle":"2026-01-21T13:12:10.369696Z","shell.execute_reply.started":"2026-01-21T13:10:49.218070Z","shell.execute_reply":"2026-01-21T13:12:10.368521Z"}},"outputs":[],"execution_count":null}]}