{"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":[{"sourceType":"competition","sourceId":112509,"databundleVersionId":14254895,"isSourceIdPinned":false},{"sourceType":"modelInstanceVersion","sourceId":865653,"databundleVersionId":17170498,"modelInstanceId":658184,"modelId":670150,"isSourceIdPinned":false},{"sourceType":"kernelVersion","sourceId":318088039,"isSourceIdPinned":false}],"dockerImageVersionId":31329,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport torch\nimport torch.nn as nn\nimport pandas as pd\nimport numpy as np\nfrom PIL import Image\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\nfrom tqdm import tqdm\nimport timm\nimport glob\nimport os\n\n\n    \n# ==========================================\n# 1. CONFIGURATION\n# ==========================================\nclass CFG:\n    model_name = 'vit_base_patch16_siglip_384'\n    img_size = 384\n    batch_size = 16\n    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n    \n    # Verify this path in your Kaggle Sidebar\n    model_path = \"/kaggle/input/notebooks/aryankhanuja/siglip-finetuning-for-regression/best_model.pth\"\n    \n    base_dir = \"/kaggle/input/competitions/csiro-biomass/\"\n    test_csv = os.path.join(base_dir, \"test.csv\")\n\n# ==========================================\n# 2. DATASET (Dual-Tile logic)\n# ==========================================\nclass BiomassTestDataset(Dataset):\n    def __init__(self, df, transform=None):\n        self.df = df\n        self.transform = transform\n        self.image_paths = df['image_path'].unique()\n\n    def __len__(self):\n        return len(self.image_paths)\n\n    def __getitem__(self, idx):\n        img_path_rel = self.image_paths[idx]\n        img_path_full = os.path.join(CFG.base_dir, img_path_rel)\n        full_img = Image.open(img_path_full).convert('RGB')\n        w, h = full_img.size\n        \n        # Crop 1000x1000 tiles from the 2000x1000 panorama\n        tile1 = self.transform(full_img.crop((0, 0, h, h)))\n        tile2 = self.transform(full_img.crop((w-h, 0, w, h)))\n        \n        return tile1, tile2, img_path_rel\n\n# ==========================================\n# 3. MAMBA ARCHITECTURE (Fixed for your .pth)\n# ==========================================\nclass MambaFusionBlock(nn.Module):\n    def __init__(self, dim):\n        super().__init__()\n        self.norm = nn.LayerNorm(dim)\n        self.conv = nn.Conv1d(dim, dim, kernel_size=3, padding=1, groups=dim)\n        self.gate = nn.Linear(dim, dim)\n\n    def forward(self, x):\n        res = x\n        x = self.norm(x)\n        x = x.transpose(1, 2)\n        x = self.conv(x)\n        x = x.transpose(1, 2)\n        gate = torch.sigmoid(self.gate(x))\n        return res + (x * gate)\n\nclass BiomassSigLIPModel(nn.Module):\n    def __init__(self, model_name):\n        super().__init__()\n        # pretrained=False is required for offline submission\n        self.backbone = timm.create_model(model_name, pretrained=False, num_classes=0)\n        embed_dim = self.backbone.num_features # 768 for vit_base\n        \n        self.fusion = MambaFusionBlock(embed_dim)\n        \n        # Matches your checkpoint's shape [512, 768] and 4-layer structure\n        self.head = nn.Sequential(\n            nn.Linear(embed_dim, 512),\n            nn.ReLU(),\n            nn.Dropout(0.1),\n            nn.Linear(512, 5)\n        )\n\n    def forward(self, x1, x2):\n        f1 = self.backbone(x1).unsqueeze(1) # (B, 1, 768)\n        f2 = self.backbone(x2).unsqueeze(1) # (B, 1, 768)\n        \n        x = torch.cat([f1, f2], dim=1)      # (B, 2, 768)\n        x = self.fusion(x)\n        x = x.mean(dim=1)                   # Pooling tiles together\n        \n        return self.head(x)\n\n# ==========================================\n# 4. INFERENCE ENGINE\n# ==========================================\n\ndef generate_submission():\n    import glob\n    test_df = pd.read_csv(CFG.test_csv)\n    \n    # --- DYNAMIC RECURSIVE SEARCH ---\n    # Looks for any .pth file in the input directory\n    search_pattern = \"/kaggle/input/**/*.pth\" \n    found_files = glob.glob(search_pattern, recursive=True)\n\n    if found_files:\n        CFG.model_path = found_files[0]\n        print(f\"File found: {CFG.model_path}\")\n    else:\n        CFG.model_path = \"best_model.pth\"\n        print(\"No file found in /kaggle/input/. Checking local directory.\")\n\n    # --- FIX: HARDCODE TARGET NAMES ---\n    # This must match the exact order your model was trained to output\n    target_names = ['Dry_Green_g', 'Dry_Dead_g', 'Dry_Clover_g', 'GDM_g', 'Dry_Total_g']\n    \n    test_transform = transforms.Compose([\n        transforms.Resize((CFG.img_size, CFG.img_size)),\n        transforms.ToTensor(),\n        transforms.Normalize([0.5]*3, [0.5]*3)\n    ])\n    \n    loader = DataLoader(BiomassTestDataset(test_df, test_transform), batch_size=CFG.batch_size)\n\n    # Initialize and Load\n    model = BiomassSigLIPModel(CFG.model_name).to(CFG.device)\n    \n    # Final check before torch.load\n    if not os.path.exists(CFG.model_path):\n         raise FileNotFoundError(f\"Weights still missing at {CFG.model_path}\")\n         \n    state_dict = torch.load(CFG.model_path, map_location=CFG.device)\n    model.load_state_dict(state_dict)\n    model.eval()\n\n    results = {}\n    with torch.no_grad():\n        for t1, t2, paths in tqdm(loader):\n            t1, t2 = t1.to(CFG.device), t2.to(CFG.device)\n            preds = model(t1, t2).cpu().numpy()\n            \n            for i, path in enumerate(paths):\n                # Map the 5 output neurons to the 5 target names\n                results[path] = {target_names[j]: max(0.0, float(preds[i, j])) for j in range(5)}\n\n    # Final long-format mapping\n    submission_data = []\n    for _, row in test_df.iterrows():\n        # Look up the specific image path and the specific target name for this row\n        val = results[row['image_path']][row['target_name']]\n        submission_data.append({'sample_id': row['sample_id'], 'target': val})\n\n    pd.DataFrame(submission_data).to_csv(\"submission.csv\", index=False)\n    print(\"Success! submission.csv created.\")\n\nif __name__ == \"__main__\":\n    generate_submission()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-10T13:57:01.918000Z","iopub.execute_input":"2026-05-10T13:57:01.918947Z","iopub.status.idle":"2026-05-10T13:57:06.207530Z","shell.execute_reply.started":"2026-05-10T13:57:01.918910Z","shell.execute_reply":"2026-05-10T13:57:06.206741Z"}},"outputs":[],"execution_count":null}]}