{"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":"none","dataSources":[{"sourceId":112509,"databundleVersionId":14254895,"sourceType":"competition"},{"sourceId":292673463,"sourceType":"kernelVersion"}],"dockerImageVersionId":31259,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nfrom PIL import Image, ImageCms\nfrom typing import Dict\nfrom skimage.feature import graycomatrix, graycoprops\nfrom tqdm import tqdm\n\nfrom sklearn.model_selection import KFold\nfrom catboost import CatBoostRegressor\nfrom xgboost import XGBRegressor\n\n\nimport warnings\nwarnings.filterwarnings('ignore')","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-01-21T17:44:29.845768Z","iopub.execute_input":"2026-01-21T17:44:29.846172Z","iopub.status.idle":"2026-01-21T17:44:29.854561Z","shell.execute_reply.started":"2026-01-21T17:44:29.846144Z","shell.execute_reply":"2026-01-21T17:44:29.853163Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\nclass PreProcess:\n    SUPPORTED_COLOR_SPACES = {\"RGB\", \"L\", \"HSV\", \"LAB\"}\n\n    def __init__(self):\n        pass\n\n    @staticmethod\n    def load_image_rgb(image_path: str) -> Image.Image:\n        with Image.open(image_path) as img:\n            return img.convert(\"RGB\")\n\n    def convert_color_space(\n        self,\n        img: Image.Image,\n        target_color_space: str\n    ) -> Image.Image:\n\n        target_color_space = target_color_space.upper()\n\n        if target_color_space not in self.SUPPORTED_COLOR_SPACES:\n            raise ValueError(\n                f\"Unsupported color space '{target_color_space}'. \"\n                f\"Supported values: {sorted(self.SUPPORTED_COLOR_SPACES)}\"\n            )\n\n        if target_color_space == \"LAB\":\n            return self.rgb_to_lab(img)\n\n        return img.convert(target_color_space)\n\n    @staticmethod\n    def rgb_to_lab(img: Image.Image) -> Image.Image:\n        srgb = ImageCms.createProfile(\"sRGB\")\n        lab = ImageCms.createProfile(\"LAB\")\n\n        transform = ImageCms.buildTransformFromOpenProfiles(\n            srgb, lab, \"RGB\", \"LAB\"\n        )\n\n        return ImageCms.applyTransform(img, transform)\n\n\n    def extract_rgb_features(self, rgb_image):\n        features = {}\n        R, G, B = rgb_image[:, :, 0], rgb_image[:, :, 1], rgb_image[:, :, 2]\n        features[\"red_mean\"] = R.mean() / 255\n        features[\"red_std\"] = R.std() / 255\n\n        features[\"green_mean\"] = G.mean() / 255\n        features[\"green_std\"] = G.std() / 255\n\n        features[\"blue_mean\"] = B.mean() / 255\n        features[\"blue_std\"] = B.std() / 255\n\n        features[\"red_percent\"] = (R>125).sum() / (R.shape[0]*R.shape[1])\n        features[\"green_percent\"] = (G>125).sum() / (G.shape[0]*G.shape[1])\n        features[\"blue_percent\"] = (B>125).sum() / (B.shape[0]*B.shape[1])\n\n        # Excess Green\n        ExG = ((2*G) - R) - B\n        features[\"exg_mean\"] = ExG.mean()\n        features[\"exg_std\"] = ExG.std()\n        features[\"exg_percent\"] = (ExG>0).sum() / (ExG.shape[0]*ExG.shape[1])\n\n        # Normalized Green Red Diff Index\n        ngdri = (G/255-R/255) / ((G/255+R/255) + 1e-6)\n        features[\"ngdri_mean\"] = ngdri.mean()\n        features[\"ngdri_std\"] = ngdri.std()\n        features[\"ngdri_+percent\"] = (ngdri>0).sum() / (ngdri.shape[0]*ngdri.shape[1])\n        features[\"ngdri_-percent\"] = (ngdri<0).sum() / (ngdri.shape[0]*ngdri.shape[1])\n        \n        return features\n\n\n    def extract_grayscale_features(self, grayscale_image):\n        features = {}\n        features[\"gray_mean\"] = grayscale_image.mean() / 255\n        features[\"gray_std\"] = grayscale_image.std() / 255\n        # features[\"gray_entropy\"] = -np.sum(\n        #     (grayscale_image/255) * np.log2((grayscale_image/255) + 1e-6)\n        # )\n        return features\n        \n\n    def extract_hsv_features(self, hsv_image):\n        H, S, V = hsv_image[:, :, 0], hsv_image[:, :, 1], hsv_image[:, :, 2]\n        features = {}\n\n        features[\"hue_mean\"] = H.mean() / 255\n        features[\"hue_std\"] = H.std() / 255\n        features[\"hue_var\"] = np.var(H/255)\n\n        features[\"sat_mean\"] = S.mean() / 255\n        features[\"sat_std\"] = S.std() / 255\n        features[\"sat_var\"] = np.var(S/255)\n\n        features[\"hsvalue_mean\"] = V.mean() / 255\n        features[\"hsvalue_std\"] = V.std() / 255\n        features[\"hsvalue_var\"] = np.var(V/255)\n\n        \n        # Green hue\n        green_mask = (H >= 35) & (H <= 85)\n        features[\"green_hue_mean\"] = H[green_mask].mean() / 255 if green_mask.any() else 0\n        features[\"green_hue_std\"]  = H[green_mask].std() / 255  if green_mask.any() else 0\n        features[\"green_hue_var\"] = np.var(green_mask/255)\n        features[\"green_hue_percent\"] = (green_mask>0).sum() / (green_mask.shape[0]*green_mask.shape[1])\n\n        yellowing_grass = (H >= 20) & (H <= 35)\n        features[\"yellowing_grass_hue_mean\"] = yellowing_grass.mean()\n        features[\"yellowing_grass_hue_std\"] = yellowing_grass.std()\n        features[\"yellowing_grass_hue_var\"] = np.var(yellowing_grass/255)\n        features[\"yellowing_grass_hue_percent\"] = (yellowing_grass>0).sum() / (yellowing_grass.shape[0]*yellowing_grass.shape[1])\n\n        \n        brown_dry = (H >= 10) & (H <= 20)\n        features[\"brown_dry_hue_mean\"] = brown_dry.mean()\n        features[\"brown_dry_hue_std\"] = brown_dry.std()\n        features[\"brown_dry_hue_var\"] = np.var(brown_dry/255)\n        features[\"brown_dry_hue_percent\"] = (brown_dry>0).sum() / (brown_dry.shape[0]*brown_dry.shape[1])\n\n        \n        soild_dead = H <= 10\n        features[\"soild_dead_hue_mean\"] = soild_dead.mean()\n        features[\"soild_dead_hue_std\"] = soild_dead.std()\n        features[\"soild_dead_hue_var\"] = np.var(soild_dead/255)\n        features[\"soild_dead_hue_percent\"] = (soild_dead>0).sum() / (soild_dead.shape[0]*soild_dead.shape[1])\n\n        # More saturation -> More vegetation\n        features[\"less_saturated_percent\"] = (S<50).sum() / (S.shape[0]*S.shape[1])\n        features[\"more_saturated_percent\"] = (S>50).sum() / (S.shape[0]*S.shape[1])\n\n        # Shadow in image\n        features[\"shadow_percent\"] = (V<50).sum() / (V.shape[0]*V.shape[1])\n\n        masked_v = V.copy()\n        masked_v[~green_mask] = 0\n        \n        masked_v = masked_v.astype(np.float32)\n        masked_v = (masked_v / 255.0 * (32 - 1)).astype(np.uint8)\n\n        distances = [1]\n        angles = [0, np.pi/4, np.pi/2, 3*np.pi/4]\n        \n        # 3. Calculate the GLCM\n        # levels=256 specifies the number of gray levels\n        glcm = graycomatrix(masked_v, \n                            distances=distances, \n                            angles=angles, \n                            levels=32, \n                            symmetric=True, \n                            normed=True) #\n        \n        # 4. Extract texture features (e.g., contrast and homogeneity)\n        contrast = graycoprops(glcm, 'contrast')\n        homogeneity = graycoprops(glcm, 'homogeneity')\n        energy = graycoprops(glcm, 'energy')\n        correlation = graycoprops(glcm, 'correlation')\n        \n        eps = 1e-10\n        biomass_entropy = -np.sum(glcm * np.log2(glcm + eps))\n\n        features[\"glcm_contrast\"] = contrast.mean()\n        features[\"glcm_homogeneity\"] = homogeneity.mean()\n        features[\"glcm_energy\"] = energy.mean()\n        features[\"glcm_correlation\"] = correlation.mean()\n        features[\"biomass_entropy\"] = biomass_entropy\n\n        return features\n        \n\n    def extract_lab_features(self, lab_image):\n        L, A, B = lab_image[:, :, 0], lab_image[:, :, 1], lab_image[:, :, 2]\n        features = {}\n\n        features[\"L_mean\"] = L.mean() / 255\n        features[\"L_std\"] = L.std() / 255\n        features[\"L_var\"] = np.var(L/255)\n\n        features[\"A_mean\"] = A.mean() / 255\n        features[\"A_std\"] = A.std() / 255\n        features[\"A_var\"] = np.var(A/255)\n\n        features[\"B_mean\"] = B.mean() / 255\n        features[\"B_std\"] = B.std() / 255\n        features[\"B_var\"] = np.var(B/255)\n\n        features[\"A_B_ratio\"] = A.mean() / (B.mean() + 1e-6)\n        \n        A_signed = A.astype(np.float32) - 128\n        B_signed = B.astype(np.float32) - 128\n        \n        features[\"A_mean\"] = A_signed.mean()\n        features[\"B_mean\"] = B_signed.mean()\n        features[\"AB_ratio\"] = A_signed.mean() / (B_signed.mean() + 1e-6)\n\n        return features    \n        \n\n    def process(self, image_path: str) -> Dict[str, Image.Image]:\n        # Load Image\n        rgb_image = self.load_image_rgb(image_path)\n        grayscale_image = self.convert_color_space(rgb_image, \"L\")\n        hsv_image = self.convert_color_space(rgb_image, \"HSV\")\n        lab_image = self.convert_color_space(rgb_image, \"LAB\")\n\n        # Image to Numpy\n        rgb_image = np.asarray(rgb_image)\n        grayscale_image = np.asarray(grayscale_image)\n        hsv_image = np.asarray(hsv_image)\n        lab_image = np.asarray(lab_image)\n\n        rgb_features = self.extract_rgb_features(rgb_image)\n        hsv_features = self.extract_hsv_features(hsv_image)\n        lab_features = self.extract_lab_features(lab_image)\n        grayscale_features = self.extract_grayscale_features(grayscale_image)\n\n        all_features = {**rgb_features, **hsv_features, **lab_features, **grayscale_features}\n        all_features[\"green_to_total\"] = all_features[\"green_percent\"] / (all_features[\"exg_percent\"] + 1e-6)\n        all_features[\"brown_to_green\"] = all_features[\"brown_dry_hue_percent\"] / (all_features[\"green_hue_percent\"] + 1e-6)\n\n        all_features[\"exg_x_sat\"] = all_features[\"exg_mean\"] * all_features[\"sat_mean\"]\n        all_features[\"entropy_x_exg\"] = all_features[\"biomass_entropy\"] * all_features[\"exg_mean\"]\n\n\n        \n        return all_features","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-21T17:44:29.856856Z","iopub.execute_input":"2026-01-21T17:44:29.857540Z","iopub.status.idle":"2026-01-21T17:44:29.900014Z","shell.execute_reply.started":"2026-01-21T17:44:29.857478Z","shell.execute_reply":"2026-01-21T17:44:29.898212Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"preprocessor = PreProcess()\n\ndef extract_features_safe(image_path):\n    return preprocessor.process(f\"/kaggle/input/csiro-biomass/{image_path}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-21T17:44:29.901818Z","iopub.execute_input":"2026-01-21T17:44:29.902373Z","iopub.status.idle":"2026-01-21T17:44:29.926419Z","shell.execute_reply.started":"2026-01-21T17:44:29.902236Z","shell.execute_reply":"2026-01-21T17:44:29.924915Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_csv_path = \"/kaggle/input/csiro-biomass/test.csv\"\ntest_image_dir_path =\"/kaggle/input/csiro-biomass/test\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-21T17:44:29.964473Z","iopub.execute_input":"2026-01-21T17:44:29.964823Z","iopub.status.idle":"2026-01-21T17:44:29.971289Z","shell.execute_reply.started":"2026-01-21T17:44:29.964790Z","shell.execute_reply":"2026-01-21T17:44:29.969950Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_df = pd.read_csv(test_csv_path)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-21T17:44:29.973280Z","iopub.execute_input":"2026-01-21T17:44:29.973797Z","iopub.status.idle":"2026-01-21T17:44:29.994361Z","shell.execute_reply.started":"2026-01-21T17:44:29.973756Z","shell.execute_reply":"2026-01-21T17:44:29.992919Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_df = pd.DataFrame(test_df.image_path.unique(), columns=[\"image_path\"])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-21T17:44:29.995467Z","iopub.execute_input":"2026-01-21T17:44:29.996090Z","iopub.status.idle":"2026-01-21T17:44:30.014514Z","shell.execute_reply.started":"2026-01-21T17:44:29.995899Z","shell.execute_reply":"2026-01-21T17:44:30.010965Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_df.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-21T17:44:30.015787Z","iopub.execute_input":"2026-01-21T17:44:30.016188Z","iopub.status.idle":"2026-01-21T17:44:30.041461Z","shell.execute_reply.started":"2026-01-21T17:44:30.016161Z","shell.execute_reply":"2026-01-21T17:44:30.040441Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\ntqdm.pandas()\n\ntest_feature_df = test_df[\"image_path\"].progress_apply(extract_features_safe)\n\n# Convert list/dict → DataFrame\ntest_feature_df = pd.DataFrame(test_feature_df.tolist())\nfor col in test_feature_df.columns:\n    test_feature_df[col + \"_rank\"] = test_feature_df[col].rank(pct=True)\n    \nprint(test_feature_df.shape)\ntest_feature_df.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-21T17:44:30.045232Z","iopub.execute_input":"2026-01-21T17:44:30.045579Z","iopub.status.idle":"2026-01-21T17:44:30.922897Z","shell.execute_reply.started":"2026-01-21T17:44:30.045552Z","shell.execute_reply":"2026-01-21T17:44:30.921469Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"LOG_FEATURES = [\n    \"glcm_contrast\",\n    \"biomass_entropy\",\n    \"exg_mean\",\n    \"exg_x_sat\",\n    \"entropy_x_exg\",\n]\n\nfor col in LOG_FEATURES:\n    if col in test_feature_df.columns:\n        test_feature_df[col] = np.log1p(test_feature_df[col])\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-21T17:44:30.923960Z","iopub.execute_input":"2026-01-21T17:44:30.924216Z","iopub.status.idle":"2026-01-21T17:44:30.931843Z","shell.execute_reply.started":"2026-01-21T17:44:30.924194Z","shell.execute_reply":"2026-01-21T17:44:30.930569Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"filter_cols = ['blue_std', 'blue_percent', 'exg_std', 'exg_percent', 'ngdri_mean',\n       'ngdri_+percent', 'ngdri_-percent', 'hue_std', 'hue_var', 'sat_mean',\n       'green_hue_percent', 'yellowing_grass_hue_mean',\n       'yellowing_grass_hue_std', 'yellowing_grass_hue_var',\n       'yellowing_grass_hue_percent', 'brown_dry_hue_mean',\n       'brown_dry_hue_std', 'brown_dry_hue_var', 'brown_dry_hue_percent',\n       'soild_dead_hue_mean', 'soild_dead_hue_std', 'soild_dead_hue_var',\n       'soild_dead_hue_percent', 'glcm_contrast', 'glcm_energy',\n       'glcm_correlation', 'biomass_entropy', 'A_mean', 'A_std', 'A_var',\n       'A_B_ratio', 'brown_to_green', 'entropy_x_exg', 'blue_std_rank',\n       'blue_percent_rank', 'exg_std_rank', 'exg_percent_rank',\n       'ngdri_mean_rank', 'ngdri_+percent_rank', 'ngdri_-percent_rank',\n       'green_hue_percent_rank', 'yellowing_grass_hue_mean_rank',\n       'yellowing_grass_hue_std_rank', 'yellowing_grass_hue_var_rank',\n       'yellowing_grass_hue_percent_rank', 'brown_dry_hue_mean_rank',\n       'brown_dry_hue_std_rank', 'brown_dry_hue_var_rank',\n       'brown_dry_hue_percent_rank', 'soild_dead_hue_mean_rank',\n       'soild_dead_hue_std_rank', 'soild_dead_hue_var_rank',\n       'soild_dead_hue_percent_rank', 'glcm_contrast_rank', 'glcm_energy_rank',\n       'glcm_correlation_rank', 'A_mean_rank', 'A_std_rank', 'A_var_rank',\n       'AB_ratio_rank', 'brown_to_green_rank']\n\n\ntest_feature_df = test_feature_df[filter_cols]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-21T17:44:30.932879Z","iopub.execute_input":"2026-01-21T17:44:30.933183Z","iopub.status.idle":"2026-01-21T17:44:30.958228Z","shell.execute_reply.started":"2026-01-21T17:44:30.933159Z","shell.execute_reply":"2026-01-21T17:44:30.957184Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"X_test = test_feature_df.values","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-21T17:44:30.959657Z","iopub.execute_input":"2026-01-21T17:44:30.960395Z","iopub.status.idle":"2026-01-21T17:44:30.982036Z","shell.execute_reply.started":"2026-01-21T17:44:30.960358Z","shell.execute_reply":"2026-01-21T17:44:30.980846Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from xgboost import XGBRegressor","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-21T17:44:30.983151Z","iopub.execute_input":"2026-01-21T17:44:30.983430Z","iopub.status.idle":"2026-01-21T17:44:31.002204Z","shell.execute_reply.started":"2026-01-21T17:44:30.983407Z","shell.execute_reply":"2026-01-21T17:44:31.000983Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_preds = {}\ntarget_cols = [\n    \"Dry_Clover_g\",\n    \"Dry_Dead_g\",\n    \"Dry_Green_g\",\n    \"GDM_g\",\n    \"Dry_Total_g\",\n]\n\nfor target in target_cols:\n    target_preds = []\n\n    for fold in range(5):\n        # CatBoost\n        cat_model = CatBoostRegressor()\n        cat_model.load_model(f\"/kaggle/input/pasture-biomass-prediction-ml/models/catboost/fold_{fold}_{target}.cbm\")\n        target_preds.append(cat_model.predict(X_test))\n\n        xgb_model = XGBRegressor()\n        xgb_model.load_model(\n            f\"/kaggle/input/pasture-biomass-prediction-ml/models/xgboost/fold_{fold}_{target}.json\"\n        )\n        target_preds.append(xgb_model.predict(X_test))\n    \n    \n        # # LightGBM\n        # lgb_model = lgb.Booster(\n        #     model_file=f\"/kaggle/input/pasture-biomass-prediction-ml/models/lightgbm/fold_{fold}_{target}.txt\"\n        # )\n        # target_preds.append(lgb_model.predict(X_test))\n    \n\n    test_preds[target] = np.median(target_preds, axis=0)\n\ntest_pred_df = pd.DataFrame(test_preds)\ntest_pred_df.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-21T17:44:31.003269Z","iopub.execute_input":"2026-01-21T17:44:31.003673Z","iopub.status.idle":"2026-01-21T17:44:41.852615Z","shell.execute_reply.started":"2026-01-21T17:44:31.003643Z","shell.execute_reply":"2026-01-21T17:44:41.851320Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def clip_non_negative(df):\n    return df.clip(lower=0.0)\n\ndef enforce_gdm(df, alpha=0.7):\n    \"\"\"\n    alpha = trust model GDM vs sum\n    \"\"\"\n    sum_gdm = df[\"Dry_Clover_g\"] + df[\"Dry_Green_g\"]\n    df[\"GDM_g\"] = alpha * df[\"GDM_g\"] + (1 - alpha) * sum_gdm\n    return df\n\ndef enforce_total(df, eps=1e-6):\n    components = (\n        df[\"Dry_Clover_g\"] +\n        df[\"Dry_Dead_g\"] +\n        df[\"Dry_Green_g\"]\n    )\n\n    scale = df[\"Dry_Total_g\"] / (components + eps)\n\n    df[\"Dry_Clover_g\"] *= scale\n    df[\"Dry_Dead_g\"]   *= scale\n    df[\"Dry_Green_g\"]  *= scale\n\n    return df\n\ndef post_process_predictions(pred_df):\n    pred_df = pred_df.copy()\n\n    # 1. Non-negative\n    pred_df = clip_non_negative(pred_df)\n\n    # 2. GDM consistency\n    pred_df = enforce_gdm(pred_df, alpha=0.7)\n\n    # 3. Total consistency\n    pred_df = enforce_total(pred_df)\n\n    return pred_df\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-21T17:44:41.853731Z","iopub.execute_input":"2026-01-21T17:44:41.854031Z","iopub.status.idle":"2026-01-21T17:44:41.864482Z","shell.execute_reply.started":"2026-01-21T17:44:41.854006Z","shell.execute_reply":"2026-01-21T17:44:41.863286Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_pred_df = post_process_predictions(test_pred_df)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-21T17:44:41.865708Z","iopub.execute_input":"2026-01-21T17:44:41.866045Z","iopub.status.idle":"2026-01-21T17:44:41.888483Z","shell.execute_reply.started":"2026-01-21T17:44:41.866004Z","shell.execute_reply":"2026-01-21T17:44:41.887226Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"predictions = pd.concat([test_df, test_pred_df], axis=1)\npredictions.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-21T17:44:41.889678Z","iopub.execute_input":"2026-01-21T17:44:41.889998Z","iopub.status.idle":"2026-01-21T17:44:41.916210Z","shell.execute_reply.started":"2026-01-21T17:44:41.889973Z","shell.execute_reply":"2026-01-21T17:44:41.915112Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submissions_rows = []\n\nfor i in range(predictions.shape[0]):\n    prediction = predictions.iloc[i]\n\n    sample_id = prediction['image_path'].split('/')[-1].split('.')[0]\n\n    for target_col in target_cols:\n        submissions_rows.append([\n            f\"{sample_id}__{target_col}\", prediction[target_col]\n        ])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-21T17:44:41.918766Z","iopub.execute_input":"2026-01-21T17:44:41.919760Z","iopub.status.idle":"2026-01-21T17:44:41.944078Z","shell.execute_reply.started":"2026-01-21T17:44:41.919727Z","shell.execute_reply":"2026-01-21T17:44:41.942591Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submission = pd.DataFrame(submissions_rows, columns=[\"sample_id\", \"target\"])\nsubmission.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-21T17:44:41.945326Z","iopub.execute_input":"2026-01-21T17:44:41.945609Z","iopub.status.idle":"2026-01-21T17:44:41.970423Z","shell.execute_reply.started":"2026-01-21T17:44:41.945585Z","shell.execute_reply":"2026-01-21T17:44:41.969517Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submission.to_csv(\"submission.csv\", index=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-21T17:44:41.971605Z","iopub.execute_input":"2026-01-21T17:44:41.971903Z","iopub.status.idle":"2026-01-21T17:44:41.992987Z","shell.execute_reply.started":"2026-01-21T17:44:41.971853Z","shell.execute_reply":"2026-01-21T17:44:41.991771Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}