{"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":"gpu","dataSources":[{"sourceId":112509,"databundleVersionId":14254895,"isSourceIdPinned":false,"sourceType":"competition"},{"sourceId":13887419,"sourceType":"datasetVersion","datasetId":8847701},{"sourceId":280982915,"sourceType":"kernelVersion"}],"dockerImageVersionId":31193,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport cv2\nfrom tqdm.auto import tqdm\n\nimport numpy as np\nimport pandas as pd\n\nimport torch\nimport torch.nn as nn\nimport timm\n\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\n\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-11-27T01:32:30.538476Z","iopub.execute_input":"2025-11-27T01:32:30.539226Z","iopub.status.idle":"2025-11-27T01:32:35.380167Z","shell.execute_reply.started":"2025-11-27T01:32:30.539182Z","shell.execute_reply":"2025-11-27T01:32:35.379305Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"$$ $$\n## ⚙️ Config","metadata":{}},{"cell_type":"code","source":"class Config:\n    # Data\n    data_dir = '/kaggle/input/csiro-biomass/test'\n\n    # Model\n    model_path = '/kaggle/input/eva02-biomass-regression/best_model_fold_4.pth'\n\n    # TTA\n    img_size = 448\n\n    # Inference\n    smooth_factor = 0.9050\n    dry_clover_min = 1.25\n    dry_dead_minimum = 1.00\n    \nconfig = Config()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-27T01:32:35.381422Z","iopub.execute_input":"2025-11-27T01:32:35.382023Z","iopub.status.idle":"2025-11-27T01:32:35.386265Z","shell.execute_reply.started":"2025-11-27T01:32:35.382001Z","shell.execute_reply":"2025-11-27T01:32:35.385629Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"$$ $$","metadata":{}},{"cell_type":"markdown","source":"## 🏭 Model Setup","metadata":{}},{"cell_type":"code","source":"class BiomassModel(nn.Module):\n        \n    def regression_head(self, name, in_features: int, dropout: float):\n        self.name = name\n        return nn.Sequential(\n            nn.Linear(in_features, in_features // 2),\n            nn.ReLU(),\n            nn.Dropout(dropout),\n            nn.Linear(in_features // 2, 1)\n        )\n    \n    def __init__(self, model_name, dropout = 0.0):\n        super().__init__()\n                \n        # Shared backbone\n        self.backbone = timm.create_model(\n            model_name,\n            pretrained=False,  \n            num_classes=0, \n            global_pool='avg'  \n        )\n        n_features = self.backbone.num_features\n        n_features *= 2\n        \n        self.head_total = self.regression_head('dry_total', n_features, dropout)\n        self.head_gdm = self.regression_head('dry_gdm', n_features, dropout)\n        self.head_green = self.regression_head('dry_green', n_features, dropout)\n\n    \n    def forward(self, img_left, img_right):\n\n        # Extract image features\n        fl = self.backbone(img_left)\n        fr = self.backbone(img_right)\n        img_feat = torch.cat([fl, fr], dim=1)\n        \n        dry_total = self.head_total(img_feat)\n        gdm = self.head_gdm(img_feat)\n        dry_green = self.head_green(img_feat)\n        \n        return dry_total, gdm, dry_green\n\n\n\n\nckpt = torch.load(config.model_path, map_location = device)\n\nmodel_name = ckpt['model_name']\nval_score = ckpt['val_r2_score']\nstate_dict = ckpt['model_state_dict']\n\n# Create Model + Load Weights\neva_model = BiomassModel(model_name = model_name)\neva_model.load_state_dict(state_dict)\neva_model.eval()\neva_model.to(device)\n\n\nnum_params = sum(p.numel() for p in eva_model.parameters())\nprint(f\"✅ Loaded model with r² val_score: {val_score:.5f} and N° of params: {num_params}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-27T01:32:35.387040Z","iopub.execute_input":"2025-11-27T01:32:35.387330Z","iopub.status.idle":"2025-11-27T01:32:37.508389Z","shell.execute_reply.started":"2025-11-27T01:32:35.387303Z","shell.execute_reply":"2025-11-27T01:32:37.507809Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"$$ $$\n## 🔮 Inference","metadata":{}},{"cell_type":"code","source":"def get_tta_batch(image_path, tta_transforms):\n    image = cv2.imread(image_path)\n    image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n    \n    height, width, _ = image.shape\n    mid_point = width // 2\n    iml_np, imr_np = image[:, :mid_point], image[:, mid_point:]\n        \n    iml, imr = [], []\n    for transform in tta_transforms:\n        iml.append(transform(image=iml_np)['image'])\n        imr.append(transform(image=imr_np)['image'])\n\n    iml = torch.stack(iml, dim=0).to(device) # (6, 3 ,img_size, img_size)\n    imr = torch.stack(imr, dim=0).to(device)\n    return iml, imr\n\ndef get_tta_transformations(img_size = 448):\n    transform = [\n        A.Resize(img_size, img_size),\n        A.Normalize(mean=[0.485, 0.456, 0.406], \n                    std=[0.229, 0.224, 0.225]),\n        ToTensorV2()\n    ]\n    \n    tta_transforms = [\n        A.Compose([*transform]),\n        A.Compose([A.HorizontalFlip(p=1.0), *transform]),\n        A.Compose([A.VerticalFlip(p=1.0), *transform]),\n        A.Compose([A.Rotate(90, p=1.0), *transform]),\n        A.Compose([A.Rotate(180, p=1.0), *transform]),\n        A.Compose([A.Rotate(270, p=1.0), *transform]),\n    ]\n    return tta_transforms","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-27T01:32:37.509765Z","iopub.execute_input":"2025-11-27T01:32:37.509989Z","iopub.status.idle":"2025-11-27T01:32:37.516858Z","shell.execute_reply.started":"2025-11-27T01:32:37.509973Z","shell.execute_reply":"2025-11-27T01:32:37.516147Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"image_paths = [os.path.join(config.data_dir, f) for f in os.listdir(config.data_dir)]\ntta_transforms = get_tta_transformations(img_size = config.img_size)\n\nsubmission = []\nfor image_path in tqdm(image_paths):\n    iml, imr = get_tta_batch(image_path, tta_transforms)\n    \n    with torch.inference_mode():\n        dry_total, gdm, dry_green = eva_model(iml, imr)\n\n    preds = torch.cat([dry_total, gdm, dry_green], dim=1).cpu().numpy() # (num_transforms , 3)\n    preds = np.maximum(preds, 0)\n    preds = preds.mean(axis=0)\n    \n    dry_total, gdm, dry_green = preds  \n    dry_clover = gdm - dry_green\n    dry_dead = dry_total - gdm\n\n    dry_clover = np.clip(gdm - dry_green, 0, config.smooth_factor * gdm)\n    dry_dead = np.clip(dry_total - gdm, 0, config.smooth_factor * dry_total)\n    \n    # If clover is very small (< 1.25g), set to 0\n    dry_clover = np.where(dry_clover < config.dry_clover_min, 0, dry_clover)\n    # If dead is very small (< 1g), set to 0\n    dry_dead = np.where(dry_dead < config.dry_dead_minimum, 0, dry_dead)\n\n    # Add predictions\n    image_id = os.path.splitext(os.path.basename(image_path))[0] + '__'\n    submission.extend([\n        {'sample_id': image_id + 'Dry_Clover_g', 'target': dry_clover},\n        {'sample_id': image_id + 'Dry_Dead_g',   'target': dry_dead},\n        {'sample_id': image_id + 'Dry_Green_g',  'target': dry_green},\n        {'sample_id': image_id + 'Dry_Total_g',  'target': dry_total},\n        {'sample_id': image_id + 'GDM_g',        'target': gdm},\n    ])\n\n\nsubmission = pd.DataFrame(submission)\nsubmission.to_csv('submission.csv', index=False)\ndisplay(submission)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-27T01:32:45.063282Z","iopub.execute_input":"2025-11-27T01:32:45.063584Z","iopub.status.idle":"2025-11-27T01:32:45.871209Z","shell.execute_reply.started":"2025-11-27T01:32:45.063562Z","shell.execute_reply":"2025-11-27T01:32:45.870554Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from PIL import Image\n\nImage.open('/kaggle/input/csiro-biomass/test/ID1001187975.jpg')","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}