{"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":112509,"databundleVersionId":14254895,"sourceType":"competition"},{"sourceId":14571731,"sourceType":"datasetVersion","datasetId":9308058},{"sourceId":14578478,"sourceType":"datasetVersion","datasetId":9312394},{"sourceId":14610878,"sourceType":"datasetVersion","datasetId":9332675,"isSourceIdPinned":false}],"dockerImageVersionId":31260,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# DualGatedEncoder -> 0.75 LB (Public) & 0.5 Ensemble weight","metadata":{}},{"cell_type":"code","source":"import os\nimport cv2\nimport timm\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport pandas as pd\nimport numpy as np\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\nfrom torch.utils.data import Dataset, DataLoader\nfrom tqdm import tqdm\nimport warnings\nwarnings.filterwarnings(\"ignore\")\n\n\nDATA_ROOT = \"/kaggle/input/csiro-biomass\" \nTEST_CSV = \"/kaggle/input/csiro-biomass/test.csv\"\nMODEL_PATH = \"/kaggle/input/aux-model/best_model (5).pt\" \nOUTPUT_PATH = \"submission_75.csv\"\n\nDEVICE = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nBATCH_SIZE = 8\nIMG_SIZE = 512\nNUM_WORKERS = 0\nBACKBONE = \"vit_huge_plus_patch16_dinov3.lvd1689m\"\n\n\n\nclass TestBiomassDataset(Dataset):\n    def __init__(self, df, data_root, img_size=512):\n        self.df = df.reset_index(drop=True)\n        self.data_root = data_root\n        \n        norm_stats = {'mean': [0.485, 0.456, 0.406], 'std': [0.229, 0.224, 0.225]}\n        target_h, target_w = img_size, img_size * 2\n\n        self.tfms = A.Compose([\n            A.Resize(height=target_h, width=target_w),\n            A.Normalize(**norm_stats),\n            ToTensorV2()\n        ])\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row = self.df.loc[idx]\n        partial_path = row[\"image_path\"]\n        \n        img_path = os.path.join(self.data_root, partial_path)\n        if not os.path.exists(img_path):\n            if partial_path.startswith(\"test/\") or partial_path.startswith(\"test\\\\\"):\n                clean_name = os.path.basename(partial_path)\n                alt_path = os.path.join(self.data_root, clean_name)\n                if os.path.exists(alt_path):\n                    img_path = alt_path\n\n        img = cv2.imread(img_path)\n        if img is None:\n            # Fallback for missing images\n            img = np.zeros((512, 1024, 3), dtype=np.uint8)\n        else:\n            img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n\n        augmented = self.tfms(image=img)\n        img_tensor = augmented[\"image\"]\n\n        _, h, w = img_tensor.shape\n        mid = w // 2\n        left = img_tensor[:, :, :mid]\n        right = img_tensor[:, :, mid:]\n\n        return left, right\n\n\nclass GatedDWConvBlock(nn.Module):\n    def __init__(self, dim: int, kernel_size: int = 5, dropout: float = 0.1):\n        super().__init__()\n        self.norm = nn.LayerNorm(dim)\n        self.dwconv = nn.Conv1d(dim, dim, kernel_size=kernel_size, padding=kernel_size // 2, groups=dim)\n        self.gate = nn.Linear(dim, dim)\n        self.proj = nn.Linear(dim, dim)\n        self.drop = nn.Dropout(dropout)\n\n    def forward(self, x: torch.Tensor) -> torch.Tensor:\n        shortcut = x\n        x = self.norm(x)\n        g = torch.sigmoid(self.gate(x))\n        x = x * g\n        x = x.transpose(1, 2)\n        x = self.dwconv(x)\n        x = x.transpose(1, 2)\n        x = self.proj(x)\n        x = self.drop(x)\n        return shortcut + x\n\nclass DualGatedEncoder(nn.Module):\n    def __init__(self, backbone_name=\"vit_huge_plus_patch16_dinov3.lvd1689m\", meta_dim=4):\n        super().__init__()\n        self.backbone = timm.create_model(backbone_name, pretrained=False, \n                                          num_classes=0, global_pool='', dynamic_img_size=True)\n        nf = self.backbone.num_features\n        \n        self.fusion = nn.Sequential(\n            GatedDWConvBlock(nf, kernel_size=5, dropout=0.1),\n            GatedDWConvBlock(nf, kernel_size=5, dropout=0.1) \n        )\n        \n        self.pool = nn.AdaptiveAvgPool1d(1)\n        \n        self.aux_head = nn.Sequential(\n            nn.Linear(nf, nf // 2), nn.SiLU(), nn.Linear(nf // 2, meta_dim)\n        )\n        \n        self.meta_encoder = nn.Sequential(\n            nn.Linear(meta_dim, 64), nn.SiLU(), nn.Linear(64, 64)\n        )\n        \n        self.consistency_gate = nn.Sequential(\n            nn.Linear(meta_dim, 32), nn.ReLU(), nn.Linear(32, 1), nn.Sigmoid() \n        )\n        \n        in_dim = nf + 64 \n        self.head_total = self._make_head(in_dim)\n        self.head_gdm = self._make_head(in_dim)\n        self.head_green = self._make_head(in_dim)\n        self.head_clover = self._make_head(in_dim)\n        self.head_dead = self._make_head(in_dim)\n\n    def _make_head(self, in_dim):\n        return nn.Sequential(\n            nn.Linear(in_dim, in_dim // 2), nn.GELU(), nn.Dropout(0.2),\n            nn.Linear(in_dim // 2, 1), nn.Softplus()\n        )\n        \n    def forward(self, left, right, meta=None):\n        l_feat = self.backbone(left)\n        r_feat = self.backbone(right)\n        x = torch.cat([l_feat, r_feat], dim=1) \n        x = self.fusion(x)\n        x = x.transpose(1, 2)\n        x = self.pool(x).flatten(1) \n\n        aux_meta_preds = self.aux_head(x)\n        if meta is None:\n            meta = aux_meta_preds\n\n        m_emb = self.meta_encoder(meta)\n        x = torch.cat([x, m_emb], dim=1)\n        gate_val = self.consistency_gate(meta) \n\n        raw_total = self.head_total(x)\n        raw_gdm = self.head_gdm(x)\n        raw_green = self.head_green(x)\n        raw_clover = self.head_clover(x)\n        raw_dead = self.head_dead(x)\n\n        derived_dead = F.relu(raw_total - raw_gdm)\n        final_dead = (gate_val * derived_dead) + ((1 - gate_val) * raw_dead)\n        \n        biomass_preds = torch.cat([raw_green, raw_clover, final_dead, raw_gdm, raw_total], dim=1)\n        return biomass_preds\n\n\ndef run_inference():\n    print(f\"Reading CSV from {TEST_CSV}...\")\n    if not os.path.exists(TEST_CSV):\n        raise FileNotFoundError(f\"Cannot find test.csv at {TEST_CSV}\")\n        \n    test_df = pd.read_csv(TEST_CSV)\n    \n    if 'image_path' in test_df.columns:\n        wide_df = test_df.drop_duplicates(subset=[\"image_path\"]).reset_index(drop=True)\n    else:\n        wide_df = test_df.copy()\n    \n    print(f\"Unique images to process: {len(wide_df)}\")\n\n    test_ds = TestBiomassDataset(wide_df, DATA_ROOT, IMG_SIZE)\n    test_loader = DataLoader(test_ds, batch_size=BATCH_SIZE, shuffle=False, \n                             num_workers=NUM_WORKERS, pin_memory=True)\n\n    print(f\"Initializing Model: {BACKBONE}...\")\n    model = DualGatedEncoder(backbone_name=BACKBONE).to(DEVICE)\n    \n    print(f\"Loading Weights from {MODEL_PATH}...\")\n    checkpoint = torch.load(MODEL_PATH, map_location=DEVICE)\n    if \"state_dict\" in checkpoint:\n        model.load_state_dict(checkpoint[\"state_dict\"])\n    else:\n        model.load_state_dict(checkpoint)\n        \n    model.eval()\n    \n    accumulated_preds = np.zeros((len(wide_df), 5))\n    \n    print(\"Starting Inference with 4-way TTA...\")\n    \n    with torch.no_grad():\n        start_idx = 0\n        for left, right in tqdm(test_loader):\n            left = left.to(DEVICE)\n            right = right.to(DEVICE)\n            batch_size = left.size(0)\n            \n            # TTA Passes\n            p1 = model(left, right, meta=None)\n            p2 = model(torch.flip(left, [3]), torch.flip(right, [3]), meta=None)\n            p3 = model(torch.flip(left, [2]), torch.flip(right, [2]), meta=None)\n            p4 = model(torch.flip(left, [2, 3]), torch.flip(right, [2, 3]), meta=None)\n            \n            avg_p = (p1 + p2 + p3 + p4) / 4.0\n            \n            end_idx = start_idx + batch_size\n            accumulated_preds[start_idx:end_idx] = avg_p.cpu().numpy()\n            start_idx = end_idx\n\n    \n    # Enforcing Mass-Balance Constraints\n    # Extract Raw Components\n    dry_green  = accumulated_preds[:, 0]\n    dry_clover = accumulated_preds[:, 1]\n    dry_dead   = accumulated_preds[:, 2]\n    \n    # 1. Enforce: GDM = Green + Clover\n    consistent_gdm = dry_green + dry_clover\n    \n    # 2. Enforce: Total = GDM + Dead\n    consistent_total = consistent_gdm + dry_dead\n    \n    # Update the array with physically consistent values\n    accumulated_preds[:, 3] = consistent_gdm  # Overwrite GDM\n    accumulated_preds[:, 4] = consistent_total # Overwrite Total\n\n    print(\"Formatting Submission...\")\n    \n    wide_df['Dry_Green_g']  = accumulated_preds[:, 0]\n    wide_df['Dry_Clover_g'] = accumulated_preds[:, 1]\n    wide_df['Dry_Dead_g']   = accumulated_preds[:, 2]\n    wide_df['GDM_g']        = accumulated_preds[:, 3]\n    wide_df['Dry_Total_g']  = accumulated_preds[:, 4]\n\n    if 'sample_id' in wide_df.columns:\n         wide_df['sample_id_prefix'] = wide_df['sample_id'].str.split('__').str[0]\n    else:\n        wide_df['sample_id_prefix'] = wide_df['image_path'].apply(lambda x: os.path.splitext(os.path.basename(x))[0])\n\n    cols_to_stack = ['Dry_Green_g', 'Dry_Dead_g', 'Dry_Clover_g', 'GDM_g', 'Dry_Total_g']\n    \n    sub_df = wide_df.set_index('sample_id_prefix')[cols_to_stack].stack().reset_index()\n    sub_df.columns = ['sample_id_prefix', 'target_name', 'target']\n    \n    sub_df['sample_id'] = sub_df['sample_id_prefix'] + '__' + sub_df['target_name']\n\n    submission = sub_df[['sample_id', 'target']]\n    submission.to_csv(OUTPUT_PATH, index=False)\n    \n    print(f\"✅ Submission saved to {OUTPUT_PATH}\")\n    print(submission.head())\n    \n    \n    del model\n    del checkpoint\n    del test_loader\n    del test_ds\n    del accumulated_preds\n    try:\n        del left, right, p1, p2, p3, p4, avg_p\n    except NameError:\n        pass\n    torch.cuda.empty_cache()\n    import gc\n    gc.collect()\n\nif __name__ == \"__main__\":\n    run_inference()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-26T06:44:30.114946Z","iopub.execute_input":"2026-01-26T06:44:30.115337Z","iopub.status.idle":"2026-01-26T06:45:57.813164Z","shell.execute_reply.started":"2026-01-26T06:44:30.115309Z","shell.execute_reply":"2026-01-26T06:45:57.812222Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 1) TwinStreamMetaNet -> 0.74 LB (Public) & 0.3 Ensemble weight","metadata":{}},{"cell_type":"code","source":"import os\nimport cv2\nimport math\nimport timm\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport pandas as pd\nimport numpy as np\nfrom torch.utils.data import Dataset, DataLoader\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\nfrom tqdm import tqdm\nimport warnings\nwarnings.filterwarnings('ignore')\n\nDATA_ROOT = \"/kaggle/input/csiro-biomass\" \nTEST_CSV = \"/kaggle/input/csiro-biomass/test.csv\"\nMODEL_PATH = \"/kaggle/input/rajvvv/best_model-2.pt\" \nOUTPUT_PATH = \"submission_74.csv\"\n\nDEVICE = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nBATCH_SIZE = 8\nIMG_SIZE = 512\nNUM_WORKERS = 0\nBACKBONE = \"vit_huge_plus_patch16_dinov3.lvd1689m\"\n\n\nclass TestBiomassDataset(Dataset):\n    def __init__(self, df, data_root, img_size=512):\n        self.df = df.reset_index(drop=True)\n        self.data_root = data_root\n        \n        norm_stats = {'mean': [0.485, 0.456, 0.406], 'std': [0.229, 0.224, 0.225]}\n        target_h, target_w = img_size, img_size * 2\n\n        self.tfms = A.Compose([\n            A.Resize(height=target_h, width=target_w),\n            A.Normalize(**norm_stats),\n            ToTensorV2()\n        ])\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row = self.df.loc[idx]\n        partial_path = row[\"image_path\"]\n        \n        img_path = os.path.join(self.data_root, partial_path)\n        if not os.path.exists(img_path):\n            if partial_path.startswith(\"test/\") or partial_path.startswith(\"test\\\\\"):\n                clean_name = os.path.basename(partial_path)\n                alt_path = os.path.join(self.data_root, clean_name)\n                if os.path.exists(alt_path):\n                    img_path = alt_path\n\n        img = cv2.imread(img_path)\n        if img is None:\n            # Fallback for missing images\n            img = np.zeros((512, 1024, 3), dtype=np.uint8)\n        else:\n            img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n\n        augmented = self.tfms(image=img)\n        img_tensor = augmented[\"image\"]\n\n        _, h, w = img_tensor.shape\n        mid = w // 2\n        left = img_tensor[:, :, :mid]\n        right = img_tensor[:, :, mid:]\n\n        return left, right\n\n\nclass GatedDWConvBlock(nn.Module):\n    def __init__(self, dim: int, kernel_size: int = 5, dropout: float = 0.1):\n        super().__init__()\n        self.norm = nn.LayerNorm(dim)\n        self.dwconv = nn.Conv1d(dim, dim, kernel_size=kernel_size, padding=kernel_size // 2, groups=dim)\n        self.gate = nn.Linear(dim, dim)\n        self.proj = nn.Linear(dim, dim)\n        self.drop = nn.Dropout(dropout)\n\n    def forward(self, x: torch.Tensor) -> torch.Tensor:\n        shortcut = x\n        x = self.norm(x)\n        g = torch.sigmoid(self.gate(x))\n        x = x * g\n        x = x.transpose(1, 2)\n        x = self.dwconv(x)\n        x = x.transpose(1, 2)\n        x = self.proj(x)\n        x = self.drop(x)\n        return shortcut + x\n\nclass TwinStreamMetaNet(nn.Module):\n    def __init__(self, backbone_name=\"vit_huge_plus_patch16_dinov3.lvd1689m\", meta_dim=4):\n        super().__init__()\n        self.backbone = timm.create_model(backbone_name, pretrained=False, \n                                          num_classes=0, global_pool='', dynamic_img_size=True)\n        nf = self.backbone.num_features\n        \n        self.fusion = nn.Sequential(\n            GatedDWConvBlock(nf, kernel_size=5, dropout=0.1),\n            GatedDWConvBlock(nf, kernel_size=5, dropout=0.1) \n        )\n        \n        self.pool = nn.AdaptiveAvgPool1d(1)\n        \n        self.aux_head = nn.Sequential(\n            nn.Linear(nf, nf // 2), nn.SiLU(), nn.Linear(nf // 2, meta_dim)\n        )\n        \n        self.meta_encoder = nn.Sequential(\n            nn.Linear(meta_dim, 64), nn.SiLU(), nn.Linear(64, 64)\n        )\n        \n        self.consistency_gate = nn.Sequential(\n            nn.Linear(meta_dim, 32), nn.ReLU(), nn.Linear(32, 1), nn.Sigmoid() \n        )\n        \n        in_dim = nf + 64 \n        self.head_total = self._make_head(in_dim)\n        self.head_gdm = self._make_head(in_dim)\n        self.head_green = self._make_head(in_dim)\n        self.head_clover = self._make_head(in_dim)\n        self.head_dead = self._make_head(in_dim)\n\n    def _make_head(self, in_dim):\n        return nn.Sequential(\n            nn.Linear(in_dim, in_dim // 2), nn.GELU(), nn.Dropout(0.2),\n            nn.Linear(in_dim // 2, 1), nn.Softplus()\n        )\n        \n    def forward(self, left, right, meta=None):\n        l_feat = self.backbone(left)\n        r_feat = self.backbone(right)\n        x = torch.cat([l_feat, r_feat], dim=1) \n        x = self.fusion(x)\n        x = x.transpose(1, 2)\n        x = self.pool(x).flatten(1) \n\n        aux_meta_preds = self.aux_head(x)\n        if meta is None:\n            meta = aux_meta_preds\n\n        m_emb = self.meta_encoder(meta)\n        x = torch.cat([x, m_emb], dim=1)\n        gate_val = self.consistency_gate(meta) \n\n        raw_total = self.head_total(x)\n        raw_gdm = self.head_gdm(x)\n        raw_green = self.head_green(x)\n        raw_clover = self.head_clover(x)\n        raw_dead = self.head_dead(x)\n\n        derived_dead = F.relu(raw_total - raw_gdm)\n        final_dead = (gate_val * derived_dead) + ((1 - gate_val) * raw_dead)\n        \n        biomass_preds = torch.cat([raw_green, raw_clover, final_dead, raw_gdm, raw_total], dim=1)\n        return biomass_preds\n\n\ndef run_inference():\n    print(f\"Reading CSV from {TEST_CSV}...\")\n    if not os.path.exists(TEST_CSV):\n        raise FileNotFoundError(f\"Cannot find test.csv at {TEST_CSV}\")\n        \n    test_df = pd.read_csv(TEST_CSV)\n    \n    if 'image_path' in test_df.columns:\n        wide_df = test_df.drop_duplicates(subset=[\"image_path\"]).reset_index(drop=True)\n    else:\n        wide_df = test_df.copy()\n    \n    print(f\"Unique images to process: {len(wide_df)}\")\n\n    test_ds = TestBiomassDataset(wide_df, DATA_ROOT, IMG_SIZE)\n    test_loader = DataLoader(test_ds, batch_size=BATCH_SIZE, shuffle=False, \n                             num_workers=NUM_WORKERS, pin_memory=True)\n\n    print(f\"Initializing Model: {BACKBONE}...\")\n    model = TwinStreamMetaNet(backbone_name=BACKBONE).to(DEVICE)\n    \n    print(f\"Loading Weights from {MODEL_PATH}...\")\n    checkpoint = torch.load(MODEL_PATH, map_location=DEVICE)\n    \n    # Handle both plain state_dict and dict with keys\n    if isinstance(checkpoint, dict):\n        if \"state_dict\" in checkpoint:\n            state_dict = checkpoint[\"state_dict\"]\n        elif \"model\" in checkpoint:\n            state_dict = checkpoint[\"model\"]\n        else:\n            state_dict = checkpoint\n    else:\n        state_dict = checkpoint\n\n    # Clean keys if needed\n    clean_dict = {}\n    for k, v in state_dict.items():\n        name = k\n        if name.startswith(\"module.\"): name = name[7:]\n        clean_dict[name] = v\n        \n    model.load_state_dict(clean_dict, strict=True) \n    model.eval()\n    \n    accumulated_preds = np.zeros((len(wide_df), 5))\n    \n    print(\"Starting Inference with 4-way TTA...\")\n    \n    with torch.no_grad():\n        start_idx = 0\n        for left, right in tqdm(test_loader):\n            left = left.to(DEVICE)\n            right = right.to(DEVICE)\n            batch_size = left.size(0)\n            \n            # TTA Passes\n            p1 = model(left, right, meta=None)\n            p2 = model(torch.flip(left, [3]), torch.flip(right, [3]), meta=None)\n            p3 = model(torch.flip(left, [2]), torch.flip(right, [2]), meta=None)\n            p4 = model(torch.flip(left, [2, 3]), torch.flip(right, [2, 3]), meta=None)\n            \n            avg_p = (p1 + p2 + p3 + p4) / 4.0\n            \n            end_idx = start_idx + batch_size\n            accumulated_preds[start_idx:end_idx] = avg_p.cpu().numpy()\n            start_idx = end_idx\n\n    \n    # Enforcing Mass-Balance Constraints\n    # Extract Raw Components\n    dry_green  = accumulated_preds[:, 0]\n    dry_clover = accumulated_preds[:, 1]\n    dry_dead   = accumulated_preds[:, 2]\n    \n    # 1. Enforce: GDM = Green + Clover\n    consistent_gdm = dry_green + dry_clover\n    \n    # 2. Enforce: Total = GDM + Dead\n    consistent_total = consistent_gdm + dry_dead\n    \n    # Update the array with physically consistent values\n    accumulated_preds[:, 3] = consistent_gdm  # Overwrite GDM\n    accumulated_preds[:, 4] = consistent_total # Overwrite Total\n\n    print(\"Formatting Submission...\")\n    \n    wide_df['Dry_Green_g']  = accumulated_preds[:, 0]\n    wide_df['Dry_Clover_g'] = accumulated_preds[:, 1]\n    wide_df['Dry_Dead_g']   = accumulated_preds[:, 2]\n    wide_df['GDM_g']        = accumulated_preds[:, 3]\n    wide_df['Dry_Total_g']  = accumulated_preds[:, 4]\n\n    if 'sample_id' in wide_df.columns:\n         wide_df['sample_id_prefix'] = wide_df['sample_id'].str.split('__').str[0]\n    else:\n        wide_df['sample_id_prefix'] = wide_df['image_path'].apply(lambda x: os.path.splitext(os.path.basename(x))[0])\n\n    cols_to_stack = ['Dry_Green_g', 'Dry_Dead_g', 'Dry_Clover_g', 'GDM_g', 'Dry_Total_g']\n    \n    sub_df = wide_df.set_index('sample_id_prefix')[cols_to_stack].stack().reset_index()\n    sub_df.columns = ['sample_id_prefix', 'target_name', 'target']\n    \n    sub_df['sample_id'] = sub_df['sample_id_prefix'] + '__' + sub_df['target_name']\n\n    submission = sub_df[['sample_id', 'target']]\n    submission.to_csv(OUTPUT_PATH, index=False)\n    \n    print(f\"✅ Submission saved to {OUTPUT_PATH}\")\n    print(submission.head())\n    \n    \n    del model\n    del checkpoint\n    del test_loader\n    del test_ds\n    del accumulated_preds\n    try:\n        del left, right, p1, p2, p3, p4, avg_p\n    except NameError:\n        pass\n    torch.cuda.empty_cache()\n    import gc\n    gc.collect()\n\nif __name__ == \"__main__\":\n    run_inference()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-26T06:45:57.814547Z","iopub.execute_input":"2026-01-26T06:45:57.814863Z","iopub.status.idle":"2026-01-26T06:46:28.725096Z","shell.execute_reply.started":"2026-01-26T06:45:57.81484Z","shell.execute_reply":"2026-01-26T06:46:28.724259Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 2) TwinStreamMetaNet -> 0.74 LB (Public) & 0.2 Ensemble weight","metadata":{}},{"cell_type":"code","source":"import os\nimport cv2\nimport math\nimport warnings\nimport numpy as np\nimport pandas as pd\nfrom tqdm.auto import tqdm\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import Dataset, DataLoader\nimport timm\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\nwarnings.filterwarnings('ignore')\n\n\nDATA_ROOT = \"/kaggle/input/csiro-biomass\"\nTEST_CSV = \"/kaggle/input/csiro-biomass/test.csv\"\nMODELS_DIR = '/kaggle/input/dinov3-imp'\nOUTPUT_PATH = \"submission_72.csv\"\n\nDEVICE = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nBATCH_SIZE = 1\nIMG_SIZE = 512\nNUM_WORKERS = 0\nBACKBONE = \"vit_huge_plus_patch16_dinov3.lvd1689m\"\n\nTRAIN_STATS = {\n    \"ndvi_mean\": 0.657423, \"ndvi_std\": 0.151972,\n    \"h_mean\": 7.595985, \"h_std\": 10.273725\n}\n\n\nclass TestBiomassDataset(Dataset):\n    def __init__(self, df, data_root, img_size=512, stats=None):\n        self.df = df.reset_index(drop=True)\n        self.data_root = data_root\n        self.stats = stats if stats else TRAIN_STATS\n        \n        self.has_ndvi = \"Pre_GSHH_NDVI\" in self.df.columns\n        self.has_height = \"Height_Ave_cm\" in self.df.columns\n        self.has_date = \"Sampling_Date\" in self.df.columns\n        \n        if self.has_date:\n            self.df[\"Sampling_Date\"] = pd.to_datetime(self.df[\"Sampling_Date\"], errors='coerce')\n\n        target_h, target_w = img_size, img_size * 2\n        self.tfms = A.Compose([\n            A.Resize(height=target_h, width=target_w),\n            A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n            ToTensorV2()\n        ])\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row = self.df.loc[idx]\n        img_path = os.path.join(self.data_root, row[\"image_path\"])\n        \n        img = cv2.imread(img_path)\n        if img is None:\n            img = np.zeros((512, 1024, 3), dtype=np.uint8)\n        else:\n            img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n\n        augmented = self.tfms(image=img)\n        img_tensor = augmented[\"image\"]\n\n        _, h, w = img_tensor.shape\n        mid = w // 2\n        left = img_tensor[:, :, :mid]\n        right = img_tensor[:, :, mid:]\n\n        # Meta Features\n        ndvi, h_val, doy_sin, doy_cos = 0.0, 0.0, 0.0, 0.0\n        if self.has_ndvi and pd.notnull(row[\"Pre_GSHH_NDVI\"]):\n            ndvi = (row[\"Pre_GSHH_NDVI\"] - self.stats[\"ndvi_mean\"]) / (self.stats[\"ndvi_std\"] + 1e-6)\n        if self.has_height and pd.notnull(row[\"Height_Ave_cm\"]):\n            h_val = (row[\"Height_Ave_cm\"] - self.stats[\"h_mean\"]) / (self.stats[\"h_std\"] + 1e-6)\n        if self.has_date and pd.notnull(row[\"Sampling_Date\"]):\n            doy = row[\"Sampling_Date\"].dayofyear\n            doy_sin = math.sin(2 * math.pi * doy / 365)\n            doy_cos = math.cos(2 * math.pi * doy / 365)\n\n        meta = torch.tensor([ndvi, h_val, doy_sin, doy_cos], dtype=torch.float32)\n        return left, right, meta\n\n\nclass GatedDWConvBlock(nn.Module):\n    def __init__(self, dim: int, kernel_size: int = 5, dropout: float = 0.1):\n        super().__init__()\n        self.norm = nn.LayerNorm(dim)\n        self.dwconv = nn.Conv1d(dim, dim, kernel_size=kernel_size, padding=kernel_size // 2, groups=dim)\n        self.gate = nn.Linear(dim, dim)\n        self.proj = nn.Linear(dim, dim)\n        self.drop = nn.Dropout(dropout)\n\n    def forward(self, x: torch.Tensor) -> torch.Tensor:\n        shortcut = x\n        x = self.norm(x)\n        g = torch.sigmoid(self.gate(x))\n        x = x * g\n        x = x.transpose(1, 2)\n        x = self.dwconv(x)\n        x = x.transpose(1, 2)\n        x = self.proj(x)\n        x = self.drop(x)\n        return shortcut + x\n\nclass TwinStreamMetaNet(nn.Module):\n    def __init__(self, backbone_name=BACKBONE, meta_dim=4):\n        super().__init__()\n        self.backbone = timm.create_model(backbone_name, pretrained=False, \n                                          num_classes=0, global_pool='', dynamic_img_size=True)\n        nf = self.backbone.num_features\n        \n        self.fusion = nn.Sequential(\n            GatedDWConvBlock(nf, kernel_size=5, dropout=0.2),\n            GatedDWConvBlock(nf, kernel_size=5, dropout=0.2) \n        )\n        \n        self.pool = nn.AdaptiveAvgPool1d(1)\n        self.meta_encoder = nn.Sequential(\n            nn.Linear(meta_dim, 64), nn.SiLU(), nn.Linear(64, 64)\n        )\n        \n        self.consistency_gate = nn.Sequential(\n            nn.Linear(meta_dim, 32), nn.ReLU(), nn.Linear(32, 1), nn.Sigmoid() \n        )\n        \n        in_dim = nf + 64 \n        self.head_total = self._make_head(in_dim)\n        self.head_gdm = self._make_head(in_dim)\n        self.head_green = self._make_head(in_dim)\n        self.head_clover = self._make_head(in_dim)\n        self.head_dead = self._make_head(in_dim)\n\n    def _make_head(self, in_dim):\n        return nn.Sequential(\n            nn.Linear(in_dim, in_dim // 2), nn.GELU(), nn.Dropout(0.2),\n            nn.Linear(in_dim // 2, 1), nn.Softplus()\n        )\n        \n    def forward(self, left, right, meta):\n        l_feat = self.backbone(left)\n        r_feat = self.backbone(right)\n        x = torch.cat([l_feat, r_feat], dim=1) \n        x = self.fusion(x)\n        x = x.transpose(1, 2)\n        x = self.pool(x).flatten(1) \n\n        m_emb = self.meta_encoder(meta)\n        x = torch.cat([x, m_emb], dim=1)\n        gate_val = self.consistency_gate(meta) \n\n        raw_total = self.head_total(x)\n        raw_gdm = self.head_gdm(x)\n        raw_green = self.head_green(x)\n        raw_clover = self.head_clover(x)\n        raw_dead = self.head_dead(x)\n\n        derived_dead = F.relu(raw_total - raw_gdm)\n        final_dead = (gate_val * derived_dead) + ((1 - gate_val) * raw_dead)\n        \n        return torch.cat([raw_green, raw_clover, final_dead, raw_gdm, raw_total], dim=1)\n\ndef run_inference():\n    print(f\"Reading CSV from {TEST_CSV}...\")\n    test_df = pd.read_csv(TEST_CSV)\n    \n    meta_cols = ['Sampling_Date', 'Pre_GSHH_NDVI', 'Height_Ave_cm']\n    available_cols = [c for c in meta_cols if c in test_df.columns]\n    wide_df = test_df[['image_path'] + available_cols].drop_duplicates(\"image_path\").reset_index(drop=True)\n    \n    print(f\"Unique images to process: {len(wide_df)}\")\n\n    test_ds = TestBiomassDataset(wide_df, DATA_ROOT, IMG_SIZE)\n    test_loader = DataLoader(test_ds, batch_size=BATCH_SIZE, shuffle=False, \n                               num_workers=NUM_WORKERS, pin_memory=True)\n\n    # Load Ensemble Models\n    model_files = sorted([f for f in os.listdir(MODELS_DIR) if f.endswith(('.pt', '.pth'))])\n    if not model_files:\n        model_files = sorted([f for f in os.listdir('.') if f.endswith(('.pt', '.pth'))])\n    \n    print(f\"Initializing {len(model_files)} models for ensemble...\")\n    models = []\n    for m_file in model_files:\n        path = os.path.join(MODELS_DIR if os.path.exists(os.path.join(MODELS_DIR, m_file)) else '.', m_file)\n        model = TwinStreamMetaNet(backbone_name=BACKBONE).to(DEVICE)\n        checkpoint = torch.load(path, map_location=DEVICE)\n        model.load_state_dict(checkpoint[\"state_dict\"] if \"state_dict\" in checkpoint else checkpoint)\n        model.eval()\n        models.append(model)\n\n    accumulated_preds = np.zeros((len(wide_df), 5))\n    \n    print(\"Starting Inference (Ensemble + 4-way TTA)...\")\n    with torch.no_grad():\n        start_idx = 0\n        for left, right, meta in tqdm(test_loader):\n            left, right, meta = left.to(DEVICE), right.to(DEVICE), meta.to(DEVICE)\n            batch_size = left.size(0)\n            \n            ensemble_p = []\n            for model in models:\n                p1 = model(left, right, meta)\n                p2 = model(torch.flip(left, [3]), torch.flip(right, [3]), meta)\n                p3 = model(torch.flip(left, [2]), torch.flip(right, [2]), meta)\n                p4 = model(torch.flip(left, [2, 3]), torch.flip(right, [2, 3]), meta)\n                tta_avg = (p1 + p2 + p3 + p4) / 4.0\n                ensemble_p.append(tta_avg.cpu().numpy())\n            \n            avg_p = np.mean(ensemble_p, axis=0)\n            end_idx = start_idx + batch_size\n            accumulated_preds[start_idx:end_idx] = avg_p\n            start_idx = end_idx\n\n    # Final Mass-Balance Refinement\n    # Order: Green, Clover, Dead, GDM, Total\n    dry_green, dry_clover, dry_dead = accumulated_preds[:, 0], accumulated_preds[:, 1], accumulated_preds[:, 2]\n    consistent_gdm = dry_green + dry_clover\n    consistent_total = consistent_gdm + dry_dead\n    \n    accumulated_preds[:, 3] = consistent_gdm\n    accumulated_preds[:, 4] = consistent_total\n\n    results_map = {}\n    for i, path in enumerate(wide_df[\"image_path\"]):\n        results_map[path] = accumulated_preds[i]\n\n    print(\"Formatting Submission...\")\n    submission_rows = []\n    for _, row in test_df.iterrows():\n        img_path = row[\"image_path\"]\n        target_name = row[\"target_name\"]\n        \n        idx_map = {\"Dry_Green_g\": 0, \"Dry_Clover_g\": 1, \"Dry_Dead_g\": 2, \"GDM_g\": 3, \"Dry_Total_g\": 4}\n        \n        pred_val = 0.0\n        if img_path in results_map:\n            pred_val = results_map[img_path][idx_map[target_name]]\n            \n        submission_rows.append({\n            \"sample_id\": row[\"sample_id\"] if \"sample_id\" in row else f\"{img_path}_{target_name}\",\n            \"target\": max(0.0, float(pred_val))\n        })\n\n    sub_df = pd.DataFrame(submission_rows)\n    sub_df.to_csv(OUTPUT_PATH, index=False)\n    print(f\"✅ Submission saved to {OUTPUT_PATH}\")\n    del models\n    torch.cuda.empty_cache()\n    gc.collect()\n\nif __name__ == \"__main__\":\n    run_inference()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-26T06:46:28.727129Z","iopub.execute_input":"2026-01-26T06:46:28.727377Z","iopub.status.idle":"2026-01-26T06:47:04.043653Z","shell.execute_reply.started":"2026-01-26T06:46:28.727357Z","shell.execute_reply":"2026-01-26T06:47:04.043067Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Final Ensemble","metadata":{}},{"cell_type":"code","source":"import os\nimport pandas as pd\nimport numpy as np\nimport warnings\nwarnings.filterwarnings(\"ignore\")\n\nTEST_CSV = \"/kaggle/input/csiro-biomass/test.csv\"\nSUBMISSION_DIR = \".\"\nOUTPUT_NAME = \"submission.csv\"\n\nTARGET_COLS = [\n    \"Dry_Green_g\", \n    \"Dry_Clover_g\", \n    \"Dry_Dead_g\", \n    \"GDM_g\", \n    \"Dry_Total_g\"\n]\n\ndef apply_physical_constraints(df):\n    \"\"\"\n    Enforces the following biological mass-balance constraints:\n    1. All values >= 0\n    2. GDM = Green + Clover\n    3. Total = GDM + Dead\n    \"\"\"\n    for col in TARGET_COLS:\n        if col in df.columns:\n            df[col] = df[col].clip(lower=0.0)\n\n    df[\"GDM_g\"] = df[\"Dry_Green_g\"] + df[\"Dry_Clover_g\"]\n    df[\"Dry_Total_g\"] = df[\"GDM_g\"] + df[\"Dry_Dead_g\"]\n    \n    return df\n\ndef run_ensemble(files, weights, postprocess=True):\n    print(f\"Starting ensemble of {len(files)} files...\")\n\n    # 1. Normalize weights\n    weights = np.array(weights, dtype=float)\n    weights /= weights.sum()\n\n    # 2. Load and align submissions\n    subs = []\n    for f in files:\n        if not os.path.exists(f):\n            print(f\"Warning: {f} not found. Skipping.\")\n            continue\n        df = pd.read_csv(f).set_index(\"sample_id\")[\"target\"]\n        subs.append(df)\n\n    if not subs:\n        raise ValueError(\"No valid submission files found for ensembling.\")\n\n    # 3. Weighted Average\n    combined = pd.concat(subs, axis=1)\n    avg_vals = np.nan_to_num(combined.values) @ weights\n    \n    ensemble_df = pd.DataFrame({\n        \"sample_id\": combined.index,\n        \"target\": avg_vals\n    })\n\n    # 4. Physical Post-Processing\n    if postprocess:\n        print(\"Applying physical constraints...\")\n        tmp = ensemble_df.copy()\n        tmp[[\"image_id\", \"target_name\"]] = tmp[\"sample_id\"].str.rsplit(\"__\", n=1, expand=True)\n\n        wide = tmp.pivot(index=\"image_id\", columns=\"target_name\", values=\"target\").reset_index()\n        \n        for col in TARGET_COLS:\n            if col not in wide.columns:\n                wide[col] = 0.0\n\n        wide = apply_physical_constraints(wide)\n\n        long = wide.melt(id_vars=\"image_id\", value_vars=TARGET_COLS, \n                         var_name=\"target_name\", value_name=\"target\")\n        long[\"sample_id\"] = long[\"image_id\"] + \"__\" + long[\"target_name\"]\n        \n        ensemble_df = long.set_index(\"sample_id\").loc[combined.index].reset_index()\n\n    # 5. Final Alignment with Test CSV\n    if os.path.exists(TEST_CSV):\n        test_df = pd.read_csv(TEST_CSV)\n        if \"sample_id\" in test_df.columns:\n            ensemble_df = test_df[[\"sample_id\"]].merge(ensemble_df, on=\"sample_id\", how=\"left\")\n    \n    ensemble_df[\"target\"] = ensemble_df[\"target\"].fillna(0.0)\n    \n    # 6. Save\n    save_path = os.path.join(SUBMISSION_DIR, OUTPUT_NAME)\n    ensemble_df.to_csv(save_path, index=False)\n    \n    print(f\"✅ Ensemble complete. Saved to: {save_path}\")\n    return ensemble_df\n\nif __name__ == \"__main__\":\n    submission_files = [\n        \"submission_72.csv\",\n        \"submission_74.csv\",\n        \"submission_75.csv\",\n    ]\n    \n    model_weights = [0.2, 0.3, 0.5]\n\n    final_sub = run_ensemble(\n        files=submission_files,\n        weights=model_weights,\n        postprocess=True\n    )\n\n    print(\"\\nPreview of Final Submission:\")\n    print(final_sub.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-26T06:47:04.045231Z","iopub.execute_input":"2026-01-26T06:47:04.045588Z","iopub.status.idle":"2026-01-26T06:47:04.255389Z","shell.execute_reply.started":"2026-01-26T06:47:04.045566Z","shell.execute_reply":"2026-01-26T06:47:04.254448Z"}},"outputs":[],"execution_count":null}]}