{"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,"isSourceIdPinned":false,"sourceType":"competition"},{"sourceId":14026812,"sourceType":"datasetVersion","datasetId":8932545}],"dockerImageVersionId":31193,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!pip install git+https://github.com/PyTorchLightning/pytorch-lightning\n!pip install segmentation_models_pytorch \n!pip install xgboost==1.5.2","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-12-08T10:19:42.095245Z","iopub.execute_input":"2025-12-08T10:19:42.096080Z","iopub.status.idle":"2025-12-08T10:21:27.694940Z","shell.execute_reply.started":"2025-12-08T10:19:42.096050Z","shell.execute_reply":"2025-12-08T10:21:27.694169Z"},"collapsed":true,"jupyter":{"outputs_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pytorch_lightning as pl\nimport torch\nimport segmentation_models_pytorch as smp\nimport numpy as np\nimport cv2\nfrom segmentation_models_pytorch.encoders import get_preprocessing_fn\nimport matplotlib.pyplot as plt\nfrom typing import Dict, List\n\n# Set device to CUDA if available, otherwise CPU\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(f\"Using device: {device}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-08T10:21:27.696575Z","iopub.execute_input":"2025-12-08T10:21:27.696807Z","iopub.status.idle":"2025-12-08T10:21:42.257426Z","shell.execute_reply.started":"2025-12-08T10:21:27.696778Z","shell.execute_reply":"2025-12-08T10:21:42.256545Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class VegAnnModel(pl.LightningModule):\n    def __init__(self, arch: str, encoder_name: str, in_channels: int, out_classes: int, **kwargs):\n        super().__init__()\n        self.model = smp.create_model(\n            arch,\n            encoder_name=encoder_name,\n            in_channels=in_channels,\n            classes=out_classes,\n            **kwargs,\n        )\n\n        # preprocessing parameteres for image\n        params = smp.encoders.get_preprocessing_params(encoder_name)\n        self.register_buffer(\"std\", torch.tensor(params[\"std\"]).view(1, 3, 1, 1))\n        self.register_buffer(\"mean\", torch.tensor(params[\"mean\"]).view(1, 3, 1, 1))\n\n        # for image segmentation dice loss could be the best first choice\n        self.loss_fn = smp.losses.DiceLoss(smp.losses.BINARY_MODE, from_logits=True)\n        self.train_outputs, self.val_outputs, self.test_outputs = [], [], []\n\n    def forward(self, image: torch.Tensor):\n        # normalize image here #todo\n        image = (image - self.mean) / self.std\n        mask = self.model(image)\n        return mask\n\n    def shared_step(self, batch: Dict, stage: str):\n        image = batch[\"image\"]\n\n        # Shape of the image should be (batch_size, num_channels, height, width)\n        # if you work with grayscale images, expand channels dim to have [batch_size, 1, height, width]\n        assert image.ndim == 4\n\n        # Check that image dimensions are divisible by 32,\n        # encoder and decoder connected by `skip connections` and usually encoder have 5 stages of\n        # downsampling by factor 2 (2 ^ 5 = 32); e.g. if we have image with shape 65x65 we will have\n        # following shapes of features in encoder and decoder: 84, 42, 21, 10, 5 -> 5, 10, 20, 40, 80\n        # and we will get an error trying to concat these features\n        h, w = image.shape[2:]\n        assert h % 32 == 0 and w % 32 == 0\n\n        mask = batch[\"mask\"]\n\n        # Shape of the mask should be [batch_size, num_classes, height, width]\n        # for binary segmentation num_classes = 1\n        assert mask.ndim == 4\n\n        # Check that mask values in between 0 and 1, NOT 0 and 255 for binary segmentation\n        assert mask.max() <= 1.0 and mask.min() >= 0\n\n        logits_mask = self.forward(image)\n\n        # Predicted mask contains logits, and loss_fn param `from_logits` is set to True\n        loss = self.loss_fn(logits_mask, mask)\n\n        # Lets compute metrics for some threshold\n        # first convert mask values to probabilities, then\n        # apply thresholding\n        prob_mask = logits_mask.sigmoid()\n        pred_mask = (prob_mask > 0.5).float()\n\n        # We will compute IoU metric by two ways\n        #   1. dataset-wise\n        #   2. image-wise\n        # but for now we just compute true positive, false positive, false negative and\n        # true negative 'pixels' for each image and class\n        # these values will be aggregated in the end of an epoch\n        tp, fp, fn, tn = smp.metrics.get_stats(pred_mask.long(), mask.long(), mode=\"binary\")\n\n        return {\n            \"loss\": loss,\n            \"tp\": tp,\n            \"fp\": fp,\n            \"fn\": fn,\n            \"tn\": tn,\n        }\n\n    def shared_epoch_end(self, outputs: List[Dict], stage: str):\n        # aggregate step metics\n        tp = torch.cat([x[\"tp\"] for x in outputs])\n        fp = torch.cat([x[\"fp\"] for x in outputs])\n        fn = torch.cat([x[\"fn\"] for x in outputs])\n        tn = torch.cat([x[\"tn\"] for x in outputs])\n\n        # per image IoU means that we first calculate IoU score for each image\n        # and then compute mean over these scores\n        per_image_iou = smp.metrics.iou_score(tp, fp, fn, tn, reduction=\"micro-imagewise\")\n        per_image_f1 = smp.metrics.f1_score(tp, fp, fn, tn, reduction=\"micro-imagewise\")\n        per_image_acc = smp.metrics.accuracy(tp, fp, fn, tn, reduction=\"micro-imagewise\")\n        # dataset IoU means that we aggregate intersection and union over whole dataset\n        # and then compute IoU score. The difference between dataset_iou and per_image_iou scores\n        # in this particular case will not be much, however for dataset\n        # with \"empty\" images (images without target class) a large gap could be observed.\n        # Empty images influence a lot on per_image_iou and much less on dataset_iou.\n        dataset_iou = smp.metrics.iou_score(tp, fp, fn, tn, reduction=\"micro\")\n        dataset_f1 = smp.metrics.f1_score(tp, fp, fn, tn, reduction=\"micro\")\n        dataset_acc = smp.metrics.accuracy(tp, fp, fn, tn, reduction=\"micro\")\n\n        metrics = {\n            f\"{stage}_per_image_iou\": per_image_iou,\n            f\"{stage}_dataset_iou\": dataset_iou,\n            f\"{stage}_per_image_f1\": per_image_f1,\n            f\"{stage}_dataset_f1\": dataset_f1,\n            f\"{stage}_per_image_acc\": per_image_acc,\n            f\"{stage}_dataset_acc\": dataset_acc,\n        }\n\n        self.log_dict(metrics, prog_bar=True, sync_dist=True, rank_zero_only=True)\n\n    def training_step(self, batch: Dict, batch_idx: int):\n        step_outputs = self.shared_step(batch, \"train\")\n        self.train_outputs.append(step_outputs)\n        return step_outputs\n\n    def on_train_epoch_end(self):\n        self.shared_epoch_end(self.train_outputs, \"train\")\n        self.train_outputs = []\n\n    def validation_step(self, batch: Dict, batch_idx: int):\n        step_outputs = self.shared_step(batch, \"valid\")\n        self.val_outputs.append(step_outputs)\n        return step_outputs\n\n    def on_validation_epoch_end(self, *args, **kwargs):\n        self.shared_epoch_end(self.val_outputs, \"valid\")\n        self.val_outputs = []\n\n    def test_step(self, batch: Dict, batch_idx: int):\n        step_outputs = self.shared_step(batch, \"test\")\n        self.test_outputs.append(step_outputs)\n        return step_outputs\n\n    def on_test_epoch_end(self):\n        self.shared_epoch_end(self.test_outputs, \"test\")\n        self.test_outputs = []\n\n    def configure_optimizers(self):\n        return torch.optim.Adam(self.parameters(), lr=0.0001)\n\n\ndef colorTransform_VegGround(im,X_true,alpha_vert,alpha_g):\n    alpha = alpha_vert\n    color = [97,65,38]\n    # color = [x / 255 for x in color]\n    image=np.copy(im)\n    for c in range(3):\n        image[:, :, c] =np.where(X_true == 0,image[:, :, c] *(1 - alpha) + alpha * color[c] ,image[:, :, c])\n    alpha = alpha_g\n    color = [34,139,34]\n#    color = [x / 255 for x in color]\n    for c in range(3):\n        image[:, :, c] =np.where(X_true == 1,image[:, :, c] *(1 - alpha) + alpha * color[c] ,image[:, :, c])\n    return image \n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-08T10:21:42.258179Z","iopub.execute_input":"2025-12-08T10:21:42.258483Z","iopub.status.idle":"2025-12-08T10:21:42.274962Z","shell.execute_reply.started":"2025-12-08T10:21:42.258458Z","shell.execute_reply":"2025-12-08T10:21:42.274244Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"ckt_path = \"/kaggle/input/vegann-model-weights/VegAnn.ckpt\"\n\ncheckpoint = torch.load(ckt_path, map_location=device)\nmodel = VegAnnModel(\"Unet\", \"resnet34\", in_channels=3, out_classes=1)\nmodel.load_state_dict(checkpoint[\"state_dict\"])\nmodel.to(device)  # Move the model to the selected device\npreprocess_fn = smp.encoders.get_preprocessing_fn(\"resnet34\", pretrained=\"imagenet\")\nmodel.eval()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-08T10:21:42.275877Z","iopub.execute_input":"2025-12-08T10:21:42.276623Z","iopub.status.idle":"2025-12-08T10:21:48.458788Z","shell.execute_reply.started":"2025-12-08T10:21:42.276592Z","shell.execute_reply":"2025-12-08T10:21:48.458121Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"imname = \"/kaggle/input/csiro-biomass/train/ID1011485656.jpg\"\n\nimage = cv2.imread(imname)\nim = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n\npreprocess_input = get_preprocessing_fn('resnet34', pretrained='imagenet')\nimage = preprocess_input(im)\nimage = image.astype('float32')\n\n\ninputs = torch.tensor(image) # , dtype=float\n# print(inputs.size)\ninputs = inputs.permute(2,0,1)\ninputs = inputs[None,:,:,:]\ninputs = inputs.to(device) # Move input tensor to the selected device\n# print(inputs.shape)\nwith torch.no_grad():\n    logits = model(inputs)\npr_mask = logits.sigmoid()\n\n# Move the result back to CPU for numpy conversion and plotting\npred = (pr_mask.cpu() > 0.5).numpy().astype(np.uint8) \n\nim1_pred = colorTransform_VegGround(im,pred,0.8,0.2)\nim2_pred = colorTransform_VegGround(im,pred,0.2,0.8)\n\nfig, (ax1, ax2) = plt.subplots(1, 2)\nax1.imshow(im)\nax1.set_title(\"Input Image\")\n\nax2.imshow(im2_pred)\nax2.set_title(\"Prediction\")\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-08T10:21:48.460261Z","iopub.execute_input":"2025-12-08T10:21:48.460480Z","iopub.status.idle":"2025-12-08T10:21:49.649370Z","shell.execute_reply.started":"2025-12-08T10:21:48.460459Z","shell.execute_reply":"2025-12-08T10:21:49.648722Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import logging\nimport pathlib\nimport pandas as pd\nimport os\nimport glob\nimport numpy as np\nimport PIL\nfrom PIL import Image, ImageCms\nimport pickle\nfrom tqdm import tqdm\nimport cv2\nfrom skimage import color, io\nimport time\n#from joblib import Parallel, delayed\nimport json\nfrom pathlib import Path\n\ndef get_features(image) :\n\n    pil_image = Image.fromarray(image)\n\n    hsv = np.array(pil_image.convert(mode='HSV'))\n    srgb_p = ImageCms.createProfile(\"sRGB\")\n    lab_p  = ImageCms.createProfile(\"LAB\")\n    rgb2lab = ImageCms.buildTransformFromOpenProfiles(srgb_p, lab_p, \"RGB\", \"LAB\")\n    Lab = np.array(ImageCms.applyTransform(pil_image, rgb2lab))\n    ycbcr = np.array(pil_image.convert(mode='YCbCr'))\n    Labb = color.rgb2lab(image)\n    r = image[:, :,0]\n    g = image[:, :, 1]\n    b = image[:, :, 2]\n\n    h = ((hsv[:,:,0]) * 360) / 255\n    s = (hsv[:, :, 1]) / 2.55\n    a = Labb[:, :, 1]\n    bb = Lab[:, :, 2]\n    ge =  np.mean([r,g,b], axis = 0)\n    \n    CMYlist = [1 - r / 255, 1 - g / 255, 1 - b / 255] \n    CMYlist = np.array([np.min(idx) for idx in zip(*CMYlist)])\n    m = ((1 - g / 255 - CMYlist ) / (1 - CMYlist )) * 100\n    ye = ((1 - b / 255 - CMYlist ) / (1 - CMYlist )) * 100\n    \n    cb = ycbcr[:, :, 1]\n    cr = ycbcr[:, :, 2]\n\n    i = (0.596 * r - 0.275 * g - 0.321 * b)\n    q = (0.212 * r - 0.523 * g + 0.311 * b)\n    \n    model_input = np.stack((r,g,b,h,s,a,bb,ge,m,ye,cb,cr,i,q), axis=2).squeeze(0)\n    model_input = np.nan_to_num(model_input) # Handle black pixels (avoiding Input contains NaN, infinity or a value too large for dtype('float64') error)\n\n    return model_input\n\ndef automatic_contrast(image, clip = 0.01):\n    \n    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)\n\n    # Grayscale histogram\n    hist = cv2.calcHist([gray],[0],None,[256],[0,256])\n    hist_size = len(hist)\n\n    # Cumulative distribution from the histogram\n    cumul = []\n    cumul.append(float(hist[0]))\n    for index in range(1, hist_size):\n        cumul.append(cumul[index -1] + float(hist[index]))\n\n    # Locate points to clip\n    maximum = cumul[-1]\n    clip *= (maximum/100.0)\n    clip /= 2.0\n\n    # Left cut\n    minimum_gray = 0\n    while cumul[minimum_gray] < clip:\n        minimum_gray += 1\n\n    # Right cut\n    maximum_gray = hist_size -1\n    while cumul[maximum_gray] >= (maximum - clip):\n        maximum_gray -= 1\n\n    # Alpha and beta values\n    alpha = (255 / (maximum_gray - minimum_gray)) + 0.4\n    beta = (-minimum_gray * alpha)*2\n\n\n    # Calculate new histogram with desired range and show histogram \n    contr_result = cv2.convertScaleAbs(image, alpha=alpha, beta=beta)\n    return (contr_result, alpha, beta)\n\n\ndef prediction(image, model, threshold, contrasted, mask) :\n\n    # Ff contrasted apply image contrast\n    if contrasted == 1 :\n        image, _, _ = automatic_contrast(image)\n    \n    # Revert channels to have RGB /!\\ IMPORTANT\n    image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n    height, width = image.shape[:2]\n\n    # If Mask doesn't exist create one full of 1's\n    if mask is None:\n        mask = np.ones((height, width))\n\n    # Flatten arrays\n    image = image.reshape((width*height), 1, 3)\n    mask = mask.reshape((width*height), 1)\n\n    # Select indices of vegetation pixels\n    yellow_green_mask = np.zeros(image.shape[:-1]) # Null array to set bckg pixels to 0\n    vegetation_pixels = mask > 0 # Get vegetation pixels indices\n    image = image[None, vegetation_pixels]\n\n    # Apply preprocessing to add features to each pixels\n    featured_image = get_features(image)\n        \n    # Predictions on vegetation pixels\n    y_pred = (model.predict_proba(featured_image)[:,1] >= threshold).astype(int)\n\n    # Replace pixels of null array at vegetation indices with output of classification\n    yellow_green_mask[vegetation_pixels] = y_pred\n    # Reshape\n    mask[(mask == 1) & (yellow_green_mask != 1)] = 2\n    mask = mask.reshape((height, width))\n    # Rotate image\n    # print(mask.shape)\n    \n    return mask\n\ndef visualisation(rgb_image: np.ndarray, yg_mask: np.ndarray) -> np.ndarray:\n    \n    image_copy = rgb_image.copy()\n    image_copy[yg_mask==1] = (0,100,0)\n    image_copy[yg_mask==2] = (0,215,255)\n\n    visualisation = cv2.addWeighted(rgb_image,0.25, image_copy, 0.75, 0)\n\n    return visualisation\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-08T10:21:49.650037Z","iopub.execute_input":"2025-12-08T10:21:49.650314Z","iopub.status.idle":"2025-12-08T10:21:50.175540Z","shell.execute_reply.started":"2025-12-08T10:21:49.650291Z","shell.execute_reply":"2025-12-08T10:21:50.174951Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"xgb_model_path = \"/kaggle/input/vegann-model-weights/XGBoost\"\nmodel_XG = pickle.load(Path(xgb_model_path).open(\"rb\"))\n\nsvm_model_path = \"/kaggle/input/vegann-model-weights/model_scikit\"\nmodel_SVM = pickle.load(Path(svm_model_path).open(\"rb\"))\n\nnew_attrs = ['grow_policy', 'max_bin', 'eval_metric', 'callbacks', 'early_stopping_rounds', 'max_cat_to_onehot', 'max_leaves', 'sampling_method', 'enable_categorical', 'feature_types', 'max_cat_threshold', 'predictor']\nfor attr in new_attrs:\n    setattr(model_XG, attr, None)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-08T10:21:50.176367Z","iopub.execute_input":"2025-12-08T10:21:50.176971Z","iopub.status.idle":"2025-12-08T10:21:50.497381Z","shell.execute_reply.started":"2025-12-08T10:21:50.176942Z","shell.execute_reply":"2025-12-08T10:21:50.496671Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import xgboost\n\nimname = \"/kaggle/input/csiro-biomass/train/ID1337107565.jpg\"\n# imname = \"train/ID230058600.jpg\"\n\nimage = cv2.imread(imname)\nim_XG = cv2.imread(imname)\nim = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n\npreprocess_input = get_preprocessing_fn('resnet34', pretrained='imagenet')\nimage = preprocess_input(im)\nimage = image.astype('float32')\n\ninputs = torch.tensor(image) # , dtype=float\n# print(inputs.size)\ninputs = inputs.permute(2,0,1)\ninputs = inputs[None,:,:,:]\ninputs = inputs.to(device) # Move input tensor to the selected device\n# print(inputs.shape)\nwith torch.no_grad():\n    logits = model(inputs)\npr_mask = logits.sigmoid()\n\n# Move the result back to CPU for numpy conversion\npred = (pr_mask.cpu() > 0.75).numpy().astype(np.uint8) \n\n\nim1_pred = colorTransform_VegGround(im,pred,0.8,0.2)\nim2_pred = colorTransform_VegGround(im,pred,0.2,0.8)\n\nraw_yellow_green_mask = prediction(im_XG, model_XG, mask=pred, threshold=0.5, contrasted=1)\nprint(np.unique(raw_yellow_green_mask))\nraw_visu = visualisation(im_XG, raw_yellow_green_mask)\nraw_visu = cv2.cvtColor(raw_visu, cv2.COLOR_BGR2RGB)\nprint(raw_visu.shape)\n\nyellow_green_mask = cv2.erode(raw_yellow_green_mask, np.ones((2,2), np.uint8), iterations=1)\nyellow_green_mask = cv2.GaussianBlur(yellow_green_mask, (3,3), 0)\nvisu = visualisation(im_XG, yellow_green_mask)\nvisu = cv2.cvtColor(visu, cv2.COLOR_BGR2RGB)\n\nprint(\"### XGBoost Model ###\")\nfig, (ax1, ax2, ax3, ax4) = plt.subplots(1, 4)\nfig.set_size_inches(18.5, 10.5)\nax1.imshow(im)\nax1.set_title(\"Input Image\")\n\nax2.imshow(im2_pred)\nax2.set_title(\"Vegetation Background Prediction\")\n\nax3.imshow(raw_visu)\nax3.set_title(\"RAW Senescent Green Prediction\")\n\nax4.imshow(visu)\nax4.set_title(\"Smoothed + Erode Senescent Green Prediction\")\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-08T10:21:50.498095Z","iopub.execute_input":"2025-12-08T10:21:50.498326Z","iopub.status.idle":"2025-12-08T10:21:54.248925Z","shell.execute_reply.started":"2025-12-08T10:21:50.498307Z","shell.execute_reply":"2025-12-08T10:21:54.248253Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport glob\nimport numpy as np\nimport cv2\nimport torch\nfrom tqdm import tqdm\nfrom pathlib import Path\n\ninput_dir = \"/kaggle/input/csiro-biomass/train/\"\noutput_dir = \"/kaggle/working/masks/\"\nos.makedirs(output_dir, exist_ok=True)\nshow = True\n\nimage_files = glob.glob(os.path.join(input_dir, \"*.jpg\"))\n\npreprocess_input_vegann = get_preprocessing_fn('resnet34', pretrained='imagenet')\n\nprint(f\"Found {len(image_files)} images to process.\")\n\nfor imname in tqdm(image_files, desc=\"Generating masks\"):\n    try:\n        image_rgb = cv2.imread(imname)\n        image_rgb = cv2.cvtColor(image_rgb, cv2.COLOR_BGR2RGB)\n\n        image_preprocessed = preprocess_input_vegann(image_rgb).astype('float32')\n\n        inputs = torch.tensor(image_preprocessed)\n        inputs = inputs.permute(2, 0, 1)\n        inputs = inputs.unsqueeze(0)\n        inputs = inputs.to(device)\n\n        with torch.no_grad():\n            logits = model(inputs)\n\n        pr_mask = logits.sigmoid()\n        vegetation_mask = (pr_mask.cpu() > 0.5).numpy().astype(np.uint8).squeeze()\n\n        image_bgr = cv2.imread(imname)\n\n        veg_mask_for_xgb = vegetation_mask\n\n        raw_yellow_green_mask = prediction(image_bgr, model_XG, mask=veg_mask_for_xgb, threshold=0.9, contrasted=1)\n\n        senescent_mask = (raw_yellow_green_mask == 2).astype(np.uint8)\n\n        combined_mask = np.stack((vegetation_mask, senescent_mask), axis=-1)\n        \n        if show:\n            vegetation_mask = combined_mask[:, :, 0]\n            senescent_mask = combined_mask[:, :, 1]\n            \n            overlay_image = image_rgb.copy()\n            overlay_image[vegetation_mask == 1] = [0, 0, 255] # BLUE for vegetation\n            overlay_image[senescent_mask == 1] = [255, 0, 0] # RED for senescent\n            \n            fig, axes = plt.subplots(1, 4, figsize=(20, 5))\n            axes[0].imshow(image_rgb)\n            axes[0].set_title(\"Original Image\")\n            axes[0].axis(\"off\")\n            \n            axes[1].imshow(vegetation_mask)\n            axes[1].set_title(\"Vegetation Mask\")\n            axes[1].axis(\"off\")\n            \n            axes[2].imshow(senescent_mask)\n            axes[2].set_title(\"Senescent Mask\")\n            axes[2].axis(\"off\")\n            \n            axes[3].imshow(overlay_image)\n            axes[3].set_title(\"Combined Overlay\")\n            axes[3].axis(\"off\")\n            # plt.tight_layout()\n            plt.show()\n            \n        # Save the combined mask\n        base_filename = Path(imname).stem\n        output_filename = os.path.join(output_dir, f\"{base_filename}.npy\")\n        np.save(output_filename, combined_mask)\n        show = False\n\n    except Exception as e:\n        print(f\"Failed to process {imname}: {e}\")\n\nsaved_files = glob.glob(os.path.join(output_dir, \"*.npy\"))\nprint(f\"Successfully generated and saved {len(saved_files)} masks.\")\n\nif len(saved_files) == len(image_files):\n    print(\"All images were processed successfully.\")\nelse:\n    print(f\"Warning: {len(image_files) - len(saved_files)} images failed to process.\")\n\nif saved_files:\n    sample_mask = np.load(saved_files[0])\n    print(f\"\\nSample mask shape: {sample_mask.shape}\")\n    print(f\"Data type: {sample_mask.dtype}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-08T11:16:29.710505Z","iopub.execute_input":"2025-12-08T11:16:29.710768Z","iopub.status.idle":"2025-12-08T11:32:08.657827Z","shell.execute_reply.started":"2025-12-08T11:16:29.710749Z","shell.execute_reply":"2025-12-08T11:32:08.657050Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nimport random\n\nsaved_mask_files = glob.glob(os.path.join(output_dir, \"*.npy\"))\n\nif saved_mask_files:\n    sample_mask_file = \"/kaggle/working/masks/ID1337107565.npy\"\n    \n    base_name = Path(sample_mask_file).stem\n    original_image_path = os.path.join(input_dir, f\"{base_name}.jpg\")\n\n    original_image = cv2.imread(original_image_path)\n    original_image = cv2.cvtColor(original_image, cv2.COLOR_BGR2RGB)\n    \n    combined_mask = np.load(sample_mask_file)\n    print(combined_mask.shape)\n    \n    vegetation_mask = combined_mask[:, :, 0]\n    senescent_mask = combined_mask[:, :, 1]\n    \n    overlay_image = original_image.copy()\n    overlay_image[vegetation_mask == 1] = [0, 255, 0] # GREEN for vegetation\n    overlay_image[senescent_mask == 1] = [255, 0, 0] # RED for senescent\n    \n    fig, axes = plt.subplots(1, 4, figsize=(20, 5))\n    \n    axes[0].imshow(original_image)\n    axes[0].set_title(\"Original Image\")\n    axes[0].axis(\"off\")\n    \n    axes[1].imshow(vegetation_mask)\n    axes[1].set_title(\"Vegetation Mask\")\n    axes[1].axis(\"off\")\n    \n    axes[2].imshow(senescent_mask)\n    axes[2].set_title(\"Senescent Mask\")\n    axes[2].axis(\"off\")\n    \n    axes[3].imshow(overlay_image)\n    axes[3].set_title(\"Combined Overlay\")\n    axes[3].axis(\"off\")\n    \n    plt.tight_layout()\n    plt.show()\n\nelse:\n    print(\"No saved masks found to display. Please run the cell above to generate them first.\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-08T11:32:08.659509Z","iopub.execute_input":"2025-12-08T11:32:08.659725Z","iopub.status.idle":"2025-12-08T11:32:09.644462Z","shell.execute_reply.started":"2025-12-08T11:32:08.659708Z","shell.execute_reply":"2025-12-08T11:32:09.643573Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!zip -r result.zip \"/kaggle/working/masks\"\n\nfrom IPython.display import FileLink\nFileLink(r'result.zip')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-08T11:37:59.433000Z","iopub.execute_input":"2025-12-08T11:37:59.433310Z","iopub.status.idle":"2025-12-08T11:38:35.870736Z","shell.execute_reply.started":"2025-12-08T11:37:59.433289Z","shell.execute_reply":"2025-12-08T11:38:35.870044Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}