{"id":24302643,"url":"https://github.com/rosnavigator/ann-mars-terrain-segmentation","last_synced_at":"2026-04-11T17:02:42.213Z","repository":{"id":269032387,"uuid":"895792397","full_name":"RosNaviGator/ANN-Mars-Terrain-Segmentation","owner":"RosNaviGator","description":"Competition in Artificial Neural Networks \u0026 Deep Learning course (2024-2025). In this assignment we received 64x128 grayscale real images from Mars terrain. Pixels in these images are categorized into five classes, each representing a particular type of terrain. This is a semantic segmentation problem, the goal is to correctly classify the pixels.","archived":false,"fork":false,"pushed_at":"2024-12-20T12:02:17.000Z","size":3092,"stargazers_count":0,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-07-17T16:59:42.613Z","etag":null,"topics":["airlab","challenge","cnn","computer-vision","convolutional-neural-networks","keras","machine-learning","mars","neural","neural-network","polimi","python","segmentation","semantic-segmentation","tensorflow","terrain"],"latest_commit_sha":null,"homepage":"","language":"Jupyter Notebook","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/RosNaviGator.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-11-28T23:15:18.000Z","updated_at":"2024-12-20T12:06:17.000Z","dependencies_parsed_at":"2024-12-20T13:18:24.210Z","dependency_job_id":"b591b6a8-5059-4cea-a2ef-58b0089eeb35","html_url":"https://github.com/RosNaviGator/ANN-Mars-Terrain-Segmentation","commit_stats":null,"previous_names":["rosnavigator/ann-mars-terrain-segmentation"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/RosNaviGator/ANN-Mars-Terrain-Segmentation","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RosNaviGator%2FANN-Mars-Terrain-Segmentation","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RosNaviGator%2FANN-Mars-Terrain-Segmentation/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RosNaviGator%2FANN-Mars-Terrain-Segmentation/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RosNaviGator%2FANN-Mars-Terrain-Segmentation/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/RosNaviGator","download_url":"https://codeload.github.com/RosNaviGator/ANN-Mars-Terrain-Segmentation/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RosNaviGator%2FANN-Mars-Terrain-Segmentation/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31687882,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-11T13:07:20.380Z","status":"ssl_error","status_checked_at":"2026-04-11T13:06:47.903Z","response_time":54,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["airlab","challenge","cnn","computer-vision","convolutional-neural-networks","keras","machine-learning","mars","neural","neural-network","polimi","python","segmentation","semantic-segmentation","tensorflow","terrain"],"created_at":"2025-01-17T00:17:54.375Z","updated_at":"2026-04-11T17:02:42.183Z","avatar_url":"https://github.com/RosNaviGator.png","language":"Jupyter Notebook","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Artificial Neural Networks and Deep Learning 2024\n\n---\n\n## Homework 2: Mars Terrain Semantic Segmentation with Deep Learning\n\n### Team Members\n- Maria Aurora Bertasini*\n- Marco Cioci*\n- Francesco Rosnati*\n- Luca Tramacere*\n\n*Master's candidate in High Performance Computing Engineering at Politecnico di Milano\n\n\n## About\nThis project focuses on semantic segmentation of 64x128 grayscale Mars terrain images, assigning each pixel to one of five terrain classes. Paired masks provide pixel-wise labels. The goal is to build a model for accurate classification, evaluated by mean intersection over union (mIoU) excluding the background class (label 0).\nThis template serves as a comprehensive tool for testing and running various models. To use it, create a `model.py` file and import it in the model section.\n\nThe most notable models we tested are stored in the `models` folder, while the best version is executed in [`FinalModel.ipynb`](./FinalModel.ipynb).\n\n![fig_intro_](./images/template.png)\n## Name your model\n```python\n# Model name\nmodel_name = 'template'\n```\n\n```python\nfrom datetime import datetime\n\n# Generate timestamp\ntimestamp = datetime.now().strftime(\"%y%m%d_%H%M%S\")\n\n# Unified filename for both the final model and checkpoint\nmodel_filename = f\"model_{model_name}_{timestamp}.keras\"\n```\n\n## Connect Colab to Google Drive\n```python\nCOLAB = True\n\nif COLAB:\n    print(\"COLAB\")\n    from google.colab import drive\n\n    drive.mount(\"/gdrive\")\n    %cd /gdrive/My Drive\nelse:\n    print(\"NO COLAB\")\n```\n\n\n## Import Libraries\n```python\n# General imports\nimport os\nimport random\nfrom datetime import datetime\n\n# Numerical and data manipulation\nimport numpy as np\nimport pandas as pd\n\n# TensorFlow and Keras\nimport tensorflow as tf\nfrom tensorflow import keras as tfk\nfrom tensorflow.keras import layers as tfkl\nfrom tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint\n\n# Mixed precision setup\nfrom tensorflow.keras import mixed_precision\nmixed_precision.set_global_policy('mixed_float16')\n\n# GPU configuration\nphysical_devices = tf.config.list_physical_devices('GPU')\nfor device in physical_devices:\n    tf.config.experimental.set_memory_growth(device, True)\n\n# Sklearn for train-test splitting\nfrom sklearn.model_selection import train_test_split\n\n# OpenCV for image processing\nimport cv2\n\n# tqdm for progress visualization\nfrom tqdm import tqdm\n\n# Matplotlib for visualization\nimport matplotlib.pyplot as plt\nfrom matplotlib import cm\nfrom matplotlib import colors\n%matplotlib inline\n\n# Set seeds for reproducibility\nnp.random.seed(42)\ntf.random.set_seed(42)\n\n# Print environment details\nprint()\nprint(f\"TensorFlow version: {tf.__version__}\")\nprint(f\"Keras version: {tfk.__version__}\")\nprint(f\"GPU devices: {len(tf.config.list_physical_devices('GPU'))}\")\n```\n\n\n## Load Data\nA cleaning process was applied to the training data to address the presence of outliers. Specifically, images with objectively incorrect masks were identified and removed, while for images with uncertain mask quality, most were retained to preserve diversity and encourage generalization.\n\nThe final dataset used for training was saved as `clean_dataset.npz`.\n\n```python\n# --------------- #\n# Load the dataset\n# --------------- #\n\ndata_name = 'clean_dataset.npz'\nfile_path = f\"data/{data_name}\"\ndata = np.load(file_path)\n\n\ntraining_set = data[\"training_set\"]\nX_train = np.stack(training_set[:, 0], axis=0)  # Images\ny_train = np.stack(training_set[:, 1], axis=0)  # Masks\n\nX_test = data[\"test_set\"]  # Test Images\n\n# Preprocess images and masks\nif X_train.ndim == 3:\n    X_train = X_train[..., np.newaxis]\nif X_test.ndim == 3:\n    X_test = X_test[..., np.newaxis]\n\nX_train = X_train / 255.0\nX_test = X_test / 255.0\n\nif y_train.ndim == 3:\n    y_train = y_train[..., np.newaxis]\n\noriginal_count = X_train.shape[0]\nprint(f\"Number of images the {data_name} dataset: {original_count}\")\n\n# Define label mapping\nlabel_mapping = {\n    0: 'Background',\n    1: 'Soil',\n    2: 'Bedrock',\n    3: 'Sand',\n    4: 'Big Rock'\n}\n\ncategory_map = {key: key for key in label_mapping.keys()}\n```\n\n```python\n# Split data into training and validation sets\nX_train_final, X_val, y_train_final, y_val = train_test_split(\n    X_train, y_train, test_size=0.2, random_state=42\n)\n\n# Print shapes of the datasets\nprint(\"Shape of X_train_final:\", X_train_final.shape)\nprint(\"Shape of X_val:\", X_val.shape)\nprint(\"Shape of y_train_final:\", y_train_final.shape)\nprint(\"Shape of y_val:\", y_val.shape)\n```\n\n## Augmentations\nAll the augmentation techniques we experimented with are detailed in the [`Augmentations.ipynb`](./Augmentations.ipynb) notebook.  \n\nIn this template, we propose the augmentation strategy that was ultimately selected. To use it, import all the necessary functions from [`AugmentationsUtils.py`](./AugmentationsUtils.py). \n\nThe `apply_augmentations` function applies the selected augmentations to the training set while retaining the original images. Additionally, it plots example outputs to visualize the effects of the augmentations.\n\n\n```python\nfrom AugmentationsUtils import *\n\n# Augment the training set and plot images for the specified index\naugmented_X_train, augmented_y_train = apply_augmentations(\n    X_train_final,\n    y_train_final,\n    num_augmented= 3,  # Number of augmented versions per original image\n    plot_index= 0  # Index of the image to plot\n)\n```\n\n\n##  Model\nPlease import your model from the `models` folder. This folder contains some of our best models, including the final model (nome.py). \n\nThe final model ([`final_dual_branch.py`](./models/final_dual_branch.py)) is also imported and executed in the [`FinalModel.ipynb`](./FinalModel.ipynb) notebook.\n\n```python\nfrom models.you_name_model import *\n\n# Instantiate the model\ninput_shape = augmented_X_train.shape[1:]  # Use the shape of the input images\nnum_classes = 5  # As per dataset details\nmodel = get_model(input_shape=input_shape, num_classes=num_classes, dropout_rate=0, l2_reg=1e-4)\n```\n\n### Custom functions and metrics definition\n\n```python\n# -------------------------------------- #\n#                  Metric                #\n# -------------------------------------- #\n\nclass MeanIntersectionOverUnion(tf.keras.metrics.MeanIoU):\n    def __init__(self, num_classes, labels_to_exclude=None, name=\"mean_iou\", dtype=None):\n        super(MeanIntersectionOverUnion, self).__init__(num_classes=num_classes, name=name, dtype=dtype)\n        if labels_to_exclude is None:\n            labels_to_exclude = [0]  # Default to excluding label 0\n        self.labels_to_exclude = labels_to_exclude\n\n    def update_state(self, y_true, y_pred, sample_weight=None):\n        # Convert predictions to class labels\n        y_pred = tf.math.argmax(y_pred, axis=-1)\n\n        # Flatten the tensors\n        y_true = tf.reshape(y_true, [-1])\n        y_pred = tf.reshape(y_pred, [-1])\n\n        # Apply mask to exclude specified labels\n        mask = tf.reduce_all([tf.not_equal(y_true, label) for label in self.labels_to_exclude], axis=0)\n        y_true = tf.boolean_mask(y_true, mask)\n        y_pred = tf.boolean_mask(y_pred, mask)\n\n        # Update the state\n        return super().update_state(y_true, y_pred, sample_weight)\n    \n\n# -------------------------------------- #\n#              Loss Functions            #\n# -------------------------------------- #\n\n\ndef mean_iou_loss(num_classes, labels_to_exclude=None, epsilon=1e-6):\n    \"\"\"\n    Mean IoU Loss Function.\n\n    Args:\n        num_classes: Number of classes in the segmentation task.\n        labels_to_exclude: List of labels to exclude when calculating IoU.\n    Returns:\n        A function that calculates the Mean IoU loss.\n    \"\"\"\n    def loss(y_true, y_pred):\n        # Convert predictions to class probabilities\n        y_pred = tf.nn.softmax(y_pred, axis=-1)\n\n        # Convert true labels to one-hot encoding\n        y_true_one_hot = tf.one_hot(tf.cast(tf.squeeze(y_true, axis=-1), tf.int32), num_classes)\n\n        # Flatten the tensors\n        y_true_flat = tf.reshape(y_true_one_hot, [-1, num_classes])\n        y_pred_flat = tf.reshape(y_pred, [-1, num_classes])\n\n        # Compute intersection and union\n        intersection = tf.reduce_sum(y_true_flat * y_pred_flat, axis=0)\n        union = tf.reduce_sum(y_true_flat + y_pred_flat, axis=0) - intersection\n\n        # Mask out excluded labels\n        if labels_to_exclude is not None:\n            for label in labels_to_exclude:\n                intersection = tf.tensor_scatter_nd_update(\n                    intersection,\n                    [[label]],\n                    [0.0]\n                )\n                union = tf.tensor_scatter_nd_update(\n                    union,\n                    [[label]],\n                    [0.0]\n                )\n\n        # Compute IoU for each class and average over classes\n        iou = (intersection + epsilon) / (union + epsilon)\n        mean_iou = tf.reduce_mean(iou)\n\n        # Invert to use as loss (lower IoU means higher loss)\n        return 1.0 - mean_iou\n\n    return loss\n\n\ndef focal_loss_ignore_class_0(alpha=0.25, gamma=2.0, num_classes=None):\n    \"\"\"\n    Focal Loss Function that ignores class 0, with fixed alpha for all other classes.\n\n    Args:\n        alpha: Weighting factor for positive classes to address class imbalance.\n        gamma: Focusing parameter to down-weight easy examples.\n        num_classes: Number of classes in the segmentation task.\n    Returns:\n        A function that calculates the Focal Loss, ignoring class 0.\n    \"\"\"\n    def loss(y_true, y_pred):\n        # Convert predictions to class probabilities (softmax output)\n        y_pred = tf.nn.softmax(y_pred, axis=-1)\n\n        # Convert true labels to one-hot encoding\n        y_true_one_hot = tf.one_hot(tf.cast(tf.squeeze(y_true, axis=-1), tf.int32), num_classes)\n\n        # Ignore class 0 in the loss calculation\n        y_true_no_bg = y_true_one_hot[..., 1:]  # Remove the first class (class 0)\n        y_pred_no_bg = y_pred[..., 1:]         # Remove the first class (class 0)\n\n        # Compute the focal loss\n        cross_entropy = -y_true_no_bg * tf.math.log(y_pred_no_bg + 1e-8)  # Cross entropy with stability\n\n        # Apply a fixed alpha for all classes and calculate the focal loss\n        weight = alpha * tf.math.pow(1 - y_pred_no_bg, gamma)  # Focusing weight\n        focal_loss = weight * cross_entropy\n\n        # Average over all pixels and classes (across the batch and spatial dimensions)\n        return tf.reduce_mean(tf.reduce_sum(focal_loss, axis=-1))\n\n    return loss\n\n# -------------------------------------- #\n#                   Tools                #\n# -------------------------------------- #\n\ndef apply_category_mapping(label):\n    \"\"\"\n    Apply category mapping to labels.\n    \"\"\"\n    keys_tensor = tf.constant(list(category_map.keys()), dtype=tf.int32)\n    vals_tensor = tf.constant(list(category_map.values()), dtype=tf.int32)\n\n    # Create the lookup table\n    table = tf.lookup.StaticHashTable(\n        tf.lookup.KeyValueTensorInitializer(keys_tensor, vals_tensor),\n        default_value=0\n    )\n\n    # Ensure the label is a Tensor\n    label = tf.convert_to_tensor(label, dtype=tf.int32)\n\n    return table.lookup(label)\n\n\ndef create_segmentation_colormap(num_classes):\n    \"\"\"\n    Create a linear colormap using a predefined palette.\n    Uses 'viridis' as default because it is perceptually uniform\n    and works well for colorblindness.\n    \"\"\"\n    return plt.cm.viridis(np.linspace(0, 1, num_classes))\n\n\ndef apply_colormap(label, colormap=None):\n    \"\"\"\n    Apply the colormap to a label.\n    \"\"\"\n    # Ensure label is 2D\n    label = np.squeeze(label)\n\n    if colormap is None:\n        num_classes = len(np.unique(label))\n        colormap = create_segmentation_colormap(num_classes)\n\n    # Apply the colormap\n    colored = colormap[label.astype(int)]\n\n    return colored\n\nclass VizCallback(tf.keras.callbacks.Callback):\n    def __init__(self, images, labels, frequency=5):\n        \"\"\"\n        Initialize the visualization callback.\n\n        Args:\n            images: A batch of validation images (numpy array or tensor).\n            labels: Corresponding ground truth labels (numpy array or tensor).\n            frequency: Frequency (in epochs) to display visualizations.\n        \"\"\"\n        super().__init__()\n        self.images = images\n        self.labels = labels\n        self.frequency = frequency\n\n    def on_epoch_end(self, epoch, logs=None):\n        if epoch % self.frequency == 0:  # Visualize only every \"frequency\" epochs\n            batch_size = len(self.images)\n            predictions = self.model.predict(self.images, verbose=0)\n            y_preds = tf.math.argmax(predictions, axis=-1).numpy()\n\n            # Define label mapping and custom colors\n            label_mapping = {\n                0: 'Background',\n                1: 'Soil',\n                2: 'Bedrock',\n                3: 'Sand',\n                4: 'Big Rock'\n            }\n            colors = [\n                '#000000',  # 0: Background \n                '#A95F44',  # 1: Soil \n                '#584C44',  # 2: Bedrock \n                '#C5986F',  # 3: Sand \n                '#A69980'   # 4: Big Rock \n            ]\n            cmap = ListedColormap(colors)\n\n            # Create legend patches\n            patches = [mpatches.Patch(color=colors[i], label=label_mapping[i]) for i in label_mapping]\n\n            for i in range(batch_size):\n                image = self.images[i]\n                label = self.labels[i]\n                label = apply_category_mapping(label)\n                y_pred = y_preds[i]\n\n                plt.figure(figsize=(18, 6))\n\n                # Input image\n                plt.subplot(1, 3, 1)\n                plt.title(f'Input Image (Sample {i})')\n                if image.shape[-1] == 1:\n                    plt.imshow(image.squeeze(), cmap='gray')\n                else:\n                    plt.imshow(image)\n                plt.axis('off')\n\n                # Ground truth mask\n                plt.subplot(1, 3, 2)\n                plt.title('Ground Truth Mask')\n                plt.imshow(label.numpy().squeeze(), cmap=cmap, vmin=0, vmax=len(label_mapping) - 1)\n                plt.axis('off')\n\n                # Predicted mask\n                plt.subplot(1, 3, 3)\n                plt.title('Predicted Mask')\n                plt.imshow(y_pred.squeeze(), cmap=cmap, vmin=0, vmax=len(label_mapping) - 1)\n                plt.axis('off')\n\n                # Add legend\n                plt.legend(handles=patches, bbox_to_anchor=(1.05, 1), loc='upper left', borderaxespad=0.)\n\n                plt.tight_layout()\n                plt.show()\n\n```\n\n\n### Training settings\nPlease substiutite your parameters\n\n```python\n# OPTIMIZER\noptimizer = tf.keras.optimizers.AdamW(\n    learning_rate=1e-3,   # Starting learning rate\n    weight_decay=1e-4     # Regularization strength\n)\n\n# METRIC\nmetric_main = 'sparse_categorical_accuracy'\n\n# LOSS FUNCTION\nloss_fn = focal_loss_ignore_class_0(alpha=0.25, gamma=2.0)\n\n# Compile the model\nmodel.compile(\n    optimizer=optimizer,\n    loss=loss_fn,\n    metrics=[metric_main, MeanIntersectionOverUnion(num_classes=5, labels_to_exclude=[0])]\n)\n```\n\n```python\n# Early stopping and learning rate scheduler\nearly_stopping = tf.keras.callbacks.EarlyStopping(\n    monitor='val_mean_iou',   # Metric to monitor\n    patience=10,                # Number of epochs with no improvement after which training will be stopped\n    restore_best_weights=True   # Restore the best model found\n)\n\nlr_scheduler = tf.keras.callbacks.ReduceLROnPlateau(\n    monitor='val_mean_iou',    # Metric to monitor\n    factor=0.5,            # Factor by which the learning rate will be reduced\n    patience=5,            # Number of epochs with no improvement after which learning rate will be reduced\n    verbose=1,             # Verbosity mode\n    min_lr=1e-6            # Lower bound on the learning rate\n)\n\n# Add ModelCheckpoint callback to save the best model based on validation loss\nmodel_checkpoint = tf.keras.callbacks.ModelCheckpoint(\n    filepath=model_filename,  # Use the unified filename\n    monitor='val_mean_iou',  # Monitor validation metrics\n    save_best_only=True,  # Save only the best model\n    mode='max',  # Mode to maximize the monitored quantity\n    verbose=1\n)\n```\n\n```python\nbatch_size = 16\nepochs = 1000\nseed = 42\n\nviz_callback = VizCallback(X_val[:2], y_val[:2])  # Visualize first 2 validation images\n\n# Choose which callbacks to activate\ncallbacks = [early_stopping, lr_scheduler, viz_callback, model_checkpoint]\n\nhistory = model.fit(\n    augmented_X_train, augmented_y_train,\n    batch_size=batch_size,\n    epochs=epochs,\n    validation_data=(X_val, y_val),\n    callbacks=callbacks,\n    verbose=1\n)\n\n# Retrieve best epoch and corresponding metrics from EarlyStopping based on Mean IoU\nbest_epoch = history.history['val_mean_iou'].index(max(history.history['val_mean_iou']))  # Index of the best validation mIoU\nbest_val_miou = max(history.history['val_mean_iou'])  # Best validation mIoU\nbest_val_loss = history.history['val_loss'][best_epoch]  # Best validation loss at that epoch\n\n# Print the best metrics\nprint(f\"Training completed after {best_epoch + 1} epochs (Early Stopping).\")\nprint(f\"Best Validation Mean IoU: {best_val_miou:.4f}\")\nprint(f\"Corresponding Validation Loss: {best_val_loss:.4f}\")\n\n# Save the final model after training\nmodel.save(model_filename)\nprint(f\"Model saved to {model_filename}\")\n```\n\n\n## Predictions\n\n```python\n# Load the most recent model\nmodel = tfk.models.load_model(model_filename, compile = False)\nprint(f\"Model loaded from {model_filename}\")\n\nmodel.compile(\n    optimizer=optimizer,\n    loss=loss_fn,\n    metrics=[metric_main, MeanIntersectionOverUnion(num_classes=5, labels_to_exclude=[0])]\n)\n\n# Predictions\npreds = model.predict(X_test)\npreds = np.argmax(preds, axis=-1)\nprint(f\"Predictions shape: {preds.shape}\")\n\n# Convert predictions to DataFrame\ndef y_to_df(y):\n    n_samples = len(y)\n    y_flat = y.reshape(n_samples, -1)\n    df = pd.DataFrame(y_flat)\n    df[\"id\"] = np.arange(n_samples)\n    cols = [\"id\"] + [col for col in df.columns if col != \"id\"]\n    return df[cols]\n\n# Save predictions to CSV\nsubmission_filename = f\"sub_{model_name}_{timestamp}.csv\"\nsubmission_df = y_to_df(preds)\nsubmission_df.to_csv(submission_filename, index=False)\n\n# Download submission\nif COLAB:\n    from google.colab import files\n    files.download(submission_filename)\n\n\nprint(f\"Submission saved to {submission_filename}\")\n```\n\n\n### Visualize Predictions\n\n```python\n# Function to visualize predictions for a list of indices with a legend\ndef visualize_predictions(idx_list):\n    # Ensure idx_list is a list even if a single integer is provided\n    if isinstance(idx_list, int):\n        idx_list = [idx_list]\n\n    # Create legend patches\n    patches = [mpatches.Patch(color=colors[i], label=label_mapping[i]) for i in label_mapping]\n\n    for idx in idx_list:\n        img = X_val[idx]\n        true_mask = y_val[idx]\n        pred_mask = model.predict(img[np.newaxis, ...])[0]\n        pred_mask = np.argmax(pred_mask, axis=-1)\n\n        plt.figure(figsize=(18, 6))\n\n        # Input Image\n        plt.subplot(1, 3, 1)\n        plt.title(f'Input Image (Index: {idx})')\n        if img.shape[-1] == 1:\n            plt.imshow(img.squeeze(), cmap='gray')\n        else:\n            plt.imshow(img)\n        plt.axis('off')\n\n        # True Mask\n        plt.subplot(1, 3, 2)\n        plt.title('True Mask')\n        plt.imshow(true_mask.squeeze(), cmap=cmap, vmin=0, vmax=len(label_mapping)-1)\n        plt.axis('off')\n\n        # Predicted Mask\n        plt.subplot(1, 3, 3)\n        plt.title('Predicted Mask')\n        plt.imshow(pred_mask.squeeze(), cmap=cmap, vmin=0, vmax=len(label_mapping)-1)\n        plt.axis('off')\n\n        # Add Legend\n        plt.legend(handles=patches, bbox_to_anchor=(1.05, 1), loc='upper left', borderaxespad=0.)\n\n        plt.tight_layout()\n        plt.show()\n\n# Example usage with a list of indices\nvisualize_predictions([i for i in range(27, 37)])  # Adjust the range as needed\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frosnavigator%2Fann-mars-terrain-segmentation","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frosnavigator%2Fann-mars-terrain-segmentation","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frosnavigator%2Fann-mars-terrain-segmentation/lists"}