{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":112509,"databundleVersionId":14254895,"sourceType":"competition"},{"sourceId":14088908,"sourceType":"datasetVersion","datasetId":8970765},{"sourceId":14109410,"sourceType":"datasetVersion","datasetId":8987267},{"sourceId":14113057,"sourceType":"datasetVersion","datasetId":8990112}],"dockerImageVersionId":31192,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\n\nprint(\"Root input:\")\nprint(os.listdir(\"/kaggle/input\"))\n\nprint(\"\\nBiomass-model contents:\")\nprint(os.listdir(\"/kaggle/input/biomass-v2\"))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-10T10:58:09.594479Z","iopub.execute_input":"2025-12-10T10:58:09.594806Z","iopub.status.idle":"2025-12-10T10:58:09.600785Z","shell.execute_reply.started":"2025-12-10T10:58:09.594775Z","shell.execute_reply":"2025-12-10T10:58:09.599996Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!ls /kaggle/working","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-11T17:47:55.472540Z","iopub.execute_input":"2025-12-11T17:47:55.473635Z","iopub.status.idle":"2025-12-11T17:47:55.613381Z","shell.execute_reply.started":"2025-12-11T17:47:55.473560Z","shell.execute_reply":"2025-12-11T17:47:55.612154Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!rm /kaggle/working/kaggle_predictions.csv\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-11T20:08:45.780635Z","iopub.execute_input":"2025-12-11T20:08:45.780949Z","iopub.status.idle":"2025-12-11T20:08:45.954577Z","shell.execute_reply.started":"2025-12-11T20:08:45.780927Z","shell.execute_reply":"2025-12-11T20:08:45.953176Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# KAGGLE NOTEBOOK: BIOMASS PREDICTION INFERENCE- V3\n# ============================================================\nimport os\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import Dataset, DataLoader\nimport torchvision.models as models\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\nimport pandas as pd\nimport numpy as np\nfrom PIL import Image\nimport cv2\nfrom tqdm import tqdm\n\n# ============================================================\n# CONFIGURATION\n# ============================================================\nclass CFG:\n    TEST_IMAGE_DIR = \"/kaggle/input/biomass-test-images-set\"   \n    LOCAL_WEIGHTS  = \"/kaggle/input/biomass-nagaty/best_model.pth\"\n    OUTPUT_FILE    = \"/kaggle/working/submission.csv\"\n    \n\n    IMAGE_SIZE = 224\n    BATCH_SIZE = 8\n    DEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\n    \n    TARGET_NAMES = [\"dry_grass\", \"dry_white_clover\", \"dry_red_clover\", \"dry_total\", \"dry_clover\"]\n\n    # External feature dimension (handcrafted features)\n    EXT_FEAT_DIM = 35\n\n    # Key mapping for submission\n    KEY_MAPPER = {\n        \"dry_grass\": \"Dry_Green_g\",\n        \"dry_white_clover\": \"Dry_Dead_g\",\n        \"dry_red_clover\": \"Dry_Clover_g\",\n        \"dry_clover\": \"GDM_g\",\n        \"dry_total\": \"Dry_Total_g\",\n    }\n    \n# ============================================================\n# DATASET\n# ============================================================\nclass BiomassDataset(Dataset):\n    def __init__(self, df, image_dir, ext_feature_cols=None, transform=None, is_test=False):\n        self.df = df.reset_index(drop=True)\n        self.image_dir = image_dir\n        self.transform = transform\n        self.ext_feature_cols = ext_feature_cols or []\n        self.is_test = is_test\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        img_path = os.path.join(self.image_dir, row[\"image_file_name\"])\n        image = np.array(Image.open(img_path).convert(\"RGB\"))\n\n        if self.transform:\n            image = self.transform(image=image)[\"image\"]\n\n        ext_feats = torch.tensor(row[self.ext_feature_cols].values.astype(np.float32), dtype=torch.float32) \\\n                    if self.ext_feature_cols else torch.tensor([])\n\n        if self.is_test:\n            return image, ext_feats, row[\"image_file_name\"]\n        else:\n            targets = torch.tensor(row[CFG.TARGET_NAMES].values.astype(np.float32), dtype=torch.float32)\n            return image, ext_feats, targets\n\n# ============================================================\n# TRANSFORMS\n# ============================================================\ndef get_valid_transforms():\n    return A.Compose([\n        A.Resize(CFG.IMAGE_SIZE, CFG.IMAGE_SIZE),\n        A.Normalize(),\n        ToTensorV2(),\n    ])\n\n# ============================================================\n# EXTERNAL FEATURES COMPUTATION\n# ============================================================\ndef compute_external_features(image_np, resize_to=(CFG.IMAGE_SIZE, CFG.IMAGE_SIZE), hist_bins=8):\n    eps = 1e-8\n    img = image_np.copy()\n    if img.dtype != np.uint8:\n        img = (np.clip(img, 0, 1) * 255).astype(np.uint8)\n    img_rs = cv2.resize(img, resize_to, interpolation=cv2.INTER_AREA)\n    f = img_rs.astype(np.float32) / 255.0\n    R, G, B = f[:, :, 0], f[:, :, 1], f[:, :, 2]\n\n    mean_r, mean_g, mean_b = R.mean(), G.mean(), B.mean()\n    std_r, std_g, std_b = R.std(), G.std(), B.std()\n\n    exg_mean = (2*G - R - B).mean()\n    gr_mean = ((G-R)/(G+R+eps)).mean()\n\n    hsv = cv2.cvtColor(img_rs, cv2.COLOR_RGB2HSV).astype(np.float32) / 255.0\n    sat_mean = hsv[:, :, 1].mean()\n\n    gray = cv2.cvtColor(img_rs, cv2.COLOR_RGB2GRAY)\n    lap_var = cv2.Laplacian(gray, cv2.CV_64F).var()\n    edge_density = cv2.Canny(gray, 100, 200).mean()\n\n    hist_feats = []\n    for ch in range(3):\n        hist = cv2.calcHist([img_rs], [ch], None, [hist_bins], [0, 256]).flatten()\n        hist_feats.extend((hist / (hist.sum() + eps)).tolist())\n\n    feats = [mean_r, mean_g, mean_b, std_r, std_g, std_b, exg_mean, gr_mean,\n             sat_mean, lap_var, edge_density] + hist_feats\n    return np.array(feats, dtype=np.float32)\n\n# ============================================================\n# MODEL\n# ============================================================\nclass CBAMBlock(nn.Module):\n    def __init__(self, channels, reduction=16):\n        super().__init__()\n        self.channel_att = nn.Sequential(\n            nn.AdaptiveAvgPool2d(1),\n            nn.Conv2d(channels, channels // reduction, 1, bias=False),\n            nn.ReLU(),\n            nn.Conv2d(channels // reduction, channels, 1, bias=False),\n            nn.Sigmoid()\n        )\n        self.spatial_att = nn.Sequential(\n            nn.Conv2d(2, 1, 7, padding=3, bias=False),\n            nn.Sigmoid()\n        )\n\n    def forward(self, x):\n        ca = self.channel_att(x) * x\n        avg_out = torch.mean(ca, dim=1, keepdim=True)\n        max_out, _ = torch.max(ca, dim=1, keepdim=True)\n        sa = self.spatial_att(torch.cat([avg_out, max_out], dim=1)) * ca\n        return sa\n\nclass SelfAttention(nn.Module):\n    def __init__(self, in_dim):\n        super().__init__()\n        self.query = nn.Conv2d(in_dim, in_dim // 8, 1)\n        self.key = nn.Conv2d(in_dim, in_dim // 8, 1)\n        self.value = nn.Conv2d(in_dim, in_dim, 1)\n        self.gamma = nn.Parameter(torch.zeros(1))\n\n    def forward(self, x):\n        B, C, H, W = x.size()\n        q = self.query(x).view(B, -1, H*W).permute(0, 2, 1)\n        k = self.key(x).view(B, -1, H*W)\n        v = self.value(x).view(B, -1, H*W)\n        attn = F.softmax(torch.bmm(q, k), dim=-1)\n        out = torch.bmm(v, attn.permute(0, 2, 1)).view(B, C, H, W)\n        return self.gamma * out + x\n\nclass WindowAttention(nn.Module):\n    def __init__(self, dim, window_size=7, num_heads=8):\n        super().__init__()\n\n\n        \n        self.dim = dim\n        self.window_size = window_size\n        self.num_heads = num_heads\n        head_dim = dim // num_heads\n        self.scale = head_dim ** -0.5\n        self.qkv = nn.Linear(dim, dim*3)\n        self.proj = nn.Linear(dim, dim)\n\n    def forward(self, x):\n        B, N, C = x.shape\n        qkv = self.qkv(x).reshape(B, N, 3, self.num_heads, C//self.num_heads)\n        q, k, v = qkv.permute(2,0,3,1,4).unbind(0)\n        attn = (q @ k.transpose(-2,-1)) * self.scale\n        attn = attn.softmax(dim=-1)\n        out = (attn @ v).transpose(2,3).reshape(B,N,C)\n        return self.proj(out)\n\nclass SwinBlock(nn.Module):\n    def __init__(self, dim=512, input_resolution=(7,7), num_heads=8, window_size=7, mlp_ratio=4.0):\n        super().__init__()\n        self.norm1 = nn.LayerNorm(dim)\n        self.attn = WindowAttention(dim=dim, window_size=window_size, num_heads=num_heads)\n        self.norm2 = nn.LayerNorm(dim)\n        self.mlp = nn.Sequential(nn.Linear(dim,int(dim*mlp_ratio)), nn.GELU(), nn.Linear(int(dim*mlp_ratio), dim))\n\n    def forward(self, x):\n        B,C,H,W = x.shape\n        x_flat = x.flatten(2).transpose(1,2)\n        shortcut = x_flat\n        x = self.norm1(x_flat)\n        x = shortcut + self.attn(x)\n        shortcut = x\n        x = self.norm2(x)\n        x = shortcut + self.mlp(x)\n        x = x.transpose(1,2).view(B,C,H,W)\n        return x\n\nclass BiomassModel(nn.Module):\n    def __init__(self, ext_feat_dim=0):\n        super().__init__()\n        base = models.resnet18(weights=None)\n        self.cnn = nn.Sequential(*list(base.children())[:-2])\n        self.cbam = CBAMBlock(512)\n        self.attn = SelfAttention(512)\n        self.swin = SwinBlock(dim=512, input_resolution=(7,7), num_heads=8)\n        self.pool = nn.AdaptiveAvgPool2d(1)\n        self.ext_feat_dim = ext_feat_dim\n        self.fc = nn.Linear(512 + ext_feat_dim, len(CFG.TARGET_NAMES))\n\n    def forward(self, x, ext_feats=None):\n        x = self.cnn(x)\n        x = self.cbam(x)\n        x = self.attn(x)\n        x = self.swin(x)\n        x = self.pool(x).squeeze(-1).squeeze(-1)\n        if ext_feats is not None and self.ext_feat_dim > 0:\n            x = torch.cat([x, ext_feats], dim=1)\n        return self.fc(x)\n\n# ============================================================\n# INFERENCE FUNCTION\n# ============================================================\ndef run_inference():\n    device = CFG.DEVICE\n    model = BiomassModel(ext_feat_dim=CFG.EXT_FEAT_DIM).to(device)\n\n    # -------------------------\n    # Load checkpoint\n    # -------------------------\n    checkpoint = torch.load(CFG.LOCAL_WEIGHTS, map_location=device)\n    model_dict = model.state_dict()\n    pretrained_dict = {k: v for k, v in checkpoint.items() if k in model_dict and \"fc\" not in k}\n    model_dict.update(pretrained_dict)\n    model.load_state_dict(model_dict)\n    model.eval()\n\n    # -------------------------\n    # Prepare test dataset\n    # -------------------------\n    test_images = sorted(os.listdir(CFG.TEST_IMAGE_DIR))\n\n    # Compute external features\n    ext_feats_list = []\n    for img_name in test_images:\n        img_path = os.path.join(CFG.TEST_IMAGE_DIR, img_name)\n        img_np = np.array(Image.open(img_path).convert(\"RGB\"))\n        ext_feats_list.append(compute_external_features(img_np))\n\n    ext_feats_df = pd.DataFrame(ext_feats_list, columns=[f\"f{i}\" for i in range(CFG.EXT_FEAT_DIM)])\n    test_df_local = pd.DataFrame({\"image_file_name\": test_images})\n    test_df_local = pd.concat([test_df_local, ext_feats_df], axis=1)\n\n    test_dataset = BiomassDataset(\n        test_df_local,\n        CFG.TEST_IMAGE_DIR,\n        ext_feature_cols=list(ext_feats_df.columns),\n        transform=get_valid_transforms(),\n        is_test=True\n    )\n\n    test_loader = DataLoader(\n        test_dataset,\n        batch_size=CFG.BATCH_SIZE,\n        shuffle=False,\n        num_workers=0,\n        pin_memory=True\n    )\n\n    # -------------------------\n    # Run inference\n    # -------------------------\n    preds_list = []\n    with torch.no_grad():\n        for images, ext_feats, _ in tqdm(test_loader, desc=\"Inference\"):\n            images = images.to(device)\n            ext_feats = ext_feats.to(device)\n            outputs = model(images, ext_feats).cpu().numpy()\n            preds_list.append(outputs)\n\n    preds = np.concatenate(preds_list, axis=0)\n\n    # -------------------------\n    # Build Kaggle submission\n    # -------------------------\n    test_df_kaggle = pd.read_csv('/kaggle/input/csiro-biomass/test.csv')\n\n    target_names = [\n        \"Dry_Green_g\",\n        \"Dry_Dead_g\",\n        \"Dry_Clover_g\",\n        \"GDM_g\",\n        \"Dry_Total_g\"\n    ]\n\n    submission_rows = []\n\n    for sample_id_full, pred_row in zip(test_df_kaggle['sample_id'], preds):\n        sample_id = sample_id_full.split(\"__\")[0]  # take only the part before the first __\n        for t, value in zip(target_names, pred_row):\n            submission_rows.append({\n                \"sample_id\": f\"{sample_id}__{t}\",\n                \"target\": float(np.abs(value))\n            })\n    submission_df = pd.DataFrame(submission_rows)\n\n    # Save to Kaggle working directory\n    submission_path = \"/kaggle/working/submission.csv\"\n    submission_df.to_csv(submission_path, index=False)\n\n    print(\"✅ Submission created successfully!\")\n    print(submission_df.head())\n    \n\"\"\"\ndef run_inference():\n    device = CFG.DEVICE\n    model = BiomassModel(ext_feat_dim=CFG.EXT_FEAT_DIM).to(device)\n\n    # -------------------------\n    # Load checkpoint (ignore fc mismatch)\n    # -------------------------\n    checkpoint = torch.load(CFG.LOCAL_WEIGHTS, map_location=device)\n    model_dict = model.state_dict()\n    pretrained_dict = {k: v for k, v in checkpoint.items() if k in model_dict and \"fc\" not in k}\n    model_dict.update(pretrained_dict)\n    model.load_state_dict(model_dict)\n    model.eval()\n\n    # -------------------------\n    # Prepare test dataset\n    # -------------------------\n    test_images = sorted(os.listdir(CFG.TEST_IMAGE_DIR))\n    test_df = pd.DataFrame({\"image_file_name\": test_images})\n    ext_feature_cols = [f for f in range(CFG.EXT_FEAT_DIM)]  # placeholder for manual features\n\n    # Compute external features for all test images\n    ext_feats_list = []\n    \n    for img_name in test_images:\n        img_path = os.path.join(CFG.TEST_IMAGE_DIR, img_name)\n        img_np = np.array(Image.open(img_path).convert(\"RGB\"))\n        ext_feats_list.append(compute_external_features(img_np))\n\n    ext_feats_df = pd.DataFrame(ext_feats_list, columns=[f\"f{i}\" for i in range(CFG.EXT_FEAT_DIM)])\n    test_df_local = pd.DataFrame({\"image_file_name\": test_images})\n    test_df_local = pd.concat([test_df_local, ext_feats_df], axis=1)\n\n    test_df = pd.concat([test_df, ext_feats_df], axis=1)\n\n    test_dataset = BiomassDataset(test_df, CFG.TEST_IMAGE_DIR,\n                                  ext_feature_cols=list(ext_feats_df.columns),\n                                  transform=get_valid_transforms(),\n                                  is_test=True)\n    test_loader = DataLoader(test_dataset, batch_size=CFG.BATCH_SIZE, shuffle=False, num_workers=0, pin_memory=True)\n    \n    # -------------------------\n    # Run inference\n    # -------------------------\n    preds_list, img_ids = [], []\n    with torch.no_grad():\n        for images, ext_feats, image_paths in tqdm(test_loader, desc=\"Inference\"):\n            images = images.to(device)\n            ext_feats = ext_feats.to(device)\n            outputs = model(images, ext_feats).cpu().numpy()\n            preds_list.append(outputs)\n            #img_ids.extend(image_paths)\n\n    preds = np.concatenate(preds_list, axis=0)\n\n    # -------------------------\n    # Build Kaggle submission\n    # -------------------------\n    # Load official Kaggle test.csv (THIS is the important part)\n    test_df_kaggle = pd.read_csv('/kaggle/input/biomass-prediction/test.csv')\n\n    target_names = [\n        \"Dry_Green_g\",\n        \"Dry_Dead_g\",\n        \"Dry_Clover_g\",\n        \"GDM_g\",\n        \"Dry_Total_g\"\n    ]\n\n    submission_rows = []\n\n    for sample_id, pred_row in zip(test_df_kaggle['sample_id'], predictions):\n        for t, value in zip(target_names, pred_row):\n            submission_rows.append({\n                \"sample_id\": f\"{sample_id}__{t}\",\n                \"target\": float(value)\n            })\n\n    submission_df = pd.DataFrame(submission_rows)\n\n    # save\n    submission_path = \"/kaggle/working/submission.csv\"\n    submission_df.to_csv(submission_path, index=False)\n\n    print(\"✅ Submission created successfully!\")\n    print(submission_df.head())\n     \n    # -------------------------\n    # Prepare submission\n    # -------------------------\n    results = []\n    for img_id, row_pred in zip(img_ids, preds):\n         base_id = os.path.splitext(img_id)[0]\n         for name, value in zip(CFG.TARGET_NAMES, row_pred):\n            results.append({\n                \"sample_id\": f\"{base_id}__{CFG.KEY_MAPPER[name]}\",\n                \"target\": float(abs(value))\n           })\n\n    out_df = pd.DataFrame(results)\n\n    # Explicitly save to /kaggle/working\n    submission = pd.DataFrame(results)\n    \n    # Save submission CSV\n    submission.to_csv(\"/kaggle/working/submission.csv\", index=False)\n\n    # Verify the file exists\n    print(f\"✅ CSV saved at: {CFG.OUTPUT_FILE}\")\n    print(\"Files currently in /kaggle/working:\")\n    print(os.listdir(\"/kaggle/working\"))\n\n    out_df.to_csv(CFG.OUTPUT_FILE, index=False)\n    print(f\"✅ Inference complete. Predictions saved to {CFG.OUTPUT_FILE}\")\n    print(\"First 5 lines:\")\n    print(out_df.head())\n    print(out_df.columns)\n  \"\"\"  \n# ============================================================\n# RUN\n# ============================================================\nrun_inference()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-12T08:38:39.065090Z","iopub.execute_input":"2025-12-12T08:38:39.065517Z","iopub.status.idle":"2025-12-12T08:39:27.788541Z","shell.execute_reply.started":"2025-12-12T08:38:39.065490Z","shell.execute_reply":"2025-12-12T08:39:27.787552Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\nprint(os.listdir(\"/kaggle/input\"))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-12T08:21:44.203369Z","iopub.execute_input":"2025-12-12T08:21:44.203675Z","iopub.status.idle":"2025-12-12T08:21:44.210637Z","shell.execute_reply.started":"2025-12-12T08:21:44.203654Z","shell.execute_reply":"2025-12-12T08:21:44.209390Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\nfor folder in ['biomass-nagaty', 'csiro-biomass', 'biomass-test-images-set', 'biomass-v2']:\n    print(\"----\", folder, \"----\")\n    print(os.listdir(f\"/kaggle/input/{folder}\"))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-12T08:24:19.325614Z","iopub.execute_input":"2025-12-12T08:24:19.326231Z","iopub.status.idle":"2025-12-12T08:24:19.343689Z","shell.execute_reply.started":"2025-12-12T08:24:19.326197Z","shell.execute_reply":"2025-12-12T08:24:19.342649Z"}},"outputs":[],"execution_count":null}]}