{"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":"none","dataSources":[{"sourceId":112509,"databundleVersionId":14254895,"sourceType":"competition"},{"sourceId":14159830,"sourceType":"datasetVersion","datasetId":9024767}],"dockerImageVersionId":31192,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"#Get our libraries\nimport os\nimport numpy as np\nimport pandas as pd\nimport cv2\nimport torch\nimport torch.nn as nn\nimport torchvision.transforms as transforms\nfrom sklearn.preprocessing import LabelEncoder\nimport torchvision.models as models\nimport joblib\nfrom tqdm import tqdm\nimport torch.nn as nn\nimport json\n\n#One base path (so we can change for the submission)\nIMAGE_BASE_PATH = '/kaggle/input/csiro-biomass/'","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-12-15T04:55:24.262658Z","iopub.execute_input":"2025-12-15T04:55:24.263307Z","iopub.status.idle":"2025-12-15T04:55:39.478509Z","shell.execute_reply.started":"2025-12-15T04:55:24.263281Z","shell.execute_reply":"2025-12-15T04:55:39.477496Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Load Test Data and Images\nTEST_CSV = '/kaggle/input/csiro-biomass/test.csv'\ntest_df = pd.read_csv(TEST_CSV)\ntest_df['image_path'] = test_df['image_path'].astype(str)\n\n# Drop sample_id \nif 'sample_id' in test_df.columns:\n    test_df = test_df.drop('sample_id', axis=1)\n\n# Remove dups\ntest_df = test_df.drop_duplicates(subset=['image_path'], keep='first').reset_index(drop=True)\n\ndef get_full_image_path(rel_path):\n    return os.path.join(IMAGE_BASE_PATH, rel_path)\n\nIMG_SIZE = 416  #Match training\ndef load_and_preprocess_image(img_path, img_size=IMG_SIZE):\n    img = cv2.imread(img_path)\n    if img is not None:\n        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n        img = cv2.resize(img, (img_size, img_size))\n        img = img.astype(np.float32) / 255.0\n        return img\n    else:\n        print(f\"Warning: Could not read image {img_path}\")\n        return np.zeros((img_size, img_size, 3), dtype=np.float32)\n\ntest_images = []\nfor idx, row in test_df.iterrows():\n    img_path = get_full_image_path(row['image_path'])\n    test_images.append(load_and_preprocess_image(img_path))\ntest_images = np.array(test_images, dtype=np.float32)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-15T04:55:39.479916Z","iopub.execute_input":"2025-12-15T04:55:39.480330Z","iopub.status.idle":"2025-12-15T04:55:39.749593Z","shell.execute_reply.started":"2025-12-15T04:55:39.480299Z","shell.execute_reply":"2025-12-15T04:55:39.748658Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Path to the species model\nspecies_model_path = '/kaggle/input/jp-csiro-v10/efficientnet_species_classifier.pt'\n\n\nspecies_transform = transforms.Compose([\n    transforms.ToTensor(),\n    transforms.Resize((416, 416)),\n    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])\n])\n\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n\n#Pull model and load weights\nnum_species =  15\nspecies_model = models.efficientnet_b0(pretrained=False)\nspecies_model.classifier[1] = nn.Linear(species_model.classifier[1].in_features, num_species)\nspecies_model.load_state_dict(torch.load(species_model_path, map_location=device))\nspecies_model = species_model.to(device)\nspecies_model.eval()\n\n#Predict species\npred_species = []\nwith torch.no_grad():\n    for img in tqdm(test_images, desc='Predicting species'):\n        img_tensor = species_transform(img).unsqueeze(0).to(device)\n        outputs = species_model(img_tensor)\n        _, pred = torch.max(outputs, 1)\n        pred_species.append(pred.item())\n\ntest_df['Species'] = pred_species\nprint(test_df.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-15T04:55:39.750563Z","iopub.execute_input":"2025-12-15T04:55:39.750924Z","iopub.status.idle":"2025-12-15T04:55:40.717673Z","shell.execute_reply.started":"2025-12-15T04:55:39.750891Z","shell.execute_reply":"2025-12-15T04:55:40.716624Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n#Lets match training architecture\nclass EfficientNetHeight(nn.Module):\n    def __init__(self, num_species):\n        super().__init__()\n        self.efficientnet = models.efficientnet_b0(pretrained=False)\n        self.efficientnet.classifier[1] = nn.Identity()\n        self.species_fc = nn.Linear(num_species, 32)\n        self.final_fc = nn.Linear(1280 + 32, 1)\n    def forward(self, img, species_feat):\n        img_feat = self.efficientnet(img)\n        species_feat = self.species_fc(species_feat)\n        concat = torch.cat([img_feat, species_feat], dim=1)\n        out = self.final_fc(concat)\n        return out.squeeze(1)\n\n#Transforms\nheight_transform = transforms.Compose([\n    transforms.ToTensor(),\n    transforms.Resize((416, 416)),\n    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])\n])\n\n#Have the keys for our species mapping\nwith open('/kaggle/input/jp-csiro-v10/species_label_map.json', 'r') as f:\n    label_map = json.load(f)\nspecies_to_number = {int(k): v for k, v in label_map['number_to_species'].items()}  # int keys for reverse mapping\nnum_species = len(label_map['species_to_number'])\n\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n\n#Load up our trained model\nheight_model_path = '/kaggle/input/jp-csiro-v10/efficientnet_height_regressor.pt'\nheight_model = EfficientNetHeight(num_species=num_species)\nheight_model.load_state_dict(torch.load(height_model_path, map_location=device))\nheight_model = height_model.to(device)\nheight_model.eval()\n\n#One-Hot encoding for species\nspecies_indices = test_df['Species'].astype(int).values\nspecies_onehot = torch.nn.functional.one_hot(torch.tensor(species_indices, dtype=torch.long), num_classes=num_species).float()\n\n#Predict the heights\npred_heights = []\nwith torch.no_grad():\n    for i, row in test_df.iterrows():\n        img = test_images[i]\n        img_tensor = height_transform(img).unsqueeze(0).to(device)\n        species_tensor = species_onehot[i].unsqueeze(0).to(device)\n        pred = height_model(img_tensor, species_tensor)\n        pred_heights.append(pred.item())\n\ntest_df['Height_Ave_cm'] = pred_heights\nprint(test_df[['image_path', 'Species', 'Height_Ave_cm']].head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-15T04:55:40.719354Z","iopub.execute_input":"2025-12-15T04:55:40.719908Z","iopub.status.idle":"2025-12-15T04:55:41.395542Z","shell.execute_reply.started":"2025-12-15T04:55:40.719882Z","shell.execute_reply":"2025-12-15T04:55:41.394629Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"mainModel_df = test_df[['image_path', 'Species', 'Height_Ave_cm']].copy()\n\n#Species is an int already\nwith open('/kaggle/input/jp-csiro-v10/species_label_map.json', 'r') as f:\n    label_map = json.load(f)\nspecies_to_number = label_map['species_to_number']\n\nIMG_SIZE = 416\ngreen_pixel_fracs = []\nearth_pixel_fracs = []\ndead_pixel_fracs = []\n\nfor idx, row in mainModel_df.iterrows():\n    path = os.path.join(IMAGE_BASE_PATH, str(row['image_path']))\n    img = cv2.imread(path)\n    if img is None:\n        green_pixel_fracs.append(np.nan)\n        earth_pixel_fracs.append(np.nan)\n        dead_pixel_fracs.append(np.nan)\n        continue\n\n    #Using 416 as we did for training\n    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n    img = cv2.resize(img, (IMG_SIZE, IMG_SIZE))\n    r, g, b = cv2.split(img)\n    total_pixels = img.shape[0] * img.shape[1]\n\n    #our masks\n    green_mask = (g > r + 20) & (g > b + 20) & (g > 60)\n\n    \n    brown_mask = (r > 60) & (g > 40) & (b < 80) & (np.abs(r - g) < 30) & (r > b) & (g > b) & (r < 180) & (g < 180)\n    black_mask = (r < 40) & (g < 40) & (b < 40)\n    earth_mask = brown_mask | black_mask\n\n \n\n    #Make sure we don't double count\n    green_mask = green_mask  & (~earth_mask)\n    earth_mask = earth_mask & (~green_mask) \n\n    #get %\n    green_pixel_fracs.append(np.count_nonzero(green_mask) / total_pixels)\n    earth_pixel_fracs.append(np.count_nonzero(earth_mask) / total_pixels)\n   \n\nmainModel_df['green_pixels'] = green_pixel_fracs\nmainModel_df['earth_pixels'] = earth_pixel_fracs\n\n\nmainModel_df.head(10)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-15T04:55:41.396444Z","iopub.execute_input":"2025-12-15T04:55:41.396692Z","iopub.status.idle":"2025-12-15T04:55:41.507855Z","shell.execute_reply.started":"2025-12-15T04:55:41.396668Z","shell.execute_reply":"2025-12-15T04:55:41.506629Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#final model - lets define it to match training\nclass BiomassCNN(nn.Module):\n    def __init__(self, n_tabular_features=2, n_targets=3):\n        super().__init__()\n        self.cnn = models.efficientnet_b0(pretrained=False)\n        self.cnn.classifier = nn.Identity()\n        self.tabular_fc = nn.Linear(n_tabular_features, 32)\n        self.final_fc = nn.Sequential(\n            nn.Linear(1280 + 32, 128),\n            nn.ReLU(),\n            nn.Linear(128, n_targets)\n        )\n    def forward(self, img, tabular):\n        img_feat = self.cnn(img)\n        tab_feat = self.tabular_fc(tabular)\n        x = torch.cat([img_feat, tab_feat], dim=1)\n        out = self.final_fc(x)\n        return out\n\n#Transforms should match training\nmain_transform = transforms.Compose([\n    transforms.ToTensor(),\n    transforms.Resize((224, 224)),\n    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])\n])\n\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n\n#Load final model\nmain_model_path = '/kaggle/input/jp-csiro-v10/best_biomass_cnn_model.pt'\nmain_model = BiomassCNN(n_tabular_features=2, n_targets=3)\nmain_model.load_state_dict(torch.load(main_model_path, map_location=device))\nmain_model = main_model.to(device)\nmain_model.eval()\n\n#Prediction prep\n\npred_dry_green = []\npred_dry_dead = []\npred_dry_clover = []\n\nwith torch.no_grad():\n    for idx, row in mainModel_df.iterrows():\n        img_path = os.path.join(IMAGE_BASE_PATH, row['image_path'])\n        img = cv2.imread(img_path)\n        if img is None:\n            pred_dry_green.append(np.nan)\n            pred_dry_dead.append(np.nan)\n            pred_dry_clover.append(np.nan)\n            continue\n        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n        img = cv2.resize(img, (224, 224))\n        img = img.astype(np.float32) / 255.0\n        img_tensor = main_transform(img).unsqueeze(0).to(device)\n        features = np.array([\n            float(row['Height_Ave_cm']),\n            float(row['Species'])\n        ], dtype=np.float32)\n        features_tensor = torch.tensor(features).unsqueeze(0).to(device)\n        preds = main_model(img_tensor, features_tensor)\n        preds = preds.cpu().numpy().flatten()\n        pred_dry_green.append(preds[0])\n        pred_dry_dead.append(preds[1])\n        pred_dry_clover.append(preds[2])\n\nmainModel_df['Dry_Green_g'] = pred_dry_green\nmainModel_df['Dry_Dead_g'] = pred_dry_dead\nmainModel_df['Dry_Clover_g'] = pred_dry_clover\nprint(mainModel_df[['image_path', 'Species', 'Height_Ave_cm', 'Dry_Green_g', 'Dry_Dead_g', 'Dry_Clover_g']].head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-15T04:55:41.508867Z","iopub.execute_input":"2025-12-15T04:55:41.509197Z","iopub.status.idle":"2025-12-15T04:55:42.153029Z","shell.execute_reply.started":"2025-12-15T04:55:41.509173Z","shell.execute_reply":"2025-12-15T04:55:42.152118Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Now we reformat the df for our submission file\nsubmission_rows = []\nfor idx, row in mainModel_df.iterrows():\n    sample_id = os.path.splitext(os.path.basename(row['image_path']))[0]\n    dry_clover = max(0.0, float(row['Dry_Clover_g']))\n    dry_dead = max(0.0, float(row['Dry_Dead_g']))\n    dry_green = max(0.0, float(row['Dry_Green_g']))\n    dry_total = max(0.0, dry_clover + dry_dead + dry_green)\n    gdm = max(0.0, dry_clover + dry_green)\n    submission_rows.append({'sample_id': f'{sample_id}__Dry_Clover_g', 'target': dry_clover})\n    submission_rows.append({'sample_id': f'{sample_id}__Dry_Dead_g', 'target': dry_dead})\n    submission_rows.append({'sample_id': f'{sample_id}__Dry_Green_g', 'target': dry_green})\n    submission_rows.append({'sample_id': f'{sample_id}__Dry_Total_g', 'target': dry_total})\n    submission_rows.append({'sample_id': f'{sample_id}__GDM_g', 'target': gdm})\n\nsubmission_df = pd.DataFrame(submission_rows)\nprint(submission_df.head(10))\n# Optionally save to CSV\nsubmission_df.to_csv('submission.csv', index=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-15T05:00:54.944170Z","iopub.execute_input":"2025-12-15T05:00:54.944477Z","iopub.status.idle":"2025-12-15T05:00:54.957246Z","shell.execute_reply.started":"2025-12-15T05:00:54.944455Z","shell.execute_reply":"2025-12-15T05:00:54.956459Z"}},"outputs":[],"execution_count":null}]}