{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.6.6","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":9988,"databundleVersionId":868324,"sourceType":"competition"},{"sourceId":13087109,"sourceType":"datasetVersion","datasetId":8289206}],"dockerImageVersionId":11105,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"**<h2>Introduction**","metadata":{"_uuid":"18c735c4a3e7d53bbcd82dfd676240301cb075e0"}},{"cell_type":"markdown","source":"# Exploring the Source Data\nThe Airbus Ship Detection dataset consists of satellite imagery (768x768 pixels) from Airbus SPOT satellites, with over 200,000 training images and corresponding run-length encoded (RLE) masks for ship segmentation. It includes a train set ( ~ 192,000 images, ~ 22.1% containing ships), test set ( ~ 60,000 images), and CSV files for annotations. File sizes total around 4–5 GB compressed.\n\nThe dataset's structure includes RGB images in TIFF format, with ship locations annotated via RLE-encoded binary masks ina CSV file (train_ship_segmentations_v2.csv).\n\n\n\n\n\n","metadata":{}},{"cell_type":"markdown","source":"Now, I will try to explore the Airbus Ship Detection Challenge dataset and get some sense of what types of features may be useful.","metadata":{"_uuid":"d30127c5abec33501bff60055ab83187a546a5c6"}},{"cell_type":"code","source":"import os\nimport sys\nimport random\nimport warnings\n\nimport pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\n%matplotlib inline\n\nfrom tqdm import tqdm_notebook, tnrange\nfrom skimage.io import imread, imshow, concatenate_images\nfrom skimage.transform import resize\nfrom skimage.morphology import label\nfrom sklearn.model_selection import train_test_split\nfrom keras.preprocessing.image import array_to_img, img_to_array, load_img\nfrom skimage.feature import canny\nfrom skimage.filters import sobel,threshold_otsu, threshold_niblack,threshold_sauvola\nfrom skimage.segmentation import felzenszwalb, slic, quickshift, watershed\nfrom skimage.segmentation import mark_boundaries\nfrom scipy import signal\n\nimport cv2\nfrom PIL import Image\nimport pdb\nfrom tqdm import tqdm\nimport seaborn as sns\nimport os \nfrom glob import glob\n\nimport warnings\nwarnings.filterwarnings(\"ignore\")","metadata":{"trusted":true,"_uuid":"f926376d1380568f248346353c42fcf599e6c0e6","_kg_hide-input":true,"execution":{"iopub.status.busy":"2025-09-17T11:58:27.034502Z","iopub.execute_input":"2025-09-17T11:58:27.034802Z","iopub.status.idle":"2025-09-17T11:58:27.044284Z","shell.execute_reply.started":"2025-09-17T11:58:27.034756Z","shell.execute_reply":"2025-09-17T11:58:27.043555Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"<h2> Setting paths","metadata":{"_uuid":"d8d19639fac647b9933ba36071a63ea26a638416"}},{"cell_type":"code","source":"import os\n\n# --- The Single Source of Truth for All Paths ---\n\n# 1. Define the base path for the competition data\nCOMPETITION_INPUT_DIR = '/kaggle/input/airbus-ship-detection/'\n\n# 2. Define the path for our curated SAR dataset\nSAR_INPUT_DIR = '/kaggle/input/ssdd-sar-images/JPEGImages/'\n\n# 3. Define the path for our temporary working/output files\nWORKING_DIR = '/kaggle/working/'\n\n# 4. Define the specific file and folder paths we will use\nOPTICAL_TRAIN_IMAGES_FULL = os.path.join(COMPETITION_INPUT_DIR, 'train_v2')\nOPTICAL_ANNOTATIONS_FULL = os.path.join(COMPETITION_INPUT_DIR, 'train_ship_segmentations_v2.csv')\n\nprint(f\"Competition Data Path: {COMPETITION_INPUT_DIR}\")\nprint(f\"SAR Data Path:         {SAR_INPUT_DIR}\")\nprint(f\"Working Directory:     {WORKING_DIR}\")\nprint(\"\\nAll core paths are now defined correctly.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-17T12:17:47.498869Z","iopub.execute_input":"2025-09-17T12:17:47.499209Z","iopub.status.idle":"2025-09-17T12:17:47.504830Z","shell.execute_reply.started":"2025-09-17T12:17:47.499153Z","shell.execute_reply":"2025-09-17T12:17:47.504143Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"<h2>Plotting Images","metadata":{"_uuid":"338497fc51d3a8876a67ff056faaa9de1f2e61b7"}},{"cell_type":"markdown","source":"Lets plot some random images from training set and then few more images with the mask overlayed on top of it.","metadata":{"_uuid":"fb586a4d1e99e575880aafcb3a04c683e95253ba"}},{"cell_type":"code","source":"import pandas as pd\nimport os\nimport numpy as np\nimport random\nimport matplotlib.pyplot as plt\nfrom skimage.io import imread\n\n# --- This script uses the path variables defined in your previous cell ---\n# It assumes 'OPTICAL_ANNOTATIONS_FULL' and 'OPTICAL_TRAIN_IMAGES_FULL' are set.\n\nprint(\"--- Starting Exploration of the Optical Ship Dataset ---\")\n\n# --- 1. Load and Analyze the Annotation Data ---\nprint(f\"Loading annotations from: {OPTICAL_ANNOTATIONS_FULL}\")\ndf = pd.read_csv(OPTICAL_ANNOTATIONS_FULL)\n\nprint(\"\\nFirst 5 rows of the annotation file:\")\nprint(df.head())\n\n# --- 2. Process and Understand the Data Structure ---\n# The key insight is that images without ships have a NaN value for EncodedPixels.\n# An image can also have multiple rows if it contains multiple ships.\n\n# Create a 'ship_count' column by grouping by ImageId and counting non-NaN masks\ndf['ship_count'] = df.groupby('ImageId')['EncodedPixels'].transform('count')\n\n# Create a clean, unique entry for each image\nunique_images_df = df.drop_duplicates(subset='ImageId').copy()\nunique_images_df = unique_images_df.sort_values(by='ship_count', ascending=False)\n\nprint(f\"\\nTotal unique images in dataset: {len(unique_images_df)}\")\nimages_with_ships = unique_images_df[unique_images_df['ship_count'] > 0]\nprint(f\"Images containing at least one ship: {len(images_with_ships)}\")\nprint(f\"Images with no ships: {len(unique_images_df) - len(images_with_ships)}\")\n\n\n# --- 3. Analyze the Ship Count Distribution ---\nprint(\"\\n--- Ship Count Distribution ---\")\nplt.figure(figsize=(10, 6))\nunique_images_df['ship_count'].hist(bins=unique_images_df['ship_count'].max())\nplt.title('Ship Count Distribution in Train Set', fontsize=16)\nplt.xlabel('Number of Ships per Image')\nplt.ylabel('Number of Images')\nplt.show()\n\nprint(\"Statistical summary of ship counts:\")\nprint(unique_images_df['ship_count'].describe())\n\n\n# --- 4. Visualize the Data ---\n\n# Helper function to decode the Run-Length Encoding (RLE) masks\ndef rle_decode(mask_rle, shape=(768, 768)):\n    if pd.isna(mask_rle):\n        return np.zeros(shape, dtype=np.uint8)\n    s = mask_rle.split()\n    starts, lengths = [np.asarray(x, dtype=int) for x in (s[0:][::2], s[1:][::2])]\n    starts -= 1\n    ends = starts + lengths\n    img = np.zeros(shape[0]*shape[1], dtype=np.uint8)\n    for lo, hi in zip(starts, ends):\n        img[lo:hi] = 1\n    return img.reshape(shape).T\n\n# Function to display a grid of random images\ndef show_image_grid(image_ids, title):\n    plt.figure(figsize=(20, 10))\n    \n    for i, image_id in enumerate(image_ids):\n        # Load the image\n        image_path = os.path.join(OPTICAL_TRAIN_IMAGES_FULL, image_id)\n        image = imread(image_path)\n        \n        # Get all RLE masks for this image\n        rle_masks = df[df['ImageId'] == image_id]['EncodedPixels'].tolist()\n        \n        # Create a combined mask for all ships\n        combined_mask = np.zeros((768, 768))\n        for rle in rle_masks:\n            combined_mask += rle_decode(rle)\n\n        # Plotting\n        plt.subplot(4, 8, i + 1) # Create a 4x8 grid\n        plt.imshow(image)\n        if np.sum(combined_mask) > 0: # Only show mask overlay if ships exist\n            plt.imshow(np.ma.masked_where(combined_mask == 0, combined_mask), cmap='spring', alpha=0.4)\n        plt.title(f\"{image_id[:12]}...\", fontsize=8)\n        plt.axis('off')\n        \n    plt.suptitle(title, fontsize=20)\n    plt.tight_layout(rect=[0, 0.03, 1, 0.95])\n    plt.show()\n\n# --- Visualize different categories ---\nprint(\"\\n--- Visualizing Image Samples ---\")\n\n# Show images with NO ships\nno_ship_ids = unique_images_df[unique_images_df['ship_count'] == 0]['ImageId'].sample(32).tolist()\nshow_image_grid(no_ship_ids, \"Training Set Samples: No Ships\")\n\n# Show images with 1 to 5 ships\nship_1_to_5_ids = unique_images_df[(unique_images_df['ship_count'] >= 1) & (unique_images_df['ship_count'] <= 5)]['ImageId'].sample(32).tolist()\nshow_image_grid(ship_1_to_5_ids, \"Training Set Samples: 1 to 5 Ships\")\n\n# Show images with more than 10 ships (crowded scenes)\nship_gt_10_ids = unique_images_df[unique_images_df['ship_count'] > 10]['ImageId'].sample(32).tolist()\nshow_image_grid(ship_gt_10_ids, \"Training Set Samples: >10 Ships (Crowded Scenes)\")\n\nprint(\"\\n--- EDA Complete ---\")","metadata":{"trusted":true,"_uuid":"12a555948195593dde1bfaf38c48946e1c046ae3","_kg_hide-input":true,"execution":{"iopub.status.busy":"2025-09-17T11:58:43.728280Z","iopub.execute_input":"2025-09-17T11:58:43.728582Z","iopub.status.idle":"2025-09-17T11:59:01.276122Z","shell.execute_reply.started":"2025-09-17T11:58:43.728530Z","shell.execute_reply":"2025-09-17T11:59:01.275250Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Create a Curated Dataset\nThe histogram of the ship_count is the most important chart. It visually confirms that the overwhelming majority of the 200,000+ images have zero ships\n\nwe only care about the ~22% of images that actually contain ships\n\nWe will use the information from the train_ship_segmentations_v2.csv file to create a new, clean, and much smaller dataset that contains only the images with ships","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nimport os\nimport shutil\n\n# --- 1. Define the CORRECT Paths Based on Your Exploration ---\n\n# The evidence from your 'ls' command shows the files are directly in /kaggle/input/\nINPUT_DIR = '/kaggle/input/airbus-ship-detection/'\nOUTPUT_DIR = '/kaggle/working/airbus_cleaned/'\n\n# These paths are now built from the correct base directory\nTRAIN_DIR = os.path.join(INPUT_DIR, 'train_v2')\nANNOTATIONS_FILE = os.path.join(INPUT_DIR, 'train_ship_segmentations_v2.csv')\nCLEAN_TRAIN_DIR = os.path.join(OUTPUT_DIR, 'train_v2_with_ships')\n\n# --- 2. Verify Paths and Files (This will now succeed) ---\nprint(\"--- Verifying file paths based on exploration ---\")\nif not os.path.exists(ANNOTATIONS_FILE):\n    raise SystemExit(f\"CRITICAL ERROR: Annotation file not found at '{ANNOTATIONS_FILE}'\")\nif not os.path.exists(TRAIN_DIR):\n    raise SystemExit(f\"CRITICAL ERROR: Training image folder not found at '{TRAIN_DIR}'\")\n\nprint(\"Success! All required paths and files have been verified.\")\nprint(f\"\\nFull dataset is at: {TRAIN_DIR}\")\nprint(f\"Cleaned dataset will be created at: {CLEAN_TRAIN_DIR}\")\n\n\n# --- 3. Create the Output Directory ---\nif not os.path.exists(CLEAN_TRAIN_DIR):\n    os.makedirs(CLEAN_TRAIN_DIR)\n\n\n# --- 4. Load Annotations and Filter ---\nprint(\"\\nLoading annotations...\")\ndf = pd.read_csv(ANNOTATIONS_FILE)\ndf_with_ships = df.dropna(subset=['EncodedPixels'])\nship_image_ids = df_with_ships['ImageId'].unique()\n\ntotal_files = len(ship_image_ids)\nprint(f\"Found {total_files} unique images that contain ships.\")\nprint(\"Preparing to copy these files to a new directory...\")\n\n\n# --- 5. Copy Relevant Images with Progress Counter ---\nfor i, image_id in enumerate(ship_image_ids):\n    source_path = os.path.join(TRAIN_DIR, image_id)\n    destination_path = os.path.join(CLEAN_TRAIN_DIR, image_id)\n    \n    if os.path.exists(source_path):\n        shutil.copy(source_path, destination_path)\n    \n    print(f\"Copying... {i + 1}/{total_files}\", end=\"\\r\")\n\nprint(f\"\\n\\nProcess complete!\")\nprint(f\"A new, clean dataset with {len(os.listdir(CLEAN_TRAIN_DIR))} images is now available at: {CLEAN_TRAIN_DIR}\")\n\n# Save the new, clean annotation file\nclean_annotations_path = os.path.join(OUTPUT_DIR, 'train_ship_segmentations_clean.csv')\ndf_with_ships.to_csv(clean_annotations_path, index=False)\n\nprint(f\"A corresponding clean annotation file is saved at: {clean_annotations_path}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-17T12:00:27.799582Z","iopub.execute_input":"2025-09-17T12:00:27.799827Z","iopub.status.idle":"2025-09-17T12:07:39.244512Z","shell.execute_reply.started":"2025-09-17T12:00:27.799787Z","shell.execute_reply":"2025-09-17T12:07:39.243571Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"We have now created a clean curated source dataset of 42,556 optical images that are guaranteed to contain ships","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nimport os\nimport random\nimport matplotlib.pyplot as plt\nfrom skimage.io import imread\nimport numpy as np\n\n# --- 1. Define Paths to Your NEW Clean Dataset ---\nCLEAN_DATA_DIR = '/kaggle/working/airbus_cleaned/'\nCLEAN_TRAIN_DIR = os.path.join(CLEAN_DATA_DIR, 'train_v2_with_ships')\nCLEAN_ANNOTATIONS_FILE = os.path.join(CLEAN_DATA_DIR, 'train_ship_segmentations_clean.csv')\n\nprint(\"--- Verifying the new, clean dataset ---\")\nprint(f\"Image folder: {CLEAN_TRAIN_DIR}\")\nprint(f\"Annotation file: {CLEAN_ANNOTATIONS_FILE}\")\n\n\n# --- 2. Load the Clean Annotations ---\ndf_clean = pd.read_csv(CLEAN_ANNOTATIONS_FILE)\n# Group masks by image ID for easy lookup\nmasks_by_image = df_clean.groupby('ImageId')['EncodedPixels'].apply(list).reset_index()\n\n\n# --- 3. RLE Decode Function (same as before) ---\ndef rle_decode(mask_rle, shape=(768, 768)):\n    s = mask_rle.split()\n    starts, lengths = [np.asarray(x, dtype=int) for x in (s[0:][::2], s[1:][::2])]\n    starts -= 1\n    ends = starts + lengths\n    img = np.zeros(shape[0]*shape[1], dtype=np.uint8)\n    for lo, hi in zip(starts, ends):\n        img[lo:hi] = 1\n    return img.reshape(shape).T\n\n\n# --- 4. Visualize Random Samples from the Clean Dataset ---\nnum_samples = 4\nsample_ship_ids = masks_by_image['ImageId'].sample(num_samples).tolist()\n\nprint(f\"\\nDisplaying {num_samples} random samples from the clean dataset...\")\nplt.figure(figsize=(20, 10))\n\nfor i, image_id in enumerate(sample_ship_ids):\n    # Load the image from our new clean directory\n    image_path = os.path.join(CLEAN_TRAIN_DIR, image_id)\n    image = imread(image_path)\n    \n    # Get all masks for this image\n    rle_masks = masks_by_image[masks_by_image['ImageId'] == image_id]['EncodedPixels'].values[0]\n    \n    # Create a combined mask for all ships in the image\n    combined_mask = np.zeros((768, 768))\n    for rle in rle_masks:\n        combined_mask += rle_decode(rle)\n\n    # Plotting\n    plt.subplot(1, num_samples, i + 1)\n    plt.imshow(image)\n    plt.imshow(np.ma.masked_where(combined_mask == 0, combined_mask), cmap='spring', alpha=0.4)\n    plt.title(f\"Image: {image_id.split('.')[0][:8]}...\")\n    plt.axis('off')\n    \nplt.suptitle(\"Verification: Random Samples from Cleaned Optical Dataset\", fontsize=20)\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-17T12:13:57.255996Z","iopub.execute_input":"2025-09-17T12:13:57.256280Z","iopub.status.idle":"2025-09-17T12:14:01.473301Z","shell.execute_reply.started":"2025-09-17T12:13:57.256240Z","shell.execute_reply":"2025-09-17T12:14:01.472484Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Exploring the target domain (SAR images)\nSSDD Dataset: Contains Diverse real SAR data with 1,160 SAR images (average 500x500 pixels) annotating 2,456 ship instances across various resolutions, polarizations, and sea conditions.\n\nUsed for both training the CDA-GAN (660 images) and evaluating ship detection models like YOLOv5 and Faster R-CNN.\n\n- Official GitHub repository: https://github.com/TianwenZhang0825/Official-SSDD\n\n- Paper with dataset details: https://www.mdpi.com/2072-4292/13/18/3690\n\n","metadata":{}},{"cell_type":"code","source":"import os\nimport random\nimport matplotlib.pyplot as plt\nimport matplotlib.image as mpimg\n\n# --- 1. Define the Path to Your New Kaggle Dataset ---\n# Based on your screenshot, this is the correct path.\n# Kaggle puts your dataset in /kaggle/input/<dataset-name>/<folder-name>\nSAR_DATA_DIR = '/kaggle/input/ssdd-sar-images/JPEGImages/'\n\nprint(f\"--- Exploring the newly uploaded SAR dataset ---\")\nprint(f\"Looking for images in: {SAR_DATA_DIR}\")\n\n\n# --- 2. Get a List of All Images ---\ntry:\n    image_files = [f for f in os.listdir(SAR_DATA_DIR) if f.endswith('.jpg')]\n    if not image_files:\n        print(\"\\nERROR: The directory is empty or contains no .jpg files.\")\n        print(\"This might mean Kaggle is still unzipping the data. Please wait a minute and try again.\")\n    else:\n        print(f\"Successfully found {len(image_files)} images.\")\nexcept FileNotFoundError:\n    print(f\"\\nERROR: The directory was not found at the path: {SAR_DATA_DIR}\")\n    print(\"Please double-check the dataset name in the path.\")\n    # Stop execution if the path is wrong\n    image_files = []\n\n\n# --- 3. Visualize Random Samples ---\nif image_files:\n    num_samples = 4\n    # Randomly select a few image filenames to display\n    sample_files = random.sample(image_files, num_samples)\n\n    print(f\"\\nDisplaying {num_samples} random samples...\")\n    \n    # Create a plot to display the samples\n    fig, axes = plt.subplots(1, num_samples, figsize=(20, 5))\n    fig.suptitle(\"Verification: Random Samples from Uploaded SAR Dataset\", fontsize=16)\n\n    for i, filename in enumerate(sample_files):\n        # Construct the full path to the image\n        image_path = os.path.join(SAR_DATA_DIR, filename)\n        \n        # Load the image\n        image = mpimg.imread(image_path)\n        \n        # Display the image\n        axes[i].imshow(image, cmap='gray') # Use a grayscale colormap for SAR\n        axes[i].set_title(filename)\n        axes[i].axis('off')\n\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-17T12:19:56.304054Z","iopub.execute_input":"2025-09-17T12:19:56.304360Z","iopub.status.idle":"2025-09-17T12:19:56.770147Z","shell.execute_reply.started":"2025-09-17T12:19:56.304298Z","shell.execute_reply":"2025-09-17T12:19:56.769215Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"---\n\nTo learn a translation, you'd expect to need a \"Rosetta Stone\" a direct, one-to-one mapping, called paired image-to-image translation. This is where you have an exact (Input, Target) pair for every training example.\n\nBut this kind of perfectly paired data is incredibly rare, and practically impossible to collect at scale.\n\nThe breakthrough lies in CycleGAN designed for unpaired image-to-image translation.\n\nThe GAN's job is to learn a function that can take any image from Optical and \"repaint\" it in the style of SAR, while preserving the core content.\n\nIt only needs two large, separate collections:\n- Domain A: Optical Images\n\n- Domain B: SAR Images\n\nHow does it preserve the content? by a thing called cycle consistency loss.\n- Generator A-to-B: Takes an optical ship image and translates it into a fake SAR image.\n- Generator B-to-A: Takes that fake SAR image and translates it back into a fake optical image.\n- The model then compares the reconstructed optical image to the original optical image. If they are very different, the model is penalized.\n\nThis forces the first generator (A-to-B) to be honest. It can't just create random SAR noise; it has to hide all the important information about the original ship (its shape, size, orientation) within the fake SAR image so that the second generator can successfully rebuild it.\n\n---\n","metadata":{}},{"cell_type":"markdown","source":"# DataLoader Pipeline\n\nNow we will load the images from both folders, apply the necessary transformations (resizing, converting to tensors), and create the data batches that will feed our neural network.","metadata":{}},{"cell_type":"code","source":"import os\nimport random\nfrom PIL import Image\nimport torch\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\n\n# --- 1. Define the Custom Dataset Class ---\n\nclass UnpairedImageDataset(Dataset):\n    def __init__(self, optical_dir, sar_dir, transform=None):\n        self.optical_dir = optical_dir\n        self.sar_dir = sar_dir\n        self.transform = transform\n        self.optical_images = [f for f in os.listdir(optical_dir) if f.endswith('.jpg')]\n        self.sar_images = [f for f in os.listdir(sar_dir) if f.endswith('.jpg')]\n        print(f\"Found {len(self.optical_images)} optical images.\")\n        print(f\"Found {len(self.sar_images)} SAR images.\")\n\n    def __len__(self):\n        return max(len(self.optical_images), len(self.sar_images))\n\n    def __getitem__(self, idx):\n        optical_img_name = os.path.join(self.optical_dir, self.optical_images[idx % len(self.optical_images)])\n        sar_img_name = os.path.join(self.sar_dir, random.choice(self.sar_images))\n        \n        optical_image = Image.open(optical_img_name).convert('RGB')\n        sar_image = Image.open(sar_img_name).convert('RGB')\n\n        if self.transform:\n            optical_image = self.transform(optical_image)\n            sar_image = self.transform(sar_image)\n            \n        return {'optical': optical_image, 'sar': sar_image}\n\n\n# --- 2. Define FINAL Paths and Transformations ---\n\n# Path to your clean optical dataset\nOPTICAL_DATA_DIR = '/kaggle/working/airbus_cleaned/train_v2_with_ships/'\n\n# Path to your verified uploaded SAR dataset\nSAR_DATA_DIR = '/kaggle/input/ssdd-sar-images/JPEGImages/'\n\n# Define the transformations\ntransformations = transforms.Compose([\n    transforms.Resize((256, 256)),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])\n])\n\n\n# --- 3. Instantiate the Dataset and DataLoader ---\nprint(\"\\n--- Creating the final dataset pipeline ---\")\n\nship_dataset = UnpairedImageDataset(\n    optical_dir=OPTICAL_DATA_DIR,\n    sar_dir=SAR_DATA_DIR,\n    transform=transformations\n)\n\n# Use a batch size that works well with GANs, like 4 or 8.\n# num_workers can be set to 2 to speed up loading.\ndataloader = DataLoader(ship_dataset, batch_size=4, shuffle=True, num_workers=2)\n\n\n# --- 4. Test the DataLoader ---\nprint(\"\\n--- Testing the DataLoader by fetching one batch ---\")\ntry:\n    # Get one batch of data from the dataloader\n    one_batch = next(iter(dataloader))\n    optical_batch = one_batch['optical']\n    sar_batch = one_batch['sar']\n\n    print(f\"Successfully fetched one batch.\")\n    print(f\"Optical batch shape: {optical_batch.shape}\")\n    print(f\"SAR batch shape: {sar_batch.shape}\")\n    print(\"\\nThis confirms the data pipeline is working correctly.\")\n\nexcept Exception as e:\n    print(f\"An error occurred while testing the dataloader: {e}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-17T12:21:24.885432Z","iopub.execute_input":"2025-09-17T12:21:24.885692Z","iopub.status.idle":"2025-09-17T12:21:25.150543Z","shell.execute_reply.started":"2025-09-17T12:21:24.885653Z","shell.execute_reply":"2025-09-17T12:21:25.149525Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Building the CDA-GAN model\nOur model is a version of CycleGAN\n\nCycleGAN has two primary components:\n1. The Generator: A deep neural network that learns to translate an image from one domain to another (e.g., Optical-to-SAR).\n2. The Discriminator: A second network that acts as a critic, trying to distinguish between real images and the fake images created by the Generator.\n\nWe will start by building the Generator. The architecture for this kind of image-to-image translation is typically a ResNet-style encoder-decoder. It's designed to process an image, understand its content, and then \"repaint\" it in a new style.\n\nWe will build it in two parts: the main Generator class and a ResidualBlock class that it uses as its core building block.\n","metadata":{}},{"cell_type":"markdown","source":"# The Generator","metadata":{}},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\n\n# --- 1. Define the Core Residual Block ---\n# This is the main building block for the Generator. It allows the network to be very deep\n# while still passing information effectively, preventing issues like vanishing gradients.\n\nclass ResidualBlock(nn.Module):\n    def __init__(self, in_features):\n        super(ResidualBlock, self).__init__()\n\n        # The block consists of two convolutional layers with normalization and ReLU activation.\n        self.block = nn.Sequential(\n            nn.ReflectionPad2d(1), # Padding to keep image size the same\n            nn.Conv2d(in_features, in_features, kernel_size=3),\n            nn.InstanceNorm2d(in_features),\n            nn.ReLU(inplace=True),\n            nn.ReflectionPad2d(1),\n            nn.Conv2d(in_features, in_features, kernel_size=3),\n            nn.InstanceNorm2d(in_features)\n        )\n\n    def forward(self, x):\n        # The \"residual\" part: we add the input 'x' to the output of the block.\n        # This is the \"skip connection\" that makes ResNets so powerful.\n        return x + self.block(x)\n\n\n# --- 2. Define the Generator Network ---\n# This network follows a classic encoder-decoder structure.\n# It downsamples the input image, processes it with residual blocks, then upsamples it back.\n\nclass Generator(nn.Module):\n    def __init__(self, input_channels=3, output_channels=3, n_residual_blocks=9):\n        super(Generator, self).__init__()\n\n        # --- Initial Convolution Block (Encoder / Downsampling) ---\n        model = [\n            nn.ReflectionPad2d(3),\n            nn.Conv2d(input_channels, 64, kernel_size=7),\n            nn.InstanceNorm2d(64),\n            nn.ReLU(inplace=True)\n        ]\n\n        # Downsampling layers\n        in_features = 64\n        out_features = in_features * 2\n        for _ in range(2):\n            model += [\n                nn.Conv2d(in_features, out_features, kernel_size=3, stride=2, padding=1),\n                nn.InstanceNorm2d(out_features),\n                nn.ReLU(inplace=True)\n            ]\n            in_features = out_features\n            out_features = in_features * 2\n\n        # --- Residual Blocks (Transformation) ---\n        # This is the core of the generator where the style transfer happens.\n        for _ in range(n_residual_blocks):\n            model += [ResidualBlock(in_features)]\n\n        # --- Upsampling Block (Decoder) ---\n        out_features = in_features // 2\n        for _ in range(2):\n            model += [\n                nn.ConvTranspose2d(in_features, out_features, kernel_size=3, stride=2, padding=1, output_padding=1),\n                nn.InstanceNorm2d(out_features),\n                nn.ReLU(inplace=True)\n            ]\n            in_features = out_features\n            out_features = in_features // 2\n\n        # --- Output Layer ---\n        # Final layer to produce the output image.\n        model += [\n            nn.ReflectionPad2d(3),\n            nn.Conv2d(64, output_channels, kernel_size=7),\n            nn.Tanh() # Tanh activation scales the output pixels to be between -1 and 1.\n        ]\n\n        self.model = nn.Sequential(*model)\n\n    def forward(self, x):\n        return self.model(x)\n\n\n# --- 3. Verification Step ---\n# Let's create a dummy generator and pass a fake batch of data through it\n# to ensure the network is wired correctly and the output shape is what we expect.\n\nprint(\"--- Verifying the Generator architecture ---\")\n\n# Instantiate the generator\ngen = Generator()\n\n# Create a dummy input tensor with the same shape as our dataloader's output\n# (batch_size=4, channels=3, height=256, width=256)\ndummy_input = torch.randn(4, 3, 256, 256)\n\n# Pass the dummy input through the generator\ndummy_output = gen(dummy_input)\n\nprint(f\"Input shape: {dummy_input.shape}\")\nprint(f\"Output shape: {dummy_output.shape}\")\n\nif dummy_input.shape == dummy_output.shape:\n    print(\"\\nSuccess! The Generator maintains the correct image shape.\")\nelse:\n    print(\"\\nError: The output shape does not match the input shape.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-17T12:21:32.368595Z","iopub.execute_input":"2025-09-17T12:21:32.368862Z","iopub.status.idle":"2025-09-17T12:21:40.153570Z","shell.execute_reply.started":"2025-09-17T12:21:32.368820Z","shell.execute_reply":"2025-09-17T12:21:40.152661Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"We have defined the ResidualBlock which is the reusable core component of our network.\n\nWe have also defined the Generator. \n\nIt takes a 3-channel (RGB) 256x256 image and, after many layers, outputs another 3-channel 256x256 image. \nThe Tanh activation at the end is crucial because our DataLoader normalizes images to be between -1 and 1, and Tanh outputs in exactly that range.\n\nWe passed a correctly-sized dummy tensor through it. The output confirms that our network doesn't crash and that the final image size is the same as the input size, which is exactly what we want for our image-to-image translation.","metadata":{}},{"cell_type":"markdown","source":"# The Discriminator\nIts job is to look at an image and decide if it's a real SAR image from our dataset or a fake one created by our Generator.\n\nFor this kind of task, a specific type of discriminator called a PatchGAN is highly effective. Instead of outputting a single number (0 for fake, 1 for real) for the entire image, it outputs a grid of values. Each value in the grid corresponds to a \"patch\" of the input image, and it represents how realistic that patch looks. This gives the Generator more detailed, localized feedback on which parts of its fake image are unconvincing.\n\nIt's a convolutional neural network that downsamples the image to produce the patch grid.\n","metadata":{}},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\n\n# --- 1. Define the CORRECTED Discriminator Network ---\n\nclass Discriminator(nn.Module):\n    def __init__(self, input_channels=3):\n        super(Discriminator, self).__init__()\n\n        def discriminator_block(in_filters, out_filters, normalize=True):\n            layers = [nn.Conv2d(in_filters, out_filters, kernel_size=4, stride=2, padding=1)]\n            if normalize:\n                layers.append(nn.InstanceNorm2d(out_filters))\n            layers.append(nn.LeakyReLU(0.2, inplace=True))\n            return layers\n\n        # We now use only 3 downsampling blocks to get to a 32x32 feature map.\n        self.model = nn.Sequential(\n            *discriminator_block(input_channels, 64, normalize=False),\n            *discriminator_block(64, 128),\n            *discriminator_block(128, 256),\n            \n            # The final convolution is now adjusted to map from 32x32 -> 30x30.\n            # We use a kernel size of 3 and no padding to achieve this.\n            nn.Conv2d(256, 1, kernel_size=3, padding=0)\n        )\n\n    def forward(self, img):\n        return self.model(img)\n\n\n# --- 2. Verification Step with the Correct Expected Shape ---\n\nprint(\"--- Verifying the CORRECTED Discriminator architecture ---\")\n\n# Instantiate the corrected discriminator\ndisc = Discriminator()\n\n# Create a dummy input tensor\ndummy_input = torch.randn(4, 3, 256, 256)\n\n# Pass the dummy input through the discriminator\ndummy_output = disc(dummy_input)\n\n# The NEW expected output shape is a 30x30 grid of patches.\nexpected_shape = (4, 1, 30, 30)\n\nprint(f\"Input shape: {dummy_input.shape}\")\nprint(f\"Output shape: {dummy_output.shape}\")\nprint(f\"Expected shape: {expected_shape}\")\n\nif dummy_output.shape == expected_shape:\n    print(\"\\nSuccess! The Discriminator now produces the correct 30x30 patch grid shape.\")\nelse:\n    print(\"\\nError: The output shape still does not match the expected patch grid shape.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-17T12:21:48.822072Z","iopub.execute_input":"2025-09-17T12:21:48.822321Z","iopub.status.idle":"2025-09-17T12:21:49.760083Z","shell.execute_reply.started":"2025-09-17T12:21:48.822284Z","shell.execute_reply":"2025-09-17T12:21:49.759269Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"We built a CNN using a series of blocks that each contain a Conv2d, InstanceNorm2d, and LeakyReLU layer. Each block halves the image's height and width.\n\nEach of the four stride=2 convolutional blocks halves the height and width of the feature map: 256 -> 128 -> 64 -> 32.\n\nTo produce the desired 30x30 patch grid we adjust the final convolution layer to correctly map this 32x32 feature map down to the target 30x30 grid. A convolution with a kernel_size=3 and padding=0 will achieve this size reduction perfectly (32 - 3 + 1 = 30).\n\nThe final layer is a convolution that reduces the channels to 1, producing the grid of real/fake predictions. For a 256x256 input, this architecture results in a 30x30 grid.\n\nAs before, we test the network with a dummy tensor. The verification step confirms that an image of size [4, 3, 256, 256] correctly produces a patch grid of size [4, 1, 30, 30].","metadata":{}},{"cell_type":"markdown","source":"# Initialize Models\nNow we will set up the training environment. It creates the four required networks (two generators, two discriminators), initializes their weights for stable training, defines the three types of losses used in CycleGAN, and sets up the Adam optimizers.\n\nWe will set the GPU as the target device for all computations\n","metadata":{}},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport itertools # Used to combine generator parameters for the optimizer\n\n# --- 1. Setup Device (GPU or CPU) ---\n# This is a standard check to use the GPU if it's available, which is crucial for training.\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(f\"Using device: {device}\")\n\n\n# --- 2. Initialize the Models ---\n# We need two generators (A->B and B->A) and two discriminators (one for each domain).\nG_AB = Generator().to(device) # Generator: Optical -> SAR\nG_BA = Generator().to(device) # Generator: SAR -> Optical\nD_A = Discriminator().to(device) # Discriminator for Optical domain\nD_B = Discriminator().to(device) # Discriminator for SAR domain\n\n# --- 3. Weight Initialization ---\n# It's a best practice to initialize the weights of GANs to a normal distribution.\n# This helps prevent the model from getting stuck during training.\ndef weights_init_normal(m):\n    classname = m.__class__.__name__\n    if classname.find(\"Conv\") != -1:\n        torch.nn.init.normal_(m.weight.data, 0.0, 0.02)\n        if hasattr(m, \"bias\") and m.bias is not None:\n            torch.nn.init.constant_(m.bias.data, 0.0)\n    elif classname.find(\"BatchNorm2d\") != -1:\n        torch.nn.init.normal_(m.weight.data, 1.0, 0.02)\n        torch.nn.init.constant_(m.bias.data, 0.0)\n\nprint(\"\\nInitializing model weights...\")\nG_AB.apply(weights_init_normal)\nG_BA.apply(weights_init_normal)\nD_A.apply(weights_init_normal)\nD_B.apply(weights_init_normal)\nprint(\"Weight initialization complete.\")\n\n\n# --- 4. Define Loss Functions ---\n# Adversarial Loss: Measures how well the generator fools the discriminator.\n# We use Mean Squared Error (MSE) loss, which is more stable than BCE for GANs.\ncriterion_GAN = torch.nn.MSELoss().to(device)\n\n# Cycle Consistency Loss: Measures if the reconstructed image is close to the original.\n# We use L1 (Mean Absolute Error) loss as it produces sharper images than L2.\ncriterion_cycle = torch.nn.L1Loss().to(device)\n\n# Identity Loss: Encourages the generator to not change images that are already in the target domain.\ncriterion_identity = torch.nn.L1Loss().to(device)\n\n\n# --- 5. Set up Optimizers ---\n# We use the Adam optimizer, which is standard for GANs.\n# Note that we have separate optimizers for the generators and discriminators.\nlearning_rate = 0.0002\nbeta1 = 0.5 # A momentum term for Adam\n\n# Optimizer for the two generators\noptimizer_G = torch.optim.Adam(\n    itertools.chain(G_AB.parameters(), G_BA.parameters()), lr=learning_rate, betas=(beta1, 0.999)\n)\n\n# Optimizer for the optical domain discriminator\noptimizer_D_A = torch.optim.Adam(D_A.parameters(), lr=learning_rate, betas=(beta1, 0.999))\n\n# Optimizer for the SAR domain discriminator\noptimizer_D_B = torch.optim.Adam(D_B.parameters(), lr=learning_rate, betas=(beta1, 0.999))\n\nprint(\"\\nSuccessfully initialized models, loss functions, and optimizers.\")\nprint(\"The training environment is now ready.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-17T12:21:56.611261Z","iopub.execute_input":"2025-09-17T12:21:56.611583Z","iopub.status.idle":"2025-09-17T12:22:00.932117Z","shell.execute_reply.started":"2025-09-17T12:21:56.611524Z","shell.execute_reply":"2025-09-17T12:22:00.931380Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}