{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":45867,"databundleVersionId":6924515,"sourceType":"competition"}],"dockerImageVersionId":30626,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# # ============================\n# # PG-ECA for UBC-OCEAN Dataset\n# # Offline, Large Image Safe\n# # ============================\n\n# import torch\n# import torch.nn as nn\n# import torch.nn.functional as F\n# from torch.utils.data import Dataset, DataLoader\n# from torchvision import transforms\n# from PIL import Image\n# import os\n# import matplotlib.pyplot as plt\n# from sklearn.cluster import KMeans\n# from sklearn.metrics import silhouette_score\n# from sklearn.decomposition import PCA\n\n# # =========================================\n# # 0. Safe handling for very large images\n# # =========================================\n# Image.MAX_IMAGE_PIXELS = None  # bypass DecompressionBombError\n\n# # ======================\n# # 1. Custom Dataset\n# # ======================\n# class UBCOceanDataset(Dataset):\n#     def __init__(self, image_dir, transform=None):\n#         self.image_dir = image_dir\n#         self.transform = transform\n#         self.images = [f for f in os.listdir(image_dir) if f.endswith(('.png','.jpg','.jpeg'))]\n\n#     def __len__(self):\n#         return len(self.images)\n\n#     def __getitem__(self, idx):\n#         img_path = os.path.join(self.image_dir, self.images[idx])\n#         image = Image.open(img_path).convert(\"L\")  # convert to grayscale\n#         if self.transform:\n#             image = self.transform(image)\n#         return image, 0  # dummy label for unsupervised\n\n# # ======================\n# # 2. DataLoader\n# # ======================\n# transform = transforms.Compose([\n#     transforms.Resize((32,32)),  # reduce size to manageable patch processing\n#     transforms.ToTensor()\n# ])\n\n# dataset = UBCOceanDataset('/kaggle/input/UBC-OCEAN/train_images', transform=transform)\n# loader = DataLoader(dataset, batch_size=32, shuffle=True)\n\n# # ======================\n# # 3. Patch Splitting\n# # ======================\n# def split_patches(x, patch_size=8):\n#     B, C, H, W = x.shape\n#     patches = x.unfold(2, patch_size, patch_size).unfold(3, patch_size, patch_size)\n#     patches = patches.contiguous().view(B, C, -1, patch_size, patch_size)\n#     patches = patches.permute(0,2,1,3,4)  # B, num_patches, C, patch_size, patch_size\n#     return patches\n\n# # ======================\n# # 4. Patch Encoder\n# # ======================\n# class PatchEncoder(nn.Module):\n#     def __init__(self, embed_dim=64):\n#         super().__init__()\n#         self.conv = nn.Sequential(\n#             nn.Conv2d(1, 32, 3, padding=1), nn.ReLU(),\n#             nn.Conv2d(32, 64, 3, padding=1), nn.ReLU(),\n#             nn.AdaptiveAvgPool2d(1)\n#         )\n#         self.fc = nn.Linear(64, embed_dim)\n        \n#     def forward(self, patches):\n#         B, N, C, H, W = patches.shape\n#         patches = patches.view(B*N, C, H, W)\n#         out = self.conv(patches).view(B*N, -1)\n#         out = self.fc(out)\n#         return out.view(B, N, -1)  # B, num_patches, embed_dim\n\n# # ======================\n# # 5. Graph Encoder\n# # ======================\n# class GraphEncoder(nn.Module):\n#     def __init__(self, embed_dim=64):\n#         super().__init__()\n#         self.fc1 = nn.Linear(embed_dim, embed_dim)\n#         self.fc2 = nn.Linear(embed_dim, embed_dim)\n        \n#     def forward(self, x):\n#         h = F.relu(self.fc1(x))\n#         h = F.relu(self.fc2(h))\n#         return h.mean(dim=1)  # global embedding per image\n\n# # ======================\n# # 6. Edge-Contrastive Loss\n# # ======================\n# def edge_contrastive_loss(embeddings):\n#     B, N, D = embeddings.shape\n#     loss = 0\n#     for i in range(B):\n#         e = embeddings[i]  # N, D\n#         sim_matrix = torch.matmul(e, e.T) / (e.norm(dim=1)[:,None]*e.norm(dim=1)[None,:] + 1e-8)\n#         pos_mask = torch.eye(N).bool()\n#         neg_mask = ~pos_mask\n#         pos_loss = -torch.log(sim_matrix[pos_mask]+1e-8).mean()\n#         neg_loss = -torch.log(1 - sim_matrix[neg_mask]+1e-8).mean()\n#         loss += pos_loss + neg_loss\n#     return loss / B\n\n# # ======================\n# # 7. PG-ECA Model\n# # ======================\n# class PGECA(nn.Module):\n#     def __init__(self, embed_dim=64, patch_size=8):\n#         super().__init__()\n#         self.encoder = PatchEncoder(embed_dim)\n#         self.graph_encoder = GraphEncoder(embed_dim)\n#         self.decoder = nn.Sequential(\n#             nn.Linear(embed_dim, 64),\n#             nn.ReLU(),\n#             nn.Linear(64, patch_size*patch_size),\n#             nn.Sigmoid()\n#         )\n#         self.patch_size = patch_size\n        \n#     def forward(self, x):\n#         patches = split_patches(x, self.patch_size)\n#         patch_embed = self.encoder(patches)\n#         global_embed = self.graph_encoder(patch_embed)\n#         recon = self.decoder(global_embed).view(-1,1,self.patch_size,self.patch_size)\n#         return patch_embed, global_embed, recon\n\n# # ======================\n# # 8. Training Loop\n# # ======================\n# device = 'cuda' if torch.cuda.is_available() else 'cpu'\n# model = PGECA().to(device)\n# optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)\n# epochs = 5\n\n# for epoch in range(epochs):\n#     total_loss = 0\n#     for imgs,_ in loader:\n#         imgs = imgs.to(device)\n#         patch_embed, global_embed, recon = model(imgs)\n#         loss_edge = edge_contrastive_loss(patch_embed)\n#         target = split_patches(imgs)[:,:,0,:,:].mean(dim=1, keepdim=True)\n#         loss_recon = F.mse_loss(recon, target)\n#         loss = loss_edge + 0.1*loss_recon\n#         optimizer.zero_grad()\n#         loss.backward()\n#         optimizer.step()\n#         total_loss += loss.item()\n#     print(f\"Epoch {epoch+1}/{epochs}, Loss: {total_loss/len(loader):.4f}\")\n\n# # ======================\n# # 9. Extract Embeddings for Clustering\n# # ======================\n# embeddings_list = []\n# with torch.no_grad():\n#     for imgs,_ in loader:\n#         imgs = imgs.to(device)\n#         _, global_embed, _ = model(imgs)\n#         embeddings_list.append(global_embed.cpu())\n# embeddings_all = torch.cat(embeddings_list).numpy()\n\n# # ======================\n# # 10. Clustering + Evaluation\n# # ======================\n# num_clusters = min(10, len(dataset))\n# kmeans = KMeans(n_clusters=num_clusters, random_state=42)\n# preds = kmeans.fit_predict(embeddings_all)\n# sil_score = silhouette_score(embeddings_all, preds)\n# print(\"Silhouette Score:\", sil_score)\n\n# # ======================\n# # 11. Visualization\n# # ======================\n# plt.figure(figsize=(6,3))\n# for i in range(min(6, len(dataset))):\n#     plt.subplot(2,6,i+1)\n#     img,_ = dataset[i]\n#     plt.imshow(img[0], cmap='gray')\n#     plt.axis('off')\n#     plt.subplot(2,6,i+7)\n#     with torch.no_grad():\n#         recon_patch = model(img.unsqueeze(0).to(device))[2][0,0].cpu().numpy()\n#     plt.imshow(recon_patch, cmap='gray')\n#     plt.axis('off')\n# plt.show()\n\n# # PCA Embedding Visualization\n# pca = PCA(n_components=2)\n# emb_2d = pca.fit_transform(embeddings_all)\n# plt.figure(figsize=(6,6))\n# plt.scatter(emb_2d[:,0], emb_2d[:,1], c=preds, cmap='tab10', s=5)\n# plt.title(\"Global Embeddings (PCA 2D)\")\n# plt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-18T19:10:48.485108Z","iopub.execute_input":"2025-08-18T19:10:48.48551Z","iopub.status.idle":"2025-08-18T19:10:48.493033Z","shell.execute_reply.started":"2025-08-18T19:10:48.485482Z","shell.execute_reply":"2025-08-18T19:10:48.492111Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch, torchvision, sklearn, PIL, matplotlib\nprint(\"Torch:\", torch.__version__)\nprint(\"Torchvision:\", torchvision.__version__)\nprint(\"CUDA available:\", torch.cuda.is_available())\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-23T19:30:49.506943Z","iopub.execute_input":"2025-08-23T19:30:49.507297Z","iopub.status.idle":"2025-08-23T19:30:49.639562Z","shell.execute_reply.started":"2025-08-23T19:30:49.507273Z","shell.execute_reply":"2025-08-23T19:30:49.638435Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================\n# All Required Imports\n# ============================\n\nimport os\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import Dataset, DataLoader, Subset\nfrom torchvision import transforms\nfrom PIL import Image\nimport matplotlib.pyplot as plt\n\n# ML utilities\nfrom sklearn.cluster import KMeans\nfrom sklearn.metrics import silhouette_score\nfrom sklearn.decomposition import PCA\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-23T19:31:01.862938Z","iopub.execute_input":"2025-08-23T19:31:01.863752Z","iopub.status.idle":"2025-08-23T19:31:02.00413Z","shell.execute_reply.started":"2025-08-23T19:31:01.863724Z","shell.execute_reply":"2025-08-23T19:31:02.002799Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n# 0. Safe handling for very large images\n# =========================================\nImage.MAX_IMAGE_PIXELS = None  # bypass DecompressionBombError\n\n# ======================\n# 1. Custom Dataset\n# ======================\nclass UBCOceanDataset(Dataset):\n    def __init__(self, image_dir, transform=None):\n        self.image_dir = image_dir\n        self.transform = transform\n        self.images = [f for f in os.listdir(image_dir) if f.lower().endswith(('.png','.jpg','.jpeg'))]\n\n    def __len__(self):\n        return len(self.images)\n\n    def __getitem__(self, idx):\n        img_path = os.path.join(self.image_dir, self.images[idx])\n        image = Image.open(img_path).convert(\"L\")  # grayscale\n        if self.transform:\n            image = self.transform(image)\n        return image, 0  # dummy label for unsupervised\n\n# ======================\n# 2. DataLoader\n# ======================\ntransform = transforms.Compose([\n    transforms.Resize((32,32)),\n    transforms.ToTensor()\n])\n\ndataset = UBCOceanDataset('/kaggle/input/UBC-OCEAN/train_images', transform=transform)\n# Use subset for testing speed\ndataset = Subset(dataset, range(min(200, len(dataset))))  # first 200 images\nloader = DataLoader(dataset, batch_size=16, shuffle=True)\n\n# ======================\n# 3. Patch Splitting\n# ======================\ndef split_patches(x, patch_size=8):\n    B, C, H, W = x.shape\n    patches = x.unfold(2, patch_size, patch_size).unfold(3, patch_size, patch_size)\n    patches = patches.contiguous().view(B, C, -1, patch_size, patch_size)\n    patches = patches.permute(0,2,1,3,4)  # B, num_patches, C, patch_size, patch_size\n    return patches\n\n# ======================\n# 4. Patch Encoder\n# ======================\nclass PatchEncoder(nn.Module):\n    def __init__(self, embed_dim=64):\n        super().__init__()\n        self.conv = nn.Sequential(\n            nn.Conv2d(1, 32, 3, padding=1), nn.ReLU(),\n            nn.Conv2d(32, 64, 3, padding=1), nn.ReLU(),\n            nn.AdaptiveAvgPool2d(1)\n        )\n        self.fc = nn.Linear(64, embed_dim)\n        \n    def forward(self, patches):\n        B, N, C, H, W = patches.shape\n        patches = patches.view(B*N, C, H, W)\n        out = self.conv(patches).view(B*N, -1)\n        out = self.fc(out)\n        return out.view(B, N, -1)  # B, num_patches, embed_dim\n\n# ======================\n# 5. Graph Encoder\n# ======================\nclass GraphEncoder(nn.Module):\n    def __init__(self, embed_dim=64):\n        super().__init__()\n        self.fc1 = nn.Linear(embed_dim, embed_dim)\n        self.fc2 = nn.Linear(embed_dim, embed_dim)\n        \n    def forward(self, x):\n        h = F.relu(self.fc1(x))\n        h = F.relu(self.fc2(h))\n        return h.mean(dim=1)  # global embedding per image\n\n# ======================\n# 6. Edge-Contrastive Loss (Vectorized)\n# ======================\ndef edge_contrastive_loss(embeddings):\n    B, N, D = embeddings.shape\n    normed = embeddings / (embeddings.norm(dim=2, keepdim=True) + 1e-8)\n    sim_matrix = torch.bmm(normed, normed.transpose(1,2))  # B x N x N\n    pos_mask = torch.eye(N, device=sim_matrix.device).bool()\n    neg_mask = ~pos_mask\n    pos_loss = -torch.log(sim_matrix[:,pos_mask] + 1e-8).mean()\n    neg_loss = -torch.log(1 - sim_matrix[:,neg_mask] + 1e-8).mean()\n    return pos_loss + neg_loss\n\n# ======================\n# 7. PG-ECA Model\n# ======================\nclass PGECA(nn.Module):\n    def __init__(self, embed_dim=64, patch_size=8):\n        super().__init__()\n        self.encoder = PatchEncoder(embed_dim)\n        self.graph_encoder = GraphEncoder(embed_dim)\n        self.decoder = nn.Sequential(\n            nn.Linear(embed_dim, 64),\n            nn.ReLU(),\n            nn.Linear(64, patch_size*patch_size),\n            nn.Sigmoid()\n        )\n        self.patch_size = patch_size\n        \n    def forward(self, x):\n        patches = split_patches(x, self.patch_size)\n        patch_embed = self.encoder(patches)\n        global_embed = self.graph_encoder(patch_embed)\n        recon = self.decoder(global_embed).view(-1,1,self.patch_size,self.patch_size)\n        return patch_embed, global_embed, recon\n\n# ======================\n# 8. Training Loop\n# ======================\ndevice = 'cuda' if torch.cuda.is_available() else 'cpu'\nmodel = PGECA().to(device)\noptimizer = torch.optim.Adam(model.parameters(), lr=1e-3)\nepochs = 5\n\nfor epoch in range(epochs):\n    total_loss = 0\n    for imgs,_ in loader:\n        imgs = imgs.to(device)\n        patch_embed, global_embed, recon = model(imgs)\n        \n        loss_edge = edge_contrastive_loss(patch_embed)\n        target = split_patches(imgs)[:,:,0,:,:].mean(dim=1, keepdim=True)\n        loss_recon = F.mse_loss(recon, target)\n        loss = loss_edge + 0.1*loss_recon\n        \n        optimizer.zero_grad()\n        loss.backward()\n        optimizer.step()\n        total_loss += loss.item()\n    print(f\"Epoch {epoch+1}/{epochs}, Loss: {total_loss/len(loader):.4f}\")\n\n# ======================\n# 9. Extract Embeddings\n# ======================\nembeddings_list = []\nwith torch.no_grad():\n    for imgs,_ in loader:\n        imgs = imgs.to(device)\n        _, global_embed, _ = model(imgs)\n        embeddings_list.append(global_embed.cpu())\nembeddings_all = torch.cat(embeddings_list).numpy()\n\n# ======================\n# 10. Clustering + Evaluation\n# ======================\nnum_clusters = min(10, len(dataset))\nkmeans = KMeans(n_clusters=num_clusters, random_state=42)\npreds = kmeans.fit_predict(embeddings_all)\nsil_score = silhouette_score(embeddings_all, preds)\nprint(\"Silhouette Score:\", sil_score)\n\n# ======================\n# 11. Visualization\n# ======================\nplt.figure(figsize=(6,3))\nfor i in range(min(6, len(dataset))):\n    plt.subplot(2,6,i+1)\n    img,_ = dataset[i]\n    plt.imshow(img[0], cmap='gray')\n    plt.axis('off')\n    \n    plt.subplot(2,6,i+7)\n    with torch.no_grad():\n        recon_patch = model(img.unsqueeze(0).to(device))[2][0,0].cpu().numpy()\n    plt.imshow(recon_patch, cmap='gray')\n    plt.axis('off')\nplt.show()\n\n# PCA Embedding Visualization\npca = PCA(n_components=2)\nemb_2d = pca.fit_transform(embeddings_all)\nplt.figure(figsize=(6,6))\nplt.scatter(emb_2d[:,0], emb_2d[:,1], c=preds, cmap='tab10', s=5)\nplt.title(\"Global Embeddings (PCA 2D)\")\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-18T19:11:07.842216Z","iopub.execute_input":"2025-08-18T19:11:07.84257Z"}},"outputs":[],"execution_count":null}]}