{"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":14606535,"sourceType":"datasetVersion","datasetId":9275153}],"dockerImageVersionId":31259,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Submission code\n","metadata":{}},{"cell_type":"markdown","source":"## Import Packages","metadata":{}},{"cell_type":"code","source":"import os\nimport pandas as pd\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\nfrom PIL import Image\nfrom tqdm import tqdm","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-24T19:42:05.510095Z","iopub.execute_input":"2026-01-24T19:42:05.510571Z","iopub.status.idle":"2026-01-24T19:42:12.422508Z","shell.execute_reply.started":"2026-01-24T19:42:05.510541Z","shell.execute_reply":"2026-01-24T19:42:12.421696Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Prepare Dataset","metadata":{}},{"cell_type":"code","source":"DATASET_PATH = \"/kaggle/input/csiro-biomass\"\ntest_csv = f\"{DATASET_PATH}/test.csv\"\n\nckpt_path = \"/kaggle/input/dinov2/best_model.pth\"\n\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\nimg_size = 224\ngrid = 4\nbatch_size = 2\n\ntargets = [\n    \"Dry_Green_g\",\n    \"Dry_Dead_g\",\n    \"Dry_Clover_g\",\n    \"GDM_g\",\n    \"Dry_Total_g\"\n]\n\n\ntfm = transforms.Compose([\n    transforms.Resize((img_size, img_size)),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])\n])\n\nclass TestDataset(Dataset):\n    def __init__(self, df):\n        self.df = df\n        self.images = df.groupby(\"image_path\").first().reset_index()\n\n\n    def __len__(self):\n        return len(self.images)\n\n    def split_tiles(self, img):\n        w, h = img.size\n        tiles = []\n        for i in range(grid):\n            for j in range(grid):\n                crop = img.crop((\n                    j*w//grid,\n                    i*h//grid,\n                    (j+1)*w//grid,\n                    (i+1)*h//grid\n                ))\n                tiles.append(tfm(crop))\n        return torch.stack(tiles)\n\n    def __getitem__(self, idx):\n        row = self.images.iloc[idx]\n        path = row.image_path\n\n        img = Image.open(os.path.join(DATASET_PATH, path)).convert(\"RGB\")\n        w, h = img.size\n\n        l = self.split_tiles(img.crop((0,0,w//2,h)))\n        r = self.split_tiles(img.crop((w//2,0,w,h)))\n\n        return path, l, r","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-24T19:42:12.423725Z","iopub.execute_input":"2026-01-24T19:42:12.424082Z","iopub.status.idle":"2026-01-24T19:42:12.484781Z","shell.execute_reply.started":"2026-01-24T19:42:12.424058Z","shell.execute_reply":"2026-01-24T19:42:12.484048Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Prepare Model","metadata":{}},{"cell_type":"code","source":"class DinoV2(nn.Module):\n    def __init__(self):\n        super().__init__()\n\n        # official dinov2 repo, we load manually because no internet connection\n        dino_repo = \"/kaggle/input/dinov2/dinov2-main/dinov2-main\"\n        #pretrained_dino = \"/kaggle/input/dinov2/dinov2_vitb14_pretrain.pth\"\n\n        \n        self.model = torch.hub.load(\n            repo_or_dir=dino_repo,\n            model=\"dinov2_vitl14_reg\",\n            source=\"local\",\n            pretrained=False \n        )\n        \n\n        state = torch.load(\"/kaggle/input/dinov2/dinov2_vitl14_reg4_pretrain.pth\", map_location=\"cpu\")\n        self.model.load_state_dict(state, strict=True)\n\n        self.dim = 1024\n\n        for p in self.model.parameters():\n            p.requires_grad = False\n\n    def forward(self, x):\n        return self.model(x)\n\n\nclass StereoDINORegression(nn.Module):\n    def __init__(self):\n        super().__init__()\n        self.backbone = DinoV2()\n        D = self.backbone.dim\n\n        self.attn = nn.MultiheadAttention(D, 16, batch_first=True)\n        self.norm = nn.LayerNorm(D)\n\n\n        self.head = nn.Sequential(\n            nn.Linear(D*2,512),\n            nn.ReLU(),\n            nn.Linear(512,5)\n        )\n\n    def encode(self, tiles):\n        B,T,C,H,W = tiles.shape\n        x = tiles.view(B*T,C,H,W)\n        f = self.backbone(x).view(B,T,-1)\n        a,_ = self.attn(f,f,f)\n        return self.norm(f+a).mean(1)\n\n    def forward(self,l,r):\n        lf = self.encode(l)\n        rf = self.encode(r)\n        return self.head(torch.cat([lf,rf],1))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-24T19:42:12.485716Z","iopub.execute_input":"2026-01-24T19:42:12.486021Z","iopub.status.idle":"2026-01-24T19:42:12.508893Z","shell.execute_reply.started":"2026-01-24T19:42:12.485982Z","shell.execute_reply":"2026-01-24T19:42:12.508181Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Submission Code","metadata":{}},{"cell_type":"code","source":"# FINAL SUBMISSION CODE\nfrom torch.cuda.amp import autocast\nimport torch.nn.functional as F\nimport numpy as np\n\ncheckpoint_paths = [\n    \"/kaggle/input/dinov2/best_model.pth\",\n]\n\nmodels = []\nfor ckpt_path in checkpoint_paths:\n    ckpt = torch.load(ckpt_path, map_location=device)\n    model = StereoDINORegression().to(device)\n    model.load_state_dict(ckpt[\"model_state\"])\n    model.eval()\n    models.append(model)\n\ndef comprehensive_predict(models, l, r):\n    all_preds = []\n    \n    for model in models:\n        preds_list = []\n        \n        with torch.no_grad():\n            preds_list.append(model(l, r))\n            preds_list.append(model(torch.flip(l, [4]), torch.flip(r, [4])))\n            preds_list.append(model(torch.flip(l, [3]), torch.flip(r, [3])))\n            preds_list.append(model(torch.flip(l, [3,4]), torch.flip(r, [3,4])))\n         \n        \n        all_preds.append(torch.stack(preds_list).mean(0))\n    \n    return torch.stack(all_preds).mean(0)\n\ntest_df = pd.read_csv(test_csv)\ndataset = TestDataset(test_df)\nloader = DataLoader(dataset, batch_size=batch_size, shuffle=False)\n\nrows = []\nwith torch.no_grad():\n    for paths, l, r in tqdm(loader):\n        l = l.to(device)\n        r = r.to(device)\n        \n        preds = comprehensive_predict(models, l, r).cpu().numpy()\n        \n        preds = np.maximum(preds, 0)  # Non-negative\n        \n        for i, path in enumerate(paths):\n            img_id = os.path.splitext(os.path.basename(path))[0]\n            for t_idx, tname in enumerate(targets):\n                rows.append({\n                    \"sample_id\": f\"{img_id}__{tname}\",\n                    \"target\": float(preds[i, t_idx])\n                })\n\nsub = pd.DataFrame(rows)\nsub.to_csv(\"submission.csv\", index=False)\nprint(\"submission.csv saved with TTA + Ensemble\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-24T19:47:04.188532Z","iopub.execute_input":"2026-01-24T19:47:04.188922Z","iopub.status.idle":"2026-01-24T19:47:13.571262Z","shell.execute_reply.started":"2026-01-24T19:47:04.188893Z","shell.execute_reply":"2026-01-24T19:47:13.570648Z"}},"outputs":[],"execution_count":null}]}