{"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":"nvidiaTeslaT4","dataSources":[{"sourceId":112509,"databundleVersionId":14254895,"isSourceIdPinned":false,"sourceType":"competition"},{"sourceId":641460,"sourceType":"modelInstanceVersion","isSourceIdPinned":false,"modelInstanceId":483748,"modelId":499259}],"dockerImageVersionId":31193,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\nimport torchvision.transforms as transforms\nfrom torchvision.models import efficientnet_b4\nimport cv2\nimport os\nfrom tqdm import tqdm\nimport warnings\nwarnings.filterwarnings('ignore')\n\n# Import LabelEncoder for safe globals\nfrom sklearn.preprocessing import LabelEncoder\n\n# Inference Configuration\nclass InferenceConfig:\n    image_size = (500, 250)\n    batch_size = 32\n    num_workers = 4\n    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n    \ninfer_config = InferenceConfig()\n\n# Test Dataset\nclass CSIROTestDataset(Dataset):\n    def __init__(self, df, image_dir, transform=None):\n        self.df = df\n        self.image_dir = image_dir\n        self.transform = transform\n        # Get unique images to avoid duplicate processing\n        self.unique_images = self.df['image_path'].unique()\n        self.image_to_idx = {img: idx for idx, img in enumerate(self.unique_images)}\n        \n    def __len__(self):\n        return len(self.unique_images)\n    \n    def __getitem__(self, idx):\n        image_path = self.unique_images[idx]\n        full_path = os.path.join(self.image_dir, image_path)\n        \n        # Load image\n        image = cv2.imread(full_path)\n        if image is None:\n            print(f\"Warning: Could not load image {full_path}\")\n            # Create blank image as fallback\n            image = np.zeros((250, 500, 3), dtype=np.uint8)\n        else:\n            image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n        \n        if self.transform:\n            image = self.transform(image)\n            \n        return image, image_path\n\n# Model for inference\nclass CSIROInferenceModel(nn.Module):\n    def __init__(self, num_states=6, num_species=20):  # Default values\n        super(CSIROInferenceModel, self).__init__()\n        \n        self.backbone = efficientnet_b4(pretrained=False)\n        in_features = self.backbone.classifier[1].in_features\n        self.backbone.classifier = nn.Identity()\n        \n        # Regression heads for inference\n        self.ndvi_head = nn.Sequential(\n            nn.Linear(in_features, 512),\n            nn.ReLU(),\n            nn.Dropout(0.1),\n            nn.Linear(512, 1)\n        )\n        \n        self.height_head = nn.Sequential(\n            nn.Linear(in_features, 512),\n            nn.ReLU(),\n            nn.Dropout(0.1),\n            nn.Linear(512, 1)\n        )\n        \n        self.clover_head = nn.Sequential(\n            nn.Linear(in_features, 512),\n            nn.ReLU(),\n            nn.Dropout(0.1),\n            nn.Linear(512, 1)\n        )\n        \n        self.dead_head = nn.Sequential(\n            nn.Linear(in_features, 512),\n            nn.ReLU(),\n            nn.Dropout(0.1),\n            nn.Linear(512, 1)\n        )\n        \n        self.green_head = nn.Sequential(\n            nn.Linear(in_features, 512),\n            nn.ReLU(),\n            nn.Dropout(0.1),\n            nn.Linear(512, 1)\n        )\n        \n    def forward(self, x):\n        features = self.backbone(x)\n        \n        outputs = {\n            'ndvi': self.ndvi_head(features).squeeze(-1),\n            'height': self.height_head(features).squeeze(-1),\n            'clover': self.clover_head(features).squeeze(-1),\n            'dead': self.dead_head(features).squeeze(-1),\n            'green': self.green_head(features).squeeze(-1)\n        }\n        \n        return outputs\n\ndef load_model_simple(model_path, device):\n    \"\"\"Simple model loading that ignores encoders\"\"\"\n    # Load without weights_only restriction\n    checkpoint = torch.load(model_path, map_location=device, weights_only=False)\n    \n    # Create model with default parameters\n    model = CSIROInferenceModel().to(device)\n    \n    # Extract state dict and remove module prefix if present\n    state_dict = checkpoint['model_state_dict']\n    new_state_dict = {}\n    \n    for key, value in state_dict.items():\n        # Remove 'module.' prefix if present\n        new_key = key.replace('module.', '')\n        \n        # Only keep weights for regression heads (ignore classification heads)\n        if any(head in new_key for head in ['ndvi_head', 'height_head', 'clover_head', 'dead_head', 'green_head', 'backbone']):\n            new_state_dict[new_key] = value\n    \n    # Load compatible weights\n    model.load_state_dict(new_state_dict, strict=False)\n    model.eval()\n    \n    return model\n\ndef run_inference():\n    print(\"Loading test data...\")\n    # Load test data\n    test_df = pd.read_csv('/kaggle/input/csiro-biomass/test.csv')\n    print(f\"Test data shape: {test_df.shape}\")\n    print(\"Test data sample:\")\n    print(test_df.head())\n    \n    # Transform for test\n    test_transform = transforms.Compose([\n        transforms.ToPILImage(),\n        transforms.Resize(infer_config.image_size[::-1]),  # (height, width)\n        transforms.ToTensor(),\n        transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n    ])\n    \n    # Create test dataset\n    test_dataset = CSIROTestDataset(test_df, '/kaggle/input/csiro-biomass/', test_transform)\n    test_loader = DataLoader(test_dataset, batch_size=infer_config.batch_size, \n                           shuffle=False, num_workers=infer_config.num_workers)\n    \n    print(f\"Number of unique test images: {len(test_dataset)}\")\n    \n    # Define model paths\n    model_paths = [\n        '/kaggle/input/csiro-stratified-kfold/pytorch/default/1/best_model_fold_0.pth',\n        '/kaggle/input/csiro-stratified-kfold/pytorch/default/1/best_model_fold_1.pth',\n        '/kaggle/input/csiro-stratified-kfold/pytorch/default/1/best_model_fold_2.pth',\n        '/kaggle/input/csiro-stratified-kfold/pytorch/default/1/best_model_fold_3.pth',\n        '/kaggle/input/csiro-stratified-kfold/pytorch/default/1/best_model_fold_4.pth'\n    ]\n    \n    models = []\n    \n    # Load models using the simple approach\n    for i, model_path in enumerate(model_paths):\n        if os.path.exists(model_path):\n            print(f\"Loading model {i+1}: {model_path}\")\n            try:\n                model = load_model_simple(model_path, infer_config.device)\n                models.append(model)\n                print(f\"✓ Successfully loaded model {i+1}\")\n            except Exception as e:\n                print(f\"✗ Failed to load model {i+1}: {e}\")\n        else:\n            print(f\"Warning: Model not found at {model_path}\")\n    \n    if not models:\n        raise ValueError(\"No trained models could be loaded for inference!\")\n    \n    print(f\"Successfully loaded {len(models)} models for ensemble inference\")\n    \n    # Inference\n    predictions_dict = {}\n    \n    print(\"Running inference...\")\n    with torch.no_grad():\n        for images, image_paths in tqdm(test_loader, desc=\"Inference\"):\n            images = images.to(infer_config.device)\n            \n            # Ensemble predictions\n            batch_predictions = {\n                'ndvi': torch.zeros(len(images), device=infer_config.device),\n                'height': torch.zeros(len(images), device=infer_config.device),\n                'clover': torch.zeros(len(images), device=infer_config.device),\n                'dead': torch.zeros(len(images), device=infer_config.device),\n                'green': torch.zeros(len(images), device=infer_config.device)\n            }\n            \n            for model in models:\n                outputs = model(images)\n                for key in batch_predictions:\n                    batch_predictions[key] += outputs[key]\n            \n            # Average predictions\n            for key in batch_predictions:\n                batch_predictions[key] /= len(models)\n            \n            # Store predictions\n            for i, image_path in enumerate(image_paths):\n                predictions_dict[image_path] = {\n                    'Pre_GSHH_NDVI': max(0.0, batch_predictions['ndvi'][i].item()),\n                    'Height_Ave_cm': max(0.0, batch_predictions['height'][i].item()),\n                    'Dry_Clover_g': max(0.0, batch_predictions['clover'][i].item()),\n                    'Dry_Dead_g': max(0.0, batch_predictions['dead'][i].item()),\n                    'Dry_Green_g': max(0.0, batch_predictions['green'][i].item())\n                }\n    \n    print(f\"Predictions generated for {len(predictions_dict)} unique images\")\n    \n    # Create submission file\n    submission_data = []\n    \n    print(\"Creating submission file...\")\n    for _, row in tqdm(test_df.iterrows(), total=len(test_df), desc=\"Creating submission\"):\n        image_path = row['image_path']\n        target_name = row['target_name']\n        sample_id = row['sample_id']\n        \n        if image_path in predictions_dict:\n            preds = predictions_dict[image_path]\n            \n            if target_name == 'Pre_GSHH_NDVI':\n                value = preds['Pre_GSHH_NDVI']\n            elif target_name == 'Height_Ave_cm':\n                value = preds['Height_Ave_cm']\n            elif target_name == 'Dry_Clover_g':\n                value = preds['Dry_Clover_g']\n            elif target_name == 'Dry_Dead_g':\n                value = preds['Dry_Dead_g']\n            elif target_name == 'Dry_Green_g':\n                value = preds['Dry_Green_g']\n            elif target_name == 'Dry_Total_g':\n                # Calculate from components: Dry_Clover_g + Dry_Dead_g + Dry_Green_g\n                value = preds['Dry_Clover_g'] + preds['Dry_Dead_g'] + preds['Dry_Green_g']\n            elif target_name == 'GDM_g':\n                # Calculate from components: Dry_Clover_g + Dry_Green_g\n                value = preds['Dry_Clover_g'] + preds['Dry_Green_g']\n            else:\n                value = 0.0\n                print(f\"Warning: Unknown target name: {target_name}\")\n        else:\n            value = 0.0\n            print(f\"Warning: No prediction found for {image_path}\")\n        \n        submission_data.append({\n            'sample_id': sample_id,\n            'target': max(0.0, value)  # Ensure non-negative values\n        })\n    \n    # Create submission DataFrame\n    submission_df = pd.DataFrame(submission_data)\n    \n    # Save submission\n    submission_df.to_csv('submission.csv', index=False)\n    print(\"Submission file saved as 'submission.csv'\")\n    print(f\"Submission shape: {submission_df.shape}\")\n    \n    # Show some statistics\n    print(\"\\nSubmission statistics:\")\n    print(f\"Total samples: {len(submission_df)}\")\n    print(f\"Unique target names: {test_df['target_name'].nunique()}\")\n    print(f\"Target value range: {submission_df['target'].min():.4f} to {submission_df['target'].max():.4f}\")\n    print(f\"Mean target value: {submission_df['target'].mean():.4f}\")\n    \n    print(\"\\nFirst 10 rows of submission:\")\n    print(submission_df.head(10))\n    \n    # Verify the required format\n    required_columns = ['sample_id', 'target']\n    if all(col in submission_df.columns for col in required_columns):\n        print(\"\\n✓ Submission format is correct!\")\n    else:\n        print(\"\\n✗ Submission format is incorrect!\")\n    \n    return submission_df\n\nif __name__ == \"__main__\":\n    submission = run_inference()","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-11-12T05:49:03.550365Z","iopub.execute_input":"2025-11-12T05:49:03.551105Z","iopub.status.idle":"2025-11-12T05:49:33.511879Z","shell.execute_reply.started":"2025-11-12T05:49:03.551080Z","shell.execute_reply":"2025-11-12T05:49:33.510964Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}