{"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":292645923,"sourceType":"kernelVersion"}],"dockerImageVersionId":31260,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport sys\nimport gc\nimport pickle\nimport numpy as np\nimport pandas as pd\nimport cv2\nimport timm\nimport xgboost as xgb\nfrom tqdm.auto import tqdm\nimport torch\nfrom torch.utils.data import Dataset, DataLoader\nimport torchvision.transforms as T\nfrom PIL import Image\n\n# ====================================================\n# CONFIGURATION\n# ====================================================\nclass Config:\n    img_size = 518\n    model_name = 'vit_large_patch14_dinov2' \n    batch_size = 8\n    num_workers = 2\n    \n    # Paths\n    artifacts_dir = '/kaggle/input/image2biomass-g/training_artifacts' \n    data_root = '/kaggle/input/csiro-biomass'\n    test_csv_path = os.path.join(data_root, 'test.csv')\n    \n    if not os.path.exists(artifacts_dir):\n        if os.path.exists('/kaggle/input/image2biomass-g'):\n            artifacts_dir = '/kaggle/input/image2biomass-g'\n        else:\n            artifacts_dir = 'training_artifacts'\n\n    target_cols = ['Dry_Green_g', 'Dry_Dead_g', 'Dry_Clover_g', 'GDM_g', 'Dry_Total_g']\n    height_col = 'Height_Ave_cm'\n    ndvi_col = 'Pre_GSHH_NDVI'\n    n_folds = 5\n\n# ====================================================\n# UTILITIES\n# ====================================================\ndef remove_artifacts(image_np):\n    hsv = cv2.cvtColor(image_np, cv2.COLOR_RGB2HSV)\n    lower_orange = np.array([0, 150, 150])\n    upper_orange = np.array([25, 255, 255])\n    mask = cv2.inRange(hsv, lower_orange, upper_orange)\n    mask = cv2.dilate(mask, np.ones((3,3), np.uint8), iterations=2)\n    return cv2.inpaint(image_np, mask, 3, cv2.INPAINT_TELEA)\n\ndef get_color_stats(image_np):\n    \"\"\"\n    Extracts simple color statistics. Matches Training.\n    \"\"\"\n    # RGB Stats\n    r_mean, r_std = np.mean(image_np[:,:,0]), np.std(image_np[:,:,0])\n    g_mean, g_std = np.mean(image_np[:,:,1]), np.std(image_np[:,:,1])\n    b_mean, b_std = np.mean(image_np[:,:,2]), np.std(image_np[:,:,2])\n    \n    # HSV Stats\n    hsv = cv2.cvtColor(image_np, cv2.COLOR_RGB2HSV)\n    h_mean, h_std = np.mean(hsv[:,:,0]), np.std(hsv[:,:,0])\n    s_mean, s_std = np.mean(hsv[:,:,1]), np.std(hsv[:,:,1])\n    v_mean, v_std = np.mean(hsv[:,:,2]), np.std(hsv[:,:,2])\n    \n    stats = np.array([\n        r_mean, r_std, g_mean, g_std, b_mean, b_std,\n        h_mean, h_std, s_mean, s_std, v_mean, v_std\n    ]) / 255.0\n    \n    return stats\n\nclass BiomassDataset(Dataset):\n    def __init__(self, df, data_root, transforms=None, do_flip=False):\n        self.df = df\n        self.data_root = data_root\n        self.transforms = transforms\n        self.do_flip = do_flip\n        self.image_paths = [os.path.join(data_root, p) for p in df['image_path']]\n        \n    def __len__(self):\n        return len(self.df)\n    \n    def __getitem__(self, idx):\n        img_path = self.image_paths[idx]\n        try:\n            image_cv = cv2.imread(img_path)\n            image_cv = cv2.cvtColor(image_cv, cv2.COLOR_BGR2RGB)\n            image_cv = remove_artifacts(image_cv)\n            if self.do_flip:\n                image_cv = cv2.flip(image_cv, 1)\n            \n            # Extract Color Stats\n            color_stats = get_color_stats(image_cv)\n            \n            image = Image.fromarray(image_cv)\n        except:\n            image = Image.new('RGB', (Config.img_size, Config.img_size))\n            color_stats = np.zeros(12, dtype=np.float32)\n\n        w, h = image.size\n        left_img = image.crop((0, 0, w // 2, h))\n        right_img = image.crop((w // 2, 0, w, h))\n        \n        if self.transforms:\n            left_t = self.transforms(left_img)\n            right_t = self.transforms(right_img)\n        images = torch.stack([left_t, right_t])\n        \n        height = self.df.iloc[idx].get(Config.height_col, 0.0)\n        ndvi = self.df.iloc[idx].get(Config.ndvi_col, 0.0)\n        meta = torch.tensor([height / 30.0, ndvi], dtype=torch.float32)\n        \n        return images, torch.tensor(color_stats, dtype=torch.float32), meta\n\ndef get_transforms():\n    return T.Compose([\n        T.Resize((Config.img_size, Config.img_size)),\n        T.ToTensor(),\n        T.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n    ])\n\nclass FeatureExtractor(torch.nn.Module):\n    def __init__(self, model_name, weights_path):\n        super().__init__()\n        self.backbone = timm.create_model(model_name, pretrained=False, num_classes=0, global_pool='')\n        print(f\"Loading weights from {weights_path}\")\n        state_dict = torch.load(weights_path, map_location='cpu')\n        self.load_state_dict(state_dict)\n        self.backbone.eval()\n    def forward(self, x):\n        b, t, c, h, w = x.shape\n        x = x.view(b * t, c, h, w)\n        with torch.no_grad():\n            features = self.backbone(x)\n            features = features.mean(dim=1)\n        features = features.view(b, t, -1)\n        features = torch.mean(features, dim=1)\n        return features\n\ndef extract_features(df, model, device, do_flip=False):\n    dataset = BiomassDataset(df, Config.data_root, transforms=get_transforms(), do_flip=do_flip)\n    safe_workers = 0 if len(dataset) < 16 else Config.num_workers\n    safe_pin = True if safe_workers > 0 else False\n    loader = DataLoader(dataset, batch_size=Config.batch_size, shuffle=False, num_workers=safe_workers, pin_memory=safe_pin)\n    \n    all_feats, all_colors, all_meta = [], [], []\n    pbar_desc = \"Extracting Flipped...\" if do_flip else \"Extracting Original...\"\n    \n    for images, colors, meta in tqdm(loader, desc=pbar_desc):\n        images = images.to(device)\n        with torch.amp.autocast('cuda'):\n            feats = model(images)\n        all_feats.append(feats.float().cpu().numpy())\n        all_colors.append(colors.numpy())\n        all_meta.append(meta.numpy())\n        \n    return np.vstack(all_feats), np.vstack(all_colors), np.vstack(all_meta)\n\ndef run_inference():\n    print(\"--- CSIRO INFERENCE (Color-Enhanced + Optimized Weights) ---\")\n    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n    \n    test_csv = pd.read_csv(Config.test_csv_path)\n    unique_test_images = test_csv[['image_path']].drop_duplicates().reset_index(drop=True)\n    print(f\"Test Images: {len(unique_test_images)}\")\n    \n    # Impute Metadata\n    stats_path = os.path.join(Config.artifacts_dir, 'meta_stats.pkl')\n    if os.path.exists(stats_path):\n        with open(stats_path, 'rb') as f: stats = pickle.load(f)\n        h_mean, n_mean = stats['height_mean'], stats['ndvi_mean']\n    else:\n        h_mean, n_mean = 0.5, 0.5\n        \n    if Config.height_col not in unique_test_images.columns:\n        unique_test_images[Config.height_col] = h_mean * 30.0\n    if Config.ndvi_col not in unique_test_images.columns:\n        unique_test_images[Config.ndvi_col] = n_mean\n        \n    # Extract Features\n    weights_path = os.path.join(Config.artifacts_dir, 'backbone_weights.pth')\n    model = FeatureExtractor(Config.model_name, weights_path)\n    model.to(device)\n    \n    print(\"\\nExtracting Features (TTA)...\")\n    X_dino_o, C_o, M_o = extract_features(unique_test_images, model, device, do_flip=False)\n    X_dino_f, C_f, M_f = extract_features(unique_test_images, model, device, do_flip=True)\n    \n    del model\n    torch.cuda.empty_cache()\n    gc.collect()\n    \n    # Concatenate Features: [DINO, Color, Meta]\n    X_test_o = np.hstack([X_dino_o, C_o, M_o])\n    X_test_f = np.hstack([X_dino_f, C_f, M_f])\n    \n    avg_preds = np.zeros((len(unique_test_images), 5))\n    \n    print(\"\\nRunning Prediction...\")\n    for fold in range(Config.n_folds):\n        fold_dir = os.path.join(Config.artifacts_dir, f'fold_{fold}')\n        fold_preds = []\n        \n        for t_idx in range(5):\n            model_xgb = xgb.XGBRegressor()\n            model_path = os.path.join(fold_dir, f'xgb_target_{t_idx}.json')\n            \n            if os.path.exists(model_path):\n                model_xgb.load_model(model_path)\n                pred_o = model_xgb.predict(X_test_o)\n                pred_f = model_xgb.predict(X_test_f)\n                fold_preds.append((pred_o + pred_f) / 2.0)\n            else:\n                fold_preds.append(np.zeros(len(X_test_o)))\n                \n        fold_preds = np.column_stack(fold_preds)\n        fold_preds = np.clip(fold_preds, 0, None)\n        \n        # --- FOLD-LEVEL POST-PROCESSING ---\n        # Apply OPTIMIZED weights: 0.736 / 0.264\n        pred_total_direct = fold_preds[:, 4]\n        pred_total_sum = fold_preds[:, 0] + fold_preds[:, 1] + fold_preds[:, 2]\n        \n        fold_preds[:, 4] = 0.736 * pred_total_direct + 0.264 * pred_total_sum\n        \n        avg_preds += fold_preds\n            \n    avg_preds /= Config.n_folds\n    \n    # Final Output Formatting\n    pred_map = {}\n    for i, row in unique_test_images.iterrows():\n        path = row['image_path']\n        pred_map[path] = avg_preds[i]\n        \n    submission_rows = []\n    req_df = pd.read_csv(Config.test_csv_path)\n    target_indices = {'Dry_Green_g': 0, 'Dry_Dead_g': 1, 'Dry_Clover_g': 2, 'GDM_g': 3, 'Dry_Total_g': 4}\n    \n    for i, row in req_df.iterrows():\n        path = row['image_path']\n        t_name = row['target_name']\n        s_id = row['sample_id']\n        t_idx = target_indices.get(t_name)\n        val = pred_map.get(path, np.zeros(5))[t_idx] if path in pred_map else 0.0\n        submission_rows.append({'sample_id': s_id, 'target': val})\n            \n    submission = pd.DataFrame(submission_rows)\n    submission.to_csv('submission.csv', index=False)\n    print(f\"Submission saved with {len(submission)} rows!\")\n\nif __name__ == \"__main__\":\n    run_inference()","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-01-19T03:01:02.498784Z","iopub.execute_input":"2026-01-19T03:01:02.499044Z","iopub.status.idle":"2026-01-19T03:01:30.157801Z","shell.execute_reply.started":"2026-01-19T03:01:02.499015Z","shell.execute_reply":"2026-01-19T03:01:30.156993Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}